@gotgenes/pi-subagents 6.14.0 → 6.15.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 +26 -0
- package/docs/architecture/architecture.md +11 -25
- package/docs/plans/0145-decompose-execute-push-ctx-to-boundary.md +290 -0
- package/docs/retro/0152-add-prompt-snippet.md +34 -0
- package/package.json +2 -3
- package/src/agent-manager.ts +7 -6
- package/src/handlers/index.ts +2 -6
- package/src/index.ts +21 -7
- package/src/service-adapter.ts +18 -16
- package/src/tools/agent-tool.ts +55 -112
- package/src/tools/background-spawner.ts +33 -51
- package/src/tools/foreground-runner.ts +36 -57
- package/src/tools/spawn-config.ts +146 -0
- package/src/types.ts +0 -1
- package/src/ui/agent-widget.ts +5 -0
- package/src/ui/conversation-viewer.ts +3 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* spawn-config.ts — Pure config resolution for the Agent tool.
|
|
3
|
+
*
|
|
4
|
+
* Extracts all config resolution logic from execute: type resolution,
|
|
5
|
+
* invocation config merge, model resolution, max-turns normalization,
|
|
6
|
+
* tag building, and detail-base construction.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Model } from "@earendil-works/pi-ai";
|
|
10
|
+
import { normalizeMaxTurns } from "../agent-runner.js";
|
|
11
|
+
import type { AgentTypeRegistry } from "../agent-types.js";
|
|
12
|
+
import { resolveAgentInvocationConfig } from "../invocation-config.js";
|
|
13
|
+
import { resolveInvocationModel } from "../model-resolver.js";
|
|
14
|
+
import type { AgentInvocation, IsolationMode, SubagentType, ThinkingLevel } from "../types.js";
|
|
15
|
+
import {
|
|
16
|
+
type AgentDetails,
|
|
17
|
+
buildInvocationTags,
|
|
18
|
+
getDisplayName,
|
|
19
|
+
getPromptModeLabel,
|
|
20
|
+
} from "../ui/display.js";
|
|
21
|
+
|
|
22
|
+
/** Model info extracted from the parent session context. */
|
|
23
|
+
export interface ModelInfo {
|
|
24
|
+
parentModel: { id: string; name?: string } | undefined;
|
|
25
|
+
modelRegistry: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Fully resolved config for spawning an agent. */
|
|
29
|
+
export interface ResolvedSpawnConfig {
|
|
30
|
+
subagentType: string;
|
|
31
|
+
rawType: SubagentType;
|
|
32
|
+
fellBack: boolean;
|
|
33
|
+
displayName: string;
|
|
34
|
+
prompt: string;
|
|
35
|
+
description: string;
|
|
36
|
+
model: Model<any> | undefined;
|
|
37
|
+
effectiveMaxTurns: number | undefined;
|
|
38
|
+
thinking: ThinkingLevel | undefined;
|
|
39
|
+
inheritContext: boolean;
|
|
40
|
+
runInBackground: boolean;
|
|
41
|
+
isolated: boolean;
|
|
42
|
+
isolation: IsolationMode | undefined;
|
|
43
|
+
modelName: string | undefined;
|
|
44
|
+
agentInvocation: AgentInvocation;
|
|
45
|
+
agentTags: string[];
|
|
46
|
+
detailBase: Pick<AgentDetails, "displayName" | "description" | "subagentType" | "modelName" | "tags">;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Error result when model resolution fails. */
|
|
50
|
+
export interface SpawnConfigError {
|
|
51
|
+
error: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve all config for an Agent tool invocation.
|
|
56
|
+
*
|
|
57
|
+
* Pure function — no SDK types, no side effects.
|
|
58
|
+
* Returns either a fully resolved config or an error.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveSpawnConfig(
|
|
61
|
+
params: Record<string, unknown>,
|
|
62
|
+
registry: AgentTypeRegistry,
|
|
63
|
+
modelInfo: ModelInfo,
|
|
64
|
+
settings: { readonly defaultMaxTurns: number | undefined },
|
|
65
|
+
): ResolvedSpawnConfig | SpawnConfigError {
|
|
66
|
+
const rawType = params.subagent_type as SubagentType;
|
|
67
|
+
const resolved = registry.resolveType(rawType);
|
|
68
|
+
const subagentType = resolved ?? "general-purpose";
|
|
69
|
+
const fellBack = resolved === undefined;
|
|
70
|
+
|
|
71
|
+
const displayName = getDisplayName(subagentType, registry);
|
|
72
|
+
|
|
73
|
+
// Merge agent config defaults with tool-call params
|
|
74
|
+
const customConfig = registry.resolveAgentConfig(subagentType);
|
|
75
|
+
const resolvedConfig = resolveAgentInvocationConfig(customConfig, params);
|
|
76
|
+
|
|
77
|
+
// Resolve model
|
|
78
|
+
const resolution = resolveInvocationModel(
|
|
79
|
+
modelInfo.parentModel,
|
|
80
|
+
resolvedConfig.modelInput,
|
|
81
|
+
resolvedConfig.modelFromParams,
|
|
82
|
+
modelInfo.modelRegistry as any,
|
|
83
|
+
);
|
|
84
|
+
if (resolution.error) return { error: resolution.error };
|
|
85
|
+
const model = resolution.model;
|
|
86
|
+
|
|
87
|
+
const thinking = resolvedConfig.thinking;
|
|
88
|
+
const inheritContext = resolvedConfig.inheritContext;
|
|
89
|
+
const runInBackground = resolvedConfig.runInBackground;
|
|
90
|
+
const isolated = resolvedConfig.isolated;
|
|
91
|
+
const isolation = resolvedConfig.isolation;
|
|
92
|
+
|
|
93
|
+
// Compute display model name (only shown when different from parent)
|
|
94
|
+
const parentModelId = modelInfo.parentModel?.id;
|
|
95
|
+
const effectiveModelId = model?.id;
|
|
96
|
+
const modelName =
|
|
97
|
+
effectiveModelId && effectiveModelId !== parentModelId
|
|
98
|
+
? (model?.name ?? effectiveModelId).replace(/^Claude\s+/i, "").toLowerCase()
|
|
99
|
+
: undefined;
|
|
100
|
+
|
|
101
|
+
const effectiveMaxTurns = normalizeMaxTurns(
|
|
102
|
+
resolvedConfig.maxTurns ?? settings.defaultMaxTurns,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const agentInvocation: AgentInvocation = {
|
|
106
|
+
modelName,
|
|
107
|
+
thinking,
|
|
108
|
+
maxTurns: normalizeMaxTurns(resolvedConfig.maxTurns),
|
|
109
|
+
isolated,
|
|
110
|
+
inheritContext,
|
|
111
|
+
runInBackground,
|
|
112
|
+
isolation,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const modeLabel = getPromptModeLabel(subagentType, registry);
|
|
116
|
+
const { tags: invocationTags } = buildInvocationTags(agentInvocation);
|
|
117
|
+
const agentTags = modeLabel ? [modeLabel, ...invocationTags] : invocationTags;
|
|
118
|
+
|
|
119
|
+
const detailBase = {
|
|
120
|
+
displayName,
|
|
121
|
+
description: params.description as string,
|
|
122
|
+
subagentType,
|
|
123
|
+
modelName,
|
|
124
|
+
tags: agentTags.length > 0 ? agentTags : undefined,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
subagentType,
|
|
129
|
+
rawType,
|
|
130
|
+
fellBack,
|
|
131
|
+
displayName,
|
|
132
|
+
prompt: params.prompt as string,
|
|
133
|
+
description: params.description as string,
|
|
134
|
+
model,
|
|
135
|
+
effectiveMaxTurns,
|
|
136
|
+
thinking,
|
|
137
|
+
inheritContext,
|
|
138
|
+
runInBackground,
|
|
139
|
+
isolated,
|
|
140
|
+
isolation,
|
|
141
|
+
modelName,
|
|
142
|
+
agentInvocation,
|
|
143
|
+
agentTags,
|
|
144
|
+
detailBase,
|
|
145
|
+
};
|
|
146
|
+
}
|
package/src/types.ts
CHANGED
package/src/ui/agent-widget.ts
CHANGED
|
@@ -64,6 +64,7 @@ export class AgentWidget {
|
|
|
64
64
|
) {}
|
|
65
65
|
|
|
66
66
|
/** Set the UI context (grabbed from first tool execution). */
|
|
67
|
+
// fallow-ignore-next-line unused-class-member
|
|
67
68
|
setUICtx(ctx: UICtx) {
|
|
68
69
|
if (ctx !== this.uiCtx) {
|
|
69
70
|
// UICtx changed — the widget registered on the old context is gone.
|
|
@@ -79,6 +80,7 @@ export class AgentWidget {
|
|
|
79
80
|
* Called on each new turn (tool_execution_start).
|
|
80
81
|
* Ages finished agents and clears those that have lingered long enough.
|
|
81
82
|
*/
|
|
83
|
+
// fallow-ignore-next-line unused-class-member
|
|
82
84
|
onTurnStart() {
|
|
83
85
|
// Age all finished agents
|
|
84
86
|
for (const [id, age] of this.finishedTurnAge) {
|
|
@@ -89,6 +91,7 @@ export class AgentWidget {
|
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
/** Ensure the widget update timer is running. */
|
|
94
|
+
// fallow-ignore-next-line unused-class-member
|
|
92
95
|
ensureTimer() {
|
|
93
96
|
if (!this.widgetInterval) {
|
|
94
97
|
this.widgetInterval = setInterval(() => this.update(), 80);
|
|
@@ -103,6 +106,7 @@ export class AgentWidget {
|
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
/** Record an agent as finished (call when agent completes). */
|
|
109
|
+
// fallow-ignore-next-line unused-class-member
|
|
106
110
|
markFinished(agentId: string) {
|
|
107
111
|
if (!this.finishedTurnAge.has(agentId)) {
|
|
108
112
|
this.finishedTurnAge.set(agentId, 0);
|
|
@@ -354,6 +358,7 @@ export class AgentWidget {
|
|
|
354
358
|
}
|
|
355
359
|
}
|
|
356
360
|
|
|
361
|
+
// fallow-ignore-next-line unused-class-member
|
|
357
362
|
dispose() {
|
|
358
363
|
if (this.widgetInterval) {
|
|
359
364
|
clearInterval(this.widgetInterval);
|
|
@@ -90,6 +90,7 @@ export class ConversationViewer implements Component {
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// fallow-ignore-next-line unused-class-member
|
|
93
94
|
handleInput(data: string): void {
|
|
94
95
|
if (matchesKey(data, "escape") || matchesKey(data, "q")) {
|
|
95
96
|
this.closed = true;
|
|
@@ -199,8 +200,10 @@ export class ConversationViewer implements Component {
|
|
|
199
200
|
return lines;
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
// fallow-ignore-next-line unused-class-member
|
|
202
204
|
invalidate(): void { /* no cached state to clear */ }
|
|
203
205
|
|
|
206
|
+
// fallow-ignore-next-line unused-class-member
|
|
204
207
|
dispose(): void {
|
|
205
208
|
this.closed = true;
|
|
206
209
|
if (this.unsubscribe) {
|