@gotgenes/pi-subagents 17.5.0 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/docs/architecture/architecture.md +24 -19
- package/docs/decisions/0004-reconsider-ui-direction.md +17 -0
- package/docs/plans/0441-remove-agent-definition-management-subtree.md +180 -0
- package/docs/plans/0442-dissolve-agents-conversation-viewer.md +195 -0
- package/docs/retro/0441-remove-agent-definition-management-subtree.md +45 -0
- package/docs/retro/0442-dissolve-agents-conversation-viewer.md +108 -0
- package/docs/retro/0463-file-snapshot-source-evicted-agents.md +54 -0
- package/package.json +1 -1
- package/src/index.ts +4 -31
- package/src/ui/session-navigation.ts +1 -1
- package/src/ui/session-navigator.ts +3 -3
- package/src/ui/subagents-settings.ts +2 -2
- package/src/ui/agent-config-editor.ts +0 -199
- package/src/ui/agent-creation-wizard.ts +0 -233
- package/src/ui/agent-file-ops.ts +0 -59
- package/src/ui/agent-file-writer.ts +0 -55
- package/src/ui/agent-menu.ts +0 -331
- package/src/ui/conversation-viewer.ts +0 -241
- package/src/ui/message-formatters.ts +0 -195
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 442
|
|
3
|
+
issue_title: "pi-subagents: dissolve /agents and remove the conversation-viewer subtree"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #442 — pi-subagents: dissolve /agents and remove the conversation-viewer subtree
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-06-23T18:06:31Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned Phase 19 Step 5: dissolve the `/agents` command and delete the conversation-viewer subtree (`agent-menu.ts`, `conversation-viewer.ts`, `message-formatters.ts`, plus their three tests), dewire `index.ts`.
|
|
13
|
+
Investigation surfaced a blocker the issue overlooked — a bidirectional type cycle between the hub and its leaves — and the operator chose a tidy-first resolution.
|
|
14
|
+
Plan committed at `packages/pi-subagents/docs/plans/0442-dissolve-agents-conversation-viewer.md`; this is a `/build-plan` (no red→green cycles), batched for release with the tail [#441].
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- Key finding: `agent-creation-wizard.ts` and `agent-config-editor.ts` (which survive until [#441]) both `import type { MenuUI } from "#src/ui/agent-menu"` and use it throughout, while the hub value-imports the wizard/editor classes — a bidirectional cycle.
|
|
19
|
+
Deleting either subtree first breaks `tsc --noEmit` (type-checks `src` + `test`).
|
|
20
|
+
The issue's "pure orphans" premise holds for runtime reachability but not at the type level.
|
|
21
|
+
- Flipping the [#442]/[#441] order does **not** fix it (cycle is bidirectional) — surfaced this directly when the operator asked about step ordering.
|
|
22
|
+
- Operator decision via `ask_user`: keep two commits, relocate `MenuUI` to a surviving `src/ui/menu-ui.ts` first (tidy-first).
|
|
23
|
+
Rejected alternatives: merge [#441] into [#442] (one deletion commit), or inline a throwaway `MenuUI` into the doomed leaves.
|
|
24
|
+
- Operator confirmed release timing: do not release until both [#442] and [#441] land — marker is `mid-batch — defer (batch "dissolve-agents")`, tail is [#441].
|
|
25
|
+
- `menu-ui.ts` is intentionally transient (one issue's lifespan); it keeps two live consumers (wizard/editor) immediately, so no fallow dead-code flag; [#441] deletes it with its consumers.
|
|
26
|
+
- Verified no collateral: `FsAgentFileOps` stays off the dead-code list because its own test still imports it; `subagents-settings.ts` defines its own `SubagentsSettingsUI` (no `MenuUI` coupling); `ui-stubs.ts`'s `makeMenuUI` is structurally typed; `join`/`buildParentSnapshot` become dead `index.ts` imports and are removed; deleting the consumers orphans no `display.ts` export.
|
|
27
|
+
- Change is breaking (the `/agents` command disappears) → deletion commit is `feat(pi-subagents)!:` with a `BREAKING CHANGE:` footer naming `/subagents:settings`, `/subagents:sessions`, and the background widget (verified the real registered command names, not the architecture's proposed `/subagents-settings`).
|
|
28
|
+
- Deferred the holistic architecture-doc refresh (Mermaid domain diagram, complexity/health tables, Phase-19-to-history migration) to the batch tail [#441] to avoid double-editing tables that [#441] also touches; [#442] keeps only the current-state file tree, the Step 5 `Outcome:` annotation, and the SKILL.md UI count accurate.
|
|
29
|
+
|
|
30
|
+
[#441]: https://github.com/gotgenes/pi-packages/issues/441
|
|
31
|
+
[#442]: https://github.com/gotgenes/pi-packages/issues/442
|
|
32
|
+
|
|
33
|
+
## Stage: Implementation — Build (2026-06-23T19:30:00Z)
|
|
34
|
+
|
|
35
|
+
### Session summary
|
|
36
|
+
|
|
37
|
+
Executed both plan commits (Step 1: extract `MenuUI`; Step 2: delete hub+viewer+formatters and dewire `index.ts`).
|
|
38
|
+
A mid-session diversion investigated the commitlint `#N`-in-body false positive (issue [#4099]), determined it is still present in v21.0.2 despite the issue being closed, and removed the `--strict` flag from `prek.toml` so the warning no longer blocks commits.
|
|
39
|
+
Pre-completion reviewer returned PASS.
|
|
40
|
+
|
|
41
|
+
### Observations
|
|
42
|
+
|
|
43
|
+
- **Unplanned fallow finding:** after deleting `agent-menu.ts`, `showAgentDetail` (`agent-config-editor.ts`) and `showCreateWizard` (`agent-creation-wizard.ts`) lost their only external callers and became unused class members.
|
|
44
|
+
The plan's risk analysis covered unused *files* (`FsAgentFileOps`) but not unused class *methods*.
|
|
45
|
+
Added `// fallow-ignore-next-line unused-class-member` suppressions with a comment pointing to [#441] as the removal commit.
|
|
46
|
+
Future plans for hub-deletion steps should explicitly check whether public methods on surviving leaf classes lose their only caller.
|
|
47
|
+
- **Unplanned commitlint diversion:** bodies with `#N` mid-sentence in multi-paragraph messages are still misidentified as footer tokens by commitlint v21.0.2 despite issue [#4099] being closed COMPLETED on 2026-06-02 with no linked PR.
|
|
48
|
+
Removed `--strict` from the `commit-msg` hook in `prek.toml` so the false-positive is a non-blocking warning; updated `AGENTS.md` accordingly.
|
|
49
|
+
This also required untangling a bad commit (the `git rm`'d pi-subagents files were swept into the commitlint fix commit) via `git reset HEAD~1` + selective re-staging.
|
|
50
|
+
- **Commit message refinement:** learned empirically that `#N` appearing in a wrapped body line (near end of line) triggers the false positive even in a single-paragraph body; a two-paragraph body with `#N` in the second paragraph was the original failure mode.
|
|
51
|
+
Workaround before the `--strict` removal: keep `#N` out of body prose; use `Refs #N` as a true footer with a blank-line separator.
|
|
52
|
+
- Pre-completion reviewer: PASS — all deterministic checks green; doc-staleness WARNs (Mermaid domain diagram, structural tables, Phase 19 history) are intentional mid-batch deferred work per the plan's Non-Goals, to be resolved at the batch tail [#441].
|
|
53
|
+
|
|
54
|
+
[#4099]: https://github.com/conventional-changelog/commitlint/issues/4099
|
|
55
|
+
|
|
56
|
+
## Stage: Final Retrospective (2026-06-23T20:05:13Z)
|
|
57
|
+
|
|
58
|
+
### Session summary
|
|
59
|
+
|
|
60
|
+
Shipped Phase 19 Step 5 across plan → build → ship: extracted `MenuUI` to break a bidirectional type cycle, deleted the `/agents` hub plus the conversation-viewer subtree, and dewired `index.ts`.
|
|
61
|
+
The release was correctly deferred at ship time per the `mid-batch — defer` marker (batch tail is [#441]).
|
|
62
|
+
The session's dominant friction was an unplanned commitlint diversion that produced a blunt fix, then a superseding surgical fix, a bad-commit untangle, and a new tracking issue ([#468]).
|
|
63
|
+
|
|
64
|
+
### Observations
|
|
65
|
+
|
|
66
|
+
#### What went well
|
|
67
|
+
|
|
68
|
+
1. The Planning `ask_user` gate caught the hub↔leaf bidirectional type cycle before any code was written and produced a clean tidy-first plan (relocate `MenuUI` first), so the two implementation commits were each green — no broken-commit sequence.
|
|
69
|
+
2. The commitlint claim was verified empirically by running `commitlint` against crafted test messages, not by trusting the closed-issue status — this confirmed [#4099] was closed `COMPLETED` without a shipped fix and that the false positive still fired in `v21.0.2`.
|
|
70
|
+
3. Incremental verification: `pnpm run check`/`lint`/`test`/`fallow dead-code` ran after each implementation step, so the unused-class-member finding surfaced at the right step rather than at the end.
|
|
71
|
+
|
|
72
|
+
#### What caused friction (agent side)
|
|
73
|
+
|
|
74
|
+
1. `premature-convergence` — the first commitlint fix (`9b60c943`, remove `--strict`) was blunt: dropping `--strict` silences every warning-level rule, including the load-bearing malformed-`!`-header rejection (Refs #457).
|
|
75
|
+
The surgical fix (`40189cc4`, disable only `footer-leading-blank`, keep `--strict`) only emerged from a separate branch reconsideration.
|
|
76
|
+
Impact: one superseded `fix(commitlint):` commit now permanent in pushed history; a #457 regression was avoided only because the blunt fix was reconsidered before the batch released.
|
|
77
|
+
2. `other` (git workflow) — `git rm` pre-stages its deletions, so a later `git add prek.toml AGENTS.md && git commit` for the commitlint fix swept the already-staged `/agents` deletions into the wrong commit.
|
|
78
|
+
Required `git reset HEAD~1` plus selective re-staging to untangle.
|
|
79
|
+
Impact: ~4 extra tool calls; caught immediately, no lasting damage.
|
|
80
|
+
3. `missing-context` / unused-tool — determining the upstream commitlint issue state took several rounds of `web_search` + `fetch_content` until the user said "Remember you have access to the `gh` CLI, too"; `gh issue view 4099 --repo conventional-changelog/commitlint` then returned the authoritative state (`CLOSED`/`COMPLETED`, no linked PR) in one call.
|
|
81
|
+
Impact: several wasted research rounds; user-caught.
|
|
82
|
+
4. `missing-context` (minor) — the plan's risk analysis covered unused *files* (`FsAgentFileOps`, kept live by its test import) but not unused class *methods*; deleting the hub orphaned `showAgentDetail` and `showCreateWizard`, tripping `fallow dead-code`.
|
|
83
|
+
Impact: low — caught by the gate, resolved with two `fallow-ignore-next-line unused-class-member` suppressions pointing at [#441].
|
|
84
|
+
|
|
85
|
+
#### What caused friction (user side)
|
|
86
|
+
|
|
87
|
+
1. The agent reached for `web_search`/`fetch_content` to check a GitHub issue's state instead of `gh`; the user had to point at an available tool.
|
|
88
|
+
Opportunity: reach for `gh issue view`/`gh pr view` first for GitHub issue/PR state, including upstream repos.
|
|
89
|
+
2. The user supplied the steer toward a surgical fix ("prefer surgical fixes over blunt ones").
|
|
90
|
+
Opportunity: a standing rule about disabling the single offending rule rather than the enforcement mode could have produced the surgical fix on the first pass.
|
|
91
|
+
|
|
92
|
+
### Diagnostic details
|
|
93
|
+
|
|
94
|
+
- **Model-performance correlation** — the `pre-completion-reviewer` subagent ran on `anthropic/claude-sonnet-4-6` (per its agent frontmatter), appropriate for judgment-heavy review; the implementation ran on `claude-opus-4-8`.
|
|
95
|
+
No quality mismatch.
|
|
96
|
+
The many `model_change` entries are transient UI cycling (no assistant turn under most), not real reassignments.
|
|
97
|
+
- **Escalation-delay tracking** — the upstream-issue-state investigation ran ~6 consecutive `web_search`/`fetch_content` calls before the `gh` reminder; one `gh issue view` would have resolved it immediately.
|
|
98
|
+
- **Unused-tool detection** — `gh` was available throughout but not used for upstream issue state until prompted.
|
|
99
|
+
- **Feedback-loop gap analysis** — no gap; verification ran incrementally after each step.
|
|
100
|
+
|
|
101
|
+
[#468]: https://github.com/gotgenes/pi-packages/issues/468
|
|
102
|
+
|
|
103
|
+
### Changes made
|
|
104
|
+
|
|
105
|
+
1. `AGENTS.md` (`## Commits`) — tightened the `footer-leading-blank` note from ~62 words to rule + `Refs #468`, dropping the parser-variant list and the two upstream issue numbers (now here and in [#468]).
|
|
106
|
+
2. `AGENTS.md` (`## Commits`) — added a single-rule-vs-mode principle: disable the single offending lint rule (`[0]`), not the `--strict` enforcement mode that gates the others.
|
|
107
|
+
3. `AGENTS.md` (`## Workflow`) — added a bullet to use `gh issue view N --repo owner/repo` for GitHub issue/PR state instead of web search.
|
|
108
|
+
4. `AGENTS.md` (`## Commits`, git mechanics) — added a note that staged `git rm` deletions ride along with the next `git commit` even when only unrelated paths are `git add`ed; commit with an explicit pathspec or check `git status` first.
|
|
@@ -47,3 +47,57 @@ Test count went from 1088 to 1099 (+11); full suite, `check`, root `lint`, and `
|
|
|
47
47
|
Both fixed and re-committed; `check` + tests confirmed green after.
|
|
48
48
|
- **Transient dead code** between steps 1–2 and 3 (predicted in the plan) cleared at the step-3 tip; final `fallow dead-code` is clean.
|
|
49
49
|
- **Pre-completion reviewer: PASS** — deterministic checks, code design, test artifacts, Mermaid render, and all three cross-step invariants (no inbound core call, read-only overlay, renderer parity) verified; no follow-ups deferred.
|
|
50
|
+
|
|
51
|
+
## Stage: Final Retrospective (2026-06-23T17:43:05Z)
|
|
52
|
+
|
|
53
|
+
### Session summary
|
|
54
|
+
|
|
55
|
+
Single continuous session carried #463 from planning through ship: a 4-step plan, TDD implementation (+11 tests, 1088→1099), pre-completion PASS, and release of `pi-subagents` v17.5.0.
|
|
56
|
+
The defining moment was planning: an `ask_user` design gate plus operator Socratic pushback diverged the candidate-set design from the issue's literal "directory scan" wording to manager-retained descriptors.
|
|
57
|
+
Execution was notably clean — no rework, no plan deviations, two auto-fixed lint nits.
|
|
58
|
+
|
|
59
|
+
### Observations
|
|
60
|
+
|
|
61
|
+
#### What went well
|
|
62
|
+
|
|
63
|
+
- **The `ask_user` gate caught a real design fork the issue body got wrong.**
|
|
64
|
+
The issue's "Proposed change" said "enumerate persisted child-session JSONL files" (a directory scan).
|
|
65
|
+
Exploration revealed the persisted session carries no subagent `type`/`description`, so a scan yields degraded labels.
|
|
66
|
+
The gate surfaced descriptors-vs-scan-vs-hybrid; the operator's "why are we evicting at all?"
|
|
67
|
+
and "tell me more about how the labels degrade" drove a better-grounded decision (descriptors).
|
|
68
|
+
This is the `/plan-issue` "treat Proposed change as a hypothesis" contract working as intended — novel because the divergence was the *enumeration mechanism*, not a surface ambiguity.
|
|
69
|
+
- **Exploratory probe before the first TDD test.**
|
|
70
|
+
A disposable script (`explore-session.mjs`, deleted after) confirmed `buildSessionContext` auto-detects the leaf with no `leafId` and the `type !== "session"` filter drops the header — validating the `fileSnapshotSource` shape before any test was written (the `testing` skill's "inspect the actual runtime shape first" rule).
|
|
71
|
+
- **The plan predicted transient dead code and sequenced to clear it.**
|
|
72
|
+
`fileSnapshotSource` and `listEvicted()` had no caller until step 3; the plan flagged this and ordered the integration step to close it, so the final `fallow dead-code` gate passed with no surprise suppressions.
|
|
73
|
+
- **Pre-completion reviewer ran on `claude-sonnet-4-6`** — model appropriate for the judgment-heavy invariant/design checklist; returned PASS.
|
|
74
|
+
|
|
75
|
+
#### What caused friction (agent side)
|
|
76
|
+
|
|
77
|
+
- `other` — the first `ask_user` led with the enumeration mechanism (descriptors/scan/hybrid) before establishing *why eviction creates the problem*.
|
|
78
|
+
The operator's first reply was "Why are we 'evicting' subagents in the first place?", i.e. asking for the framing I already held but had not front-loaded.
|
|
79
|
+
Impact: two extra `ask_user` round-trips before the decision — but they produced a better-understood outcome, so net-positive; no rework.
|
|
80
|
+
- `other` (lint nit) — four `entry?.kind` optional chains on a destructured array element tripped `@typescript-eslint/no-unnecessary-condition`, and a stray `!` tripped `no-unnecessary-type-assertion`; both surfaced via the pre-commit hook.
|
|
81
|
+
Impact: two re-commits during steps 2–3, auto-fixed, zero rework.
|
|
82
|
+
|
|
83
|
+
#### What caused friction (user side)
|
|
84
|
+
|
|
85
|
+
- None.
|
|
86
|
+
The operator's Socratic questions mid-`ask_user` were the session's highest-value input — they converted a plausible-but-degraded design (scan) into the right one (descriptors).
|
|
87
|
+
If anything, this is a model for how the gate should be used.
|
|
88
|
+
|
|
89
|
+
### Diagnostic details
|
|
90
|
+
|
|
91
|
+
- **Model-performance correlation** — the `pre-completion-reviewer` subagent ran on `anthropic/claude-sonnet-4-6` (judgment-heavy review): appropriate, no mismatch.
|
|
92
|
+
Main-thread `model_change` events included a `deepseek-v4-flash` variant; no quality degradation was observed in any artifact (plan, code, tests all clean), so no actionable correlation.
|
|
93
|
+
- **Escalation-delay tracking** — no `rabbit-hole` points; longest same-error streak was the two lint auto-fixes, each resolved in one re-commit.
|
|
94
|
+
- **Unused-tool detection** — exploration used `colgrep`, `grep`, and a disposable runtime probe; no missed-tool opportunities.
|
|
95
|
+
- **Feedback-loop gap analysis** — verification ran incrementally: `pnpm run check` after the shared-interface steps (2 and 3), per-file `vitest` on each red/green cycle, and the full suite + lint + `fallow` before the docs commit.
|
|
96
|
+
No end-only verification.
|
|
97
|
+
|
|
98
|
+
### Changes made
|
|
99
|
+
|
|
100
|
+
1. Appended this Final Retrospective stage entry to `packages/pi-subagents/docs/retro/0463-file-snapshot-source-evicted-agents.md`.
|
|
101
|
+
|
|
102
|
+
No `AGENTS.md` or prompt changes — the session's friction was net-positive (the `ask_user` round-trips) or auto-fixed with zero rework (the lint nits).
|
|
103
|
+
Two candidate changes were considered and rejected as noise (a `code-design` note on `entry?.kind` optional chaining; an `ask-user` "why-before-mechanism" guidance already covered by the skill).
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -8,11 +8,9 @@
|
|
|
8
8
|
* steer_subagent — LLM-callable: send a steering message to a running agent
|
|
9
9
|
*
|
|
10
10
|
* Commands:
|
|
11
|
-
* /agents — Interactive agent management menu
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
13
|
import { readFileSync } from "node:fs";
|
|
15
|
-
import { join } from "node:path";
|
|
16
14
|
import {
|
|
17
15
|
createAgentSession,
|
|
18
16
|
DefaultResourceLoader,
|
|
@@ -27,7 +25,6 @@ import { InterruptHandler, SessionLifecycleHandler, ToolStartHandler } from "#sr
|
|
|
27
25
|
import { createChildLifecyclePublisher } from "#src/lifecycle/child-lifecycle";
|
|
28
26
|
import { ConcurrencyLimiter } from "#src/lifecycle/concurrency-limiter";
|
|
29
27
|
import { createSubagentSession, type SubagentSessionDeps } from "#src/lifecycle/create-subagent-session";
|
|
30
|
-
import { buildParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
31
28
|
import { SubagentManager } from "#src/lifecycle/subagent-manager";
|
|
32
29
|
import { CompositeSubagentObserver } from "#src/observation/composite-subagent-observer";
|
|
33
30
|
import { type NotificationDetails, NotificationManager } from "#src/observation/notification";
|
|
@@ -45,8 +42,6 @@ import { SettingsManager } from "#src/settings";
|
|
|
45
42
|
import { AgentTool } from "#src/tools/agent-tool";
|
|
46
43
|
import { GetResultTool } from "#src/tools/get-result-tool";
|
|
47
44
|
import { SteerTool } from "#src/tools/steer-tool";
|
|
48
|
-
import { FsAgentFileOps } from "#src/ui/agent-file-ops";
|
|
49
|
-
import { AgentsMenuHandler } from "#src/ui/agent-menu";
|
|
50
45
|
import { AgentWidget } from "#src/ui/agent-widget";
|
|
51
46
|
import { SessionNavigatorHandler } from "#src/ui/session-navigator";
|
|
52
47
|
import { SubagentsSettingsHandler } from "#src/ui/subagents-settings";
|
|
@@ -162,44 +157,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
162
157
|
|
|
163
158
|
pi.registerTool(new SteerTool(manager, pi.events).toToolDefinition());
|
|
164
159
|
|
|
165
|
-
// ---- /
|
|
166
|
-
|
|
167
|
-
const agentsMenu = new AgentsMenuHandler(
|
|
168
|
-
manager,
|
|
169
|
-
registry,
|
|
170
|
-
settings,
|
|
171
|
-
new FsAgentFileOps(),
|
|
172
|
-
join(getAgentDir(), "agents"),
|
|
173
|
-
join(process.cwd(), ".pi", "agents"),
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
pi.registerCommand("agents", {
|
|
177
|
-
description: "Manage agents",
|
|
178
|
-
handler: async (_args, ctx) => {
|
|
179
|
-
await agentsMenu.handle({
|
|
180
|
-
ui: ctx.ui,
|
|
181
|
-
modelRegistry: ctx.modelRegistry,
|
|
182
|
-
parentSnapshot: buildParentSnapshot(ctx),
|
|
183
|
-
});
|
|
184
|
-
},
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// ---- /subagents-settings command ----
|
|
160
|
+
// ---- /subagents:settings command ----
|
|
188
161
|
|
|
189
162
|
const subagentsSettings = new SubagentsSettingsHandler(settings);
|
|
190
163
|
|
|
191
|
-
pi.registerCommand("subagents
|
|
164
|
+
pi.registerCommand("subagents:settings", {
|
|
192
165
|
description: "Configure subagent settings (concurrency, turn limits)",
|
|
193
166
|
handler: async (_args, ctx) => {
|
|
194
167
|
await subagentsSettings.handle({ ui: ctx.ui });
|
|
195
168
|
},
|
|
196
169
|
});
|
|
197
170
|
|
|
198
|
-
// ---- /
|
|
171
|
+
// ---- /subagents:sessions command ----
|
|
199
172
|
|
|
200
173
|
const sessionNavigator = new SessionNavigatorHandler();
|
|
201
174
|
|
|
202
|
-
pi.registerCommand("
|
|
175
|
+
pi.registerCommand("subagents:sessions", {
|
|
203
176
|
description: "View a subagent's session transcript (read-only)",
|
|
204
177
|
handler: async (_args, ctx) => {
|
|
205
178
|
await sessionNavigator.handle({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* session-navigation.ts — Pure selection and transcript-sourcing for native session navigation.
|
|
3
3
|
*
|
|
4
|
-
* Splits the unit-testable core of the `/
|
|
4
|
+
* Splits the unit-testable core of the `/subagents:sessions` command from its TUI
|
|
5
5
|
* wiring (`session-navigator.ts`): which subagents are navigable and how a picked
|
|
6
6
|
* agent's transcript is sourced (live, in this slice).
|
|
7
7
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* session-navigator.ts — The `/
|
|
2
|
+
* session-navigator.ts — The `/subagents:sessions` command: pick a subagent and
|
|
3
3
|
* read its transcript through Pi's own per-entry session components.
|
|
4
4
|
*
|
|
5
5
|
* SDK/TUI consumer half of native session navigation. The unit-testable core
|
|
@@ -65,7 +65,7 @@ export interface SessionNavigatorUI {
|
|
|
65
65
|
custom<R>(component: OverlayComponentFactory<R>, options?: unknown): Promise<R>;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
/** Parameters for one `/
|
|
68
|
+
/** Parameters for one `/subagents:sessions` invocation. */
|
|
69
69
|
export interface SessionNavigatorParams {
|
|
70
70
|
ui: SessionNavigatorUI;
|
|
71
71
|
agents: readonly NavigableSubagent[];
|
|
@@ -89,7 +89,7 @@ export interface TranscriptOverlayOptions {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
* Handler for the `/
|
|
92
|
+
* Handler for the `/subagents:sessions` slash command.
|
|
93
93
|
*
|
|
94
94
|
* Lists navigable subagents, lets the operator pick one, and opens its transcript
|
|
95
95
|
* read-only. Receives the agent snapshot (`manager.listAgents()`) rather than the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ---- Narrow interfaces ----
|
|
2
2
|
|
|
3
|
-
/** Narrow settings interface required by the subagents
|
|
3
|
+
/** Narrow settings interface required by the subagents:settings command. */
|
|
4
4
|
export interface SubagentsSettingsManager {
|
|
5
5
|
readonly maxConcurrent: number;
|
|
6
6
|
readonly defaultMaxTurns: number | undefined;
|
|
@@ -20,7 +20,7 @@ export interface SubagentsSettingsUI {
|
|
|
20
20
|
// ---- Class ----
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Handler for the `/subagents
|
|
23
|
+
* Handler for the `/subagents:settings` slash command.
|
|
24
24
|
*
|
|
25
25
|
* Call `handle({ ui })` from the Pi command registration to open the interactive
|
|
26
26
|
* settings list. Lifted from `AgentsMenuHandler.showSettings`.
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* agent-config-editor.ts — Agent detail view with edit/delete/eject/disable/enable transitions.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from agent-menu.ts to give each concern a single responsibility.
|
|
5
|
-
* Receives dependencies via injection — no direct `node:fs` imports.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
|
|
10
|
-
import type { AgentTypeRegistry } from "#src/config/agent-types";
|
|
11
|
-
import type { AgentConfig } from "#src/types";
|
|
12
|
-
import type { AgentFileOps } from "#src/ui/agent-file-ops";
|
|
13
|
-
import { writeAgentFile } from "#src/ui/agent-file-writer";
|
|
14
|
-
import type { MenuUI } from "#src/ui/agent-menu";
|
|
15
|
-
|
|
16
|
-
// ---- Pure helpers ----
|
|
17
|
-
|
|
18
|
-
/** Compute the menu option list for the agent detail view. */
|
|
19
|
-
export function buildMenuOptions(
|
|
20
|
-
cfg: { isDefault?: boolean; enabled?: boolean },
|
|
21
|
-
file: string | undefined,
|
|
22
|
-
): string[] {
|
|
23
|
-
const isDefault = cfg.isDefault === true;
|
|
24
|
-
const disabled = cfg.enabled === false;
|
|
25
|
-
|
|
26
|
-
if (disabled && file) {
|
|
27
|
-
return isDefault
|
|
28
|
-
? ["Enable", "Edit", "Reset to default", "Delete", "Back"]
|
|
29
|
-
: ["Enable", "Edit", "Delete", "Back"];
|
|
30
|
-
}
|
|
31
|
-
if (isDefault && !file) {
|
|
32
|
-
return ["Eject (export as .md)", "Disable", "Back"];
|
|
33
|
-
}
|
|
34
|
-
if (isDefault && file) {
|
|
35
|
-
return ["Edit", "Disable", "Reset to default", "Delete", "Back"];
|
|
36
|
-
}
|
|
37
|
-
return ["Edit", "Disable", "Delete", "Back"];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Build the `.md` file content (frontmatter + system prompt) for an ejected agent. */
|
|
41
|
-
export function buildEjectContent(cfg: AgentConfig): string {
|
|
42
|
-
const fmFields: string[] = [];
|
|
43
|
-
fmFields.push(`description: ${cfg.description}`);
|
|
44
|
-
if (cfg.displayName) fmFields.push(`display_name: ${cfg.displayName}`);
|
|
45
|
-
fmFields.push(`tools: ${cfg.builtinToolNames?.join(", ") ?? "all"}`);
|
|
46
|
-
if (cfg.model) fmFields.push(`model: ${cfg.model}`);
|
|
47
|
-
if (cfg.thinking) fmFields.push(`thinking: ${cfg.thinking}`);
|
|
48
|
-
if (cfg.maxTurns) fmFields.push(`max_turns: ${cfg.maxTurns}`);
|
|
49
|
-
fmFields.push(`prompt_mode: ${cfg.promptMode}`);
|
|
50
|
-
if (cfg.inheritContext) fmFields.push("inherit_context: true");
|
|
51
|
-
if (cfg.runInBackground) fmFields.push("run_in_background: true");
|
|
52
|
-
return `---\n${fmFields.join("\n")}\n---\n\n${cfg.systemPrompt}\n`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ---- Class ----
|
|
56
|
-
|
|
57
|
-
export class AgentConfigEditor {
|
|
58
|
-
constructor(
|
|
59
|
-
private readonly fileOps: AgentFileOps,
|
|
60
|
-
private readonly registry: AgentTypeRegistry,
|
|
61
|
-
private readonly personalAgentsDir: string,
|
|
62
|
-
private readonly projectAgentsDir: string,
|
|
63
|
-
) {}
|
|
64
|
-
|
|
65
|
-
private agentDirs(): string[] {
|
|
66
|
-
return [this.projectAgentsDir, this.personalAgentsDir];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async showAgentDetail(ui: MenuUI, name: string): Promise<void> {
|
|
70
|
-
if (this.registry.resolveType(name) == null) {
|
|
71
|
-
ui.notify(`Agent config not found for "${name}".`, "warning");
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const cfg = this.registry.resolveAgentConfig(name);
|
|
75
|
-
const file = this.fileOps.findAgentFile(name, this.agentDirs());
|
|
76
|
-
|
|
77
|
-
const choice = await ui.select(name, buildMenuOptions(cfg, file));
|
|
78
|
-
if (!choice || choice === "Back") return;
|
|
79
|
-
|
|
80
|
-
if (choice === "Edit" && file) await this.handleEdit(ui, name, file);
|
|
81
|
-
else if (choice === "Delete" && file) await this.handleDelete(ui, name, file);
|
|
82
|
-
else if (choice === "Reset to default" && file)
|
|
83
|
-
await this.handleReset(ui, name, file);
|
|
84
|
-
else if (choice.startsWith("Eject")) await this.ejectAgent(ui, name, cfg);
|
|
85
|
-
else if (choice === "Disable") await this.disableAgent(ui, name);
|
|
86
|
-
else if (choice === "Enable") await this.enableAgent(ui, name);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private async handleEdit(ui: MenuUI, name: string, file: string): Promise<void> {
|
|
90
|
-
const content = this.fileOps.read(file);
|
|
91
|
-
if (content === undefined) return;
|
|
92
|
-
const edited = await ui.editor(`Edit ${name}`, content);
|
|
93
|
-
if (edited !== undefined && edited !== content) {
|
|
94
|
-
this.fileOps.write(file, edited);
|
|
95
|
-
this.registry.reload();
|
|
96
|
-
ui.notify(`Updated ${file}`, "info");
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
private async handleDelete(ui: MenuUI, name: string, file: string): Promise<void> {
|
|
101
|
-
const confirmed = await ui.confirm(
|
|
102
|
-
"Delete agent",
|
|
103
|
-
`Delete ${name} (${file})?`,
|
|
104
|
-
);
|
|
105
|
-
if (confirmed) {
|
|
106
|
-
this.fileOps.remove(file);
|
|
107
|
-
this.registry.reload();
|
|
108
|
-
ui.notify(`Deleted ${file}`, "info");
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private async handleReset(ui: MenuUI, name: string, file: string): Promise<void> {
|
|
113
|
-
const confirmed = await ui.confirm(
|
|
114
|
-
"Reset to default",
|
|
115
|
-
`Delete override ${file} and restore embedded default?`,
|
|
116
|
-
);
|
|
117
|
-
if (confirmed) {
|
|
118
|
-
this.fileOps.remove(file);
|
|
119
|
-
this.registry.reload();
|
|
120
|
-
ui.notify(`Restored default ${name}`, "info");
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private async ejectAgent(ui: MenuUI, name: string, cfg: AgentConfig): Promise<void> {
|
|
125
|
-
const location = await ui.select("Choose location", [
|
|
126
|
-
"Project (.pi/agents/)",
|
|
127
|
-
`Personal (${this.personalAgentsDir})`,
|
|
128
|
-
]);
|
|
129
|
-
if (!location) return;
|
|
130
|
-
|
|
131
|
-
const targetDir = location.startsWith("Project")
|
|
132
|
-
? this.projectAgentsDir
|
|
133
|
-
: this.personalAgentsDir;
|
|
134
|
-
|
|
135
|
-
const targetPath = join(targetDir, `${name}.md`);
|
|
136
|
-
await writeAgentFile(
|
|
137
|
-
this.fileOps,
|
|
138
|
-
ui,
|
|
139
|
-
this.registry,
|
|
140
|
-
targetPath,
|
|
141
|
-
buildEjectContent(cfg),
|
|
142
|
-
`Ejected ${name} to`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
private async disableAgent(ui: MenuUI, name: string): Promise<void> {
|
|
147
|
-
const file = this.fileOps.findAgentFile(name, this.agentDirs());
|
|
148
|
-
if (file) {
|
|
149
|
-
const content = this.fileOps.read(file);
|
|
150
|
-
if (content?.includes("\nenabled: false\n")) {
|
|
151
|
-
ui.notify(`${name} is already disabled.`, "info");
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
if (content) {
|
|
155
|
-
const updated = content.replace(/^---\n/, "---\nenabled: false\n");
|
|
156
|
-
this.fileOps.write(file, updated);
|
|
157
|
-
this.registry.reload();
|
|
158
|
-
ui.notify(`Disabled ${name} (${file})`, "info");
|
|
159
|
-
}
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const location = await ui.select("Choose location", [
|
|
164
|
-
"Project (.pi/agents/)",
|
|
165
|
-
`Personal (${this.personalAgentsDir})`,
|
|
166
|
-
]);
|
|
167
|
-
if (!location) return;
|
|
168
|
-
|
|
169
|
-
const targetDir = location.startsWith("Project")
|
|
170
|
-
? this.projectAgentsDir
|
|
171
|
-
: this.personalAgentsDir;
|
|
172
|
-
|
|
173
|
-
const targetPath = join(targetDir, `${name}.md`);
|
|
174
|
-
this.fileOps.write(targetPath, "---\nenabled: false\n---\n");
|
|
175
|
-
this.registry.reload();
|
|
176
|
-
ui.notify(`Disabled ${name} (${targetPath})`, "info");
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
180
|
-
private async enableAgent(ui: MenuUI, name: string): Promise<void> {
|
|
181
|
-
const file = this.fileOps.findAgentFile(name, this.agentDirs());
|
|
182
|
-
if (!file) return;
|
|
183
|
-
|
|
184
|
-
const content = this.fileOps.read(file);
|
|
185
|
-
if (!content) return;
|
|
186
|
-
|
|
187
|
-
const updated = content.replace(/^(---\n)enabled: false\n/, "$1");
|
|
188
|
-
|
|
189
|
-
if (updated.trim() === "---\n---" || updated.trim() === "---\n---\n") {
|
|
190
|
-
this.fileOps.remove(file);
|
|
191
|
-
this.registry.reload();
|
|
192
|
-
ui.notify(`Enabled ${name} (removed ${file})`, "info");
|
|
193
|
-
} else {
|
|
194
|
-
this.fileOps.write(file, updated);
|
|
195
|
-
this.registry.reload();
|
|
196
|
-
ui.notify(`Enabled ${name} (${file})`, "info");
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|