@esso0428/pi-subagents 0.15.5 → 0.15.7
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 +10 -19
- package/README.md +5 -5
- package/dist/agent-history-list.d.ts +19 -0
- package/dist/agent-history-list.d.ts.map +1 -0
- package/dist/agent-history-list.js +69 -0
- package/dist/agent-history-list.js.map +1 -0
- package/dist/agent-history.d.ts +2 -1
- package/dist/agent-history.d.ts.map +1 -0
- package/dist/agent-history.js +32 -34
- package/dist/agent-history.js.map +1 -0
- package/dist/agent-manager.d.ts +24 -8
- package/dist/agent-manager.d.ts.map +1 -0
- package/dist/agent-manager.js +171 -93
- package/dist/agent-manager.js.map +1 -0
- package/dist/agent-runner.d.ts +1 -0
- package/dist/agent-runner.d.ts.map +1 -0
- package/dist/agent-runner.js +75 -86
- package/dist/agent-runner.js.map +1 -0
- package/dist/agent-types.d.ts +1 -0
- package/dist/agent-types.d.ts.map +1 -0
- package/dist/agent-types.js +28 -45
- package/dist/agent-types.js.map +1 -0
- package/dist/context.d.ts +1 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +3 -6
- package/dist/context.js.map +1 -0
- package/dist/cross-extension-rpc.d.ts +1 -0
- package/dist/cross-extension-rpc.d.ts.map +1 -0
- package/dist/cross-extension-rpc.js +6 -9
- package/dist/cross-extension-rpc.js.map +1 -0
- package/dist/custom-agents.d.ts +1 -0
- package/dist/custom-agents.d.ts.map +1 -0
- package/dist/custom-agents.js +16 -18
- package/dist/custom-agents.js.map +1 -0
- package/dist/default-agents.d.ts +1 -0
- package/dist/default-agents.d.ts.map +1 -0
- package/dist/default-agents.js +2 -4
- package/dist/default-agents.js.map +1 -0
- package/dist/enabled-models.d.ts +1 -0
- package/dist/enabled-models.d.ts.map +1 -0
- package/dist/enabled-models.js +12 -16
- package/dist/enabled-models.js.map +1 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +2 -4
- package/dist/env.js.map +1 -0
- package/dist/group-join.d.ts +1 -0
- package/dist/group-join.d.ts.map +1 -0
- package/dist/group-join.js +2 -5
- package/dist/group-join.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +306 -292
- package/dist/index.js.map +1 -0
- package/dist/invocation-config.d.ts +1 -0
- package/dist/invocation-config.d.ts.map +1 -0
- package/dist/invocation-config.js +3 -6
- package/dist/invocation-config.js.map +1 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +25 -34
- package/dist/memory.js.map +1 -0
- package/dist/model-resolver.d.ts +1 -0
- package/dist/model-resolver.d.ts.map +1 -0
- package/dist/model-resolver.js +2 -4
- package/dist/model-resolver.js.map +1 -0
- package/dist/nico-overrides.d.ts +1 -0
- package/dist/nico-overrides.d.ts.map +1 -0
- package/dist/nico-overrides.js +15 -20
- package/dist/nico-overrides.js.map +1 -0
- package/dist/output-file.d.ts +1 -0
- package/dist/output-file.d.ts.map +1 -0
- package/dist/output-file.js +17 -22
- package/dist/output-file.js.map +1 -0
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +2 -4
- package/dist/prompts.js.map +1 -0
- package/dist/schedule-store.d.ts +1 -0
- package/dist/schedule-store.d.ts.map +1 -0
- package/dist/schedule-store.js +17 -21
- package/dist/schedule-store.js.map +1 -0
- package/dist/schedule.d.ts +1 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +9 -12
- package/dist/schedule.js.map +1 -0
- package/dist/settings.d.ts +1 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +16 -23
- package/dist/settings.js.map +1 -0
- package/dist/skill-loader.d.ts +1 -0
- package/dist/skill-loader.d.ts.map +1 -0
- package/dist/skill-loader.js +21 -23
- package/dist/skill-loader.js.map +1 -0
- package/dist/status-note.d.ts +1 -0
- package/dist/status-note.d.ts.map +1 -0
- package/dist/status-note.js +2 -4
- package/dist/status-note.js.map +1 -0
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -4
- package/dist/types.js.map +1 -0
- package/dist/ui/agent-widget.d.ts +2 -5
- package/dist/ui/agent-widget.d.ts.map +1 -0
- package/dist/ui/agent-widget.js +29 -51
- package/dist/ui/agent-widget.js.map +1 -0
- package/dist/ui/ccstyle/diff/ansi-utils.d.ts +11 -0
- package/dist/ui/ccstyle/diff/ansi-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/ansi-utils.js +145 -0
- package/dist/ui/ccstyle/diff/ansi-utils.js.map +1 -0
- package/dist/ui/ccstyle/diff/diff-presentation.d.ts +12 -0
- package/dist/ui/ccstyle/diff/diff-presentation.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/diff-presentation.js +49 -0
- package/dist/ui/ccstyle/diff/diff-presentation.js.map +1 -0
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts +46 -0
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/diff-renderer.js +2044 -0
- package/dist/ui/ccstyle/diff/diff-renderer.js.map +1 -0
- package/dist/ui/ccstyle/diff/line-width-safety.d.ts +12 -0
- package/dist/ui/ccstyle/diff/line-width-safety.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/line-width-safety.js +59 -0
- package/dist/ui/ccstyle/diff/line-width-safety.js.map +1 -0
- package/dist/ui/ccstyle/diff/render-utils.d.ts +6 -0
- package/dist/ui/ccstyle/diff/render-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/render-utils.js +25 -0
- package/dist/ui/ccstyle/diff/render-utils.js.map +1 -0
- package/dist/ui/ccstyle/diff/types.d.ts +22 -0
- package/dist/ui/ccstyle/diff/types.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/types.js +10 -0
- package/dist/ui/ccstyle/diff/types.js.map +1 -0
- package/dist/ui/ccstyle/diff/write-display-utils.d.ts +2 -0
- package/dist/ui/ccstyle/diff/write-display-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/write-display-utils.js +12 -0
- package/dist/ui/ccstyle/diff/write-display-utils.js.map +1 -0
- package/dist/ui/ccstyle/tool-renderer.d.ts +17 -0
- package/dist/ui/ccstyle/tool-renderer.d.ts.map +1 -0
- package/dist/ui/ccstyle/tool-renderer.js +58 -0
- package/dist/ui/ccstyle/tool-renderer.js.map +1 -0
- package/dist/ui/ccstyle/tool-result.d.ts +24 -0
- package/dist/ui/ccstyle/tool-result.d.ts.map +1 -0
- package/dist/ui/ccstyle/tool-result.js +193 -0
- package/dist/ui/ccstyle/tool-result.js.map +1 -0
- package/dist/ui/conversation-blocks.d.ts +46 -0
- package/dist/ui/conversation-blocks.d.ts.map +1 -0
- package/dist/ui/conversation-blocks.js +306 -0
- package/dist/ui/conversation-blocks.js.map +1 -0
- package/dist/ui/conversation-nvim.d.ts +8 -0
- package/dist/ui/conversation-nvim.d.ts.map +1 -0
- package/dist/ui/conversation-nvim.js +117 -0
- package/dist/ui/conversation-nvim.js.map +1 -0
- package/dist/ui/conversation-role.d.ts +13 -0
- package/dist/ui/conversation-role.d.ts.map +1 -0
- package/dist/ui/conversation-role.js +39 -0
- package/dist/ui/conversation-role.js.map +1 -0
- package/dist/ui/conversation-search.d.ts +39 -0
- package/dist/ui/conversation-search.d.ts.map +1 -0
- package/dist/ui/conversation-search.js +124 -0
- package/dist/ui/conversation-search.js.map +1 -0
- package/dist/ui/conversation-timeline.d.ts +62 -0
- package/dist/ui/conversation-timeline.d.ts.map +1 -0
- package/dist/ui/conversation-timeline.js +263 -0
- package/dist/ui/conversation-timeline.js.map +1 -0
- package/dist/ui/conversation-viewer.d.ts +62 -20
- package/dist/ui/conversation-viewer.d.ts.map +1 -0
- package/dist/ui/conversation-viewer.js +449 -224
- package/dist/ui/conversation-viewer.js.map +1 -0
- package/dist/ui/fleet-list.d.ts +24 -11
- package/dist/ui/fleet-list.d.ts.map +1 -0
- package/dist/ui/fleet-list.js +86 -78
- package/dist/ui/fleet-list.js.map +1 -0
- package/dist/ui/schedule-menu.d.ts +1 -0
- package/dist/ui/schedule-menu.d.ts.map +1 -0
- package/dist/ui/schedule-menu.js +2 -4
- package/dist/ui/schedule-menu.js.map +1 -0
- package/dist/ui/viewer-keys.d.ts +1 -0
- package/dist/ui/viewer-keys.d.ts.map +1 -0
- package/dist/ui/viewer-keys.js +8 -10
- package/dist/ui/viewer-keys.js.map +1 -0
- package/dist/usage.d.ts +1 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +5 -10
- package/dist/usage.js.map +1 -0
- package/dist/worktree.d.ts +1 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +27 -31
- package/dist/worktree.js.map +1 -0
- package/dist/write-execution.d.ts +42 -0
- package/dist/write-execution.d.ts.map +1 -0
- package/dist/write-execution.js +138 -0
- package/dist/write-execution.js.map +1 -0
- package/package.json +1 -1
- package/src/agent-history-list.ts +91 -0
- package/src/agent-history.ts +12 -17
- package/src/agent-manager.ts +169 -81
- package/src/agent-runner.ts +12 -13
- package/src/index.ts +117 -98
- package/src/output-file.ts +1 -1
- package/src/types.ts +4 -1
- package/src/ui/agent-widget.ts +3 -9
- package/src/ui/ccstyle/ATTRIBUTION.md +37 -0
- package/src/ui/ccstyle/diff/ansi-utils.ts +168 -0
- package/src/ui/ccstyle/diff/diff-presentation.ts +70 -0
- package/src/ui/ccstyle/diff/diff-renderer.ts +3148 -0
- package/src/ui/ccstyle/diff/line-width-safety.ts +92 -0
- package/src/ui/ccstyle/diff/render-utils.ts +32 -0
- package/src/ui/ccstyle/diff/types.ts +33 -0
- package/src/ui/ccstyle/diff/write-display-utils.ts +12 -0
- package/src/ui/ccstyle/tool-renderer.ts +71 -0
- package/src/ui/ccstyle/tool-result.ts +238 -0
- package/src/ui/conversation-blocks.ts +404 -0
- package/src/ui/conversation-nvim.ts +127 -0
- package/src/ui/conversation-role.ts +45 -0
- package/src/ui/conversation-search.ts +161 -0
- package/src/ui/conversation-timeline.ts +315 -0
- package/src/ui/conversation-viewer.ts +418 -210
- package/src/ui/fleet-list.ts +73 -52
- package/src/write-execution.ts +180 -0
- package/test/agent-history-list.test.ts +121 -0
- package/test/agent-manager-history.test.ts +59 -58
- package/test/agent-menu-history-navigation.test.ts +86 -0
- package/test/output-file-history.test.ts +50 -0
- package/test/ui/conversation-blocks.test.ts +103 -0
- package/test/ui/conversation-diff.test.ts +91 -0
- package/test/ui/conversation-search.test.ts +55 -0
- package/test/ui/conversation-viewer.test.ts +182 -0
- package/test/ui/fleet-list.test.ts +131 -0
- package/test/write-execution.test.ts +83 -0
- package/tsconfig.json +16 -0
- package/vitest.temp.config.mts +6 -0
- package/dist/ui/markdown-result.d.ts +0 -3
- package/dist/ui/markdown-result.js +0 -53
- package/src/ui/markdown-result.ts +0 -56
- package/test/agent-history.test.ts +0 -63
- package/test/ui/markdown-result.test.ts +0 -45
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* pi-agents — A pi extension providing Claude Code-style autonomous sub-agents.
|
|
4
3
|
*
|
|
@@ -10,34 +9,31 @@
|
|
|
10
9
|
* Commands:
|
|
11
10
|
* /agents — Interactive agent management menu
|
|
12
11
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const markdown_result_js_1 = require("./ui/markdown-result.js");
|
|
39
|
-
const schedule_menu_js_1 = require("./ui/schedule-menu.js");
|
|
40
|
-
const usage_js_1 = require("./usage.js");
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { defineTool, getAgentDir, getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { Container, Key, matchesKey, SettingsList, Spacer, Text } from "@earendil-works/pi-tui";
|
|
16
|
+
import { Type } from "@sinclair/typebox";
|
|
17
|
+
import { AgentManager } from "./agent-manager.js";
|
|
18
|
+
import { getAgentConversation, getDefaultMaxTurns, getGraceTurns, normalizeMaxTurns, SUBAGENT_TOOL_NAMES, setDefaultMaxTurns, setGraceTurns, steerAgent } from "./agent-runner.js";
|
|
19
|
+
import { applyNicoOverrides, BUILTIN_TOOL_NAMES, getAgentConfig, getAllTypes, getAvailableTypes, isDefaultsDisabled, registerAgents, resolveType, setDefaultsDisabled } from "./agent-types.js";
|
|
20
|
+
import { registerRpcHandlers } from "./cross-extension-rpc.js";
|
|
21
|
+
import { loadCustomAgents } from "./custom-agents.js";
|
|
22
|
+
import { isModelInScope, readEnabledModels, resolveEnabledModels } from "./enabled-models.js";
|
|
23
|
+
import { GroupJoinManager } from "./group-join.js";
|
|
24
|
+
import { resolveAgentInvocationConfig, resolveJoinMode } from "./invocation-config.js";
|
|
25
|
+
import { resolveModel } from "./model-resolver.js";
|
|
26
|
+
import { agentHistoryLocator, createAgentHistoryPath, readAgentHistory } from "./agent-history.js";
|
|
27
|
+
import { buildAgentStatusMenuEntries, canOpenActiveAgent, canOpenAgentHistory, formatAgentHistoryOption, splitAgentRecords } from "./agent-history-list.js";
|
|
28
|
+
import { createOutputFilePath, streamToOutputFile, writeInitialEntry } from "./output-file.js";
|
|
29
|
+
import { SubagentScheduler } from "./schedule.js";
|
|
30
|
+
import { resolveStorePath, ScheduleStore } from "./schedule-store.js";
|
|
31
|
+
import { applyAndEmitLoaded, saveAndEmitChanged } from "./settings.js";
|
|
32
|
+
import { getStatusNote } from "./status-note.js";
|
|
33
|
+
import { AgentWidget, buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatDuration, formatMs, formatTokens, formatTurns, getDisplayName, getPromptModeLabel, SPINNER, } from "./ui/agent-widget.js";
|
|
34
|
+
import { FleetList } from "./ui/fleet-list.js";
|
|
35
|
+
import { showSchedulesMenu } from "./ui/schedule-menu.js";
|
|
36
|
+
import { addUsage, getLifetimeTotal, getSessionContextPercent } from "./usage.js";
|
|
41
37
|
// ---- Shared helpers ----
|
|
42
38
|
/** Tool execute return value for a text response. */
|
|
43
39
|
function textResult(msg, details) {
|
|
@@ -75,16 +71,16 @@ function abortable(promise, signal) {
|
|
|
75
71
|
});
|
|
76
72
|
});
|
|
77
73
|
}
|
|
78
|
-
function renderRunningAgentStatus(frame, statsText, activity, theme) {
|
|
79
|
-
const container = new
|
|
80
|
-
container.addChild(new
|
|
81
|
-
container.addChild(new
|
|
74
|
+
export function renderRunningAgentStatus(frame, statsText, activity, theme) {
|
|
75
|
+
const container = new Container();
|
|
76
|
+
container.addChild(new Text(theme.fg("accent", frame) + (statsText ? " " + statsText : ""), 0, 0));
|
|
77
|
+
container.addChild(new Text(theme.fg("dim", ` ⎿ ${activity}`), 0, 0));
|
|
82
78
|
return container;
|
|
83
79
|
}
|
|
84
80
|
/** Format an agent's lifetime token total, or "" when zero. */
|
|
85
81
|
function formatLifetimeTokens(o) {
|
|
86
|
-
const t =
|
|
87
|
-
return t > 0 ?
|
|
82
|
+
const t = getLifetimeTotal(o.lifetimeUsage);
|
|
83
|
+
return t > 0 ? formatTokens(t) : "";
|
|
88
84
|
}
|
|
89
85
|
/**
|
|
90
86
|
* Create an AgentActivity state and spawn callbacks for tracking tool usage.
|
|
@@ -128,7 +124,7 @@ function createActivityTracker(maxTurns, onStreamUpdate) {
|
|
|
128
124
|
state.session = session;
|
|
129
125
|
},
|
|
130
126
|
onAssistantUsage: (usage) => {
|
|
131
|
-
|
|
127
|
+
addUsage(state.lifetimeUsage, usage);
|
|
132
128
|
onStreamUpdate?.();
|
|
133
129
|
},
|
|
134
130
|
};
|
|
@@ -169,8 +165,8 @@ function escapeXml(s) {
|
|
|
169
165
|
function formatTaskNotification(record, resultMaxLen) {
|
|
170
166
|
const status = getStatusLabel(record.status, record.error);
|
|
171
167
|
const durationMs = record.completedAt ? record.completedAt - record.startedAt : 0;
|
|
172
|
-
const totalTokens =
|
|
173
|
-
const contextPercent =
|
|
168
|
+
const totalTokens = getLifetimeTotal(record.lifetimeUsage);
|
|
169
|
+
const contextPercent = getSessionContextPercent(record.session);
|
|
174
170
|
const ctxXml = contextPercent !== null ? `<context_percent>${Math.round(contextPercent)}</context_percent>` : "";
|
|
175
171
|
const compactXml = record.compactionCount ? `<compactions>${record.compactionCount}</compactions>` : "";
|
|
176
172
|
const resultPreview = record.result
|
|
@@ -184,7 +180,7 @@ function formatTaskNotification(record, resultMaxLen) {
|
|
|
184
180
|
record.toolCallId ? `<tool-use-id>${escapeXml(record.toolCallId)}</tool-use-id>` : null,
|
|
185
181
|
record.outputFile ? `<output-file>${escapeXml(record.outputFile)}</output-file>` : null,
|
|
186
182
|
`<status>${escapeXml(status)}</status>`,
|
|
187
|
-
`<summary>Agent "${escapeXml(record.description)}" ${record.status}${
|
|
183
|
+
`<summary>Agent "${escapeXml(record.description)}" ${record.status}${getStatusNote(record.status)}</summary>`,
|
|
188
184
|
`<result>${escapeXml(resultPreview)}</result>`,
|
|
189
185
|
`<usage><total_tokens>${totalTokens}</total_tokens><tool_uses>${record.toolUses}</tool_uses>${ctxXml}${compactXml}<duration_ms>${durationMs}</duration_ms></usage>`,
|
|
190
186
|
`</task-notification>`,
|
|
@@ -205,11 +201,9 @@ function buildDetails(base, record, activity, overrides) {
|
|
|
205
201
|
...overrides,
|
|
206
202
|
};
|
|
207
203
|
}
|
|
208
|
-
/** Maximum expanded notification body retained in message details. */
|
|
209
|
-
const MAX_NOTIFICATION_RESULT_TEXT_LENGTH = 20_000;
|
|
210
204
|
/** Build notification details for the custom message renderer. */
|
|
211
205
|
function buildNotificationDetails(record, resultMaxLen, activity) {
|
|
212
|
-
const totalTokens =
|
|
206
|
+
const totalTokens = getLifetimeTotal(record.lifetimeUsage);
|
|
213
207
|
return {
|
|
214
208
|
id: record.id,
|
|
215
209
|
description: record.description,
|
|
@@ -221,11 +215,6 @@ function buildNotificationDetails(record, resultMaxLen, activity) {
|
|
|
221
215
|
durationMs: record.completedAt ? record.completedAt - record.startedAt : 0,
|
|
222
216
|
outputFile: record.outputFile,
|
|
223
217
|
error: record.error,
|
|
224
|
-
resultText: record.result
|
|
225
|
-
? record.result.length > MAX_NOTIFICATION_RESULT_TEXT_LENGTH
|
|
226
|
-
? record.result.slice(0, MAX_NOTIFICATION_RESULT_TEXT_LENGTH) + "…"
|
|
227
|
-
: record.result
|
|
228
|
-
: undefined,
|
|
229
218
|
resultPreview: record.result
|
|
230
219
|
? record.result.length > resultMaxLen
|
|
231
220
|
? record.result.slice(0, resultMaxLen) + "…"
|
|
@@ -233,7 +222,7 @@ function buildNotificationDetails(record, resultMaxLen, activity) {
|
|
|
233
222
|
: "No output.",
|
|
234
223
|
};
|
|
235
224
|
}
|
|
236
|
-
function
|
|
225
|
+
export default function (pi) {
|
|
237
226
|
// ---- Register custom notification renderer ----
|
|
238
227
|
pi.registerMessageRenderer("subagent-notification", (message, { expanded }, theme) => {
|
|
239
228
|
const d = message.details;
|
|
@@ -250,49 +239,40 @@ function default_1(pi) {
|
|
|
250
239
|
// Line 2: stats
|
|
251
240
|
const parts = [];
|
|
252
241
|
if (d.turnCount > 0)
|
|
253
|
-
parts.push(
|
|
242
|
+
parts.push(formatTurns(d.turnCount, d.maxTurns));
|
|
254
243
|
if (d.toolUses > 0)
|
|
255
244
|
parts.push(`${d.toolUses} tool use${d.toolUses === 1 ? "" : "s"}`);
|
|
256
245
|
if (d.totalTokens > 0)
|
|
257
|
-
parts.push(
|
|
246
|
+
parts.push(formatTokens(d.totalTokens));
|
|
258
247
|
if (d.durationMs > 0)
|
|
259
|
-
parts.push(
|
|
248
|
+
parts.push(formatMs(d.durationMs));
|
|
260
249
|
if (parts.length) {
|
|
261
250
|
line += "\n " + parts.map(p => theme.fg("dim", p)).join(" " + theme.fg("dim", "·") + " ");
|
|
262
251
|
}
|
|
263
|
-
|
|
264
|
-
|
|
252
|
+
// Line 3: result preview (collapsed) or full (expanded)
|
|
253
|
+
if (expanded) {
|
|
254
|
+
const lines = d.resultPreview.split("\n").slice(0, 30);
|
|
255
|
+
for (const l of lines)
|
|
256
|
+
line += "\n" + theme.fg("dim", ` ${l}`);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
265
259
|
const preview = d.resultPreview.split("\n")[0]?.slice(0, 80) ?? "";
|
|
266
260
|
line += "\n " + theme.fg("dim", `⎿ ${preview}`);
|
|
267
|
-
if (d.outputFile) {
|
|
268
|
-
line += "\n " + theme.fg("muted", `transcript: ${d.outputFile}`);
|
|
269
|
-
}
|
|
270
|
-
return line;
|
|
271
261
|
}
|
|
272
|
-
|
|
273
|
-
container.addChild(new pi_tui_1.Text(line, 0, 0));
|
|
274
|
-
container.addChild((0, markdown_result_js_1.createMarkdownResult)(d.resultText ?? d.resultPreview, theme, 30));
|
|
262
|
+
// Line 4: output file link (if present)
|
|
275
263
|
if (d.outputFile) {
|
|
276
|
-
|
|
264
|
+
line += "\n " + theme.fg("muted", `transcript: ${d.outputFile}`);
|
|
277
265
|
}
|
|
278
|
-
return
|
|
266
|
+
return line;
|
|
279
267
|
}
|
|
280
268
|
const all = [d, ...(d.others ?? [])];
|
|
281
|
-
|
|
282
|
-
return new pi_tui_1.Text(all.map(renderOne).join("\n"), 0, 0);
|
|
283
|
-
const container = new pi_tui_1.Container();
|
|
284
|
-
all.forEach((item, index) => {
|
|
285
|
-
if (index > 0)
|
|
286
|
-
container.addChild(new pi_tui_1.Spacer(1));
|
|
287
|
-
container.addChild(renderOne(item));
|
|
288
|
-
});
|
|
289
|
-
return container;
|
|
269
|
+
return new Text(all.map(renderOne).join("\n"), 0, 0);
|
|
290
270
|
});
|
|
291
271
|
/** Reload agents from project/global custom agent dirs and merge with defaults (called on init and each Agent invocation). */
|
|
292
272
|
const reloadCustomAgents = () => {
|
|
293
|
-
const userAgents =
|
|
294
|
-
|
|
295
|
-
|
|
273
|
+
const userAgents = loadCustomAgents(process.cwd());
|
|
274
|
+
registerAgents(userAgents);
|
|
275
|
+
applyNicoOverrides();
|
|
296
276
|
};
|
|
297
277
|
// Initial load
|
|
298
278
|
reloadCustomAgents();
|
|
@@ -344,7 +324,7 @@ function default_1(pi) {
|
|
|
344
324
|
widget.update();
|
|
345
325
|
}
|
|
346
326
|
// ---- Group join manager ----
|
|
347
|
-
const groupJoin = new
|
|
327
|
+
const groupJoin = new GroupJoinManager((records, partial) => {
|
|
348
328
|
for (const r of records) {
|
|
349
329
|
agentActivity.delete(r.id);
|
|
350
330
|
widget.markFinished(r.id);
|
|
@@ -384,7 +364,7 @@ function default_1(pi) {
|
|
|
384
364
|
// tokens is omitted when nothing was ever produced (e.g. agent errored before
|
|
385
365
|
// any message_end fired), preserving prior payload shape.
|
|
386
366
|
const u = record.lifetimeUsage;
|
|
387
|
-
const total =
|
|
367
|
+
const total = getLifetimeTotal(u);
|
|
388
368
|
const tokens = total > 0
|
|
389
369
|
? { input: u.input, output: u.output, total }
|
|
390
370
|
: undefined;
|
|
@@ -401,7 +381,7 @@ function default_1(pi) {
|
|
|
401
381
|
};
|
|
402
382
|
}
|
|
403
383
|
// Background completion: route through group join or send individual nudge
|
|
404
|
-
const manager = new
|
|
384
|
+
const manager = new AgentManager((record) => {
|
|
405
385
|
// Emit lifecycle event based on terminal status
|
|
406
386
|
const isError = record.status === "error" || record.status === "stopped" || record.status === "aborted";
|
|
407
387
|
const eventData = buildEventData(record);
|
|
@@ -411,13 +391,14 @@ function default_1(pi) {
|
|
|
411
391
|
else {
|
|
412
392
|
pi.events.emit("subagents:completed", eventData);
|
|
413
393
|
}
|
|
414
|
-
// Persist final record for cross-extension history reconstruction
|
|
415
|
-
// the transcript body in a project-local file rather than inflating the
|
|
416
|
-
// parent session JSONL; only its project-relative locator is persisted.
|
|
394
|
+
// Persist final record for cross-extension history reconstruction
|
|
417
395
|
pi.appendEntry("subagents:record", {
|
|
418
396
|
id: record.id, type: record.type, description: record.description,
|
|
419
397
|
status: record.status, result: record.result, error: record.error,
|
|
420
398
|
startedAt: record.startedAt, completedAt: record.completedAt,
|
|
399
|
+
toolUses: record.toolUses,
|
|
400
|
+
lifetimeUsage: record.lifetimeUsage,
|
|
401
|
+
invocation: record.invocation,
|
|
421
402
|
transcriptPath: record.transcriptPath,
|
|
422
403
|
});
|
|
423
404
|
// Skip notification if result was already consumed via get_subagent_result
|
|
@@ -493,14 +474,14 @@ function default_1(pi) {
|
|
|
493
474
|
// Session-scoped: store is constructed inside session_start once sessionId
|
|
494
475
|
// is available. Mirrors pi-chonky-tasks's session-scoped task store —
|
|
495
476
|
// schedules reset on /new, restore on /resume.
|
|
496
|
-
const scheduler = new
|
|
477
|
+
const scheduler = new SubagentScheduler();
|
|
497
478
|
function startScheduler(ctx) {
|
|
498
479
|
try {
|
|
499
480
|
const sessionId = ctx.sessionManager?.getSessionId?.();
|
|
500
481
|
if (!sessionId)
|
|
501
482
|
return; // sessionId not yet available — try again on next event
|
|
502
|
-
const path =
|
|
503
|
-
const store = new
|
|
483
|
+
const path = resolveStorePath(ctx.cwd, sessionId);
|
|
484
|
+
const store = new ScheduleStore(path);
|
|
504
485
|
scheduler.start(pi, ctx, manager, store);
|
|
505
486
|
pi.events.emit("subagents:scheduler_ready", { sessionId, jobCount: store.list().length });
|
|
506
487
|
}
|
|
@@ -515,27 +496,15 @@ function default_1(pi) {
|
|
|
515
496
|
// bound session_start, so a filtered-out activation never advertises (#142).
|
|
516
497
|
pi.on("session_start", async (_event, ctx) => {
|
|
517
498
|
currentCtx = ctx;
|
|
518
|
-
try {
|
|
519
|
-
(0, agent_history_js_1.ensureSubagentsGitignore)(ctx.cwd);
|
|
520
|
-
}
|
|
521
|
-
catch (err) {
|
|
522
|
-
console.warn("[pi-subagents] Failed to protect project-local history:", err);
|
|
523
|
-
}
|
|
524
499
|
manager.clearCompleted(true);
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
.filter((entry) => entry?.type === "custom" && entry
|
|
528
|
-
.map((entry) => entry.data);
|
|
529
|
-
manager.restoreCompleted(historicalRecords);
|
|
530
|
-
// Bind the UI here as well as on tool execution so rehydrated terminal
|
|
531
|
-
// records are immediately visible and openable from FleetView after reload.
|
|
532
|
-
widget.setUICtx(ctx.ui);
|
|
533
|
-
fleet.setUICtx(ctx.ui);
|
|
534
|
-
fleet.update();
|
|
500
|
+
const branch = ctx.sessionManager?.getBranch?.() ?? [];
|
|
501
|
+
manager.restoreCompleted(branch
|
|
502
|
+
.filter((entry) => entry?.type === "custom" && entry?.customType === "subagents:record")
|
|
503
|
+
.map((entry) => entry.data));
|
|
535
504
|
// Guard mirrors the `!scheduler.isActive()` pattern below: session_start
|
|
536
505
|
// fires once per activation, but a double-bind must not leak listeners.
|
|
537
506
|
if (!rpcHandle) {
|
|
538
|
-
rpcHandle =
|
|
507
|
+
rpcHandle = registerRpcHandlers({
|
|
539
508
|
events: pi.events,
|
|
540
509
|
pi,
|
|
541
510
|
getCtx: () => currentCtx,
|
|
@@ -581,10 +550,10 @@ function default_1(pi) {
|
|
|
581
550
|
// everything else; "off" = hide the widget entirely. Read live at render time.
|
|
582
551
|
let widgetMode = "background";
|
|
583
552
|
function getWidgetMode() { return widgetMode; }
|
|
584
|
-
const widget = new
|
|
553
|
+
const widget = new AgentWidget(manager, agentActivity, getWidgetMode);
|
|
585
554
|
function setWidgetMode(m) { widgetMode = m; widget.update(); }
|
|
586
555
|
// Claude Code-style FleetView: navigable list of main + subagents below the editor.
|
|
587
|
-
const fleet = new
|
|
556
|
+
const fleet = new FleetList(manager, agentActivity, () => currentCtx?.cwd, pi, () => currentCtx);
|
|
588
557
|
let fleetViewEnabled = true;
|
|
589
558
|
function isFleetViewEnabled() { return fleetViewEnabled; }
|
|
590
559
|
function setFleetViewEnabled(b) { fleetViewEnabled = b; fleet.setEnabled(b); }
|
|
@@ -625,7 +594,7 @@ function default_1(pi) {
|
|
|
625
594
|
// State lives in agent-types.ts (isDefaultsDisabled) because registerAgents
|
|
626
595
|
// needs it; this wrapper just re-registers after flipping it.
|
|
627
596
|
function setDisableDefaultAgents(b) {
|
|
628
|
-
|
|
597
|
+
setDefaultsDisabled(b);
|
|
629
598
|
reloadCustomAgents(); // re-register with new setting
|
|
630
599
|
}
|
|
631
600
|
// ---- Agent tool description mode ----
|
|
@@ -688,15 +657,15 @@ function default_1(pi) {
|
|
|
688
657
|
const tools = cfg?.builtinToolNames;
|
|
689
658
|
if (!tools || tools.length === 0)
|
|
690
659
|
return "*";
|
|
691
|
-
const isFullSet = tools.length ===
|
|
692
|
-
&&
|
|
660
|
+
const isFullSet = tools.length === BUILTIN_TOOL_NAMES.length
|
|
661
|
+
&& BUILTIN_TOOL_NAMES.every((t) => tools.includes(t));
|
|
693
662
|
return isFullSet ? "*" : tools.join(", ");
|
|
694
663
|
};
|
|
695
664
|
/** Build the full type list text dynamically from available agents only. */
|
|
696
665
|
const buildTypeListText = () => {
|
|
697
|
-
const available =
|
|
666
|
+
const available = getAvailableTypes();
|
|
698
667
|
return available.map((name) => {
|
|
699
|
-
const cfg =
|
|
668
|
+
const cfg = getAgentConfig(name);
|
|
700
669
|
const modelSuffix = cfg?.model ? ` (${getModelLabelFromConfig(cfg.model)})` : "";
|
|
701
670
|
const toolsSuffix = ` (Tools: ${formatToolsSuffix(cfg)})`;
|
|
702
671
|
return `- ${name}: ${cfg?.description ?? name}${modelSuffix}${toolsSuffix}`;
|
|
@@ -708,8 +677,8 @@ function default_1(pi) {
|
|
|
708
677
|
return (match ? match[0] : text).replace(/\s+/g, " ").trim();
|
|
709
678
|
};
|
|
710
679
|
/** Compact type list: one line per agent, first sentence only. */
|
|
711
|
-
const buildCompactTypeListText = () =>
|
|
712
|
-
const cfg =
|
|
680
|
+
const buildCompactTypeListText = () => getAvailableTypes().map((name) => {
|
|
681
|
+
const cfg = getAgentConfig(name);
|
|
713
682
|
return `- ${name}: ${firstSentence(cfg?.description ?? name)} (Tools: ${formatToolsSuffix(cfg)})`;
|
|
714
683
|
}).join("\n");
|
|
715
684
|
/** Derive a short model label from a model string. */
|
|
@@ -722,10 +691,10 @@ function default_1(pi) {
|
|
|
722
691
|
// Apply persisted settings on startup and emit `subagents:settings_loaded`.
|
|
723
692
|
// Global + project merged; missing → defaults; corrupt file emits a warning
|
|
724
693
|
// to stderr and falls back to defaults.
|
|
725
|
-
|
|
694
|
+
applyAndEmitLoaded({
|
|
726
695
|
setMaxConcurrent: (n) => manager.setMaxConcurrent(n),
|
|
727
|
-
setDefaultMaxTurns
|
|
728
|
-
setGraceTurns
|
|
696
|
+
setDefaultMaxTurns,
|
|
697
|
+
setGraceTurns,
|
|
729
698
|
setDefaultJoinMode,
|
|
730
699
|
setSchedulingEnabled,
|
|
731
700
|
setScopeModels: setScopeModelsEnabled,
|
|
@@ -742,7 +711,7 @@ function default_1(pi) {
|
|
|
742
711
|
// preserves Type.Object's inference when present and produces a
|
|
743
712
|
// `schedule`-free schema when absent — zero LLM-context cost in disabled mode.
|
|
744
713
|
const scheduleParamShape = {
|
|
745
|
-
schedule:
|
|
714
|
+
schedule: Type.Optional(Type.String({
|
|
746
715
|
description: 'Opt-in only — fire later instead of now. Omit to run immediately (the default, almost always correct). ' +
|
|
747
716
|
'Formats: 6-field cron ("0 0 9 * * 1" = 9am Mon), interval ("5m"/"1h"), one-shot ("+10m" or ISO). ' +
|
|
748
717
|
'Forces run_in_background; incompatible with inherit_context and resume. Returns job ID.',
|
|
@@ -758,7 +727,7 @@ function default_1(pi) {
|
|
|
758
727
|
const compactAgentToolDescription = `Launch an autonomous agent for complex, multi-step tasks. Agent types:
|
|
759
728
|
${buildCompactTypeListText()}
|
|
760
729
|
|
|
761
|
-
Custom agents: .pi/agents/<name>.md (project) or ${
|
|
730
|
+
Custom agents: .pi/agents/<name>.md (project) or ${getAgentDir()}/agents/<name>.md (global).
|
|
762
731
|
|
|
763
732
|
Notes:
|
|
764
733
|
- description: 3-5 words (shown in UI). Prompts must be self-contained — the agent has not seen this conversation.
|
|
@@ -771,7 +740,7 @@ Notes:
|
|
|
771
740
|
Available agent types and the tools they have access to:
|
|
772
741
|
${buildTypeListText()}
|
|
773
742
|
|
|
774
|
-
Custom agents can be defined in .pi/agents/<name>.md (project) or ${
|
|
743
|
+
Custom agents can be defined in .pi/agents/<name>.md (project) or ${getAgentDir()}/agents/<name>.md (global) — they are picked up automatically. Project-level agents override global ones. Creating a .md file with the same name as a default agent overrides it.
|
|
775
744
|
|
|
776
745
|
When using the Agent tool, specify a subagent_type parameter to select which agent type to use.
|
|
777
746
|
|
|
@@ -816,7 +785,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
816
785
|
const vars = {
|
|
817
786
|
typeList: buildTypeListText,
|
|
818
787
|
compactTypeList: buildCompactTypeListText,
|
|
819
|
-
agentDir:
|
|
788
|
+
agentDir: getAgentDir,
|
|
820
789
|
scheduleGuideline: () => scheduleGuideline,
|
|
821
790
|
};
|
|
822
791
|
// Replacement callback (not a string) — agent descriptions may contain `$&` etc.
|
|
@@ -829,13 +798,13 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
829
798
|
};
|
|
830
799
|
const loadCustomToolDescription = () => {
|
|
831
800
|
for (const path of [
|
|
832
|
-
|
|
833
|
-
|
|
801
|
+
join(process.cwd(), ".pi", "agent-tool-description.md"),
|
|
802
|
+
join(getAgentDir(), "agent-tool-description.md"),
|
|
834
803
|
]) {
|
|
835
804
|
try {
|
|
836
|
-
if (!
|
|
805
|
+
if (!existsSync(path))
|
|
837
806
|
continue;
|
|
838
|
-
const text =
|
|
807
|
+
const text = readFileSync(path, "utf-8").trim();
|
|
839
808
|
if (text)
|
|
840
809
|
return renderToolDescriptionTemplate(text);
|
|
841
810
|
console.warn(`[pi-subagents] ${path} is empty — ignoring`);
|
|
@@ -858,8 +827,8 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
858
827
|
}
|
|
859
828
|
return fullAgentToolDescription;
|
|
860
829
|
})();
|
|
861
|
-
pi.registerTool(
|
|
862
|
-
name:
|
|
830
|
+
pi.registerTool(defineTool({
|
|
831
|
+
name: SUBAGENT_TOOL_NAMES.AGENT,
|
|
863
832
|
label: "Agent",
|
|
864
833
|
description: agentToolDescription,
|
|
865
834
|
promptSnippet: "Launch autonomous sub-agents for complex multi-step tasks",
|
|
@@ -869,54 +838,54 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
869
838
|
"When an agent runs in the background, you will be notified on completion — do not poll or sleep waiting for it. Continue with other work instead.",
|
|
870
839
|
"Trust but verify: an agent's summary describes intent, not outcome. When an agent writes or edits code, check the actual changes before reporting work as done.",
|
|
871
840
|
],
|
|
872
|
-
parameters:
|
|
873
|
-
prompt:
|
|
841
|
+
parameters: Type.Object({
|
|
842
|
+
prompt: Type.String({
|
|
874
843
|
description: "The task for the agent to perform.",
|
|
875
844
|
}),
|
|
876
|
-
description:
|
|
845
|
+
description: Type.String({
|
|
877
846
|
description: "A short (3-5 word) description of the task (shown in UI).",
|
|
878
847
|
}),
|
|
879
|
-
subagent_type:
|
|
880
|
-
description: `The type of specialized agent to use. Available types: ${
|
|
848
|
+
subagent_type: Type.String({
|
|
849
|
+
description: `The type of specialized agent to use. Available types: ${getAvailableTypes().join(", ")}. Custom agents from .pi/agents/*.md (project) or ${getAgentDir()}/agents/*.md (global) are also available.`,
|
|
881
850
|
}),
|
|
882
|
-
model:
|
|
851
|
+
model: Type.Optional(Type.String({
|
|
883
852
|
description: 'Optional model override. Accepts "provider/modelId" or fuzzy name (e.g. "haiku", "sonnet"). Omit to use the agent type\'s default.',
|
|
884
853
|
})),
|
|
885
|
-
thinking:
|
|
854
|
+
thinking: Type.Optional(Type.String({
|
|
886
855
|
description: `Thinking level: ${THINKING_LEVELS.join(", ")}. Overrides agent default.`,
|
|
887
856
|
})),
|
|
888
|
-
max_turns:
|
|
857
|
+
max_turns: Type.Optional(Type.Number({
|
|
889
858
|
description: "Maximum number of agentic turns before stopping. Omit for unlimited (default).",
|
|
890
859
|
minimum: 1,
|
|
891
860
|
})),
|
|
892
|
-
run_in_background:
|
|
861
|
+
run_in_background: Type.Optional(Type.Boolean({
|
|
893
862
|
description: "Set to true to run in background. Returns agent ID immediately. You will be notified on completion.",
|
|
894
863
|
})),
|
|
895
|
-
resume:
|
|
864
|
+
resume: Type.Optional(Type.String({
|
|
896
865
|
description: "Optional agent ID to resume from. Continues from previous context.",
|
|
897
866
|
})),
|
|
898
|
-
isolated:
|
|
867
|
+
isolated: Type.Optional(Type.Boolean({
|
|
899
868
|
description: "If true, agent gets no extension/MCP tools — only built-in tools.",
|
|
900
869
|
})),
|
|
901
|
-
inherit_context:
|
|
870
|
+
inherit_context: Type.Optional(Type.Boolean({
|
|
902
871
|
description: "If true, fork parent conversation into the agent. Default: false (fresh context).",
|
|
903
872
|
})),
|
|
904
|
-
isolation:
|
|
873
|
+
isolation: Type.Optional(Type.Literal("worktree", {
|
|
905
874
|
description: 'Set to "worktree" to run the agent in a temporary git worktree (isolated copy of the repo). Changes are saved to a branch on completion.',
|
|
906
875
|
})),
|
|
907
876
|
...scheduleParam,
|
|
908
877
|
}),
|
|
909
878
|
// ---- Custom rendering: Claude Code style ----
|
|
910
879
|
renderCall(args, theme) {
|
|
911
|
-
const displayName = args.subagent_type ?
|
|
880
|
+
const displayName = args.subagent_type ? getDisplayName(args.subagent_type) : "Agent";
|
|
912
881
|
const desc = args.description ?? "";
|
|
913
|
-
return new
|
|
882
|
+
return new Text("▸ " + theme.fg("toolTitle", theme.bold(displayName)) + (desc ? " " + theme.fg("muted", desc) : ""), 0, 0);
|
|
914
883
|
},
|
|
915
884
|
renderResult(result, { expanded, isPartial }, theme) {
|
|
916
885
|
const details = result.details;
|
|
917
886
|
if (!details) {
|
|
918
887
|
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
919
|
-
return new
|
|
888
|
+
return new Text(text, 0, 0);
|
|
920
889
|
}
|
|
921
890
|
// Helper: build "haiku · thinking: high · ↻5≤30 · 3 tool uses · 33.8k tokens" stats string
|
|
922
891
|
const stats = (d) => {
|
|
@@ -926,27 +895,27 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
926
895
|
if (d.tags)
|
|
927
896
|
parts.push(...d.tags);
|
|
928
897
|
if (d.turnCount != null && d.turnCount > 0) {
|
|
929
|
-
parts.push(
|
|
898
|
+
parts.push(formatTurns(d.turnCount, d.maxTurns));
|
|
930
899
|
}
|
|
931
900
|
if (d.toolUses > 0)
|
|
932
901
|
parts.push(`${d.toolUses} tool use${d.toolUses === 1 ? "" : "s"}`);
|
|
933
902
|
if (d.tokens)
|
|
934
903
|
parts.push(d.tokens);
|
|
935
|
-
return parts.map(p =>
|
|
904
|
+
return parts.map(p => fgPreservingNestedStyles(theme, "dim", p)).join(" " + theme.fg("dim", "·") + " ");
|
|
936
905
|
};
|
|
937
906
|
// ---- While running (streaming) ----
|
|
938
907
|
if (isPartial || details.status === "running") {
|
|
939
|
-
const frame =
|
|
908
|
+
const frame = SPINNER[details.spinnerFrame ?? 0];
|
|
940
909
|
const s = stats(details);
|
|
941
910
|
return renderRunningAgentStatus(frame, s, details.activity ?? "thinking…", theme);
|
|
942
911
|
}
|
|
943
912
|
// ---- Background agent launched ----
|
|
944
913
|
if (details.status === "background") {
|
|
945
|
-
return new
|
|
914
|
+
return new Text(theme.fg("dim", ` ⎿ Running in background (ID: ${details.agentId})`), 0, 0);
|
|
946
915
|
}
|
|
947
916
|
// ---- Completed / Steered ----
|
|
948
917
|
if (details.status === "completed" || details.status === "steered") {
|
|
949
|
-
const duration =
|
|
918
|
+
const duration = formatMs(details.durationMs);
|
|
950
919
|
const isSteered = details.status === "steered";
|
|
951
920
|
const icon = isSteered ? theme.fg("warning", "✓") : theme.fg("success", "✓");
|
|
952
921
|
const s = stats(details);
|
|
@@ -954,28 +923,28 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
954
923
|
line += " " + theme.fg("dim", "·") + " " + theme.fg("dim", duration);
|
|
955
924
|
if (expanded) {
|
|
956
925
|
const resultText = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
957
|
-
const container = new pi_tui_1.Container();
|
|
958
|
-
container.addChild(new pi_tui_1.Text(line, 0, 0));
|
|
959
926
|
if (resultText) {
|
|
960
|
-
|
|
927
|
+
const lines = resultText.split("\n").slice(0, 50);
|
|
928
|
+
for (const l of lines) {
|
|
929
|
+
line += "\n" + theme.fg("dim", ` ${l}`);
|
|
930
|
+
}
|
|
961
931
|
if (resultText.split("\n").length > 50) {
|
|
962
|
-
|
|
932
|
+
line += "\n" + theme.fg("muted", " ... (use get_subagent_result with verbose for full output)");
|
|
963
933
|
}
|
|
964
934
|
}
|
|
965
|
-
return container;
|
|
966
935
|
}
|
|
967
936
|
else {
|
|
968
937
|
const doneText = isSteered ? "Wrapped up (turn limit)" : "Done";
|
|
969
938
|
line += "\n" + theme.fg("dim", ` ⎿ ${doneText}`);
|
|
970
939
|
}
|
|
971
|
-
return new
|
|
940
|
+
return new Text(line, 0, 0);
|
|
972
941
|
}
|
|
973
942
|
// ---- Stopped (user-initiated abort) ----
|
|
974
943
|
if (details.status === "stopped") {
|
|
975
944
|
const s = stats(details);
|
|
976
945
|
let line = theme.fg("dim", "■") + (s ? " " + s : "");
|
|
977
946
|
line += "\n" + theme.fg("dim", " ⎿ Stopped");
|
|
978
|
-
return new
|
|
947
|
+
return new Text(line, 0, 0);
|
|
979
948
|
}
|
|
980
949
|
// ---- Error / Aborted (hard max_turns) ----
|
|
981
950
|
const s = stats(details);
|
|
@@ -986,7 +955,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
986
955
|
else {
|
|
987
956
|
line += "\n" + theme.fg("warning", " ⎿ Aborted (max turns exceeded)");
|
|
988
957
|
}
|
|
989
|
-
return new
|
|
958
|
+
return new Text(line, 0, 0);
|
|
990
959
|
},
|
|
991
960
|
// ---- Execute ----
|
|
992
961
|
execute: async (toolCallId, params, signal, onUpdate, ctx) => {
|
|
@@ -995,17 +964,17 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
995
964
|
// Reload custom agents so new project/global .md files are picked up without restart
|
|
996
965
|
reloadCustomAgents();
|
|
997
966
|
const rawType = params.subagent_type;
|
|
998
|
-
const resolved =
|
|
967
|
+
const resolved = resolveType(rawType);
|
|
999
968
|
const subagentType = resolved ?? "general-purpose";
|
|
1000
969
|
const fellBack = resolved === undefined;
|
|
1001
|
-
const displayName =
|
|
970
|
+
const displayName = getDisplayName(subagentType);
|
|
1002
971
|
// Get agent config (if any)
|
|
1003
|
-
const customConfig =
|
|
1004
|
-
const resolvedConfig =
|
|
972
|
+
const customConfig = getAgentConfig(subagentType);
|
|
973
|
+
const resolvedConfig = resolveAgentInvocationConfig(customConfig, params);
|
|
1005
974
|
// Resolve model from agent config first; tool-call params only fill gaps.
|
|
1006
975
|
let model = ctx.model;
|
|
1007
976
|
if (resolvedConfig.modelInput) {
|
|
1008
|
-
const resolved =
|
|
977
|
+
const resolved = resolveModel(resolvedConfig.modelInput, ctx.modelRegistry);
|
|
1009
978
|
if (typeof resolved === "string") {
|
|
1010
979
|
if (resolvedConfig.modelFromParams)
|
|
1011
980
|
return textResult(resolved);
|
|
@@ -1025,8 +994,8 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1025
994
|
// user authored/installed this agent or chose the parent's model; trust it).
|
|
1026
995
|
// See SubagentsSettings.scopeModels docstring for the full policy.
|
|
1027
996
|
if (isScopeModelsEnabled() && model) {
|
|
1028
|
-
const allowed =
|
|
1029
|
-
if (allowed && !
|
|
997
|
+
const allowed = resolveEnabledModels(readEnabledModels(ctx.cwd), ctx.modelRegistry, ctx.cwd);
|
|
998
|
+
if (allowed && !isModelInScope(model, allowed)) {
|
|
1030
999
|
if (resolvedConfig.modelFromParams) {
|
|
1031
1000
|
const list = [...allowed].sort().map(m => ` ${m}`).join("\n");
|
|
1032
1001
|
return textResult(`Model not in scope: "${resolvedConfig.modelInput}".\n\n` +
|
|
@@ -1052,20 +1021,17 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1052
1021
|
const attachTranscript = (rec, agentId) => {
|
|
1053
1022
|
if (!rec || !outputTranscript)
|
|
1054
1023
|
return;
|
|
1055
|
-
rec.outputFile =
|
|
1056
|
-
|
|
1057
|
-
// Keep the existing /tmp output for compatibility, while also writing a
|
|
1058
|
-
// durable project-local copy for reload-safe, read-only history viewing.
|
|
1024
|
+
rec.outputFile = createOutputFilePath(ctx.cwd, agentId, ctx.sessionManager.getSessionId());
|
|
1025
|
+
writeInitialEntry(rec.outputFile, agentId, params.prompt, ctx.cwd);
|
|
1059
1026
|
try {
|
|
1060
|
-
rec.historyFile =
|
|
1061
|
-
rec.transcriptPath =
|
|
1062
|
-
|
|
1027
|
+
rec.historyFile = createAgentHistoryPath(ctx.cwd, agentId);
|
|
1028
|
+
rec.transcriptPath = agentHistoryLocator(ctx.cwd, rec.historyFile);
|
|
1029
|
+
writeInitialEntry(rec.historyFile, agentId, params.prompt, ctx.cwd);
|
|
1063
1030
|
}
|
|
1064
1031
|
catch (err) {
|
|
1065
|
-
// History is best effort; never disable the existing output transcript.
|
|
1066
1032
|
rec.historyFile = undefined;
|
|
1067
1033
|
rec.transcriptPath = undefined;
|
|
1068
|
-
|
|
1034
|
+
ctx.ui.notify(`Could not create durable transcript for agent ${agentId}: ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
1069
1035
|
}
|
|
1070
1036
|
};
|
|
1071
1037
|
const parentModelId = ctx.model?.id;
|
|
@@ -1073,21 +1039,23 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1073
1039
|
const modelName = effectiveModelId && effectiveModelId !== parentModelId
|
|
1074
1040
|
? (model?.name ?? effectiveModelId).replace(/^Claude\s+/i, "").toLowerCase()
|
|
1075
1041
|
: undefined;
|
|
1076
|
-
const effectiveMaxTurns =
|
|
1042
|
+
const effectiveMaxTurns = normalizeMaxTurns(resolvedConfig.maxTurns ?? getDefaultMaxTurns());
|
|
1077
1043
|
const agentInvocation = {
|
|
1078
1044
|
modelName,
|
|
1045
|
+
effectiveModelName: model?.name ?? model?.id ?? ctx.model?.name ?? ctx.model?.id,
|
|
1079
1046
|
thinking,
|
|
1047
|
+
effectiveThinking: thinking,
|
|
1080
1048
|
// Explicit value only — the default fallback would just add noise.
|
|
1081
1049
|
// Normalize so `0` (unlimited) doesn't surface as a misleading "max turns: 0".
|
|
1082
|
-
maxTurns:
|
|
1050
|
+
maxTurns: normalizeMaxTurns(resolvedConfig.maxTurns),
|
|
1083
1051
|
isolated,
|
|
1084
1052
|
inheritContext,
|
|
1085
1053
|
runInBackground,
|
|
1086
1054
|
isolation,
|
|
1087
1055
|
};
|
|
1088
1056
|
// Tool-result render shows the mode label too; viewer's header already does.
|
|
1089
|
-
const modeLabel =
|
|
1090
|
-
const { tags: invocationTags } =
|
|
1057
|
+
const modeLabel = getPromptModeLabel(subagentType);
|
|
1058
|
+
const { tags: invocationTags } = buildInvocationTags(agentInvocation);
|
|
1091
1059
|
const agentTags = modeLabel ? [modeLabel, ...invocationTags] : invocationTags;
|
|
1092
1060
|
const detailBase = {
|
|
1093
1061
|
displayName,
|
|
@@ -1167,7 +1135,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1167
1135
|
origBgOnSession(session);
|
|
1168
1136
|
const rec = manager.getRecord(id);
|
|
1169
1137
|
if (rec?.outputFile) {
|
|
1170
|
-
rec.outputCleanup =
|
|
1138
|
+
rec.outputCleanup = streamToOutputFile(session, rec.outputFile, id, ctx.cwd, rec.historyFile);
|
|
1171
1139
|
}
|
|
1172
1140
|
};
|
|
1173
1141
|
try {
|
|
@@ -1189,7 +1157,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1189
1157
|
}
|
|
1190
1158
|
// Set output file + join mode synchronously after spawn, before the
|
|
1191
1159
|
// event loop yields — onSessionCreated is async so this is safe.
|
|
1192
|
-
const joinMode =
|
|
1160
|
+
const joinMode = resolveJoinMode(defaultJoinMode, true);
|
|
1193
1161
|
const record = manager.getRecord(id);
|
|
1194
1162
|
if (record && joinMode) {
|
|
1195
1163
|
record.joinMode = joinMode;
|
|
@@ -1244,8 +1212,8 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1244
1212
|
maxTurns: fgState.maxTurns,
|
|
1245
1213
|
durationMs: Date.now() - startedAt,
|
|
1246
1214
|
status: "running",
|
|
1247
|
-
activity:
|
|
1248
|
-
spinnerFrame: spinnerFrame %
|
|
1215
|
+
activity: describeActivity(fgState.activeTools, fgState.responseText),
|
|
1216
|
+
spinnerFrame: spinnerFrame % SPINNER.length,
|
|
1249
1217
|
};
|
|
1250
1218
|
onUpdate?.({
|
|
1251
1219
|
content: [{ type: "text", text: `${fgState.toolUses} tool uses...` }],
|
|
@@ -1273,7 +1241,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1273
1241
|
if (fgId) {
|
|
1274
1242
|
const rec = manager.getRecord(fgId);
|
|
1275
1243
|
if (rec?.outputFile) {
|
|
1276
|
-
rec.outputCleanup =
|
|
1244
|
+
rec.outputCleanup = streamToOutputFile(session, rec.outputFile, fgId, ctx.cwd, rec.historyFile);
|
|
1277
1245
|
}
|
|
1278
1246
|
}
|
|
1279
1247
|
};
|
|
@@ -1321,7 +1289,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1321
1289
|
// "general-purpose" may itself be unregistered (defaults disabled, no
|
|
1322
1290
|
// user override) — getConfig then uses the hardcoded fallback config.
|
|
1323
1291
|
const fallbackNote = fellBack
|
|
1324
|
-
? `Note: Unknown agent type "${rawType}" — using ${
|
|
1292
|
+
? `Note: Unknown agent type "${rawType}" — using ${resolveType("general-purpose") ? "general-purpose" : "the fallback agent config"}.\n\n`
|
|
1325
1293
|
: "";
|
|
1326
1294
|
if (record.status === "error") {
|
|
1327
1295
|
// Error headline + any partial output the run produced before failing.
|
|
@@ -1331,24 +1299,24 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1331
1299
|
const statsParts = [`${record.toolUses} tool uses`];
|
|
1332
1300
|
if (tokenText)
|
|
1333
1301
|
statsParts.push(tokenText);
|
|
1334
|
-
return textResult(`${fallbackNote}Agent completed in ${
|
|
1302
|
+
return textResult(`${fallbackNote}Agent completed in ${formatMs(durationMs)} (${statsParts.join(", ")})${getStatusNote(record.status)}.\n\n` +
|
|
1335
1303
|
(record.result?.trim() || "No output."), details);
|
|
1336
1304
|
},
|
|
1337
1305
|
}));
|
|
1338
1306
|
// ---- get_subagent_result tool ----
|
|
1339
|
-
pi.registerTool(
|
|
1340
|
-
name:
|
|
1307
|
+
pi.registerTool(defineTool({
|
|
1308
|
+
name: SUBAGENT_TOOL_NAMES.GET_RESULT,
|
|
1341
1309
|
label: "Get Agent Result",
|
|
1342
1310
|
description: "Check status and retrieve results from a background agent. Use the agent ID returned by Agent with run_in_background.",
|
|
1343
1311
|
promptSnippet: "Check status and retrieve results from a background agent",
|
|
1344
|
-
parameters:
|
|
1345
|
-
agent_id:
|
|
1312
|
+
parameters: Type.Object({
|
|
1313
|
+
agent_id: Type.String({
|
|
1346
1314
|
description: "The agent ID to check.",
|
|
1347
1315
|
}),
|
|
1348
|
-
wait:
|
|
1316
|
+
wait: Type.Optional(Type.Boolean({
|
|
1349
1317
|
description: "If true, wait for the agent to complete before returning. Default: false.",
|
|
1350
1318
|
})),
|
|
1351
|
-
verbose:
|
|
1319
|
+
verbose: Type.Optional(Type.Boolean({
|
|
1352
1320
|
description: "If true, include the agent's full conversation (messages + tool calls). Default: false.",
|
|
1353
1321
|
})),
|
|
1354
1322
|
}),
|
|
@@ -1369,10 +1337,10 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1369
1337
|
if (record.promise)
|
|
1370
1338
|
await abortable(record.promise, signal);
|
|
1371
1339
|
}
|
|
1372
|
-
const displayName =
|
|
1373
|
-
const duration =
|
|
1340
|
+
const displayName = getDisplayName(record.type);
|
|
1341
|
+
const duration = formatDuration(record.startedAt, record.completedAt);
|
|
1374
1342
|
const tokens = formatLifetimeTokens(record);
|
|
1375
|
-
const contextPercent =
|
|
1343
|
+
const contextPercent = getSessionContextPercent(record.session);
|
|
1376
1344
|
const statsParts = [`Tool uses: ${record.toolUses}`];
|
|
1377
1345
|
if (tokens)
|
|
1378
1346
|
statsParts.push(tokens);
|
|
@@ -1382,7 +1350,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1382
1350
|
statsParts.push(`Compactions: ${record.compactionCount}`);
|
|
1383
1351
|
statsParts.push(`Duration: ${duration}`);
|
|
1384
1352
|
let output = `Agent: ${record.id}\n` +
|
|
1385
|
-
`Type: ${displayName} | Status: ${record.status}${
|
|
1353
|
+
`Type: ${displayName} | Status: ${record.status}${getStatusNote(record.status)} | ${statsParts.join(" | ")}\n` +
|
|
1386
1354
|
`Description: ${record.description}\n\n`;
|
|
1387
1355
|
if (record.status === "running") {
|
|
1388
1356
|
output += "Agent is still running. Use wait: true or check back later.";
|
|
@@ -1400,7 +1368,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1400
1368
|
}
|
|
1401
1369
|
// Verbose: include full conversation
|
|
1402
1370
|
if (params.verbose && record.session) {
|
|
1403
|
-
const conversation =
|
|
1371
|
+
const conversation = getAgentConversation(record.session);
|
|
1404
1372
|
if (conversation) {
|
|
1405
1373
|
output += `\n\n--- Agent Conversation ---\n${conversation}`;
|
|
1406
1374
|
}
|
|
@@ -1409,17 +1377,17 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1409
1377
|
},
|
|
1410
1378
|
}));
|
|
1411
1379
|
// ---- steer_subagent tool ----
|
|
1412
|
-
pi.registerTool(
|
|
1413
|
-
name:
|
|
1380
|
+
pi.registerTool(defineTool({
|
|
1381
|
+
name: SUBAGENT_TOOL_NAMES.STEER,
|
|
1414
1382
|
label: "Steer Agent",
|
|
1415
1383
|
description: "Send a steering message to a running agent. The message will interrupt the agent after its current tool execution " +
|
|
1416
1384
|
"and be injected into its conversation, allowing you to redirect its work mid-run. Only works on running agents.",
|
|
1417
1385
|
promptSnippet: "Send a steering message to redirect a running background agent",
|
|
1418
|
-
parameters:
|
|
1419
|
-
agent_id:
|
|
1386
|
+
parameters: Type.Object({
|
|
1387
|
+
agent_id: Type.String({
|
|
1420
1388
|
description: "The agent ID to steer (must be currently running).",
|
|
1421
1389
|
}),
|
|
1422
|
-
message:
|
|
1390
|
+
message: Type.String({
|
|
1423
1391
|
description: "The steering message to send. This will appear as a user message in the agent's conversation.",
|
|
1424
1392
|
}),
|
|
1425
1393
|
}),
|
|
@@ -1440,10 +1408,10 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1440
1408
|
return textResult(`Steering message queued for agent ${record.id}. It will be delivered once the session initializes.`);
|
|
1441
1409
|
}
|
|
1442
1410
|
try {
|
|
1443
|
-
await
|
|
1411
|
+
await steerAgent(record.session, params.message);
|
|
1444
1412
|
pi.events.emit("subagents:steered", { id: record.id, message: params.message });
|
|
1445
1413
|
const tokens = formatLifetimeTokens(record);
|
|
1446
|
-
const contextPercent =
|
|
1414
|
+
const contextPercent = getSessionContextPercent(record.session);
|
|
1447
1415
|
const stateParts = [];
|
|
1448
1416
|
if (tokens)
|
|
1449
1417
|
stateParts.push(tokens);
|
|
@@ -1461,30 +1429,30 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1461
1429
|
},
|
|
1462
1430
|
}));
|
|
1463
1431
|
// ---- /agents interactive menu ----
|
|
1464
|
-
const projectAgentsDir = () =>
|
|
1465
|
-
const workspaceAgentsDir = () =>
|
|
1466
|
-
const personalAgentsDir = () =>
|
|
1432
|
+
const projectAgentsDir = () => join(process.cwd(), ".pi", "agents");
|
|
1433
|
+
const workspaceAgentsDir = () => join(process.cwd(), ".agents", "agents");
|
|
1434
|
+
const personalAgentsDir = () => join(getAgentDir(), "agents");
|
|
1467
1435
|
/** Find the file path of a custom agent by name, in discovery-precedence order (project, workspace, then global). */
|
|
1468
1436
|
function findAgentFile(name) {
|
|
1469
|
-
const projectPath =
|
|
1470
|
-
if (
|
|
1437
|
+
const projectPath = join(projectAgentsDir(), `${name}.md`);
|
|
1438
|
+
if (existsSync(projectPath))
|
|
1471
1439
|
return { path: projectPath, location: "project" };
|
|
1472
|
-
const workspacePath =
|
|
1473
|
-
if (
|
|
1440
|
+
const workspacePath = join(workspaceAgentsDir(), `${name}.md`);
|
|
1441
|
+
if (existsSync(workspacePath))
|
|
1474
1442
|
return { path: workspacePath, location: "workspace" };
|
|
1475
|
-
const personalPath =
|
|
1476
|
-
if (
|
|
1443
|
+
const personalPath = join(personalAgentsDir(), `${name}.md`);
|
|
1444
|
+
if (existsSync(personalPath))
|
|
1477
1445
|
return { path: personalPath, location: "personal" };
|
|
1478
1446
|
return undefined;
|
|
1479
1447
|
}
|
|
1480
1448
|
function getModelLabel(type, registry) {
|
|
1481
|
-
const cfg =
|
|
1449
|
+
const cfg = getAgentConfig(type);
|
|
1482
1450
|
if (!cfg?.model)
|
|
1483
1451
|
return "inherit"; // no model configured → really inherits parent
|
|
1484
1452
|
const label = getModelLabelFromConfig(cfg.model);
|
|
1485
1453
|
if (!registry)
|
|
1486
1454
|
return label;
|
|
1487
|
-
const resolved =
|
|
1455
|
+
const resolved = resolveModel(cfg.model, registry);
|
|
1488
1456
|
// Configured but unresolvable: the runtime silently falls back to the parent
|
|
1489
1457
|
// model, so flag it (and the fallback) rather than hiding the config.
|
|
1490
1458
|
if (typeof resolved === "string")
|
|
@@ -1500,18 +1468,13 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1500
1468
|
}
|
|
1501
1469
|
async function showAgentsMenu(ctx) {
|
|
1502
1470
|
reloadCustomAgents();
|
|
1503
|
-
const allNames =
|
|
1471
|
+
const allNames = getAllTypes();
|
|
1504
1472
|
// Build select options
|
|
1505
1473
|
const options = [];
|
|
1506
|
-
//
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
if (agents.length > 0) {
|
|
1511
|
-
const running = agents.filter(a => a.status === "running" || a.status === "queued").length;
|
|
1512
|
-
const done = agents.filter(a => a.status === "completed" || a.status === "steered").length;
|
|
1513
|
-
options.push(`Running agents (${agents.length}) — ${running} running, ${done} done`);
|
|
1514
|
-
}
|
|
1474
|
+
// Keep active agents and terminal history in separate menu entries.
|
|
1475
|
+
const records = manager.listAgents();
|
|
1476
|
+
const { active, history } = splitAgentRecords(records, ctx.cwd);
|
|
1477
|
+
options.push(...buildAgentStatusMenuEntries(records, ctx.cwd));
|
|
1515
1478
|
// Agent types list
|
|
1516
1479
|
if (allNames.length > 0) {
|
|
1517
1480
|
options.push(`Agent types (${allNames.length})`);
|
|
@@ -1524,7 +1487,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1524
1487
|
// Actions
|
|
1525
1488
|
options.push("Create new agent");
|
|
1526
1489
|
options.push("Settings");
|
|
1527
|
-
const noAgentsMsg = allNames.length === 0 &&
|
|
1490
|
+
const noAgentsMsg = allNames.length === 0 && active.length === 0 && history.length === 0
|
|
1528
1491
|
? "No agents found. Create specialized subagents that can be delegated to.\n\n" +
|
|
1529
1492
|
"Each subagent has its own context window, custom system prompt, and specific tools.\n\n" +
|
|
1530
1493
|
"Try creating: Code Reviewer, Security Auditor, Test Writer, or Documentation Writer.\n\n"
|
|
@@ -1539,12 +1502,16 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1539
1502
|
await showRunningAgents(ctx);
|
|
1540
1503
|
await showAgentsMenu(ctx);
|
|
1541
1504
|
}
|
|
1505
|
+
else if (choice.startsWith("Agent history (")) {
|
|
1506
|
+
await showAgentHistory(ctx);
|
|
1507
|
+
await showAgentsMenu(ctx);
|
|
1508
|
+
}
|
|
1542
1509
|
else if (choice.startsWith("Agent types (")) {
|
|
1543
1510
|
await showAllAgentsList(ctx);
|
|
1544
1511
|
await showAgentsMenu(ctx);
|
|
1545
1512
|
}
|
|
1546
1513
|
else if (choice.startsWith("Scheduled jobs (")) {
|
|
1547
|
-
await
|
|
1514
|
+
await showSchedulesMenu(ctx, scheduler);
|
|
1548
1515
|
await showAgentsMenu(ctx);
|
|
1549
1516
|
}
|
|
1550
1517
|
else if (choice === "Create new agent") {
|
|
@@ -1556,7 +1523,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1556
1523
|
}
|
|
1557
1524
|
}
|
|
1558
1525
|
async function showAllAgentsList(ctx) {
|
|
1559
|
-
const allNames =
|
|
1526
|
+
const allNames = getAllTypes();
|
|
1560
1527
|
if (allNames.length === 0) {
|
|
1561
1528
|
ctx.ui.notify("No agents.", "info");
|
|
1562
1529
|
return;
|
|
@@ -1577,7 +1544,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1577
1544
|
// full description renders below the highlighted row via SettingsList,
|
|
1578
1545
|
// exactly like the Settings menu — so long descriptions never wrap the list.
|
|
1579
1546
|
const items = allNames.map(name => {
|
|
1580
|
-
const cfg =
|
|
1547
|
+
const cfg = getAgentConfig(name);
|
|
1581
1548
|
const disabled = cfg?.enabled === false;
|
|
1582
1549
|
const model = getModelLabel(name, ctx.modelRegistry);
|
|
1583
1550
|
return {
|
|
@@ -1590,22 +1557,22 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1590
1557
|
values: [model],
|
|
1591
1558
|
};
|
|
1592
1559
|
});
|
|
1593
|
-
const hasCustom = allNames.some(n => { const c =
|
|
1594
|
-
const hasDisabled = allNames.some(n =>
|
|
1560
|
+
const hasCustom = allNames.some(n => { const c = getAgentConfig(n); return c && !c.isDefault && c.enabled !== false; });
|
|
1561
|
+
const hasDisabled = allNames.some(n => getAgentConfig(n)?.enabled === false);
|
|
1595
1562
|
const legendParts = [];
|
|
1596
1563
|
if (hasCustom)
|
|
1597
1564
|
legendParts.push("• = project ◦ = global");
|
|
1598
1565
|
if (hasDisabled)
|
|
1599
1566
|
legendParts.push("✕ = disabled");
|
|
1600
1567
|
const selected = await ctx.ui.custom((_tui, _theme, _kb, done) => {
|
|
1601
|
-
const slTheme =
|
|
1602
|
-
const list = new
|
|
1568
|
+
const slTheme = getSettingsListTheme();
|
|
1569
|
+
const list = new SettingsList(items, Math.min(items.length, 12), slTheme, id => done(id), // Enter/Space on a row → return that agent's name
|
|
1603
1570
|
() => done(undefined));
|
|
1604
|
-
const container = new
|
|
1605
|
-
container.addChild(new
|
|
1571
|
+
const container = new Container();
|
|
1572
|
+
container.addChild(new Text("Agent types", 0, 0));
|
|
1606
1573
|
if (legendParts.length)
|
|
1607
|
-
container.addChild(new
|
|
1608
|
-
container.addChild(new
|
|
1574
|
+
container.addChild(new Text(slTheme.hint(legendParts.join(" ")), 0, 0));
|
|
1575
|
+
container.addChild(new Spacer(1));
|
|
1609
1576
|
container.addChild(list);
|
|
1610
1577
|
return {
|
|
1611
1578
|
render: (w) => container.render(w),
|
|
@@ -1613,65 +1580,111 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1613
1580
|
handleInput: (data) => list.handleInput?.(data),
|
|
1614
1581
|
};
|
|
1615
1582
|
});
|
|
1616
|
-
if (selected &&
|
|
1583
|
+
if (selected && getAgentConfig(selected)) {
|
|
1617
1584
|
await showAgentDetail(ctx, selected);
|
|
1618
1585
|
await showAllAgentsList(ctx);
|
|
1619
1586
|
}
|
|
1620
1587
|
}
|
|
1588
|
+
function makeUniqueAgentOptionLabels(pairs) {
|
|
1589
|
+
const counts = new Map();
|
|
1590
|
+
for (const pair of pairs)
|
|
1591
|
+
counts.set(pair.label, (counts.get(pair.label) ?? 0) + 1);
|
|
1592
|
+
const used = new Set();
|
|
1593
|
+
return pairs.map((pair) => {
|
|
1594
|
+
const { record, label } = pair;
|
|
1595
|
+
if ((counts.get(label) ?? 0) === 1) {
|
|
1596
|
+
used.add(label);
|
|
1597
|
+
return label;
|
|
1598
|
+
}
|
|
1599
|
+
const suffix = ` · #${record.id.slice(-8)}`;
|
|
1600
|
+
let candidate = `${label}${suffix}`;
|
|
1601
|
+
let n = 2;
|
|
1602
|
+
while (used.has(candidate))
|
|
1603
|
+
candidate = `${label}${suffix}-${n++}`;
|
|
1604
|
+
used.add(candidate);
|
|
1605
|
+
pair.label = candidate;
|
|
1606
|
+
return candidate;
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1621
1609
|
async function showRunningAgents(ctx) {
|
|
1622
|
-
const agents = manager.listAgents()
|
|
1610
|
+
const { active: agents } = splitAgentRecords(manager.listAgents(), ctx.cwd);
|
|
1623
1611
|
if (agents.length === 0) {
|
|
1624
1612
|
ctx.ui.notify("No agents.", "info");
|
|
1625
1613
|
return;
|
|
1626
1614
|
}
|
|
1627
|
-
const
|
|
1628
|
-
const dn =
|
|
1629
|
-
const dur =
|
|
1630
|
-
return `${dn} (${
|
|
1615
|
+
const pairs = agents.map((record) => {
|
|
1616
|
+
const dn = getDisplayName(record.type);
|
|
1617
|
+
const dur = formatDuration(record.startedAt, record.completedAt);
|
|
1618
|
+
return { record, label: `${dn} (${record.description}) · ${record.toolUses} tools · ${record.status} · ${dur}` };
|
|
1631
1619
|
});
|
|
1620
|
+
const options = makeUniqueAgentOptionLabels(pairs);
|
|
1632
1621
|
const choice = await ctx.ui.select("Running agents", options);
|
|
1633
1622
|
if (!choice)
|
|
1634
1623
|
return;
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
if (idx < 0)
|
|
1624
|
+
const record = pairs.find((pair) => pair.label === choice)?.record;
|
|
1625
|
+
if (!record)
|
|
1638
1626
|
return;
|
|
1639
|
-
|
|
1640
|
-
await viewAgentConversation(ctx, record);
|
|
1627
|
+
await viewAgentConversation(ctx, record, "live");
|
|
1641
1628
|
// Back-navigation: re-show the list
|
|
1642
1629
|
await showRunningAgents(ctx);
|
|
1643
1630
|
}
|
|
1644
|
-
async function
|
|
1645
|
-
|
|
1646
|
-
|
|
1631
|
+
async function showAgentHistory(ctx) {
|
|
1632
|
+
const { history } = splitAgentRecords(manager.listAgents(), ctx.cwd);
|
|
1633
|
+
if (history.length === 0) {
|
|
1634
|
+
ctx.ui.notify("No agent history.", "info");
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
const pairs = history.map((record) => ({ record, label: formatAgentHistoryOption(record, Date.now()) }));
|
|
1638
|
+
const options = makeUniqueAgentOptionLabels(pairs);
|
|
1639
|
+
const choice = await ctx.ui.select("Agent history", options);
|
|
1640
|
+
if (!choice)
|
|
1641
|
+
return;
|
|
1642
|
+
const record = pairs.find((pair) => pair.label === choice)?.record;
|
|
1643
|
+
if (!record)
|
|
1644
|
+
return;
|
|
1645
|
+
await viewAgentConversation(ctx, record, "history");
|
|
1646
|
+
}
|
|
1647
|
+
async function viewAgentConversation(ctx, record, mode) {
|
|
1648
|
+
if (mode === "live" && !canOpenActiveAgent(record)) {
|
|
1649
|
+
ctx.ui.notify(`Agent is ${record.status === "queued" ? "queued" : "expired"} — no history available.`, "info");
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
if (mode === "history" && !canOpenAgentHistory(record, ctx.cwd)) {
|
|
1653
|
+
ctx.ui.notify("No agent history.", "info");
|
|
1647
1654
|
return;
|
|
1648
1655
|
}
|
|
1649
1656
|
const { ConversationViewer, VIEWPORT_HEIGHT_PCT, createStaticConversationSource } = await import("./ui/conversation-viewer.js");
|
|
1650
|
-
const session =
|
|
1651
|
-
?
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1657
|
+
const session = mode === "live"
|
|
1658
|
+
? record.session
|
|
1659
|
+
: (() => {
|
|
1660
|
+
const messages = record.transcriptPath
|
|
1661
|
+
? readAgentHistory(ctx.cwd, record.transcriptPath)
|
|
1662
|
+
: undefined;
|
|
1663
|
+
return messages
|
|
1664
|
+
? createStaticConversationSource(messages)
|
|
1665
|
+
: record.session
|
|
1666
|
+
? createStaticConversationSource(record.session.messages)
|
|
1667
|
+
: undefined;
|
|
1668
|
+
})();
|
|
1656
1669
|
if (!session) {
|
|
1657
|
-
ctx.ui.notify(
|
|
1670
|
+
ctx.ui.notify("No agent history.", "info");
|
|
1658
1671
|
return;
|
|
1659
1672
|
}
|
|
1660
1673
|
const activity = agentActivity.get(record.id);
|
|
1661
|
-
const isLive =
|
|
1674
|
+
const isLive = mode === "live";
|
|
1662
1675
|
await ctx.ui.custom((tui, theme, keybindings, done) => {
|
|
1663
1676
|
return new ConversationViewer(tui, session, record, activity, theme, done, isLive ? () => {
|
|
1664
1677
|
if (manager.abort(record.id)) {
|
|
1665
1678
|
ctx.ui.notify(`Stopped "${record.description}".`, "info");
|
|
1666
1679
|
}
|
|
1667
|
-
} : undefined, keybindings, isLive ? (message) => manager.steer(record.id, message) : undefined);
|
|
1680
|
+
} : undefined, keybindings, isLive ? (message) => manager.steer(record.id, message) : undefined, mode === "history" ? { pi, ctx, readOnly: true } : { pi, ctx });
|
|
1668
1681
|
}, {
|
|
1669
1682
|
overlay: true,
|
|
1670
1683
|
overlayOptions: { anchor: "center", width: "90%", maxHeight: `${VIEWPORT_HEIGHT_PCT}%` },
|
|
1671
1684
|
});
|
|
1672
1685
|
}
|
|
1673
1686
|
async function showAgentDetail(ctx, name) {
|
|
1674
|
-
const cfg =
|
|
1687
|
+
const cfg = getAgentConfig(name);
|
|
1675
1688
|
if (!cfg) {
|
|
1676
1689
|
ctx.ui.notify(`Agent config not found for "${name}".`, "warning");
|
|
1677
1690
|
return;
|
|
@@ -1702,7 +1715,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1702
1715
|
if (!choice || choice === "Back")
|
|
1703
1716
|
return;
|
|
1704
1717
|
if (choice === "Edit" && file) {
|
|
1705
|
-
const content =
|
|
1718
|
+
const content = readFileSync(file.path, "utf-8");
|
|
1706
1719
|
const edited = await ctx.ui.editor(`Edit ${name}`, content);
|
|
1707
1720
|
if (edited !== undefined && edited !== content) {
|
|
1708
1721
|
const { writeFileSync } = await import("node:fs");
|
|
@@ -1715,7 +1728,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1715
1728
|
if (file) {
|
|
1716
1729
|
const confirmed = await ctx.ui.confirm("Delete agent", `Delete ${name} from ${file.location} (${file.path})?`);
|
|
1717
1730
|
if (confirmed) {
|
|
1718
|
-
|
|
1731
|
+
unlinkSync(file.path);
|
|
1719
1732
|
reloadCustomAgents();
|
|
1720
1733
|
ctx.ui.notify(`Deleted ${file.path}`, "info");
|
|
1721
1734
|
}
|
|
@@ -1724,7 +1737,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1724
1737
|
else if (choice === "Reset to default" && file) {
|
|
1725
1738
|
const confirmed = await ctx.ui.confirm("Reset to default", `Delete override ${file.path} and restore embedded default?`);
|
|
1726
1739
|
if (confirmed) {
|
|
1727
|
-
|
|
1740
|
+
unlinkSync(file.path);
|
|
1728
1741
|
reloadCustomAgents();
|
|
1729
1742
|
ctx.ui.notify(`Restored default ${name}`, "info");
|
|
1730
1743
|
}
|
|
@@ -1748,9 +1761,9 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1748
1761
|
if (!location)
|
|
1749
1762
|
return;
|
|
1750
1763
|
const targetDir = location.startsWith("Project") ? projectAgentsDir() : personalAgentsDir();
|
|
1751
|
-
|
|
1752
|
-
const targetPath =
|
|
1753
|
-
if (
|
|
1764
|
+
mkdirSync(targetDir, { recursive: true });
|
|
1765
|
+
const targetPath = join(targetDir, `${name}.md`);
|
|
1766
|
+
if (existsSync(targetPath)) {
|
|
1754
1767
|
const overwrite = await ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`);
|
|
1755
1768
|
if (!overwrite)
|
|
1756
1769
|
return;
|
|
@@ -1803,7 +1816,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1803
1816
|
const file = findAgentFile(name);
|
|
1804
1817
|
if (file) {
|
|
1805
1818
|
// Existing file — set enabled: false in frontmatter (idempotent)
|
|
1806
|
-
const content =
|
|
1819
|
+
const content = readFileSync(file.path, "utf-8");
|
|
1807
1820
|
if (content.includes("\nenabled: false\n")) {
|
|
1808
1821
|
ctx.ui.notify(`${name} is already disabled.`, "info");
|
|
1809
1822
|
return;
|
|
@@ -1823,8 +1836,8 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1823
1836
|
if (!location)
|
|
1824
1837
|
return;
|
|
1825
1838
|
const targetDir = location.startsWith("Project") ? projectAgentsDir() : personalAgentsDir();
|
|
1826
|
-
|
|
1827
|
-
const targetPath =
|
|
1839
|
+
mkdirSync(targetDir, { recursive: true });
|
|
1840
|
+
const targetPath = join(targetDir, `${name}.md`);
|
|
1828
1841
|
const { writeFileSync } = await import("node:fs");
|
|
1829
1842
|
writeFileSync(targetPath, "---\nenabled: false\n---\n", "utf-8");
|
|
1830
1843
|
reloadCustomAgents();
|
|
@@ -1835,12 +1848,12 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1835
1848
|
const file = findAgentFile(name);
|
|
1836
1849
|
if (!file)
|
|
1837
1850
|
return;
|
|
1838
|
-
const content =
|
|
1851
|
+
const content = readFileSync(file.path, "utf-8");
|
|
1839
1852
|
const updated = content.replace(/^(---\n)enabled: false\n/, "$1");
|
|
1840
1853
|
const { writeFileSync } = await import("node:fs");
|
|
1841
1854
|
// If the file was just a stub ("---\n---\n"), delete it to restore the built-in default
|
|
1842
1855
|
if (updated.trim() === "---\n---" || updated.trim() === "---\n---\n") {
|
|
1843
|
-
|
|
1856
|
+
unlinkSync(file.path);
|
|
1844
1857
|
reloadCustomAgents();
|
|
1845
1858
|
ctx.ui.notify(`Enabled ${name} (removed ${file.path})`, "info");
|
|
1846
1859
|
}
|
|
@@ -1878,9 +1891,9 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1878
1891
|
const name = await ctx.ui.input("Agent name (filename, no spaces)");
|
|
1879
1892
|
if (!name)
|
|
1880
1893
|
return;
|
|
1881
|
-
|
|
1882
|
-
const targetPath =
|
|
1883
|
-
if (
|
|
1894
|
+
mkdirSync(targetDir, { recursive: true });
|
|
1895
|
+
const targetPath = join(targetDir, `${name}.md`);
|
|
1896
|
+
if (existsSync(targetPath)) {
|
|
1884
1897
|
const overwrite = await ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`);
|
|
1885
1898
|
if (!overwrite)
|
|
1886
1899
|
return;
|
|
@@ -1934,7 +1947,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
1934
1947
|
return;
|
|
1935
1948
|
}
|
|
1936
1949
|
reloadCustomAgents();
|
|
1937
|
-
if (
|
|
1950
|
+
if (existsSync(targetPath)) {
|
|
1938
1951
|
ctx.ui.notify(`Created ${targetPath}`, "info");
|
|
1939
1952
|
}
|
|
1940
1953
|
else {
|
|
@@ -1956,7 +1969,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
1956
1969
|
return;
|
|
1957
1970
|
let tools;
|
|
1958
1971
|
if (toolChoice === "all") {
|
|
1959
|
-
tools =
|
|
1972
|
+
tools = BUILTIN_TOOL_NAMES.join(", ");
|
|
1960
1973
|
}
|
|
1961
1974
|
else if (toolChoice === "none") {
|
|
1962
1975
|
tools = "none";
|
|
@@ -1965,7 +1978,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
1965
1978
|
tools = "read, bash, grep, find, ls";
|
|
1966
1979
|
}
|
|
1967
1980
|
else {
|
|
1968
|
-
const customTools = await ctx.ui.input("Tools (comma-separated)",
|
|
1981
|
+
const customTools = await ctx.ui.input("Tools (comma-separated)", BUILTIN_TOOL_NAMES.join(", "));
|
|
1969
1982
|
if (!customTools)
|
|
1970
1983
|
return;
|
|
1971
1984
|
tools = customTools;
|
|
@@ -2013,9 +2026,9 @@ prompt_mode: replace
|
|
|
2013
2026
|
|
|
2014
2027
|
${systemPrompt}
|
|
2015
2028
|
`;
|
|
2016
|
-
|
|
2017
|
-
const targetPath =
|
|
2018
|
-
if (
|
|
2029
|
+
mkdirSync(targetDir, { recursive: true });
|
|
2030
|
+
const targetPath = join(targetDir, `${name}.md`);
|
|
2031
|
+
if (existsSync(targetPath)) {
|
|
2019
2032
|
const overwrite = await ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`);
|
|
2020
2033
|
if (!overwrite)
|
|
2021
2034
|
return;
|
|
@@ -2030,12 +2043,12 @@ ${systemPrompt}
|
|
|
2030
2043
|
maxConcurrent: manager.getMaxConcurrent(),
|
|
2031
2044
|
// 0 = unlimited — per SubagentsSettings.defaultMaxTurns docstring and
|
|
2032
2045
|
// normalizeMaxTurns() in agent-runner.ts (which maps 0 → undefined).
|
|
2033
|
-
defaultMaxTurns:
|
|
2034
|
-
graceTurns:
|
|
2046
|
+
defaultMaxTurns: getDefaultMaxTurns() ?? 0,
|
|
2047
|
+
graceTurns: getGraceTurns(),
|
|
2035
2048
|
defaultJoinMode: getDefaultJoinMode(),
|
|
2036
2049
|
schedulingEnabled: isSchedulingEnabled(),
|
|
2037
2050
|
scopeModels: isScopeModelsEnabled(),
|
|
2038
|
-
disableDefaultAgents:
|
|
2051
|
+
disableDefaultAgents: isDefaultsDisabled(),
|
|
2039
2052
|
toolDescriptionMode: getToolDescriptionMode(),
|
|
2040
2053
|
fleetView: isFleetViewEnabled(),
|
|
2041
2054
|
widgetMode: getWidgetMode(),
|
|
@@ -2046,8 +2059,8 @@ ${systemPrompt}
|
|
|
2046
2059
|
async function showSettings(ctx) {
|
|
2047
2060
|
function buildItems() {
|
|
2048
2061
|
const mc = manager.getMaxConcurrent();
|
|
2049
|
-
const dmt =
|
|
2050
|
-
const gt =
|
|
2062
|
+
const dmt = getDefaultMaxTurns() ?? 0;
|
|
2063
|
+
const gt = getGraceTurns();
|
|
2051
2064
|
return [
|
|
2052
2065
|
{
|
|
2053
2066
|
id: "maxConcurrent",
|
|
@@ -2095,7 +2108,7 @@ ${systemPrompt}
|
|
|
2095
2108
|
id: "disableDefaultAgents",
|
|
2096
2109
|
label: "Disable defaults",
|
|
2097
2110
|
description: "Hide built-in agents (general-purpose, Explore, Plan) — custom agents are unaffected",
|
|
2098
|
-
currentValue:
|
|
2111
|
+
currentValue: isDefaultsDisabled() ? "on" : "off",
|
|
2099
2112
|
values: ["on", "off"],
|
|
2100
2113
|
},
|
|
2101
2114
|
{
|
|
@@ -2139,18 +2152,18 @@ ${systemPrompt}
|
|
|
2139
2152
|
else if (id === "defaultMaxTurns") {
|
|
2140
2153
|
const n = parseInt(value, 10);
|
|
2141
2154
|
if (n === 0) {
|
|
2142
|
-
|
|
2155
|
+
setDefaultMaxTurns(undefined);
|
|
2143
2156
|
notifyApplied(ctx, "Default max turns set to unlimited");
|
|
2144
2157
|
}
|
|
2145
2158
|
else if (n >= 1) {
|
|
2146
|
-
|
|
2159
|
+
setDefaultMaxTurns(n);
|
|
2147
2160
|
notifyApplied(ctx, `Default max turns set to ${n}`);
|
|
2148
2161
|
}
|
|
2149
2162
|
}
|
|
2150
2163
|
else if (id === "graceTurns") {
|
|
2151
2164
|
const n = parseInt(value, 10);
|
|
2152
2165
|
if (n >= 1) {
|
|
2153
|
-
|
|
2166
|
+
setGraceTurns(n);
|
|
2154
2167
|
notifyApplied(ctx, `Grace turns set to ${n}`);
|
|
2155
2168
|
}
|
|
2156
2169
|
}
|
|
@@ -2205,26 +2218,26 @@ ${systemPrompt}
|
|
|
2205
2218
|
let currentIndex = 0;
|
|
2206
2219
|
const result = await ctx.ui.custom((_tui, _theme, _kb, done) => {
|
|
2207
2220
|
const items = buildItems();
|
|
2208
|
-
list = new
|
|
2221
|
+
list = new SettingsList(items, items.length + 2, getSettingsListTheme(), (id, newValue) => {
|
|
2209
2222
|
applyValue(id, newValue);
|
|
2210
2223
|
}, () => done(undefined));
|
|
2211
|
-
const container = new
|
|
2212
|
-
container.addChild(new
|
|
2213
|
-
container.addChild(new
|
|
2224
|
+
const container = new Container();
|
|
2225
|
+
container.addChild(new Text("⚙ Subagent Settings", 0, 0));
|
|
2226
|
+
container.addChild(new Spacer(1));
|
|
2214
2227
|
container.addChild(list);
|
|
2215
2228
|
return {
|
|
2216
2229
|
render: (w) => container.render(w),
|
|
2217
2230
|
invalidate: () => container.invalidate(),
|
|
2218
2231
|
handleInput: (data) => {
|
|
2219
2232
|
// Track navigation so Enter knows the current field
|
|
2220
|
-
if (
|
|
2233
|
+
if (matchesKey(data, "up")) {
|
|
2221
2234
|
currentIndex = Math.max(0, currentIndex - 1);
|
|
2222
2235
|
}
|
|
2223
|
-
else if (
|
|
2236
|
+
else if (matchesKey(data, "down")) {
|
|
2224
2237
|
currentIndex = Math.min(items.length - 1, currentIndex + 1);
|
|
2225
2238
|
}
|
|
2226
2239
|
// Enter on numeric field → close and prompt for typed input
|
|
2227
|
-
if (
|
|
2240
|
+
if (matchesKey(data, Key.enter) && NUMERIC_IDS.has(items[currentIndex].id)) {
|
|
2228
2241
|
done(items[currentIndex].id);
|
|
2229
2242
|
return;
|
|
2230
2243
|
}
|
|
@@ -2237,8 +2250,8 @@ ${systemPrompt}
|
|
|
2237
2250
|
const current = result === "maxConcurrent"
|
|
2238
2251
|
? String(manager.getMaxConcurrent())
|
|
2239
2252
|
: result === "defaultMaxTurns"
|
|
2240
|
-
? String(
|
|
2241
|
-
: String(
|
|
2253
|
+
? String(getDefaultMaxTurns() ?? 0)
|
|
2254
|
+
: String(getGraceTurns());
|
|
2242
2255
|
const label = result === "maxConcurrent"
|
|
2243
2256
|
? "Max concurrency (1+)"
|
|
2244
2257
|
: result === "defaultMaxTurns"
|
|
@@ -2265,7 +2278,7 @@ ${systemPrompt}
|
|
|
2265
2278
|
// to warning so users aren't silently reverted on restart. Event fires regardless
|
|
2266
2279
|
// of outcome so listeners see the in-memory change.
|
|
2267
2280
|
function notifyApplied(ctx, successMsg) {
|
|
2268
|
-
const { message, level } =
|
|
2281
|
+
const { message, level } = saveAndEmitChanged(snapshotSettings(), successMsg, (event, payload) => pi.events.emit(event, payload));
|
|
2269
2282
|
ctx.ui.notify(message, level);
|
|
2270
2283
|
}
|
|
2271
2284
|
pi.registerCommand("agents", {
|
|
@@ -2273,3 +2286,4 @@ ${systemPrompt}
|
|
|
2273
2286
|
handler: async (_args, ctx) => { await showAgentsMenu(ctx); },
|
|
2274
2287
|
});
|
|
2275
2288
|
}
|
|
2289
|
+
//# sourceMappingURL=index.js.map
|