@gotgenes/pi-subagents 18.0.1 → 18.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/docs/architecture/architecture.md +157 -204
- package/docs/architecture/history/phase-19-implement-ui-decisions.md +282 -0
- package/package.json +5 -6
- package/src/lifecycle/subagent.ts +5 -9
- package/src/observation/notification.ts +23 -10
- package/src/observation/subagent-events-observer.ts +1 -3
- package/src/tools/get-result-report.ts +60 -0
- package/src/tools/get-result-tool.ts +25 -40
- package/src/types.ts +1 -1
- package/src/ui/session-navigator.ts +0 -2
- package/.prettierignore +0 -5
- package/AGENTS.md +0 -7
- package/docs/plans/0048-implement-subagents-api.md +0 -304
- package/docs/plans/0049-remove-group-join-output-file-rpc.md +0 -180
- package/docs/plans/0051-update-adr-0001-hard-fork.md +0 -77
- package/docs/plans/0052-remove-scheduled-subagents.md +0 -133
- package/docs/plans/0053-extract-model-resolution-from-execute.md +0 -181
- package/docs/plans/0054-decompose-index-into-modules.md +0 -302
- package/docs/plans/0057-structured-debug-logging.md +0 -154
- package/docs/plans/0061-session-format-transcript.md +0 -284
- package/docs/plans/0066-replace-as-any-with-sdk-types.md +0 -215
- package/docs/plans/0069-create-subagent-runtime.md +0 -345
- package/docs/plans/0070-extract-event-handlers.md +0 -306
- package/docs/plans/0071-extract-session-config-assembler.md +0 -362
- package/docs/plans/0072-inject-agent-manager-collaborators.md +0 -329
- package/docs/plans/0076-inject-cwd-into-agent-manager.md +0 -102
- package/docs/plans/0077-inject-project-agents-dir.md +0 -115
- package/docs/plans/0080-consolidate-agent-config-lookup.md +0 -247
- package/docs/plans/0084-extract-git-worktree-manager.md +0 -142
- package/docs/plans/0087-evolve-subagent-runtime-methods.md +0 -240
- package/docs/plans/0098-extract-agent-record-state-machine.md +0 -435
- package/docs/plans/0099-replace-ctx-with-parent-snapshot.md +0 -488
- package/docs/plans/0100-replace-callback-threading-with-session-subscription.md +0 -454
- package/docs/plans/0102-consolidate-test-record-factory.md +0 -176
- package/docs/plans/0108-extract-agent-type-registry.md +0 -322
- package/docs/plans/0109-extract-settings-manager.md +0 -276
- package/docs/plans/0110-agent-activity-tracker.md +0 -297
- package/docs/plans/0111-split-agent-record-lifecycle.md +0 -582
- package/docs/plans/0112-replace-agent-manager-callbacks.md +0 -241
- package/docs/plans/0113-disambiguate-spawn-options.md +0 -155
- package/docs/plans/0114-narrow-agent-tool-menu-deps.md +0 -279
- package/docs/plans/0115-decompose-agent-tool.md +0 -337
- package/docs/plans/0116-type-housekeeping.md +0 -351
- package/docs/plans/0118-settings-manager-apply-methods.md +0 -271
- package/docs/plans/0123-remove-vi-fn-cast-smell.md +0 -179
- package/docs/plans/0131-consolidate-shared-test-fixtures.md +0 -207
- package/docs/plans/0132-inject-io-into-session-config.md +0 -219
- package/docs/plans/0133-inject-sdk-boundary-into-agent-runner.md +0 -373
- package/docs/plans/0134-reduce-as-any-casts.md +0 -366
- package/docs/plans/0135-extract-display-helpers.md +0 -182
- package/docs/plans/0136-decompose-agent-menu.md +0 -300
- package/docs/plans/0144-consolidate-observation-model.md +0 -263
- package/docs/plans/0145-decompose-execute-push-ctx-to-boundary.md +0 -290
- package/docs/plans/0146-narrow-ui-context.md +0 -319
- package/docs/plans/0147-inject-wrap-text-into-conversation-viewer.md +0 -166
- package/docs/plans/0148-split-agent-widget-rendering.md +0 -255
- package/docs/plans/0152-add-prompt-snippet.md +0 -91
- package/docs/plans/0164-reorganize-into-domain-directories.md +0 -409
- package/docs/plans/0165-decompose-resolved-spawn-config.md +0 -157
- package/docs/plans/0166-extract-parent-session-info.md +0 -231
- package/docs/plans/0167-narrow-runner-io.md +0 -150
- package/docs/plans/0168-extract-tool-filter-config.md +0 -173
- package/docs/plans/0169-extract-run-context-from-run-options.md +0 -194
- package/docs/plans/0170-reduce-build-content-lines-complexity.md +0 -225
- package/docs/plans/0171-reduce-render-result-complexity.md +0 -200
- package/docs/plans/0172-extract-turn-formatting.md +0 -206
- package/docs/plans/0180-reorder-append-prompt-for-kv-cache.md +0 -100
- package/docs/plans/0185-remove-persistent-agent-memory.md +0 -161
- package/docs/plans/0188-replace-any-casts-with-sdk-types.md +0 -162
- package/docs/plans/0192-define-session-context-interface.md +0 -107
- package/docs/plans/0193-runtime-owns-context-queries.md +0 -245
- package/docs/plans/0194-align-tool-interfaces-for-structural-typing.md +0 -139
- package/docs/plans/0195-convert-tool-factories-to-classes.md +0 -264
- package/docs/plans/0196-convert-runner-menu-to-classes.md +0 -268
- package/docs/plans/0205-decompose-render-widget-lines.md +0 -140
- package/docs/plans/0206-decompose-show-agent-detail.md +0 -211
- package/docs/plans/0207-decompose-agent-widget-update.md +0 -242
- package/docs/plans/0208-extract-shared-test-fixtures.md +0 -298
- package/docs/plans/0214-convert-remaining-closure-factories-to-classes.md +0 -261
- package/docs/plans/0215-decompose-build-parent-context.md +0 -166
- package/docs/plans/0216-decompose-start-agent.md +0 -255
- package/docs/plans/0217-extract-overwrite-guard.md +0 -176
- package/docs/plans/0218-push-sdk-boundary-in-settings.md +0 -172
- package/docs/plans/0219-reduce-test-duplication-top-3-clone-families.md +0 -162
- package/docs/plans/0227-evolve-agent-record-into-agent.md +0 -322
- package/docs/plans/0228-async-start-agent-dissolve-run-handle.md +0 -288
- package/docs/plans/0229-agent-born-complete.md +0 -564
- package/docs/plans/0230-extract-concurrency-queue.md +0 -265
- package/docs/plans/0231-push-exec-registry-to-runner.md +0 -245
- package/docs/plans/0232-agent-resume-internal-observer-lifecycle.md +0 -180
- package/docs/plans/0237-remove-disallowed-tools.md +0 -180
- package/docs/plans/0238-remove-extensions-filtering.md +0 -191
- package/docs/plans/0239-collapse-filter-active-tools.md +0 -217
- package/docs/plans/0242-rename-agent-tool-to-subagent.md +0 -165
- package/docs/plans/0256-extract-worktree-isolation.md +0 -256
- package/docs/plans/0257-extract-child-session-factory.md +0 -285
- package/docs/plans/0262-add-workspace-provider-seam.md +0 -264
- package/docs/plans/0264-remove-extension-lifecycle-control.md +0 -277
- package/docs/plans/0265-born-complete-subagent-session.md +0 -333
- package/docs/plans/0270-type-consumable-public-surface.md +0 -204
- package/docs/plans/0272-export-workspace-collaborator-types.md +0 -147
- package/docs/plans/0277-encapsulate-agent-session.md +0 -304
- package/docs/plans/0280-rename-agent-to-subagent.md +0 -197
- package/docs/plans/0360-custom-agents-default-append.md +0 -132
- package/docs/plans/0373-extract-subagent-state.md +0 -250
- package/docs/plans/0374-encapsulate-subagent-start-notification.md +0 -268
- package/docs/plans/0375-extract-run-listener-workspace-bracket.md +0 -300
- package/docs/plans/0376-extract-manager-observer.md +0 -232
- package/docs/plans/0377-split-widget-delegation-from-runtime.md +0 -254
- package/docs/plans/0378-consolidate-lifecycle-test-fixtures.md +0 -243
- package/docs/plans/0379-consolidate-ui-tools-test-fixtures.md +0 -335
- package/docs/plans/0380-extract-layered-settings-loader.md +0 -223
- package/docs/plans/0381-replace-concurrency-queue-with-limiter.md +0 -267
- package/docs/plans/0400-include-parent-prompt-in-replace-mode.md +0 -199
- package/docs/plans/0403-abort-subagents-on-interrupt.md +0 -180
- package/docs/plans/0412-unify-session-mock-builders.md +0 -146
- package/docs/plans/0420-fold-run-metrics-live-activity-onto-record.md +0 -228
- package/docs/plans/0421-migrate-activity-readers-to-record-getters.md +0 -309
- package/docs/plans/0422-delete-activity-tracker-ui-observer.md +0 -204
- package/docs/plans/0423-widget-self-drive-from-lifecycle.md +0 -264
- package/docs/plans/0424-drop-widget-dep-from-subagent-tool.md +0 -166
- package/docs/plans/0425-reconcile-subagent-events-contract.md +0 -144
- package/docs/plans/0426-consolidate-residual-test-clone-families.md +0 -235
- package/docs/plans/0427-reconsider-ui-direction.md +0 -212
- package/docs/plans/0441-remove-agent-definition-management-subtree.md +0 -180
- package/docs/plans/0442-dissolve-agents-conversation-viewer.md +0 -195
- package/docs/plans/0443-consolidate-remaining-test-clone-families.md +0 -268
- package/docs/plans/0444-shrink-widget-to-background-agents.md +0 -156
- package/docs/plans/0445-native-session-navigation.md +0 -365
- package/docs/plans/0446-spike-session-navigation-entry-criteria.md +0 -171
- package/docs/plans/0447-extract-subagents-settings-command.md +0 -220
- package/docs/plans/0448-block-disabled-agent-spawn.md +0 -207
- package/docs/plans/0462-navigation-renderer-tui-components.md +0 -338
- package/docs/plans/0463-file-snapshot-source-evicted-agents.md +0 -306
- package/docs/plans/0470-refresh-readme-command-surface.md +0 -159
- package/docs/retro/0048-implement-subagents-api.md +0 -44
- package/docs/retro/0049-remove-group-join-output-file-rpc.md +0 -49
- package/docs/retro/0051-update-adr-0001-hard-fork.md +0 -39
- package/docs/retro/0053-extract-model-resolution-from-execute.md +0 -40
- package/docs/retro/0054-decompose-index-into-modules.md +0 -53
- package/docs/retro/0057-structured-debug-logging.md +0 -77
- package/docs/retro/0061-session-format-transcript.md +0 -41
- package/docs/retro/0066-replace-as-any-with-sdk-types.md +0 -44
- package/docs/retro/0069-create-subagent-runtime.md +0 -43
- package/docs/retro/0070-extract-event-handlers.md +0 -49
- package/docs/retro/0071-extract-session-config-assembler.md +0 -60
- package/docs/retro/0072-inject-agent-manager-collaborators.md +0 -46
- package/docs/retro/0076-inject-cwd-into-agent-manager.md +0 -35
- package/docs/retro/0077-inject-project-agents-dir.md +0 -33
- package/docs/retro/0080-consolidate-agent-config-lookup.md +0 -35
- package/docs/retro/0084-extract-git-worktree-manager.md +0 -37
- package/docs/retro/0087-evolve-subagent-runtime-methods.md +0 -42
- package/docs/retro/0098-extract-agent-record-state-machine.md +0 -46
- package/docs/retro/0099-replace-ctx-with-parent-snapshot.md +0 -37
- package/docs/retro/0100-replace-callback-threading-with-session-subscription.md +0 -36
- package/docs/retro/0102-consolidate-test-record-factory.md +0 -30
- package/docs/retro/0108-extract-agent-type-registry.md +0 -41
- package/docs/retro/0109-extract-settings-manager.md +0 -55
- package/docs/retro/0110-agent-activity-tracker.md +0 -44
- package/docs/retro/0111-split-agent-record-lifecycle.md +0 -61
- package/docs/retro/0112-replace-agent-manager-callbacks.md +0 -35
- package/docs/retro/0113-disambiguate-spawn-options.md +0 -29
- package/docs/retro/0114-narrow-agent-tool-menu-deps.md +0 -38
- package/docs/retro/0115-decompose-agent-tool.md +0 -51
- package/docs/retro/0116-type-housekeeping.md +0 -42
- package/docs/retro/0118-settings-manager-apply-methods.md +0 -40
- package/docs/retro/0123-remove-vi-fn-cast-smell.md +0 -49
- package/docs/retro/0131-consolidate-shared-test-fixtures.md +0 -46
- package/docs/retro/0132-inject-io-into-session-config.md +0 -33
- package/docs/retro/0133-inject-sdk-boundary-into-agent-runner.md +0 -45
- package/docs/retro/0134-reduce-as-any-casts.md +0 -56
- package/docs/retro/0135-extract-display-helpers.md +0 -38
- package/docs/retro/0136-decompose-agent-menu.md +0 -43
- package/docs/retro/0144-consolidate-observation-model.md +0 -39
- package/docs/retro/0145-decompose-execute-push-ctx-to-boundary.md +0 -56
- package/docs/retro/0146-narrow-ui-context.md +0 -70
- package/docs/retro/0147-inject-wrap-text-into-conversation-viewer.md +0 -90
- package/docs/retro/0148-split-agent-widget-rendering.md +0 -39
- package/docs/retro/0152-add-prompt-snippet.md +0 -34
- package/docs/retro/0164-reorganize-into-domain-directories.md +0 -82
- package/docs/retro/0165-decompose-resolved-spawn-config.md +0 -78
- package/docs/retro/0166-extract-parent-session-info.md +0 -66
- package/docs/retro/0167-narrow-runner-io.md +0 -63
- package/docs/retro/0168-extract-tool-filter-config.md +0 -76
- package/docs/retro/0169-extract-run-context-from-run-options.md +0 -68
- package/docs/retro/0170-reduce-build-content-lines-complexity.md +0 -72
- package/docs/retro/0171-reduce-render-result-complexity.md +0 -77
- package/docs/retro/0172-extract-turn-formatting.md +0 -80
- package/docs/retro/0180-reorder-append-prompt-for-kv-cache.md +0 -62
- package/docs/retro/0185-remove-persistent-agent-memory.md +0 -73
- package/docs/retro/0188-replace-any-casts-with-sdk-types.md +0 -69
- package/docs/retro/0192-define-session-context-interface.md +0 -59
- package/docs/retro/0193-runtime-owns-context-queries.md +0 -75
- package/docs/retro/0194-align-tool-interfaces-for-structural-typing.md +0 -62
- package/docs/retro/0195-convert-tool-factories-to-classes.md +0 -84
- package/docs/retro/0196-convert-runner-menu-to-classes.md +0 -73
- package/docs/retro/0205-decompose-render-widget-lines.md +0 -64
- package/docs/retro/0206-decompose-show-agent-detail.md +0 -67
- package/docs/retro/0207-decompose-agent-widget-update.md +0 -92
- package/docs/retro/0208-extract-shared-test-fixtures.md +0 -102
- package/docs/retro/0214-convert-remaining-closure-factories-to-classes.md +0 -66
- package/docs/retro/0215-decompose-build-parent-context.md +0 -61
- package/docs/retro/0216-decompose-start-agent.md +0 -80
- package/docs/retro/0217-extract-overwrite-guard.md +0 -63
- package/docs/retro/0218-push-sdk-boundary-in-settings.md +0 -72
- package/docs/retro/0219-reduce-test-duplication-top-3-clone-families.md +0 -64
- package/docs/retro/0227-evolve-agent-record-into-agent.md +0 -80
- package/docs/retro/0228-async-start-agent-dissolve-run-handle.md +0 -80
- package/docs/retro/0229-agent-born-complete.md +0 -89
- package/docs/retro/0230-extract-concurrency-queue.md +0 -38
- package/docs/retro/0231-push-exec-registry-to-runner.md +0 -71
- package/docs/retro/0232-agent-resume-internal-observer-lifecycle.md +0 -109
- package/docs/retro/0237-remove-disallowed-tools.md +0 -78
- package/docs/retro/0238-remove-extensions-filtering.md +0 -72
- package/docs/retro/0239-collapse-filter-active-tools.md +0 -70
- package/docs/retro/0242-rename-agent-tool-to-subagent.md +0 -82
- package/docs/retro/0256-extract-worktree-isolation.md +0 -89
- package/docs/retro/0257-extract-child-session-factory.md +0 -33
- package/docs/retro/0262-add-workspace-provider-seam.md +0 -89
- package/docs/retro/0264-remove-extension-lifecycle-control.md +0 -91
- package/docs/retro/0265-born-complete-subagent-session.md +0 -97
- package/docs/retro/0270-type-consumable-public-surface.md +0 -108
- package/docs/retro/0272-export-workspace-collaborator-types.md +0 -38
- package/docs/retro/0277-encapsulate-agent-session.md +0 -80
- package/docs/retro/0280-rename-agent-to-subagent.md +0 -96
- package/docs/retro/0360-custom-agents-default-append.md +0 -81
- package/docs/retro/0373-extract-subagent-state.md +0 -94
- package/docs/retro/0374-encapsulate-subagent-start-notification.md +0 -171
- package/docs/retro/0375-extract-run-listener-workspace-bracket.md +0 -104
- package/docs/retro/0376-extract-manager-observer.md +0 -81
- package/docs/retro/0377-split-widget-delegation-from-runtime.md +0 -105
- package/docs/retro/0378-consolidate-lifecycle-test-fixtures.md +0 -100
- package/docs/retro/0379-consolidate-ui-tools-test-fixtures.md +0 -52
- package/docs/retro/0380-extract-layered-settings-loader.md +0 -97
- package/docs/retro/0381-replace-concurrency-queue-with-limiter.md +0 -95
- package/docs/retro/0400-include-parent-prompt-in-replace-mode.md +0 -84
- package/docs/retro/0403-abort-subagents-on-interrupt.md +0 -90
- package/docs/retro/0412-unify-session-mock-builders.md +0 -80
- package/docs/retro/0420-fold-run-metrics-live-activity-onto-record.md +0 -90
- package/docs/retro/0421-migrate-activity-readers-to-record-getters.md +0 -105
- package/docs/retro/0422-delete-activity-tracker-ui-observer.md +0 -97
- package/docs/retro/0423-widget-self-drive-from-lifecycle.md +0 -112
- package/docs/retro/0424-drop-widget-dep-from-subagent-tool.md +0 -89
- package/docs/retro/0425-reconcile-subagent-events-contract.md +0 -101
- package/docs/retro/0426-consolidate-residual-test-clone-families.md +0 -100
- package/docs/retro/0427-reconsider-ui-direction.md +0 -102
- package/docs/retro/0441-remove-agent-definition-management-subtree.md +0 -97
- package/docs/retro/0442-dissolve-agents-conversation-viewer.md +0 -108
- package/docs/retro/0443-consolidate-remaining-test-clone-families.md +0 -28
- package/docs/retro/0444-shrink-widget-to-background-agents.md +0 -81
- package/docs/retro/0445-native-session-navigation.md +0 -122
- package/docs/retro/0446-spike-session-navigation-entry-criteria.md +0 -99
- package/docs/retro/0447-extract-subagents-settings-command.md +0 -91
- package/docs/retro/0448-block-disabled-agent-spawn.md +0 -75
- package/docs/retro/0462-navigation-renderer-tui-components.md +0 -105
- package/docs/retro/0463-file-snapshot-source-evicted-agents.md +0 -103
- package/docs/retro/0470-refresh-readme-command-surface.md +0 -40
- package/src/observation/notification-state.ts +0 -27
- package/vitest.config.ts +0 -14
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
issue: 445
|
|
3
|
-
issue_title: "pi-subagents: implement native session navigation for any subagent (live or completed)"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Native session navigation for subagents (live transcript, text renderer)
|
|
7
|
-
|
|
8
|
-
## Release Recommendation
|
|
9
|
-
|
|
10
|
-
**Release:** ship independently
|
|
11
|
-
|
|
12
|
-
Phase 19 Step 4 ([#445]) carries `Release: independent` (spike-gated) in the architecture roadmap.
|
|
13
|
-
The only batch defined in Phase 19 is "dissolve-agents" (Steps 5–6, [#442]/[#441]); Step 4 is an additive replacement surface that stands up alongside the bespoke viewer and is not part of that batch.
|
|
14
|
-
It ships on its own.
|
|
15
|
-
|
|
16
|
-
## Problem Statement
|
|
17
|
-
|
|
18
|
-
The operator can view a subagent's conversation only through the bespoke `ConversationViewer` overlay, reachable inside the monolithic `/agents` menu.
|
|
19
|
-
That overlay re-implements session-transcript rendering — message formatting, a streaming indicator, scroll chrome — even though Pi already persists each child as a standalone session JSONL and ships its own session-rendering machinery.
|
|
20
|
-
ADR-0004 Decision B replaces the bespoke overlay with **native session navigation**: a flat command that lists the session's subagents, lets the operator pick one, and renders that child's transcript read-only through Pi's own components.
|
|
21
|
-
|
|
22
|
-
ADR-0004 Addendum 2 (the Step 1 spike, [#446]) refines the sourcing: a file read is a *snapshot* (good for a completed agent, frozen for a running one), so the transcript is **dual-sourced by liveness** — live from the in-memory record for a tracked agent, a file snapshot for an evicted one — rendered through one renderer.
|
|
23
|
-
|
|
24
|
-
This issue is deliberately **sliced** (see Decomposition below).
|
|
25
|
-
It stands up the complete navigation UX — list → pick → read-only, live transcript — behind a renderer-agnostic source seam, using Pi's `serializeConversation` as the first renderer.
|
|
26
|
-
Upgrading that renderer to Pi's per-entry TUI components, and broadening the candidate set to evicted agents, are named follow-ups.
|
|
27
|
-
|
|
28
|
-
## Goals
|
|
29
|
-
|
|
30
|
-
- Add a flat `/subagent-sessions` command that lists navigable subagents (`manager.listAgents()`, gated on `isSessionReady()`), lets the operator pick one, and renders its transcript **read-only** (non-interactive) in a scrollable overlay.
|
|
31
|
-
- Source the transcript **live** from the in-memory record: history from a new typed `agentMessages` accessor, live re-render via `record.subscribeToUpdates()`, and a running-agent streaming indicator from `record.activeTools` / `record.responseText`.
|
|
32
|
-
- Render through Pi's own `serializeConversation` (no bespoke message-formatting code) behind a `TranscriptSource` seam, so a follow-up swaps in Pi's per-entry TUI components without touching selection or sourcing.
|
|
33
|
-
- Add a typed `agentMessages` accessor on `SubagentSession` and `Subagent` returning `readonly SessionMessage[]` (the boundary currently widens `messages` to `readonly unknown[]`).
|
|
34
|
-
- Stand the new surface up **alongside** the existing `viewAgentConversation` / `ConversationViewer` path — it is removed only by Phase 19 Step 5 ([#442]).
|
|
35
|
-
|
|
36
|
-
Non-breaking: this is pure addition.
|
|
37
|
-
No existing export, command, default, or behavior changes.
|
|
38
|
-
|
|
39
|
-
## Non-Goals
|
|
40
|
-
|
|
41
|
-
- Do **not** implement the **file-snapshot** source branch (`parseSessionEntries` → `buildSessionContext`) in this issue.
|
|
42
|
-
With the `listAgents()`-only candidate set, no listed record is ever session-disposed (see Background), so the file branch has **no reachable caller** here — implementing it now would be dead code that fails the `fallow dead-code` gate.
|
|
43
|
-
It ships with the follow-up that broadens the candidate set to evicted agents.
|
|
44
|
-
- Do **not** use Pi's per-entry TUI components (`AssistantMessageComponent` / `ToolExecutionComponent` / …) as the renderer in this issue — that is the named renderer follow-up.
|
|
45
|
-
- Do **not** add in-session steering or any interactive gesture — steering stays in `steer_subagent` and the widget; the viewer is strictly read-only.
|
|
46
|
-
- Do **not** use `switchSession` or `loadEntriesFromFile` (per the ADR-0004 addendum).
|
|
47
|
-
- Do **not** touch `agent-menu.ts`, `conversation-viewer.ts`, or `message-formatters.ts` — they stay live until Step 5 ([#442]).
|
|
48
|
-
- Do **not** wire a widget selection gesture — the command is the primary surface; a widget gesture ([#444]) is an optional later enhancement.
|
|
49
|
-
|
|
50
|
-
## Background
|
|
51
|
-
|
|
52
|
-
Relevant existing modules:
|
|
53
|
-
|
|
54
|
-
- `src/lifecycle/subagent-session.ts` — `SubagentSession` wraps one SDK `AgentSession`.
|
|
55
|
-
Its `get messages(): readonly unknown[]` returns `this._session.messages as readonly unknown[]` — deliberately widened at the core boundary.
|
|
56
|
-
It already exposes `subscribe(fn)`, `outputFile`, `getContextPercent()`.
|
|
57
|
-
- `src/lifecycle/subagent.ts` — `Subagent` delegates `get messages(): readonly unknown[]`, `subscribeToUpdates(fn)`, `get outputFile()`, `isSessionReady()`, `get activeTools()`, `get responseText()`, `get status()` to its `subagentSession` / `state`.
|
|
58
|
-
These are the exact accessors the bespoke `ConversationViewer` reads — the new navigator reads the same surface.
|
|
59
|
-
- `src/lifecycle/subagent-manager.ts` — `listAgents()` returns tracked `Subagent[]` (newest first).
|
|
60
|
-
`cleanup()` (every 60 s) and `clearCompleted()` call `removeRecord(id, record)`, which calls `record.disposeSession()` **then** `agents.delete(id)` atomically.
|
|
61
|
-
`disposeSession()` disposes the wrapped session but does **not** null `subagentSession`; however, because dispose-and-delete are atomic, **no record that remains in `listAgents()` is ever session-disposed**.
|
|
62
|
-
Consequence: with the `listAgents()`-only candidate set, every listed, session-ready record has a live (non-disposed) session — so the live source is always valid and the file-snapshot branch is unreachable (drives the Non-Goal above).
|
|
63
|
-
- `src/ui/conversation-viewer.ts` — the bespoke overlay being replaced (deleted in Step 5).
|
|
64
|
-
Reads `record.messages`, subscribes via `record.subscribeToUpdates()`, renders a streaming indicator from `record.activeTools` / `record.responseText`, and owns its own scroll chrome (up/down/pageUp/pageDown/home/end/esc).
|
|
65
|
-
The new navigator must **not** import from it or from `message-formatters.ts` (both doomed in Step 5).
|
|
66
|
-
- `src/ui/agent-menu.ts` — `viewAgentConversation(ui, record)` gates on `record.isSessionReady()` and opens the overlay via `ui.custom<undefined>((tui, theme, _kb, done) => new ConversationViewer({...}), { overlay: true, overlayOptions: { anchor: "center", width: "90%", maxHeight: "70%" } })`.
|
|
67
|
-
This is the wiring shape the navigator mirrors.
|
|
68
|
-
- `src/ui/subagents-settings.ts` ([#447]) — the precedent for an additive, flat, narrow-interface command extracted from the menu.
|
|
69
|
-
- `src/ui/display.ts` — `getDisplayName(type, registry)`, `formatDuration(startedAt, completedAt)`, `getPromptModeLabel(type, registry)` for option labels.
|
|
70
|
-
- `src/types.ts` — shared type barrel; already imports SDK types (`AgentSessionEvent`, `ThinkingLevel`).
|
|
71
|
-
- `src/index.ts` — constructs `manager`, `registry`, and `settings`, and registers commands (`agents`, `subagents-settings`).
|
|
72
|
-
Sibling commands use flat hyphenated names with no `:` namespace.
|
|
73
|
-
|
|
74
|
-
Pi SDK facts verified for this plan:
|
|
75
|
-
|
|
76
|
-
- `parseSessionEntries`, `buildSessionContext`, `serializeConversation`, `SessionContext`, `FileEntry`, `SessionEntry`, and the per-entry components are all re-exported from the public `@earendil-works/pi-coding-agent` barrel; `loadEntriesFromFile` is **not** (confirmed in `dist/index.d.ts`).
|
|
77
|
-
- `SessionContext.messages` is typed `AgentMessage[]` (`AgentMessage` from `@earendil-works/pi-agent-core`, which is **not** a direct dependency).
|
|
78
|
-
Derive the type from the barrel-exported `SessionContext` rather than adding a dependency: `type SessionMessage = SessionContext["messages"][number]`.
|
|
79
|
-
- `serializeConversation(messages: Message[]): string` takes a **mutable** `Message[]` (from `@earendil-works/pi-ai`).
|
|
80
|
-
The accessor returns `readonly SessionMessage[]`, so the renderer must spread (`serializeConversation([...messages])`); the spread also resolves the readonly→mutable mismatch.
|
|
81
|
-
`AgentMessage` assignability to `Message` is verified in TDD step 2 (Pi itself feeds `buildSessionContext` output to serialization, so it is expected to hold); if it does not, narrow via a typed adapter at the renderer boundary.
|
|
82
|
-
|
|
83
|
-
AGENTS.md / package constraints:
|
|
84
|
-
|
|
85
|
-
- pi-subagents is a minimal core; surviving UI is an **in-core reactive consumer** with no inbound calls into the core.
|
|
86
|
-
The new `agentMessages` accessor is a read-only getter — it adds no inbound call and preserves the Phase 18 spine invariants ([#422]–[#425]).
|
|
87
|
-
- Keep Pi SDK *coupling* out of pure helpers: the pure module uses only Pi's pure utility functions (`serializeConversation`) and an **injected** file/registry, so it stays unit-testable; the overlay component and command handler are SDK/TUI consumers (allowed to import SDK/TUI directly).
|
|
88
|
-
- Use narrow interface types (not the concrete `Subagent` class) at the seam — concrete class types leak private fields into test mocks.
|
|
89
|
-
|
|
90
|
-
## Design Overview
|
|
91
|
-
|
|
92
|
-
Two new modules split pure logic from SDK/TUI wiring, plus a typed accessor on the record.
|
|
93
|
-
|
|
94
|
-
### Typed `agentMessages` accessor (boundary narrowing)
|
|
95
|
-
|
|
96
|
-
`src/types.ts` gains a derived alias (no new dependency):
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
import type { SessionContext } from "@earendil-works/pi-coding-agent";
|
|
100
|
-
|
|
101
|
-
/** One message in a child session's history, typed from Pi's SessionContext. */
|
|
102
|
-
export type SessionMessage = SessionContext["messages"][number];
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
`SubagentSession` and `Subagent` each gain a typed read accessor alongside the existing widened `messages`:
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
// SubagentSession
|
|
109
|
-
get agentMessages(): readonly SessionMessage[] {
|
|
110
|
-
return this._session.messages;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Subagent
|
|
114
|
-
get agentMessages(): readonly SessionMessage[] {
|
|
115
|
-
return this.subagentSession?.agentMessages ?? [];
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
`_session.messages` is already `AgentMessage[]` (= `SessionMessage[]`), so this is a typed view of the same data — no cast through `unknown`.
|
|
120
|
-
The widened `messages` getter stays (other readers depend on it); `agentMessages` is the typed accessor the navigator uses.
|
|
121
|
-
|
|
122
|
-
### `session-navigation.ts` — pure selection, source, and text rendering
|
|
123
|
-
|
|
124
|
-
A pure module (SDK pure-utility imports only; no TUI, no `ExtensionAPI`).
|
|
125
|
-
It declares narrow interfaces for the record fields it reads (ISP — not the concrete `Subagent`).
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
/** The record fields the navigator reads to label and source a transcript. */
|
|
129
|
-
export interface NavigableSubagent {
|
|
130
|
-
readonly id: string;
|
|
131
|
-
readonly type: SubagentType;
|
|
132
|
-
readonly description: string;
|
|
133
|
-
readonly status: SubagentStatus;
|
|
134
|
-
readonly startedAt: number;
|
|
135
|
-
readonly completedAt: number | undefined;
|
|
136
|
-
readonly toolUses: number;
|
|
137
|
-
readonly activeTools: ReadonlyMap<string, string>;
|
|
138
|
-
readonly responseText: string;
|
|
139
|
-
readonly agentMessages: readonly SessionMessage[];
|
|
140
|
-
isSessionReady(): boolean;
|
|
141
|
-
subscribeToUpdates(fn: (event: AgentSessionEvent) => void): (() => void) | undefined;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** A navigable entry: the record plus its display label. */
|
|
145
|
-
export interface NavigationEntry {
|
|
146
|
-
readonly record: NavigableSubagent;
|
|
147
|
-
readonly label: string;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Liveness-agnostic transcript source consumed by the renderer. */
|
|
151
|
-
export interface TranscriptSource {
|
|
152
|
-
/** Current message history. */
|
|
153
|
-
getMessages(): readonly SessionMessage[];
|
|
154
|
-
/** Subscribe to live updates; returns an unsubscribe, or undefined for a static snapshot. */
|
|
155
|
-
subscribe(onChange: () => void): (() => void) | undefined;
|
|
156
|
-
/** Running-agent streaming state, or undefined when not streaming. */
|
|
157
|
-
streaming(): { activeTools: ReadonlyMap<string, string>; responseText: string } | undefined;
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Functions:
|
|
162
|
-
|
|
163
|
-
- `listNavigableAgents(agents, registry): NavigationEntry[]` — keep records with `isSessionReady()`, map each to a label (`getDisplayName` · description · status · `formatDuration`).
|
|
164
|
-
- `liveSource(record: NavigableSubagent): TranscriptSource` — `getMessages` → `record.agentMessages`; `subscribe` → `record.subscribeToUpdates`; `streaming` → `{ activeTools, responseText }` when `record.status === "running"`, else `undefined`.
|
|
165
|
-
- `renderTranscriptLines(source, opts): string[]` — `serializeConversation([...source.getMessages()])` split on `\n`, then append a minimal streaming indicator (active tool names + a truncated `responseText` preview) when `source.streaming()` is defined; return `[]`-safe content for an empty history (`"(no messages yet)"`).
|
|
166
|
-
|
|
167
|
-
`renderTranscriptLines` carries **no bespoke message formatting** — the transcript text is Pi's `serializeConversation` output; only the small streaming indicator is local (a few lines), and it does not import the doomed `message-formatters.ts`.
|
|
168
|
-
|
|
169
|
-
### `session-navigator.ts` — overlay component + command handler
|
|
170
|
-
|
|
171
|
-
SDK/TUI consumer.
|
|
172
|
-
|
|
173
|
-
- `TranscriptOverlay` — a read-only scrollable `Component`: on construct, subscribe via `source.subscribe(() => tui.requestRender())`; each `render(width)` calls `renderTranscriptLines(source)` and paints a scrolling viewport with header/footer chrome and key handling (up/down, pageUp/pageDown, home/end, esc/q to close); `dispose()` unsubscribes.
|
|
174
|
-
Scroll math mirrors the bespoke viewer's behavior but the content comes from `renderTranscriptLines` (the bespoke viewer is deleted in Step 5; this is fresh code with its own tests, not a clone of doomed code).
|
|
175
|
-
- `SessionNavigatorHandler` — `handle({ ui, agents, registry })`:
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
async handle({ ui, agents, registry }: SessionNavigatorParams): Promise<void> {
|
|
179
|
-
const entries = listNavigableAgents(agents, registry);
|
|
180
|
-
if (entries.length === 0) { ui.notify("No subagent sessions to view.", "info"); return; }
|
|
181
|
-
const choice = await ui.select("Subagent sessions", entries.map(e => e.label));
|
|
182
|
-
const entry = entries.find(e => e.label === choice);
|
|
183
|
-
if (!entry) return;
|
|
184
|
-
const source = liveSource(entry.record);
|
|
185
|
-
await ui.custom<undefined>(
|
|
186
|
-
(tui, theme, _kb, done) => new TranscriptOverlay({ tui, theme, source, done, wrapText: wrapTextWithAnsi }),
|
|
187
|
-
{ overlay: true, overlayOptions: { anchor: "center", width: "90%", maxHeight: "70%" } },
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
`SessionNavigatorParams` / `SessionNavigatorUI` are narrow interfaces declaring only `select`, `notify`, and `custom` — the methods the handler uses.
|
|
193
|
-
|
|
194
|
-
### Registration call site (`index.ts`)
|
|
195
|
-
|
|
196
|
-
```typescript
|
|
197
|
-
const sessionNavigator = new SessionNavigatorHandler();
|
|
198
|
-
pi.registerCommand("subagent-sessions", {
|
|
199
|
-
description: "View a subagent's session transcript (read-only)",
|
|
200
|
-
handler: async (_args, ctx) => {
|
|
201
|
-
await sessionNavigator.handle({ ui: ctx.ui, agents: manager.listAgents(), registry });
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
`manager.listAgents()` is called in the handler registration (the navigator never holds a manager reference — it receives the snapshot), keeping the navigator a pure reactive consumer with no inbound core call.
|
|
207
|
-
|
|
208
|
-
### Consumer call-site sketch (Tell-Don't-Ask / LoD check)
|
|
209
|
-
|
|
210
|
-
`liveSource` does not reach through the record to a stranger: it reads the record's own getters (`agentMessages`, `status`, `activeTools`, `responseText`) and delegates subscription to `record.subscribeToUpdates`.
|
|
211
|
-
The renderer talks only to `TranscriptSource`, never to the record — so the component renderer follow-up swaps the source's internals (file vs live) and the renderer (text vs components) independently.
|
|
212
|
-
No output arguments: nothing writes back into the record or the source.
|
|
213
|
-
|
|
214
|
-
### Decomposition and follow-ups (Kent Beck: make the change easy, then make the easy change)
|
|
215
|
-
|
|
216
|
-
This issue is the first releasable vertical slice.
|
|
217
|
-
The `TranscriptSource` seam is the "change made easy" — it decouples *how messages are sourced* from *how they are rendered*, so each follow-up is a localized swap:
|
|
218
|
-
|
|
219
|
-
1. **Renderer upgrade (named follow-up):** replace `renderTranscriptLines`/`TranscriptOverlay`'s `serializeConversation` text path with Pi's per-entry TUI components (`AssistantMessageComponent` / `ToolExecutionComponent` / …) behind the same `TranscriptSource`.
|
|
220
|
-
Selection and sourcing are untouched.
|
|
221
|
-
2. **Evicted-agent source (named follow-up):** broaden the candidate set beyond `listAgents()` and add the **file-snapshot** `TranscriptSource` (`parseSessionEntries(readFileSync(outputFile))` → drop `SessionHeader` → `buildSessionContext(...).messages`) for agents no longer tracked.
|
|
222
|
-
This is where the dual-source design lands its second branch; the renderer is untouched.
|
|
223
|
-
|
|
224
|
-
Recommendation: file these two as Phase 19 follow-up issues at ship time, and update the architecture roadmap's Step 4 description (which currently scopes dual-source + components as one step) to reflect the slice.
|
|
225
|
-
|
|
226
|
-
### Design-review findings
|
|
227
|
-
|
|
228
|
-
| Smell | Location | Evidence | Result |
|
|
229
|
-
| ------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
230
|
-
| Wide interface | `NavigableSubagent` (11 members) | `listNavigableAgents` reads 7 label fields; `liveSource` reads `agentMessages`/streaming/sub | Acceptable — two consumers split the surface; both are real reads, no unused field |
|
|
231
|
-
| LoD violation | `session-navigation.ts` | `liveSource` reads record getters + delegates `subscribeToUpdates`; no `record.x.y.z` chains | None |
|
|
232
|
-
| Output argument | `session-navigation.ts` | source/renderer return values; never write back into the record | None |
|
|
233
|
-
| Concrete-class leak | seam interfaces | navigator depends on `NavigableSubagent`/`TranscriptSource`, not the concrete `Subagent` | None — narrow interfaces |
|
|
234
|
-
|
|
235
|
-
The new collaborators each own behavior (`liveSource` returns a stateful subscription view; `TranscriptOverlay` owns scroll state and the subscription lifecycle; `renderTranscriptLines` returns rendered lines) — not procedure-splitting.
|
|
236
|
-
|
|
237
|
-
### Edge cases
|
|
238
|
-
|
|
239
|
-
- Empty candidate set → `ui.notify("No subagent sessions to view.", "info")`, return.
|
|
240
|
-
- Operator cancels `select` (`undefined`) or the label is stale → return, no overlay.
|
|
241
|
-
- Empty message history (`agentMessages.length === 0`) → render `"(no messages yet)"`.
|
|
242
|
-
- Running agent → streaming indicator appended; a `subscribeToUpdates` event triggers re-render with the latest `agentMessages` + streaming state.
|
|
243
|
-
- Completed tracked agent → `streaming()` returns `undefined` (status not `running`); transcript is the final `agentMessages`, no indicator; `subscribe` still attaches harmlessly.
|
|
244
|
-
- `subscribeToUpdates` returns `undefined` (no live session) → cannot happen for a `listAgents()`-gated, session-ready record, but the overlay tolerates `undefined` (no subscription, static render).
|
|
245
|
-
- Very narrow terminal width → overlay returns `[]` below a minimum width, matching the bespoke viewer.
|
|
246
|
-
|
|
247
|
-
## Module-Level Changes
|
|
248
|
-
|
|
249
|
-
- **New** `src/ui/session-navigation.ts` — `NavigableSubagent`, `NavigationEntry`, `TranscriptSource` interfaces; `listNavigableAgents`, `liveSource`, `renderTranscriptLines` functions.
|
|
250
|
-
- **New** `src/ui/session-navigator.ts` — `TranscriptOverlay` component, `SessionNavigatorHandler` class, `SessionNavigatorParams` / `SessionNavigatorUI` narrow interfaces.
|
|
251
|
-
- **Changed** `src/types.ts` — add `export type SessionMessage = SessionContext["messages"][number]` and the `SessionContext` type import.
|
|
252
|
-
- **Changed** `src/lifecycle/subagent-session.ts` — add `get agentMessages(): readonly SessionMessage[]`.
|
|
253
|
-
- **Changed** `src/lifecycle/subagent.ts` — add `get agentMessages(): readonly SessionMessage[]`.
|
|
254
|
-
- **Changed** `src/index.ts` — import `SessionNavigatorHandler`, construct it, register the `subagent-sessions` command (sole call site of the new export).
|
|
255
|
-
- **New** `test/ui/session-navigation.test.ts` — unit tests for `listNavigableAgents`, `liveSource`, `renderTranscriptLines`.
|
|
256
|
-
- **New** `test/ui/session-navigator.test.ts` — unit tests for `TranscriptOverlay` render/scroll/key handling and `SessionNavigatorHandler` select→custom wiring.
|
|
257
|
-
- **Changed** `test/lifecycle/subagent-session.test.ts`, `test/lifecycle/subagent.test.ts` — add `agentMessages` accessor tests.
|
|
258
|
-
|
|
259
|
-
No removed or renamed exports — grep for removed symbols is not applicable (pure addition).
|
|
260
|
-
|
|
261
|
-
Doc / skill grep results:
|
|
262
|
-
|
|
263
|
-
- `docs/architecture/architecture.md` Step 4 currently describes the **full** dual-source + components scope and names `src/ui/session-navigator.ts` and the typed accessor.
|
|
264
|
-
This issue narrows that scope (live-source + text renderer).
|
|
265
|
-
Update the Step 4 description to reflect the slice and the two named follow-ups — defer the status-line flip and the rescoping edit to ship time (matches the [#447] precedent), or fold a short rescoping note in this issue if preferred.
|
|
266
|
-
- `.pi/skills/package-pi-subagents/SKILL.md` lists the UI domain by count (`ui/` = 10 modules); this adds two files (→ 12).
|
|
267
|
-
The table is a coarse summary not maintained per-file — leave for a later Phase 19 doc-sync unless the operator asks otherwise.
|
|
268
|
-
- No `src/`/`test/` references to a removed symbol (nothing is removed); the bespoke `ConversationViewer` and `message-formatters.ts` are untouched and keep their tests.
|
|
269
|
-
|
|
270
|
-
## Test Impact Analysis
|
|
271
|
-
|
|
272
|
-
1. **New tests enabled.**
|
|
273
|
-
`listNavigableAgents`, `liveSource`, and `renderTranscriptLines` are pure and unit-testable against narrow `NavigableSubagent` / `TranscriptSource` stubs — no full `Subagent`, `TUI`, or `AgentSession` construction.
|
|
274
|
-
This is strictly more focused than `conversation-viewer.test.ts`, which builds a full record + TUI to exercise transcript rendering.
|
|
275
|
-
The `agentMessages` accessor gets direct unit tests on both `SubagentSession` and `Subagent`.
|
|
276
|
-
2. **Existing tests that become redundant.**
|
|
277
|
-
None in this issue.
|
|
278
|
-
`conversation-viewer.test.ts` and `message-formatters.test.ts` still exercise the live, still-shipping bespoke overlay; they are removed only when Step 5 ([#442]) deletes those modules.
|
|
279
|
-
3. **Existing tests that must stay as-is.**
|
|
280
|
-
All bespoke-viewer and menu tests stay unchanged — this issue does not touch `agent-menu.ts`, `conversation-viewer.ts`, or `message-formatters.ts`.
|
|
281
|
-
|
|
282
|
-
## Invariants at risk
|
|
283
|
-
|
|
284
|
-
This issue adds two modules, one accessor, and one command registration; it does not touch the Phase 18 spine that prior steps refactored.
|
|
285
|
-
The relevant `Outcome:` invariants and their pins:
|
|
286
|
-
|
|
287
|
-
- **[#422] — runtime holds zero UI state.**
|
|
288
|
-
The navigator holds its own overlay/scroll state; nothing is pushed onto the runtime.
|
|
289
|
-
Pinned by `runtime.test.ts` / the service spine tests; unchanged.
|
|
290
|
-
- **[#423] — widget/UI is a reactive consumer with no inbound calls from core spawn tools.**
|
|
291
|
-
The navigator reads via the injected `manager.listAgents()` snapshot and record getters only; the new `agentMessages` accessor is a read-only getter.
|
|
292
|
-
No inbound call into the core.
|
|
293
|
-
A focused navigator test asserts the handler reads only through its injected `agents` + record getters.
|
|
294
|
-
- **[#424] — the `subagent` tool depends only on manager/runtime/settings/registry.**
|
|
295
|
-
No tool change.
|
|
296
|
-
- **[#425] — declared event channels equal emitted channels, no vacant hook.**
|
|
297
|
-
The navigator subscribes to existing record updates; it declares/emits no new channel.
|
|
298
|
-
Pinned by the events-contract suite (`test/service`, `test/observation`); unchanged.
|
|
299
|
-
|
|
300
|
-
A later phase step (the renderer / evicted-source follow-ups) must not regress these with a green suite — the navigator-reads-only test pins #423 for the seam.
|
|
301
|
-
|
|
302
|
-
## TDD Order
|
|
303
|
-
|
|
304
|
-
1. **Red→Green: typed `agentMessages` accessor.**
|
|
305
|
-
Test surface: `test/lifecycle/subagent-session.test.ts`, `test/lifecycle/subagent.test.ts`.
|
|
306
|
-
Tests: `SubagentSession.agentMessages` returns the wrapped session's typed messages; `Subagent.agentMessages` delegates to the session and returns `[]` when no session.
|
|
307
|
-
Implementation: add `SessionMessage` to `src/types.ts` (with the `SessionContext` import); add the two getters.
|
|
308
|
-
Run `pnpm run check` (verifies `SessionContext["messages"][number]` resolves and `_session.messages` is assignable).
|
|
309
|
-
Commit: `feat: add typed agentMessages accessor on subagent record (#445)`.
|
|
310
|
-
2. **Red→Green: pure selection, live source, and text rendering.**
|
|
311
|
-
Test surface: `test/ui/session-navigation.test.ts`.
|
|
312
|
-
Tests:
|
|
313
|
-
- `listNavigableAgents` keeps only `isSessionReady()` records and builds the expected labels (name · description · status · duration); empty input → `[]`.
|
|
314
|
-
- `liveSource.getMessages` returns the record's `agentMessages`; `subscribe` delegates to `subscribeToUpdates`; `streaming()` returns state only when `status === "running"`, else `undefined`.
|
|
315
|
-
- `renderTranscriptLines` returns `serializeConversation` output split into lines; appends the streaming indicator when streaming; returns the empty-history placeholder for `[]`.
|
|
316
|
-
Implementation: create `src/ui/session-navigation.ts` with the three interfaces and three functions.
|
|
317
|
-
Verify `AgentMessage`→`Message` assignability for `serializeConversation([...messages])` here; if it fails, add a typed adapter at the boundary.
|
|
318
|
-
Commit: `feat: add subagent session selection and live transcript source (#445)`.
|
|
319
|
-
3. **Red→Green: read-only overlay component + `/subagent-sessions` command.**
|
|
320
|
-
Test surface: `test/ui/session-navigator.test.ts`.
|
|
321
|
-
Tests:
|
|
322
|
-
- `TranscriptOverlay.render(width)` paints the current `renderTranscriptLines` content with chrome; scroll keys move the viewport; `esc`/`q` calls `done`; `dispose()` unsubscribes; a `subscribe` callback triggers `tui.requestRender`.
|
|
323
|
-
- `SessionNavigatorHandler.handle`: empty entries → `notify` + no `custom`; cancel at `select` → no `custom`; a valid pick → `custom` opened with a `liveSource` for the chosen record (assert via a `makeMenuUI`-style stub).
|
|
324
|
-
Implementation: create `src/ui/session-navigator.ts`; register the command in `src/index.ts` (sole call site).
|
|
325
|
-
Run `pnpm run check` immediately (the registration is the only consumer of the new handler export — both must compile together).
|
|
326
|
-
Run `pnpm fallow dead-code` before pushing (confirms no unreachable file-source branch slipped in).
|
|
327
|
-
Commit: `feat: add /subagent-sessions read-only navigation command (#445)`.
|
|
328
|
-
|
|
329
|
-
Steps 2 and 3 may be folded if preferred, but keeping the pure logic (with its stub-only tests) separate from the TUI/SDK wiring keeps each commit self-contained.
|
|
330
|
-
|
|
331
|
-
## Risks and Mitigations
|
|
332
|
-
|
|
333
|
-
- **Risk: `AgentMessage` is not assignable to `serializeConversation`'s `Message[]`.**
|
|
334
|
-
Mitigation: verify in TDD step 2 (`pnpm run check`); Pi feeds `buildSessionContext` output to serialization, so it is expected to hold.
|
|
335
|
-
If not, add a typed adapter (`toMessages(messages): Message[]`) at the renderer boundary — the seam isolates the fix to `renderTranscriptLines`.
|
|
336
|
-
- **Risk: implementing the file-snapshot branch creates dead code (no caller under `listAgents()`-only) and fails `fallow dead-code`.**
|
|
337
|
-
Mitigation: this issue ships the **live source only** behind the seam; the file source is a named follow-up.
|
|
338
|
-
`pnpm fallow dead-code` runs before push.
|
|
339
|
-
- **Risk: importing the doomed `message-formatters.ts` / `conversation-viewer.ts` couples the navigator to code Step 5 deletes.**
|
|
340
|
-
Mitigation: the navigator imports neither; the streaming indicator is a small local helper and the transcript text is Pi's `serializeConversation`.
|
|
341
|
-
- **Risk: the new overlay duplicates the bespoke viewer's scroll chrome (apparent duplication).**
|
|
342
|
-
Mitigation: the bespoke viewer is deleted in Step 5, so there is no lasting duplication; the new overlay is fresh code with its own tests and a different content source (`renderTranscriptLines` vs bespoke `formatMessage`).
|
|
343
|
-
- **Risk: command-name choice.**
|
|
344
|
-
Mitigation: `subagent-sessions` follows the flat-hyphenated sibling convention (`agents`, `subagents-settings`); confirm at ship time (see Open Questions).
|
|
345
|
-
- **Risk: the architecture roadmap's Step 4 description diverges from the sliced scope.**
|
|
346
|
-
Mitigation: update the Step 4 description and file the two follow-ups at ship time.
|
|
347
|
-
|
|
348
|
-
## Open Questions
|
|
349
|
-
|
|
350
|
-
- **Command name** — `subagent-sessions` is proposed (flat, hyphenated, sibling-consistent).
|
|
351
|
-
Alternatives: `subagent-transcript`, `view-subagent`, `subagents-view`.
|
|
352
|
-
Confirm at ship time; the command is not yet shipped, so it is cheaply renamable.
|
|
353
|
-
- **Whether to rescope the architecture roadmap's Step 4 entry in this issue or defer** to a Phase 19 doc-sync — decide at ship time (matches the [#447] precedent).
|
|
354
|
-
- **Whether to file the two named follow-ups (renderer upgrade, evicted-agent source) now or after this slice lands** — recommended at ship time so the roadmap stays accurate.
|
|
355
|
-
|
|
356
|
-
[#441]: https://github.com/gotgenes/pi-packages/issues/441
|
|
357
|
-
[#442]: https://github.com/gotgenes/pi-packages/issues/442
|
|
358
|
-
[#444]: https://github.com/gotgenes/pi-packages/issues/444
|
|
359
|
-
[#445]: https://github.com/gotgenes/pi-packages/issues/445
|
|
360
|
-
[#446]: https://github.com/gotgenes/pi-packages/issues/446
|
|
361
|
-
[#447]: https://github.com/gotgenes/pi-packages/issues/447
|
|
362
|
-
[#422]: https://github.com/gotgenes/pi-packages/issues/422
|
|
363
|
-
[#423]: https://github.com/gotgenes/pi-packages/issues/423
|
|
364
|
-
[#424]: https://github.com/gotgenes/pi-packages/issues/424
|
|
365
|
-
[#425]: https://github.com/gotgenes/pi-packages/issues/425
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
issue: 446
|
|
3
|
-
issue_title: "pi-subagents: spike — resolve ADR-0004 session-navigation entry criteria"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Spike: resolve ADR-0004 session-navigation entry criteria
|
|
7
|
-
|
|
8
|
-
## Release Recommendation
|
|
9
|
-
|
|
10
|
-
**Release:** ship independently
|
|
11
|
-
|
|
12
|
-
Phase 19 Step 1 ([#446]) carries `Release: independent` in the architecture roadmap, and the roadmap's "Release batches" subsection lists it among the independently releasable steps (the only batch is "dissolve-agents" = Steps 5, 6).
|
|
13
|
-
The deliverable is an ADR addendum that unblocks Step 4 ([#445]); it ships on its own with no batch coupling.
|
|
14
|
-
|
|
15
|
-
## Problem Statement
|
|
16
|
-
|
|
17
|
-
ADR-0004 (Phase 18) decided the UI *direction* — replace the bespoke `ConversationViewer` overlay with Pi's own session machinery applied to each child's already-persisted session JSONL — but deliberately left the *mechanism* open behind four "Phase 19 entry criteria."
|
|
18
|
-
Each criterion changes the shape of the Step 4 session-navigation code, so all four must be answered before that work can begin.
|
|
19
|
-
This is a spike: produce a minimal, throwaway investigation that answers each criterion against the real SDK surface, then record the answers as an addendum to `docs/decisions/0004-reconsider-ui-direction.md`.
|
|
20
|
-
No production source files change; the spike closes when the ADR addendum is merged.
|
|
21
|
-
|
|
22
|
-
## Goals
|
|
23
|
-
|
|
24
|
-
- Answer all four ADR-0004 entry criteria with evidence from the bundled `@earendil-works/pi-coding-agent` SDK surface and a real child session JSONL:
|
|
25
|
-
1. Root-continuity during a session switch (and the correct "return to root" gesture).
|
|
26
|
-
2. View-only vs interactive (switch-takeover vs `loadEntriesFromFile` read-only transcript).
|
|
27
|
-
3. Parallel-agent navigation gesture (widget, command, or both).
|
|
28
|
-
4. Settings command name (`/subagents-settings` vs `/agents-settings` vs a `:`-namespaced form).
|
|
29
|
-
- Record the answers as a dated addendum to ADR-0004 so Step 4 ([#445]) can commit to a mechanism.
|
|
30
|
-
- Validate the read-only transcript path empirically: a throwaway vitest harness exercises `loadEntriesFromFile` against a real child session file and confirms entries render without a session switch.
|
|
31
|
-
|
|
32
|
-
## Non-Goals
|
|
33
|
-
|
|
34
|
-
- No production source changes — `session-navigator.ts`, the widget background-only filter, the settings command, and the `/agents` dissolution are all later Phase 19 steps ([#445], [#444], [#447], [#442], [#441]).
|
|
35
|
-
- Do not implement the chosen mechanism; the spike only chooses it.
|
|
36
|
-
- Do not commit the vitest spike harness — it is throwaway evidence, discarded after observation (operator decision: ADR addendum only).
|
|
37
|
-
- Do not modify `0004-reconsider-ui-direction.md`'s existing Status/Decision/Consequences body beyond appending the addendum.
|
|
38
|
-
|
|
39
|
-
## Background
|
|
40
|
-
|
|
41
|
-
Relevant SDK surface, verified against the bundled `@earendil-works/pi-coding-agent` types in `packages/pi-subagents/node_modules/.../dist`:
|
|
42
|
-
|
|
43
|
-
- `ExtensionCommandContext.switchSession(sessionPath, { withSession })` — a **full active-session takeover** (`core/extensions/types.d.ts:276`).
|
|
44
|
-
It returns `{ cancelled }` and hands the replacement session to an optional `withSession(ctx: ReplacedSessionContext)` callback.
|
|
45
|
-
- `ReplacedSessionContext extends ExtensionCommandContext` (`types.d.ts:289`) — exposes `sendUserMessage` and `sendMessage`, so a switched-to child session is **interactive**, not read-only.
|
|
46
|
-
- `session_before_switch` (cancellable; `reason: "new" | "resume"`) and `session_shutdown` (`reason: "quit" | "reload" | "new" | "resume" | "fork"`, with `targetSessionFile`) events (`types.d.ts:414`, `:440`) fire around a switch — the current runtime is torn down when the active session is replaced.
|
|
47
|
-
- `setBeforeSessionInvalidate` lives on the **host** runtime (`core/agent-session-runtime.*`, `modes/interactive/interactive-mode.*`), not on the extension command context — it is a host-owned UI-teardown seam, not something this extension calls.
|
|
48
|
-
- `loadEntriesFromFile(filePath): FileEntry[]` and `parseSessionEntries(content): FileEntry[]` (`core/session-manager.d.ts:151`, `:141`, both re-exported from the package root `index.d.ts`) — read a session file's entries **without** switching.
|
|
49
|
-
- `Subagent.outputFile: string | undefined` (`src/lifecycle/subagent.ts:120`, delegating to `subagentSession?.outputFile`) — already exposes each child's persisted session JSONL path; no new SDK dependency is needed.
|
|
50
|
-
- Sibling command registration uses **flat, hyphenated** names with no `:` namespace: `registerCommand("agents", …)` (this package), `registerCommand("colgrep-reindex", …)`, `registerCommand("permission-system", …)`.
|
|
51
|
-
|
|
52
|
-
AGENTS.md constraint: pi-subagents is a minimal core; the surviving UI is an in-core reactive consumer (ADR-0004 Decision D).
|
|
53
|
-
The spike chooses the navigation mechanism but adds no policy and no new core dependency.
|
|
54
|
-
|
|
55
|
-
## Design Overview
|
|
56
|
-
|
|
57
|
-
The spike is an investigation, not a feature.
|
|
58
|
-
Each criterion is answered from the SDK surface above; the one path with genuine runtime uncertainty (read-only transcript rendering) is confirmed with a throwaway vitest harness against a real child session JSONL.
|
|
59
|
-
|
|
60
|
-
### Criterion 1 — Root-continuity during a session switch
|
|
61
|
-
|
|
62
|
-
`switchSession` replaces the active session: it fires `session_before_switch` (cancellable) then tears the current runtime down via `session_shutdown` (the `targetSessionFile` field marks a replacement-driven shutdown).
|
|
63
|
-
The root's in-flight turn does **not** survive a takeover — the runtime that owns that turn is invalidated.
|
|
64
|
-
The "return to root" gesture under a true round-trip would be a second `switchSession(rootSessionFile)`, re-incurring the same teardown/replay cost on the way back.
|
|
65
|
-
|
|
66
|
-
Expected finding: a true `switchSession` round-trip is hostile to a root that may have a turn in flight (background agents run precisely while the operator keeps working at root).
|
|
67
|
-
This pushes the recommendation toward the read-only `loadEntriesFromFile` path, which never touches the active session and so has no return gesture to get wrong.
|
|
68
|
-
|
|
69
|
-
### Criterion 2 — View-only vs interactive
|
|
70
|
-
|
|
71
|
-
`ReplacedSessionContext` exposes `sendUserMessage`/`sendMessage`, so switching makes the child interactive — the operator could steer a child from inside its own session.
|
|
72
|
-
ADR-0004 frames operator visibility (concern 3) as "switch in, scroll/read, switch between, exit back to root" — a **navigation** interaction, not a live steering overlay.
|
|
73
|
-
Steering already has a home (`steer_subagent` tool / the widget), so conflating read-navigation with in-session steering adds a second, redundant steering surface.
|
|
74
|
-
|
|
75
|
-
Expected finding: the viewer should be **read-only**, favoring `loadEntriesFromFile` to render the child's persisted entries without leaving the root session.
|
|
76
|
-
This also resolves Criterion 1 by construction (no switch, no root-continuity problem).
|
|
77
|
-
|
|
78
|
-
### Criterion 3 — Parallel-agent navigation
|
|
79
|
-
|
|
80
|
-
With N background agents running, the operator needs a gesture to pick which child to view.
|
|
81
|
-
The background widget (ADR-0004 Decision A, [#444]) already represents N parallel agents as a per-agent tree, making it the natural **selection** surface.
|
|
82
|
-
A flat command (Criterion 4 naming) gives a non-widget entry point that lists running background agents and lets the operator pick one keyed on `record.outputFile`.
|
|
83
|
-
|
|
84
|
-
Expected finding: provide a **command** as the primary, testable selection surface (lists background agents → operator picks → render that child's transcript read-only), with a widget gesture as an optional later enhancement.
|
|
85
|
-
The command is unit-testable and does not depend on the widget landing first; "both" is the eventual target, command-first is the spike's recommended Step 4 starting point.
|
|
86
|
-
|
|
87
|
-
### Criterion 4 — Settings command name
|
|
88
|
-
|
|
89
|
-
Sibling packages register flat, hyphenated command names with no `:` namespace (`agents`, `colgrep-reindex`, `permission-system`).
|
|
90
|
-
A `/subagents:settings` form would be inconsistent with every existing command in the repo.
|
|
91
|
-
|
|
92
|
-
Expected finding: confirm **`/subagents-settings`** (flat, hyphenated) — already the name used in the architecture roadmap's Step 2 ([#447]).
|
|
93
|
-
Reject the ADR's tentative `/subagents:settings` and the `/agents-settings` alternative (the latter implies it manages agent definitions, which Decision C removes).
|
|
94
|
-
|
|
95
|
-
### Throwaway vitest harness (discarded, not committed)
|
|
96
|
-
|
|
97
|
-
A single throwaway spec confirms the read-only path end to end:
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
// throwaway — discarded after observation, never committed
|
|
101
|
-
import { loadEntriesFromFile } from "@earendil-works/pi-coding-agent";
|
|
102
|
-
|
|
103
|
-
// against a REAL child session JSONL produced by a background subagent run
|
|
104
|
-
const entries = loadEntriesFromFile(childOutputFile);
|
|
105
|
-
// assert: returns FileEntry[] with the expected message/turn entries,
|
|
106
|
-
// renderable as a transcript, with no switchSession / no active-session mutation.
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
The harness sources `childOutputFile` from a real run (or an existing session fixture under the Pi session dir), not a synthetic stub, so the observation reflects the actual on-disk JSONL shape the Step 4 viewer will consume.
|
|
110
|
-
It asserts the entries are well-formed and that the read path requires no session switch — confirming `loadEntriesFromFile(Subagent.outputFile)` is a viable read-only transcript source.
|
|
111
|
-
|
|
112
|
-
## Module-Level Changes
|
|
113
|
-
|
|
114
|
-
- `packages/pi-subagents/docs/decisions/0004-reconsider-ui-direction.md` — **append** an addendum section (e.g. `## Addendum (2026-06-..): Phase 19 entry-criteria answers`) recording the four answers with their SDK evidence and the resulting Step 4 mechanism decision (read-only `loadEntriesFromFile` transcript, command-first selection, `/subagents-settings` name).
|
|
115
|
-
Do not rewrite the existing "Phase 19 entry criteria" section — leave it as the question of record and let the addendum answer it.
|
|
116
|
-
- `packages/pi-subagents/docs/architecture/architecture.md` — optional doc-sync only: the Step 1 entry already states its `Outcome`; if marking the spike resolved here is desired, update only that step's status line.
|
|
117
|
-
Keep out of scope unless the operator wants it folded in at ship time.
|
|
118
|
-
|
|
119
|
-
No `src/` or committed `test/` files change.
|
|
120
|
-
The vitest spike harness is throwaway and is **not** added to the committed tree (operator decision: ADR addendum only).
|
|
121
|
-
|
|
122
|
-
## Test Impact Analysis
|
|
123
|
-
|
|
124
|
-
Not applicable in the usual sense — this spike commits no production code and no retained tests.
|
|
125
|
-
The throwaway vitest harness exists only to observe `loadEntriesFromFile` behavior against a real child session JSONL and is discarded; it enables no new committed test surface and makes no existing test redundant.
|
|
126
|
-
Step 4 ([#445]) will introduce the committed tests for the chosen mechanism.
|
|
127
|
-
|
|
128
|
-
## Invariants at risk
|
|
129
|
-
|
|
130
|
-
None.
|
|
131
|
-
The spike changes no runtime code, so the Phase 18 spine invariants ADR-0004 lists (runtime holds zero UI state [#422]; widget is a reactive consumer with no inbound core calls [#423]; the `subagent` tool depends only on manager/runtime/settings/registry [#424]; declared event channels equal emitted channels [#425]) are untouched and stay pinned by their existing suites.
|
|
132
|
-
The addendum must not recommend a Step 4 mechanism that would later violate them — the read-only `loadEntriesFromFile` path is chosen partly because it keeps transcript rendering out of core and adds no inbound call to the core from the UI.
|
|
133
|
-
|
|
134
|
-
## Build Order
|
|
135
|
-
|
|
136
|
-
This is a docs/spike deliverable (next stage: `/build-plan`), so the order is investigate → confirm → write → discard, not red→green→commit.
|
|
137
|
-
|
|
138
|
-
1. **Investigate the SDK surface and confirm the read path.**
|
|
139
|
-
Re-verify the `switchSession`/`ReplacedSessionContext`/event semantics and `loadEntriesFromFile` signatures in the bundled SDK types, then run the throwaway vitest harness against a real child session JSONL to confirm read-only transcript rendering is viable.
|
|
140
|
-
No commit (throwaway harness is discarded).
|
|
141
|
-
2. **Write the ADR-0004 addendum.**
|
|
142
|
-
Append the dated addendum answering all four criteria with their SDK evidence and the resulting Step 4 mechanism decision.
|
|
143
|
-
Verify: addendum present, four criteria each answered with a recommendation, markdown lints clean (`pnpm --filter @gotgenes/pi-subagents run lint` or repo `pnpm run lint`).
|
|
144
|
-
Commit: `docs: resolve ADR-0004 session-navigation entry criteria (#446)`.
|
|
145
|
-
3. **Optional doc-sync.**
|
|
146
|
-
If folding in the architecture status update, amend only Step 1's status line in `architecture.md` in the same or a follow-up `docs:` commit; otherwise leave it for ship time.
|
|
147
|
-
|
|
148
|
-
## Risks and Mitigations
|
|
149
|
-
|
|
150
|
-
- **Risk: root-continuity cannot be fully proven without a live multi-session run, and the chosen method is an automated harness only.**
|
|
151
|
-
Mitigation: the answer is derivable from the documented event semantics (a switch tears down the active runtime via `session_shutdown`), which is exactly why the recommendation avoids `switchSession` for navigation; the harness confirms the read-only alternative works, sidestepping the unproven path.
|
|
152
|
-
- **Risk: the addendum recommends a mechanism that Step 4 cannot honor (e.g. a TUI rendering limitation in `loadEntriesFromFile`).**
|
|
153
|
-
Mitigation: the throwaway harness exercises the real entries shape before the recommendation is written; Step 4 retains the freedom to revisit if a rendering gap surfaces, since the ADR records direction, not an irreversible commitment.
|
|
154
|
-
- **Risk: scope creep into implementing the chosen surface.**
|
|
155
|
-
Mitigation: Non-Goals explicitly defer all production changes to [#445]/[#444]/[#447]/[#442]/[#441]; the only committed file is the ADR addendum.
|
|
156
|
-
|
|
157
|
-
## Open Questions
|
|
158
|
-
|
|
159
|
-
- Whether to also land the `architecture.md` Step 1 status update in this issue or defer it — decide at ship time (Build Order step 3).
|
|
160
|
-
- The widget gesture for parallel-agent navigation (Criterion 3 "both") is left to Step 4 — the spike recommends command-first and notes the widget gesture as an optional follow-up.
|
|
161
|
-
|
|
162
|
-
[#422]: https://github.com/gotgenes/pi-packages/issues/422
|
|
163
|
-
[#423]: https://github.com/gotgenes/pi-packages/issues/423
|
|
164
|
-
[#424]: https://github.com/gotgenes/pi-packages/issues/424
|
|
165
|
-
[#425]: https://github.com/gotgenes/pi-packages/issues/425
|
|
166
|
-
[#441]: https://github.com/gotgenes/pi-packages/issues/441
|
|
167
|
-
[#442]: https://github.com/gotgenes/pi-packages/issues/442
|
|
168
|
-
[#444]: https://github.com/gotgenes/pi-packages/issues/444
|
|
169
|
-
[#445]: https://github.com/gotgenes/pi-packages/issues/445
|
|
170
|
-
[#446]: https://github.com/gotgenes/pi-packages/issues/446
|
|
171
|
-
[#447]: https://github.com/gotgenes/pi-packages/issues/447
|