@gotgenes/pi-subagents 7.0.0 → 7.2.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 +33 -0
- package/docs/architecture/architecture.md +190 -91
- package/docs/architecture/history/phase-10-structural-decomposition.md +141 -0
- package/docs/plans/0192-define-session-context-interface.md +107 -0
- package/docs/plans/0193-runtime-owns-context-queries.md +245 -0
- package/docs/retro/0185-remove-persistent-agent-memory.md +34 -0
- package/docs/retro/0192-define-session-context-interface.md +59 -0
- package/docs/retro/0193-runtime-owns-context-queries.md +43 -0
- package/package.json +1 -1
- package/src/handlers/lifecycle.ts +4 -4
- package/src/index.ts +6 -22
- package/src/lifecycle/parent-snapshot.ts +4 -3
- package/src/runtime.ts +31 -3
- package/src/service/service-adapter.ts +14 -12
- package/src/session/context.ts +20 -6
- package/src/types.ts +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [7.2.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.1.0...pi-subagents-v7.2.0) (2026-05-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* SubagentRuntime stores typed SessionContext and owns context queries ([#193](https://github.com/gotgenes/pi-packages/issues/193)) ([4ca5319](https://github.com/gotgenes/pi-packages/commit/4ca531934e36c37c7cbc8fef8314a483e7dec479))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
* mark Phase 11 Layer 1 complete, update metrics ([#193](https://github.com/gotgenes/pi-packages/issues/193)) ([32233ed](https://github.com/gotgenes/pi-packages/commit/32233ed89f27dca854ce858978c3acc029b1e801))
|
|
19
|
+
* plan SubagentRuntime owns context queries ([#193](https://github.com/gotgenes/pi-packages/issues/193)) ([6ea475a](https://github.com/gotgenes/pi-packages/commit/6ea475af94f8456c1d665adcd007dd2833ab7a4b))
|
|
20
|
+
* **retro:** add planning stage notes for issue [#193](https://github.com/gotgenes/pi-packages/issues/193) ([7da6d5a](https://github.com/gotgenes/pi-packages/commit/7da6d5abac82bdea0cbdbc8677dda04d38a7887d))
|
|
21
|
+
* **retro:** add retro notes for issue [#192](https://github.com/gotgenes/pi-packages/issues/192) ([1223de4](https://github.com/gotgenes/pi-packages/commit/1223de4a68d4514eb504c5c95d64fb35500d286b))
|
|
22
|
+
* **retro:** add TDD stage notes for issue [#193](https://github.com/gotgenes/pi-packages/issues/193) ([3950b81](https://github.com/gotgenes/pi-packages/commit/3950b81228a3db57eb4c24236fa7d75c638a335a))
|
|
23
|
+
|
|
24
|
+
## [7.1.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.0.0...pi-subagents-v7.1.0) (2026-05-24)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
* **pi-subagents:** define SessionContext narrow interface ([#192](https://github.com/gotgenes/pi-packages/issues/192)) ([a043d4d](https://github.com/gotgenes/pi-packages/commit/a043d4d970a8aacc084a125d9a07b860a7fb6e9b))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Documentation
|
|
33
|
+
|
|
34
|
+
* **pi-subagents:** archive Phase 10, propose Phase 11 roadmap ([d474eef](https://github.com/gotgenes/pi-packages/commit/d474eef98c1a39757d933da291725ff126f1b8ac))
|
|
35
|
+
* **pi-subagents:** revise Phase 11 roadmap — layered class conversion ([35d1083](https://github.com/gotgenes/pi-packages/commit/35d1083445aece783e344c37fc949395e190f9e5))
|
|
36
|
+
* plan SessionContext narrow interface ([#192](https://github.com/gotgenes/pi-packages/issues/192)) ([95cb16e](https://github.com/gotgenes/pi-packages/commit/95cb16e46aa630ecc34f423aaaa4ff02845ed5b5))
|
|
37
|
+
* **retro:** add planning stage notes for issue [#192](https://github.com/gotgenes/pi-packages/issues/192) ([31fd729](https://github.com/gotgenes/pi-packages/commit/31fd7290985b0ef1d240cd5afd5e0e0e7eec9131))
|
|
38
|
+
* **retro:** add retro notes for issue [#185](https://github.com/gotgenes/pi-packages/issues/185) ([66e49cf](https://github.com/gotgenes/pi-packages/commit/66e49cfb4129b9bba3b78e0850402bc61d99dda8))
|
|
39
|
+
* **retro:** add TDD stage notes for issue [#192](https://github.com/gotgenes/pi-packages/issues/192) ([6cf3f95](https://github.com/gotgenes/pi-packages/commit/6cf3f95f6c39f3f81c1d335348d5abd74d948ff3))
|
|
40
|
+
|
|
8
41
|
## [7.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.19.1...pi-subagents-v7.0.0) (2026-05-24)
|
|
9
42
|
|
|
10
43
|
|
|
@@ -314,16 +314,16 @@ The widget reads agent state by polling a shared `Map<string, AgentActivityTrack
|
|
|
314
314
|
|
|
315
315
|
```mermaid
|
|
316
316
|
flowchart TD
|
|
317
|
-
subgraph core["@gotgenes/pi-subagents
|
|
317
|
+
subgraph core["@gotgenes/pi-subagents"]
|
|
318
318
|
direction TB
|
|
319
|
-
exports["SubagentsService
|
|
320
|
-
engine["Agent
|
|
321
|
-
ui_int["Internal UI: widget, viewer
|
|
319
|
+
exports["SubagentsService API<br/>publish / getSubagentsService<br/>SubagentRecord, SubagentStatus"]
|
|
320
|
+
engine["Tools: Agent, get_subagent_result,<br/>steer_subagent<br/>AgentManager, agent-runner"]
|
|
321
|
+
ui_int["Internal UI: widget, viewer,<br/>/agents menu"]
|
|
322
322
|
end
|
|
323
323
|
|
|
324
|
-
core -- "Symbol.for
|
|
325
|
-
core -- "Symbol.for
|
|
326
|
-
core -- "Symbol.for
|
|
324
|
+
core -- "Symbol.for on globalThis" --> sched["scheduling extension<br/>(hypothetical)"]
|
|
325
|
+
core -- "Symbol.for on globalThis" --> subui["pi-subagents-ui<br/>(deferred)"]
|
|
326
|
+
core -- "Symbol.for on globalThis" --> future["any future extension"]
|
|
327
327
|
```
|
|
328
328
|
|
|
329
329
|
Consumers call `getSubagentsService()?.spawn(...)` at runtime.
|
|
@@ -599,121 +599,220 @@ export type RunnerIO = EnvironmentIO & SessionFactoryIO;
|
|
|
599
599
|
`RunnerIO` is kept as a type alias for the intersection.
|
|
600
600
|
All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
|
|
601
601
|
|
|
602
|
-
## Improvement roadmap (Phase
|
|
602
|
+
## Improvement roadmap (Phase 11)
|
|
603
603
|
|
|
604
|
-
Phase
|
|
604
|
+
Phase 11 converts closure factories to classes, eliminating the adapter closure density in `index.ts` (the package's #1 churn hotspot: 128 commits, accelerating, fan-out 25).
|
|
605
|
+
The approach is layered: each step makes the next step trivial.
|
|
605
606
|
|
|
606
|
-
|
|
607
|
+
> "Make the change that makes the change easy." —Kent Beck
|
|
607
608
|
|
|
608
|
-
|
|
609
|
-
All `src/` internal imports now use `#src/` path aliases (same style as `test/` files), eliminating relative depth arithmetic for future moves.
|
|
609
|
+
### Findings
|
|
610
610
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
611
|
+
| Metric | Value |
|
|
612
|
+
| ------------------------- | --------------------------------------- |
|
|
613
|
+
| Health score | 75/100 (B) |
|
|
614
|
+
| #1 hotspot | `index.ts` (128 commits, accelerating) |
|
|
615
|
+
| Dead exports | 1 (`getToolCallName` re-export) |
|
|
616
|
+
| Production duplication | 0 |
|
|
617
|
+
| Test duplication | 1,396 lines (69 clone groups, 22 files) |
|
|
618
|
+
| `as any` casts in index | 1 (down from 5; Layer 1 resolved 4) |
|
|
619
|
+
| Adapter closures in index | 41 (down from 44; Layer 1 resolved 3) |
|
|
620
|
+
| Index fan-out | 25 imports |
|
|
614
621
|
|
|
615
|
-
###
|
|
622
|
+
### Root cause
|
|
616
623
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
Enables Step 3 (narrowing AgentSpawnConfig, [#166][166]).
|
|
624
|
+
The 44 adapter closures in `index.ts` exist because the tool factories accept narrow interfaces that don't structurally match the real objects.
|
|
625
|
+
The real objects can't satisfy the interfaces because:
|
|
620
626
|
|
|
621
|
-
|
|
627
|
+
1. ~~`SubagentRuntime.currentCtx` is typed `{ pi: unknown; ctx: unknown }` — so every consumer must `as any` cast to read fields.~~
|
|
628
|
+
Resolved by Layer 0 (#192) + Layer 1 (#193).
|
|
629
|
+
2. ~~Context queries (`buildSnapshot`, `getModelInfo`, `getSessionInfo`) live as closures in index.ts instead of methods on the state holder.~~
|
|
630
|
+
Resolved by Layer 1 (#193).
|
|
631
|
+
3. `AgentToolManager` mixes fields from `AgentManager` and `SettingsManager` (source mismatch).
|
|
632
|
+
4. `AgentToolWidget` uses different method names than `SubagentRuntime` (name mismatch).
|
|
622
633
|
|
|
623
|
-
|
|
624
|
-
`AgentSpawnConfig`, `BackgroundParams`, `ForegroundParams`, and `RunOptions` all carry the nested group.
|
|
634
|
+
Fix these structural misalignments and the class conversions become mechanical.
|
|
625
635
|
|
|
626
|
-
###
|
|
636
|
+
### Layer 0: Define `SessionContext` narrow interface ([#192][192])
|
|
627
637
|
|
|
628
|
-
`
|
|
629
|
-
`
|
|
630
|
-
|
|
638
|
+
`SubagentRuntime` currently types its context as `unknown` to avoid SDK coupling.
|
|
639
|
+
But `ExtensionContext` is exported by the SDK — the `unknown` is a historical choice, not a constraint.
|
|
640
|
+
Define a narrow `SessionContext` interface capturing the 5 fields runtime actually needs:
|
|
631
641
|
|
|
632
|
-
|
|
642
|
+
```typescript
|
|
643
|
+
export interface SessionContext {
|
|
644
|
+
readonly cwd: string;
|
|
645
|
+
readonly model: unknown;
|
|
646
|
+
readonly modelRegistry: ModelRegistry | undefined;
|
|
647
|
+
getSystemPrompt(): string;
|
|
648
|
+
readonly sessionManager: {
|
|
649
|
+
getSessionFile(): string | undefined;
|
|
650
|
+
getSessionId(): string;
|
|
651
|
+
getBranch(): unknown[];
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
```
|
|
633
655
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
`
|
|
656
|
+
- Target: `src/types.ts`
|
|
657
|
+
- Smell: Category C (platform type threading)
|
|
658
|
+
- Outcome: typed foundation for Layers 1–4; no `as any` needed by consumers of `SubagentRuntime`
|
|
659
|
+
|
|
660
|
+
### Layer 1: `SubagentRuntime` stores typed context, owns its queries ([#193][193]) ✓ done
|
|
661
|
+
|
|
662
|
+
Change `currentCtx` from `{ pi: unknown; ctx: unknown }` to `SessionContext | undefined`.
|
|
663
|
+
The single `as SessionContext` cast moves into `handleSessionStart` — the boundary where the SDK hands us the value.
|
|
664
|
+
Add typed methods: `buildSnapshot(inheritContext)`, `getModelInfo()`, `getSessionInfo()`.
|
|
665
|
+
|
|
666
|
+
- Target: `src/runtime.ts`, `src/handlers/lifecycle.ts`, `src/service/service-adapter.ts`, `src/index.ts`
|
|
667
|
+
- Smell: Category C (closure queries on mutable field → methods on state owner)
|
|
668
|
+
- Outcome: 3 closure queries in index.ts → 0; `SubagentRuntime` is self-sufficient for tool deps
|
|
669
|
+
- Enables: Layer 3 (tools accept `SubagentRuntime` directly)
|
|
670
|
+
|
|
671
|
+
### Layer 2: Align interfaces so real objects satisfy tool deps structurally ([#194][194])
|
|
637
672
|
|
|
638
|
-
|
|
673
|
+
Three alignment changes:
|
|
639
674
|
|
|
640
|
-
|
|
641
|
-
|
|
675
|
+
1. **Move `getMaxConcurrent` off `AgentToolManager`** — it reads from `SettingsManager`, not `AgentManager`.
|
|
676
|
+
The tool already receives `settings`; read it from there.
|
|
677
|
+
2. **Rename widget methods** — align `SubagentRuntime` method names with `AgentToolWidget` (either rename `updateWidget()` → `update()` on runtime, or rename the interface to match).
|
|
678
|
+
3. **Remove dead re-export** — `getToolCallName` in `ui/message-formatters.ts` (fallow finding).
|
|
642
679
|
|
|
643
|
-
|
|
680
|
+
After this step, `AgentManager` structurally satisfies `AgentToolManager` and `SubagentRuntime` structurally satisfies `AgentToolWidget`.
|
|
644
681
|
|
|
645
|
-
|
|
646
|
-
|
|
682
|
+
- Target: `src/tools/agent-tool.ts` (interface), `src/runtime.ts` (method names), `src/ui/message-formatters.ts`
|
|
683
|
+
- Smell: Category C (source mismatch, name mismatch) + Category A (dead export)
|
|
684
|
+
- Outcome: structural typing connects real objects to tool interfaces without adapters
|
|
685
|
+
- Enables: Layer 3 (class constructors accept real objects directly)
|
|
647
686
|
|
|
648
|
-
###
|
|
687
|
+
### Layer 3: Convert closure factories to classes ([#195][195], [#196][196])
|
|
649
688
|
|
|
650
|
-
|
|
651
|
-
`renderResult` reduced from ~80 lines (cognitive complexity 43) to a 10-line guard + `renderAgentResult` dispatcher.
|
|
652
|
-
The inline `stats()` closure became the exported `renderStats` helper, shared by all status renderers.
|
|
689
|
+
With Layers 0–2 complete, each factory is a mechanical conversion:
|
|
653
690
|
|
|
654
|
-
|
|
691
|
+
| Factory | Class | Constructor params |
|
|
692
|
+
| -------------------------------- | ------------------------------ | --------------------------------------------------- |
|
|
693
|
+
| `createAgentTool({...})` | `AgentTool` | `manager`, `runtime`, `settings`, `registry` |
|
|
694
|
+
| `createGetResultTool(...)` | `GetResultTool` | `manager`, `notifications`, `registry` |
|
|
695
|
+
| `createSteerTool(...)` | `SteerTool` | `manager`, `events` |
|
|
696
|
+
| `createAgentRunner(runnerIO)` | `AgentRunner` (concrete class) | `io: RunnerIO` |
|
|
697
|
+
| `createAgentsMenuHandler({...})` | `AgentsMenuHandler` | `manager`, `registry`, `settings`, `fileOps`, paths |
|
|
655
698
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
699
|
+
Each class satisfies the existing interface via structural typing.
|
|
700
|
+
The `defineTool()` wrapper moves into a `toToolDefinition()` method on each tool class.
|
|
701
|
+
|
|
702
|
+
- Target: `src/tools/*.ts`, `src/lifecycle/agent-runner.ts`, `src/ui/agent-menu.ts`
|
|
703
|
+
- Smell: Category C (closure factories masquerading as classes)
|
|
704
|
+
- Outcome: deps are constructor params (inspectable, testable); no captured closures
|
|
705
|
+
- Enables: Layer 4 (index.ts simplification)
|
|
706
|
+
|
|
707
|
+
### Layer 4: Simplify index.ts (included in [#196][196])
|
|
708
|
+
|
|
709
|
+
With real objects satisfying tool interfaces and queries living on `SubagentRuntime`, the composition root becomes pure construction:
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
const runtime = new SubagentRuntime();
|
|
713
|
+
const settings = new SettingsManager(...);
|
|
714
|
+
const manager = new AgentManager(...);
|
|
715
|
+
const agentTool = new AgentTool(manager, runtime, settings, registry);
|
|
716
|
+
pi.registerTool(agentTool.toToolDefinition());
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
No adapter closures.
|
|
720
|
+
No `as any`.
|
|
721
|
+
Fan-out drops from 25 to ~15 (internal factories eliminated).
|
|
722
|
+
|
|
723
|
+
- Target: `src/index.ts`
|
|
724
|
+
- Smell: Category B (god file) + Category C (adapter closure density)
|
|
725
|
+
- Outcome: index.ts shrinks from 280 to ~150 lines; churn hotspot stabilizes
|
|
659
726
|
|
|
660
727
|
### Step dependencies
|
|
661
728
|
|
|
662
729
|
```mermaid
|
|
663
730
|
flowchart LR
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
S2["#165: ResolvedSpawnConfig"] --> S3["#166: AgentSpawnConfig"]
|
|
669
|
-
S4["#167: RunnerIO"]
|
|
670
|
-
S5["#168: SessionConfig"]
|
|
671
|
-
S6["#169: RunOptions"]
|
|
672
|
-
end
|
|
673
|
-
subgraph complexity["Complexity reduction"]
|
|
674
|
-
S7["#170: buildContentLines"]
|
|
675
|
-
S8["#171: renderResult"]
|
|
676
|
-
S9["#172: Shared turn-formatting"]
|
|
677
|
-
end
|
|
678
|
-
S1 --> S2 & S4 & S5 & S6
|
|
679
|
-
S1 --> S7 & S8 & S9
|
|
731
|
+
L0["Layer 0: SessionContext interface"] --> L1["Layer 1: Runtime owns queries"]
|
|
732
|
+
L1 --> L3["Layer 3: Classes replace factories"]
|
|
733
|
+
L2["Layer 2: Align interfaces"] --> L3
|
|
734
|
+
L3 --> L4["Layer 4: Simplify index.ts"]
|
|
680
735
|
```
|
|
681
736
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
737
|
+
Layers 0 and 2 are independent of each other.
|
|
738
|
+
Layer 1 depends on Layer 0.
|
|
739
|
+
Layer 3 depends on both Layer 1 and Layer 2.
|
|
740
|
+
Layer 4 depends on Layer 3.
|
|
741
|
+
|
|
742
|
+
## Improvement roadmap (Phase 12)
|
|
743
|
+
|
|
744
|
+
Phase 12 addresses the remaining fallow refactoring targets and test duplication.
|
|
745
|
+
These are independent of Phase 11 and can proceed in parallel if desired.
|
|
746
|
+
|
|
747
|
+
### Step 1: Decompose `renderWidgetLines` (cognitive 44)
|
|
748
|
+
|
|
749
|
+
`renderWidgetLines` in `ui/widget-renderer.ts` handles agent-status formatting, tree connectors, overflow, and empty states.
|
|
750
|
+
Extract per-status renderers and a tree-connector utility.
|
|
751
|
+
|
|
752
|
+
- Target: `src/ui/widget-renderer.ts`
|
|
753
|
+
- Outcome: cognitive complexity < 10
|
|
754
|
+
|
|
755
|
+
### Step 2: Decompose `showAgentDetail` (cognitive 33)
|
|
756
|
+
|
|
757
|
+
`showAgentDetail` in `ui/agent-config-editor.ts` handles display, edit, eject, and delete flows.
|
|
758
|
+
Extract sub-functions per menu action.
|
|
759
|
+
|
|
760
|
+
- Target: `src/ui/agent-config-editor.ts`
|
|
761
|
+
- Outcome: cognitive complexity < 10
|
|
762
|
+
|
|
763
|
+
### Step 3: Decompose `update` in `agent-widget.ts` (cognitive 31)
|
|
764
|
+
|
|
765
|
+
`update` mixes timer lifecycle, agent list assembly, render delegation, and visibility state.
|
|
766
|
+
Extract `assembleWidgetState` (pure) and timer management.
|
|
767
|
+
|
|
768
|
+
- Target: `src/ui/agent-widget.ts`
|
|
769
|
+
- Outcome: cognitive complexity < 10
|
|
770
|
+
|
|
771
|
+
### Step 4: Extract shared test fixtures
|
|
772
|
+
|
|
773
|
+
The 3 heaviest clone families:
|
|
774
|
+
|
|
775
|
+
- `agent-runner.test.ts` + `agent-runner-extension-tools.test.ts` (60-line shared setup)
|
|
776
|
+
- `agent-menu.test.ts` + `agent-creation-wizard.test.ts` + `agent-config-editor.test.ts` (54+51+24 lines)
|
|
777
|
+
- `agent-manager.test.ts` (18 internal clone groups, 210 duplicated lines)
|
|
778
|
+
|
|
779
|
+
Extract shared factories into `test/fixtures/` modules.
|
|
780
|
+
|
|
781
|
+
- Target: new `test/fixtures/` modules
|
|
782
|
+
- Outcome: test duplication reduced by ~400 lines
|
|
685
783
|
|
|
686
784
|
## Refactoring history
|
|
687
785
|
|
|
688
|
-
Phases 1–5 and 7–
|
|
786
|
+
Phases 1–5 and 7–10 are complete.
|
|
689
787
|
Phase 6 (UI extraction to a separate package) is deferred.
|
|
690
788
|
Detailed records are preserved in per-phase history files:
|
|
691
789
|
|
|
692
|
-
| Phase | Title | Status | History
|
|
693
|
-
| ----- | --------------------------------------------------- | -------- |
|
|
694
|
-
| 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md)
|
|
695
|
-
| 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md)
|
|
696
|
-
| 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md)
|
|
697
|
-
| 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md)
|
|
698
|
-
| 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md)
|
|
699
|
-
| 6 | Extract UI to separate package | Deferred | —
|
|
700
|
-
| 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md)
|
|
701
|
-
| 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md)
|
|
702
|
-
| 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md)
|
|
790
|
+
| Phase | Title | Status | History |
|
|
791
|
+
| ----- | --------------------------------------------------- | -------- | ------------------------------------------------------------------------------------ |
|
|
792
|
+
| 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
|
|
793
|
+
| 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
|
|
794
|
+
| 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
|
|
795
|
+
| 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
|
|
796
|
+
| 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
|
|
797
|
+
| 6 | Extract UI to separate package | Deferred | — |
|
|
798
|
+
| 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
|
|
799
|
+
| 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
|
|
800
|
+
| 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
|
|
801
|
+
| 10 | Domain organization, bag decomposition, complexity | Complete | [phase-10-structural-decomposition.md](history/phase-10-structural-decomposition.md) |
|
|
703
802
|
|
|
704
803
|
### Structural refactoring issues
|
|
705
804
|
|
|
706
|
-
| Phase | Issue | Summary
|
|
707
|
-
| ------------------ | ---------------------------------------------------------- |
|
|
708
|
-
| Foundation | #69, #71, #76, #80 | SubagentRuntime, pure assembler, cwd injection, config consolidation
|
|
709
|
-
| Core decomposition | #84, #72, #87, #70 | WorktreeManager, AgentManager DI, runtime methods, handler extraction
|
|
710
|
-
| Interface polish | #66, #77 | SDK types, projectAgentsDir
|
|
711
|
-
| Features | #61 | JSONL session transcripts
|
|
712
|
-
| AgentManager | #98, #99, #100, #102 | Record state machine, ParentSnapshot, session-event observation, test factory
|
|
713
|
-
| Encapsulation | #108, #109, #110, #111, #112, #113, #114, #115, #116, #118 | Registry, settings, activity tracker, record lifecycle, observer, spawn options, deps narrowing, tool split, type housekeeping
|
|
714
|
-
| Testability | #131, #132, #133, #134, #135, #136 | Shared fixtures, session-config IO, runner SDK boundary, as-any reduction, display extraction, menu decomposition
|
|
715
|
-
| Observation/ctx | #144, #145, #146, #147, #148 | Observation consolidation, execute decomposition, UI context, text wrapping injection, widget rendering split
|
|
716
|
-
| Phase 10 | #164, #166, #167, #168, #169, #170, #171
|
|
805
|
+
| Phase | Issue | Summary |
|
|
806
|
+
| ------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
807
|
+
| Foundation | #69, #71, #76, #80 | SubagentRuntime, pure assembler, cwd injection, config consolidation |
|
|
808
|
+
| Core decomposition | #84, #72, #87, #70 | WorktreeManager, AgentManager DI, runtime methods, handler extraction |
|
|
809
|
+
| Interface polish | #66, #77 | SDK types, projectAgentsDir |
|
|
810
|
+
| Features | #61 | JSONL session transcripts |
|
|
811
|
+
| AgentManager | #98, #99, #100, #102 | Record state machine, ParentSnapshot, session-event observation, test factory |
|
|
812
|
+
| Encapsulation | #108, #109, #110, #111, #112, #113, #114, #115, #116, #118 | Registry, settings, activity tracker, record lifecycle, observer, spawn options, deps narrowing, tool split, type housekeeping |
|
|
813
|
+
| Testability | #131, #132, #133, #134, #135, #136 | Shared fixtures, session-config IO, runner SDK boundary, as-any reduction, display extraction, menu decomposition |
|
|
814
|
+
| Observation/ctx | #144, #145, #146, #147, #148 | Observation consolidation, execute decomposition, UI context, text wrapping injection, widget rendering split |
|
|
815
|
+
| Phase 10 | #164, #165, #166, #167, #168, #169, #170, #171, #172 | Domain directories, ResolvedSpawnConfig, ParentSessionInfo, RunnerIO split, ToolFilterConfig, RunContext, buildContentLines, renderResult, content-items |
|
|
717
816
|
|
|
718
817
|
The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
|
|
719
818
|
|
|
@@ -733,12 +832,12 @@ The upstream test suite is run periodically as a regression canary for the agent
|
|
|
733
832
|
[gotgenes/pi-packages]: https://github.com/gotgenes/pi-packages
|
|
734
833
|
[tintinweb/pi-subagents]: https://github.com/tintinweb/pi-subagents
|
|
735
834
|
|
|
736
|
-
[164]: https://github.com/gotgenes/pi-packages/issues/164
|
|
737
|
-
[165]: https://github.com/gotgenes/pi-packages/issues/165
|
|
738
835
|
[166]: https://github.com/gotgenes/pi-packages/issues/166
|
|
739
836
|
[167]: https://github.com/gotgenes/pi-packages/issues/167
|
|
740
837
|
[168]: https://github.com/gotgenes/pi-packages/issues/168
|
|
741
838
|
[169]: https://github.com/gotgenes/pi-packages/issues/169
|
|
742
|
-
[
|
|
743
|
-
[
|
|
744
|
-
[
|
|
839
|
+
[192]: https://github.com/gotgenes/pi-packages/issues/192
|
|
840
|
+
[193]: https://github.com/gotgenes/pi-packages/issues/193
|
|
841
|
+
[194]: https://github.com/gotgenes/pi-packages/issues/194
|
|
842
|
+
[195]: https://github.com/gotgenes/pi-packages/issues/195
|
|
843
|
+
[196]: https://github.com/gotgenes/pi-packages/issues/196
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Phase 10: Domain organization, bag decomposition, and complexity reduction
|
|
2
|
+
|
|
3
|
+
Target: reorganize source into domain directories; decompose remaining wide parameter bags into focused value objects; reduce cyclomatic complexity in rendering functions; eliminate production code duplication.
|
|
4
|
+
|
|
5
|
+
## Current smells
|
|
6
|
+
|
|
7
|
+
| Smell | Location | Evidence | Severity |
|
|
8
|
+
| ------------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------- |
|
|
9
|
+
| Flat `src/` directory | `src/` root | 20+ files in a single directory with no domain grouping; hard to see module boundaries | Medium |
|
|
10
|
+
| `ResolvedSpawnConfig` is a 15-field bag | `spawn-config.ts` | Mixes identity (name, slug), execution (model, permissions), and presentation (icon, color) concerns in one interface | Medium |
|
|
11
|
+
| `AgentSpawnConfig` threads parent session fields | `agent-manager.ts` | `parentSessionFile`, `parentSessionId`, `toolCallId` always travel together but are separate parameters | Low |
|
|
12
|
+
| `RunnerIO` is too wide | `foreground-runner.ts` | 9 methods spanning environment concerns (cwd, env) and session factory concerns (create, attach, resume) | Medium |
|
|
13
|
+
| `SessionConfig` mixes tool filtering with session setup | `session-config.ts` | `toolNames`, `disallowedSet`, `extensions` are a cohesive filter group buried in a larger config | Low |
|
|
14
|
+
| `RunOptions` is a 12-field bag | `foreground-runner.ts` | Mixes execution context (exec, registry, cwd) with per-run options (model, prompt, permissions) | Medium |
|
|
15
|
+
| `buildContentLines` high complexity | `ui/build-content-lines.ts` | ~60-line function with a switch over every content type; each branch is an independent formatter | Medium |
|
|
16
|
+
| `renderResult` high complexity | `tools/agent-tool.ts` | ~80-line function with status-based branching; each status is an independent rendering concern | Medium |
|
|
17
|
+
| Duplicated turn-formatting logic | `session/session-runner.ts`, `tools/agent-tool.ts` | 18 lines of identical `ToolCallContent` extraction and `getToolCallName` logic in two production files | Low |
|
|
18
|
+
|
|
19
|
+
## Step 1: Reorganize source into domain directories (#164)
|
|
20
|
+
|
|
21
|
+
Moved files into `config/`, `session/`, `lifecycle/`, `observation/`, and `service/` subdirectories.
|
|
22
|
+
All `src/` internal imports now use `#src/` path aliases.
|
|
23
|
+
Root `src/` reduced to 5 files + 8 directories.
|
|
24
|
+
|
|
25
|
+
Impact: domain boundaries are visible in the directory tree; imports communicate intent via path alias.
|
|
26
|
+
|
|
27
|
+
## Step 2: Decompose ResolvedSpawnConfig (#165)
|
|
28
|
+
|
|
29
|
+
Split the 15-field `ResolvedSpawnConfig` into three focused value objects:
|
|
30
|
+
|
|
31
|
+
- `SpawnIdentity` — name, slug, key
|
|
32
|
+
- `SpawnExecution` — model, permissions, tools, prompt, systemPrompt
|
|
33
|
+
- `SpawnPresentation` — icon, color, description
|
|
34
|
+
|
|
35
|
+
`ResolvedSpawnConfig` composes these three plus the remaining spawn-level fields.
|
|
36
|
+
|
|
37
|
+
Impact: each consumer receives only the fields it needs; adding a presentation field no longer touches execution code.
|
|
38
|
+
|
|
39
|
+
## Step 3: Extract ParentSessionInfo from AgentSpawnConfig (#166)
|
|
40
|
+
|
|
41
|
+
Extracted `parentSessionFile`, `parentSessionId`, and `toolCallId` into `ParentSessionInfo`.
|
|
42
|
+
`AgentSpawnConfig` and `AgentManager.spawn()` accept the single value object instead of three separate parameters.
|
|
43
|
+
|
|
44
|
+
Impact: eliminated parameter co-travel; the three fields that always move together are now a single concept.
|
|
45
|
+
|
|
46
|
+
## Step 4: Narrow RunnerIO (#167)
|
|
47
|
+
|
|
48
|
+
Split `RunnerIO` into two focused interfaces:
|
|
49
|
+
|
|
50
|
+
- `EnvironmentIO` (3 methods) — cwd, env, platform concerns
|
|
51
|
+
- `SessionFactoryIO` (5+1 methods) — create, attach, resume, and related session lifecycle
|
|
52
|
+
|
|
53
|
+
`RunnerIO` kept as a backward-compatible type alias (`EnvironmentIO & SessionFactoryIO`).
|
|
54
|
+
|
|
55
|
+
Impact: consumers declare which IO surface they actually need; test doubles shrink to the relevant subset.
|
|
56
|
+
|
|
57
|
+
## Step 5: Extract ToolFilterConfig from SessionConfig (#168)
|
|
58
|
+
|
|
59
|
+
Grouped `toolNames`, `disallowedSet`, and `extensions` into `ToolFilterConfig`.
|
|
60
|
+
`filterActiveTools` accepts a single `ToolFilterConfig` argument instead of three separate parameters.
|
|
61
|
+
|
|
62
|
+
Impact: tool-filtering concern is encapsulated; `SessionConfig` is narrower and more focused.
|
|
63
|
+
|
|
64
|
+
## Step 6: Extract RunContext from RunOptions (#169)
|
|
65
|
+
|
|
66
|
+
Extracted `exec`, `registry`, `cwd`, and `parentSession` into `RunContext`.
|
|
67
|
+
`RunOptions` reduced from 12 fields to 9 (the 4 extracted fields replaced by a single `context` field, plus the remaining per-run options).
|
|
68
|
+
|
|
69
|
+
Impact: execution context is a reusable value object; `RunOptions` focuses on per-run configuration.
|
|
70
|
+
|
|
71
|
+
## Step 7: Reduce buildContentLines complexity (#170)
|
|
72
|
+
|
|
73
|
+
Extracted per-content-type formatters into `ui/message-formatters.ts`.
|
|
74
|
+
Each content type (text, tool-use, tool-result, image, etc.) has a dedicated pure function.
|
|
75
|
+
`buildContentLines` is now a ~30-line dispatch loop that delegates to the appropriate formatter.
|
|
76
|
+
|
|
77
|
+
Impact: cyclomatic complexity of `buildContentLines` dropped from ~15 to ~5; each formatter is independently testable.
|
|
78
|
+
|
|
79
|
+
## Step 8: Reduce renderResult complexity (#171)
|
|
80
|
+
|
|
81
|
+
Extracted per-status formatters into `tools/result-renderer.ts`.
|
|
82
|
+
Each result status (success, error, timeout, cancelled) has a dedicated pure function.
|
|
83
|
+
`renderResult` reduced from ~80 lines to a 10-line guard that dispatches to the appropriate renderer.
|
|
84
|
+
|
|
85
|
+
Impact: cyclomatic complexity of `renderResult` dropped from ~10 to ~3; status rendering is independently testable.
|
|
86
|
+
|
|
87
|
+
## Step 9: Extract shared turn-formatting logic (#172)
|
|
88
|
+
|
|
89
|
+
Extracted `ToolCallContent`, `getToolCallName`, and `extractAssistantContent` into `session/content-items.ts`.
|
|
90
|
+
Both `session/session-runner.ts` and `tools/agent-tool.ts` import from the shared module.
|
|
91
|
+
|
|
92
|
+
Impact: eliminated 18 lines of production code duplication; single source of truth for turn-content extraction.
|
|
93
|
+
|
|
94
|
+
## Step dependencies
|
|
95
|
+
|
|
96
|
+
```mermaid
|
|
97
|
+
flowchart LR
|
|
98
|
+
subgraph bag["Bag decomposition track"]
|
|
99
|
+
S2["2: Decompose ResolvedSpawnConfig #165"] --> S3["3: Extract ParentSessionInfo #166"]
|
|
100
|
+
S4["4: Narrow RunnerIO #167"]
|
|
101
|
+
S5["5: Extract ToolFilterConfig #168"]
|
|
102
|
+
S6["6: Extract RunContext #169"]
|
|
103
|
+
end
|
|
104
|
+
subgraph complexity["Complexity reduction track"]
|
|
105
|
+
S7["7: Reduce buildContentLines #170"]
|
|
106
|
+
S8["8: Reduce renderResult #171"]
|
|
107
|
+
S9["9: Extract turn-formatting #172"]
|
|
108
|
+
end
|
|
109
|
+
S1["1: Reorganize into domain dirs #164"] --> bag
|
|
110
|
+
S1 --> complexity
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Step 1 unblocked all other steps.
|
|
114
|
+
Within the bag decomposition track, Step 2 enabled Step 3.
|
|
115
|
+
The bag decomposition and complexity reduction tracks were independent of each other.
|
|
116
|
+
|
|
117
|
+
## Impact
|
|
118
|
+
|
|
119
|
+
| Metric | Before | After |
|
|
120
|
+
| ------------------------------ | ------------------------------ | --------------------------------------------- |
|
|
121
|
+
| Health score | ~65 | 75 |
|
|
122
|
+
| `src/` root files | 20+ | 5 files + 8 directories |
|
|
123
|
+
| `ResolvedSpawnConfig` fields | 15 | 3 composed value objects |
|
|
124
|
+
| `RunOptions` fields | 12 | 9 |
|
|
125
|
+
| `RunnerIO` methods | 9 | 3 (`EnvironmentIO`) + 6 (`SessionFactoryIO`) |
|
|
126
|
+
| `buildContentLines` complexity | ~15 | ~5 |
|
|
127
|
+
| `renderResult` lines | ~80 | ~10 |
|
|
128
|
+
| Production duplication | 18 lines | 0 |
|
|
129
|
+
| Test duplication | ~1,400 lines (69 clone groups) | ~1,400 lines (69 clone groups) — not targeted |
|
|
130
|
+
|
|
131
|
+
## Related issues
|
|
132
|
+
|
|
133
|
+
- #164 — Reorganize source into domain directories
|
|
134
|
+
- #165 — Decompose ResolvedSpawnConfig
|
|
135
|
+
- #166 — Extract ParentSessionInfo from AgentSpawnConfig
|
|
136
|
+
- #167 — Narrow RunnerIO
|
|
137
|
+
- #168 — Extract ToolFilterConfig from SessionConfig
|
|
138
|
+
- #169 — Extract RunContext from RunOptions
|
|
139
|
+
- #170 — Reduce buildContentLines complexity
|
|
140
|
+
- #171 — Reduce renderResult complexity
|
|
141
|
+
- #172 — Extract shared turn-formatting logic
|