pi-subagents-j0k3r 1.1.0 → 1.1.2
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 +6 -0
- package/README.md +10 -14
- package/index.ts +26 -22
- package/package.json +1 -1
- package/skills/subagents-configuration/SKILL.md +10 -9
- package/src/config.ts +34 -12
- package/src/model-profiles-ui.ts +45 -23
- package/src/profile-resolver.ts +7 -2
- package/src/tools.ts +2 -1
- package/src/types.ts +4 -0
- package/src/ui.ts +30 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Fixed manual task-mode background handoff so it frees the chat only when the user explicitly sends the running subagent to background.
|
|
7
|
+
- Fixed background completion delivery so notifications arrive while the main agent continues working without triggering an extra follow-up turn.
|
|
8
|
+
|
|
3
9
|
## 1.1.0 - 2026-06-27
|
|
4
10
|
|
|
5
11
|
### Added
|
package/README.md
CHANGED
|
@@ -74,6 +74,8 @@ Load order:
|
|
|
74
74
|
|
|
75
75
|
Project definitions override global definitions with the same normalized name. Within the same scope, `subagents` definitions override `agents` definitions with the same normalized name and Pi shows a startup warning so the duplicate can be cleaned up.
|
|
76
76
|
|
|
77
|
+
The npm package is the extension runtime only. It does not ship or load subagent definitions from `node_modules/pi-subagents-j0k3r/agents`; use the directories above, or run `subagent_list_agents` / `subagent({ action: "list" })` to inspect the definitions Pi actually loaded.
|
|
78
|
+
|
|
77
79
|
Default global agent directory:
|
|
78
80
|
|
|
79
81
|
```txt
|
|
@@ -129,7 +131,7 @@ Config files:
|
|
|
129
131
|
.pi/subagents.json # project
|
|
130
132
|
```
|
|
131
133
|
|
|
132
|
-
Config resolves as a cascade: project `.pi/subagents.json` overrides global `~/.pi/agent/subagents.json`; missing project fields fall back to global config; fields missing from both fall back to built-in defaults. `model_profiles` are the
|
|
134
|
+
Config resolves as a cascade: project `.pi/subagents.json` overrides global `~/.pi/agent/subagents.json`; missing project fields fall back to global config; fields missing from both fall back to built-in defaults. `model_profiles` are scoped to the definition source: project-local definitions use `.pi/subagents.json`, while global definitions use the global `subagents.json`. If a project definition overrides a global definition with the same name, the project definition and its project-local profile win.
|
|
133
135
|
|
|
134
136
|
Example:
|
|
135
137
|
|
|
@@ -170,7 +172,7 @@ Example:
|
|
|
170
172
|
|---|---:|---|
|
|
171
173
|
| `default_model` | current orchestrator model | Fallback model for all subagents. Format: `provider/model-id`. |
|
|
172
174
|
| `default_effort` | current orchestrator effort | Fallback thinking effort. Also accepts `default_thinking_level` or `thinkingLevel`. |
|
|
173
|
-
| `model_profiles` | `{}` |
|
|
175
|
+
| `model_profiles` | `{}` | Per-agent model/effort overrides scoped to matching definitions. Project-local profiles apply to project-local definitions; global profiles apply to global definitions. |
|
|
174
176
|
| `timeout_ms` | `600000` | Total timeout per subagent task. |
|
|
175
177
|
| `stall_timeout_ms` | `120000` | Inactivity timeout for a subagent session. |
|
|
176
178
|
| `max_concurrency` | `5` | Max concurrent subagent tasks per cwd/config pair. |
|
|
@@ -202,7 +204,7 @@ any tool starting with subagent_
|
|
|
202
204
|
|
|
203
205
|
Effective model resolution order:
|
|
204
206
|
|
|
205
|
-
1. `model_profiles[agent].model`
|
|
207
|
+
1. `model_profiles[agent].model` from the config matching the selected definition scope: project-local for project definitions, global for global definitions
|
|
206
208
|
2. subagent frontmatter `model`
|
|
207
209
|
3. `default_model`
|
|
208
210
|
4. current orchestrator model
|
|
@@ -210,7 +212,7 @@ Effective model resolution order:
|
|
|
210
212
|
|
|
211
213
|
Effective effort resolution order:
|
|
212
214
|
|
|
213
|
-
1. `model_profiles[agent].effort`
|
|
215
|
+
1. `model_profiles[agent].effort` from the config matching the selected definition scope: project-local for project definitions, global for global definitions
|
|
214
216
|
2. subagent frontmatter `effort` / `thinking_level` / `thinkingLevel`
|
|
215
217
|
3. `default_effort`
|
|
216
218
|
4. current orchestrator thinking level
|
|
@@ -284,20 +286,14 @@ Behavior:
|
|
|
284
286
|
| Entry point | Description |
|
|
285
287
|
|---|---|
|
|
286
288
|
| `/subagents` | Open the session-focused TUI subagent history panel. |
|
|
287
|
-
| `/subagent-models` | Configure
|
|
289
|
+
| `/subagent-models` | Configure subagent and SDD phase model profiles in the matching local or global config. |
|
|
288
290
|
| `ctrl+,` | Open the TUI subagent history panel in OpenCode mode by default. Configurable via `history_panel_shortcut` in `subagents.json`. |
|
|
289
291
|
| `x` | Cancel the currently selected queued/running subagent from the open history/detail panel by default. Configurable via `detail_cancel_shortcut` in `subagents.json`. |
|
|
290
292
|
| `ctrl+h` | Send the running Claude-mode subagent task to the background by default. Configurable via `background_handoff_shortcut` in `subagents.json`. |
|
|
291
293
|
|
|
292
|
-
`/subagent-models` writes
|
|
293
|
-
|
|
294
|
-
```txt
|
|
295
|
-
~/.pi/agent/subagents.json
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
or `$PI_CODING_AGENT_DIR/subagents.json` when `PI_CODING_AGENT_DIR` is set.
|
|
294
|
+
`/subagent-models` writes profile changes to the config that matches each selected definition: project-local subagents write to `.pi/subagents.json`, while global subagents and synthetic SDD phase rows write to `~/.pi/agent/subagents.json` or `$PI_CODING_AGENT_DIR/subagents.json` when `PI_CODING_AGENT_DIR` is set.
|
|
299
295
|
|
|
300
|
-
In non-TUI environments, edit `model_profiles` manually in
|
|
296
|
+
In non-TUI environments, edit `model_profiles` manually in the matching local or global JSON file.
|
|
301
297
|
|
|
302
298
|
## Task history
|
|
303
299
|
|
|
@@ -394,7 +390,7 @@ This package bundles:
|
|
|
394
390
|
- `index.ts` and `src/**` — the Pi extension runtime.
|
|
395
391
|
- `skills/subagents-configuration/SKILL.md` — configuration guidance for agents that need to create or edit subagent definitions.
|
|
396
392
|
|
|
397
|
-
Subagent definitions are intentionally user/project configuration, not hard-coded package behavior. Add them globally in `$PI_CODING_AGENT_DIR/agents/*.md` or `$PI_CODING_AGENT_DIR/subagents/*.md`, or project-locally in `.pi/agents/*.md` or `.pi/subagents/*.md`.
|
|
393
|
+
Subagent definitions are intentionally user/project configuration, not hard-coded package behavior. Add them globally in `$PI_CODING_AGENT_DIR/agents/*.md` or `$PI_CODING_AGENT_DIR/subagents/*.md`, or project-locally in `.pi/agents/*.md` or `.pi/subagents/*.md`. Do not inspect `node_modules/pi-subagents-j0k3r/agents` for definitions; that path is not part of the package design and may not exist.
|
|
398
394
|
|
|
399
395
|
## Development
|
|
400
396
|
|
package/index.ts
CHANGED
|
@@ -290,6 +290,31 @@ export function completionMessage(task: any): string {
|
|
|
290
290
|
].join('\n');
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
export function sendSubagentCompletionMessage(pi: any, task: any): void {
|
|
294
|
+
pi.sendMessage?.({
|
|
295
|
+
customType: 'subagent-completion',
|
|
296
|
+
content: completionMessage(task),
|
|
297
|
+
display: true,
|
|
298
|
+
details: {
|
|
299
|
+
full_result: task.result ?? task.error ?? task.output_preview,
|
|
300
|
+
task: {
|
|
301
|
+
id: task.id,
|
|
302
|
+
agent: task.agent,
|
|
303
|
+
status: task.status,
|
|
304
|
+
mode: task.mode,
|
|
305
|
+
model: task.model,
|
|
306
|
+
effort: task.effort,
|
|
307
|
+
usage: task.usage,
|
|
308
|
+
result: task.result,
|
|
309
|
+
error: task.error,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
}, {
|
|
313
|
+
triggerTurn: false,
|
|
314
|
+
deliverAs: 'steer',
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
293
318
|
export function renderSubagentCompletionMessage(message: any, options: any, theme: any) {
|
|
294
319
|
const details = message.details ?? {};
|
|
295
320
|
const task = details.task ?? details;
|
|
@@ -336,28 +361,7 @@ export function renderSubagentCompletionMessage(message: any, options: any, them
|
|
|
336
361
|
export default function subagentsExtension(pi: any): void {
|
|
337
362
|
pi.registerMessageRenderer?.('subagent-completion', renderSubagentCompletionMessage);
|
|
338
363
|
const manager = new SubagentManager(undefined, undefined, (task) => {
|
|
339
|
-
pi
|
|
340
|
-
customType: 'subagent-completion',
|
|
341
|
-
content: completionMessage(task),
|
|
342
|
-
display: true,
|
|
343
|
-
details: {
|
|
344
|
-
full_result: task.result ?? task.error ?? task.output_preview,
|
|
345
|
-
task: {
|
|
346
|
-
id: task.id,
|
|
347
|
-
agent: task.agent,
|
|
348
|
-
status: task.status,
|
|
349
|
-
mode: task.mode,
|
|
350
|
-
model: task.model,
|
|
351
|
-
effort: task.effort,
|
|
352
|
-
usage: task.usage,
|
|
353
|
-
result: task.result,
|
|
354
|
-
error: task.error,
|
|
355
|
-
},
|
|
356
|
-
},
|
|
357
|
-
}, {
|
|
358
|
-
triggerTurn: true,
|
|
359
|
-
deliverAs: 'followUp',
|
|
360
|
-
});
|
|
364
|
+
sendSubagentCompletionMessage(pi, task);
|
|
361
365
|
});
|
|
362
366
|
registerSubagentTools(pi, manager);
|
|
363
367
|
|
package/package.json
CHANGED
|
@@ -92,10 +92,11 @@ Do not load this skill for ordinary subagent delegation/use (`subagent_run`, tas
|
|
|
92
92
|
- For SDD/PRD phase agents, prefer deterministic active-flow memory tools only: `memory_search`, `memory_get`, `memory_add`, and `memory_update`; avoid `memory_context` and `memory_recall` in subagent allowlists unless there is a specific reviewed need.
|
|
93
93
|
- For SDD phase agents, memory write tools may be allowed only for active SDD flow memory/artifacts according to `sdd-workflow`.
|
|
94
94
|
- Project subagent definitions live in `.pi/agents/*.md` and `.pi/subagents/*.md`; global user definitions live in `$PI_CODING_AGENT_DIR/agents/*.md`, `$PI_CODING_AGENT_DIR/subagents/*.md`, `~/.pi/agent/agents/*.md`, or `~/.pi/agent/subagents/*.md`.
|
|
95
|
+
- The npm package is the extension runtime only; do not tell users or future agents to inspect `node_modules/pi-subagents-j0k3r/agents` for subagent definitions. Use the real global/project definition directories above, or runtime listing via `subagent_list_agents` / `subagent({ action: "list" })`.
|
|
95
96
|
- Project definitions override global definitions with the same normalized name. Within the same scope, definitions in `subagents` override definitions in `agents` with the same normalized name, and Pi should warn at session startup so users can clean up the duplicate.
|
|
96
|
-
- Subagents config resolves as a cascade: project `.pi/subagents.json` overrides global `$PI_CODING_AGENT_DIR/subagents.json` or `~/.pi/agent/subagents.json`; missing project fields fall back to global config; fields missing from both fall back to built-in defaults. Communicate this precedence to users when explaining config behavior
|
|
97
|
-
- `model_profiles` are
|
|
98
|
-
- Prefer configuring subagent `model` and `effort`
|
|
97
|
+
- Subagents config resolves as a cascade: project `.pi/subagents.json` overrides global `$PI_CODING_AGENT_DIR/subagents.json` or `~/.pi/agent/subagents.json`; missing project fields fall back to global config; fields missing from both fall back to built-in defaults. Communicate this precedence to users when explaining config behavior.
|
|
98
|
+
- `model_profiles` are scoped to the matching subagent definition source: project-local profile entries in `.pi/subagents.json` apply to project-local definitions, while global profile entries apply to global definitions. If a project definition overrides a global definition with the same normalized name, the project definition and its project-local profile win.
|
|
99
|
+
- Prefer configuring subagent `model` and `effort` under `model_profiles` in the config matching the definition scope: project-local definitions use `.pi/subagents.json`; global definitions use `$PI_CODING_AGENT_DIR/subagents.json` or `~/.pi/agent/subagents.json`. Markdown definitions should usually contain identity, description, tool allowlist, and behavioral instructions only.
|
|
99
100
|
- Nested subagent sessions should use `session_resources: "lean"` by default so the subagent markdown body becomes the nested session system prompt, the delegated user prompt contains only orchestrator context/task, and workflow skills, prompt templates, themes, context files, and startup context injections are not auto-loaded.
|
|
100
101
|
- In lean mode, extensions are loaded for allowlisted tools and tool-safety hooks only; prompt/context lifecycle hooks such as `before_agent_start` and `context` must not inject hidden messages into subagent turns.
|
|
101
102
|
- Subagent task history is stored globally under data storage, but rows remain project-scoped by `cwd`; history stores delegated prompt and subagent system prompt separately.
|
|
@@ -154,7 +155,7 @@ tools:
|
|
|
154
155
|
Instructions...
|
|
155
156
|
```
|
|
156
157
|
|
|
157
|
-
Configure model/effort routing separately in the global `subagents.json` when needed. If no
|
|
158
|
+
Configure model/effort routing separately in the matching local or global `subagents.json` when needed. If no matching profile/default is configured, the subagent inherits the current orchestrator model and thinking effort.
|
|
158
159
|
|
|
159
160
|
```json
|
|
160
161
|
{
|
|
@@ -169,7 +170,7 @@ Configure model/effort routing separately in the global `subagents.json` when ne
|
|
|
169
170
|
|
|
170
171
|
Model/effort resolution order:
|
|
171
172
|
|
|
172
|
-
1.
|
|
173
|
+
1. `model_profiles[agentName]` from the config matching the selected definition scope: `.pi/subagents.json` for project-local definitions, or `$PI_CODING_AGENT_DIR/subagents.json` / `~/.pi/agent/subagents.json` for global definitions.
|
|
173
174
|
2. Markdown frontmatter `model` / `effort` only for explicit per-file overrides.
|
|
174
175
|
3. `default_model` / `default_effort` from effective `subagents.json` config.
|
|
175
176
|
4. Current orchestrator model / effort.
|
|
@@ -179,16 +180,16 @@ Model/effort resolution order:
|
|
|
179
180
|
- If the subagent will modify files, run bash, or write memory, ask whether a full SDD workflow or stricter review is required.
|
|
180
181
|
- If the subagent needs human input, require a structured `interaction_required` request with enough prompt, payload, and expected-response data for the parent to answer.
|
|
181
182
|
- If a project wants many subagents or broad tools, recommend starting with read-only discovery agents and expanding deliberately.
|
|
182
|
-
- If the user asks for
|
|
183
|
+
- If the user asks for model profiles, choose the config that matches the subagent definition scope: project-local profiles for project-local definitions and global profiles for global definitions or synthetic SDD phase rows.
|
|
183
184
|
|
|
184
185
|
## Execution Steps
|
|
185
186
|
|
|
186
187
|
1. Identify target scope: npm package install/update, global subagent, project subagent, global config, or project config, and explain the cascade when relevant: project-local config first, then global config for missing fields, then built-in defaults.
|
|
187
188
|
2. For package setup, inspect settings before editing; use `pi install npm:pi-subagents-j0k3r` when possible, or edit `~/.pi/agent/settings.json` only when the CLI is unavailable/broken. Prefer unpinned `npm:pi-subagents-j0k3r` unless the user asks for a fixed version.
|
|
188
|
-
3. Read existing subagent markdown/config before editing, checking both `agents` and `subagents` directories for the requested scope.
|
|
189
|
-
4. For new subagents, choose lowercase kebab-case names and clear trigger-focused descriptions. Prefer `subagents` for new definitions unless the user explicitly needs compatibility with an `agents` harness.
|
|
189
|
+
3. Read existing subagent markdown/config before editing, checking both `agents` and `subagents` directories for the requested scope. Check optional directories for existence before listing them so missing user/project definition directories do not produce confusing `Path not found` messages.
|
|
190
|
+
4. For new subagents, choose lowercase kebab-case names and clear trigger-focused descriptions. Write subagent markdown definitions in English by default; use another language only when the user explicitly requests it. Prefer `subagents` for new definitions unless the user explicitly needs compatibility with an `agents` harness.
|
|
190
191
|
5. Set minimal tool allowlists; remove any `subagent_*` entries.
|
|
191
|
-
6. Configure `model_profiles`
|
|
192
|
+
6. Configure `model_profiles` in the matching local/global `subagents.json` when the user wants explicit per-agent routing. Configure `default_model` and `default_effort` in effective `subagents.json` only when the user wants defaults. Do not put model routing in subagent markdown unless the user explicitly asks for per-file overrides. Explain that unconfigured fields inherit from the orchestrator.
|
|
192
193
|
7. Configure `debug: true` only for temporary diagnostics; keep `debug: false` by default and remember logs are written under the executing project's `.pi` directory.
|
|
193
194
|
8. Validate JSON syntax for settings/subagents config and frontmatter/body structure for markdown agents.
|
|
194
195
|
9. When configuring OpenCode-mode history opening, prefer `history_panel_shortcut` with `ctrl+<letter>` or `ctrl+,` values and document any built-in shortcut conflicts.
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import type { ModelRef, SubagentDefinition, SubagentModelProfile, SubagentModelProfiles, SubagentSessionResources, SubagentsConfig, SubagentUiMode, ThinkingEffort } from './types.js';
|
|
4
|
+
import type { ModelRef, SubagentDefinition, SubagentDefinitionScope, SubagentModelProfile, SubagentModelProfiles, SubagentSessionResources, SubagentsConfig, SubagentUiMode, ThinkingEffort } from './types.js';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_TOOLS = ['read', 'memory_context', 'memory_search', 'memory_recall', 'memory_get'];
|
|
7
7
|
const DEFAULT_MAX_CONCURRENCY = 5;
|
|
@@ -155,7 +155,7 @@ function parseModelProfiles(value: unknown): SubagentModelProfiles {
|
|
|
155
155
|
if (!isPlainObject(value)) return {};
|
|
156
156
|
const profiles: SubagentModelProfiles = {};
|
|
157
157
|
for (const [name, rawProfile] of Object.entries(value)) {
|
|
158
|
-
const normalizedName = name.trim();
|
|
158
|
+
const normalizedName = name.trim().toLowerCase();
|
|
159
159
|
if (!normalizedName) continue;
|
|
160
160
|
const profile = parseModelProfile(rawProfile);
|
|
161
161
|
if (profile) profiles[normalizedName] = profile;
|
|
@@ -178,10 +178,14 @@ export function readSubagentsConfig(cwd: string): SubagentsConfig {
|
|
|
178
178
|
const globalRaw = readJson(subagentsConfigPath());
|
|
179
179
|
const projectRaw = readJson(path.join(cwd, '.pi', 'subagents.json'));
|
|
180
180
|
const raw = { ...globalRaw, ...projectRaw };
|
|
181
|
+
const globalModelProfiles = parseModelProfiles(globalRaw.model_profiles);
|
|
182
|
+
const projectModelProfiles = parseModelProfiles(projectRaw.model_profiles);
|
|
181
183
|
return {
|
|
182
184
|
default_model: parseModel(raw.default_model),
|
|
183
185
|
default_effort: parseEffort(raw.default_effort ?? raw.default_thinking_level ?? raw.thinkingLevel),
|
|
184
|
-
model_profiles:
|
|
186
|
+
model_profiles: { ...globalModelProfiles, ...projectModelProfiles },
|
|
187
|
+
global_model_profiles: globalModelProfiles,
|
|
188
|
+
project_model_profiles: projectModelProfiles,
|
|
185
189
|
timeout_ms: positiveInteger(raw.timeout_ms, DEFAULT_TIMEOUT_MS),
|
|
186
190
|
stall_timeout_ms: positiveInteger(raw.stall_timeout_ms, DEFAULT_STALL_TIMEOUT_MS),
|
|
187
191
|
max_concurrency: positiveInteger(raw.max_concurrency, DEFAULT_MAX_CONCURRENCY),
|
|
@@ -195,8 +199,16 @@ export function readSubagentsConfig(cwd: string): SubagentsConfig {
|
|
|
195
199
|
};
|
|
196
200
|
}
|
|
197
201
|
|
|
198
|
-
|
|
199
|
-
|
|
202
|
+
function projectSubagentsConfigPath(cwd: string): string {
|
|
203
|
+
return path.join(cwd, '.pi', 'subagents.json');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function subagentsConfigPathForScope(input: { scope?: SubagentDefinitionScope; cwd?: string; agentDir?: string }): string {
|
|
207
|
+
return input.scope === 'project' && input.cwd ? projectSubagentsConfigPath(input.cwd) : subagentsConfigPath(input.agentDir);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function saveSubagentModelProfile(input: { agentName: string; profile: SubagentModelProfile; scope?: SubagentDefinitionScope; cwd?: string; agentDir?: string }): void {
|
|
211
|
+
const file = subagentsConfigPathForScope(input);
|
|
200
212
|
const root = readJson(file);
|
|
201
213
|
const writableRoot: Record<string, unknown> = isPlainObject(root) ? { ...root } : {};
|
|
202
214
|
const modelProfiles = isPlainObject(writableRoot.model_profiles) ? { ...writableRoot.model_profiles } : {};
|
|
@@ -209,8 +221,12 @@ export function saveGlobalSubagentModelProfile(input: { agentName: string; profi
|
|
|
209
221
|
fs.writeFileSync(file, `${JSON.stringify(writableRoot, null, 2)}\n`, 'utf8');
|
|
210
222
|
}
|
|
211
223
|
|
|
212
|
-
export function
|
|
213
|
-
|
|
224
|
+
export function saveGlobalSubagentModelProfile(input: { agentName: string; profile: SubagentModelProfile; agentDir?: string }): void {
|
|
225
|
+
saveSubagentModelProfile({ ...input, scope: 'global' });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function resetSubagentModelProfileField(input: { agentName: string; field: 'model' | 'effort'; scope?: SubagentDefinitionScope; cwd?: string; agentDir?: string }): void {
|
|
229
|
+
const file = subagentsConfigPathForScope(input);
|
|
214
230
|
const root = readJson(file);
|
|
215
231
|
const writableRoot: Record<string, unknown> = isPlainObject(root) ? { ...root } : {};
|
|
216
232
|
const modelProfiles = isPlainObject(writableRoot.model_profiles) ? { ...writableRoot.model_profiles } : {};
|
|
@@ -225,7 +241,11 @@ export function resetGlobalSubagentModelProfileField(input: { agentName: string;
|
|
|
225
241
|
fs.writeFileSync(file, `${JSON.stringify(writableRoot, null, 2)}\n`, 'utf8');
|
|
226
242
|
}
|
|
227
243
|
|
|
228
|
-
function
|
|
244
|
+
export function resetGlobalSubagentModelProfileField(input: { agentName: string; field: 'model' | 'effort'; agentDir?: string }): void {
|
|
245
|
+
resetSubagentModelProfileField({ ...input, scope: 'global' });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function loadSubagentsFromDir(dir: string, scope: SubagentDefinitionScope): SubagentDefinition[] {
|
|
229
249
|
if (!fs.existsSync(dir)) return [];
|
|
230
250
|
return fs.readdirSync(dir)
|
|
231
251
|
.filter((f) => f.endsWith('.md'))
|
|
@@ -236,7 +256,7 @@ function loadSubagentsFromDir(dir: string): SubagentDefinition[] {
|
|
|
236
256
|
const name = String(data.name || path.basename(file, '.md')).trim().toLowerCase();
|
|
237
257
|
const description = String(data.description || `${name} subagent`).trim();
|
|
238
258
|
const tools = sanitizeTools(Array.isArray(data.tools) ? data.tools.map(String) : DEFAULT_TOOLS);
|
|
239
|
-
return { name, description, filePath, instructions: body.trim(), model: parseModel(data.model), effort: parseEffort(data.effort ?? data.thinking_level ?? data.thinkingLevel), tools };
|
|
259
|
+
return { name, description, filePath, instructions: body.trim(), model: parseModel(data.model), effort: parseEffort(data.effort ?? data.thinking_level ?? data.thinkingLevel), tools, scope };
|
|
240
260
|
});
|
|
241
261
|
}
|
|
242
262
|
|
|
@@ -254,8 +274,10 @@ export function subagentSourceWarnings(cwd: string): string[] {
|
|
|
254
274
|
const warnings: string[] = [];
|
|
255
275
|
for (const scope of ['global', 'project'] as const) {
|
|
256
276
|
const sources = agentsDirSources(cwd).filter((source) => source.scope === scope);
|
|
257
|
-
const
|
|
258
|
-
const
|
|
277
|
+
const agentsSource = sources.find((source) => source.kind === 'agents')!;
|
|
278
|
+
const subagentsSource = sources.find((source) => source.kind === 'subagents')!;
|
|
279
|
+
const agents = loadSubagentsFromDir(agentsSource.dir, agentsSource.scope);
|
|
280
|
+
const subagents = loadSubagentsFromDir(subagentsSource.dir, subagentsSource.scope);
|
|
259
281
|
const subagentNames = new Map(subagents.map((definition) => [definition.name, definition]));
|
|
260
282
|
for (const agent of agents) {
|
|
261
283
|
const subagent = subagentNames.get(agent.name);
|
|
@@ -269,7 +291,7 @@ export function subagentSourceWarnings(cwd: string): string[] {
|
|
|
269
291
|
export function loadSubagents(cwd: string): SubagentDefinition[] {
|
|
270
292
|
const byName = new Map<string, SubagentDefinition>();
|
|
271
293
|
for (const source of agentsDirSources(cwd)) {
|
|
272
|
-
for (const agent of loadSubagentsFromDir(source.dir)) byName.set(agent.name, agent);
|
|
294
|
+
for (const agent of loadSubagentsFromDir(source.dir, source.scope)) byName.set(agent.name, agent);
|
|
273
295
|
}
|
|
274
296
|
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
275
297
|
}
|
package/src/model-profiles-ui.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { loadSubagents, readSubagentsConfig,
|
|
3
|
+
import { loadSubagents, readSubagentsConfig, resetSubagentModelProfileField, saveSubagentModelProfile } from './config.js';
|
|
4
4
|
import { resolveEffectiveSubagentProfile } from './profile-resolver.js';
|
|
5
|
-
import type { ModelRef, SubagentDefinition, SubagentModelProfile, SubagentModelProfiles, SubagentsConfig, ThinkingEffort } from './types.js';
|
|
5
|
+
import type { ModelRef, SubagentDefinition, SubagentDefinitionScope, SubagentModelProfile, SubagentModelProfiles, SubagentsConfig, ThinkingEffort } from './types.js';
|
|
6
6
|
|
|
7
7
|
export const KNOWN_SDD_PHASES = [
|
|
8
8
|
'sdd-explore',
|
|
@@ -28,12 +28,17 @@ export type ModelProfileRow = {
|
|
|
28
28
|
effectiveModel?: ModelRef;
|
|
29
29
|
effectiveEffort?: ThinkingEffort;
|
|
30
30
|
explicitProfile: SubagentModelProfile;
|
|
31
|
+
scope?: SubagentDefinitionScope;
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
export function globalSubagentsConfigPath(agentDir = path.join(os.homedir(), '.pi', 'agent')): string {
|
|
34
35
|
return path.join(agentDir, 'subagents.json');
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
export function projectSubagentsConfigPath(cwd: string): string {
|
|
39
|
+
return path.join(cwd, '.pi', 'subagents.json');
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
function modelKey(model: ModelRef): string {
|
|
38
43
|
return `${model.provider}/${model.id}`;
|
|
39
44
|
}
|
|
@@ -101,7 +106,8 @@ export function buildModelProfileRows(input: {
|
|
|
101
106
|
effortLabel: resolved.effort.label,
|
|
102
107
|
effectiveModel: resolved.model.value,
|
|
103
108
|
effectiveEffort: resolved.effort.value,
|
|
104
|
-
explicitProfile: { ...(input.config.
|
|
109
|
+
explicitProfile: { ...((definition.scope === 'project' ? input.config.project_model_profiles?.[definition.name] : input.config.global_model_profiles?.[definition.name]) ?? {}) },
|
|
110
|
+
scope: definition.scope ?? 'global',
|
|
105
111
|
};
|
|
106
112
|
});
|
|
107
113
|
}
|
|
@@ -156,16 +162,25 @@ export function applyDirtyProfileEdit(input: {
|
|
|
156
162
|
return nextDirtyProfiles;
|
|
157
163
|
}
|
|
158
164
|
|
|
159
|
-
export function commitStagedModelProfiles(input: { stagedProfiles: SubagentModelProfiles; save: boolean; agentDir?: string }): string {
|
|
160
|
-
|
|
165
|
+
export function commitStagedModelProfiles(input: { stagedProfiles: SubagentModelProfiles; save: boolean; agentDir?: string; cwd?: string; profileScopes?: Record<string, SubagentDefinitionScope> }): string {
|
|
166
|
+
const globalPath = globalSubagentsConfigPath(input.agentDir);
|
|
167
|
+
const projectPath = input.cwd ? projectSubagentsConfigPath(input.cwd) : undefined;
|
|
168
|
+
if (!input.save) return `Cancelled. No changes written to ${globalPath}.`;
|
|
169
|
+
const touched = new Set<string>();
|
|
161
170
|
for (const [agentName, profile] of Object.entries(input.stagedProfiles)) {
|
|
162
|
-
|
|
171
|
+
const scope = input.profileScopes?.[agentName.trim().toLowerCase()] ?? 'global';
|
|
172
|
+
const target = scope === 'project' && input.cwd ? projectPath! : globalPath;
|
|
173
|
+
touched.add(target);
|
|
174
|
+
if (profile.model || profile.effort) saveSubagentModelProfile({ agentName, profile, scope, cwd: input.cwd, agentDir: input.agentDir });
|
|
163
175
|
else {
|
|
164
|
-
|
|
165
|
-
|
|
176
|
+
resetSubagentModelProfileField({ agentName, field: 'model', scope, cwd: input.cwd, agentDir: input.agentDir });
|
|
177
|
+
resetSubagentModelProfileField({ agentName, field: 'effort', scope, cwd: input.cwd, agentDir: input.agentDir });
|
|
166
178
|
}
|
|
167
179
|
}
|
|
168
|
-
|
|
180
|
+
const targets = [...touched];
|
|
181
|
+
return targets.length > 1
|
|
182
|
+
? `Saved subagent model profiles to ${targets.join(' and ')}.`
|
|
183
|
+
: `Saved subagent model profiles to ${targets[0] ?? globalPath}.`;
|
|
169
184
|
}
|
|
170
185
|
|
|
171
186
|
export function buildNoChangesModelProfilesMessage(agentDir?: string): string {
|
|
@@ -188,6 +203,7 @@ type ModalInput = {
|
|
|
188
203
|
rows: ModelProfileRow[];
|
|
189
204
|
availableModels?: any[];
|
|
190
205
|
tui?: { requestRender?: () => void };
|
|
206
|
+
theme?: any;
|
|
191
207
|
done: (result: SubagentModelProfilesModalResult) => void;
|
|
192
208
|
};
|
|
193
209
|
|
|
@@ -322,6 +338,9 @@ export function createSubagentModelProfilesModal(input: ModalInput): ModalCompon
|
|
|
322
338
|
};
|
|
323
339
|
|
|
324
340
|
const rowKey = (row: ModelProfileRow): string => row.name.trim().toLowerCase();
|
|
341
|
+
const rowScope = (row: ModelProfileRow): SubagentDefinitionScope => row.scope ?? 'global';
|
|
342
|
+
const dim = (text: string): string => input.theme?.fg?.('dim', text) ?? text;
|
|
343
|
+
const scopedName = (row: ModelProfileRow): string => `${row.name} ${dim(rowScope(row) === 'project' ? '(local)' : '(global)')}`;
|
|
325
344
|
const hasDirtyProfileFor = (row: ModelProfileRow): boolean => Object.prototype.hasOwnProperty.call(dirtyProfiles, rowKey(row));
|
|
326
345
|
const dirtyProfileFor = (row: ModelProfileRow): SubagentModelProfile | undefined => dirtyProfiles[rowKey(row)];
|
|
327
346
|
|
|
@@ -351,16 +370,16 @@ export function createSubagentModelProfilesModal(input: ModalInput): ModalCompon
|
|
|
351
370
|
const rowListLines = (width: number): string[] => {
|
|
352
371
|
const innerWidth = Math.max(1, Math.floor(width || 1) - 2);
|
|
353
372
|
const visibleRows = rows.slice(scrollOffset, scrollOffset + 10);
|
|
354
|
-
if (innerWidth >=
|
|
355
|
-
const nameWidth =
|
|
356
|
-
const effortWidth =
|
|
357
|
-
const modelWidth = Math.max(
|
|
373
|
+
if (innerWidth >= 100) {
|
|
374
|
+
const nameWidth = 32;
|
|
375
|
+
const effortWidth = 24;
|
|
376
|
+
const modelWidth = Math.max(24, innerWidth - nameWidth - effortWidth - 6);
|
|
358
377
|
const lines = [`${padToVisibleWidth('agent/phase', nameWidth)} ${padToVisibleWidth('model', modelWidth)} ${padToVisibleWidth('effort', effortWidth)}`];
|
|
359
378
|
for (const [offset, item] of visibleRows.entries()) {
|
|
360
379
|
const index = scrollOffset + offset;
|
|
361
380
|
const marker = index === selectedIndex ? '›' : ' ';
|
|
362
381
|
const dirty = hasDirtyProfileFor(item) ? '*' : ' ';
|
|
363
|
-
lines.push(`${marker} ${dirty} ${padToVisibleWidth(item
|
|
382
|
+
lines.push(`${marker} ${dirty} ${padToVisibleWidth(scopedName(item), nameWidth - 4)} ${padToVisibleWidth(rowModelText(item), modelWidth)} ${padToVisibleWidth(rowEffortText(item), effortWidth)}`);
|
|
364
383
|
}
|
|
365
384
|
return lines;
|
|
366
385
|
}
|
|
@@ -369,7 +388,7 @@ export function createSubagentModelProfilesModal(input: ModalInput): ModalCompon
|
|
|
369
388
|
const index = scrollOffset + offset;
|
|
370
389
|
const marker = index === selectedIndex ? '›' : ' ';
|
|
371
390
|
const dirty = hasDirtyProfileFor(item) ? '*' : ' ';
|
|
372
|
-
lines.push(`${marker} ${dirty} ${item
|
|
391
|
+
lines.push(`${marker} ${dirty} ${scopedName(item)} · ${rowModelText(item)} · ${rowEffortText(item)}`);
|
|
373
392
|
}
|
|
374
393
|
return lines;
|
|
375
394
|
};
|
|
@@ -377,7 +396,7 @@ export function createSubagentModelProfilesModal(input: ModalInput): ModalCompon
|
|
|
377
396
|
const renderMain = (width: number): string[] => {
|
|
378
397
|
const dirtyCount = Object.keys(dirtyProfiles).length;
|
|
379
398
|
const body = [
|
|
380
|
-
`target: global · ${pendingLabel(dirtyCount)}`,
|
|
399
|
+
`target: local/global by subagent scope · ${pendingLabel(dirtyCount)}`,
|
|
381
400
|
'↑/↓/j/k move · enter/m model · e effort · M/E/r reset · s save · esc/q cancel',
|
|
382
401
|
'',
|
|
383
402
|
...rowListLines(width),
|
|
@@ -513,7 +532,8 @@ export function buildNonTuiModelProfilesMessage(agentDir?: string): string {
|
|
|
513
532
|
}
|
|
514
533
|
|
|
515
534
|
function rowChoice(row: ModelProfileRow): string {
|
|
516
|
-
|
|
535
|
+
const scope = row.scope === 'project' ? 'local' : 'global';
|
|
536
|
+
return `${row.name} (${scope}) — model ${row.modelLabel}; effort ${row.effortLabel}`;
|
|
517
537
|
}
|
|
518
538
|
|
|
519
539
|
async function getAvailableModels(ctx: any): Promise<any[]> {
|
|
@@ -525,9 +545,9 @@ async function getAvailableModels(ctx: any): Promise<any[]> {
|
|
|
525
545
|
}
|
|
526
546
|
}
|
|
527
547
|
|
|
528
|
-
async function chooseSave(ctx: any, stagedProfiles: SubagentModelProfiles, agentDir?: string): Promise<string> {
|
|
548
|
+
async function chooseSave(ctx: any, stagedProfiles: SubagentModelProfiles, input: { agentDir?: string; cwd?: string; profileScopes?: Record<string, SubagentDefinitionScope> } = {}): Promise<string> {
|
|
529
549
|
const decision = await ctx.ui.select('Save subagent model profile changes?', ['Save', 'Cancel']);
|
|
530
|
-
const message = commitStagedModelProfiles({ stagedProfiles, save: decision === 'Save', agentDir });
|
|
550
|
+
const message = commitStagedModelProfiles({ stagedProfiles, save: decision === 'Save', agentDir: input.agentDir, cwd: input.cwd, profileScopes: input.profileScopes });
|
|
531
551
|
ctx.ui.notify?.(message, decision === 'Save' ? 'info' : 'warning');
|
|
532
552
|
return message;
|
|
533
553
|
}
|
|
@@ -543,22 +563,24 @@ export async function runSubagentModelsCommand(ctx: any = {}): Promise<string> {
|
|
|
543
563
|
const config = readSubagentsConfig(cwd);
|
|
544
564
|
const availableModels = await getAvailableModels(ctx);
|
|
545
565
|
const rows = buildModelProfileRows({ definitions, config, ctx, availableModels });
|
|
566
|
+
const profileScopes: Record<string, SubagentDefinitionScope> = Object.fromEntries(rows.map((row) => [row.name.trim().toLowerCase(), row.scope ?? 'global']));
|
|
546
567
|
|
|
547
568
|
if (hasCustomUi) {
|
|
548
569
|
const result = await ctx.ui.custom(
|
|
549
|
-
(tui: any,
|
|
570
|
+
(tui: any, theme: any, _keybindings: any, done: (result: SubagentModelProfilesModalResult) => void) => createSubagentModelProfilesModal({
|
|
550
571
|
rows,
|
|
551
572
|
availableModels,
|
|
552
573
|
tui,
|
|
574
|
+
theme,
|
|
553
575
|
done,
|
|
554
576
|
}),
|
|
555
|
-
{ overlay: true, overlayOptions: { anchor: 'center', width: '
|
|
577
|
+
{ overlay: true, overlayOptions: { anchor: 'center', width: '96%', maxHeight: '90%', minWidth: 96 } },
|
|
556
578
|
) as SubagentModelProfilesModalResult | undefined;
|
|
557
579
|
|
|
558
580
|
if (result?.action === 'save') {
|
|
559
581
|
const hasDirtyRows = Object.keys(result.dirtyProfiles).length > 0;
|
|
560
582
|
const message = hasDirtyRows
|
|
561
|
-
? commitStagedModelProfiles({ stagedProfiles: result.dirtyProfiles, save: true, agentDir })
|
|
583
|
+
? commitStagedModelProfiles({ stagedProfiles: result.dirtyProfiles, save: true, agentDir, cwd, profileScopes })
|
|
562
584
|
: buildNoChangesModelProfilesMessage(agentDir);
|
|
563
585
|
ctx.ui.notify?.(message, 'info');
|
|
564
586
|
return message;
|
|
@@ -605,5 +627,5 @@ export async function runSubagentModelsCommand(ctx: any = {}): Promise<string> {
|
|
|
605
627
|
else staged = stageModelProfileEdit(staged, { agentName: row.name, effort });
|
|
606
628
|
}
|
|
607
629
|
|
|
608
|
-
return chooseSave(ctx, staged, agentDir);
|
|
630
|
+
return chooseSave(ctx, staged, { agentDir, cwd, profileScopes });
|
|
609
631
|
}
|
package/src/profile-resolver.ts
CHANGED
|
@@ -25,8 +25,13 @@ function field<T>(source: ProfileValueSource, value: T | undefined, format: (val
|
|
|
25
25
|
return { value, source, label: profileSourceLabel(source, value, format) };
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function profileForDefinition(definition: SubagentDefinition, config: SubagentsConfig) {
|
|
29
|
+
if (definition.scope === 'project') return config.project_model_profiles?.[definition.name] ?? (config.project_model_profiles ? undefined : config.model_profiles[definition.name]);
|
|
30
|
+
return config.global_model_profiles?.[definition.name] ?? (config.global_model_profiles ? undefined : config.model_profiles[definition.name]);
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
function resolveModel(definition: SubagentDefinition, config: SubagentsConfig, ctx: any): ResolvedProfileField<ModelRef> {
|
|
29
|
-
const profile = config
|
|
34
|
+
const profile = profileForDefinition(definition, config);
|
|
30
35
|
if (profile?.model) return field('profile', profile.model, modelLabel);
|
|
31
36
|
if (definition.model) return field('definition', definition.model, modelLabel);
|
|
32
37
|
if (config.default_model) return field('default', config.default_model, modelLabel);
|
|
@@ -36,7 +41,7 @@ function resolveModel(definition: SubagentDefinition, config: SubagentsConfig, c
|
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
function resolveEffort(definition: SubagentDefinition, config: SubagentsConfig, ctx: any): ResolvedProfileField<ThinkingEffort> {
|
|
39
|
-
const profile = config
|
|
44
|
+
const profile = profileForDefinition(definition, config);
|
|
40
45
|
if (profile?.effort) return field('profile', profile.effort, String);
|
|
41
46
|
if (definition.effort) return field('definition', definition.effort, String);
|
|
42
47
|
if (config.default_effort) return field('default', config.default_effort, String);
|
package/src/tools.ts
CHANGED
|
@@ -357,7 +357,8 @@ export function registerSubagentTools(pi: any, manager: SubagentManager): void {
|
|
|
357
357
|
if (cancelledByDoubleEscape) throw new Error('Subagent run cancelled by double escape');
|
|
358
358
|
if (!('results' in result)) {
|
|
359
359
|
const details = compactResultDetails(result as any);
|
|
360
|
-
|
|
360
|
+
const response = ok(backgroundLaunchContent(result.task_ids, 'Sent'), details);
|
|
361
|
+
return isBackground ? response : { ...response, terminate: true };
|
|
361
362
|
}
|
|
362
363
|
const failedTasks = (result.results ?? []).filter((task) => task.status === 'failed' || task.status === 'cancelled');
|
|
363
364
|
const text = result.mode === 'background'
|
package/src/types.ts
CHANGED
|
@@ -12,6 +12,7 @@ export type SubagentModelProfile = {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export type SubagentModelProfiles = Record<string, SubagentModelProfile>;
|
|
15
|
+
export type SubagentDefinitionScope = 'global' | 'project';
|
|
15
16
|
|
|
16
17
|
export type ProfileValueSource = 'profile' | 'definition' | 'default' | 'orchestrator' | 'unresolved';
|
|
17
18
|
|
|
@@ -35,6 +36,7 @@ export type SubagentDefinition = {
|
|
|
35
36
|
model?: ModelRef;
|
|
36
37
|
effort?: ThinkingEffort;
|
|
37
38
|
tools: string[];
|
|
39
|
+
scope?: SubagentDefinitionScope;
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
export type SubagentSessionResources = 'full' | 'lean';
|
|
@@ -44,6 +46,8 @@ export type SubagentsConfig = {
|
|
|
44
46
|
default_model?: ModelRef;
|
|
45
47
|
default_effort?: ThinkingEffort;
|
|
46
48
|
model_profiles: SubagentModelProfiles;
|
|
49
|
+
global_model_profiles?: SubagentModelProfiles;
|
|
50
|
+
project_model_profiles?: SubagentModelProfiles;
|
|
47
51
|
timeout_ms: number;
|
|
48
52
|
stall_timeout_ms: number;
|
|
49
53
|
max_concurrency: number;
|
package/src/ui.ts
CHANGED
|
@@ -214,15 +214,37 @@ export class SubagentsHistoryPanel {
|
|
|
214
214
|
|
|
215
215
|
private taskStrip(width: number): string {
|
|
216
216
|
const tasks = this.tasks();
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
217
|
+
const dim = (s: string) => this.theme?.fg?.('dim', s) ?? s;
|
|
218
|
+
const selected = (s: string) => this.theme?.fg?.('warning', s) ?? s;
|
|
219
|
+
const chip = (index: number): { raw: string; styled: string } => {
|
|
220
|
+
const task = tasks[index]!;
|
|
221
|
+
const raw = `${index === this.selected ? '●' : '○'} ${task.agent}:${task.status}${task.effort ? ` effort:${task.effort}` : ''}`;
|
|
222
|
+
return { raw, styled: index === this.selected ? selected(raw) : dim(raw) };
|
|
223
|
+
};
|
|
224
|
+
const selectedChip = chip(this.selected);
|
|
225
|
+
let start = this.selected;
|
|
226
|
+
let end = this.selected + 1;
|
|
227
|
+
let raw = selectedChip.raw;
|
|
228
|
+
while (start > 0 || end < tasks.length) {
|
|
229
|
+
const preferLeft = this.selected - start <= end - this.selected - 1;
|
|
230
|
+
const nextIndex = preferLeft && start > 0 ? start - 1 : end < tasks.length ? end : start > 0 ? start - 1 : -1;
|
|
231
|
+
if (nextIndex < 0) break;
|
|
232
|
+
const next = chip(nextIndex).raw;
|
|
233
|
+
const candidate = nextIndex < start ? `${next} ${raw}` : `${raw} ${next}`;
|
|
234
|
+
const prefix = `executions ${Math.min(start, nextIndex) + 1}-${Math.max(end, nextIndex + 1)}/${tasks.length} `;
|
|
235
|
+
const leftIndicator = Math.min(start, nextIndex) > 0 ? '‹ ' : '';
|
|
236
|
+
const rightIndicator = Math.max(end, nextIndex + 1) < tasks.length ? ' ›' : '';
|
|
237
|
+
if (this.visibleWidth(`${prefix}${leftIndicator}${candidate}${rightIndicator}`) > width) break;
|
|
238
|
+
raw = candidate;
|
|
239
|
+
start = Math.min(start, nextIndex);
|
|
240
|
+
end = Math.max(end, nextIndex + 1);
|
|
224
241
|
}
|
|
225
|
-
|
|
242
|
+
const styledChips: string[] = [];
|
|
243
|
+
for (let i = start; i < end; i++) styledChips.push(chip(i).styled);
|
|
244
|
+
const prefix = dim(`executions ${start + 1}-${end}/${tasks.length}`);
|
|
245
|
+
const leftIndicator = start > 0 ? `${dim('‹')} ` : '';
|
|
246
|
+
const rightIndicator = end < tasks.length ? ` ${dim('›')}` : '';
|
|
247
|
+
return `${prefix} ${leftIndicator}${styledChips.join(' ')}${rightIndicator}`;
|
|
226
248
|
}
|
|
227
249
|
|
|
228
250
|
private tasks(): SubagentTask[] {
|