@nklisch/pi-enhanced 0.2.6 → 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 +16 -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 +25 -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 +112 -105
- 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 -223
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +569 -683
- 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 -119
- 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 -92
- 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/resume-tool.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import type { SubagentMode } from "#src/types";
|
|
4
|
+
import type { ResumeOutcome } from "#src/lifecycle/subagent-manager";
|
|
5
|
+
import type { Subagent } from "#src/lifecycle/subagent";
|
|
6
|
+
import { buildDetails, formatLifetimeTokens, textResult } from "#src/tools/helpers";
|
|
7
|
+
import { formatModelThinking, formatMs } from "#src/ui/display";
|
|
8
|
+
|
|
9
|
+
export interface ResumeToolManager {
|
|
10
|
+
resume(id: string, prompt: string, mode: SubagentMode, timeoutSeconds: number | undefined, signal?: AbortSignal, onReserved?: (record: Subagent) => void): Promise<ResumeOutcome>;
|
|
11
|
+
getRecord(id: string): Subagent | undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ResumeTool {
|
|
15
|
+
constructor(private readonly manager: ResumeToolManager) {}
|
|
16
|
+
|
|
17
|
+
async execute(_toolCallId: string, params: { agent_id: string; prompt: string; mode?: SubagentMode; timeout_seconds?: number }, signal: AbortSignal | undefined, onUpdate: ((update: unknown) => void) | undefined, _ctx: unknown) {
|
|
18
|
+
if (params.timeout_seconds != null && (!Number.isInteger(params.timeout_seconds) || params.timeout_seconds <= 0)) {
|
|
19
|
+
return textResult("timeout_seconds must be a positive integer");
|
|
20
|
+
}
|
|
21
|
+
const mode = params.mode ?? "detached";
|
|
22
|
+
let spinnerFrame = 0;
|
|
23
|
+
const timer = setInterval(() => {
|
|
24
|
+
if (!onUpdate || mode !== "joined") return;
|
|
25
|
+
const record = this.manager.getRecord(params.agent_id);
|
|
26
|
+
if (!record) return;
|
|
27
|
+
onUpdate({ content: [{ type: "text", text: `${record.toolUses} tool uses...` }], details: buildDetails({
|
|
28
|
+
displayName: record.type, description: record.description, subagentType: record.type,
|
|
29
|
+
modelName: record.modelLabel, thinkingLevel: record.effectiveThinkingLevel, tags: [`mode: ${record.mode}`],
|
|
30
|
+
}, record, {
|
|
31
|
+
status: record.status,
|
|
32
|
+
terminalReason: record.stateTerminalReason,
|
|
33
|
+
durationMs: record.activeRuntimeMs,
|
|
34
|
+
spinnerFrame: spinnerFrame++,
|
|
35
|
+
}) });
|
|
36
|
+
}, 500);
|
|
37
|
+
let outcome: ResumeOutcome;
|
|
38
|
+
try {
|
|
39
|
+
outcome = await this.manager.resume(params.agent_id, params.prompt, mode, params.timeout_seconds, signal, (record) => {
|
|
40
|
+
if (onUpdate && mode === "joined") onUpdate({ content: [{ type: "text", text: "Resuming..." }], details: buildDetails({
|
|
41
|
+
displayName: record.type, description: record.description, subagentType: record.type,
|
|
42
|
+
modelName: record.modelLabel, thinkingLevel: record.effectiveThinkingLevel, tags: [`mode: ${record.mode}`],
|
|
43
|
+
}, record, {
|
|
44
|
+
status: record.status,
|
|
45
|
+
terminalReason: record.stateTerminalReason,
|
|
46
|
+
durationMs: record.activeRuntimeMs,
|
|
47
|
+
}) });
|
|
48
|
+
});
|
|
49
|
+
} finally {
|
|
50
|
+
clearInterval(timer);
|
|
51
|
+
}
|
|
52
|
+
if (outcome.kind === "not_found") return textResult(`Agent not found: "${outcome.agentId}". It may have been cleaned up.`);
|
|
53
|
+
if (outcome.kind === "wrong_state") return textResult(`Agent "${outcome.agentId}" cannot be resumed (status: ${outcome.status}). It must have a retained settled session.`);
|
|
54
|
+
|
|
55
|
+
const record = outcome.kind === "joined" ? outcome.record : this.manager.getRecord(outcome.agentId);
|
|
56
|
+
if (!record) return textResult(`Agent not found after resume: "${params.agent_id}".`);
|
|
57
|
+
if (outcome.kind === "detached") {
|
|
58
|
+
return textResult(`Resume detached for agent ${record.id}.\nRun ID: ${record.runId}\nMode: detached\nCompletion will send one notification.`, buildDetails({
|
|
59
|
+
displayName: record.type,
|
|
60
|
+
description: record.description,
|
|
61
|
+
subagentType: record.type,
|
|
62
|
+
modelName: record.modelLabel,
|
|
63
|
+
thinkingLevel: record.effectiveThinkingLevel,
|
|
64
|
+
tags: [`mode: ${record.mode}`],
|
|
65
|
+
}, record, {
|
|
66
|
+
status: record.status,
|
|
67
|
+
terminalReason: record.stateTerminalReason,
|
|
68
|
+
durationMs: record.activeRuntimeMs,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
record.markConsumed();
|
|
72
|
+
return textResult(`Agent ${record.id} ${record.status}.\nRun ID: ${record.runId}\nModel: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatMs(record.activeRuntimeMs)}\nReason: ${record.stateTerminalReason ?? "unknown"}\n\n${boundResult(record.result, record.outputFile)}`, buildDetails({
|
|
73
|
+
displayName: record.type,
|
|
74
|
+
description: record.description,
|
|
75
|
+
subagentType: record.type,
|
|
76
|
+
modelName: record.modelLabel,
|
|
77
|
+
thinkingLevel: record.effectiveThinkingLevel,
|
|
78
|
+
tags: [`mode: ${record.mode}`],
|
|
79
|
+
}, record, { tokens: formatLifetimeTokens(record), durationMs: record.activeRuntimeMs, terminalReason: record.stateTerminalReason }));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
toToolDefinition() {
|
|
83
|
+
return defineTool({
|
|
84
|
+
name: "resume_subagent" as const,
|
|
85
|
+
label: "Resume Subagent",
|
|
86
|
+
promptSnippet: "resume_subagent: Continue a retained subagent session.",
|
|
87
|
+
description: "Continue a retained settled subagent session. Delivery defaults to detached; joined waits for the resumed run to settle.",
|
|
88
|
+
parameters: Type.Object({
|
|
89
|
+
agent_id: Type.String({ description: "The retained subagent ID to resume." }),
|
|
90
|
+
prompt: Type.String({ description: "The next task or instruction." }),
|
|
91
|
+
mode: Type.Optional(Type.Union([Type.Literal("joined"), Type.Literal("detached")], { description: "Result delivery mode. Defaults to detached." })),
|
|
92
|
+
timeout_seconds: Type.Optional(Type.Integer({ description: "Active runtime deadline in seconds; queued time is excluded.", minimum: 1 })),
|
|
93
|
+
}),
|
|
94
|
+
execute: (toolCallId: string, params: { agent_id: string; prompt: string; mode?: SubagentMode; timeout_seconds?: number }, signal: AbortSignal | undefined, onUpdate: unknown, ctx: unknown) => this.execute(toolCallId, params, signal, onUpdate as ((update: unknown) => void) | undefined, ctx),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const MAX_RESULT_OUTPUT = 12_000;
|
|
100
|
+
function boundResult(result: string | undefined, outputFile: string | undefined): string {
|
|
101
|
+
const output = result?.trim() || "No output.";
|
|
102
|
+
return output.length > MAX_RESULT_OUTPUT
|
|
103
|
+
? output.slice(0, MAX_RESULT_OUTPUT) + `\n\nOutput truncated. Full transcript: ${outputFile ?? "unavailable"}`
|
|
104
|
+
: output;
|
|
105
|
+
}
|
|
@@ -15,7 +15,7 @@ import type { ModelRegistry } from "#src/session/model-resolver";
|
|
|
15
15
|
import { formatModelLabel } from "#src/session/model-label";
|
|
16
16
|
import { resolveInvocationModel } from "#src/session/model-resolver";
|
|
17
17
|
import { resolveEffectiveThinkingLevel } from "#src/session/thinking-level";
|
|
18
|
-
import type { AgentInvocation, SubagentType, ThinkingLevel } from "#src/types";
|
|
18
|
+
import type { AgentInvocation, SubagentMode, SubagentType, ThinkingLevel } from "#src/types";
|
|
19
19
|
import {
|
|
20
20
|
type AgentDetails,
|
|
21
21
|
buildInvocationTags,
|
|
@@ -50,7 +50,8 @@ export interface SpawnExecution {
|
|
|
50
50
|
/** Exact level passed to the child session after model-capability clamping. */
|
|
51
51
|
effectiveThinkingLevel: ThinkingLevel;
|
|
52
52
|
inheritContext: boolean;
|
|
53
|
-
|
|
53
|
+
mode: SubagentMode;
|
|
54
|
+
timeoutSeconds?: number;
|
|
54
55
|
agentInvocation: AgentInvocation;
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -88,6 +89,13 @@ export function resolveSpawnConfig(
|
|
|
88
89
|
readonly fallbackSubagent?: string | false;
|
|
89
90
|
},
|
|
90
91
|
): ResolvedSpawnConfig | SpawnConfigError {
|
|
92
|
+
const removedField = ["run_in_background", "foreground", "resume"].find((field) =>
|
|
93
|
+
Object.prototype.hasOwnProperty.call(params, field),
|
|
94
|
+
);
|
|
95
|
+
if (removedField) {
|
|
96
|
+
return { error: `Removed field "${removedField}"; use mode: joined|detached${removedField === "resume" ? " and resume_subagent" : ""}.` };
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
const rawType = params.subagent_type as SubagentType;
|
|
92
100
|
const typeResolution = resolveDispatchAgentType(
|
|
93
101
|
rawType,
|
|
@@ -122,21 +130,30 @@ export function resolveSpawnConfig(
|
|
|
122
130
|
modelInfo.parentThinkingLevel,
|
|
123
131
|
);
|
|
124
132
|
const inheritContext = resolvedConfig.inheritContext;
|
|
125
|
-
const
|
|
133
|
+
const mode = resolvedConfig.mode;
|
|
134
|
+
if (mode !== "joined" && mode !== "detached") {
|
|
135
|
+
return { error: "mode must be joined or detached" };
|
|
136
|
+
}
|
|
137
|
+
const maxTurns = resolvedConfig.maxTurns ?? settings.defaultMaxTurns;
|
|
138
|
+
if (maxTurns != null && (!Number.isInteger(maxTurns) || maxTurns < 0)) {
|
|
139
|
+
return { error: "max_turns must be a non-negative integer" };
|
|
140
|
+
}
|
|
141
|
+
if (resolvedConfig.timeoutSeconds != null && (!Number.isInteger(resolvedConfig.timeoutSeconds) || resolvedConfig.timeoutSeconds <= 0)) {
|
|
142
|
+
return { error: "timeout_seconds must be a positive integer" };
|
|
143
|
+
}
|
|
126
144
|
|
|
127
145
|
// Every status surface shows the exact effective model, including inheritance.
|
|
128
146
|
const modelName = formatModelLabel(model);
|
|
129
147
|
|
|
130
|
-
const effectiveMaxTurns = normalizeMaxTurns(
|
|
131
|
-
resolvedConfig.maxTurns ?? settings.defaultMaxTurns,
|
|
132
|
-
);
|
|
148
|
+
const effectiveMaxTurns = normalizeMaxTurns(maxTurns);
|
|
133
149
|
|
|
134
150
|
const agentInvocation: AgentInvocation = {
|
|
135
151
|
modelName,
|
|
136
152
|
thinking,
|
|
137
153
|
maxTurns: normalizeMaxTurns(resolvedConfig.maxTurns),
|
|
138
154
|
inheritContext,
|
|
139
|
-
|
|
155
|
+
mode,
|
|
156
|
+
timeoutSeconds: resolvedConfig.timeoutSeconds,
|
|
140
157
|
};
|
|
141
158
|
|
|
142
159
|
const modeLabel = getPromptModeLabel(subagentType, registry);
|
|
@@ -162,7 +179,8 @@ export function resolveSpawnConfig(
|
|
|
162
179
|
thinking,
|
|
163
180
|
effectiveThinkingLevel,
|
|
164
181
|
inheritContext,
|
|
165
|
-
|
|
182
|
+
mode,
|
|
183
|
+
timeoutSeconds: resolvedConfig.timeoutSeconds,
|
|
166
184
|
agentInvocation,
|
|
167
185
|
},
|
|
168
186
|
presentation: { modelName, agentTags, detailBase },
|
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/steer-tool.ts
CHANGED
|
@@ -1,117 +1,33 @@
|
|
|
1
1
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import { formatDuration, formatModelThinking } from "#src/ui/display";
|
|
3
|
+
import type { ManagerSteerOutcome } from "#src/lifecycle/subagent-manager";
|
|
4
|
+
import { textResult } from "#src/tools/helpers";
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export interface SteerToolManager {
|
|
10
|
-
getRecord(id: string): Subagent | undefined;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface SteerToolEvents {
|
|
14
|
-
emit(name: string, data: unknown): void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// ---- Class ----
|
|
6
|
+
export interface SteerToolManager { steer(id: string, message: string): Promise<ManagerSteerOutcome>; }
|
|
7
|
+
export interface SteerToolEvents { emit(name: string, data: unknown): void; }
|
|
18
8
|
|
|
19
9
|
export class SteerTool {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return textResult(
|
|
44
|
-
`Failed to steer agent: ${err instanceof Error ? err.message : String(err)}\n`
|
|
45
|
-
+ this.renderRunIdentity(record),
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
switch (outcome.kind) {
|
|
50
|
-
case "rejected":
|
|
51
|
-
return textResult(
|
|
52
|
-
`Agent "${params.agent_id}" is not running (status: ${outcome.status}). Cannot steer a non-running agent.\n`
|
|
53
|
-
+ this.renderRunIdentity(record),
|
|
54
|
-
);
|
|
55
|
-
case "buffered":
|
|
56
|
-
this.events.emit("subagents:steered", { id: record.id, message: params.message });
|
|
57
|
-
return textResult(
|
|
58
|
-
`Steering message queued for agent ${record.id}. It will be delivered once the session initializes.\n`
|
|
59
|
-
+ this.renderRunIdentity(record),
|
|
60
|
-
);
|
|
61
|
-
case "delivered":
|
|
62
|
-
this.events.emit("subagents:steered", { id: record.id, message: params.message });
|
|
63
|
-
return this.renderDelivered(record);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Render the success message with live state for a delivered steer. */
|
|
68
|
-
private renderDelivered(record: Subagent) {
|
|
69
|
-
const tokens = formatLifetimeTokens(record);
|
|
70
|
-
const contextPercent = record.getContextPercent();
|
|
71
|
-
const stateParts: string[] = [formatModelThinking(record.modelLabel, record.effectiveThinkingLevel), formatDuration(record.startedAt, record.completedAt)];
|
|
72
|
-
if (tokens) stateParts.push(tokens);
|
|
73
|
-
stateParts.push(`${record.toolUses} tool ${record.toolUses === 1 ? "use" : "uses"}`);
|
|
74
|
-
if (contextPercent !== null)
|
|
75
|
-
stateParts.push(`context ${Math.round(contextPercent)}% full`);
|
|
76
|
-
if (record.compactionCount)
|
|
77
|
-
stateParts.push(
|
|
78
|
-
`${record.compactionCount} compaction${record.compactionCount === 1 ? "" : "s"}`,
|
|
79
|
-
);
|
|
80
|
-
return textResult(
|
|
81
|
-
`Steering message sent to agent ${record.id}. The agent will process it after its current tool execution.\n` +
|
|
82
|
-
`Current state: ${stateParts.join(" · ")}`,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private renderRunIdentity(record: Subagent): string {
|
|
87
|
-
return `Model: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatDuration(record.startedAt, record.completedAt)}`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
toToolDefinition() {
|
|
91
|
-
return defineTool({
|
|
92
|
-
name: "steer_subagent" as const,
|
|
93
|
-
label: "Steer Agent",
|
|
94
|
-
promptSnippet:
|
|
95
|
-
"steer_subagent: Send a mid-run message to redirect a running background agent.",
|
|
96
|
-
description:
|
|
97
|
-
"Send a steering message to a running agent. The message will interrupt the agent after its current tool execution " +
|
|
98
|
-
"and be injected into its conversation, allowing you to redirect its work mid-run. Only works on running agents.",
|
|
99
|
-
parameters: Type.Object({
|
|
100
|
-
agent_id: Type.String({
|
|
101
|
-
description: "The agent ID to steer (must be currently running).",
|
|
102
|
-
}),
|
|
103
|
-
message: Type.String({
|
|
104
|
-
description:
|
|
105
|
-
"The steering message to send. This will appear as a user message in the agent's conversation.",
|
|
106
|
-
}),
|
|
107
|
-
}),
|
|
108
|
-
execute: (
|
|
109
|
-
toolCallId: string,
|
|
110
|
-
params: { agent_id: string; message: string },
|
|
111
|
-
signal: AbortSignal,
|
|
112
|
-
onUpdate: unknown,
|
|
113
|
-
ctx: unknown,
|
|
114
|
-
) => this.execute(toolCallId, params, signal, onUpdate, ctx),
|
|
115
|
-
});
|
|
116
|
-
}
|
|
10
|
+
constructor(private readonly manager: SteerToolManager, private readonly events: SteerToolEvents) {}
|
|
11
|
+
|
|
12
|
+
async execute(_toolCallId: string, params: { agent_id: string; message: string }, _signal: AbortSignal, _onUpdate: unknown, _ctx: unknown) {
|
|
13
|
+
const outcome = await this.manager.steer(params.agent_id, params.message);
|
|
14
|
+
if (outcome.kind === "not_found") return textResult(`Agent not found: "${outcome.agentId}". It may have been cleaned up.`, outcome);
|
|
15
|
+
if (outcome.kind === "rejected") return textResult(`Agent "${params.agent_id}" cannot be steered (status: ${outcome.status}).`, { ...outcome, agentId: params.agent_id });
|
|
16
|
+
this.events.emit("subagents:steered", { id: params.agent_id, runId: outcome.runId, outcome: outcome.kind, message: params.message });
|
|
17
|
+
return textResult(`Steering message ${outcome.kind} for agent ${params.agent_id}.\nRun ID: ${outcome.runId}.`, { ...outcome, agentId: params.agent_id });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
toToolDefinition() {
|
|
21
|
+
return defineTool({
|
|
22
|
+
name: "steer_subagent" as const,
|
|
23
|
+
label: "Steer Subagent",
|
|
24
|
+
promptSnippet: "steer_subagent: Send a message to a running subagent.",
|
|
25
|
+
description: "Send a steering message to a running subagent. The structured outcome distinguishes delivered, buffered, rejected, and not-found cases.",
|
|
26
|
+
parameters: Type.Object({
|
|
27
|
+
agent_id: Type.String({ description: "The running subagent ID." }),
|
|
28
|
+
message: Type.String({ description: "The message to add to the subagent's conversation." }),
|
|
29
|
+
}),
|
|
30
|
+
execute: (toolCallId: string, params: { agent_id: string; message: string }, signal: AbortSignal, onUpdate: unknown, ctx: unknown) => this.execute(toolCallId, params, signal, onUpdate, ctx),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
117
33
|
}
|
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/stop-tool.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import type { StopOutcome } from "#src/lifecycle/subagent-manager";
|
|
4
|
+
import type { Subagent } from "#src/lifecycle/subagent";
|
|
5
|
+
import { buildDetails, formatLifetimeTokens, textResult } from "#src/tools/helpers";
|
|
6
|
+
import { formatModelThinking, formatMs } from "#src/ui/display";
|
|
7
|
+
|
|
8
|
+
export interface StopToolManager {
|
|
9
|
+
stop(id: string, settlementTimeoutSeconds?: number, waitSignal?: AbortSignal): Promise<StopOutcome>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class StopTool {
|
|
13
|
+
constructor(private readonly manager: StopToolManager) {}
|
|
14
|
+
|
|
15
|
+
async execute(_toolCallId: string, params: { agent_id: string; settlement_timeout_seconds?: number }, signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) {
|
|
16
|
+
const timeout = params.settlement_timeout_seconds ?? 5;
|
|
17
|
+
if (!Number.isInteger(timeout) || timeout < 1 || timeout > 30) return textResult("settlement_timeout_seconds must be an integer from 1 to 30");
|
|
18
|
+
const outcome = await this.manager.stop(params.agent_id, timeout, signal);
|
|
19
|
+
if (outcome.kind === "not_found") return textResult(`Agent not found: "${outcome.agentId}". It may have been cleaned up.`, { kind: outcome.kind, agentId: outcome.agentId });
|
|
20
|
+
if (outcome.kind === "already_terminal") return textResult(`Agent ${outcome.agentId} is already terminal (${outcome.record.status}, ${outcome.record.stateTerminalReason ?? "unknown"}).`, renderDetails(outcome.record));
|
|
21
|
+
if (outcome.kind === "stop_pending") return textResult(`Stop requested for agent ${outcome.agentId}, but its run has not settled within ${timeout}s. It remains active; no hard kill was attempted.\nRun ID: ${outcome.runId}\nReason: ${outcome.reason}`, renderDetails(outcome.record));
|
|
22
|
+
return textResult(`Agent ${outcome.agentId} stopped.\nRun ID: ${outcome.runId}\nReason: ${outcome.reason}\nRuntime: ${formatMs(outcome.record.activeRuntimeMs)}`, renderDetails(outcome.record));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
toToolDefinition() {
|
|
26
|
+
return defineTool({
|
|
27
|
+
name: "stop_subagent" as const,
|
|
28
|
+
label: "Stop Subagent",
|
|
29
|
+
promptSnippet: "stop_subagent: Request cooperative cancellation of a subagent.",
|
|
30
|
+
description: "Stop queued work immediately or request cooperative cancellation of a live subagent. The result distinguishes settled, already-terminal, not-found, and stop-pending outcomes; it never hard-kills JavaScript.",
|
|
31
|
+
parameters: Type.Object({
|
|
32
|
+
agent_id: Type.String({ description: "The subagent ID to stop." }),
|
|
33
|
+
settlement_timeout_seconds: Type.Optional(Type.Integer({ description: "How long to wait for cooperative settlement (1-30 seconds, default 5).", minimum: 1, maximum: 30 })),
|
|
34
|
+
}),
|
|
35
|
+
execute: (toolCallId: string, params: { agent_id: string; settlement_timeout_seconds?: number }, signal: AbortSignal | undefined, onUpdate: unknown, ctx: unknown) => this.execute(toolCallId, params, signal, onUpdate, ctx),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function renderDetails(record: Subagent) {
|
|
41
|
+
return buildDetails({
|
|
42
|
+
displayName: record.type,
|
|
43
|
+
description: record.description,
|
|
44
|
+
subagentType: record.type,
|
|
45
|
+
modelName: record.modelLabel,
|
|
46
|
+
thinkingLevel: record.effectiveThinkingLevel,
|
|
47
|
+
tags: [`mode: ${record.mode}`],
|
|
48
|
+
}, record, {
|
|
49
|
+
terminalReason: record.stateTerminalReason,
|
|
50
|
+
tokens: formatLifetimeTokens(record),
|
|
51
|
+
durationMs: record.activeRuntimeMs,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -33,6 +33,9 @@ export interface SubscribableSession {
|
|
|
33
33
|
/** Agent type: any string name (built-in defaults or user-defined). */
|
|
34
34
|
export type SubagentType = string;
|
|
35
35
|
|
|
36
|
+
/** Delivery mode for one initial or resumed run. */
|
|
37
|
+
export type SubagentMode = "joined" | "detached";
|
|
38
|
+
|
|
36
39
|
/** UI display and agent listing — name, display name, description, prompt mode. */
|
|
37
40
|
export interface AgentIdentity {
|
|
38
41
|
name: string;
|
|
@@ -54,10 +57,12 @@ export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
|
|
|
54
57
|
model?: string;
|
|
55
58
|
thinking?: ThinkingLevel;
|
|
56
59
|
maxTurns?: number;
|
|
57
|
-
/**
|
|
60
|
+
/** Active runtime deadline in seconds for launch/resume. */
|
|
61
|
+
timeoutSeconds?: number;
|
|
62
|
+
/** Default for launch/resume: fork parent conversation. undefined = caller decides. */
|
|
58
63
|
inheritContext?: boolean;
|
|
59
|
-
/** Default
|
|
60
|
-
|
|
64
|
+
/** Default delivery mode for launch/resume. */
|
|
65
|
+
mode?: SubagentMode;
|
|
61
66
|
/** One-line usage guideline for the subagent tool's Guidelines: block. Omitted — no guideline line. */
|
|
62
67
|
toolGuideline?: string;
|
|
63
68
|
/** true = this is an embedded default agent (informational) */
|
|
@@ -74,7 +79,8 @@ export interface AgentInvocation {
|
|
|
74
79
|
thinking?: ThinkingLevel;
|
|
75
80
|
maxTurns?: number;
|
|
76
81
|
inheritContext?: boolean;
|
|
77
|
-
|
|
82
|
+
mode?: SubagentMode;
|
|
83
|
+
timeoutSeconds?: number;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
/**
|
|
@@ -115,7 +121,7 @@ export interface ParentSessionInfo {
|
|
|
115
121
|
parentSessionFile?: string;
|
|
116
122
|
/** Session ID of the parent agent (stored in the child session's parentSession header). */
|
|
117
123
|
parentSessionId?: string;
|
|
118
|
-
/** Tool call ID for
|
|
124
|
+
/** Tool call ID for detached completion notification wiring. Exposed on the record via Subagent.toolCallId. */
|
|
119
125
|
toolCallId?: string;
|
|
120
126
|
}
|
|
121
127
|
|
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts
CHANGED
|
@@ -87,14 +87,14 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
87
87
|
private widgetFrame = 0;
|
|
88
88
|
private widgetInterval: ReturnType<typeof setInterval> | undefined;
|
|
89
89
|
/**
|
|
90
|
-
* Bounded reactive read model: lifecycle callbacks add active
|
|
90
|
+
* Bounded reactive read model: lifecycle callbacks add active detached
|
|
91
91
|
* records and the small terminal linger set; render ticks never ask the
|
|
92
92
|
* manager to clone or sort its retained history.
|
|
93
93
|
*/
|
|
94
|
-
private readonly
|
|
94
|
+
private readonly detachedAgents = new Map<string, Subagent>();
|
|
95
95
|
/** Tracks how many turns each finished agent has survived. */
|
|
96
96
|
private finishedTurnAge = new Map<string, number>();
|
|
97
|
-
/** How many extra turns errors/
|
|
97
|
+
/** How many extra turns errors/stopped agents linger (completed agents clear after 1 turn). */
|
|
98
98
|
private static readonly ERROR_LINGER_TURNS = 2;
|
|
99
99
|
/** Pi renders the whole component tree for each requestRender call. */
|
|
100
100
|
private static readonly STATUS_REFRESH_INTERVAL_MS = 500;
|
|
@@ -122,7 +122,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
122
122
|
// Lifecycle events can arrive before the first tool gives us a UI context.
|
|
123
123
|
// Refresh when that context becomes available, but let update() decide
|
|
124
124
|
// whether any active record actually needs an animation timer.
|
|
125
|
-
if (contextChanged && this.uiCtx && this.
|
|
125
|
+
if (contextChanged && this.uiCtx && this.detachedAgents.size > 0) this.refresh();
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
@@ -131,13 +131,13 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
131
131
|
*/
|
|
132
132
|
onTurnStart() {
|
|
133
133
|
// Age terminal records and remove them before the next render. Completed
|
|
134
|
-
// records linger for one turn; errors and
|
|
134
|
+
// records linger for one turn; errors and stopped records linger for two.
|
|
135
135
|
for (const [id, age] of this.finishedTurnAge) {
|
|
136
|
-
const record = this.
|
|
136
|
+
const record = this.detachedAgents.get(id);
|
|
137
137
|
const nextAge = age + 1;
|
|
138
138
|
if (!record || nextAge >= this.maxFinishedAge(record.status)) {
|
|
139
139
|
this.finishedTurnAge.delete(id);
|
|
140
|
-
this.
|
|
140
|
+
this.detachedAgents.delete(id);
|
|
141
141
|
} else {
|
|
142
142
|
this.finishedTurnAge.set(id, nextAge);
|
|
143
143
|
}
|
|
@@ -149,47 +149,47 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
149
149
|
|
|
150
150
|
/** A subagent started running — ensure the update loop is live and render. */
|
|
151
151
|
onSubagentStarted(record: Subagent) {
|
|
152
|
-
if (!this.
|
|
152
|
+
if (!this.trackDetached(record)) return;
|
|
153
153
|
this.finishedTurnAge.delete(record.id);
|
|
154
154
|
this.startLoop();
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
/** A
|
|
157
|
+
/** A detached subagent was created (queued) — ensure the loop is live and render. */
|
|
158
158
|
onSubagentCreated(record: Subagent) {
|
|
159
|
-
if (!this.
|
|
159
|
+
if (!this.trackDetached(record)) return;
|
|
160
160
|
this.finishedTurnAge.delete(record.id);
|
|
161
161
|
this.startLoop();
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/** A subagent completed — seed its bounded linger entry and render. */
|
|
165
165
|
onSubagentCompleted(record: Subagent) {
|
|
166
|
-
if (!this.
|
|
166
|
+
if (!this.trackDetached(record)) return;
|
|
167
167
|
this.finishedTurnAge.set(record.id, 0);
|
|
168
168
|
this.refresh();
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/** A resumed subagent started — ensure the update loop is live and render. */
|
|
172
172
|
onSubagentResumedStarted(record: Subagent) {
|
|
173
|
-
if (!this.
|
|
173
|
+
if (!this.trackDetached(record)) return;
|
|
174
174
|
this.finishedTurnAge.delete(record.id);
|
|
175
175
|
this.startLoop();
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/** A resumed subagent settled — seed its terminal linger entry. */
|
|
179
179
|
onSubagentResumed(record: Subagent) {
|
|
180
|
-
if (!this.
|
|
180
|
+
if (!this.trackDetached(record)) return;
|
|
181
181
|
this.finishedTurnAge.set(record.id, 0);
|
|
182
182
|
this.refresh();
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
/** A subagent's session compacted — render to refresh the compaction count. */
|
|
186
186
|
onSubagentCompacted(record: Subagent, _info: CompactionInfo) {
|
|
187
|
-
if (this.
|
|
187
|
+
if (this.trackDetached(record)) this.refresh();
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
/** Remove terminal state when the manager clears a parent session. */
|
|
191
191
|
onSubagentCleared(record: Subagent) {
|
|
192
|
-
if (!this.
|
|
192
|
+
if (!this.detachedAgents.delete(record.id)) return;
|
|
193
193
|
this.finishedTurnAge.delete(record.id);
|
|
194
194
|
this.refresh();
|
|
195
195
|
}
|
|
@@ -226,19 +226,23 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
226
226
|
return ERROR_STATUSES.has(status) ? AgentWidget.ERROR_LINGER_TURNS : 1;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
/** Add a record only when its
|
|
230
|
-
private
|
|
231
|
-
if (record.
|
|
232
|
-
|
|
229
|
+
/** Add a record only when its current run mode is detached. */
|
|
230
|
+
private trackDetached(record: Subagent): boolean {
|
|
231
|
+
if (record.mode !== "detached") {
|
|
232
|
+
this.detachedAgents.delete(record.id);
|
|
233
|
+
this.finishedTurnAge.delete(record.id);
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
this.detachedAgents.set(record.id, record);
|
|
233
237
|
return true;
|
|
234
238
|
}
|
|
235
239
|
|
|
236
240
|
/** Drop terminal entries whose turn-based linger window has already elapsed. */
|
|
237
241
|
private pruneExpiredFinished(): void {
|
|
238
|
-
for (const [id, record] of this.
|
|
242
|
+
for (const [id, record] of this.detachedAgents) {
|
|
239
243
|
if (record.isActive()) continue;
|
|
240
244
|
if (record.completedAt == null || !this.shouldShowFinished(id, record.status)) {
|
|
241
|
-
this.
|
|
245
|
+
this.detachedAgents.delete(id);
|
|
242
246
|
this.finishedTurnAge.delete(id);
|
|
243
247
|
}
|
|
244
248
|
}
|
|
@@ -250,12 +254,14 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
250
254
|
id: record.id,
|
|
251
255
|
type: record.type,
|
|
252
256
|
status: record.status,
|
|
257
|
+
terminalReason: record.stateTerminalReason,
|
|
253
258
|
description: record.description,
|
|
254
259
|
modelLabel: record.modelLabel,
|
|
255
260
|
thinkingLevel: record.effectiveThinkingLevel,
|
|
256
261
|
toolUses: record.toolUses,
|
|
257
262
|
startedAt: record.startedAt,
|
|
258
263
|
completedAt: record.completedAt,
|
|
264
|
+
activeRuntimeMs: record.activeRuntimeMs,
|
|
259
265
|
error: record.error,
|
|
260
266
|
lifetimeUsage: record.lifetimeUsage,
|
|
261
267
|
compactionCount: record.compactionCount,
|
|
@@ -270,7 +276,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
270
276
|
/** Delegate rendering to the pure widget-renderer module. */
|
|
271
277
|
private renderWidget(tui: any, theme: Theme): string[] {
|
|
272
278
|
return renderWidgetLines({
|
|
273
|
-
agents: [...this.
|
|
279
|
+
agents: [...this.detachedAgents.values()].map(r => this.toWidgetAgent(r)),
|
|
274
280
|
registry: this.registry,
|
|
275
281
|
spinnerFrame: this.widgetFrame,
|
|
276
282
|
terminalWidth: tui.terminal.columns,
|
|
@@ -295,7 +301,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
295
301
|
this.lastStatusText = undefined;
|
|
296
302
|
}
|
|
297
303
|
this.stopTimer();
|
|
298
|
-
this.
|
|
304
|
+
this.detachedAgents.clear();
|
|
299
305
|
this.finishedTurnAge.clear();
|
|
300
306
|
}
|
|
301
307
|
|
|
@@ -316,8 +322,8 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
316
322
|
if (!this.uiCtx) return;
|
|
317
323
|
|
|
318
324
|
this.pruneExpiredFinished();
|
|
319
|
-
const
|
|
320
|
-
const state = assembleWidgetState(
|
|
325
|
+
const detachedAgents = [...this.detachedAgents.values()];
|
|
326
|
+
const state = assembleWidgetState(detachedAgents, (id, status) => this.shouldShowFinished(id, status));
|
|
321
327
|
|
|
322
328
|
if (!state.hasActive && !state.hasFinished) {
|
|
323
329
|
this.clearWidget();
|
|
@@ -329,7 +335,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
329
335
|
// static completion widget remains registered.
|
|
330
336
|
if (state.hasActive) this.ensureTimer();
|
|
331
337
|
else this.stopTimer();
|
|
332
|
-
this.updateStatusBar(state,
|
|
338
|
+
this.updateStatusBar(state, detachedAgents);
|
|
333
339
|
this.widgetFrame++;
|
|
334
340
|
|
|
335
341
|
// Register widget callback once; subsequent updates use requestRender()
|
|
@@ -387,7 +393,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
387
393
|
this.widgetRegistered = false;
|
|
388
394
|
this.tui = undefined;
|
|
389
395
|
this.lastStatusText = undefined;
|
|
390
|
-
this.
|
|
396
|
+
this.detachedAgents.clear();
|
|
391
397
|
this.finishedTurnAge.clear();
|
|
392
398
|
}
|
|
393
399
|
}
|
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { AgentConfigLookup } from "#src/config/agent-types";
|
|
9
9
|
import { GLYPHS } from "#src/ui/glyphs";
|
|
10
|
+
import type { SubagentTerminalReason } from "#src/lifecycle/subagent-state";
|
|
10
11
|
import type { AgentInvocation, SubagentType, ThinkingLevel } from "#src/types";
|
|
11
12
|
|
|
12
13
|
// ---- Types ----
|
|
@@ -24,7 +25,8 @@ export interface AgentDetails {
|
|
|
24
25
|
toolUses: number;
|
|
25
26
|
tokens: string;
|
|
26
27
|
durationMs: number;
|
|
27
|
-
status: "queued" | "running" | "completed" | "
|
|
28
|
+
status: "queued" | "running" | "completed" | "stopped" | "error";
|
|
29
|
+
terminalReason?: SubagentTerminalReason;
|
|
28
30
|
/** Human-readable description of what the agent is currently doing. */
|
|
29
31
|
activity?: string;
|
|
30
32
|
/** Current spinner frame index (for animated running indicator). */
|
|
@@ -49,7 +51,7 @@ export interface AgentDetails {
|
|
|
49
51
|
export const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
50
52
|
|
|
51
53
|
/** Statuses that indicate an error/non-success outcome (used for linger behavior and icon rendering). */
|
|
52
|
-
export const ERROR_STATUSES = new Set(["error", "
|
|
54
|
+
export const ERROR_STATUSES = new Set(["error", "stopped"]);
|
|
53
55
|
|
|
54
56
|
/** Tool name → human-readable action for activity descriptions. */
|
|
55
57
|
const TOOL_DISPLAY: Record<string, string> = {
|
|
@@ -146,8 +148,9 @@ export function buildInvocationTags(
|
|
|
146
148
|
if (!invocation) return { tags };
|
|
147
149
|
if (invocation.thinking) tags.push(`thinking: ${invocation.thinking}`);
|
|
148
150
|
if (invocation.inheritContext) tags.push("inherit context");
|
|
149
|
-
if (invocation.
|
|
151
|
+
if (invocation.mode) tags.push(`mode: ${invocation.mode}`);
|
|
150
152
|
if (invocation.maxTurns != null) tags.push(`max turns: ${invocation.maxTurns}`);
|
|
153
|
+
if (invocation.timeoutSeconds != null) tags.push(`timeout: ${invocation.timeoutSeconds}s`);
|
|
151
154
|
return { modelName: invocation.modelName, tags };
|
|
152
155
|
}
|
|
153
156
|
|