@selesai/code 0.8.5 → 0.8.6

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 (62) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/defaults/models.json +5 -0
  3. package/dist/extensions/agent-browser.test.ts +178 -2
  4. package/dist/extensions/agent-browser.ts +2 -0
  5. package/dist/extensions/copy-turn.test.ts +229 -0
  6. package/dist/extensions/copy-turn.ts +11 -0
  7. package/dist/extensions/grep-app/index.test.ts +356 -1
  8. package/dist/extensions/grep-app/index.ts +6 -1
  9. package/dist/extensions/handoff-new.test.ts +351 -161
  10. package/dist/extensions/handoff-new.ts +3 -0
  11. package/dist/extensions/inline-skills.test.ts +93 -0
  12. package/dist/extensions/inline-skills.ts +3 -0
  13. package/dist/extensions/pi-subagents/agents/builder.md +5 -2
  14. package/dist/extensions/pi-subagents/agents/commentator.md +1 -1
  15. package/dist/extensions/pi-subagents/docs/configuration.md +8 -0
  16. package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
  17. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +1 -1
  18. package/dist/extensions/pi-subagents/src/extension/config.ts +6 -0
  19. package/dist/extensions/pi-subagents/src/extension/index.ts +83 -14
  20. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +12 -6
  21. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +3 -0
  22. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +5 -2
  23. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +19 -1
  24. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +2 -2
  25. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +5 -2
  26. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +7 -5
  27. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +2 -1
  28. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +7 -4
  29. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +39 -9
  30. package/dist/extensions/pi-subagents/src/shared/session-lineage.ts +71 -0
  31. package/dist/extensions/pi-subagents/src/shared/types.ts +16 -0
  32. package/dist/extensions/pi-subagents/src/tui/fleet.ts +1 -1
  33. package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +28 -0
  34. package/dist/extensions/pi-subagents/test/unit/handoff-adoption.test.ts +103 -0
  35. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +22 -0
  36. package/dist/extensions/pi-subagents/test/unit/session-lineage.test.ts +73 -0
  37. package/dist/extensions/pi-subagents/test/unit/workflow-auto-relaunch.test.ts +45 -0
  38. package/dist/extensions/ponytail/index.js +11 -0
  39. package/dist/extensions/ponytail/ponytail-config.cjs +2 -0
  40. package/dist/extensions/ponytail/ponytail-instructions.cjs +6 -0
  41. package/dist/extensions/ponytail/test/extension.test.js +274 -140
  42. package/dist/extensions/ponytail/test/helpers.test.js +280 -92
  43. package/dist/extensions/question/index.ts +18 -0
  44. package/dist/extensions/question/question-list.ts +22 -0
  45. package/dist/extensions/question/tests/batch.test.ts +103 -65
  46. package/dist/extensions/question/tests/helpers.test.ts +68 -35
  47. package/dist/extensions/question/tests/question-list.test.ts +506 -204
  48. package/dist/extensions/question/tests/row-layout.test.ts +180 -114
  49. package/dist/extensions/question/tests/schemas.test.ts +42 -19
  50. package/dist/extensions/question/tests/shortcuts.test.ts +106 -84
  51. package/dist/extensions/question/tests/wizard.test.ts +771 -50
  52. package/dist/extensions/rtk.test.ts +180 -1
  53. package/dist/extensions/tokenin-onboarding.ts +3 -0
  54. package/dist/extensions/undo.test.ts +720 -0
  55. package/dist/extensions/undo.ts +6 -0
  56. package/dist/extensions/web-agent-onboarding.test.ts +415 -0
  57. package/dist/extensions/workflow/extension.ts +3 -3
  58. package/dist/extensions/workflow/modes.ts +41 -19
  59. package/dist/skills/pi-subagents/references/execution-controls.md +1 -1
  60. package/docs/plans/workflow-autoloop-reference.md +1 -2
  61. package/docs/plans/workflow-handoff-carryover.md +108 -0
  62. package/package.json +4 -1
@@ -0,0 +1,108 @@
1
+ # Plan: Carry workflows over compaction & auto-handoff
2
+
3
+ Status: implemented (P0 + P1). The first workflow attempt timed out in `build-1` with zero changes; the parent session then implemented the plan directly. All changes are in `src/extensions/pi-subagents`; no core changes. Feasibility held at ~90%.
4
+
5
+ ## 1. Problem
6
+
7
+ Running a scripted workflow (`/workflow-*` → `launchSlashSubagent({ workflowScript, async: true })` → pi-subagents async run) and then triggering **compaction** or **auto-handoff** (`handoff-new` via `_checkAutoHandoff`) makes the workflow "disappear":
8
+
9
+ - After **auto-handoff** the new session shows no widget/fleet jobs, receives no completion results, cannot resume/steer the workflow, and the handoff document itself omits tool-call details — so the fresh agent doesn't even know a workflow exists.
10
+ - After **compaction** the parent agent loses the workflow manifest (run ids, keys, phases) from its context; the only countermeasure is a generic "resume the parent task" nudge.
11
+
12
+ ## 2. Root cause (trace)
13
+
14
+ 1. Workflow runs are **detached child processes** with state on disk under a global tmp root (`DIRS.async` / `DIRS.results`, `src/extensions/pi-subagents/src/shared/types.ts:2030`). The workflow script itself (worker thread + `runs.run` children + mission-backed `state.get/set`) lives in that spawned run — it keeps running after the parent session is replaced. Nothing about the workflow actually dies.
15
+ 2. Auto-handoff replaces the session: `ctx.newSession()` (`src/extensions/handoff-new.ts:146`) → `AgentSessionRuntime.newSession` (`src/core/agent-session-runtime.ts:226`) → `teardownCurrent` + fresh `createRuntime` → **fresh extension instances**. The new pi-subagents instance restores state only for its own session identity.
16
+ 3. The identity key is the **session file path** (`resolveCurrentSessionId`, `src/extensions/pi-subagents/src/shared/session-identity.ts` → `getSessionFile() ?? getSessionId()`). Every restore path filters by it and misses the old session's runs:
17
+
18
+ | Component | Filter that drops the old session | File |
19
+ |---|---|---|
20
+ | Async job tracker | `restoreActiveJobs` → `listAsyncRuns(..., sessionId: state.currentSessionId)` | `runs/background/async-job-tracker.ts:648` |
21
+ | Result delivery | `ownsSession(sessionId, epoch)` / `identity.sessionId === state.currentSessionId` | `runs/background/result-watcher.ts:168, 211` |
22
+ | Foreground history | `run.sessionId === sessionId` | `runs/foreground/foreground-history.ts:124` |
23
+ | Retained children (`children.list`) | `listRetainedChildren(DIRS.async, ownerSessionId)` | `runs/background/retained-children.ts` |
24
+ | Wait subscriptions | `record.sessionId === state.currentSessionId` | `runs/background/wait-subscriptions.ts:~245` |
25
+ | Async capacity | `getActiveAsyncCapacitySnapshot(state.currentSessionId, ...)` | `extension/index.ts:757` |
26
+ | Mission goal notices | `ownerSessionId = state.currentSessionId` | `extension/index.ts:~634` |
27
+
28
+ 4. `session_shutdown` (`extension/index.ts:~843`) clears in-memory jobs, stops the result watcher, and disposes watchers — by design, but nothing re-links the old session's estate.
29
+ 5. The new session's header records the parent: `parentSession` in `SessionHeader` (`src/core/session-manager.ts:942`), and `SessionStartEvent` carries `previousSessionFile` for reason `"new"`/`"fork"` (`src/core/extensions/types.ts:562`). The extension never uses either.
30
+ 6. Compaction: tool results are summarized away (`compaction.ts` / `branch-summarization.ts` skip tool-result bodies). The `session_compact` handler injects a resume message only when active async jobs exist, with **generic** text (`extension/index.ts:810`) — run ids/keys/phases are lost from the model's context.
31
+
32
+ ## 3. Fix design — session lineage adoption
33
+
34
+ > Implemented. Files changed: `shared/session-lineage.ts` (new), `shared/types.ts`, `runs/background/async-status.ts`, `runs/background/async-job-tracker.ts`, `runs/background/result-watcher.ts`, `runs/background/notify.ts`, `runs/background/run-status.ts`, `runs/background/run-id-resolver.ts`, `runs/background/retained-children.ts`, `runs/background/wait-subscriptions.ts`, `runs/foreground/foreground-history.ts`, `runs/foreground/subagent-executor.ts`, `tui/fleet.ts`, `extension/index.ts`. Tests: `test/unit/session-lineage.test.ts` (new), `test/unit/handoff-adoption.test.ts` (new), existing `compaction-resume.test.ts` unchanged and passing. One extra find beyond the plan: the completion notifier (`notify.ts`) rejected non-current-session results, so it also needed lineage awareness for adopted result delivery.
35
+
36
+ One concept fixes the handoff half: **on `session_start` with `previousSessionFile`, adopt the previous session's workflow estate** instead of filtering on the current session id alone. Everything is keyed by stable session-file paths and the chain is recoverable via `SessionManager.getHeader().parentSession` (`session-manager.ts:1291`), so adoption is additive and needs **zero core changes**.
37
+
38
+ ### 3.1 New helper — `shared/session-lineage.ts`
39
+
40
+ - `resolveSessionLineage(ctx, { depth = 8 })`: `[currentSessionId, ...walk(getHeader().parentSession)]`, depth-bounded, deduped, skipping paths that don't exist (`existsSync` guard, same as `retainedSessionFile` in `retained-children.ts`).
41
+ - Stored as `state.sessionLineage: string[]` in `resetSessionState` (`extension/index.ts:~766`).
42
+ - `listAsyncRuns` gains a `sessionIds: string[]` filter (`async-status.ts`) so all callers share one primitive.
43
+
44
+ ### 3.2 Adopt the estate (P0)
45
+
46
+ | # | Change | Location |
47
+ |---|---|---|
48
+ | 1 | `restoreActiveJobs`: list `queued|running` runs for `sessionIds: state.sessionLineage`; mark adopted jobs (`adopted: true`) so completion notices can say "started in a previous session". | `async-job-tracker.ts:648` |
49
+ | 2 | Result watcher: treat lineage as owned (`ownsSession` + identity checks accept lineage set); dedupe: at adoption only prime results whose `runId` ∈ adopted set or `completedAt` ≥ session-start epoch — never replay ancient results. | `result-watcher.ts:168–262` |
50
+ | 3 | Foreground history, retained children, wait subscriptions: restore/union over lineage. | `foreground-history.ts:124`, `retained-children.ts`, `wait-subscriptions.ts:~245` |
51
+ | 4 | Async capacity: snapshot over lineage so adopted runs count against `maxActiveAsyncRunsPerSession`. | `active-async-capacity.ts` caller at `extension/index.ts:757` |
52
+ | 5 | Mission goal notices: collect for each lineage owner on `session_start` (reasons `new`/`fork`) in addition to the existing `agent_end` path. Missions themselves already persist per-project (`missions/store.ts`) and `mission.list` works. | `extension/index.ts:~634`, `missions/goal-driver.ts:118` |
53
+ | 6 | Schedules: already re-armed on the new session (`bindSession` → `selectProject` → `restoreOne` → `arm`); `scheduledRunManager.stop()` on shutdown is safe because of that re-arm. Lock in with a regression test. | `scheduled-runs.ts:376–381` |
54
+
55
+ ### 3.3 Make the new agent aware (P0)
56
+
57
+ - On `session_start` (reason `new`/`fork`) with adopted active workflows, set `state.handoffResumePending`. On the first `agent_settled` after that (avoids racing the handoff doc's first turn), send one custom message — same pattern as `subagent-compaction-resume`:
58
+
59
+ ```
60
+ customType: "subagent-handoff-resume", display: false, triggerTurn: true
61
+ content: "Session handoff detected. N active background workflows carried over from the previous session:
62
+ - <id8> workflow[task] builder · step 3/5 · running (last: read)
63
+ - <id8> single explorer · running
64
+ Monitor with subagent status <id>, interrupt/resume/steer/approve-checkpoint by run id; children.list shows completed steps."
65
+ ```
66
+
67
+ Built from `state.asyncJobs` (id, mode, `workflowKey`, agent, phase/currentStep, activity).
68
+
69
+ ### 3.4 Enrich the compaction nudge (P1)
70
+
71
+ - Replace the generic `subagent-compaction-resume` text with the same workflow manifest (one line per active job). Keep the trigger conditions and custom type unchanged — test at `test/unit/compaction-resume.test.ts` covers the pattern.
72
+
73
+ ### 3.5 Out of scope (documented, intentional)
74
+
75
+ - **Watchdog**: resets on `session_before_switch` by design (`watchdog/register-main.ts:431`). Keep.
76
+ - **Core changes**: none needed. If a third extension later needs cross-session state, revisit with a generic `session_replaced` state-bag API (rejected now — YAGNI).
77
+ - **Blocking handoff while workflows run**: rejected; long workflows would lock the session. Carry-over is the correct fix.
78
+
79
+ ## 4. Tests
80
+
81
+ - unit: `resolveSessionLineage` (chain walk, depth bound, missing files, dedupe) — `test/unit/session-lineage.test.ts` ✔
82
+ - unit: `restoreActiveJobs` adopts lineage runs — covered by the adoption harness ✔
83
+ - unit: result watcher delivers lineage results; no replay of pre-adoption results (dedupe watermark) — harness asserts adopted result delivery + cleanup ✔
84
+ - unit: `session_start(reason:"new")` + first `agent_settled` injects `subagent-handoff-resume` when adopted workflows exist; nothing when none — `test/unit/handoff-adoption.test.ts` ✔ (repeated-settle no-op also asserted)
85
+ - unit: compaction resume message contains the workflow manifest — harness asserts run id in content ✔
86
+ - regression: full pi-subagents unit suite 1907 pass / 0 fail ✔
87
+
88
+ ## 5. Risk register
89
+
90
+ | Risk | Mitigation |
91
+ |---|---|
92
+ | Result replay across the boundary (ancient results re-delivered) | Adopt-set + epoch watermark (3.2 #2) |
93
+ | Resume message races the handoff doc's first turn | Defer to first `agent_settled` (3.3) |
94
+ | Stale/deleted parent session files | `existsSync` guard + bounded walk (3.1) |
95
+ | Adopted runs exceeding capacity | Count lineage in capacity snapshot (3.2 #4); existing spawn budgets still enforce |
96
+ | Double-watch in the same process | Old instance fully disposed at `session_shutdown` before the new one starts; watchers are per-instance |
97
+ | Extra turn noise after handoff | One message, `display:false`, same precedent as compaction resume |
98
+
99
+ ## 6. Effort & feasibility
100
+
101
+ - **Possibility: ~90%.** Everything the workflow needs already persists on disk keyed by stable session-file paths; the spawned workflow process survives the session switch; the new session already knows its parent (`parentSession` header + `previousSessionFile` event); all filters to widen live in one package; the message-injection pattern already exists. The change is additive filtering + one notification — no protocol, no core change, no data migration.
102
+ - **Size:** ~500–700 LOC + ~12–16 tests, all in `src/extensions/pi-subagents`.
103
+ - **Effort:** 2–3 focused dev-days.
104
+ - **Order:** P0 (3.1–3.3) → P1 (3.4) → tests/regression. Each P0 item is independently shippable.
105
+
106
+ ## 7. Verification (manual)
107
+
108
+ Same as before — worth an interactive smoke test with `/workflow-task` + `/handoff-new` to see the widget adoption and the `subagent-handoff-resume` notice end-to-end. Automated coverage: `src/extensions/pi-subagents/test/unit/{session-lineage,handoff-adoption}.test.ts` + existing compaction-resume test.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selesai/code",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "description": "Maintained, extension-first Pi coding agent with built-in workflows, subagents, web research, questions, skills, and an enhanced terminal UI.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -50,6 +50,8 @@
50
50
  ],
51
51
  "scripts": {
52
52
  "clean": "shx rm -rf dist",
53
+ "test": "vitest run src/extensions/undo.test.ts src/extensions/agent-browser.test.ts src/extensions/context-compaction-reminder.test.ts src/extensions/copy-turn.test.ts src/extensions/grep-app/index.test.ts src/extensions/inline-skills.test.ts src/extensions/rtk.test.ts src/extensions/web-agent-onboarding.test.ts src/extensions/handoff-new.test.ts src/extensions/question/tests src/extensions/ponytail/test src/__tests__/tokenin-onboarding.test.ts src/__tests__/workflow-modes.test.ts",
54
+ "test:coverage": "vitest run --coverage src/extensions/undo.test.ts src/extensions/agent-browser.test.ts src/extensions/context-compaction-reminder.test.ts src/extensions/copy-turn.test.ts src/extensions/grep-app/index.test.ts src/extensions/inline-skills.test.ts src/extensions/rtk.test.ts src/extensions/web-agent-onboarding.test.ts src/extensions/handoff-new.test.ts src/extensions/question/tests src/extensions/ponytail/test src/__tests__/tokenin-onboarding.test.ts src/__tests__/workflow-modes.test.ts",
53
55
  "prepare": "npm run build",
54
56
  "build": "npm run clean && tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js dist/rpc-entry.js && npm run copy-assets",
55
57
  "copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/ && shx mkdir -p dist/defaults && shx cp src/defaults/* dist/defaults/ && shx mkdir -p dist/extensions && node scripts/copy-extensions.mjs && shx mkdir -p dist/themes && shx cp -r src/themes/. dist/themes/ && shx mkdir -p dist/skills && shx cp -r src/skills/. dist/skills/"
@@ -89,6 +91,7 @@
89
91
  "@types/proper-lockfile": "4.1.4",
90
92
  "@types/semver": "7.7.1",
91
93
  "@typescript/native-preview": "^7.0.0-dev.20260120.1",
94
+ "@vitest/coverage-v8": "4.1.9",
92
95
  "shx": "0.4.0",
93
96
  "typescript": "5.9.3",
94
97
  "vitest": "4.1.9"