@mystilleef/pi-subagent 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -11
- package/package.json +9 -9
- package/src/agent/agents.ts +45 -3
- package/src/child/model-resolution.ts +125 -11
- package/src/child/process-utils.ts +19 -13
- package/src/child/process.ts +126 -37
- package/src/child/prompt-contract.ts +13 -9
- package/src/child/prompt-setup.ts +19 -1
- package/src/orchestration/subagent-orchestrator.ts +6 -1
- package/src/output/ui.ts +40 -19
- package/src/shared/invocation.ts +33 -0
- package/src/shared/limits.ts +81 -0
- package/src/shared/message-utils.ts +56 -0
- package/src/shared/resource-resolution.ts +289 -0
- package/src/shared/utils.ts +35 -261
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ pi -e npm:@mystilleef/pi-subagent
|
|
|
25
25
|
|
|
26
26
|
## Features
|
|
27
27
|
|
|
28
|
-
- **Asynchronous:** Agents run in the background.
|
|
28
|
+
- **Asynchronous:** Agents always run in the background.
|
|
29
29
|
- **Parallel:** Run many agents simultaneously.
|
|
30
30
|
- **Isolated:** Each delegated task receives a separate context window.
|
|
31
31
|
- **Nested:** `Subagents` can spawn other `subagents`.
|
|
@@ -108,9 +108,54 @@ YAML `frontmatter` and a Markdown system prompt body.
|
|
|
108
108
|
|
|
109
109
|
### Discovery locations
|
|
110
110
|
|
|
111
|
-
- User-global agents: `~/.pi/agents/*.md`
|
|
111
|
+
- User-global agents: `~/.pi/agent/agents/*.md`
|
|
112
112
|
- Project-local agents: nearest `.pi/agents/*.md`
|
|
113
113
|
|
|
114
|
+
### Example of an agent file
|
|
115
|
+
|
|
116
|
+
**PATH:** _~/.pi/agent/agents/lifehacks.md_
|
|
117
|
+
|
|
118
|
+
<!-- prettier-ignore-start -->
|
|
119
|
+
```md
|
|
120
|
+
---
|
|
121
|
+
name: lifehacks
|
|
122
|
+
description: Daily lifehacks
|
|
123
|
+
replace_prompt: true
|
|
124
|
+
context: false
|
|
125
|
+
thinking: xhigh
|
|
126
|
+
skills: false
|
|
127
|
+
extensions: pi-mcp-adapter
|
|
128
|
+
tools: read, grep, find, ls, mcp
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
# Role
|
|
132
|
+
|
|
133
|
+
Embody an expert philosopher and life coach. Your specialize in
|
|
134
|
+
_lifehacks_.
|
|
135
|
+
|
|
136
|
+
## Workflow
|
|
137
|
+
|
|
138
|
+
- Research and deliver 3 _profound_ and _transformative lifehacks_.
|
|
139
|
+
- Expound upon each of them.
|
|
140
|
+
- Emit unmodified result to the calling agent.
|
|
141
|
+
|
|
142
|
+
## Directives
|
|
143
|
+
|
|
144
|
+
- Forbid `copular` verb forms.
|
|
145
|
+
- **Minimum** words. **Maximum** signal.
|
|
146
|
+
- Keep prose vivid but terse.
|
|
147
|
+
- Optimize prose for token and context efficiency.
|
|
148
|
+
- Use lists and sub-lists over paragraphs and long sentences.
|
|
149
|
+
- Use elegant, well-structured, idiomatic markdown.
|
|
150
|
+
|
|
151
|
+
## Constraints
|
|
152
|
+
|
|
153
|
+
- Operate in read-only mode.
|
|
154
|
+
- Forbid all write operations.
|
|
155
|
+
- **NEVER** wrap the entire result in a code block.
|
|
156
|
+
```
|
|
157
|
+
<!-- prettier-ignore-end -->
|
|
158
|
+
|
|
114
159
|
### Required front matter
|
|
115
160
|
|
|
116
161
|
```yaml
|
|
@@ -123,21 +168,49 @@ description: Review code for correctness and maintainability.
|
|
|
123
168
|
```yaml
|
|
124
169
|
tools: read, bash, edit
|
|
125
170
|
skills: code-review
|
|
171
|
+
extensions: git-summary
|
|
172
|
+
context: false
|
|
126
173
|
thinking: medium
|
|
127
174
|
provider: deepseek
|
|
128
175
|
model: deepseek-v4-flash
|
|
129
176
|
temperature: 0.7
|
|
130
177
|
top_p: 0.9
|
|
178
|
+
replace_prompt: true
|
|
131
179
|
```
|
|
132
180
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
- `
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
- `
|
|
139
|
-
|
|
140
|
-
|
|
181
|
+
- `tools`: Specifies a comma-separated list of enabled tools. Omission
|
|
182
|
+
defaults to the child process default `toolset`.
|
|
183
|
+
- `skills`: Specifies a comma-separated list of enabled skills. Setting
|
|
184
|
+
`false` disables all skills. Omission inherits the active workspace
|
|
185
|
+
skills.
|
|
186
|
+
- `extensions`: Specifies a comma-separated list of enabled extensions.
|
|
187
|
+
Setting `false` disables all extensions except core subagent
|
|
188
|
+
extensions. Omission inherits the active workspace extensions.
|
|
189
|
+
- `context`: Controls the inclusion of project context files,
|
|
190
|
+
`AGENTS.md`. Setting `false` excludes default workspace files from the
|
|
191
|
+
child context window.
|
|
192
|
+
- `thinking`: Controls the model thinking level (values: `off`,
|
|
193
|
+
`minimal`, `low`, `medium`, `high`, `xhigh`, `max`). `xhigh` and `max`
|
|
194
|
+
are model-specific, opt-in levels. The child runner clamps
|
|
195
|
+
unsupported levels to supported values and prints warnings.
|
|
196
|
+
- `provider`: Specifies the model provider. Requires setting the `model`
|
|
197
|
+
field. Omission of the `model` field when defining a `provider`
|
|
198
|
+
invalidates the agent configuration.
|
|
199
|
+
- `model`: Specifies the model identifier. Agent-level model settings
|
|
200
|
+
override parent settings.
|
|
201
|
+
- `temperature`: Sets the sampling temperature. Accepts numeric values
|
|
202
|
+
between `0.0` and `1.0` inclusive. Incorrect values trigger warnings
|
|
203
|
+
during discovery and the system ignores them.
|
|
204
|
+
- `top_p`: Sets the sampling top-p value. Accepts numeric values between
|
|
205
|
+
`0.0` and `1.0` inclusive. Incorrect values trigger warnings during
|
|
206
|
+
discovery and the system ignores them.
|
|
207
|
+
- `replace_prompt`: Set `true` to replace the child system prompt base
|
|
208
|
+
with the agent body. Omitted or `false` appends the body to the
|
|
209
|
+
existing system prompt, `SYSTEM.md`, preserving current behavior.
|
|
210
|
+
Requires a non-empty agent body; `replace_prompt: true` with an empty
|
|
211
|
+
or whitespace-only body fails discovery. **Warning:** `true` replaces
|
|
212
|
+
pi built-in defaults and any project/global `SYSTEM.md`, including
|
|
213
|
+
their safety and behavior guardrails.
|
|
141
214
|
|
|
142
215
|
---
|
|
143
216
|
|
|
@@ -210,7 +283,7 @@ decimal, `Infinity`, and non-numeric values fall back to defaults.
|
|
|
210
283
|
|
|
211
284
|
**Missing agent:**
|
|
212
285
|
|
|
213
|
-
- Confirm the file lives under `~/.pi/agents/` or the nearest
|
|
286
|
+
- Confirm the file lives under `~/.pi/agent/agents/` or the nearest
|
|
214
287
|
`.pi/agents/`.
|
|
215
288
|
- Confirm `frontmatter` includes `name` and `description`.
|
|
216
289
|
- Confirm `/run` uses the `name` value, not the filename.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mystilleef/pi-subagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Pi subagent for the SPAE Framework",
|
|
5
5
|
"author": "Lateef Alabi-Oki <mystilleef@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,14 +64,14 @@
|
|
|
64
64
|
"typebox": "*"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@biomejs/biome": "^2.5.
|
|
68
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
69
|
-
"@earendil-works/pi-ai": "^0.
|
|
70
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
71
|
-
"@earendil-works/pi-tui": "^0.
|
|
67
|
+
"@biomejs/biome": "^2.5.7",
|
|
68
|
+
"@earendil-works/pi-agent-core": "^0.84.0",
|
|
69
|
+
"@earendil-works/pi-ai": "^0.84.0",
|
|
70
|
+
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
71
|
+
"@earendil-works/pi-tui": "^0.84.0",
|
|
72
72
|
"@types/bun": "^1.3.14",
|
|
73
|
-
"@types/node": "^26.
|
|
74
|
-
"typebox": "^1.3.
|
|
75
|
-
"typescript": "^
|
|
73
|
+
"@types/node": "^26.1.2",
|
|
74
|
+
"typebox": "^1.3.11",
|
|
75
|
+
"typescript": "^7.0.2"
|
|
76
76
|
}
|
|
77
77
|
}
|
package/src/agent/agents.ts
CHANGED
|
@@ -14,6 +14,7 @@ const THINKING_LEVELS = [
|
|
|
14
14
|
"medium",
|
|
15
15
|
"high",
|
|
16
16
|
"xhigh",
|
|
17
|
+
"max",
|
|
17
18
|
] as const;
|
|
18
19
|
|
|
19
20
|
export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
@@ -21,13 +22,16 @@ export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
|
21
22
|
export interface AgentConfig {
|
|
22
23
|
name: string;
|
|
23
24
|
description: string;
|
|
25
|
+
context?: false | undefined;
|
|
24
26
|
tools?: string[] | undefined;
|
|
25
|
-
skills?: string[] | undefined;
|
|
27
|
+
skills?: string[] | false | undefined;
|
|
28
|
+
extensions?: string[] | undefined;
|
|
26
29
|
thinking?: ThinkingLevel | undefined;
|
|
27
30
|
model?: string | undefined;
|
|
28
31
|
provider?: string | undefined;
|
|
29
32
|
temperature?: number | undefined;
|
|
30
33
|
topP?: number | undefined;
|
|
34
|
+
replacePrompt?: true | undefined;
|
|
31
35
|
systemPrompt: string;
|
|
32
36
|
source: AgentSource;
|
|
33
37
|
filePath: string;
|
|
@@ -71,6 +75,21 @@ function parseCommaList(raw: unknown): string[] | undefined {
|
|
|
71
75
|
return items.length > 0 ? items : undefined;
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
function parseExtensions(raw: unknown): string[] | undefined {
|
|
79
|
+
if (raw === undefined) return undefined;
|
|
80
|
+
if (raw === false || Array.isArray(raw)) return [];
|
|
81
|
+
if (typeof raw === "string") {
|
|
82
|
+
const trimmed = raw.trim();
|
|
83
|
+
if (trimmed.length === 0) return [];
|
|
84
|
+
const items = trimmed
|
|
85
|
+
.split(",")
|
|
86
|
+
.map((s) => s.trim())
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
return items.length > 0 ? items : [];
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
74
93
|
function parseThinkingLevel(raw: unknown): ThinkingLevel | undefined {
|
|
75
94
|
if (typeof raw !== "string") return undefined;
|
|
76
95
|
const normalized = raw.trim().toLowerCase();
|
|
@@ -124,29 +143,49 @@ function parseAgentConfig(
|
|
|
124
143
|
const {
|
|
125
144
|
name,
|
|
126
145
|
description,
|
|
146
|
+
context: rawContext,
|
|
127
147
|
tools: rawTools,
|
|
128
148
|
skills: rawSkills,
|
|
149
|
+
extensions: rawExtensions,
|
|
129
150
|
thinking: rawThinking,
|
|
130
151
|
model: rawModel,
|
|
131
152
|
provider: rawProvider,
|
|
132
153
|
temperature: rawTemperature,
|
|
133
154
|
top_p: rawTopP,
|
|
155
|
+
replace_prompt: rawReplacePrompt,
|
|
134
156
|
} = frontmatter;
|
|
135
157
|
if (typeof name !== "string" || typeof description !== "string") return null;
|
|
158
|
+
if (rawContext !== undefined && typeof rawContext !== "boolean") return null;
|
|
159
|
+
if (rawReplacePrompt !== undefined && typeof rawReplacePrompt !== "boolean")
|
|
160
|
+
return null;
|
|
136
161
|
if (isNonStringOptional(rawTools)) return null;
|
|
137
|
-
if (
|
|
162
|
+
if (rawSkills != null && typeof rawSkills !== "string" && rawSkills !== false)
|
|
163
|
+
return null;
|
|
164
|
+
if (
|
|
165
|
+
rawExtensions !== undefined &&
|
|
166
|
+
rawExtensions !== false &&
|
|
167
|
+
typeof rawExtensions !== "string" &&
|
|
168
|
+
!(Array.isArray(rawExtensions) && rawExtensions.length === 0)
|
|
169
|
+
)
|
|
170
|
+
return null;
|
|
138
171
|
if (isNonStringOptional(rawThinking)) return null;
|
|
139
172
|
if (isNonStringOptional(rawModel)) return null;
|
|
140
173
|
if (isNonStringOptional(rawProvider)) return null;
|
|
141
174
|
const tools = parseCommaList(rawTools);
|
|
142
175
|
const skills =
|
|
143
|
-
rawSkills
|
|
176
|
+
rawSkills === false
|
|
177
|
+
? false
|
|
178
|
+
: rawSkills !== undefined
|
|
179
|
+
? (parseCommaList(rawSkills) ?? [])
|
|
180
|
+
: undefined;
|
|
144
181
|
const thinking = parseThinkingLevel(rawThinking);
|
|
145
182
|
const model = parseOptionalString(rawModel);
|
|
146
183
|
const provider = parseOptionalString(rawProvider);
|
|
147
184
|
if (provider !== undefined && model === undefined) return null;
|
|
148
185
|
const temperature = parseSamplingValue(rawTemperature, "temperature", name);
|
|
149
186
|
const topP = parseSamplingValue(rawTopP, "top_p", name);
|
|
187
|
+
const extensions = parseExtensions(rawExtensions);
|
|
188
|
+
if (rawReplacePrompt === true && body.trim().length === 0) return null;
|
|
150
189
|
return {
|
|
151
190
|
name,
|
|
152
191
|
description,
|
|
@@ -158,8 +197,11 @@ function parseAgentConfig(
|
|
|
158
197
|
systemPrompt: body,
|
|
159
198
|
source,
|
|
160
199
|
filePath,
|
|
200
|
+
...(rawContext === false && { context: false }),
|
|
161
201
|
...(temperature !== undefined && { temperature }),
|
|
162
202
|
...(topP !== undefined && { topP }),
|
|
203
|
+
...(extensions !== undefined && { extensions }),
|
|
204
|
+
...(rawReplacePrompt === true && { replacePrompt: true }),
|
|
163
205
|
};
|
|
164
206
|
}
|
|
165
207
|
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
type Api,
|
|
7
8
|
clampThinkingLevel,
|
|
8
9
|
getSupportedThinkingLevels,
|
|
10
|
+
type Model,
|
|
9
11
|
type ModelThinkingLevel,
|
|
10
12
|
} from "@earendil-works/pi-ai";
|
|
11
13
|
import { getModel } from "@earendil-works/pi-ai/compat";
|
|
@@ -17,22 +19,87 @@ export type ChildModelSettings = {
|
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
+
* Minimal live-registry seam consumed by thinking-level resolution.
|
|
23
|
+
* Accepts the public {@link ModelRegistry} from the host extension context
|
|
24
|
+
* or any test double that provides the same lookup contract.
|
|
22
25
|
*/
|
|
23
|
-
export
|
|
26
|
+
export interface ModelRegistry {
|
|
27
|
+
find(provider: string, modelId: string): Model<Api> | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ResolveThinkingLevelOptions {
|
|
31
|
+
registry?: ModelRegistry | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ResolvedThinkingLevel {
|
|
35
|
+
level: ThinkingLevel;
|
|
36
|
+
warning?: string | undefined;
|
|
37
|
+
diagnostic?: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const FALLBACK_LEVEL: ThinkingLevel = "off";
|
|
41
|
+
|
|
42
|
+
function formatProviderModelLabel(
|
|
43
|
+
provider: string | undefined,
|
|
44
|
+
modelId: string | undefined,
|
|
45
|
+
): string {
|
|
46
|
+
const providerLabel = provider ?? "unknown";
|
|
47
|
+
const modelLabel = modelId ?? "unknown";
|
|
48
|
+
return `(provider: ${providerLabel}, model: ${modelLabel})`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formatUnsupportedWarning(
|
|
24
52
|
requested: ThinkingLevel,
|
|
53
|
+
effective: ThinkingLevel,
|
|
54
|
+
provider: string | undefined,
|
|
55
|
+
modelId: string | undefined,
|
|
56
|
+
): string {
|
|
57
|
+
return `Thinking level "${requested}" is not supported; using "${effective}" instead ${formatProviderModelLabel(provider, modelId)}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function formatUnconfirmedWarning(
|
|
61
|
+
requested: ThinkingLevel,
|
|
62
|
+
provider: string | undefined,
|
|
63
|
+
modelId: string | undefined,
|
|
64
|
+
): string {
|
|
65
|
+
return `Thinking level "${requested}" support could not be confirmed; requesting as-is ${formatProviderModelLabel(provider, modelId)}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveModel(
|
|
25
69
|
provider: string,
|
|
26
70
|
modelId: string,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
71
|
+
registry: ModelRegistry | undefined,
|
|
72
|
+
): { model: Model<Api> | undefined; diagnostic: string | undefined } {
|
|
73
|
+
if (registry) {
|
|
74
|
+
const live = registry.find(provider, modelId);
|
|
75
|
+
if (live) return { model: live, diagnostic: undefined };
|
|
76
|
+
}
|
|
77
|
+
const diagnostic = registry
|
|
78
|
+
? undefined
|
|
79
|
+
: "Live model registry unavailable; falling back to static catalog.";
|
|
80
|
+
const staticModel = getModel(provider as never, modelId as never) as
|
|
81
|
+
| Model<Api>
|
|
82
|
+
| undefined;
|
|
83
|
+
return { model: staticModel, diagnostic };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolveForModel(
|
|
87
|
+
requested: ThinkingLevel,
|
|
88
|
+
provider: string | undefined,
|
|
89
|
+
modelId: string | undefined,
|
|
90
|
+
model: Model<Api>,
|
|
91
|
+
): ResolvedThinkingLevel {
|
|
32
92
|
if (model.reasoning === false) {
|
|
33
|
-
return {
|
|
93
|
+
return {
|
|
94
|
+
level: FALLBACK_LEVEL,
|
|
95
|
+
warning: formatUnsupportedWarning(
|
|
96
|
+
requested,
|
|
97
|
+
FALLBACK_LEVEL,
|
|
98
|
+
provider,
|
|
99
|
+
modelId,
|
|
100
|
+
),
|
|
101
|
+
};
|
|
34
102
|
}
|
|
35
|
-
if (!model.thinkingLevelMap) return { level: requested };
|
|
36
103
|
const supported = getSupportedThinkingLevels(model);
|
|
37
104
|
if (supported.length === 0) return { level: requested };
|
|
38
105
|
const clamped = clampThinkingLevel(
|
|
@@ -40,7 +107,54 @@ export function resolveThinkingLevel(
|
|
|
40
107
|
requested as ModelThinkingLevel,
|
|
41
108
|
) as ThinkingLevel;
|
|
42
109
|
if (clamped === requested) return { level: requested };
|
|
43
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
level: clamped,
|
|
112
|
+
warning: formatUnsupportedWarning(requested, clamped, provider, modelId),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolves the effective thinking level for a model, preferring the live
|
|
118
|
+
* registry over the static catalog. Confirmed matches (live or static) clamp
|
|
119
|
+
* unsupported levels with the standard unsupported-level warning. Unconfirmed
|
|
120
|
+
* misses—missing provider or model identifiers, missing/unavailable registries,
|
|
121
|
+
* and live or static catalog misses—preserve the requested level for display
|
|
122
|
+
* while warning that support could not be confirmed. Requested `off` always
|
|
123
|
+
* stays warning-free in unconfirmed paths. Registry diagnostics are returned
|
|
124
|
+
* separately and never included in the user-facing warning.
|
|
125
|
+
*/
|
|
126
|
+
export function resolveThinkingLevel(
|
|
127
|
+
requested: ThinkingLevel,
|
|
128
|
+
provider: string | undefined,
|
|
129
|
+
modelId: string | undefined,
|
|
130
|
+
options?: ResolveThinkingLevelOptions,
|
|
131
|
+
): ResolvedThinkingLevel {
|
|
132
|
+
if (!provider || !modelId) {
|
|
133
|
+
if (requested === FALLBACK_LEVEL) return { level: FALLBACK_LEVEL };
|
|
134
|
+
return {
|
|
135
|
+
level: requested,
|
|
136
|
+
warning: formatUnconfirmedWarning(requested, provider, modelId),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const { model, diagnostic } = resolveModel(
|
|
140
|
+
provider,
|
|
141
|
+
modelId,
|
|
142
|
+
options?.registry,
|
|
143
|
+
);
|
|
144
|
+
if (!model) {
|
|
145
|
+
if (requested === FALLBACK_LEVEL) {
|
|
146
|
+
return diagnostic
|
|
147
|
+
? { level: FALLBACK_LEVEL, diagnostic }
|
|
148
|
+
: { level: FALLBACK_LEVEL };
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
level: requested,
|
|
152
|
+
warning: formatUnconfirmedWarning(requested, provider, modelId),
|
|
153
|
+
diagnostic,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const result = resolveForModel(requested, provider, modelId, model);
|
|
157
|
+
return diagnostic ? { ...result, diagnostic } : result;
|
|
44
158
|
}
|
|
45
159
|
|
|
46
160
|
/**
|
|
@@ -43,6 +43,17 @@ export function resolveSamplingExtensionPath(dir?: string): string {
|
|
|
43
43
|
return resolveExtensionPath("sampling-extension", dir);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the absolute path to the pi-subagent package entry extension file.
|
|
48
|
+
* This is the main index that registers the subagent tool and commands.
|
|
49
|
+
* Prefers the extension matching the current runtime, then falls back to the
|
|
50
|
+
* alternate extension. Throws if neither exists.
|
|
51
|
+
* Pass `dir` in tests to use a controlled directory.
|
|
52
|
+
*/
|
|
53
|
+
export function resolvePackageExtensionPath(dir?: string): string {
|
|
54
|
+
return resolveExtensionPath("../index", dir);
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
/**
|
|
47
58
|
* Appends data to a string while enforcing a maximum byte limit.
|
|
48
59
|
* Handles both string and Buffer inputs, truncating at valid UTF-8 boundaries.
|
|
@@ -79,7 +90,7 @@ export function truncateValidUtf8(buffer: Buffer, max: number): string {
|
|
|
79
90
|
/**
|
|
80
91
|
* Resolves the context window size in tokens for a given message.
|
|
81
92
|
* Returns undefined if the message doesn't have valid provider/model info
|
|
82
|
-
* or
|
|
93
|
+
* or the model isn't in the built-in catalog.
|
|
83
94
|
*
|
|
84
95
|
* Rationale: Subagent usage reporting needs context window awareness to provide
|
|
85
96
|
* meaningful "context full" indicators to the parent.
|
|
@@ -88,16 +99,11 @@ export function resolveContextWindowTokens(msg: Message): number | undefined {
|
|
|
88
99
|
const m = msg as unknown as Record<string, unknown>;
|
|
89
100
|
if (typeof m["provider"] !== "string" || typeof m["model"] !== "string")
|
|
90
101
|
return;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
: undefined;
|
|
99
|
-
} catch {
|
|
100
|
-
/* model lookup failures return undefined to skip context window tracking */
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
102
|
+
const contextWindow = getModel(
|
|
103
|
+
m["provider"] as never,
|
|
104
|
+
m["model"] as never,
|
|
105
|
+
)?.contextWindow;
|
|
106
|
+
return Number.isFinite(contextWindow) && contextWindow > 0
|
|
107
|
+
? contextWindow
|
|
108
|
+
: undefined;
|
|
103
109
|
}
|