@nklisch/pi-enhanced 0.2.7 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.linux-x64-gnu.node +0 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-x64-msvc.node +0 -0
- package/node_modules/@nklisch/pi-plugins/README.md +4 -2
- package/node_modules/@nklisch/pi-plugins/dist/hooks.js +81 -8
- package/node_modules/@nklisch/pi-plugins/dist/hooks.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/mcp.js +32 -1
- package/node_modules/@nklisch/pi-plugins/dist/mcp.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.d.ts +10 -4
- package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js +144 -10
- package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js +40 -14
- package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/CHANGELOG.md +19 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/README.md +75 -47
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/public.d.ts +171 -140
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/settings.d.ts +2 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/FORK-MAINTENANCE.md +8 -7
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/VISION.md +12 -10
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/architecture.md +111 -104
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/comparison-with-upstream.md +3 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/decisions/0004-reconsider-ui-direction.md +5 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/package.json +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/custom-agents.ts +31 -4
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/default-agents.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/invocation-config.ts +7 -4
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/index.ts +18 -9
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/layered-settings.ts +2 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/child-lifecycle.ts +10 -63
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/concurrency-limiter.ts +122 -76
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/create-subagent-session.ts +3 -5
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/lifecycle-interceptor.ts +4 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/run-listeners.ts +2 -23
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-manager.ts +177 -252
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-session.ts +90 -280
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-state.ts +153 -239
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +569 -700
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/workspace.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/notification.ts +78 -122
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/renderer.ts +15 -42
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/subagent-events-observer.ts +44 -95
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service-adapter.ts +157 -90
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service.ts +60 -68
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/conversation.ts +3 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query-source.ts +28 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query.ts +327 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/settings.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/agent-tool.ts +171 -249
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-tool.ts +51 -94
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/helpers.ts +9 -6
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/list-tool.ts +46 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/parent-tool-registry.ts +17 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/query-session-tool.ts +330 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/result-renderer.ts +27 -111
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/resume-tool.ts +105 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/spawn-config.ts +26 -8
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/steer-tool.ts +27 -111
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/stop-tool.ts +53 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/types.ts +11 -5
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts +33 -27
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts +6 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigation.ts +121 -10
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigator.ts +430 -112
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/subagents-settings.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/widget-renderer.ts +8 -9
- package/node_modules/@nklisch/pi-plugins/package.json +2 -2
- package/package.json +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/background-spawner.ts +0 -81
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/foreground-runner.ts +0 -149
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-report.ts +0 -77
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/agent-tool.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions -- Pi SDK types are
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions -- Pi SDK render types are intentionally narrow at this boundary */
|
|
2
|
+
|
|
2
3
|
import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
|
|
3
4
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import { Text } from "@earendil-works/pi-tui";
|
|
@@ -6,266 +7,187 @@ import { Type } from "@sinclair/typebox";
|
|
|
6
7
|
import { AgentTypeRegistry } from "#src/config/agent-types";
|
|
7
8
|
import { THINKING_LEVELS_DESCRIPTION } from "#src/config/thinking-levels";
|
|
8
9
|
import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
9
|
-
import type { AgentSpawnConfig } from "#src/lifecycle/subagent-manager";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { runForeground } from "#src/tools/foreground-runner";
|
|
13
|
-
import { buildAgentGuidelines, buildDetails, buildTypeListText, textResult } from "#src/tools/helpers";
|
|
10
|
+
import type { AgentSpawnConfig, DeliveryOutcome } from "#src/lifecycle/subagent-manager";
|
|
11
|
+
import type { Subagent } from "#src/lifecycle/subagent";
|
|
12
|
+
import { buildAgentGuidelines, buildDetails, buildTypeListText, formatLifetimeTokens, textResult } from "#src/tools/helpers";
|
|
14
13
|
import { renderAgentResult } from "#src/tools/result-renderer";
|
|
15
14
|
import { type ModelInfo, resolveSpawnConfig } from "#src/tools/spawn-config";
|
|
16
|
-
import type { ParentSessionInfo
|
|
17
|
-
import { type AgentDetails,
|
|
18
|
-
|
|
19
|
-
// ---- Deps interfaces ----
|
|
15
|
+
import type { ParentSessionInfo } from "#src/types";
|
|
16
|
+
import { describeActivity, type AgentDetails, formatModelThinking, formatMs, getDisplayName } from "#src/ui/display";
|
|
20
17
|
|
|
21
|
-
/** Narrow manager interface — only the methods the Agent tool calls. */
|
|
22
18
|
export interface AgentToolManager {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
resume: (id: string, prompt: string, signal: AbortSignal) => Promise<Subagent | undefined>;
|
|
26
|
-
getRecord: (id: string) => Subagent | undefined;
|
|
19
|
+
launch(snapshot: ParentSnapshot, type: string, prompt: string, opts: AgentSpawnConfig): Promise<DeliveryOutcome>;
|
|
20
|
+
getRecord(id: string): Subagent | undefined;
|
|
27
21
|
}
|
|
28
22
|
|
|
29
|
-
/** Narrow runtime interface — the Agent tool's slice of SubagentRuntime. */
|
|
30
23
|
export interface AgentToolRuntime {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
buildSnapshot(inheritContext: boolean): ParentSnapshot;
|
|
25
|
+
getModelInfo(): ModelInfo;
|
|
26
|
+
getSessionInfo(): { parentSessionFile: string; parentSessionId: string };
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
/** Narrow settings accessor — only the fields the Agent tool reads. */
|
|
37
29
|
export type AgentToolSettings = {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
readonly fallbackSubagent?: string | false;
|
|
30
|
+
readonly defaultMaxTurns: number | undefined;
|
|
31
|
+
readonly fallbackSubagent?: string | false;
|
|
41
32
|
};
|
|
42
33
|
|
|
43
|
-
// ---- Class ----
|
|
44
|
-
|
|
45
34
|
export class AgentTool {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}),
|
|
198
|
-
),
|
|
199
|
-
thinking: Type.Optional(
|
|
200
|
-
Type.String({
|
|
201
|
-
description:
|
|
202
|
-
`Thinking level: ${THINKING_LEVELS_DESCRIPTION}. Overrides agent default. Availability depends on the selected model and host Pi version.`,
|
|
203
|
-
}),
|
|
204
|
-
),
|
|
205
|
-
max_turns: Type.Optional(
|
|
206
|
-
Type.Number({
|
|
207
|
-
description:
|
|
208
|
-
"Maximum number of agentic turns before stopping. Omit for unlimited (default).",
|
|
209
|
-
minimum: 1,
|
|
210
|
-
}),
|
|
211
|
-
),
|
|
212
|
-
run_in_background: Type.Optional(
|
|
213
|
-
Type.Boolean({
|
|
214
|
-
description:
|
|
215
|
-
"Set to true to run in background. Returns an agent ID immediately; completion automatically wakes you with a result preview.",
|
|
216
|
-
}),
|
|
217
|
-
),
|
|
218
|
-
resume: Type.Optional(
|
|
219
|
-
Type.String({
|
|
220
|
-
description:
|
|
221
|
-
"Agent ID of a retained, finished session to continue with the same conversation history. Omit to start a new context.",
|
|
222
|
-
}),
|
|
223
|
-
),
|
|
224
|
-
inherit_context: Type.Optional(
|
|
225
|
-
Type.Boolean({
|
|
226
|
-
description:
|
|
227
|
-
"If true, fork parent conversation into the agent. Default: false (fresh context).",
|
|
228
|
-
}),
|
|
229
|
-
),
|
|
230
|
-
}),
|
|
231
|
-
|
|
232
|
-
// ---- Custom rendering: inline subagent results ----
|
|
233
|
-
|
|
234
|
-
renderCall(args: Record<string, unknown>, theme: any) {
|
|
235
|
-
const displayName = args.subagent_type
|
|
236
|
-
? getDisplayName(args.subagent_type as string, registry)
|
|
237
|
-
: "Subagent";
|
|
238
|
-
const desc = (args.description as string | undefined) ?? "";
|
|
239
|
-
return new Text(
|
|
240
|
-
"▸ " +
|
|
241
|
-
theme.fg("toolTitle", theme.bold(displayName)) +
|
|
242
|
-
(desc ? " " + theme.fg("muted", desc) : ""),
|
|
243
|
-
0,
|
|
244
|
-
0,
|
|
245
|
-
);
|
|
246
|
-
},
|
|
247
|
-
|
|
248
|
-
renderResult(result: any, { expanded, isPartial }: any, theme: any) {
|
|
249
|
-
const details = result.details as AgentDetails | undefined;
|
|
250
|
-
if (!details) {
|
|
251
|
-
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
252
|
-
return new Text(text, 0, 0);
|
|
253
|
-
}
|
|
254
|
-
const resultText = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
255
|
-
return new Text(
|
|
256
|
-
renderAgentResult(details, resultText, expanded, isPartial, theme),
|
|
257
|
-
0,
|
|
258
|
-
0,
|
|
259
|
-
);
|
|
260
|
-
},
|
|
35
|
+
private readonly typeListText: string;
|
|
36
|
+
private readonly availableTypesText: string;
|
|
37
|
+
private readonly agentGuidelines: string[];
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly manager: AgentToolManager,
|
|
41
|
+
private readonly runtime: AgentToolRuntime,
|
|
42
|
+
private readonly settings: AgentToolSettings,
|
|
43
|
+
private readonly registry: AgentTypeRegistry,
|
|
44
|
+
private readonly agentDir: string,
|
|
45
|
+
) {
|
|
46
|
+
this.typeListText = buildTypeListText(registry, agentDir);
|
|
47
|
+
this.availableTypesText = registry.getAvailableTypes().join(", ");
|
|
48
|
+
this.agentGuidelines = buildAgentGuidelines(registry);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async execute(
|
|
52
|
+
toolCallId: string,
|
|
53
|
+
params: Record<string, unknown>,
|
|
54
|
+
signal: AbortSignal | undefined,
|
|
55
|
+
onUpdate: ((update: AgentToolResult<any>) => void) | undefined,
|
|
56
|
+
_ctx: any,
|
|
57
|
+
) {
|
|
58
|
+
this.registry.reload();
|
|
59
|
+
const config = resolveSpawnConfig(params, this.registry, this.runtime.getModelInfo(), this.settings);
|
|
60
|
+
if ("error" in config) return textResult(config.error);
|
|
61
|
+
|
|
62
|
+
const snapshot = this.runtime.buildSnapshot(config.execution.inheritContext);
|
|
63
|
+
const sessionInfo = this.runtime.getSessionInfo();
|
|
64
|
+
const parentSession: ParentSessionInfo = { ...sessionInfo, toolCallId };
|
|
65
|
+
let currentId: string | undefined;
|
|
66
|
+
let spinnerFrame = 0;
|
|
67
|
+
const update = (): void => {
|
|
68
|
+
const record = currentId ? this.manager.getRecord(currentId) : undefined;
|
|
69
|
+
if (!record || !onUpdate || config.execution.mode !== "joined") return;
|
|
70
|
+
const details = buildDetails(config.presentation.detailBase, record, {
|
|
71
|
+
durationMs: record.activeRuntimeMs,
|
|
72
|
+
status: record.status,
|
|
73
|
+
terminalReason: record.stateTerminalReason,
|
|
74
|
+
tokens: formatLifetimeTokens(record),
|
|
75
|
+
activity: record.stateTerminalReason ? undefined : describeActivity(record.activeTools, record.responseText),
|
|
76
|
+
spinnerFrame: spinnerFrame++,
|
|
77
|
+
});
|
|
78
|
+
onUpdate({ content: [{ type: "text", text: `${record.toolUses} tool uses...` }], details: details as any });
|
|
79
|
+
};
|
|
80
|
+
const timer = setInterval(update, 500);
|
|
81
|
+
|
|
82
|
+
let outcome: DeliveryOutcome;
|
|
83
|
+
try {
|
|
84
|
+
outcome = await this.manager.launch(snapshot, config.identity.subagentType, config.execution.prompt, {
|
|
85
|
+
description: config.execution.description,
|
|
86
|
+
model: config.execution.model,
|
|
87
|
+
maxTurns: config.execution.effectiveMaxTurns,
|
|
88
|
+
inheritContext: config.execution.inheritContext,
|
|
89
|
+
thinkingLevel: config.execution.effectiveThinkingLevel,
|
|
90
|
+
mode: config.execution.mode,
|
|
91
|
+
timeoutSeconds: config.execution.timeoutSeconds,
|
|
92
|
+
origin: "tool",
|
|
93
|
+
invocation: config.execution.agentInvocation,
|
|
94
|
+
parentSession,
|
|
95
|
+
signal,
|
|
96
|
+
onCreated: (record) => { currentId = record.id; update(); },
|
|
97
|
+
});
|
|
98
|
+
currentId = outcome.kind === "joined" ? outcome.record.id : outcome.agentId;
|
|
99
|
+
update();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return textResult(error instanceof Error ? error.message : String(error));
|
|
102
|
+
} finally {
|
|
103
|
+
clearInterval(timer);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const record = this.manager.getRecord(currentId!);
|
|
107
|
+
if (!record) return textResult(`Agent not found after launch: "${currentId}".`);
|
|
108
|
+
if (outcome.kind === "detached") {
|
|
109
|
+
return textResult(
|
|
110
|
+
`Agent detached and ${record.status === "queued" ? "queued" : "started"}.\n` +
|
|
111
|
+
`Agent ID: ${record.id}\nRun ID: ${record.runId}\nType: ${config.identity.displayName}\n` +
|
|
112
|
+
`Mode: detached\nModel: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\n` +
|
|
113
|
+
`Description: ${record.description}\n` +
|
|
114
|
+
(record.status === "queued" ? "Admission: queued\n" : "") +
|
|
115
|
+
"Completion will send one notification. Use get_subagent_result for the bounded final result.",
|
|
116
|
+
buildDetails(config.presentation.detailBase, record, {
|
|
117
|
+
status: record.status,
|
|
118
|
+
terminalReason: record.stateTerminalReason,
|
|
119
|
+
durationMs: record.activeRuntimeMs,
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
record.markConsumed();
|
|
125
|
+
const details = buildDetails(config.presentation.detailBase, record, {
|
|
126
|
+
status: record.status,
|
|
127
|
+
terminalReason: record.stateTerminalReason,
|
|
128
|
+
tokens: formatLifetimeTokens(record),
|
|
129
|
+
durationMs: record.activeRuntimeMs,
|
|
130
|
+
});
|
|
131
|
+
const fallback = config.identity.fellBack ? `Unknown agent type "${config.identity.rawType}" — using ${config.identity.subagentType}.\n\n` : "";
|
|
132
|
+
const boundedResult = boundResult(record.result, record.outputFile);
|
|
133
|
+
if (record.status === "error") {
|
|
134
|
+
return textResult(`${fallback}Model: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatMs(record.activeRuntimeMs)}\nAgent failed: ${record.error}${boundedResult ? `\n\nPartial output:\n${boundedResult}` : ""}`, details);
|
|
135
|
+
}
|
|
136
|
+
return textResult(`${fallback}Model: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatMs(record.activeRuntimeMs)}\nAgent ${record.status}${record.stateTerminalReason ? ` (${record.stateTerminalReason.replaceAll("_", " ")})` : ""}.\n\n${boundedResult}`, details);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
toToolDefinition() {
|
|
140
|
+
const typeListText = this.typeListText;
|
|
141
|
+
const availableTypesText = this.availableTypesText;
|
|
142
|
+
const agentDir = this.agentDir;
|
|
143
|
+
const registry = this.registry;
|
|
144
|
+
const guidelines = [
|
|
145
|
+
"- Use mode: joined when the parent needs the result in this turn; use mode: detached for independent work (default).",
|
|
146
|
+
...this.agentGuidelines,
|
|
147
|
+
"- Provide clear, detailed prompts so the agent can work autonomously.",
|
|
148
|
+
"- Detached completion automatically wakes you with one bounded result preview; do not poll.",
|
|
149
|
+
"- Use resume_subagent to continue a retained session and stop_subagent to request cooperative cancellation.",
|
|
150
|
+
'- Use model to specify a different model (as "provider/modelId", or fuzzy e.g. "haiku", "sonnet").',
|
|
151
|
+
"- Use thinking to control extended thinking level.",
|
|
152
|
+
"- Use inherit_context if the agent needs the parent conversation history.",
|
|
153
|
+
].join("\n");
|
|
154
|
+
|
|
155
|
+
return defineTool({
|
|
156
|
+
name: "subagent" as const,
|
|
157
|
+
label: "Subagent",
|
|
158
|
+
promptSnippet: "subagent: Launch a specialized agent for complex, multi-step tasks.",
|
|
159
|
+
description: `Launch one specialized agent. Delivery defaults to detached; choose joined when this parent turn must receive the settled result.\n\nAvailable agent types:\n${typeListText}\n\nGuidelines:\n${guidelines}\n`,
|
|
160
|
+
parameters: Type.Object({
|
|
161
|
+
prompt: Type.String({ description: "The task for the agent to perform." }),
|
|
162
|
+
description: Type.String({ description: "A short (3-5 word) description of the task." }),
|
|
163
|
+
subagent_type: Type.String({ description: `The specialized agent type. Available types: ${availableTypesText}. Custom agents from .pi/agents/<name>.md or ${agentDir}/agents/<name>.md are also available.` }),
|
|
164
|
+
model: Type.Optional(Type.String({ description: 'Optional model override: "provider/modelId" or fuzzy name.' })),
|
|
165
|
+
thinking: Type.Optional(Type.String({ description: `Thinking level: ${THINKING_LEVELS_DESCRIPTION}.` })),
|
|
166
|
+
max_turns: Type.Optional(Type.Integer({ description: "Maximum agentic turns. Use 0 or omit for unlimited.", minimum: 0 })),
|
|
167
|
+
timeout_seconds: Type.Optional(Type.Integer({ description: "Active runtime deadline in seconds; queued time is excluded.", minimum: 1 })),
|
|
168
|
+
mode: Type.Optional(Type.Union([Type.Literal("joined"), Type.Literal("detached")], { description: "Result delivery mode. Defaults to detached." })),
|
|
169
|
+
inherit_context: Type.Optional(Type.Boolean({ description: "Fork parent conversation into the agent. Default: false." })),
|
|
170
|
+
}),
|
|
171
|
+
renderCall(args: Record<string, unknown>, theme: any) {
|
|
172
|
+
const displayName = args.subagent_type ? getDisplayName(args.subagent_type as string, registry) : "Subagent";
|
|
173
|
+
const desc = (args.description as string | undefined) ?? "";
|
|
174
|
+
return new Text("▸ " + theme.fg("toolTitle", theme.bold(displayName)) + (desc ? " " + theme.fg("muted", desc) : ""), 0, 0);
|
|
175
|
+
},
|
|
176
|
+
renderResult(result: any, { expanded, isPartial }: any, theme: any) {
|
|
177
|
+
const details = result.details as AgentDetails | undefined;
|
|
178
|
+
if (!details) return new Text(result.content[0]?.type === "text" ? result.content[0].text : "", 0, 0);
|
|
179
|
+
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
180
|
+
return new Text(renderAgentResult(details, text, expanded, isPartial, theme), 0, 0);
|
|
181
|
+
},
|
|
182
|
+
execute: (toolCallId: string, params: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: ((update: AgentToolResult<any>) => void) | undefined, ctx: any) => this.execute(toolCallId, params, signal, onUpdate, ctx),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
261
186
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
) => this.execute(toolCallId, params, signal, onUpdate, ctx),
|
|
269
|
-
});
|
|
270
|
-
}
|
|
187
|
+
const MAX_RESULT_OUTPUT = 12_000;
|
|
188
|
+
function boundResult(result: string | undefined, outputFile: string | undefined): string {
|
|
189
|
+
const output = result?.trim() || "No output.";
|
|
190
|
+
return output.length > MAX_RESULT_OUTPUT
|
|
191
|
+
? output.slice(0, MAX_RESULT_OUTPUT) + `\n\nOutput truncated. Full transcript: ${outputFile ?? "unavailable"}`
|
|
192
|
+
: output;
|
|
271
193
|
}
|
|
@@ -1,104 +1,61 @@
|
|
|
1
1
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
3
|
import type { AgentConfigLookup } from "#src/config/agent-types";
|
|
4
|
-
import
|
|
4
|
+
import type { Subagent } from "#src/lifecycle/subagent";
|
|
5
5
|
import { formatLifetimeTokens, textResult } from "#src/tools/helpers";
|
|
6
|
-
import type {
|
|
7
|
-
import {
|
|
6
|
+
import type { AgentDetails } from "#src/ui/display";
|
|
7
|
+
import { describeActivity, formatMs, formatModelThinking } from "#src/ui/display";
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
const MAX_RESULT_OUTPUT = 12_000;
|
|
10
10
|
|
|
11
|
-
export interface GetResultToolManager {
|
|
12
|
-
getRecord(id: string): Subagent | undefined;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// ---- Class ----
|
|
11
|
+
export interface GetResultToolManager { getRecord(id: string): Subagent | undefined; }
|
|
16
12
|
|
|
17
13
|
export class GetResultTool {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
result: record.result,
|
|
61
|
-
error: record.error,
|
|
62
|
-
stoppedWhileQueued: record.stoppedWhileQueued,
|
|
63
|
-
conversation: verbose ? record.getConversation() : undefined,
|
|
64
|
-
transcriptPath: record.outputFile,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
14
|
+
constructor(private readonly manager: GetResultToolManager, private readonly registry: AgentConfigLookup) {}
|
|
15
|
+
|
|
16
|
+
async execute(_toolCallId: string, params: { agent_id: string }, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) {
|
|
17
|
+
const record = this.manager.getRecord(params.agent_id);
|
|
18
|
+
if (!record) return textResult(`Agent not found: "${params.agent_id}". It may have been cleaned up.`);
|
|
19
|
+
if (!record.isActive()) record.markConsumed();
|
|
20
|
+
|
|
21
|
+
const activity = describeActivity(record.activeTools, record.responseText);
|
|
22
|
+
const text = record.isActive()
|
|
23
|
+
? `Agent ${record.id} is ${record.status}.\nRun ID: ${record.runId}\nMode: ${record.mode}\nModel: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatMs(record.activeRuntimeMs)} (running)\nActivity: ${activity}`
|
|
24
|
+
: renderTerminalResult(record);
|
|
25
|
+
const details: AgentDetails = {
|
|
26
|
+
displayName: this.registry.resolveAgentConfig(record.type).displayName ?? record.type,
|
|
27
|
+
description: record.description,
|
|
28
|
+
subagentType: record.type,
|
|
29
|
+
toolUses: record.toolUses,
|
|
30
|
+
tokens: formatLifetimeTokens(record),
|
|
31
|
+
durationMs: record.activeRuntimeMs,
|
|
32
|
+
status: record.status,
|
|
33
|
+
terminalReason: record.stateTerminalReason,
|
|
34
|
+
modelName: record.modelLabel,
|
|
35
|
+
thinkingLevel: record.effectiveThinkingLevel,
|
|
36
|
+
turnCount: record.turnCount,
|
|
37
|
+
maxTurns: record.maxTurns,
|
|
38
|
+
activity,
|
|
39
|
+
agentId: record.id,
|
|
40
|
+
error: record.error,
|
|
41
|
+
};
|
|
42
|
+
return textResult(text, details);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
toToolDefinition() {
|
|
46
|
+
return defineTool({
|
|
47
|
+
name: "get_subagent_result" as const,
|
|
48
|
+
label: "Get Subagent Result",
|
|
49
|
+
promptSnippet: "get_subagent_result: Read a bounded subagent result without waiting.",
|
|
50
|
+
description: "Read a subagent's current status or bounded terminal result. This tool never waits and never dumps the full conversation. Use the transcript path when output is truncated.",
|
|
51
|
+
parameters: Type.Object({ agent_id: Type.String({ description: "The subagent ID to inspect." }) }),
|
|
52
|
+
execute: (toolCallId: string, params: { agent_id: string }, signal: AbortSignal, onUpdate: unknown, ctx: unknown) => this.execute(toolCallId, params, signal, onUpdate, ctx),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
67
56
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
promptSnippet:
|
|
73
|
-
"get_subagent_result: Inspect status or retrieve full results from a background agent.",
|
|
74
|
-
description:
|
|
75
|
-
"Background completion automatically wakes you with a result preview, so do not poll. " +
|
|
76
|
-
"Use this tool only for full output, verbose conversation, an explicit status check or synchronization point, " +
|
|
77
|
-
"or recovery after a missed notification.",
|
|
78
|
-
parameters: Type.Object({
|
|
79
|
-
agent_id: Type.String({
|
|
80
|
-
description: "The agent ID to check.",
|
|
81
|
-
}),
|
|
82
|
-
wait: Type.Optional(
|
|
83
|
-
Type.Boolean({
|
|
84
|
-
description:
|
|
85
|
-
"If true, wait for the agent to complete before returning. Default: false.",
|
|
86
|
-
}),
|
|
87
|
-
),
|
|
88
|
-
verbose: Type.Optional(
|
|
89
|
-
Type.Boolean({
|
|
90
|
-
description:
|
|
91
|
-
"If true, include the agent's full conversation (messages + tool calls). Default: false.",
|
|
92
|
-
}),
|
|
93
|
-
),
|
|
94
|
-
}),
|
|
95
|
-
execute: (
|
|
96
|
-
toolCallId: string,
|
|
97
|
-
params: { agent_id: string; wait?: boolean; verbose?: boolean },
|
|
98
|
-
signal: AbortSignal,
|
|
99
|
-
onUpdate: unknown,
|
|
100
|
-
ctx: unknown,
|
|
101
|
-
) => this.execute(toolCallId, params, signal, onUpdate, ctx),
|
|
102
|
-
});
|
|
103
|
-
}
|
|
57
|
+
function renderTerminalResult(record: Subagent): string {
|
|
58
|
+
const output = record.result?.trim() || "No output.";
|
|
59
|
+
const bounded = output.length > MAX_RESULT_OUTPUT ? output.slice(0, MAX_RESULT_OUTPUT) + `\n\nOutput truncated. Full transcript: ${record.outputFile ?? "unavailable"}` : output;
|
|
60
|
+
return `Agent ${record.id} ${record.status}.\nRun ID: ${record.runId}\nMode: ${record.mode}\nModel: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatMs(record.activeRuntimeMs)}\nReason: ${record.stateTerminalReason ?? "unknown"}\n\n${record.status === "error" ? `Error: ${record.error}\n\n` : ""}${bounded}`;
|
|
104
61
|
}
|