@exaudeus/workrail 3.40.0 → 3.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/cli/commands/init.js +0 -3
  2. package/dist/cli-worktrain.js +8 -0
  3. package/dist/cli.js +0 -18
  4. package/dist/config/app-config.d.ts +0 -16
  5. package/dist/config/app-config.js +0 -14
  6. package/dist/config/config-file.js +0 -3
  7. package/dist/console-ui/assets/index-CQt4UhPB.js +28 -0
  8. package/dist/console-ui/assets/index-DGj8EsFR.css +1 -0
  9. package/dist/console-ui/index.html +2 -2
  10. package/dist/coordinators/pr-review.d.ts +17 -0
  11. package/dist/coordinators/pr-review.js +164 -0
  12. package/dist/daemon/daemon-events.d.ts +9 -1
  13. package/dist/daemon/soul-template.d.ts +2 -2
  14. package/dist/daemon/soul-template.js +11 -1
  15. package/dist/daemon/workflow-runner.d.ts +14 -1
  16. package/dist/daemon/workflow-runner.js +395 -25
  17. package/dist/di/container.js +1 -25
  18. package/dist/di/tokens.d.ts +0 -3
  19. package/dist/di/tokens.js +0 -3
  20. package/dist/engine/engine-factory.js +0 -1
  21. package/dist/infrastructure/console-defaults.d.ts +1 -0
  22. package/dist/infrastructure/console-defaults.js +4 -0
  23. package/dist/infrastructure/session/index.d.ts +0 -1
  24. package/dist/infrastructure/session/index.js +1 -3
  25. package/dist/manifest.json +87 -103
  26. package/dist/mcp/handlers/session.d.ts +1 -0
  27. package/dist/mcp/handlers/session.js +61 -13
  28. package/dist/mcp/server.js +1 -18
  29. package/dist/mcp/transports/http-entry.js +0 -2
  30. package/dist/mcp/transports/stdio-entry.js +1 -2
  31. package/dist/mcp/types.d.ts +0 -2
  32. package/dist/trigger/daemon-console.d.ts +2 -0
  33. package/dist/trigger/daemon-console.js +1 -1
  34. package/dist/trigger/trigger-listener.d.ts +2 -0
  35. package/dist/trigger/trigger-listener.js +3 -1
  36. package/dist/trigger/trigger-router.d.ts +4 -3
  37. package/dist/trigger/trigger-router.js +4 -3
  38. package/dist/trigger/trigger-store.js +17 -4
  39. package/dist/v2/usecases/console-routes.d.ts +2 -1
  40. package/dist/v2/usecases/console-routes.js +29 -5
  41. package/dist/v2/usecases/console-service.js +14 -0
  42. package/dist/v2/usecases/console-types.d.ts +1 -0
  43. package/docs/authoring.md +16 -16
  44. package/docs/design/coordinator-message-queue-drain-plan.md +241 -0
  45. package/docs/design/coordinator-message-queue-drain-review.md +120 -0
  46. package/docs/design/coordinator-message-queue-drain.md +289 -0
  47. package/docs/design/shaping-workflow-external-research.md +119 -0
  48. package/docs/discovery/late-bound-goals-impl-plan.md +147 -0
  49. package/docs/discovery/late-bound-goals-review.md +82 -0
  50. package/docs/discovery/late-bound-goals.md +118 -0
  51. package/docs/discovery/steer-endpoint-design-candidates.md +288 -0
  52. package/docs/discovery/steer-endpoint-design-review-findings.md +104 -0
  53. package/docs/discovery/steer-endpoint-implementation-plan.md +284 -0
  54. package/docs/ideas/backlog.md +292 -0
  55. package/docs/ideas/design-candidates-console-session-tree-impl.md +64 -0
  56. package/docs/ideas/design-candidates-session-tree-view.md +196 -0
  57. package/docs/ideas/design-review-findings-console-session-tree-impl.md +75 -0
  58. package/docs/ideas/design-review-findings-session-tree-view.md +88 -0
  59. package/docs/ideas/implementation_plan_session_tree_view.md +238 -0
  60. package/package.json +2 -1
  61. package/spec/authoring-spec.json +16 -16
  62. package/spec/shape.schema.json +178 -0
  63. package/spec/workflow-tags.json +232 -47
  64. package/workflows/coding-task-workflow-agentic.json +491 -480
  65. package/workflows/wr.shaping.json +182 -0
  66. package/dist/console-ui/assets/index-8dh0Psu-.css +0 -1
  67. package/dist/console-ui/assets/index-CXWCAonr.js +0 -28
  68. package/dist/infrastructure/session/DashboardHeartbeat.d.ts +0 -8
  69. package/dist/infrastructure/session/DashboardHeartbeat.js +0 -39
  70. package/dist/infrastructure/session/DashboardLockRelease.d.ts +0 -2
  71. package/dist/infrastructure/session/DashboardLockRelease.js +0 -29
  72. package/dist/infrastructure/session/HttpServer.d.ts +0 -60
  73. package/dist/infrastructure/session/HttpServer.js +0 -912
  74. package/workflows/coding-task-workflow-agentic.lean.v2.json +0 -648
  75. package/workflows/coding-task-workflow-agentic.v2.json +0 -324
@@ -0,0 +1,196 @@
1
+ # Design Candidates: Session Tree View in Console
2
+
3
+ *Discovery session: 2026-04-18*
4
+
5
+ ---
6
+
7
+ ## Problem Understanding
8
+
9
+ ### Core Tensions
10
+
11
+ 1. **Flat API vs tree UI**: `/api/v2/sessions` returns a flat array. The UI wants a tree grouped by parent-child relationships. Options: build tree client-side from parentSessionId index, or change the API. The existing repo pattern (flat projection DTOs, pure use-case functions) favors building the tree client-side.
12
+
13
+ 2. **Orphaned children vs tree integrity**: If a parent session is older than MAX_SESSIONS_TO_LOAD=500, its children have a dangling parentSessionId. Showing orphaned children as roots is the only safe fallback, but the tree is incomplete. Acceptable for MVP.
14
+
15
+ 3. **Filtering with tree structure**: When filtering by status or search, should the parent appear if only a child matches? Naive filtering breaks the tree. Better approach: include parent when any child matches. Adds complexity to filterSessions().
16
+
17
+ 4. **Type mirror sync**: ConsoleSessionSummary is duplicated between `src/v2/usecases/console-types.ts` (server) and `console/src/api/types.ts` (client mirror). Both must be updated in sync. This is an existing technical debt, not new -- adding parentSessionId just adds one more field to keep in sync.
18
+
19
+ ### Likely Seam
20
+
21
+ The seam is between the flat sessions array and the tree render. The right place is a new pure function `buildSessionTree(sessions)` in `session-list-use-cases.ts`, not a new HTTP endpoint and not a modification to the GROUP_AXES grouping infrastructure.
22
+
23
+ ### What Makes This Hard
24
+
25
+ The existing GROUP_AXES abstraction in `session-list-use-cases.ts` is flat -- each group has a string label. Tree grouping requires a fundamentally different rendering shape: a clickable coordinator SessionCard as the group header, with indented children below. Shoehorning tree rendering into GROUP_AXES produces an illegal state (coordinator appears as both a plain-text header label AND a card inside the group).
26
+
27
+ The filter-with-tree interaction is the hardest sub-problem: when tree mode is active, a filter that excludes the parent but matches a child must still show the parent as context.
28
+
29
+ ---
30
+
31
+ ## Philosophy Constraints
32
+
33
+ From CLAUDE.md and repo patterns:
34
+
35
+ - **Immutability by default**: all new types use readonly fields
36
+ - **Pure functions in use-cases**: business logic in `session-list-use-cases.ts`, not in React components
37
+ - **Make illegal states unrepresentable**: coordinator session must not appear twice (as header AND as card)
38
+ - **Errors are data**: orphaned children (parent not in loaded set) should degrade gracefully to root-level display, never throw
39
+ - **YAGNI**: 2-level tree only for MVP; no recursive structure needed
40
+ - **Compose with small pure functions**: `buildSessionTree()` should be pure and independently testable
41
+ - **Validate at boundaries**: `extractParentSessionId()` happens in console-service.ts (the projection boundary); frontend trusts the value
42
+
43
+ **No philosophy conflicts found.** CLAUDE.md principles and repo patterns are consistent for this problem.
44
+
45
+ ---
46
+
47
+ ## Impact Surface
48
+
49
+ If `ConsoleSessionSummary` gains a `parentSessionId` field:
50
+ - `src/v2/usecases/console-types.ts` -- server type definition
51
+ - `console/src/api/types.ts` -- client type mirror (must stay in sync manually)
52
+ - `projectSessionSummary()` in `console-service.ts` -- new field returned in the projection
53
+ - `filterSessions()` in `session-list-use-cases.ts` -- needs parentIndex for tree-mode filtering
54
+ - `SessionList.tsx` -- new tree rendering path
55
+ - Any future codegen for the type mirror would pick this up automatically
56
+
57
+ Existing consumers of the flat `/api/v2/sessions` endpoint are unaffected -- the new field is additive and optional for root sessions.
58
+
59
+ ---
60
+
61
+ ## Candidates
62
+
63
+ ### Candidate A -- Minimal: add parentSessionId, reuse GROUP_AXES with a 'tree' option
64
+
65
+ **Summary:** Add `parentSessionId` to both type files. Add a 'tree' option to GROUP_AXES that groups children under their parent's sessionId as the group key. The existing SessionGroup component shows the parent sessionId as the group label; children are SessionCards inside.
66
+
67
+ **Tensions resolved:** API stability (flat array unchanged). Type sync (one field added to both).
68
+
69
+ **Tensions accepted:** GROUP_AXES abstraction is abused. SessionGroup renders a plain-text label (the parent sessionId string), not a clickable coordinator SessionCard.
70
+
71
+ **Boundary solved at:** Frontend GROUP_AXES layer only.
72
+
73
+ **Why that boundary:** Minimum viable change -- no new components, no new functions.
74
+
75
+ **Failure mode:** Coordinator session appears BOTH as the group label text AND as a SessionCard inside the group (it's in the flat list). The group header is a non-navigable string, not a card. This is an illegal state: the coordinator is visible twice in different forms. Fixing this requires adding a custom node-renderer to SessionGroup -- at which point you've rebuilt Candidate B anyway.
76
+
77
+ **Repo pattern:** Abuses GROUP_AXES -- designed for grouping by string key, not parent-child hierarchies.
78
+
79
+ **Gains:** Zero new components. Minimal diff.
80
+
81
+ **Losses:** Visual quality. Coordinator not navigable from group header. No tree connector lines. Duplication bug.
82
+
83
+ **Scope:** Too narrow -- doesn't deliver the tree view quality described in the backlog.
84
+
85
+ **Philosophy:** YAGNI honored. Make illegal states unrepresentable violated (coordinator appears twice).
86
+
87
+ ---
88
+
89
+ ### Candidate B -- Best-fit: new buildSessionTree() + dedicated SessionTreeView component
90
+
91
+ **Summary:** Add `parentSessionId: string | null` to `ConsoleSessionSummary` on server and client. Implement `extractParentSessionId(events)` in `console-service.ts` (O(1), session_created is always eventIndex=0). Add a new pure function to `session-list-use-cases.ts`:
92
+
93
+ ```typescript
94
+ interface SessionTreeNode {
95
+ readonly session: ConsoleSessionSummary;
96
+ readonly children: readonly ConsoleSessionSummary[];
97
+ }
98
+
99
+ interface SessionTree {
100
+ readonly roots: readonly SessionTreeNode[];
101
+ readonly orphanChildIds: ReadonlySet<string>;
102
+ }
103
+
104
+ function buildSessionTree(sessions: readonly ConsoleSessionSummary[]): SessionTree
105
+ ```
106
+
107
+ Add a view mode toggle (tree/flat) to `SessionListState`. When tree mode is active, render a new `SessionTreeView` component: coordinator cards at root level, children indented 20px with a CSS `border-left` connector line on the wrapper div. Orphans (parent not loaded) appear as roots.
108
+
109
+ Modify `filterSessions()` to accept an optional `parentIndex: ReadonlyMap<string, string>` parameter. When tree mode is active: if a child matches the filter, include its parent too (parent appears even if it doesn't match the filter text/status).
110
+
111
+ **Tensions resolved:** API stability. Tree rendering quality (no duplication). Orphan handling (explicit orphanChildIds set). Filter+tree interaction (parent included when child matches).
112
+
113
+ **Tensions accepted:** Type mirror sync remains manual.
114
+
115
+ **Boundary solved at:** Frontend use-cases layer (`buildSessionTree()` is pure) + new presenter component.
116
+
117
+ **Why that boundary:** `buildSessionTree()` is pure and testable without React. The tree is computed once per render cycle, not per-card. Follows the exact same pattern as the existing pure functions in `session-list-use-cases.ts`.
118
+
119
+ **Failure mode:** Filter-with-tree is the hardest case. The `filterSessions()` modification adds complexity. If the logic is wrong, the parent could be shown when it shouldn't (e.g., filter=complete, parent is in_progress, child is complete -- should the in_progress parent appear?). Decision: include parent when any child matches, regardless of parent's own status. This is the most useful behavior for the tree view use case.
120
+
121
+ **Repo pattern:** Follows the pure use-cases + presenter pattern exactly. New SessionTreeView component follows the same presenter shape as existing components.
122
+
123
+ **Gains:** Clean tree rendering with visual connectors. Navigable coordinator cards. No duplication. Pure testable logic. Degrades gracefully for orphaned children.
124
+
125
+ **Losses:** Slightly more code than Candidate A. Two components for the same view (flat list + tree view).
126
+
127
+ **Scope:** Best-fit -- delivers the design described in the backlog without overbuilding.
128
+
129
+ **Philosophy:** All principles honored. Immutability, explicit domain types, pure functions, YAGNI (2-level tree only).
130
+
131
+ ---
132
+
133
+ ### Candidate C -- Server-side tree: new `GET /api/v2/sessions/tree` endpoint
134
+
135
+ **Summary:** Add a new server endpoint that returns `{ roots: ConsoleSessionSummaryWithChildren[], orphans: ConsoleSessionSummary[] }`. The flat `/api/v2/sessions` endpoint is unchanged. Server builds the tree from the loaded session set, embedding children under their parent in the response.
136
+
137
+ **Tensions resolved:** Tree structure computed at the source of truth (server, with access to the full 500-session window). Client receives a ready-to-render tree.
138
+
139
+ **Tensions accepted:** New endpoint means new React Query hook, new cache key, new loading state. Two overlapping endpoints that must be kept consistent.
140
+
141
+ **Boundary solved at:** Server projection layer (`console-service.ts`).
142
+
143
+ **Why that boundary:** Server has the full session set in scope; client doesn't need to rebuild the tree from a flat list.
144
+
145
+ **Failure mode:** API surface grows. The flat endpoint and tree endpoint must stay consistent. Cache invalidation logic must be updated for both. If the tree endpoint is slow (500 sessions), the flat endpoint is still fast -- users may not understand why.
146
+
147
+ **Repo pattern:** Departs from the existing pattern (all console endpoints return flat projection DTOs). Adds server complexity for a problem that client-side pure functions can solve with zero HTTP overhead.
148
+
149
+ **Gains:** Tree is always consistent (parent and children computed together). Client rendering is simpler -- just map the tree response.
150
+
151
+ **Losses:** API surface growth. Additional React Query hook. More complex cache invalidation. Server-side complexity for a problem solvable client-side.
152
+
153
+ **Scope:** Too broad -- the flat list already contains all the data needed to build the tree client-side.
154
+
155
+ **Philosophy:** Conflicts with YAGNI (new endpoint not needed). Conflicts with validate-at-boundaries (boundary moved to server when client can handle it).
156
+
157
+ ---
158
+
159
+ ## Comparison and Recommendation
160
+
161
+ | Tension | A | B | C |
162
+ |---|---|---|---|
163
+ | API stability | Resolves | Resolves | Adds new endpoint |
164
+ | Tree rendering quality | Fails (duplication) | Resolves | Resolves |
165
+ | Orphan handling | None | Explicit | Server-side |
166
+ | Filter+tree interaction | Broken | Manageable | Handled server-side |
167
+ | Repo pattern fit | Abuses GROUP_AXES | Follows pure-function pattern | Departs from flat-DTO pattern |
168
+ | Reversibility | Easy | Easy | Medium |
169
+ | Philosophy fit | Partial | Full | Partial |
170
+
171
+ **Recommendation: Candidate B.**
172
+
173
+ B resolves all real tensions without overbuilding. It follows the existing pure-function/presenter split the repo already practices. `buildSessionTree()` is pure and testable independently of React. The filter-with-tree interaction is manageable with a contained change to `filterSessions()`. The 2-level tree constraint matches the backlog's examples exactly.
174
+
175
+ ---
176
+
177
+ ## Self-Critique
178
+
179
+ **Strongest argument against B:** What if the Phase 2 UX design requires 3-level trees (coordinator → child coordinator → grandchild)? B explicitly excludes this by using `readonly children: readonly ConsoleSessionSummary[]` instead of `readonly children: readonly SessionTreeNode[]`. Changing to recursive SessionTreeNode later is a contained change, but it would require updating the SessionTreeView component too.
180
+
181
+ **Pivot conditions:**
182
+ - If Phase 2 UX requires >2 levels: change `SessionTreeNode.children` to `readonly SessionTreeNode[]` and make `SessionTreeView` recursive. The `buildSessionTree()` function would need to become recursive as well.
183
+ - If filter+tree interaction proves too complex: ship tree view without filter support in tree mode, show a "tree view disabled while filtered" state.
184
+ - If the type mirror sync problem grows: introduce codegen for the type mirror (separate from this feature).
185
+
186
+ **Assumption that would invalidate B:** If the flat `/api/v2/sessions` endpoint doesn't return all sessions needed to build a complete tree (e.g., if sessions are paginated server-side before reaching the client). Currently MAX_SESSIONS_TO_LOAD=500 applies before the response; if a coordinator and its children are all within the 500-session window, the tree will be complete. If the coordinator is old but children are recent, the tree will be incomplete -- but this degrades gracefully (children show as roots).
187
+
188
+ ---
189
+
190
+ ## Open Questions for the Main Agent
191
+
192
+ 1. **Filter behavior in tree mode**: When filtering by status=in_progress and the coordinator is complete but has an in_progress child -- should the coordinator appear? Proposed: yes, include parent when any child matches. Is this the right UX?
193
+
194
+ 2. **Tree mode default or opt-in**: Should the tree view be the default mode, or should users opt in via a toggle? Given that zero sessions have parentSessionId today, defaulting to tree view would show an identical flat list -- tree mode only activates when coordinator sessions exist.
195
+
196
+ 3. **Connector line style**: Simple `border-left` CSS on the indented container, or tree-line SVG connectors like `TreeLine.tsx` (which already exists in `console/src/components/TreeLine.tsx`)? The existing component should be used if it fits.
@@ -0,0 +1,75 @@
1
+ # Design Review Findings: Console Session Tree Implementation (Phase 3)
2
+
3
+ *2026-04-18 -- Covering Slice 5: SessionTreeView component*
4
+
5
+ ---
6
+
7
+ ## Tradeoff Review
8
+
9
+ | Tradeoff | Acceptable? | Condition for Failure | Notes |
10
+ |---|---|---|---|
11
+ | Transient expand state (resets on navigation) | Yes | Never -- no acceptance criterion requires persistence | Acceptable for MVP |
12
+ | Auto-expand only on initial render | Yes | User expects newly-in_progress coordinators to auto-expand during the page session | Known limitation, acceptable |
13
+ | Expand toggle outside ConsoleCard | Yes | Layout mismatch -- toggle disconnected from card visually | WorkspaceView.tsx proves the flex-row pattern works |
14
+
15
+ ---
16
+
17
+ ## Failure Mode Review
18
+
19
+ | Failure Mode | Handled? | Mitigation | Risk |
20
+ |---|---|---|---|
21
+ | Expand toggle triggers card navigation | Yes | Toggle is separate DOM button outside ConsoleCard | None |
22
+ | Coordinator with no children shows toggle | Yes | Hide toggle when children.length === 0 | None |
23
+ | cycle in parentSessionId | Yes | buildSessionTree() cycle guard | None |
24
+ | Newly in_progress coordinator won't auto-expand after initial render | Partial | Accepted for MVP; useState init only covers initial render | Low |
25
+ | TypeScript type errors | None expected | All types already defined in Slices 1-4 | None |
26
+
27
+ ---
28
+
29
+ ## Runner-Up / Simpler Alternative Review
30
+
31
+ **Simpler variant adopted:** Skip auto-expand entirely, or keep auto-expand for initial render only. Decision: keep auto-expand on initial render (simple useState initializer), accept that mid-session state changes won't auto-expand. Avoids useEffect/useMemo complexity.
32
+
33
+ ---
34
+
35
+ ## Philosophy Alignment
36
+
37
+ | Principle | Status |
38
+ |---|---|
39
+ | Pure functions in use-cases | Satisfied -- buildSessionTree() is pure |
40
+ | Immutability | Satisfied -- readonly types throughout |
41
+ | Compose with small functions | Satisfied -- SessionTreeView is a named function |
42
+ | YAGNI | Satisfied -- auto-expand is minimal |
43
+ | Functional/declarative | Acceptable tension -- useState Map is mutable but correct for React UI state |
44
+
45
+ ---
46
+
47
+ ## Findings
48
+
49
+ ### Red (must fix before implementation)
50
+
51
+ None.
52
+
53
+ ### Orange (should address)
54
+
55
+ **O1: Expand toggle placement requires specific layout**
56
+ The expand toggle must be in a flex row with the coordinator card, but the coordinator card itself must not be a parent of the toggle (avoids nested interactive elements). The pattern from WorkspaceView.tsx is `<div className="flex items-start gap-2">` containing `[toggle button]` then `[coordinator card button]`.
57
+
58
+ ### Yellow (advisory)
59
+
60
+ **Y1: Auto-expand fires only on initial render**
61
+ If a coordinator transitions to in_progress after the component first renders, it will not auto-expand. This is acceptable for MVP.
62
+
63
+ ---
64
+
65
+ ## Recommended Revisions
66
+
67
+ 1. Use `flex items-start gap-2` wrapper row for [expand-toggle, coordinator-card] to avoid nested interactive elements.
68
+ 2. Initialize expand state with `useState(() => new Map(roots.filter(...).map(n => [n.session.sessionId, true])))` -- function initializer to avoid re-running on re-renders.
69
+
70
+ ---
71
+
72
+ ## Residual Concerns
73
+
74
+ - Visual quality of the amber left border + [COORD] badge combination requires human visual review -- cannot be verified by TypeScript check.
75
+ - No real coordinator sessions exist locally, so end-to-end visual testing requires either manual mock data or running a spawn_agent workflow.
@@ -0,0 +1,88 @@
1
+ # Design Review Findings: Session Tree View in Console
2
+
3
+ *Discovery session: 2026-04-18*
4
+
5
+ ---
6
+
7
+ ## Tradeoff Review
8
+
9
+ | Tradeoff | Violates Acceptance Criteria? | Conditions for Failure | Verdict |
10
+ |---|---|---|---|
11
+ | Manual type mirror sync | No -- TypeScript catches at build time | Developer forgets to update client mirror | Acceptable -- compiler enforces |
12
+ | Incomplete tree when parent outside 500-session window | No -- degrades to orphan-as-root | Coordinator runs many children over days | Acceptable for MVP |
13
+ | 2-level tree max | No -- engine depth limit aligns (maxSubagentDepth=2) | If maxSubagentDepth raised in config | Acceptable -- contained change to fix |
14
+ | Tree mode opt-in (not default) | No -- flat view is current default | Users never discover toggle | Acceptable -- can add auto-suggest later |
15
+
16
+ ---
17
+
18
+ ## Failure Mode Review
19
+
20
+ | Failure Mode | Handled? | Missing Mitigation | Risk |
21
+ |---|---|---|---|
22
+ | Cyclic parentSessionId (self-parent) | Partial | Explicit cycle guard in buildSessionTree(): `if (parentId === session.sessionId) treat as root` | Low -- easy to add, must add |
23
+ | Orphaned child (parent outside window) | Yes | Optional: 'child session' badge. Low priority. | Low |
24
+ | Filter shows no results in tree mode | Yes -- existing empty-state UI handles it | None needed | Low |
25
+ | Tree toggle state lost on navigation | Not handled | Persist in sessionStorage or URL param. Low priority. | Low |
26
+ | Client mirror type not updated | Yes -- TypeScript build fails | None needed | None at runtime |
27
+
28
+ ---
29
+
30
+ ## Runner-Up / Simpler Alternative Review
31
+
32
+ **Runner-up (C, server-side tree):** No elements worth borrowing. Client already has all 500 sessions; server-side computation adds API surface with no benefit.
33
+
34
+ **Simpler variant adopted:** Tree mode and filter mode are mutually exclusive for MVP. When tree mode is active, status/search filters are disabled (or auto-cleared). This eliminates the filter+tree parentIdIndex complexity from filterSessions() entirely. The more complex filter-aware tree can be added later when coordinator sessions are common enough to justify it.
35
+
36
+ ---
37
+
38
+ ## Philosophy Alignment
39
+
40
+ **Satisfied:** Immutability, make illegal states unrepresentable, compose with small pure functions, validate at boundaries, errors as data, YAGNI.
41
+
42
+ **Under tension (acceptable):**
43
+ - Functional/declarative: buildSessionTree() is imperative-style but pure. Same-input/same-output invariant holds.
44
+ - Type mirror sync: two copies of ConsoleSessionSummary can diverge, but TypeScript build catches this before runtime.
45
+
46
+ **No risky philosophy tensions.**
47
+
48
+ ---
49
+
50
+ ## Findings
51
+
52
+ ### Red (must fix before implementation)
53
+
54
+ None.
55
+
56
+ ### Orange (should fix before implementation)
57
+
58
+ **O1: Cycle guard missing in design**
59
+ buildSessionTree() must guard against `parentSessionId === session.sessionId`. Without this, a session appears both as a root and as its own child. Add explicitly to implementation.
60
+
61
+ ### Yellow (low priority, note for future)
62
+
63
+ **Y1: Orphaned child has no visual indicator**
64
+ Children with a dangling parentSessionId (parent outside 500-session window) show as roots with no indication they're children. A small 'child session' badge would improve UX for multi-day coordinator runs. Not needed for MVP.
65
+
66
+ **Y2: Tree toggle state not persisted**
67
+ Navigating to session detail and back resets tree/flat mode. Could be persisted in sessionStorage or URL param. Not needed for MVP.
68
+
69
+ **Y3: Tree mode not auto-suggested when coordinator sessions exist**
70
+ Users may not discover the tree mode toggle. When any session has `parentSessionId != null`, show a subtle prompt or auto-activate tree mode. Not needed for MVP.
71
+
72
+ ---
73
+
74
+ ## Recommended Revisions
75
+
76
+ 1. **Add cycle guard to buildSessionTree():** Before adding a session to a parent's children array, check `parentSessionId !== session.sessionId`. Treat self-parenting sessions as roots.
77
+
78
+ 2. **Make filter mode and tree mode mutually exclusive:** When tree mode is activated, clear active filters (or disable the filter controls). When a filter is applied, tree mode is disabled. Show a clear UI state for this (e.g., "Filters disabled in tree view").
79
+
80
+ 3. **Document 2-level max as explicit constant:** Define `TREE_MAX_DEPTH = 2` in session-list-use-cases.ts with a comment explaining the engine's maxSubagentDepth alignment.
81
+
82
+ ---
83
+
84
+ ## Residual Concerns
85
+
86
+ 1. **Untestable in real UI until coordinator sessions are run.** Zero sessions have parentSessionId today. The implementation can be verified only via manual testing with artificially constructed session data or by running an actual spawn_agent workflow. Recommendation: add a simple unit test for buildSessionTree() with mock ConsoleSessionSummary objects in session-list-use-cases.test.ts (if the test file exists -- check before implementing).
87
+
88
+ 2. **Filter+tree interaction deferred, not designed.** The current design explicitly excludes filtering while in tree mode. If a future sprint adds filter+tree, the parentIdIndex approach was analyzed and is the right path -- but it must be designed and tested carefully to avoid surprising UX (parent shown in 'complete' filter even though it's in_progress).
@@ -0,0 +1,238 @@
1
+ # Implementation Plan: Session Tree View in Console
2
+
3
+ *Created: 2026-04-18*
4
+
5
+ ---
6
+
7
+ ## Problem Statement
8
+
9
+ WorkRail creates a separate session for every workflow run. When spawn_agent is used, a coordinator session creates multiple child sessions. Today, all sessions appear as a flat list in the console -- there is no visual grouping that shows coordinator-child relationships. The `parentSessionId` field exists in the `session_created` event schema and is written by `makeSpawnAgentTool`, but it is not surfaced in `ConsoleSessionSummary` or rendered in the console UI.
10
+
11
+ ---
12
+
13
+ ## Acceptance Criteria
14
+
15
+ 1. `ConsoleSessionSummary` includes a `parentSessionId: string | null` field. Root sessions have `null`; child sessions have the parent's session ID.
16
+ 2. `/api/v2/sessions` response includes `parentSessionId` on each session summary.
17
+ 3. The console SessionList includes a tree view mode toggle.
18
+ 4. When tree view is active: coordinator sessions (sessions that have children) display their child sessions indented below them, separated by a `TreeLine` connector.
19
+ 5. When tree view is active: filter controls are disabled with a clear UI indicator.
20
+ 6. When tree view is active: sessions with no children display identically to the flat view.
21
+ 7. Orphaned children (parent session not in the loaded 500-session set) display as root sessions in tree view.
22
+ 8. `buildSessionTree()` handles the case where `parentSessionId === sessionId` (self-parent) by treating the session as a root.
23
+ 9. TypeScript build passes with no new errors.
24
+
25
+ ---
26
+
27
+ ## Non-Goals
28
+
29
+ - No new HTTP endpoints (flat `/api/v2/sessions` is unchanged except for the new field).
30
+ - No multi-level (depth > 2) tree rendering for MVP.
31
+ - No filter+tree interaction for MVP (tree mode and filter mode are mutually exclusive).
32
+ - No pagination changes.
33
+ - No persistence of tree view toggle state.
34
+ - No automatic activation of tree mode.
35
+
36
+ ---
37
+
38
+ ## Philosophy-Driven Constraints
39
+
40
+ - **Immutability by default**: all new interfaces use `readonly`.
41
+ - **Pure functions in use-cases**: `buildSessionTree()` is a pure function with no side effects, in `session-list-use-cases.ts`. No business logic in React components.
42
+ - **Make illegal states unrepresentable**: coordinator session appears exactly once (as a root card). Cycle guard prevents self-parenting.
43
+ - **Explicit domain types**: `SessionTree` and `SessionTreeNode` are named interfaces, not anonymous objects.
44
+ - **YAGNI**: 2-level tree, mutually exclusive filter/tree modes, no new endpoints.
45
+ - **Errors as data**: `buildSessionTree()` returns an `orphanChildIds` set; never throws on missing parents.
46
+
47
+ ---
48
+
49
+ ## Invariants
50
+
51
+ 1. `parentSessionId` is `null` for root sessions, a non-empty string for child sessions.
52
+ 2. `buildSessionTree()` is a pure function: same input always produces the same output.
53
+ 3. A session with `parentSessionId === sessionId` is treated as a root (cycle guard).
54
+ 4. An orphaned child (parent not in the input sessions array) is included in `orphanChildIds` and rendered as a root.
55
+ 5. The flat `/api/v2/sessions` endpoint response is backward-compatible: `parentSessionId` is a new additive field.
56
+ 6. `console/src/api/types.ts` mirrors `src/v2/usecases/console-types.ts` for `ConsoleSessionSummary` -- both must be updated together.
57
+
58
+ ---
59
+
60
+ ## Selected Approach
61
+
62
+ **Candidate B: `buildSessionTree()` pure function + `SessionTreeView` presenter**
63
+
64
+ See `design-candidates-session-tree-view.md` for full analysis. Summary: add `parentSessionId` to the type chain (server + client mirror), extract it at the projection boundary in `console-service.ts`, build the tree client-side in a pure function, and render it in a new presenter component using the existing `TreeLine` connector component.
65
+
66
+ **Runner-up:** Candidate C (server-side tree endpoint) -- loses because the flat list already contains all data needed; a new endpoint adds API surface with no benefit.
67
+
68
+ ---
69
+
70
+ ## Vertical Slices
71
+
72
+ ### Slice 1: Add `parentSessionId` to server-side types and projection
73
+
74
+ **Files changed:**
75
+ - `src/v2/usecases/console-types.ts` -- add `readonly parentSessionId: string | null` to `ConsoleSessionSummary`
76
+ - `src/v2/usecases/console-service.ts` -- add `extractParentSessionId(events)` function, include field in `projectSessionSummary()` return value
77
+
78
+ **Acceptance criterion:** `GET /api/v2/sessions` response includes `parentSessionId: null` on all existing sessions. TypeScript build passes.
79
+
80
+ **Pattern to follow:** `extractGitBranch(events)` / `extractRepoRoot(events)` in `console-service.ts` -- scan events for `session_created`, return `data.parentSessionId ?? null`.
81
+
82
+ **Risk:** Low. Additive field, backward-compatible.
83
+
84
+ ---
85
+
86
+ ### Slice 2: Update client-side type mirror
87
+
88
+ **Files changed:**
89
+ - `console/src/api/types.ts` -- add `readonly parentSessionId: string | null` to `ConsoleSessionSummary`
90
+
91
+ **Acceptance criterion:** TypeScript build of the console package passes. The new field is accessible in all React components that receive `ConsoleSessionSummary`.
92
+
93
+ **Risk:** None. Compile-time only.
94
+
95
+ ---
96
+
97
+ ### Slice 3: Add `buildSessionTree()` to session-list-use-cases
98
+
99
+ **Files changed:**
100
+ - `console/src/views/session-list-use-cases.ts` -- add `SessionTreeNode`, `SessionTree` interfaces and `buildSessionTree()` function
101
+
102
+ **Implementation:**
103
+ ```typescript
104
+ export interface SessionTreeNode {
105
+ readonly session: ConsoleSessionSummary;
106
+ readonly children: readonly ConsoleSessionSummary[];
107
+ }
108
+
109
+ export interface SessionTree {
110
+ readonly roots: readonly SessionTreeNode[];
111
+ readonly orphanChildIds: ReadonlySet<string>;
112
+ }
113
+
114
+ export const TREE_MAX_DEPTH = 2; // aligns with engine's default maxSubagentDepth
115
+
116
+ export function buildSessionTree(sessions: readonly ConsoleSessionSummary[]): SessionTree {
117
+ // Build parent -> children index
118
+ // Cycle guard: skip if parentSessionId === sessionId
119
+ // Orphan detection: if parent not in session set, add to orphanChildIds
120
+ // Return roots (sessions with no parent, plus orphaned children as roots) with children attached
121
+ }
122
+ ```
123
+
124
+ **Acceptance criterion:** Unit test in `console/src/views/session-list-use-cases.test.ts` (create if not exists) covers: empty input, all roots, parent-child pairs, cycle detection, orphaned children.
125
+
126
+ **Risk:** Low. Pure function, no I/O.
127
+
128
+ ---
129
+
130
+ ### Slice 4: Add tree view mode to SessionList state and reducer
131
+
132
+ **Files changed:**
133
+ - `console/src/views/session-list-reducer.ts` -- add `viewMode: 'flat' | 'tree'` to state; add `view_mode_changed` action; when `view_mode_changed` to `tree`, clear filters
134
+ - `console/src/hooks/useSessionListViewModel.ts` (or equivalent) -- expose `viewMode` and `dispatch` for tree toggle
135
+
136
+ **Acceptance criterion:** Toggling to tree mode clears active status filter and search. State transitions are deterministic. TypeScript build passes.
137
+
138
+ **Risk:** Low. Reducer is pure, state change is straightforward.
139
+
140
+ ---
141
+
142
+ ### Slice 5: Add `SessionTreeView` component and tree toggle UI
143
+
144
+ **Files changed:**
145
+ - `console/src/views/SessionList.tsx` -- add tree/flat mode toggle button; when `viewMode === 'tree'`, render `SessionTreeView` instead of the flat list; show "Filters disabled in tree view" when tree mode active and filters would normally show
146
+ - New component `SessionTreeView` (inline in `SessionList.tsx` or in a new file) -- renders `SessionTreeNode[]` with `TreeLine` wrappers for indented children
147
+
148
+ **Acceptance criterion:**
149
+ - Toggle button visible in toolbar
150
+ - Clicking toggle switches between tree and flat mode
151
+ - In tree mode: coordinator sessions show children indented below using `TreeLine`
152
+ - In tree mode: filter controls show a disabled state or a note
153
+ - In tree mode with no coordinator sessions: renders identically to flat mode (no indentation)
154
+ - `TreeLine` amber connector lines appear between coordinator card and child cards
155
+
156
+ **Risk:** Medium. No real coordinator sessions exist for manual testing. Must use mock data or construct a test scenario.
157
+
158
+ ---
159
+
160
+ ## Test Design
161
+
162
+ ### Unit tests (`console/src/views/session-list-use-cases.test.ts`)
163
+
164
+ Add a new `describe('buildSessionTree')` block:
165
+ 1. Empty input -> `{ roots: [], orphanChildIds: Set() }`
166
+ 2. All root sessions (no parentSessionId) -> all in roots, no orphans
167
+ 3. One coordinator with two children -> coordinator root with two children attached
168
+ 4. Orphaned child (parent not in input) -> child in roots, parentId in orphanChildIds
169
+ 5. Cycle detection (parentSessionId === sessionId) -> treated as root
170
+ 6. Multiple coordinators with overlapping children (edge case -- shouldn't occur but test graceful handling)
171
+
172
+ ### Build verification
173
+
174
+ - `npm run build` (or equivalent) in both workrail root and console package must pass with no new TypeScript errors.
175
+
176
+ ### Manual verification
177
+
178
+ - Create a test session data fixture (mock two sessions with parent-child relationship) and verify tree rendering visually.
179
+ - Or: run a `spawn_agent` workflow in the daemon and observe the console.
180
+
181
+ ---
182
+
183
+ ## Risk Register
184
+
185
+ | Risk | Likelihood | Impact | Mitigation |
186
+ |---|---|---|---|
187
+ | Cycle in parentSessionId causes infinite loop | Low | High | Cycle guard in buildSessionTree() (Slice 3) |
188
+ | Client mirror not updated with server type | Low | Low | TypeScript build fails immediately |
189
+ | No real coordinator sessions to test against | High | Medium | Unit tests cover the logic; manual test with mock data |
190
+ | Phase 2 UX design requires depth-3 trees | Low | Medium | Change `children: readonly ConsoleSessionSummary[]` to `readonly SessionTreeNode[]`; contained change |
191
+ | Tree mode breaks existing GROUP_AXES grouping | None | N/A | Tree mode is a separate view mode; GROUP_AXES unchanged |
192
+
193
+ ---
194
+
195
+ ## PR Packaging Strategy
196
+
197
+ **Single PR: `feat/console-session-tree`**
198
+
199
+ All 5 slices in one PR. Rationale:
200
+ - Slices 1-2 (type changes) are tiny and safe but useless without the rendering
201
+ - Slices 3-4 (logic) are testable independently but nothing to show
202
+ - Slice 5 (UI) depends on all prior slices
203
+ - The entire change is low-risk and additive -- no breaking changes
204
+ - A single PR is easier to review as a coherent feature
205
+
206
+ ---
207
+
208
+ ## Philosophy Alignment Per Slice
209
+
210
+ | Slice | Principle | Status |
211
+ |---|---|---|
212
+ | 1 (server type + extraction) | Validate at boundaries | Satisfied -- extraction at service boundary |
213
+ | 1 | Compose with small pure functions | Satisfied -- extractParentSessionId follows extractRepoRoot pattern |
214
+ | 1 | Immutability | Satisfied -- readonly field |
215
+ | 2 (client mirror) | Make illegal states unrepresentable | Tension -- manual sync; TypeScript catches divergence at build time |
216
+ | 3 (buildSessionTree) | Pure function composition | Satisfied |
217
+ | 3 | Errors as data | Satisfied -- orphanChildIds, no throws |
218
+ | 3 | Make illegal states unrepresentable | Satisfied -- cycle guard prevents self-parenting |
219
+ | 4 (reducer) | Determinism over cleverness | Satisfied -- pure reducer |
220
+ | 4 | Functional/declarative | Satisfied |
221
+ | 5 (UI) | Compose with small pure functions | Satisfied -- SessionTreeView is a pure presenter |
222
+ | 5 | YAGNI | Satisfied -- 2-level only, no filter+tree |
223
+
224
+ ---
225
+
226
+ ## Follow-Up Tickets
227
+
228
+ - **Visual indicator on orphaned child sessions** (Y1): Add a small 'child session' badge to sessions in `orphanChildIds`. Low priority.
229
+ - **Persist tree toggle state** (Y2): Save to sessionStorage or URL param. Low priority.
230
+ - **Auto-suggest tree mode when coordinator sessions exist** (Y3): Detect `parentSessionId != null` in session list; show a subtle prompt. Low priority.
231
+ - **Filter+tree interaction** (design exists): Add `parentIdIndex` to `filterSessions()` so tree mode and filter mode are compatible. Design is documented in `design-candidates-session-tree-view.md`.
232
+
233
+ ---
234
+
235
+ ## Plan Confidence
236
+
237
+ - `unresolvedUnknownCount`: 1 (no real coordinator sessions to validate tree rendering end-to-end -- mitigated by unit tests)
238
+ - `planConfidenceBand`: High
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "3.40.0",
3
+ "version": "3.41.0",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -103,6 +103,7 @@
103
103
  "open": "^11.0.0",
104
104
  "reflect-metadata": "^0.2.0",
105
105
  "semver": "^7.7.2",
106
+ "tinyglobby": "^0.2.15",
106
107
  "tsconfig-paths": "^4.2.0",
107
108
  "tslib": "^2.8.1",
108
109
  "tsyringe": "^4.8.0",