@herbertgao/pi-subagents 0.15.5 → 0.16.1
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 +14 -0
- package/README.md +46 -25
- package/examples/agent-tool-description.md +5 -5
- package/package.json +7 -7
- package/src/agent-file-toggle.ts +7 -1
- package/src/agent-manager.ts +48 -19
- package/src/agent-runner.ts +17 -13
- package/src/cross-extension-rpc.ts +15 -4
- package/src/custom-agents.ts +26 -2
- package/src/index.ts +235 -40
- package/src/invocation-config.ts +101 -3
- package/src/nested-tools.ts +18 -4
- package/src/settings.ts +99 -0
- package/src/types.ts +22 -3
- package/src/ui/agent-widget.ts +51 -3
- package/src/ui/conversation-viewer.ts +25 -3
- package/src/ui/fleet-list.ts +21 -6
- package/src/usage.ts +129 -1
- package/src/worktree.ts +20 -0
package/src/index.ts
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
getDefaultMaxTurns,
|
|
49
49
|
getGraceTurns,
|
|
50
50
|
normalizeMaxTurns,
|
|
51
|
+
resolveEffectiveMaxTurns,
|
|
51
52
|
SUBAGENT_TOOL_NAMES,
|
|
52
53
|
setDefaultMaxTurns,
|
|
53
54
|
setGraceTurns,
|
|
@@ -72,6 +73,7 @@ import { type RpcHandle, registerRpcHandlers } from "./cross-extension-rpc.js"
|
|
|
72
73
|
import { loadCustomAgents } from "./custom-agents.js"
|
|
73
74
|
import { GroupJoinManager } from "./group-join.js"
|
|
74
75
|
import {
|
|
76
|
+
isolationParam,
|
|
75
77
|
resolveAgentInvocationConfig,
|
|
76
78
|
resolveJoinMode,
|
|
77
79
|
} from "./invocation-config.js"
|
|
@@ -120,6 +122,7 @@ import {
|
|
|
120
122
|
buildInvocationTags,
|
|
121
123
|
describeActivity,
|
|
122
124
|
fgPreservingNestedStyles,
|
|
125
|
+
formatCost,
|
|
123
126
|
formatDuration,
|
|
124
127
|
formatMs,
|
|
125
128
|
formatTokens,
|
|
@@ -134,11 +137,17 @@ import { FleetList, type FleetUICtx } from "./ui/fleet-list.js"
|
|
|
134
137
|
import { showSchedulesMenu } from "./ui/schedule-menu.js"
|
|
135
138
|
import { selectItem } from "./ui/select-item.js"
|
|
136
139
|
import {
|
|
137
|
-
|
|
140
|
+
getLifetimeCost,
|
|
138
141
|
getLifetimeTotal,
|
|
139
142
|
getSessionContextPercent,
|
|
140
143
|
type LifetimeUsage,
|
|
144
|
+
PendingUsagePool,
|
|
145
|
+
toReportedUsage,
|
|
141
146
|
} from "./usage.js"
|
|
147
|
+
import {
|
|
148
|
+
isWorktreeIsolationEnabled,
|
|
149
|
+
setWorktreeIsolationEnabled,
|
|
150
|
+
} from "./worktree.js"
|
|
142
151
|
|
|
143
152
|
// ---- Shared helpers ----
|
|
144
153
|
|
|
@@ -186,7 +195,6 @@ function createActivityTracker(maxTurns?: number, onStreamUpdate?: () => void) {
|
|
|
186
195
|
maxTurns,
|
|
187
196
|
responseText: "",
|
|
188
197
|
session: undefined,
|
|
189
|
-
lifetimeUsage: { input: 0, output: 0, cacheWrite: 0 },
|
|
190
198
|
}
|
|
191
199
|
|
|
192
200
|
const callbacks = {
|
|
@@ -218,12 +226,7 @@ function createActivityTracker(maxTurns?: number, onStreamUpdate?: () => void) {
|
|
|
218
226
|
onSessionCreated: (session: any) => {
|
|
219
227
|
state.session = session
|
|
220
228
|
},
|
|
221
|
-
onAssistantUsage: (
|
|
222
|
-
input: number
|
|
223
|
-
output: number
|
|
224
|
-
cacheWrite: number
|
|
225
|
-
}) => {
|
|
226
|
-
addUsage(state.lifetimeUsage, usage)
|
|
229
|
+
onAssistantUsage: (_usage: LifetimeUsage) => {
|
|
227
230
|
onStreamUpdate?.()
|
|
228
231
|
},
|
|
229
232
|
}
|
|
@@ -273,6 +276,7 @@ function escapeXml(s: string): string {
|
|
|
273
276
|
function formatTaskNotification(
|
|
274
277
|
record: AgentRecord,
|
|
275
278
|
resultMaxLen: number,
|
|
279
|
+
showCost = false,
|
|
276
280
|
): string {
|
|
277
281
|
const status = getStatusLabel(record.status, record.error)
|
|
278
282
|
const durationMs = record.completedAt
|
|
@@ -287,6 +291,11 @@ function formatTaskNotification(
|
|
|
287
291
|
const compactXml = record.compactionCount
|
|
288
292
|
? `<compactions>${record.compactionCount}</compactions>`
|
|
289
293
|
: ""
|
|
294
|
+
const cost = showCost ? getLifetimeCost(record.lifetimeUsage) : 0
|
|
295
|
+
const costXml =
|
|
296
|
+
cost > 0
|
|
297
|
+
? `<estimated_cost_usd>${cost.toFixed(4)}</estimated_cost_usd>`
|
|
298
|
+
: ""
|
|
290
299
|
|
|
291
300
|
const resultPreview = record.result
|
|
292
301
|
? record.result.length > resultMaxLen
|
|
@@ -307,7 +316,7 @@ function formatTaskNotification(
|
|
|
307
316
|
`<status>${escapeXml(status)}</status>`,
|
|
308
317
|
`<summary>Agent "${escapeXml(record.description)}" ${record.status}${getStatusNote(record.status)}</summary>`,
|
|
309
318
|
`<result>${escapeXml(resultPreview)}</result>`,
|
|
310
|
-
`<usage><total_tokens>${totalTokens}</total_tokens><tool_uses>${record.toolUses}</tool_uses>${ctxXml}${compactXml}<duration_ms>${durationMs}</duration_ms></usage>`,
|
|
319
|
+
`<usage><total_tokens>${totalTokens}</total_tokens><tool_uses>${record.toolUses}</tool_uses>${ctxXml}${compactXml}${costXml}<duration_ms>${durationMs}</duration_ms></usage>`,
|
|
311
320
|
`</task-notification>`,
|
|
312
321
|
]
|
|
313
322
|
.filter(Boolean)
|
|
@@ -337,6 +346,7 @@ function buildDetails(
|
|
|
337
346
|
...base,
|
|
338
347
|
toolUses: record.toolUses,
|
|
339
348
|
tokens: formatLifetimeTokens(record),
|
|
349
|
+
cost: getLifetimeCost(record.lifetimeUsage),
|
|
340
350
|
turnCount: activity?.turnCount,
|
|
341
351
|
maxTurns: activity?.maxTurns,
|
|
342
352
|
durationMs: (record.completedAt ?? Date.now()) - record.startedAt,
|
|
@@ -363,6 +373,7 @@ function buildNotificationDetails(
|
|
|
363
373
|
turnCount: activity?.turnCount ?? 0,
|
|
364
374
|
maxTurns: activity?.maxTurns,
|
|
365
375
|
totalTokens,
|
|
376
|
+
totalCost: getLifetimeCost(record.lifetimeUsage),
|
|
366
377
|
durationMs: record.completedAt ? record.completedAt - record.startedAt : 0,
|
|
367
378
|
outputFile: record.outputFile,
|
|
368
379
|
error: record.error,
|
|
@@ -445,6 +456,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
445
456
|
if (d.toolUses > 0)
|
|
446
457
|
parts.push(`${d.toolUses} tool use${d.toolUses === 1 ? "" : "s"}`)
|
|
447
458
|
if (d.totalTokens > 0) parts.push(formatTokens(d.totalTokens))
|
|
459
|
+
if (showCost) {
|
|
460
|
+
const costText = formatCost(d.totalCost ?? 0)
|
|
461
|
+
if (costText) parts.push(costText)
|
|
462
|
+
}
|
|
448
463
|
if (d.durationMs > 0) parts.push(formatMs(d.durationMs))
|
|
449
464
|
if (parts.length) {
|
|
450
465
|
line +=
|
|
@@ -472,7 +487,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
472
487
|
}
|
|
473
488
|
|
|
474
489
|
const all = [d, ...(d.others ?? [])]
|
|
475
|
-
|
|
490
|
+
const rendered = all.map(renderOne)
|
|
491
|
+
if (showCost && all.length > 1) {
|
|
492
|
+
const total = formatCost(
|
|
493
|
+
all.reduce((sum, item) => sum + (item.totalCost ?? 0), 0),
|
|
494
|
+
)
|
|
495
|
+
if (total) {
|
|
496
|
+
const tokens = all.reduce((sum, item) => sum + item.totalTokens, 0)
|
|
497
|
+
rendered.unshift(
|
|
498
|
+
theme.fg(
|
|
499
|
+
"dim",
|
|
500
|
+
`${all.length} agents · ${formatTokens(tokens)} · ${total}`,
|
|
501
|
+
),
|
|
502
|
+
)
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return new Text(rendered.join("\n"), 0, 0)
|
|
476
506
|
},
|
|
477
507
|
)
|
|
478
508
|
|
|
@@ -492,6 +522,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
492
522
|
// ---- Agent activity tracking + widget ----
|
|
493
523
|
const agentActivity = new Map<string, AgentActivity>()
|
|
494
524
|
|
|
525
|
+
// ---- Usage reporting ----
|
|
526
|
+
let reportUsage = false
|
|
527
|
+
function isReportUsageEnabled(): boolean {
|
|
528
|
+
return reportUsage
|
|
529
|
+
}
|
|
530
|
+
const pendingUsage = new PendingUsagePool()
|
|
531
|
+
function setReportUsage(enabled: boolean): void {
|
|
532
|
+
reportUsage = enabled
|
|
533
|
+
if (!enabled) pendingUsage.drain()
|
|
534
|
+
}
|
|
535
|
+
let showCost = false
|
|
536
|
+
function isShowCostEnabled(): boolean {
|
|
537
|
+
return showCost
|
|
538
|
+
}
|
|
539
|
+
function setShowCost(enabled: boolean): void {
|
|
540
|
+
showCost = enabled
|
|
541
|
+
widget.update()
|
|
542
|
+
fleet.update()
|
|
543
|
+
}
|
|
544
|
+
|
|
495
545
|
// ---- Cancellable pending notifications ----
|
|
496
546
|
// Holds notifications briefly so get_subagent_result can cancel them
|
|
497
547
|
// before they reach pi.sendMessage (fire-and-forget).
|
|
@@ -528,7 +578,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
528
578
|
function emitIndividualNudge(record: AgentRecord) {
|
|
529
579
|
if (record.resultConsumed) return // re-check at send time
|
|
530
580
|
|
|
531
|
-
const notification = formatTaskNotification(record, 500)
|
|
581
|
+
const notification = formatTaskNotification(record, 500, showCost)
|
|
532
582
|
const footer = record.outputFile
|
|
533
583
|
? `\nFull transcript available at: ${record.outputFile}`
|
|
534
584
|
: ""
|
|
@@ -574,7 +624,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
574
624
|
}
|
|
575
625
|
|
|
576
626
|
const notifications = unconsumed
|
|
577
|
-
.map((r) => formatTaskNotification(r, 300))
|
|
627
|
+
.map((r) => formatTaskNotification(r, 300, showCost))
|
|
578
628
|
.join("\n\n")
|
|
579
629
|
const label = partial
|
|
580
630
|
? `${unconsumed.length} agent(s) finished (partial — others still running)`
|
|
@@ -618,6 +668,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
618
668
|
const total = getLifetimeTotal(u)
|
|
619
669
|
const tokens =
|
|
620
670
|
total > 0 ? { input: u.input, output: u.output, total } : undefined
|
|
671
|
+
const usage = toReportedUsage(u)
|
|
621
672
|
return {
|
|
622
673
|
id: record.id,
|
|
623
674
|
type: record.type,
|
|
@@ -628,6 +679,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
628
679
|
toolUses: record.toolUses,
|
|
629
680
|
durationMs,
|
|
630
681
|
tokens,
|
|
682
|
+
usage,
|
|
631
683
|
}
|
|
632
684
|
}
|
|
633
685
|
|
|
@@ -716,6 +768,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
716
768
|
compactionCount: record.compactionCount,
|
|
717
769
|
})
|
|
718
770
|
},
|
|
771
|
+
(_record, usage) => {
|
|
772
|
+
if (reportUsage) pendingUsage.add(usage)
|
|
773
|
+
},
|
|
719
774
|
)
|
|
720
775
|
|
|
721
776
|
// Expose manager via Symbol.for() global registry for cross-package access.
|
|
@@ -753,7 +808,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
753
808
|
reloadCustomAgents()
|
|
754
809
|
const dispatch = resolveSpawnType(type)
|
|
755
810
|
if (!dispatch.ok) throw new Error(dispatch.message)
|
|
756
|
-
|
|
811
|
+
const { state, callbacks } = createActivityTracker(
|
|
812
|
+
resolveEffectiveMaxTurns(dispatch.type, safeOptions.maxTurns),
|
|
813
|
+
)
|
|
814
|
+
const id = manager.spawn(piRef, ctxRef, dispatch.type, prompt, {
|
|
815
|
+
...safeOptions,
|
|
816
|
+
...callbacks,
|
|
817
|
+
})
|
|
818
|
+
agentActivity.set(id, state)
|
|
819
|
+
return id
|
|
757
820
|
}
|
|
758
821
|
const registryEntry = {
|
|
759
822
|
waitForAll: () => manager.waitForAll(),
|
|
@@ -827,6 +890,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
827
890
|
const record = manager.getRecord(id)
|
|
828
891
|
return !record?.parentAgentId && manager.abort(id)
|
|
829
892
|
},
|
|
893
|
+
consumeResult: (id) => {
|
|
894
|
+
const record = manager.getRecord(id)
|
|
895
|
+
if (
|
|
896
|
+
!record ||
|
|
897
|
+
record.parentAgentId ||
|
|
898
|
+
record.status === "running" ||
|
|
899
|
+
record.status === "queued"
|
|
900
|
+
) {
|
|
901
|
+
return false
|
|
902
|
+
}
|
|
903
|
+
record.resultConsumed = true
|
|
904
|
+
cancelNudge(record.id)
|
|
905
|
+
return true
|
|
906
|
+
},
|
|
830
907
|
},
|
|
831
908
|
})
|
|
832
909
|
// Broadcast readiness so extensions loaded alongside us can discover us.
|
|
@@ -848,6 +925,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
848
925
|
rpcHandle?.unsubSpawn()
|
|
849
926
|
rpcHandle?.unsubStop()
|
|
850
927
|
rpcHandle?.unsubPing()
|
|
928
|
+
rpcHandle?.unsubConsume()
|
|
851
929
|
rpcHandle = undefined
|
|
852
930
|
currentCtx = undefined
|
|
853
931
|
// Only release the global slot if this activation claimed it — a child
|
|
@@ -863,7 +941,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
863
941
|
for (const timer of pendingNudges.values()) clearTimeout(timer)
|
|
864
942
|
pendingNudges.clear()
|
|
865
943
|
fleet.dispose()
|
|
866
|
-
manager.dispose()
|
|
944
|
+
await manager.dispose()
|
|
867
945
|
})
|
|
868
946
|
|
|
869
947
|
// Live widget: show running agents above editor.
|
|
@@ -875,14 +953,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
875
953
|
function getWidgetMode(): WidgetMode {
|
|
876
954
|
return widgetMode
|
|
877
955
|
}
|
|
878
|
-
const widget = new AgentWidget(
|
|
956
|
+
const widget = new AgentWidget(
|
|
957
|
+
manager,
|
|
958
|
+
agentActivity,
|
|
959
|
+
getWidgetMode,
|
|
960
|
+
isShowCostEnabled,
|
|
961
|
+
)
|
|
879
962
|
function setWidgetMode(m: WidgetMode): void {
|
|
880
963
|
widgetMode = m
|
|
881
964
|
widget.update()
|
|
882
965
|
}
|
|
883
966
|
|
|
884
967
|
// Claude Code-style FleetView: navigable list of main + subagents below the editor.
|
|
885
|
-
const fleet = new FleetList(manager, agentActivity)
|
|
968
|
+
const fleet = new FleetList(manager, agentActivity, isShowCostEnabled)
|
|
886
969
|
let fleetViewEnabled = true
|
|
887
970
|
function isFleetViewEnabled(): boolean {
|
|
888
971
|
return fleetViewEnabled
|
|
@@ -906,6 +989,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
906
989
|
defaultJoinMode = mode
|
|
907
990
|
}
|
|
908
991
|
|
|
992
|
+
let backgroundByDefault = true
|
|
993
|
+
function getBackgroundByDefault(): boolean {
|
|
994
|
+
return backgroundByDefault
|
|
995
|
+
}
|
|
996
|
+
function setBackgroundByDefault(enabled: boolean): void {
|
|
997
|
+
backgroundByDefault = enabled
|
|
998
|
+
}
|
|
999
|
+
|
|
909
1000
|
// Master switch for the schedule subagent feature. Defaults to enabled.
|
|
910
1001
|
// Read once at extension init (before tool registration) so the Agent tool's
|
|
911
1002
|
// param schema reflects the persisted setting. Runtime toggles via /agents
|
|
@@ -992,6 +1083,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
992
1083
|
// Grab UI context from first tool execution + clear lingering widget on new turn
|
|
993
1084
|
pi.on("tool_execution_start", async (_event, ctx) => {
|
|
994
1085
|
widget.setUICtx(ctx.ui as UICtx)
|
|
1086
|
+
// SAFETY: both UI adapters receive the same Pi ExtensionContext UI surface.
|
|
995
1087
|
fleet.setUICtx(ctx.ui as unknown as FleetUICtx)
|
|
996
1088
|
widget.onTurnStart()
|
|
997
1089
|
})
|
|
@@ -1044,6 +1136,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1044
1136
|
setDefaultMaxTurns,
|
|
1045
1137
|
setGraceTurns,
|
|
1046
1138
|
setDefaultJoinMode,
|
|
1139
|
+
setBackgroundByDefault,
|
|
1047
1140
|
setSchedulingEnabled,
|
|
1048
1141
|
setScopeModels: setScopeModelsEnabled,
|
|
1049
1142
|
setStrictAgentFiles: (enabled) => {
|
|
@@ -1054,8 +1147,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1054
1147
|
setFleetView: setFleetViewEnabled,
|
|
1055
1148
|
setWidgetMode: setWidgetMode,
|
|
1056
1149
|
setOutputTranscript: setOutputTranscriptDefault,
|
|
1150
|
+
setWorktreeIsolation: setWorktreeIsolationEnabled,
|
|
1057
1151
|
setMaxSubagentDepth: setMaxSubagentDepth,
|
|
1058
1152
|
setFallbackSubagent: setFallbackSubagent,
|
|
1153
|
+
setReportUsage,
|
|
1154
|
+
setShowCost,
|
|
1059
1155
|
},
|
|
1060
1156
|
(event, payload) => pi.events.emit(event, payload),
|
|
1061
1157
|
)
|
|
@@ -1084,6 +1180,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
1084
1180
|
? `\n- Use \`schedule\` only when the user explicitly asked for scheduled / recurring / delayed execution (e.g. "every Monday", "in an hour"). Don't auto-schedule from vague intent like "monitor X" — run once now or ask.`
|
|
1085
1181
|
: ""
|
|
1086
1182
|
|
|
1183
|
+
const isolationGuideline = isWorktreeIsolationEnabled()
|
|
1184
|
+
? `\n- Use isolation: "worktree" to give the agent its own git worktree; leave it unset, or pass "off", for none. A worktree cannot see uncommitted or staged changes in the main checkout.`
|
|
1185
|
+
: ""
|
|
1186
|
+
const isolationCompactGuideline = isWorktreeIsolationEnabled()
|
|
1187
|
+
? `\n- isolation: "worktree" gives the agent its own git worktree; "off" leaves it in the current checkout.`
|
|
1188
|
+
: ""
|
|
1189
|
+
|
|
1087
1190
|
// Compact Agent tool description (#91, `toolDescriptionMode: "compact"`) —
|
|
1088
1191
|
// the same load-bearing facts as the full version at ~75% fewer tokens, for
|
|
1089
1192
|
// small/local models. Per-option details live in the param descriptions.
|
|
@@ -1094,10 +1197,10 @@ Custom agents: .pi/agents/<name>.md (project) or ${getAgentDir()}/agents/<name>.
|
|
|
1094
1197
|
|
|
1095
1198
|
Notes:
|
|
1096
1199
|
- description: 3-5 words (shown in UI). Prompts must be self-contained — the agent has not seen this conversation.
|
|
1097
|
-
- Parallel work: one message, multiple Agent calls
|
|
1200
|
+
- Parallel work: one message, multiple Agent calls — they run concurrently.
|
|
1201
|
+
- Subagents run in the background by default; you'll be notified when one completes. Pass run_in_background: false only when your next action depends on its result.
|
|
1098
1202
|
- The result is not shown to the user — summarize it for them. Verify an agent's claimed code changes before reporting work done.
|
|
1099
|
-
- resume continues a previous agent by ID; steer_subagent messages a running one
|
|
1100
|
-
- isolation: "worktree" runs the agent in an isolated git worktree; changes land on a branch.`
|
|
1203
|
+
- resume continues a previous agent by ID; steer_subagent messages a running one.${isolationCompactGuideline}`
|
|
1101
1204
|
|
|
1102
1205
|
const fullAgentToolDescription = `Launch a new agent to handle complex, multi-step tasks autonomously. Each agent type has specific capabilities and tools available to it.
|
|
1103
1206
|
|
|
@@ -1115,19 +1218,19 @@ If the target is already known, use a direct tool — \`read\` for a known path,
|
|
|
1115
1218
|
## Usage notes
|
|
1116
1219
|
|
|
1117
1220
|
- Always include a short (3-5 word) description summarizing what the agent will do (shown in UI).
|
|
1118
|
-
- When you launch multiple agents for independent work, send them in a single message with multiple tool uses
|
|
1221
|
+
- When you launch multiple agents for independent work, send them in a single message with multiple tool uses so they run concurrently.
|
|
1119
1222
|
- When the agent is done, it returns a single message back to you. The result is not visible to the user — to show the user, send a text message with a concise summary.
|
|
1120
1223
|
- Trust but verify: an agent's summary describes what it intended to do, not necessarily what it did. When an agent writes or edits code, check the actual changes before reporting work as done.
|
|
1121
|
-
-
|
|
1122
|
-
-
|
|
1224
|
+
- Agents run in the background by default. You will be notified when one completes — do NOT poll or sleep waiting for it.
|
|
1225
|
+
- Pass \`run_in_background: false\` only when your very next action depends on the result and nothing else could usefully happen while it runs.
|
|
1226
|
+
- Never fabricate or predict a pending agent's results; if asked before completion, say it is still running.
|
|
1123
1227
|
- Use resume with an agent ID to continue a previous agent's work. A new (non-resume) Agent call starts a fresh agent with no memory of prior runs, so the prompt must be self-contained.
|
|
1124
1228
|
- Use steer_subagent to send mid-run messages to a running background agent.
|
|
1125
1229
|
- Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, etc.), since it is not aware of the user's intent.
|
|
1126
1230
|
- If an agent's description says it should be used proactively, try to use it without the user having to ask for it first.
|
|
1127
1231
|
- Use model to specify a different model (as "provider/modelId", or fuzzy e.g. "haiku", "sonnet").
|
|
1128
1232
|
- Use thinking to control extended thinking level.
|
|
1129
|
-
- Use inherit_context if the agent needs the parent conversation history
|
|
1130
|
-
- Use isolation: "worktree" to run the agent in an isolated git worktree (safe parallel file modifications). The worktree is automatically cleaned up if the agent makes no changes; otherwise the path and branch are returned in the result.${scheduleGuideline}
|
|
1233
|
+
- Use inherit_context if the agent needs the parent conversation history.${isolationGuideline}${scheduleGuideline}
|
|
1131
1234
|
|
|
1132
1235
|
## Writing the prompt
|
|
1133
1236
|
|
|
@@ -1151,6 +1254,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1151
1254
|
typeList: buildTypeListText,
|
|
1152
1255
|
compactTypeList: buildCompactTypeListText,
|
|
1153
1256
|
agentDir: getAgentDir,
|
|
1257
|
+
isolationGuideline: () => isolationGuideline,
|
|
1154
1258
|
scheduleGuideline: () => scheduleGuideline,
|
|
1155
1259
|
}
|
|
1156
1260
|
// Replacement callback (not a string) — agent descriptions may contain `$&` etc.
|
|
@@ -1195,7 +1299,19 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1195
1299
|
return fullAgentToolDescription
|
|
1196
1300
|
})()
|
|
1197
1301
|
|
|
1198
|
-
|
|
1302
|
+
function registerToolReportingUsage(tool: any): void {
|
|
1303
|
+
pi.registerTool({
|
|
1304
|
+
...tool,
|
|
1305
|
+
execute: async (toolCallId: string | undefined, ...args: any[]) => {
|
|
1306
|
+
const result = await tool.execute(toolCallId, ...args)
|
|
1307
|
+
if (!reportUsage || !toolCallId) return result
|
|
1308
|
+
const usage = pendingUsage.drain()
|
|
1309
|
+
return usage ? { ...result, usage } : result
|
|
1310
|
+
},
|
|
1311
|
+
})
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
registerToolReportingUsage(
|
|
1199
1315
|
defineTool({
|
|
1200
1316
|
name: SUBAGENT_TOOL_NAMES.AGENT,
|
|
1201
1317
|
label: "Agent",
|
|
@@ -1240,13 +1356,13 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1240
1356
|
run_in_background: Type.Optional(
|
|
1241
1357
|
Type.Boolean({
|
|
1242
1358
|
description:
|
|
1243
|
-
"
|
|
1359
|
+
"Defaults to true: returns the agent ID immediately and notifies on completion. Set false to block and return the full output inline.",
|
|
1244
1360
|
}),
|
|
1245
1361
|
),
|
|
1246
1362
|
resume: Type.Optional(
|
|
1247
1363
|
Type.String({
|
|
1248
1364
|
description:
|
|
1249
|
-
"Optional agent ID to resume from.
|
|
1365
|
+
"Optional agent ID to resume from. Resumes detached by default; pass run_in_background: false to block and get the result inline. An agent can only be resumed after its current run finishes.",
|
|
1250
1366
|
}),
|
|
1251
1367
|
),
|
|
1252
1368
|
isolated: Type.Optional(
|
|
@@ -1261,12 +1377,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1261
1377
|
"If true, fork parent conversation into the agent. Default: false (fresh context).",
|
|
1262
1378
|
}),
|
|
1263
1379
|
),
|
|
1264
|
-
|
|
1265
|
-
Type.Literal("worktree", {
|
|
1266
|
-
description:
|
|
1267
|
-
'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.',
|
|
1268
|
-
}),
|
|
1269
|
-
),
|
|
1380
|
+
...isolationParam(isWorktreeIsolationEnabled()),
|
|
1270
1381
|
...scheduleParam,
|
|
1271
1382
|
}),
|
|
1272
1383
|
|
|
@@ -1325,6 +1436,10 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1325
1436
|
if (d.toolUses > 0)
|
|
1326
1437
|
parts.push(`${d.toolUses} tool use${d.toolUses === 1 ? "" : "s"}`)
|
|
1327
1438
|
if (d.tokens) parts.push(d.tokens)
|
|
1439
|
+
if (showCost) {
|
|
1440
|
+
const costText = formatCost(d.cost ?? 0)
|
|
1441
|
+
if (costText) parts.push(costText)
|
|
1442
|
+
}
|
|
1328
1443
|
return parts
|
|
1329
1444
|
.map((p) => fgPreservingNestedStyles(theme, "dim", p))
|
|
1330
1445
|
.join(" " + theme.fg("dim", "·") + " ")
|
|
@@ -1460,6 +1575,10 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1460
1575
|
const resolvedConfig = resolveAgentInvocationConfig(
|
|
1461
1576
|
customConfig,
|
|
1462
1577
|
params,
|
|
1578
|
+
{
|
|
1579
|
+
worktreeAllowed: isWorktreeIsolationEnabled(),
|
|
1580
|
+
defaultRunInBackground: getBackgroundByDefault(),
|
|
1581
|
+
},
|
|
1463
1582
|
)
|
|
1464
1583
|
|
|
1465
1584
|
// Resolve model from agent config first; tool-call params only fill gaps.
|
|
@@ -1890,7 +2009,10 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1890
2009
|
const details: AgentDetails = {
|
|
1891
2010
|
...detailBase,
|
|
1892
2011
|
toolUses: fgState.toolUses,
|
|
1893
|
-
tokens: formatLifetimeTokens(
|
|
2012
|
+
tokens: fgId ? formatLifetimeTokens(manager.getRecord(fgId)!) : "",
|
|
2013
|
+
cost: fgId
|
|
2014
|
+
? getLifetimeCost(manager.getRecord(fgId)?.lifetimeUsage)
|
|
2015
|
+
: 0,
|
|
1894
2016
|
turnCount: fgState.turnCount,
|
|
1895
2017
|
maxTurns: fgState.maxTurns,
|
|
1896
2018
|
durationMs: Date.now() - startedAt,
|
|
@@ -1990,7 +2112,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1990
2112
|
}
|
|
1991
2113
|
|
|
1992
2114
|
// Get final token count
|
|
1993
|
-
const tokenText = formatLifetimeTokens(
|
|
2115
|
+
const tokenText = formatLifetimeTokens(record)
|
|
1994
2116
|
|
|
1995
2117
|
const details = buildDetails(detailBase, record, fgState, {
|
|
1996
2118
|
tokens: tokenText,
|
|
@@ -2007,6 +2129,10 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
2007
2129
|
const durationMs = (record.completedAt ?? Date.now()) - record.startedAt
|
|
2008
2130
|
const statsParts = [`${record.toolUses} tool uses`]
|
|
2009
2131
|
if (tokenText) statsParts.push(tokenText)
|
|
2132
|
+
if (showCost) {
|
|
2133
|
+
const costText = formatCost(getLifetimeCost(record.lifetimeUsage))
|
|
2134
|
+
if (costText) statsParts.push(costText)
|
|
2135
|
+
}
|
|
2010
2136
|
return textResult(
|
|
2011
2137
|
`${fallbackNote}Agent completed in ${formatMs(durationMs)} (${statsParts.join(", ")})${getForegroundOutcomeNote(record.status)}.\n\n` +
|
|
2012
2138
|
(record.result?.trim() || "No output."),
|
|
@@ -2018,7 +2144,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
2018
2144
|
|
|
2019
2145
|
// ---- get_subagent_result tool ----
|
|
2020
2146
|
|
|
2021
|
-
|
|
2147
|
+
registerToolReportingUsage(
|
|
2022
2148
|
defineTool({
|
|
2023
2149
|
name: SUBAGENT_TOOL_NAMES.GET_RESULT,
|
|
2024
2150
|
label: "Get Agent Result",
|
|
@@ -2077,6 +2203,10 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
2077
2203
|
const contextPercent = getSessionContextPercent(record.session)
|
|
2078
2204
|
const statsParts = [`Tool uses: ${record.toolUses}`]
|
|
2079
2205
|
if (tokens) statsParts.push(tokens)
|
|
2206
|
+
if (showCost) {
|
|
2207
|
+
const costText = formatCost(getLifetimeCost(record.lifetimeUsage))
|
|
2208
|
+
if (costText) statsParts.push(`Cost: ${costText}`)
|
|
2209
|
+
}
|
|
2080
2210
|
if (contextPercent !== null)
|
|
2081
2211
|
statsParts.push(`Context: ${Math.round(contextPercent)}%`)
|
|
2082
2212
|
if (record.compactionCount)
|
|
@@ -2118,7 +2248,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
2118
2248
|
|
|
2119
2249
|
// ---- steer_subagent tool ----
|
|
2120
2250
|
|
|
2121
|
-
|
|
2251
|
+
registerToolReportingUsage(
|
|
2122
2252
|
defineTool({
|
|
2123
2253
|
name: SUBAGENT_TOOL_NAMES.STEER,
|
|
2124
2254
|
label: "Steer Agent",
|
|
@@ -2422,6 +2552,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
2422
2552
|
},
|
|
2423
2553
|
keybindings,
|
|
2424
2554
|
(message: string) => manager.steer(record.id, message),
|
|
2555
|
+
showCost,
|
|
2425
2556
|
)
|
|
2426
2557
|
},
|
|
2427
2558
|
{
|
|
@@ -2680,11 +2811,14 @@ extensions: <true (inherit all MCP/extension tools), false (none), or comma-sepa
|
|
|
2680
2811
|
skills: <true (inherit all), false (none), or comma-separated skill names to preload into prompt. Default: true>
|
|
2681
2812
|
disallowed_tools: <comma-separated tool names to block, even if otherwise available. Omit for none>
|
|
2682
2813
|
inherit_context: <true to fork parent conversation into agent so it sees chat history. Default: false>
|
|
2683
|
-
run_in_background: <
|
|
2814
|
+
run_in_background: <pin this agent to background (true) or foreground (false). Omit to follow backgroundByDefault>
|
|
2684
2815
|
output_transcript: <false to write no transcript file or path for this agent. Independent of persist_session. Default: true>
|
|
2685
2816
|
isolated: <true for no extension/MCP tools, only built-in tools. Default: false>
|
|
2686
|
-
memory: <"user" (global), "project" (per-project), or "local" (gitignored per-project) for persistent memory. Omit for none
|
|
2687
|
-
|
|
2817
|
+
memory: <"user" (global), "project" (per-project), or "local" (gitignored per-project) for persistent memory. Omit for none>${
|
|
2818
|
+
isWorktreeIsolationEnabled()
|
|
2819
|
+
? `\nisolation: <"worktree" to run in an isolated git worktree; "off" to refuse one. Omit for normal>`
|
|
2820
|
+
: ""
|
|
2821
|
+
}
|
|
2688
2822
|
---
|
|
2689
2823
|
|
|
2690
2824
|
<system prompt body — instructions for the agent>
|
|
@@ -2839,6 +2973,7 @@ Write the file using the write tool. Only write the file, nothing else.`
|
|
|
2839
2973
|
defaultMaxTurns: getDefaultMaxTurns() ?? 0,
|
|
2840
2974
|
graceTurns: getGraceTurns(),
|
|
2841
2975
|
defaultJoinMode: getDefaultJoinMode(),
|
|
2976
|
+
backgroundByDefault: getBackgroundByDefault(),
|
|
2842
2977
|
schedulingEnabled: isSchedulingEnabled(),
|
|
2843
2978
|
scopeModels: isScopeModelsEnabled(),
|
|
2844
2979
|
strictAgentFiles,
|
|
@@ -2847,12 +2982,15 @@ Write the file using the write tool. Only write the file, nothing else.`
|
|
|
2847
2982
|
fleetView: isFleetViewEnabled(),
|
|
2848
2983
|
widgetMode: getWidgetMode(),
|
|
2849
2984
|
outputTranscript: getOutputTranscriptDefault(),
|
|
2985
|
+
worktreeIsolation: isWorktreeIsolationEnabled(),
|
|
2850
2986
|
maxSubagentDepth: getMaxSubagentDepth(),
|
|
2851
2987
|
// Deliberately NOT `?? "general-purpose"`: every settings change writes the
|
|
2852
2988
|
// whole snapshot, and materializing the implicit default would turn it into
|
|
2853
2989
|
// explicit configuration — which then fails loudly if general-purpose later
|
|
2854
2990
|
// goes away. undefined is dropped by JSON.stringify.
|
|
2855
2991
|
fallbackSubagent: getFallbackSubagent(),
|
|
2992
|
+
reportUsage: isReportUsageEnabled(),
|
|
2993
|
+
showCost: isShowCostEnabled(),
|
|
2856
2994
|
} satisfies SubagentsSettings
|
|
2857
2995
|
}
|
|
2858
2996
|
|
|
@@ -2929,6 +3067,14 @@ Write the file using the write tool. Only write the file, nothing else.`
|
|
|
2929
3067
|
currentValue: getDefaultJoinMode(),
|
|
2930
3068
|
values: ["smart", "async", "group"],
|
|
2931
3069
|
},
|
|
3070
|
+
{
|
|
3071
|
+
id: "backgroundByDefault",
|
|
3072
|
+
label: "Background by default",
|
|
3073
|
+
description:
|
|
3074
|
+
"Unqualified top-level Agent calls run detached (off = block and return inline)",
|
|
3075
|
+
currentValue: getBackgroundByDefault() ? "on" : "off",
|
|
3076
|
+
values: ["on", "off"],
|
|
3077
|
+
},
|
|
2932
3078
|
{
|
|
2933
3079
|
id: "schedulingEnabled",
|
|
2934
3080
|
label: "Scheduling",
|
|
@@ -2976,6 +3122,28 @@ Write the file using the write tool. Only write the file, nothing else.`
|
|
|
2976
3122
|
currentValue: getOutputTranscriptDefault() ? "on" : "off",
|
|
2977
3123
|
values: ["on", "off"],
|
|
2978
3124
|
},
|
|
3125
|
+
{
|
|
3126
|
+
id: "worktreeIsolation",
|
|
3127
|
+
label: "Worktree isolation",
|
|
3128
|
+
description:
|
|
3129
|
+
"Allow isolation: worktree (off removes the Agent parameter on next pi session)",
|
|
3130
|
+
currentValue: isWorktreeIsolationEnabled() ? "on" : "off",
|
|
3131
|
+
values: ["on", "off"],
|
|
3132
|
+
},
|
|
3133
|
+
{
|
|
3134
|
+
id: "reportUsage",
|
|
3135
|
+
label: "Report usage",
|
|
3136
|
+
description: "Include subagent spend in parent session accounting",
|
|
3137
|
+
currentValue: isReportUsageEnabled() ? "on" : "off",
|
|
3138
|
+
values: ["on", "off"],
|
|
3139
|
+
},
|
|
3140
|
+
{
|
|
3141
|
+
id: "showCost",
|
|
3142
|
+
label: "Show cost",
|
|
3143
|
+
description: "Show estimated USD cost beside subagent token counts",
|
|
3144
|
+
currentValue: isShowCostEnabled() ? "on" : "off",
|
|
3145
|
+
values: ["on", "off"],
|
|
3146
|
+
},
|
|
2979
3147
|
{
|
|
2980
3148
|
id: "fleetView",
|
|
2981
3149
|
label: "Fleet view",
|
|
@@ -3039,6 +3207,15 @@ Write the file using the write tool. Only write the file, nothing else.`
|
|
|
3039
3207
|
} else if (id === "joinMode") {
|
|
3040
3208
|
setDefaultJoinMode(value as JoinMode)
|
|
3041
3209
|
notifyApplied(ctx, `Default join mode set to ${value}`)
|
|
3210
|
+
} else if (id === "backgroundByDefault") {
|
|
3211
|
+
const enabled = value === "on"
|
|
3212
|
+
setBackgroundByDefault(enabled)
|
|
3213
|
+
notifyApplied(
|
|
3214
|
+
ctx,
|
|
3215
|
+
enabled
|
|
3216
|
+
? "Agent calls run in the background unless explicitly set false"
|
|
3217
|
+
: "Agent calls block unless explicitly set true",
|
|
3218
|
+
)
|
|
3042
3219
|
} else if (id === "schedulingEnabled") {
|
|
3043
3220
|
const enabled = value === "on"
|
|
3044
3221
|
if (enabled === isSchedulingEnabled()) {
|
|
@@ -3087,6 +3264,24 @@ Write the file using the write tool. Only write the file, nothing else.`
|
|
|
3087
3264
|
ctx,
|
|
3088
3265
|
`Output transcript ${enabled ? "enabled" : "disabled"} by default`,
|
|
3089
3266
|
)
|
|
3267
|
+
} else if (id === "worktreeIsolation") {
|
|
3268
|
+
const enabled = value === "on"
|
|
3269
|
+
setWorktreeIsolationEnabled(enabled)
|
|
3270
|
+
notifyApplied(
|
|
3271
|
+
ctx,
|
|
3272
|
+
`Worktree isolation ${enabled ? "enabled" : "disabled"}. Tool parameter updates on next pi session.`,
|
|
3273
|
+
)
|
|
3274
|
+
} else if (id === "reportUsage") {
|
|
3275
|
+
const enabled = value === "on"
|
|
3276
|
+
setReportUsage(enabled)
|
|
3277
|
+
notifyApplied(
|
|
3278
|
+
ctx,
|
|
3279
|
+
`Usage reporting ${enabled ? "enabled" : "disabled"}`,
|
|
3280
|
+
)
|
|
3281
|
+
} else if (id === "showCost") {
|
|
3282
|
+
const enabled = value === "on"
|
|
3283
|
+
setShowCost(enabled)
|
|
3284
|
+
notifyApplied(ctx, `Cost display ${enabled ? "enabled" : "disabled"}`)
|
|
3090
3285
|
} else if (id === "toolDescriptionMode") {
|
|
3091
3286
|
setToolDescriptionMode(value as ToolDescriptionMode)
|
|
3092
3287
|
notifyApplied(
|