pi-subagents-j0k3r 1.0.1 → 1.1.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/CHANGELOG.md +13 -0
- package/README.md +15 -17
- package/index.ts +3 -1
- package/package.json +1 -1
- package/skills/subagents-configuration/SKILL.md +16 -12
- package/src/config.ts +60 -21
- package/src/model-profiles-ui.ts +45 -23
- package/src/profile-resolver.ts +7 -2
- package/src/tools.ts +1 -1
- package/src/types.ts +4 -0
- package/src/ui.ts +30 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0 - 2026-06-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Added support for loading markdown-defined subagents from both `agents` and `subagents` directories globally and project-locally.
|
|
7
|
+
- Added startup warnings when duplicate names exist in `agents` and `subagents` at the same scope, while preserving `subagents` as the winning source.
|
|
8
|
+
- Enabled project Skill Registry configuration and ignored generated registry cache outputs.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Made `model_profiles` global-only so project-local `.pi/subagents.json` cannot override per-agent model/effort routing.
|
|
12
|
+
|
|
13
|
+
### Documentation
|
|
14
|
+
- Updated README and Subagents configuration skill guidance for `agents`/`subagents` source precedence and global-only model profiles.
|
|
15
|
+
|
|
3
16
|
## 1.0.1 - 2026-06-27
|
|
4
17
|
|
|
5
18
|
### Fixed
|
package/README.md
CHANGED
|
@@ -67,10 +67,14 @@ Subagents are markdown files with optional YAML-like frontmatter.
|
|
|
67
67
|
|
|
68
68
|
Load order:
|
|
69
69
|
|
|
70
|
-
1. Global user
|
|
71
|
-
2.
|
|
70
|
+
1. Global user agents from `$PI_CODING_AGENT_DIR/agents/*.md`.
|
|
71
|
+
2. Global user subagents from `$PI_CODING_AGENT_DIR/subagents/*.md`.
|
|
72
|
+
3. Project agents from `.pi/agents/*.md`.
|
|
73
|
+
4. Project subagents from `.pi/subagents/*.md`.
|
|
72
74
|
|
|
73
|
-
Project definitions override global definitions with the same normalized name.
|
|
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
|
+
|
|
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.
|
|
74
78
|
|
|
75
79
|
Default global agent directory:
|
|
76
80
|
|
|
@@ -127,7 +131,7 @@ Config files:
|
|
|
127
131
|
.pi/subagents.json # project
|
|
128
132
|
```
|
|
129
133
|
|
|
130
|
-
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.
|
|
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.
|
|
131
135
|
|
|
132
136
|
Example:
|
|
133
137
|
|
|
@@ -168,7 +172,7 @@ Example:
|
|
|
168
172
|
|---|---:|---|
|
|
169
173
|
| `default_model` | current orchestrator model | Fallback model for all subagents. Format: `provider/model-id`. |
|
|
170
174
|
| `default_effort` | current orchestrator effort | Fallback thinking effort. Also accepts `default_thinking_level` or `thinkingLevel`. |
|
|
171
|
-
| `model_profiles` | `{}` | Per-agent model/effort overrides. |
|
|
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. |
|
|
172
176
|
| `timeout_ms` | `600000` | Total timeout per subagent task. |
|
|
173
177
|
| `stall_timeout_ms` | `120000` | Inactivity timeout for a subagent session. |
|
|
174
178
|
| `max_concurrency` | `5` | Max concurrent subagent tasks per cwd/config pair. |
|
|
@@ -200,7 +204,7 @@ any tool starting with subagent_
|
|
|
200
204
|
|
|
201
205
|
Effective model resolution order:
|
|
202
206
|
|
|
203
|
-
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
|
|
204
208
|
2. subagent frontmatter `model`
|
|
205
209
|
3. `default_model`
|
|
206
210
|
4. current orchestrator model
|
|
@@ -208,7 +212,7 @@ Effective model resolution order:
|
|
|
208
212
|
|
|
209
213
|
Effective effort resolution order:
|
|
210
214
|
|
|
211
|
-
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
|
|
212
216
|
2. subagent frontmatter `effort` / `thinking_level` / `thinkingLevel`
|
|
213
217
|
3. `default_effort`
|
|
214
218
|
4. current orchestrator thinking level
|
|
@@ -282,20 +286,14 @@ Behavior:
|
|
|
282
286
|
| Entry point | Description |
|
|
283
287
|
|---|---|
|
|
284
288
|
| `/subagents` | Open the session-focused TUI subagent history panel. |
|
|
285
|
-
| `/subagent-models` | Configure
|
|
289
|
+
| `/subagent-models` | Configure subagent and SDD phase model profiles in the matching local or global config. |
|
|
286
290
|
| `ctrl+,` | Open the TUI subagent history panel in OpenCode mode by default. Configurable via `history_panel_shortcut` in `subagents.json`. |
|
|
287
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`. |
|
|
288
292
|
| `ctrl+h` | Send the running Claude-mode subagent task to the background by default. Configurable via `background_handoff_shortcut` in `subagents.json`. |
|
|
289
293
|
|
|
290
|
-
`/subagent-models` writes
|
|
291
|
-
|
|
292
|
-
```txt
|
|
293
|
-
~/.pi/agent/subagents.json
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
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.
|
|
297
295
|
|
|
298
|
-
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.
|
|
299
297
|
|
|
300
298
|
## Task history
|
|
301
299
|
|
|
@@ -392,7 +390,7 @@ This package bundles:
|
|
|
392
390
|
- `index.ts` and `src/**` — the Pi extension runtime.
|
|
393
391
|
- `skills/subagents-configuration/SKILL.md` — configuration guidance for agents that need to create or edit subagent definitions.
|
|
394
392
|
|
|
395
|
-
Subagent definitions are intentionally user/project configuration, not hard-coded package behavior. Add them globally in `$PI_CODING_AGENT_DIR/
|
|
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.
|
|
396
394
|
|
|
397
395
|
## Development
|
|
398
396
|
|
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SubagentManager } from './src/manager.js';
|
|
2
|
-
import { readSubagentsConfig } from './src/config.js';
|
|
2
|
+
import { readSubagentsConfig, subagentSourceWarnings } from './src/config.js';
|
|
3
3
|
import { registerSubagentTools, triggerClaudeBackgroundHandoff } from './src/tools.js';
|
|
4
4
|
import { runSubagentModelsCommand } from './src/model-profiles-ui.js';
|
|
5
5
|
import { SubagentsHistoryPanel } from './src/ui.js';
|
|
@@ -412,6 +412,8 @@ export default function subagentsExtension(pi: any): void {
|
|
|
412
412
|
|
|
413
413
|
pi.on?.('session_start', (_event: unknown, ctx: any) => {
|
|
414
414
|
clearClaudeBackgroundWidget();
|
|
415
|
+
const cwd = ctx?.cwd ?? process.cwd();
|
|
416
|
+
for (const warning of subagentSourceWarnings(cwd)) ctx?.ui?.notify?.(warning, 'warning');
|
|
415
417
|
if (typeof ctx?.ui?.setWidget !== 'function') return;
|
|
416
418
|
widgetCtx = ctx;
|
|
417
419
|
if (!installClaudeBackgroundWidget(ctx)) return;
|
package/package.json
CHANGED
|
@@ -19,10 +19,13 @@ Use this block as the machine-readable source for `.pi/skill-registry.json` gene
|
|
|
19
19
|
"domains": ["subagents-configuration", "subagent-config", "model-profile-config", "tool-allowlist-config", "subagent-history-config", "subagent-shortcut-config"],
|
|
20
20
|
"triggers": {
|
|
21
21
|
"paths": [
|
|
22
|
+
".pi/agents/**/*.md",
|
|
22
23
|
".pi/subagents/**/*.md",
|
|
23
24
|
".pi/subagents.json",
|
|
25
|
+
"agents/**/*.md",
|
|
24
26
|
"subagents/**/*.md",
|
|
25
27
|
"subagents.json",
|
|
28
|
+
"~/.pi/agent/agents/**/*.md",
|
|
26
29
|
"~/.pi/agent/subagents/**/*.md",
|
|
27
30
|
"~/.pi/agent/subagents.json"
|
|
28
31
|
],
|
|
@@ -88,11 +91,12 @@ Do not load this skill for ordinary subagent delegation/use (`subagent_run`, tas
|
|
|
88
91
|
- Prefer narrow tool allowlists per subagent. Do not grant write/bash tools unless the subagent purpose requires them.
|
|
89
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.
|
|
90
93
|
- For SDD phase agents, memory write tools may be allowed only for active SDD flow memory/artifacts according to `sdd-workflow`.
|
|
91
|
-
- Project
|
|
92
|
-
-
|
|
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" })`.
|
|
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.
|
|
93
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.
|
|
94
|
-
- `model_profiles` are
|
|
95
|
-
- Prefer configuring subagent `model` and `effort` in
|
|
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.
|
|
96
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.
|
|
97
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.
|
|
98
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.
|
|
@@ -151,7 +155,7 @@ tools:
|
|
|
151
155
|
Instructions...
|
|
152
156
|
```
|
|
153
157
|
|
|
154
|
-
Configure routing separately in `subagents.json` when needed. If no profile/default is configured, the subagent inherits the current orchestrator model and thinking effort.
|
|
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.
|
|
155
159
|
|
|
156
160
|
```json
|
|
157
161
|
{
|
|
@@ -166,9 +170,9 @@ Configure routing separately in `subagents.json` when needed. If no profile/defa
|
|
|
166
170
|
|
|
167
171
|
Model/effort resolution order:
|
|
168
172
|
|
|
169
|
-
1. `model_profiles[agentName]`
|
|
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.
|
|
170
174
|
2. Markdown frontmatter `model` / `effort` only for explicit per-file overrides.
|
|
171
|
-
3. `default_model` / `default_effort`
|
|
175
|
+
3. `default_model` / `default_effort` from effective `subagents.json` config.
|
|
172
176
|
4. Current orchestrator model / effort.
|
|
173
177
|
|
|
174
178
|
## Decision Gates
|
|
@@ -176,16 +180,16 @@ Model/effort resolution order:
|
|
|
176
180
|
- If the subagent will modify files, run bash, or write memory, ask whether a full SDD workflow or stricter review is required.
|
|
177
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.
|
|
178
182
|
- If a project wants many subagents or broad tools, recommend starting with read-only discovery agents and expanding deliberately.
|
|
179
|
-
- If model profiles
|
|
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.
|
|
180
184
|
|
|
181
185
|
## Execution Steps
|
|
182
186
|
|
|
183
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.
|
|
184
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.
|
|
185
|
-
3. Read existing subagent markdown/config before editing.
|
|
186
|
-
4. For new subagents, choose lowercase kebab-case names and clear trigger-focused descriptions.
|
|
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.
|
|
187
191
|
5. Set minimal tool allowlists; remove any `subagent_*` entries.
|
|
188
|
-
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.
|
|
189
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.
|
|
190
194
|
8. Validate JSON syntax for settings/subagents config and frontmatter/body structure for markdown agents.
|
|
191
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.
|
|
@@ -200,7 +204,7 @@ Model/effort resolution order:
|
|
|
200
204
|
Return:
|
|
201
205
|
|
|
202
206
|
- Skill applied: `subagents-configuration`.
|
|
203
|
-
- Scope/path configured or reviewed
|
|
207
|
+
- Scope/path configured or reviewed, including whether definitions came from `agents` or `subagents`.
|
|
204
208
|
- Package settings and subagents/config fields added, changed, or preserved.
|
|
205
209
|
- Tool allowlist, system-prompt isolation, Context7 scope, memory-tool scope, debug logging, model/effort decisions, and inheritance behavior.
|
|
206
210
|
- Runtime behavior explained when relevant: task vs background, automatic notifications, `/subagents`, `ctrl+o`, and `subagent_result`.
|
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;
|
|
@@ -163,16 +163,6 @@ function parseModelProfiles(value: unknown): SubagentModelProfiles {
|
|
|
163
163
|
return profiles;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
function mergeModelProfiles(globalRaw: unknown, projectRaw: unknown): SubagentModelProfiles {
|
|
167
|
-
const globalProfiles = parseModelProfiles(globalRaw);
|
|
168
|
-
const projectProfiles = parseModelProfiles(projectRaw);
|
|
169
|
-
const merged: SubagentModelProfiles = { ...globalProfiles };
|
|
170
|
-
for (const [name, projectProfile] of Object.entries(projectProfiles)) {
|
|
171
|
-
merged[name] = { ...(merged[name] ?? {}), ...projectProfile };
|
|
172
|
-
}
|
|
173
|
-
return merged;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
166
|
function serializeModelRef(model: ModelRef): string {
|
|
177
167
|
return `${model.provider}/${model.id}`;
|
|
178
168
|
}
|
|
@@ -188,10 +178,14 @@ export function readSubagentsConfig(cwd: string): SubagentsConfig {
|
|
|
188
178
|
const globalRaw = readJson(subagentsConfigPath());
|
|
189
179
|
const projectRaw = readJson(path.join(cwd, '.pi', 'subagents.json'));
|
|
190
180
|
const raw = { ...globalRaw, ...projectRaw };
|
|
181
|
+
const globalModelProfiles = parseModelProfiles(globalRaw.model_profiles);
|
|
182
|
+
const projectModelProfiles = parseModelProfiles(projectRaw.model_profiles);
|
|
191
183
|
return {
|
|
192
184
|
default_model: parseModel(raw.default_model),
|
|
193
185
|
default_effort: parseEffort(raw.default_effort ?? raw.default_thinking_level ?? raw.thinkingLevel),
|
|
194
|
-
model_profiles:
|
|
186
|
+
model_profiles: { ...globalModelProfiles, ...projectModelProfiles },
|
|
187
|
+
global_model_profiles: globalModelProfiles,
|
|
188
|
+
project_model_profiles: projectModelProfiles,
|
|
195
189
|
timeout_ms: positiveInteger(raw.timeout_ms, DEFAULT_TIMEOUT_MS),
|
|
196
190
|
stall_timeout_ms: positiveInteger(raw.stall_timeout_ms, DEFAULT_STALL_TIMEOUT_MS),
|
|
197
191
|
max_concurrency: positiveInteger(raw.max_concurrency, DEFAULT_MAX_CONCURRENCY),
|
|
@@ -205,8 +199,16 @@ export function readSubagentsConfig(cwd: string): SubagentsConfig {
|
|
|
205
199
|
};
|
|
206
200
|
}
|
|
207
201
|
|
|
208
|
-
|
|
209
|
-
|
|
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);
|
|
210
212
|
const root = readJson(file);
|
|
211
213
|
const writableRoot: Record<string, unknown> = isPlainObject(root) ? { ...root } : {};
|
|
212
214
|
const modelProfiles = isPlainObject(writableRoot.model_profiles) ? { ...writableRoot.model_profiles } : {};
|
|
@@ -219,8 +221,12 @@ export function saveGlobalSubagentModelProfile(input: { agentName: string; profi
|
|
|
219
221
|
fs.writeFileSync(file, `${JSON.stringify(writableRoot, null, 2)}\n`, 'utf8');
|
|
220
222
|
}
|
|
221
223
|
|
|
222
|
-
export function
|
|
223
|
-
|
|
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);
|
|
224
230
|
const root = readJson(file);
|
|
225
231
|
const writableRoot: Record<string, unknown> = isPlainObject(root) ? { ...root } : {};
|
|
226
232
|
const modelProfiles = isPlainObject(writableRoot.model_profiles) ? { ...writableRoot.model_profiles } : {};
|
|
@@ -235,7 +241,11 @@ export function resetGlobalSubagentModelProfileField(input: { agentName: string;
|
|
|
235
241
|
fs.writeFileSync(file, `${JSON.stringify(writableRoot, null, 2)}\n`, 'utf8');
|
|
236
242
|
}
|
|
237
243
|
|
|
238
|
-
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[] {
|
|
239
249
|
if (!fs.existsSync(dir)) return [];
|
|
240
250
|
return fs.readdirSync(dir)
|
|
241
251
|
.filter((f) => f.endsWith('.md'))
|
|
@@ -246,14 +256,43 @@ function loadSubagentsFromDir(dir: string): SubagentDefinition[] {
|
|
|
246
256
|
const name = String(data.name || path.basename(file, '.md')).trim().toLowerCase();
|
|
247
257
|
const description = String(data.description || `${name} subagent`).trim();
|
|
248
258
|
const tools = sanitizeTools(Array.isArray(data.tools) ? data.tools.map(String) : DEFAULT_TOOLS);
|
|
249
|
-
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 };
|
|
250
260
|
});
|
|
251
261
|
}
|
|
252
262
|
|
|
263
|
+
function agentsDirSources(cwd: string): Array<{ scope: 'global' | 'project'; kind: 'agents' | 'subagents'; dir: string }> {
|
|
264
|
+
const globalAgentDir = agentDir();
|
|
265
|
+
return [
|
|
266
|
+
{ scope: 'global', kind: 'agents', dir: path.join(globalAgentDir, 'agents') },
|
|
267
|
+
{ scope: 'global', kind: 'subagents', dir: path.join(globalAgentDir, 'subagents') },
|
|
268
|
+
{ scope: 'project', kind: 'agents', dir: path.join(cwd, '.pi', 'agents') },
|
|
269
|
+
{ scope: 'project', kind: 'subagents', dir: path.join(cwd, '.pi', 'subagents') },
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function subagentSourceWarnings(cwd: string): string[] {
|
|
274
|
+
const warnings: string[] = [];
|
|
275
|
+
for (const scope of ['global', 'project'] as const) {
|
|
276
|
+
const sources = agentsDirSources(cwd).filter((source) => source.scope === scope);
|
|
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);
|
|
281
|
+
const subagentNames = new Map(subagents.map((definition) => [definition.name, definition]));
|
|
282
|
+
for (const agent of agents) {
|
|
283
|
+
const subagent = subagentNames.get(agent.name);
|
|
284
|
+
if (!subagent) continue;
|
|
285
|
+
warnings.push(`Duplicate subagent name "${agent.name}" found in ${scope} agents and subagents directories; using subagents definition (${subagent.filePath}).`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return warnings;
|
|
289
|
+
}
|
|
290
|
+
|
|
253
291
|
export function loadSubagents(cwd: string): SubagentDefinition[] {
|
|
254
292
|
const byName = new Map<string, SubagentDefinition>();
|
|
255
|
-
for (const
|
|
256
|
-
|
|
293
|
+
for (const source of agentsDirSources(cwd)) {
|
|
294
|
+
for (const agent of loadSubagentsFromDir(source.dir, source.scope)) byName.set(agent.name, agent);
|
|
295
|
+
}
|
|
257
296
|
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
258
297
|
}
|
|
259
298
|
|
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
|
@@ -299,7 +299,7 @@ export function registerSubagentTools(pi: any, manager: SubagentManager): void {
|
|
|
299
299
|
name: 'subagent_list_agents',
|
|
300
300
|
label: 'Subagent List Agents',
|
|
301
301
|
description: 'List available markdown-defined subagents for delegation.',
|
|
302
|
-
promptSnippet: 'List available subagents loaded from
|
|
302
|
+
promptSnippet: 'List available subagents loaded from global/project agents and subagents markdown directories.',
|
|
303
303
|
parameters: Type.Object({}),
|
|
304
304
|
async execute(_id: string, _params: any, _signal: any, _onUpdate: any, ctx: any) {
|
|
305
305
|
try { const agents = manager.listAgents(ctx?.cwd ?? process.cwd()); return ok(`Found ${agents.length} subagent(s).`, { agents }); } catch (e) { return fail(e); }
|
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[] {
|