@signalridge/pi-subagents 1.8.0 → 1.9.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 +170 -0
- package/README.md +85 -55
- package/package.json +2 -2
- package/src/agent-manager.ts +235 -120
- package/src/agent-runner.ts +90 -72
- package/src/agent-tiers.ts +236 -70
- package/src/cross-extension-rpc.ts +6 -0
- package/src/default-agents.ts +3 -3
- package/src/index.ts +149 -139
- package/src/internal-run.ts +14 -0
- package/src/invocation-config.ts +24 -13
- package/src/model-resolver.ts +12 -0
- package/src/model-scope.ts +5 -2
- package/src/nested-tools.ts +35 -22
- package/src/schedule.ts +3 -0
- package/src/settings.ts +68 -247
- package/src/types.ts +10 -13
- package/src/ui/agent-display.ts +1 -1
- package/src/workflow-tiers.ts +0 -202
package/src/types.ts
CHANGED
|
@@ -4,13 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import type { ThinkingLevel } from "@earendil-works/pi-ai";
|
|
6
6
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import type { WorkflowTier } from "@signalridge/pi-subagents-protocol";
|
|
8
7
|
import type { AgentTierResolutionSnapshot } from "./agent-tiers.js";
|
|
9
8
|
import type { LifetimeUsage } from "./usage.js";
|
|
10
|
-
import type { WorkflowTierResolutionSnapshot } from "./workflow-tiers.js";
|
|
11
9
|
import type { WorktreeCleanupResult, WorktreeInfo } from "./worktree.js";
|
|
12
10
|
|
|
13
|
-
export type { ThinkingLevel
|
|
11
|
+
export type { ThinkingLevel };
|
|
14
12
|
|
|
15
13
|
/** Agent type: any string name (built-in defaults or user-defined). */
|
|
16
14
|
export type SubagentType = string;
|
|
@@ -237,18 +235,18 @@ export interface ResumableAgentEntry {
|
|
|
237
235
|
}
|
|
238
236
|
|
|
239
237
|
export interface AgentInvocation {
|
|
240
|
-
/**
|
|
238
|
+
/**
|
|
239
|
+
* Short display name, e.g. "haiku". Set only when the spawn is not simply
|
|
240
|
+
* running the parent's model: an untiered spawn that resolved a different one,
|
|
241
|
+
* or a tier whose profile pins one. A tier that inherits leaves this absent,
|
|
242
|
+
* which is how the UI says "same model as the parent".
|
|
243
|
+
*/
|
|
241
244
|
modelName?: string;
|
|
242
245
|
thinking?: ThinkingLevel;
|
|
243
|
-
/** Semantic workflow tier selected by the workflow definition. */
|
|
244
|
-
tier?: WorkflowTier;
|
|
245
|
-
/** Immutable model/thinking resolution captured by pi-subagents. */
|
|
246
|
-
tierSnapshot?: WorkflowTierResolutionSnapshot;
|
|
247
246
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* exhaustiveness with it.
|
|
247
|
+
* The tier applied to this spawn, from any caller — the Agent tool, nested
|
|
248
|
+
* delegation, the scheduler, or a managed workflow call. All four name a key
|
|
249
|
+
* from the one `agentTiers` catalogue.
|
|
252
250
|
*/
|
|
253
251
|
agentTier?: string;
|
|
254
252
|
/** Immutable model/thinking resolution for `agentTier`. */
|
|
@@ -269,7 +267,6 @@ export type AgentRecordSnapshot = Omit<Readonly<AgentRecord>, "session" | "abort
|
|
|
269
267
|
readonly lifetimeUsage: Readonly<LifetimeUsage>;
|
|
270
268
|
readonly pendingSteers?: readonly string[];
|
|
271
269
|
readonly invocation?: Readonly<AgentInvocation> & {
|
|
272
|
-
readonly tierSnapshot?: Readonly<NonNullable<AgentInvocation["tierSnapshot"]>>;
|
|
273
270
|
readonly agentTierSnapshot?: Readonly<NonNullable<AgentInvocation["agentTierSnapshot"]>>;
|
|
274
271
|
};
|
|
275
272
|
};
|
package/src/ui/agent-display.ts
CHANGED
|
@@ -174,7 +174,7 @@ export function buildInvocationTags(
|
|
|
174
174
|
): { modelName?: string; tags: string[] } {
|
|
175
175
|
const tags: string[] = [];
|
|
176
176
|
if (!invocation) return { tags };
|
|
177
|
-
if (invocation.
|
|
177
|
+
if (invocation.agentTier) tags.push(`tier: ${invocation.agentTier}`);
|
|
178
178
|
if (invocation.thinking) tags.push(`thinking: ${invocation.thinking}`);
|
|
179
179
|
if (invocation.isolated) tags.push("isolated");
|
|
180
180
|
if (invocation.isolation === "worktree") tags.push("worktree");
|
package/src/workflow-tiers.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/** Resolution and audit snapshots for semantic workflow model tiers. */
|
|
2
|
-
import {
|
|
3
|
-
type Api,
|
|
4
|
-
clampThinkingLevel,
|
|
5
|
-
getSupportedThinkingLevels,
|
|
6
|
-
type Model,
|
|
7
|
-
} from "@earendil-works/pi-ai";
|
|
8
|
-
import type { WorkflowTier } from "@signalridge/pi-subagents-protocol";
|
|
9
|
-
import { type ModelRegistry, resolveModel } from "./model-resolver.js";
|
|
10
|
-
import {
|
|
11
|
-
DEFAULT_WORKFLOW_TIER_PROFILES,
|
|
12
|
-
type WorkflowSettings,
|
|
13
|
-
type WorkflowThinking,
|
|
14
|
-
type WorkflowTierProfile,
|
|
15
|
-
} from "./settings.js";
|
|
16
|
-
import type { AgentConfig, ThinkingLevel } from "./types.js";
|
|
17
|
-
|
|
18
|
-
let workflowSettings: WorkflowSettings = {};
|
|
19
|
-
|
|
20
|
-
export type { WorkflowTier };
|
|
21
|
-
|
|
22
|
-
export type WorkflowResolutionSource = "frontmatter" | "tier" | "parent";
|
|
23
|
-
|
|
24
|
-
/** Durable, JSON-safe explanation of the policy used for one managed spawn. */
|
|
25
|
-
export interface WorkflowTierResolutionSnapshot {
|
|
26
|
-
tier: WorkflowTier;
|
|
27
|
-
/** Effective provider/model id, after resolving or inheriting the parent. */
|
|
28
|
-
model?: string;
|
|
29
|
-
/** Effective thinking level; omitted when the selected model supports no level. */
|
|
30
|
-
thinking?: ThinkingLevel;
|
|
31
|
-
configuredModel?: string;
|
|
32
|
-
configuredThinking?: WorkflowThinking;
|
|
33
|
-
requestedThinking?: ThinkingLevel;
|
|
34
|
-
modelSource: WorkflowResolutionSource;
|
|
35
|
-
thinkingSource: WorkflowResolutionSource;
|
|
36
|
-
/** True when pi-ai lowered the requested level for the selected model. */
|
|
37
|
-
clamped?: boolean;
|
|
38
|
-
diagnostic?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface WorkflowTierResolution {
|
|
42
|
-
model?: Model<Api>;
|
|
43
|
-
thinkingLevel?: ThinkingLevel;
|
|
44
|
-
snapshot?: WorkflowTierResolutionSnapshot;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface ResolveWorkflowTierInput {
|
|
48
|
-
/** Explicit semantic tier from a workflow task. */
|
|
49
|
-
tier?: WorkflowTier;
|
|
50
|
-
/** Settings are passed by pi-subagents; workflows never resolve policy. */
|
|
51
|
-
settings?: WorkflowSettings;
|
|
52
|
-
agentConfig?: AgentConfig;
|
|
53
|
-
/** Direct model objects take precedence over agent frontmatter and tiers. */
|
|
54
|
-
directModel?: Model<Api>;
|
|
55
|
-
/** Direct model references take precedence over agent frontmatter and tiers. */
|
|
56
|
-
modelOverride?: string;
|
|
57
|
-
thinkingOverride?: ThinkingLevel;
|
|
58
|
-
parentModel?: Model<Api>;
|
|
59
|
-
parentThinking?: ThinkingLevel;
|
|
60
|
-
modelRegistry: ModelRegistry<Model<Api>>;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function isWorkflowTier(value: unknown): value is WorkflowTier {
|
|
64
|
-
return value === "small" || value === "medium" || value === "large";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function getWorkflowSettings(): WorkflowSettings {
|
|
68
|
-
return structuredClone(workflowSettings);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function setWorkflowSettings(settings: WorkflowSettings): void {
|
|
72
|
-
workflowSettings = structuredClone(settings);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
function effectiveModelId(model: Model<Api> | undefined): string | undefined {
|
|
77
|
-
return model ? `${model.provider}/${model.id}` : undefined;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Resolve model and thinking independently, preserving agent frontmatter as the
|
|
82
|
-
* authoritative override. A tier only fills fields omitted by frontmatter; the
|
|
83
|
-
* parent session is the final fallback. An unavailable model explicitly selected
|
|
84
|
-
* by a tier fails closed instead of silently changing the model.
|
|
85
|
-
*/
|
|
86
|
-
function isCompleteProfile(value: unknown): value is WorkflowTierProfile {
|
|
87
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
88
|
-
const profile = value as Record<string, unknown>;
|
|
89
|
-
return (
|
|
90
|
-
typeof profile.model === "string" &&
|
|
91
|
-
profile.model.length > 0 &&
|
|
92
|
-
typeof profile.thinking === "string" &&
|
|
93
|
-
["inherit", "minimal", "low", "medium", "high", "xhigh", "max"].includes(profile.thinking)
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Resolve model and thinking independently, preserving agent frontmatter as the
|
|
99
|
-
* authoritative override. A tier only fills fields omitted by frontmatter; the
|
|
100
|
-
* parent session is the final fallback. The configured default tier is applied
|
|
101
|
-
* only inside pi-subagents, never by the workflow package.
|
|
102
|
-
*/
|
|
103
|
-
export function resolveWorkflowTier(input: ResolveWorkflowTierInput): WorkflowTierResolution {
|
|
104
|
-
if (input.tier !== undefined && !isWorkflowTier(input.tier)) {
|
|
105
|
-
throw new Error("workflow tier must be one of small, medium, or large");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const workflow = input.settings ?? workflowSettings;
|
|
109
|
-
if (input.tier === undefined && workflow.blockedDefaultTier) {
|
|
110
|
-
throw new Error("workflow defaultTier is blocked by malformed configuration");
|
|
111
|
-
}
|
|
112
|
-
const tier = input.tier ?? workflow.defaultTier;
|
|
113
|
-
if (tier !== undefined && !isWorkflowTier(tier)) {
|
|
114
|
-
throw new Error("workflow defaultTier must be one of small, medium, or large");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const defaultProfile = tier === undefined ? undefined : DEFAULT_WORKFLOW_TIER_PROFILES[tier];
|
|
118
|
-
const configuredProfile = tier === undefined ? undefined : workflow.tiers?.[tier];
|
|
119
|
-
if (tier !== undefined && workflow.blockedTiers?.includes(tier)) {
|
|
120
|
-
throw new Error(`workflow tier "${tier}" is blocked by malformed configuration`);
|
|
121
|
-
}
|
|
122
|
-
if (configuredProfile !== undefined && !isCompleteProfile(configuredProfile)) {
|
|
123
|
-
// This should be unreachable after settings validation. Throwing here keeps
|
|
124
|
-
// a forged in-process settings object from degrading into parent policy.
|
|
125
|
-
throw new Error(`workflow tier "${tier}" has an incomplete model/thinking profile`);
|
|
126
|
-
}
|
|
127
|
-
const profile = tier === undefined
|
|
128
|
-
? undefined
|
|
129
|
-
: { ...defaultProfile, ...(configuredProfile ?? {}) };
|
|
130
|
-
|
|
131
|
-
const tierConfiguredModel = profile?.model;
|
|
132
|
-
const configuredModel = input.modelOverride ?? input.agentConfig?.model ?? tierConfiguredModel;
|
|
133
|
-
const modelSource: WorkflowResolutionSource = input.directModel !== undefined || input.modelOverride !== undefined || input.agentConfig?.model !== undefined
|
|
134
|
-
? "frontmatter"
|
|
135
|
-
: configuredModel !== undefined && configuredModel !== "inherit"
|
|
136
|
-
? "tier"
|
|
137
|
-
: "parent";
|
|
138
|
-
let model = input.directModel ?? input.parentModel;
|
|
139
|
-
let diagnostic: string | undefined;
|
|
140
|
-
|
|
141
|
-
if (input.directModel === undefined && configuredModel !== undefined && configuredModel !== "inherit") {
|
|
142
|
-
const resolved = resolveModel(configuredModel, input.modelRegistry);
|
|
143
|
-
if (typeof resolved === "string") {
|
|
144
|
-
const tierOwnsModel =
|
|
145
|
-
input.modelOverride === undefined &&
|
|
146
|
-
input.agentConfig?.model === undefined &&
|
|
147
|
-
tierConfiguredModel !== undefined &&
|
|
148
|
-
tierConfiguredModel !== "inherit";
|
|
149
|
-
if (tierOwnsModel) {
|
|
150
|
-
throw new Error(`workflow tier "${tier}" has an unavailable model: ${resolved}`);
|
|
151
|
-
}
|
|
152
|
-
diagnostic = `${resolved} Falling back to the parent model.`;
|
|
153
|
-
} else {
|
|
154
|
-
model = resolved;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const tierConfiguredThinking = profile?.thinking;
|
|
159
|
-
const configuredThinking = input.thinkingOverride ?? input.agentConfig?.thinking ?? tierConfiguredThinking;
|
|
160
|
-
const thinkingSource: WorkflowResolutionSource = input.thinkingOverride !== undefined || input.agentConfig?.thinking !== undefined
|
|
161
|
-
? "frontmatter"
|
|
162
|
-
: tierConfiguredThinking !== undefined && tierConfiguredThinking !== "inherit"
|
|
163
|
-
? "tier"
|
|
164
|
-
: "parent";
|
|
165
|
-
const requestedThinking = configuredThinking === "inherit" ? input.parentThinking : configuredThinking ?? input.parentThinking;
|
|
166
|
-
let thinkingLevel = requestedThinking;
|
|
167
|
-
let clamped = false;
|
|
168
|
-
|
|
169
|
-
if (model && requestedThinking) {
|
|
170
|
-
const supported = getSupportedThinkingLevels(model);
|
|
171
|
-
const clampedLevel = clampThinkingLevel(model, requestedThinking);
|
|
172
|
-
if (clampedLevel !== requestedThinking) {
|
|
173
|
-
clamped = true;
|
|
174
|
-
const supportedText = supported.join(", ");
|
|
175
|
-
const clampDiagnostic = `Thinking level "${requestedThinking}" is not supported by ${effectiveModelId(model) ?? "the selected model"}; using "${clampedLevel}" (supported: ${supportedText}).`;
|
|
176
|
-
diagnostic = diagnostic ? `${diagnostic} ${clampDiagnostic}` : clampDiagnostic;
|
|
177
|
-
}
|
|
178
|
-
// "off" is a ModelThinkingLevel sentinel, not a ThinkingLevel accepted by
|
|
179
|
-
// AgentSession options. Omitting the option preserves the provider's off behavior.
|
|
180
|
-
thinkingLevel = clampedLevel === "off" ? undefined : clampedLevel as ThinkingLevel;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (tier === undefined) return { model, thinkingLevel };
|
|
184
|
-
|
|
185
|
-
const snapshot: WorkflowTierResolutionSnapshot = {
|
|
186
|
-
tier,
|
|
187
|
-
...(effectiveModelId(model) ? { model: effectiveModelId(model) } : {}),
|
|
188
|
-
...(thinkingLevel ? { thinking: thinkingLevel } : {}),
|
|
189
|
-
...(configuredModel !== undefined ? { configuredModel } : {}),
|
|
190
|
-
...(configuredThinking !== undefined ? { configuredThinking } : {}),
|
|
191
|
-
...(requestedThinking !== undefined ? { requestedThinking } : {}),
|
|
192
|
-
modelSource,
|
|
193
|
-
thinkingSource,
|
|
194
|
-
...(clamped ? { clamped: true } : {}),
|
|
195
|
-
...(diagnostic ? { diagnostic } : {}),
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
return { model, thinkingLevel, snapshot };
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Compatibility alias for callers that use the shorter tier terminology. */
|
|
202
|
-
export const resolveTier = resolveWorkflowTier;
|