@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,454 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
issue: 100
|
|
3
|
-
issue_title: "Replace callback threading with direct session-event subscription"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Replace callback threading with direct session-event subscription
|
|
7
|
-
|
|
8
|
-
## Problem Statement
|
|
9
|
-
|
|
10
|
-
`SpawnOptions` carries 6 `on*` callback fields that thread through three layers: `agent-tool.ts` creates activity-tracking callbacks → `AgentManager.startAgent()` wraps each to update the record, then forwards → `runner.run()` subscribes to session events and translates them into callback invocations.
|
|
11
|
-
The session already emits all of these events via `session.subscribe()`.
|
|
12
|
-
Three layers reimplement what two independent subscriptions could provide.
|
|
13
|
-
|
|
14
|
-
The same pattern repeats in `resume()`, which hand-rolls 3 of the 4 callback wrappers.
|
|
15
|
-
|
|
16
|
-
## Goals
|
|
17
|
-
|
|
18
|
-
- Replace the 3-layer callback chain with two direct session subscribers: a **record observer** (AgentManager, updates record stats) and a **UI observer** (agent-tool, streams widget state).
|
|
19
|
-
- Drop 5 `on*` callback fields from `SpawnOptions` (keep `onSessionCreated` as the session-delivery mechanism).
|
|
20
|
-
- Drop 5 `on*` callback fields from `RunOptions`.
|
|
21
|
-
- Drop 3 `on*` callback fields from `ResumeOptions`.
|
|
22
|
-
- Simplify `startAgent()` from ~80 lines of callback wiring to ~20 lines of observer setup.
|
|
23
|
-
- Eliminate duplicated callback wiring between `startAgent()` and `resume()`.
|
|
24
|
-
|
|
25
|
-
## Non-Goals
|
|
26
|
-
|
|
27
|
-
- Extracting turn-limit enforcement from the runner — it stays as the runner's own subscription, just simplified.
|
|
28
|
-
A follow-up could extract it into a `turn-limiter.ts` module.
|
|
29
|
-
- Changing the public `SubagentsService` API in `service.ts` — the public `SpawnOptions` type is unaffected.
|
|
30
|
-
- Removing `onSessionCreated` — it remains as the session-delivery bridge between the runner and external subscribers.
|
|
31
|
-
- Changing `collectResponseText` or `forwardAbortSignal` in the runner — those are the runner's own concerns.
|
|
32
|
-
|
|
33
|
-
## Background
|
|
34
|
-
|
|
35
|
-
### Prerequisite status
|
|
36
|
-
|
|
37
|
-
- Issue #98 (AgentRecord state machine) — **done**.
|
|
38
|
-
`AgentRecord` is a class with encapsulated transition methods (`markRunning`, `markCompleted`, `markError`, etc.).
|
|
39
|
-
The record observer uses public mutable fields (`toolUses`, `lifetimeUsage`, `compactionCount`) and the `addUsage` helper.
|
|
40
|
-
- Issue #99 (ParentSnapshot) — **done**.
|
|
41
|
-
`runner.run()` accepts `ParentSnapshot`, `SpawnArgs` stores a snapshot, `AgentManager` has `exec: ShellExec` injected.
|
|
42
|
-
|
|
43
|
-
### Current callback threading
|
|
44
|
-
|
|
45
|
-
In `startAgent()`, AgentManager wraps 4 of the 6 callbacks to interleave record mutations:
|
|
46
|
-
|
|
47
|
-
| Callback | Record mutation | Forwarding |
|
|
48
|
-
| ------------------ | ------------------------------------------------------------- | ------------------------------------- |
|
|
49
|
-
| `onToolActivity` | `record.toolUses++` on end | Forward to `options.onToolActivity` |
|
|
50
|
-
| `onAssistantUsage` | `addUsage(record.lifetimeUsage, …)` | Forward to `options.onAssistantUsage` |
|
|
51
|
-
| `onCompaction` | `record.compactionCount++` + `this.onCompact?.(record, info)` | Forward to `options.onCompaction` |
|
|
52
|
-
| `onSessionCreated` | Store session, capture file, flush steers | Forward to `options.onSessionCreated` |
|
|
53
|
-
| `onTextDelta` | None | Pass-through |
|
|
54
|
-
| `onTurnEnd` | None | Pass-through |
|
|
55
|
-
|
|
56
|
-
In `resume()`, 3 callbacks are wired manually (onToolActivity, onAssistantUsage, onCompaction) with no forwarding — only record mutations.
|
|
57
|
-
|
|
58
|
-
The runner's big `session.subscribe()` block (lines 323–370 of `agent-runner.ts`) mixes turn-limit enforcement with event→callback translation for all 6 callback types.
|
|
59
|
-
|
|
60
|
-
### Code-design constraints
|
|
61
|
-
|
|
62
|
-
- **Parameter relay** (code-design skill): the 5 UI callbacks thread through `SpawnOptions` → `startAgent()` wrapping → `RunOptions` → runner subscription.
|
|
63
|
-
The intermediary (`startAgent`) only uses them to interleave record mutations.
|
|
64
|
-
- **Scattered resets** (design-review): `resume()` hand-rolls 3 of the same 4 callback wrappers from `startAgent()`.
|
|
65
|
-
|
|
66
|
-
### Relevant modules
|
|
67
|
-
|
|
68
|
-
| Module | Role in this change |
|
|
69
|
-
| ------------------------- | -------------------------------------------------------------------- |
|
|
70
|
-
| `src/agent-manager.ts` | Owns `SpawnOptions`, `startAgent()`, `resume()` — primary target |
|
|
71
|
-
| `src/agent-runner.ts` | Owns `RunOptions`, `ResumeOptions`, runner subscription — simplifies |
|
|
72
|
-
| `src/tools/agent-tool.ts` | Owns `createActivityTracker()` — replaced by UI observer |
|
|
73
|
-
| `src/ui/agent-widget.ts` | Defines `AgentActivity` interface — unchanged |
|
|
74
|
-
| `src/agent-record.ts` | `AgentRecord` class — unchanged (observer writes to public fields) |
|
|
75
|
-
| `src/usage.ts` | `addUsage` helper — unchanged, used by record observer |
|
|
76
|
-
|
|
77
|
-
## Design Overview
|
|
78
|
-
|
|
79
|
-
### Two independent observers
|
|
80
|
-
|
|
81
|
-
```text
|
|
82
|
-
session.subscribe()
|
|
83
|
-
│
|
|
84
|
-
┌─────────────┼─────────────┐
|
|
85
|
-
│ │
|
|
86
|
-
Record observer UI observer
|
|
87
|
-
(accumulates stats on record) (updates widget state)
|
|
88
|
-
managed by AgentManager managed by agent-tool
|
|
89
|
-
subscribes in onSessionCreated subscribes in onSessionCreated
|
|
90
|
-
unsubscribes in .then/.catch unsubscribes on completion
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Both subscribe to the same session but update independent state.
|
|
94
|
-
Neither wraps or forwards the other's events.
|
|
95
|
-
|
|
96
|
-
### Record observer
|
|
97
|
-
|
|
98
|
-
New module `src/record-observer.ts`:
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
export interface RecordObserverOptions {
|
|
102
|
-
onCompact?: (record: AgentRecord, info: CompactionInfo) => void;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function subscribeRecordObserver(
|
|
106
|
-
session: AgentSession,
|
|
107
|
-
record: AgentRecord,
|
|
108
|
-
options?: RecordObserverOptions,
|
|
109
|
-
): () => void;
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Handles three event types:
|
|
113
|
-
|
|
114
|
-
| Session event | Record mutation |
|
|
115
|
-
| ------------------------------------------- | ---------------------------------------------------- |
|
|
116
|
-
| `tool_execution_end` | `record.toolUses++` |
|
|
117
|
-
| `message_end` (assistant, with usage) | `addUsage(record.lifetimeUsage, usage)` |
|
|
118
|
-
| `compaction_end` (not aborted, with result) | `record.compactionCount++`, call `options.onCompact` |
|
|
119
|
-
|
|
120
|
-
These are the exact mutations currently scattered across `startAgent()` and `resume()` callback wrappers.
|
|
121
|
-
|
|
122
|
-
The returned function unsubscribes from the session.
|
|
123
|
-
|
|
124
|
-
### UI observer
|
|
125
|
-
|
|
126
|
-
New module `src/ui/ui-observer.ts`:
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
export function subscribeUIObserver(
|
|
130
|
-
session: AgentSession,
|
|
131
|
-
state: AgentActivity,
|
|
132
|
-
onUpdate?: () => void,
|
|
133
|
-
): () => void;
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Handles six event types — the same events currently translated by `createActivityTracker`'s callbacks:
|
|
137
|
-
|
|
138
|
-
| Session event | State mutation |
|
|
139
|
-
| ------------------------------------- | --------------------------------------------------- |
|
|
140
|
-
| `tool_execution_start` | Add to `state.activeTools` |
|
|
141
|
-
| `tool_execution_end` | Remove from `state.activeTools`, `state.toolUses++` |
|
|
142
|
-
| `message_start` | Reset `state.responseText` |
|
|
143
|
-
| `message_update` (text_delta) | Append to `state.responseText` |
|
|
144
|
-
| `turn_end` | `state.turnCount++` |
|
|
145
|
-
| `message_end` (assistant, with usage) | `addUsage(state.lifetimeUsage, usage)` |
|
|
146
|
-
|
|
147
|
-
Calls `onUpdate?.()` after each mutation (matching current `onStreamUpdate` behavior for foreground rendering).
|
|
148
|
-
|
|
149
|
-
The returned function unsubscribes from the session.
|
|
150
|
-
|
|
151
|
-
### SpawnOptions after the change
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
export interface SpawnOptions {
|
|
155
|
-
description: string;
|
|
156
|
-
model?: Model<any>;
|
|
157
|
-
maxTurns?: number;
|
|
158
|
-
isolated?: boolean;
|
|
159
|
-
inheritContext?: boolean;
|
|
160
|
-
thinkingLevel?: ThinkingLevel;
|
|
161
|
-
isBackground?: boolean;
|
|
162
|
-
bypassQueue?: boolean;
|
|
163
|
-
isolation?: IsolationMode;
|
|
164
|
-
invocation?: AgentInvocation;
|
|
165
|
-
signal?: AbortSignal;
|
|
166
|
-
parentSessionFile?: string;
|
|
167
|
-
parentSessionId?: string;
|
|
168
|
-
/** Called when the session is created — the one remaining callback. */
|
|
169
|
-
onSessionCreated?: (session: AgentSession) => void;
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
Drops: `onToolActivity`, `onTextDelta`, `onTurnEnd`, `onAssistantUsage`, `onCompaction`.
|
|
174
|
-
|
|
175
|
-
### RunOptions after the change
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
export interface RunOptions {
|
|
179
|
-
exec: ShellExec;
|
|
180
|
-
model?: Model<any>;
|
|
181
|
-
maxTurns?: number;
|
|
182
|
-
signal?: AbortSignal;
|
|
183
|
-
isolated?: boolean;
|
|
184
|
-
thinkingLevel?: ThinkingLevel;
|
|
185
|
-
cwd?: string;
|
|
186
|
-
parentSessionFile?: string;
|
|
187
|
-
parentSessionId?: string;
|
|
188
|
-
defaultMaxTurns?: number;
|
|
189
|
-
graceTurns?: number;
|
|
190
|
-
/** Called once after session creation — session delivery mechanism. */
|
|
191
|
-
onSessionCreated?: (session: AgentSession) => void;
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
Drops: `onToolActivity`, `onTextDelta`, `onTurnEnd`, `onAssistantUsage`, `onCompaction`.
|
|
196
|
-
|
|
197
|
-
### ResumeOptions after the change
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
export interface ResumeOptions {
|
|
201
|
-
signal?: AbortSignal;
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Drops: `onToolActivity`, `onAssistantUsage`, `onCompaction`.
|
|
206
|
-
|
|
207
|
-
### Runner simplification
|
|
208
|
-
|
|
209
|
-
The runner's big `session.subscribe()` block in `runAgent()` (currently ~50 lines mixing turn-limit enforcement with callback forwarding) simplifies to turn-limit enforcement only (~15 lines):
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
const unsubTurns = session.subscribe((event) => {
|
|
213
|
-
if (event.type === "turn_end") {
|
|
214
|
-
turnCount++;
|
|
215
|
-
if (maxTurns != null) {
|
|
216
|
-
if (!softLimitReached && turnCount >= maxTurns) {
|
|
217
|
-
softLimitReached = true;
|
|
218
|
-
session.steer("...");
|
|
219
|
-
} else if (softLimitReached && turnCount >= maxTurns + grace) {
|
|
220
|
-
aborted = true;
|
|
221
|
-
session.abort();
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
`collectResponseText(session)` and `forwardAbortSignal(session, signal)` remain unchanged — they are the runner's own concerns.
|
|
229
|
-
|
|
230
|
-
`resumeAgent()` drops its conditional event subscription entirely — only `collectResponseText` and `forwardAbortSignal` remain.
|
|
231
|
-
|
|
232
|
-
### Unsubscription strategy
|
|
233
|
-
|
|
234
|
-
| Observer | Subscribe point | Unsubscribe point |
|
|
235
|
-
| ------------------- | -------------------------- | ---------------------------- |
|
|
236
|
-
| Record (startAgent) | `onSessionCreated` handler | `.then()` and `.catch()` |
|
|
237
|
-
| Record (resume) | Before `runner.resume()` | `finally` block |
|
|
238
|
-
| UI (foreground) | `onSessionCreated` handler | After `spawnAndWait` returns |
|
|
239
|
-
| UI (background) | `onSessionCreated` handler | Session disposal on cleanup |
|
|
240
|
-
|
|
241
|
-
### createActivityTracker replacement
|
|
242
|
-
|
|
243
|
-
`createActivityTracker()` in `agent-tool.ts` is replaced by:
|
|
244
|
-
|
|
245
|
-
1. Inline `AgentActivity` state construction (the state object is simple enough).
|
|
246
|
-
2. In the `onSessionCreated` callback: `subscribeUIObserver(session, state, onStreamUpdate)`.
|
|
247
|
-
|
|
248
|
-
The `callbacks` spread pattern (`...bgCallbacks` / `...fgCallbacks`) disappears entirely.
|
|
249
|
-
|
|
250
|
-
## Module-Level Changes
|
|
251
|
-
|
|
252
|
-
### New files
|
|
253
|
-
|
|
254
|
-
1. `src/record-observer.ts` — `subscribeRecordObserver()` function.
|
|
255
|
-
2. `test/record-observer.test.ts` — unit tests with mock session.
|
|
256
|
-
3. `src/ui/ui-observer.ts` — `subscribeUIObserver()` function.
|
|
257
|
-
4. `test/ui/ui-observer.test.ts` — unit tests with mock session.
|
|
258
|
-
|
|
259
|
-
### Changed files (source)
|
|
260
|
-
|
|
261
|
-
1. `src/agent-manager.ts` — Wire record observer in `onSessionCreated`; simplify callback wrappers to pass-through (cycle 3); drop 5 `on*` fields from `SpawnOptions` (cycle 4).
|
|
262
|
-
2. `src/tools/agent-tool.ts` — Replace `createActivityTracker` with inline state + `subscribeUIObserver`; drop callback spread from spawn calls (cycle 4).
|
|
263
|
-
3. `src/agent-runner.ts` — Drop 5 `on*` fields from `RunOptions`; drop 3 `on*` fields from `ResumeOptions`; simplify `runAgent()` subscription to turn-limit only; remove conditional subscription from `resumeAgent()` (cycle 5).
|
|
264
|
-
|
|
265
|
-
### Changed files (tests)
|
|
266
|
-
|
|
267
|
-
1. `test/agent-manager.test.ts` — Upgrade `mockSession()` to support `subscribe()`; update 3 stat-verification tests to emit events through mock session instead of calling callbacks on `RunOptions` (cycle 3).
|
|
268
|
-
2. `test/agent-runner.test.ts` — Drop tests for callback forwarding (`onAssistantUsage` wiring, `onCompaction` forwarding); runner tests focus on turn-limit enforcement and session creation (cycle 5).
|
|
269
|
-
3. `test/agent-runner-extension-tools.test.ts` — Drop callback-related fields from `RunOptions` mock construction (cycle 5).
|
|
270
|
-
|
|
271
|
-
### Unchanged files
|
|
272
|
-
|
|
273
|
-
- `src/service.ts` — public API unchanged (its `SpawnOptions` is a separate type).
|
|
274
|
-
- `src/agent-record.ts` — public mutable fields used by observer, no changes needed.
|
|
275
|
-
- `src/usage.ts` — `addUsage` helper used by observer, unchanged.
|
|
276
|
-
- `src/ui/agent-widget.ts` — `AgentActivity` interface unchanged; widget reads state as before.
|
|
277
|
-
- `src/session-config.ts`, `src/context.ts`, `src/env.ts` — unrelated.
|
|
278
|
-
- `src/service-adapter.ts` — `AgentManagerLike.spawn` signature unchanged (onSessionCreated is already optional on SpawnOptions).
|
|
279
|
-
- `test/tools/agent-tool.test.ts` — tests use mocked `deps.manager` and don't exercise callback wiring; spawn mock shape doesn't change.
|
|
280
|
-
- `test/service-adapter.test.ts` — tests mock `AgentManagerLike`, unaffected by internal SpawnOptions changes.
|
|
281
|
-
|
|
282
|
-
## Test Impact Analysis
|
|
283
|
-
|
|
284
|
-
### New tests enabled by the extraction
|
|
285
|
-
|
|
286
|
-
1. `subscribeRecordObserver` tested in isolation with a mock session and real `AgentRecord`.
|
|
287
|
-
Previously impossible — record stat updates were interleaved with callback wrapping inside `startAgent()`.
|
|
288
|
-
2. `subscribeUIObserver` tested in isolation with a mock session and `AgentActivity` state object.
|
|
289
|
-
Previously impossible — UI state updates were buried in `createActivityTracker` closures that required a full spawn flow to exercise.
|
|
290
|
-
|
|
291
|
-
### Existing tests that simplify
|
|
292
|
-
|
|
293
|
-
1. `agent-manager.test.ts` stat tests — currently simulate callbacks by having the mock runner call `opts.onAssistantUsage?.(...)`.
|
|
294
|
-
After the change, mock runners call `opts.onSessionCreated?.(session)` and tests emit events through the mock session.
|
|
295
|
-
The pattern is more realistic (events drive state, not callbacks).
|
|
296
|
-
2. `agent-runner.test.ts` — callback forwarding tests (`onAssistantUsage wiring`, `onCompaction forwarding`) become unnecessary since the runner no longer translates events to callbacks.
|
|
297
|
-
Turn-limit tests remain and simplify (no callback interleaving).
|
|
298
|
-
|
|
299
|
-
### Existing tests that stay as-is
|
|
300
|
-
|
|
301
|
-
1. All `agent-manager.test.ts` lifecycle tests (spawn, abort, queue drain, worktree, resume flow) — they verify AgentManager behavior and don't depend on callback wiring.
|
|
302
|
-
Only the mock construction (`mockSession`) gains a `subscribe` method.
|
|
303
|
-
2. `agent-record.test.ts` — state-machine transitions are unrelated.
|
|
304
|
-
3. `agent-runner-extension-tools.test.ts` — tool filtering tests are unrelated to callbacks.
|
|
305
|
-
4. `test/tools/agent-tool.test.ts` — tests mock the manager; spawn/spawnAndWait call shapes don't change from the test's perspective.
|
|
306
|
-
|
|
307
|
-
## TDD Order
|
|
308
|
-
|
|
309
|
-
### Phase 1: Extract observers (additive, no breaking changes)
|
|
310
|
-
|
|
311
|
-
#### Cycle 1: Record observer module
|
|
312
|
-
|
|
313
|
-
Test surface: `test/record-observer.test.ts` (new).
|
|
314
|
-
|
|
315
|
-
Tests:
|
|
316
|
-
|
|
317
|
-
- `tool_execution_end` event increments `record.toolUses`.
|
|
318
|
-
- `message_end` (assistant, with usage) accumulates into `record.lifetimeUsage`.
|
|
319
|
-
- `compaction_end` (not aborted) increments `record.compactionCount` and calls `onCompact`.
|
|
320
|
-
- `compaction_end` with `aborted: true` is ignored.
|
|
321
|
-
- Returned function unsubscribes from session.
|
|
322
|
-
|
|
323
|
-
Source changes:
|
|
324
|
-
|
|
325
|
-
- `src/record-observer.ts`: `subscribeRecordObserver()` function.
|
|
326
|
-
|
|
327
|
-
Commit: `feat: add record observer for direct session subscription (#100)`
|
|
328
|
-
|
|
329
|
-
#### Cycle 2: UI observer module
|
|
330
|
-
|
|
331
|
-
Test surface: `test/ui/ui-observer.test.ts` (new).
|
|
332
|
-
|
|
333
|
-
Tests:
|
|
334
|
-
|
|
335
|
-
- `tool_execution_start` adds to `state.activeTools`, calls `onUpdate`.
|
|
336
|
-
- `tool_execution_end` removes from `state.activeTools`, increments `state.toolUses`, calls `onUpdate`.
|
|
337
|
-
- `message_start` resets `state.responseText`.
|
|
338
|
-
- `message_update` (text_delta) appends to `state.responseText`, calls `onUpdate`.
|
|
339
|
-
- `turn_end` increments `state.turnCount`, calls `onUpdate`.
|
|
340
|
-
- `message_end` (assistant, with usage) accumulates into `state.lifetimeUsage`, calls `onUpdate`.
|
|
341
|
-
- Returned function unsubscribes from session.
|
|
342
|
-
|
|
343
|
-
Source changes:
|
|
344
|
-
|
|
345
|
-
- `src/ui/ui-observer.ts`: `subscribeUIObserver()` function.
|
|
346
|
-
|
|
347
|
-
Commit: `feat: add UI observer for direct session subscription (#100)`
|
|
348
|
-
|
|
349
|
-
### Phase 2: AgentManager uses record observer
|
|
350
|
-
|
|
351
|
-
#### Cycle 3: Wire record observer into startAgent and resume
|
|
352
|
-
|
|
353
|
-
Test surface: `test/agent-manager.test.ts` (updated).
|
|
354
|
-
|
|
355
|
-
This cycle replaces the record-mutation logic in `startAgent()` and `resume()` callback wrappers with `subscribeRecordObserver`.
|
|
356
|
-
The 5 UI callbacks (`onToolActivity` through `onCompaction`) are still accepted on `SpawnOptions` and forwarded to `RunOptions` as pass-through (no wrapping).
|
|
357
|
-
They are removed in cycle 4.
|
|
358
|
-
|
|
359
|
-
Source changes:
|
|
360
|
-
|
|
361
|
-
- `src/agent-manager.ts`:
|
|
362
|
-
- Import `subscribeRecordObserver`.
|
|
363
|
-
- In `startAgent()`: subscribe record observer inside `onSessionCreated` handler; remove `record.toolUses++` from `onToolActivity` wrapper (pass through `options.onToolActivity`); remove `addUsage` from `onAssistantUsage` wrapper (pass through); remove `record.compactionCount++` from `onCompaction` wrapper (pass through, `this.onCompact` moves to observer's `onCompact` option); capture unsubscribe function, call in `.then()` and `.catch()`.
|
|
364
|
-
- In `resume()`: subscribe record observer to `record.session` before calling `runner.resume()`; drop `onToolActivity`, `onAssistantUsage`, `onCompaction` args from `runner.resume()` call; unsubscribe in `finally` block.
|
|
365
|
-
|
|
366
|
-
Test changes:
|
|
367
|
-
|
|
368
|
-
- `test/agent-manager.test.ts`:
|
|
369
|
-
- Upgrade `mockSession()` to support `subscribe()` and provide a test helper `emit()` method.
|
|
370
|
-
- Update the `onAssistantUsage` test: mock runner calls `opts.onSessionCreated?.(session)`, then emits `message_end` events through the mock session.
|
|
371
|
-
- Update the `onCompaction` test: same pattern — emit `compaction_end` events through mock session.
|
|
372
|
-
- Update the `resume()` accumulation test: mock session emits events during `runner.resume()`.
|
|
373
|
-
|
|
374
|
-
Run: full test suite + `pnpm run check`.
|
|
375
|
-
|
|
376
|
-
Commit: `refactor: AgentManager subscribes record observer directly (#100)`
|
|
377
|
-
|
|
378
|
-
### Phase 3: Agent-tool uses UI observer
|
|
379
|
-
|
|
380
|
-
#### Cycle 4: Replace createActivityTracker with UI observer, drop SpawnOptions callbacks
|
|
381
|
-
|
|
382
|
-
Test surface: `test/tools/agent-tool.test.ts` (verified unchanged), `test/agent-manager.test.ts` (verified compatible).
|
|
383
|
-
|
|
384
|
-
This cycle replaces `createActivityTracker` with inline `AgentActivity` state construction and `subscribeUIObserver` subscription via `onSessionCreated`.
|
|
385
|
-
`SpawnOptions` drops 5 `on*` fields.
|
|
386
|
-
|
|
387
|
-
Source changes:
|
|
388
|
-
|
|
389
|
-
- `src/tools/agent-tool.ts`:
|
|
390
|
-
- Import `subscribeUIObserver` from `../ui/ui-observer.js`.
|
|
391
|
-
- Remove `createActivityTracker` function.
|
|
392
|
-
- Background path: construct `AgentActivity` state inline; pass `onSessionCreated` callback that subscribes UI observer; remove `...bgCallbacks` spread.
|
|
393
|
-
- Foreground path: construct `AgentActivity` state inline; pass `onSessionCreated` callback that subscribes UI observer, registers activity in widget, and captures unsubscribe; remove `...fgCallbacks` spread; call UI unsubscribe after `spawnAndWait` returns.
|
|
394
|
-
- `src/agent-manager.ts`:
|
|
395
|
-
- `SpawnOptions`: remove `onToolActivity`, `onTextDelta`, `onTurnEnd`, `onAssistantUsage`, `onCompaction` fields.
|
|
396
|
-
- `startAgent()`: remove pass-through forwarding of the 5 dropped callbacks to `RunOptions`.
|
|
397
|
-
|
|
398
|
-
Run: full test suite + `pnpm run check`.
|
|
399
|
-
|
|
400
|
-
Commit: `refactor: agent-tool subscribes UI observer, drop SpawnOptions callbacks (#100)`
|
|
401
|
-
|
|
402
|
-
### Phase 4: Runner drops callback forwarding
|
|
403
|
-
|
|
404
|
-
#### Cycle 5: RunOptions and ResumeOptions drop callback fields, runner simplifies
|
|
405
|
-
|
|
406
|
-
Test surface: `test/agent-runner.test.ts` (updated), `test/agent-runner-extension-tools.test.ts` (updated).
|
|
407
|
-
|
|
408
|
-
Source changes:
|
|
409
|
-
|
|
410
|
-
- `src/agent-runner.ts`:
|
|
411
|
-
- `RunOptions`: remove `onToolActivity`, `onTextDelta`, `onTurnEnd`, `onAssistantUsage`, `onCompaction` fields.
|
|
412
|
-
- `ResumeOptions`: remove `onToolActivity`, `onAssistantUsage`, `onCompaction` fields (keep `signal`).
|
|
413
|
-
- `runAgent()`: simplify the big `session.subscribe()` block to turn-limit enforcement only (turn_end → count, steer, abort).
|
|
414
|
-
Remove message_start/message_update/tool_execution_start/tool_execution_end/message_end/compaction_end handling.
|
|
415
|
-
- `resumeAgent()`: remove the conditional `session.subscribe()` block entirely (no callbacks to forward).
|
|
416
|
-
Keep `collectResponseText` and `forwardAbortSignal`.
|
|
417
|
-
- Remove `ToolActivity` export if no longer needed externally (check consumers).
|
|
418
|
-
|
|
419
|
-
Test changes:
|
|
420
|
-
|
|
421
|
-
- `test/agent-runner.test.ts`:
|
|
422
|
-
- Remove or simplify callback forwarding tests (onAssistantUsage wiring, onCompaction forwarding, tool activity forwarding).
|
|
423
|
-
- Turn-limit tests remain, with simplified setup (no callback expectations).
|
|
424
|
-
- `runAgent()` call sites drop the 5 callback fields.
|
|
425
|
-
- `test/agent-runner-extension-tools.test.ts`:
|
|
426
|
-
- Drop callback-related fields from `runAgent()` call construction.
|
|
427
|
-
|
|
428
|
-
Run: full test suite + `pnpm run check`.
|
|
429
|
-
|
|
430
|
-
Commit: `refactor: RunOptions and ResumeOptions drop callback fields (#100)`
|
|
431
|
-
|
|
432
|
-
## Risks and Mitigations
|
|
433
|
-
|
|
434
|
-
| Risk | Mitigation |
|
|
435
|
-
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
436
|
-
| Double-counting during transition (old callbacks + new observer both update record) | Cycle 3 replaces record mutations atomically — old wrappers become pass-through in the same commit that adds the observer |
|
|
437
|
-
| Mock session complexity increases (need `subscribe` + `emit` helper) | One-time investment; upgrade `mockSession()` once, reused across all tests; more realistic than simulating callbacks |
|
|
438
|
-
| Event ordering between record observer and UI observer | Observers update independent state (record vs AgentActivity); no cross-dependency, ordering irrelevant |
|
|
439
|
-
| `ToolActivity` type used outside this package | Grep before removing; if used, keep the export and add a deprecation note |
|
|
440
|
-
| `createActivityTracker` exported but unused externally | Verified: only used within `agent-tool.ts`; safe to remove |
|
|
441
|
-
| Turn-limit enforcement mixed with callback subscription in runner | Cycle 5 extracts turn-limit into its own subscription cleanly; `collectResponseText` stays separate |
|
|
442
|
-
| Unsubscription missed on error paths | Record observer unsubscribe captured in closure, called in both `.then()` and `.catch()`; resume uses `finally` |
|
|
443
|
-
|
|
444
|
-
## Open Questions
|
|
445
|
-
|
|
446
|
-
- Whether to extract turn-limit enforcement from the runner into a separate `turn-limiter.ts` module.
|
|
447
|
-
This would improve testability but is orthogonal to callback elimination.
|
|
448
|
-
Deferred — the runner's subscription simplifies enough in cycle 5.
|
|
449
|
-
- Whether `ResumeOptions` should be renamed or inlined since it reduces to `{ signal?: AbortSignal }`.
|
|
450
|
-
Keeping it as a named type is fine for extensibility.
|
|
451
|
-
Deferred — cosmetic, can be done anytime.
|
|
452
|
-
- Whether to introduce a `SessionLike` interface for the mock session pattern used in `record-observer.test.ts` and `ui-observer.test.ts`.
|
|
453
|
-
Both tests need a minimal session with `subscribe()`.
|
|
454
|
-
If the pattern proves reusable, extract it; otherwise keep the inline mock.
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
issue: 102
|
|
3
|
-
issue_title: "Consolidate test AgentRecord construction into a shared factory"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Consolidate test AgentRecord construction into a shared factory
|
|
7
|
-
|
|
8
|
-
## Problem Statement
|
|
9
|
-
|
|
10
|
-
Eight test files independently construct `AgentRecord` objects using three different patterns: copy-pasted `makeRecord()`/`mockRecord()` factory functions (5 files), inline `const baseRecord: AgentRecord = { ... }` literals (2 files), and `as AgentRecord` casts.
|
|
11
|
-
When issue #98 converts `AgentRecord` from an interface to a class, every object-literal construction site breaks.
|
|
12
|
-
A shared factory confines that future breakage to a single file.
|
|
13
|
-
|
|
14
|
-
## Goals
|
|
15
|
-
|
|
16
|
-
- Create a shared `createTestRecord()` factory in `test/helpers/make-record.ts`.
|
|
17
|
-
- Migrate all 7 affected test files to import the shared factory.
|
|
18
|
-
- No production code changes.
|
|
19
|
-
- No behavior changes — purely mechanical.
|
|
20
|
-
|
|
21
|
-
## Non-Goals
|
|
22
|
-
|
|
23
|
-
- Converting `AgentRecord` to a class — that is issue #98, which depends on this change.
|
|
24
|
-
- Adding new test coverage — this is a refactoring of test infrastructure only.
|
|
25
|
-
- Touching `test/agent-manager.test.ts` — it constructs records via `manager.spawn()`, not literals.
|
|
26
|
-
- Consolidating other test helpers (mock sessions, mock TUI, etc.).
|
|
27
|
-
|
|
28
|
-
## Background
|
|
29
|
-
|
|
30
|
-
### Relevant modules
|
|
31
|
-
|
|
32
|
-
| Module | Role |
|
|
33
|
-
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
34
|
-
| `src/types.ts` | Defines the `AgentRecord` interface (20+ fields) |
|
|
35
|
-
| `test/tools/agent-tool.test.ts` | `makeRecord()` factory — 12 default fields, status "completed" |
|
|
36
|
-
| `test/tools/get-result-tool.test.ts` | `makeRecord()` factory — 10 default fields, status "completed" |
|
|
37
|
-
| `test/tools/steer-tool.test.ts` | `makeRecord()` factory — 9 default fields, status "running", includes mock session, uses `as AgentRecord` cast |
|
|
38
|
-
| `test/ui/agent-menu.test.ts` | `makeRecord()` factory — 10 default fields, status "completed" |
|
|
39
|
-
| `test/conversation-viewer.test.ts` | `mockRecord()` factory — 6 default fields, status "running", uses `as AgentRecord` cast |
|
|
40
|
-
| `test/notification.test.ts` | 4 inline `baseRecord` literals, status "completed" |
|
|
41
|
-
| `test/service-adapter.test.ts` | 4 inline `baseRecord` / `minimal` literals, mixed statuses |
|
|
42
|
-
|
|
43
|
-
### Convention from sibling packages
|
|
44
|
-
|
|
45
|
-
`packages/pi-autoformat/test/helpers/rpc.ts` is the only existing shared test helper in the monorepo.
|
|
46
|
-
The pattern is a plain module under `test/helpers/` with named exports — no class, no framework.
|
|
47
|
-
|
|
48
|
-
### Relationship to issue #98
|
|
49
|
-
|
|
50
|
-
Issue #98 plans to extract `MutableAgentRecord` as a class implementing the `AgentRecord` interface.
|
|
51
|
-
That plan explicitly notes: "All test files that construct `AgentRecord` literals — they create interface-compatible objects, not class instances" and lists them as unchanged.
|
|
52
|
-
Once this consolidation lands, issue #98's "unchanged" assumption becomes trivially true: only the shared factory needs updating if the construction API changes.
|
|
53
|
-
|
|
54
|
-
## Design Overview
|
|
55
|
-
|
|
56
|
-
### Shared factory: `createTestRecord()`
|
|
57
|
-
|
|
58
|
-
A single function in `test/helpers/make-record.ts` with the `Partial<AgentRecord>` override pattern already used by 5 of the 7 files:
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
import type { AgentRecord } from "../../src/types.js";
|
|
62
|
-
|
|
63
|
-
export function createTestRecord(
|
|
64
|
-
overrides: Partial<AgentRecord> = {},
|
|
65
|
-
): AgentRecord {
|
|
66
|
-
return {
|
|
67
|
-
id: "agent-1",
|
|
68
|
-
type: "general-purpose",
|
|
69
|
-
description: "Test task",
|
|
70
|
-
status: "completed",
|
|
71
|
-
result: "All done.",
|
|
72
|
-
toolUses: 3,
|
|
73
|
-
startedAt: 1000,
|
|
74
|
-
completedAt: 2000,
|
|
75
|
-
compactionCount: 0,
|
|
76
|
-
lifetimeUsage: { input: 500, output: 500, cacheWrite: 0 },
|
|
77
|
-
...overrides,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### Default-value decisions
|
|
83
|
-
|
|
84
|
-
The defaults match the majority pattern (6 of 7 files default to a "completed" record).
|
|
85
|
-
The two files that need "running" records (`steer-tool`, `conversation-viewer`) pass `{ status: "running" }` as overrides — a one-field change.
|
|
86
|
-
|
|
87
|
-
The `as AgentRecord` cast used by `steer-tool.test.ts` and `conversation-viewer.test.ts` is no longer needed: the shared factory returns a full `AgentRecord` with all required fields populated, so TypeScript is satisfied without casting.
|
|
88
|
-
|
|
89
|
-
### Migration strategy for inline-literal files
|
|
90
|
-
|
|
91
|
-
`notification.test.ts` and `service-adapter.test.ts` construct multiple distinct inline literals — they don't have a single factory.
|
|
92
|
-
Each inline literal becomes a `createTestRecord({ ...specific overrides })` call.
|
|
93
|
-
The `baseRecord` variable declared in each `describe` block is replaced with a call to `createTestRecord()`.
|
|
94
|
-
|
|
95
|
-
For `service-adapter.test.ts`, the top-level `baseRecord` with custom values (`id: "abc-123"`, `type: "Explore"`, etc.) becomes `createTestRecord({ id: "abc-123", type: "Explore", ... })`.
|
|
96
|
-
|
|
97
|
-
## Module-Level Changes
|
|
98
|
-
|
|
99
|
-
### New files
|
|
100
|
-
|
|
101
|
-
1. `test/helpers/make-record.ts` — exports `createTestRecord()`.
|
|
102
|
-
|
|
103
|
-
### Changed files
|
|
104
|
-
|
|
105
|
-
1. `test/tools/agent-tool.test.ts` — remove local `makeRecord()`, import `createTestRecord` from helpers.
|
|
106
|
-
2. `test/tools/get-result-tool.test.ts` — remove local `makeRecord()`, import `createTestRecord` from helpers.
|
|
107
|
-
3. `test/tools/steer-tool.test.ts` — remove local `makeRecord()`, import `createTestRecord` from helpers.
|
|
108
|
-
Replace default `status: "running"` and `session` with overrides in each call site.
|
|
109
|
-
4. `test/ui/agent-menu.test.ts` — remove local `makeRecord()`, import `createTestRecord` from helpers.
|
|
110
|
-
5. `test/conversation-viewer.test.ts` — remove local `mockRecord()`, import `createTestRecord` from helpers.
|
|
111
|
-
Replace default `status: "running"` and `startedAt: Date.now()` with overrides in each call site.
|
|
112
|
-
6. `test/notification.test.ts` — replace 4 inline `baseRecord` literals with `createTestRecord()` calls.
|
|
113
|
-
7. `test/service-adapter.test.ts` — replace inline `baseRecord` / `minimal` / per-test literals with `createTestRecord()` calls.
|
|
114
|
-
|
|
115
|
-
### Unchanged files
|
|
116
|
-
|
|
117
|
-
1. `test/agent-manager.test.ts` — constructs records via `manager.spawn()`, not literals.
|
|
118
|
-
2. All production source files — no changes.
|
|
119
|
-
|
|
120
|
-
## Test Impact Analysis
|
|
121
|
-
|
|
122
|
-
### New tests enabled
|
|
123
|
-
|
|
124
|
-
1. A small sanity test in `test/helpers/make-record.test.ts` verifying that `createTestRecord()` returns a valid `AgentRecord` with expected defaults and that overrides are applied.
|
|
125
|
-
This is optional — the factory is exercised transitively by every consumer — but it documents the contract for future maintainers (especially when #98 changes construction).
|
|
126
|
-
|
|
127
|
-
### Existing tests that become redundant
|
|
128
|
-
|
|
129
|
-
None.
|
|
130
|
-
This is a pure refactoring of test infrastructure; no production behavior changes.
|
|
131
|
-
|
|
132
|
-
### Existing tests that stay as-is
|
|
133
|
-
|
|
134
|
-
All existing test assertions stay unchanged.
|
|
135
|
-
Only the construction of `AgentRecord` objects in test setup code changes; the assertions that read those records are untouched.
|
|
136
|
-
|
|
137
|
-
## TDD Order
|
|
138
|
-
|
|
139
|
-
1. **Create shared factory and its test.**
|
|
140
|
-
Add `test/helpers/make-record.ts` with `createTestRecord()`.
|
|
141
|
-
Add `test/helpers/make-record.test.ts` verifying defaults and override behavior.
|
|
142
|
-
Commit: `test: add shared createTestRecord factory (#102)`
|
|
143
|
-
|
|
144
|
-
2. **Migrate tool test files.**
|
|
145
|
-
Update `agent-tool.test.ts`, `get-result-tool.test.ts`, `steer-tool.test.ts` to import `createTestRecord` and remove local `makeRecord()` functions.
|
|
146
|
-
Run `pnpm vitest run test/tools/agent-tool.test.ts test/tools/get-result-tool.test.ts test/tools/steer-tool.test.ts` to verify.
|
|
147
|
-
Commit: `test: migrate tool tests to shared createTestRecord (#102)`
|
|
148
|
-
|
|
149
|
-
3. **Migrate UI test files.**
|
|
150
|
-
Update `agent-menu.test.ts` and `conversation-viewer.test.ts` to import `createTestRecord` and remove local `makeRecord()`/`mockRecord()` functions.
|
|
151
|
-
Run `pnpm vitest run test/ui/agent-menu.test.ts test/conversation-viewer.test.ts` to verify.
|
|
152
|
-
Commit: `test: migrate UI tests to shared createTestRecord (#102)`
|
|
153
|
-
|
|
154
|
-
4. **Migrate notification and service-adapter tests.**
|
|
155
|
-
Update `notification.test.ts` and `service-adapter.test.ts` to replace inline literals with `createTestRecord()` calls.
|
|
156
|
-
Run `pnpm vitest run test/notification.test.ts test/service-adapter.test.ts` to verify.
|
|
157
|
-
Commit: `test: migrate notification and service-adapter tests to shared createTestRecord (#102)`
|
|
158
|
-
|
|
159
|
-
5. **Final verification.**
|
|
160
|
-
Run full test suite (`pnpm vitest run`) and type check (`pnpm run check`) to confirm no regressions.
|
|
161
|
-
Commit: not needed if steps 2–4 are clean; otherwise a fix-up commit.
|
|
162
|
-
|
|
163
|
-
## Risks and Mitigations
|
|
164
|
-
|
|
165
|
-
| Risk | Mitigation |
|
|
166
|
-
| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
167
|
-
| Shared defaults don't match a test's assumptions, causing silent false-passes | Each migration step runs the affected test file immediately; review each test's overrides to ensure they still express the test's intent |
|
|
168
|
-
| `steer-tool.test.ts` relies on `session: { fake: true }` in its factory default, which the shared factory omits | Pass `session` as an override at each call site; the mock session is test-specific and doesn't belong in shared defaults |
|
|
169
|
-
| `conversation-viewer.test.ts` uses `startedAt: Date.now()` which the shared factory replaces with `1000` | Replace with `createTestRecord({ status: "running" })`; `startedAt` value is not asserted in any conversation-viewer test |
|
|
170
|
-
| `service-adapter.test.ts` uses custom `id`, `type`, `description` values that carry semantic meaning in its assertions | Pass those values explicitly as overrides to `createTestRecord()` |
|
|
171
|
-
| The `as AgentRecord` cast removal changes type-checking strictness | The shared factory returns a complete object satisfying all required fields, so removing the cast is strictly safer |
|
|
172
|
-
|
|
173
|
-
## Open Questions
|
|
174
|
-
|
|
175
|
-
- The factory name `createTestRecord` vs `makeRecord` vs `makeAgentRecord`: the plan uses `createTestRecord` to distinguish it from the production `AgentRecord` constructor that #98 will introduce.
|
|
176
|
-
If #98 names its constructor differently, this can be revisited.
|