gsd-pi 2.26.1-next.1 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -6
- package/dist/cli.js +4 -2
- package/dist/headless.d.ts +2 -0
- package/dist/headless.js +99 -7
- package/dist/help-text.js +3 -0
- package/dist/resources/extensions/bg-shell/index.ts +19 -2
- package/dist/resources/extensions/bg-shell/process-manager.ts +45 -0
- package/dist/resources/extensions/bg-shell/types.ts +21 -1
- package/dist/resources/extensions/gsd/auto/session.ts +224 -0
- package/dist/resources/extensions/gsd/auto-budget.ts +32 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +63 -10
- package/dist/resources/extensions/gsd/auto-direct-dispatch.ts +229 -0
- package/dist/resources/extensions/gsd/auto-dispatch.ts +23 -10
- package/dist/resources/extensions/gsd/auto-model-selection.ts +179 -0
- package/dist/resources/extensions/gsd/auto-observability.ts +74 -0
- package/dist/resources/extensions/gsd/auto-prompts.ts +0 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.ts +262 -0
- package/dist/resources/extensions/gsd/auto-tool-tracking.ts +54 -0
- package/dist/resources/extensions/gsd/auto-unit-closeout.ts +46 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +207 -0
- package/dist/resources/extensions/gsd/auto.ts +849 -1584
- package/dist/resources/extensions/gsd/commands.ts +3 -3
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +47 -72
- package/dist/resources/extensions/gsd/doctor-proactive.ts +9 -4
- package/dist/resources/extensions/gsd/export-html.ts +1001 -0
- package/dist/resources/extensions/gsd/export.ts +49 -1
- package/dist/resources/extensions/gsd/git-service.ts +6 -0
- package/dist/resources/extensions/gsd/gitignore.ts +4 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +24 -5
- package/dist/resources/extensions/gsd/index.ts +54 -1
- package/dist/resources/extensions/gsd/native-git-bridge.ts +30 -2
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +231 -20
- package/dist/resources/extensions/gsd/preferences.ts +20 -1
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/reports.ts +510 -0
- package/dist/resources/extensions/gsd/roadmap-slices.ts +1 -1
- package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +178 -0
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/answer-injection.md +54 -0
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +59 -0
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/multi-session.md +185 -0
- package/dist/resources/extensions/gsd/state.ts +30 -0
- package/dist/resources/extensions/gsd/tests/auto-dashboard.test.ts +13 -0
- package/dist/resources/extensions/gsd/tests/continue-here.test.ts +81 -0
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +5 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +10 -1
- package/dist/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +132 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +14 -0
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/native-has-changes-cache.test.ts +61 -0
- package/dist/resources/extensions/gsd/tests/network-error-fallback.test.ts +51 -1
- package/dist/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +331 -0
- package/dist/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +298 -0
- package/dist/resources/extensions/gsd/tests/parallel-merge.test.ts +465 -0
- package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +39 -10
- package/dist/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +71 -0
- package/dist/resources/extensions/gsd/tests/replan-slice.test.ts +42 -0
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +9 -9
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +44 -10
- package/dist/resources/extensions/gsd/tests/worktree.test.ts +3 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +25 -3
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +31 -21
- package/dist/resources/extensions/gsd/visualizer-views.ts +15 -66
- package/dist/resources/extensions/search-the-web/tool-search.ts +26 -0
- package/dist/resources/extensions/shared/format-utils.ts +85 -0
- package/dist/resources/extensions/shared/tests/format-utils.test.ts +153 -0
- package/dist/resources/extensions/subagent/index.ts +46 -1
- package/dist/resources/extensions/subagent/isolation.ts +9 -6
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +7 -4
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-completions.ts +7 -4
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +7 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.js +9 -2
- package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
- package/packages/pi-coding-agent/src/core/lsp/client.ts +8 -0
- package/packages/pi-coding-agent/src/core/lsp/config.ts +9 -2
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +1 -1
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/src/components/editor.ts +3 -1
- package/src/resources/extensions/bg-shell/index.ts +19 -2
- package/src/resources/extensions/bg-shell/process-manager.ts +45 -0
- package/src/resources/extensions/bg-shell/types.ts +21 -1
- package/src/resources/extensions/gsd/auto/session.ts +224 -0
- package/src/resources/extensions/gsd/auto-budget.ts +32 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +63 -10
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +229 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +23 -10
- package/src/resources/extensions/gsd/auto-model-selection.ts +179 -0
- package/src/resources/extensions/gsd/auto-observability.ts +74 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +0 -1
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +262 -0
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +54 -0
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +46 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +207 -0
- package/src/resources/extensions/gsd/auto.ts +849 -1584
- package/src/resources/extensions/gsd/commands.ts +3 -3
- package/src/resources/extensions/gsd/dashboard-overlay.ts +47 -72
- package/src/resources/extensions/gsd/doctor-proactive.ts +9 -4
- package/src/resources/extensions/gsd/export-html.ts +1001 -0
- package/src/resources/extensions/gsd/export.ts +49 -1
- package/src/resources/extensions/gsd/git-service.ts +6 -0
- package/src/resources/extensions/gsd/gitignore.ts +4 -1
- package/src/resources/extensions/gsd/guided-flow.ts +24 -5
- package/src/resources/extensions/gsd/index.ts +54 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +30 -2
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +231 -20
- package/src/resources/extensions/gsd/preferences.ts +20 -1
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/reports.ts +510 -0
- package/src/resources/extensions/gsd/roadmap-slices.ts +1 -1
- package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +178 -0
- package/src/resources/extensions/gsd/skills/gsd-headless/references/answer-injection.md +54 -0
- package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +59 -0
- package/src/resources/extensions/gsd/skills/gsd-headless/references/multi-session.md +185 -0
- package/src/resources/extensions/gsd/state.ts +30 -0
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/native-has-changes-cache.test.ts +61 -0
- package/src/resources/extensions/gsd/tests/network-error-fallback.test.ts +51 -1
- package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +298 -0
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +465 -0
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +39 -10
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +71 -0
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +9 -9
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +44 -10
- package/src/resources/extensions/gsd/tests/worktree.test.ts +3 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +25 -3
- package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -21
- package/src/resources/extensions/gsd/visualizer-views.ts +15 -66
- package/src/resources/extensions/search-the-web/tool-search.ts +26 -0
- package/src/resources/extensions/shared/format-utils.ts +85 -0
- package/src/resources/extensions/shared/tests/format-utils.test.ts +153 -0
- package/src/resources/extensions/subagent/index.ts +46 -1
- package/src/resources/extensions/subagent/isolation.ts +9 -6
package/README.md
CHANGED
|
@@ -39,6 +39,7 @@ Full documentation is available in the [`docs/`](./docs/) directory:
|
|
|
39
39
|
- **[Architecture](./docs/architecture.md)** — system design and dispatch pipeline
|
|
40
40
|
- **[Troubleshooting](./docs/troubleshooting.md)** — common issues, doctor, forensics, recovery
|
|
41
41
|
- **[VS Code Extension](./vscode-extension/README.md)** — chat participant, sidebar dashboard, RPC integration
|
|
42
|
+
- **[Visualizer](./docs/visualizer.md)** — workflow visualizer with stats and discussion status
|
|
42
43
|
- **[Migration from v1](./docs/migration.md)** — `.planning` → `.gsd` migration
|
|
43
44
|
|
|
44
45
|
---
|
|
@@ -67,6 +68,9 @@ GSD v2 solves all of these because it's not a prompt framework anymore — it's
|
|
|
67
68
|
| Context injection | "Read this file" | Pre-inlined into dispatch prompt |
|
|
68
69
|
| Roadmap reassessment | Manual | Automatic after each slice completes |
|
|
69
70
|
| Skill discovery | None | Auto-detect and install relevant skills during research |
|
|
71
|
+
| Verification | Manual | Automated verification commands with auto-fix retries |
|
|
72
|
+
| Reporting | None | Self-contained HTML reports with metrics and dep graphs |
|
|
73
|
+
| Parallel execution | None | Multi-worker parallel milestone orchestration |
|
|
70
74
|
|
|
71
75
|
### Migrating from v1
|
|
72
76
|
|
|
@@ -117,7 +121,7 @@ Research → Plan → Execute (per task) → Complete → Reassess Roadmap → N
|
|
|
117
121
|
Validate Milestone → Complete Milestone
|
|
118
122
|
```
|
|
119
123
|
|
|
120
|
-
**Research** scouts the codebase and relevant docs. **Plan** decomposes the slice into tasks with must-haves (mechanically verifiable outcomes). **Execute** runs each task in a fresh context window with only the relevant files pre-loaded. **Complete** writes the summary, UAT script, marks the roadmap, and commits. **Reassess** checks if the roadmap still makes sense given what was learned. **Validate Milestone** runs a reconciliation gate after all slices complete — comparing roadmap success criteria against actual results before sealing the milestone.
|
|
124
|
+
**Research** scouts the codebase and relevant docs. **Plan** decomposes the slice into tasks with must-haves (mechanically verifiable outcomes). **Execute** runs each task in a fresh context window with only the relevant files pre-loaded — then runs configured verification commands (lint, test, etc.) with auto-fix retries. **Complete** writes the summary, UAT script, marks the roadmap, and commits with meaningful messages derived from task summaries. **Reassess** checks if the roadmap still makes sense given what was learned. **Validate Milestone** runs a reconciliation gate after all slices complete — comparing roadmap success criteria against actual results before sealing the milestone.
|
|
121
125
|
|
|
122
126
|
### `/gsd auto` — The Main Event
|
|
123
127
|
|
|
@@ -137,7 +141,7 @@ Auto mode is a state machine driven by files on disk. It reads `.gsd/STATE.md`,
|
|
|
137
141
|
|
|
138
142
|
3. **Git worktree isolation** — Each milestone runs in its own git worktree with a `milestone/<MID>` branch. All slice work commits sequentially — no branch switching, no merge conflicts. When the milestone completes, it's squash-merged to main as one clean commit.
|
|
139
143
|
|
|
140
|
-
4. **Crash recovery** — A lock file tracks the current unit. If the session dies, the next `/gsd auto` reads the surviving session file, synthesizes a recovery briefing from every tool call that made it to disk, and resumes with full context.
|
|
144
|
+
4. **Crash recovery** — A lock file tracks the current unit. If the session dies, the next `/gsd auto` reads the surviving session file, synthesizes a recovery briefing from every tool call that made it to disk, and resumes with full context. Parallel orchestrator state is persisted to disk with PID liveness detection, so multi-worker sessions survive crashes too.
|
|
141
145
|
|
|
142
146
|
5. **Stuck detection** — If the same unit dispatches twice (the LLM didn't produce the expected artifact), it retries once with a deep diagnostic. If it fails again, auto mode stops with the exact file it expected.
|
|
143
147
|
|
|
@@ -147,7 +151,11 @@ Auto mode is a state machine driven by files on disk. It reads `.gsd/STATE.md`,
|
|
|
147
151
|
|
|
148
152
|
8. **Adaptive replanning** — After each slice completes, the roadmap is reassessed. If the work revealed new information that changes the plan, slices are reordered, added, or removed before continuing.
|
|
149
153
|
|
|
150
|
-
9. **
|
|
154
|
+
9. **Verification enforcement** — Configure shell commands (`npm run lint`, `npm run test`, etc.) that run automatically after task execution. Failures trigger auto-fix retries before advancing. Configurable via `verification_commands`, `verification_auto_fix`, and `verification_max_retries` preferences.
|
|
155
|
+
|
|
156
|
+
10. **Milestone validation** — After all slices complete, a `validate-milestone` gate compares roadmap success criteria against actual results before sealing the milestone.
|
|
157
|
+
|
|
158
|
+
11. **Escape hatch** — Press Escape to pause. The conversation is preserved. Interact with the agent, inspect what happened, or just `/gsd auto` to resume from disk state.
|
|
151
159
|
|
|
152
160
|
### `/gsd` and `/gsd next` — Step Mode
|
|
153
161
|
|
|
@@ -233,17 +241,22 @@ Both terminals read and write the same `.gsd/` files on disk. Your decisions in
|
|
|
233
241
|
# Run auto mode in CI
|
|
234
242
|
gsd headless --timeout 600000
|
|
235
243
|
|
|
244
|
+
# Create and execute a milestone end-to-end
|
|
245
|
+
gsd headless new-milestone --context spec.md --auto
|
|
246
|
+
|
|
236
247
|
# One unit at a time (cron-friendly)
|
|
237
248
|
gsd headless next
|
|
238
249
|
|
|
239
|
-
# Machine-readable
|
|
250
|
+
# Machine-readable JSONL event stream
|
|
240
251
|
gsd headless --json status
|
|
241
252
|
|
|
242
253
|
# Force a specific pipeline phase
|
|
243
254
|
gsd headless dispatch plan
|
|
244
255
|
```
|
|
245
256
|
|
|
246
|
-
Headless auto-responds to interactive prompts, detects completion, and exits with structured codes: `0` complete, `1` error/timeout, `2` blocked. Pair with [remote questions](./docs/remote-questions.md) to route decisions to Slack or Discord when human input is needed.
|
|
257
|
+
Headless auto-responds to interactive prompts, detects completion, and exits with structured codes: `0` complete, `1` error/timeout, `2` blocked. Auto-restarts on crash with exponential backoff. Pair with [remote questions](./docs/remote-questions.md) to route decisions to Slack or Discord when human input is needed.
|
|
258
|
+
|
|
259
|
+
**Multi-session orchestration** — headless mode supports file-based IPC in `.gsd/parallel/` for coordinating multiple GSD workers across milestones. Build orchestrators that spawn, monitor, and budget-cap a fleet of GSD workers.
|
|
247
260
|
|
|
248
261
|
### First launch
|
|
249
262
|
|
|
@@ -269,6 +282,7 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
|
|
|
269
282
|
| `/gsd forensics` | Post-mortem investigation of auto-mode failures |
|
|
270
283
|
| `/gsd cleanup` | Archive phase directories from completed milestones |
|
|
271
284
|
| `/gsd doctor` | Runtime health checks with auto-fix for common issues |
|
|
285
|
+
| `/gsd export --html` | Generate HTML report for current or completed milestone |
|
|
272
286
|
| `/worktree` (`/wt`) | Git worktree lifecycle — create, switch, merge, remove |
|
|
273
287
|
| `/voice` | Toggle real-time speech-to-text (macOS, Linux) |
|
|
274
288
|
| `/exit` | Graceful shutdown — saves session state before exiting |
|
|
@@ -277,6 +291,7 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
|
|
|
277
291
|
| `Ctrl+Alt+G` | Toggle dashboard overlay |
|
|
278
292
|
| `Ctrl+Alt+V` | Toggle voice transcription |
|
|
279
293
|
| `Ctrl+Alt+B` | Show background shell processes |
|
|
294
|
+
| `Alt+V` | Paste clipboard image (macOS) |
|
|
280
295
|
| `gsd config` | Re-run the setup wizard (LLM provider + tool keys) |
|
|
281
296
|
| `gsd update` | Update GSD to the latest version |
|
|
282
297
|
| `gsd headless [cmd]` | Run `/gsd` commands without TUI (CI, cron, scripts) |
|
|
@@ -321,7 +336,7 @@ gsd/M001/S01 (deleted after merge):
|
|
|
321
336
|
feat(S01/T01): core types and interfaces
|
|
322
337
|
```
|
|
323
338
|
|
|
324
|
-
One squash commit per milestone on main (or whichever branch you started from). The worktree is torn down after merge. Git bisect works. Individual milestones are revertable.
|
|
339
|
+
One squash commit per milestone on main (or whichever branch you started from). The worktree is torn down after merge. Git bisect works. Individual milestones are revertable. Commit messages are generated from task summaries — no more generic "complete task" messages.
|
|
325
340
|
|
|
326
341
|
### Verification
|
|
327
342
|
|
|
@@ -343,6 +358,15 @@ The verification ladder: static checks → command execution → behavioral test
|
|
|
343
358
|
- Cost projections based on completed work
|
|
344
359
|
- Completed and in-progress units
|
|
345
360
|
|
|
361
|
+
### HTML Reports
|
|
362
|
+
|
|
363
|
+
After a milestone completes, GSD auto-generates a self-contained HTML report in `.gsd/reports/`. Each report includes project summary, progress tree, slice dependency graph (SVG DAG), cost/token metrics with bar charts, execution timeline, changelog, and knowledge base sections. No external dependencies — all CSS and JS are inlined, printable to PDF from any browser.
|
|
364
|
+
|
|
365
|
+
An auto-generated `index.html` shows all reports with progression metrics across milestones.
|
|
366
|
+
|
|
367
|
+
- **Automatic** — generated after milestone completion (configurable via `auto_report` preference)
|
|
368
|
+
- **Manual** — run `/gsd export --html` anytime
|
|
369
|
+
|
|
346
370
|
---
|
|
347
371
|
|
|
348
372
|
## Configuration
|
|
@@ -370,6 +394,10 @@ auto_supervisor:
|
|
|
370
394
|
hard_timeout_minutes: 30
|
|
371
395
|
budget_ceiling: 50.00
|
|
372
396
|
unique_milestone_ids: true
|
|
397
|
+
verification_commands:
|
|
398
|
+
- npm run lint
|
|
399
|
+
- npm run test
|
|
400
|
+
auto_report: true
|
|
373
401
|
---
|
|
374
402
|
```
|
|
375
403
|
|
|
@@ -387,6 +415,11 @@ unique_milestone_ids: true
|
|
|
387
415
|
| `skill_staleness_days` | Skills unused for N days get deprioritized (default: 60, 0 = disabled) |
|
|
388
416
|
| `unique_milestone_ids` | Uses unique milestone names to avoid clashes when working in teams of people |
|
|
389
417
|
| `git.isolation` | `worktree` (default) or `none` — disable worktree isolation for projects that don't need it |
|
|
418
|
+
| `verification_commands`| Array of shell commands to run after task execution (e.g., `["npm run lint", "npm run test"]`) |
|
|
419
|
+
| `verification_auto_fix`| Auto-retry on verification failures (default: true) |
|
|
420
|
+
| `verification_max_retries` | Max retries for verification failures (default: 2) |
|
|
421
|
+
| `require_slice_discussion` | Pause auto-mode before each slice for human discussion review |
|
|
422
|
+
| `auto_report` | Auto-generate HTML reports after milestone completion (default: true) |
|
|
390
423
|
|
|
391
424
|
### Agent Instructions
|
|
392
425
|
|
|
@@ -471,6 +504,10 @@ The best practice for working in teams is to ensure unique milestone names acros
|
|
|
471
504
|
.gsd/runtime/
|
|
472
505
|
# Git worktree working copies
|
|
473
506
|
.gsd/worktrees/
|
|
507
|
+
# Parallel orchestration IPC and worker status
|
|
508
|
+
.gsd/parallel/
|
|
509
|
+
# Generated HTML reports (regenerable via /gsd export --html)
|
|
510
|
+
.gsd/reports/
|
|
474
511
|
# Session-specific interrupted-work markers
|
|
475
512
|
.gsd/milestones/**/continue.md
|
|
476
513
|
.gsd/milestones/**/*-CONTINUE.md
|
package/dist/cli.js
CHANGED
|
@@ -239,7 +239,9 @@ const configuredExists = configuredProvider && configuredModel &&
|
|
|
239
239
|
allModels.some((m) => m.provider === configuredProvider && m.id === configuredModel);
|
|
240
240
|
const configuredAvailable = configuredProvider && configuredModel &&
|
|
241
241
|
availableModels.some((m) => m.provider === configuredProvider && m.id === configuredModel);
|
|
242
|
-
if (!configuredModel || !configuredExists
|
|
242
|
+
if (!configuredModel || !configuredExists) {
|
|
243
|
+
// Model not configured at all, or removed from registry — pick a fallback.
|
|
244
|
+
// Only fires when the model is genuinely unknown (not just temporarily unavailable).
|
|
243
245
|
const piDefault = getPiDefaultModelAndProvider();
|
|
244
246
|
const preferred = (piDefault
|
|
245
247
|
? availableModels.find((m) => m.provider === piDefault.provider && m.id === piDefault.model)
|
|
@@ -254,7 +256,7 @@ if (!configuredModel || !configuredExists || !configuredAvailable) {
|
|
|
254
256
|
settingsManager.setDefaultModelAndProvider(preferred.provider, preferred.id);
|
|
255
257
|
}
|
|
256
258
|
}
|
|
257
|
-
if (settingsManager.getDefaultThinkingLevel() !== 'off' &&
|
|
259
|
+
if (settingsManager.getDefaultThinkingLevel() !== 'off' && !configuredExists) {
|
|
258
260
|
settingsManager.setDefaultThinkingLevel('off');
|
|
259
261
|
}
|
|
260
262
|
// GSD always uses quiet startup — the gsd extension renders its own branded header
|
package/dist/headless.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface HeadlessOptions {
|
|
|
21
21
|
auto?: boolean;
|
|
22
22
|
verbose?: boolean;
|
|
23
23
|
maxRestarts?: number;
|
|
24
|
+
supervised?: boolean;
|
|
25
|
+
responseTimeout?: number;
|
|
24
26
|
}
|
|
25
27
|
export declare function parseHeadlessArgs(argv: string[]): HeadlessOptions;
|
|
26
28
|
export declare function runHeadless(options: HeadlessOptions): Promise<void>;
|
package/dist/headless.js
CHANGED
|
@@ -15,6 +15,7 @@ import { join, resolve } from 'node:path';
|
|
|
15
15
|
// RpcClient is not in @gsd/pi-coding-agent's public exports — import from dist directly.
|
|
16
16
|
// This relative path resolves correctly from both src/ (via tsx) and dist/ (compiled).
|
|
17
17
|
import { RpcClient } from '../packages/pi-coding-agent/dist/modes/rpc/rpc-client.js';
|
|
18
|
+
import { attachJsonlLineReader, serializeJsonLine } from '../packages/pi-coding-agent/dist/modes/rpc/jsonl.js';
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
// CLI Argument Parser
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
@@ -65,6 +66,17 @@ export function parseHeadlessArgs(argv) {
|
|
|
65
66
|
process.exit(1);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
69
|
+
else if (arg === '--supervised') {
|
|
70
|
+
options.supervised = true;
|
|
71
|
+
options.json = true; // supervised implies json
|
|
72
|
+
}
|
|
73
|
+
else if (arg === '--response-timeout' && i + 1 < args.length) {
|
|
74
|
+
options.responseTimeout = parseInt(args[++i], 10);
|
|
75
|
+
if (Number.isNaN(options.responseTimeout) || options.responseTimeout <= 0) {
|
|
76
|
+
process.stderr.write('[headless] Error: --response-timeout must be a positive integer (milliseconds)\n');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
68
80
|
}
|
|
69
81
|
else if (!positionalStarted) {
|
|
70
82
|
positionalStarted = true;
|
|
@@ -77,12 +89,6 @@ export function parseHeadlessArgs(argv) {
|
|
|
77
89
|
return options;
|
|
78
90
|
}
|
|
79
91
|
// ---------------------------------------------------------------------------
|
|
80
|
-
// JSONL Helper
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
function serializeJsonLine(obj) {
|
|
83
|
-
return JSON.stringify(obj) + '\n';
|
|
84
|
-
}
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
92
|
// Extension UI Auto-Responder
|
|
87
93
|
// ---------------------------------------------------------------------------
|
|
88
94
|
function handleExtensionUIRequest(event, writeToStdin) {
|
|
@@ -184,6 +190,7 @@ function isMilestoneReadyNotification(event) {
|
|
|
184
190
|
// ---------------------------------------------------------------------------
|
|
185
191
|
// Quick Command Detection
|
|
186
192
|
// ---------------------------------------------------------------------------
|
|
193
|
+
const FIRE_AND_FORGET_METHODS = new Set(['notify', 'setStatus', 'setWidget', 'setTitle', 'set_editor_text']);
|
|
187
194
|
const QUICK_COMMANDS = new Set([
|
|
188
195
|
'status', 'queue', 'history', 'hooks', 'export', 'stop', 'pause',
|
|
189
196
|
'capture', 'skip', 'undo', 'knowledge', 'config', 'prefs',
|
|
@@ -194,6 +201,42 @@ function isQuickCommand(command) {
|
|
|
194
201
|
return QUICK_COMMANDS.has(command);
|
|
195
202
|
}
|
|
196
203
|
// ---------------------------------------------------------------------------
|
|
204
|
+
// Supervised Stdin Reader
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
function startSupervisedStdinReader(stdinWriter, client, onResponse) {
|
|
207
|
+
return attachJsonlLineReader(process.stdin, (line) => {
|
|
208
|
+
let msg;
|
|
209
|
+
try {
|
|
210
|
+
msg = JSON.parse(line);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
process.stderr.write(`[headless] Warning: invalid JSON from orchestrator stdin, skipping\n`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const type = String(msg.type ?? '');
|
|
217
|
+
switch (type) {
|
|
218
|
+
case 'extension_ui_response':
|
|
219
|
+
stdinWriter(line + '\n');
|
|
220
|
+
if (typeof msg.id === 'string') {
|
|
221
|
+
onResponse(msg.id);
|
|
222
|
+
}
|
|
223
|
+
break;
|
|
224
|
+
case 'prompt':
|
|
225
|
+
client.prompt(String(msg.message ?? ''));
|
|
226
|
+
break;
|
|
227
|
+
case 'steer':
|
|
228
|
+
client.steer(String(msg.message ?? ''));
|
|
229
|
+
break;
|
|
230
|
+
case 'follow_up':
|
|
231
|
+
client.followUp(String(msg.message ?? ''));
|
|
232
|
+
break;
|
|
233
|
+
default:
|
|
234
|
+
process.stderr.write(`[headless] Warning: unknown message type "${type}" from orchestrator stdin\n`);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
197
240
|
// Main Orchestrator
|
|
198
241
|
// ---------------------------------------------------------------------------
|
|
199
242
|
// ---------------------------------------------------------------------------
|
|
@@ -254,6 +297,11 @@ async function runHeadlessOnce(options, restartCount) {
|
|
|
254
297
|
let interrupted = false;
|
|
255
298
|
const startTime = Date.now();
|
|
256
299
|
const isNewMilestone = options.command === 'new-milestone';
|
|
300
|
+
// Supervised mode cannot share stdin with --context -
|
|
301
|
+
if (options.supervised && options.context === '-') {
|
|
302
|
+
process.stderr.write('[headless] Error: --supervised cannot be used with --context - (both require stdin)\n');
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
257
305
|
// For new-milestone, load context and bootstrap .gsd/ before spawning RPC child
|
|
258
306
|
if (isNewMilestone) {
|
|
259
307
|
if (!options.context && !options.contextText) {
|
|
@@ -329,6 +377,17 @@ async function runHeadlessOnce(options, restartCount) {
|
|
|
329
377
|
}
|
|
330
378
|
// Stdin writer for sending extension_ui_response to child
|
|
331
379
|
let stdinWriter = null;
|
|
380
|
+
// Supervised mode state
|
|
381
|
+
const pendingResponseTimers = new Map();
|
|
382
|
+
let supervisedFallback = false;
|
|
383
|
+
let stopSupervisedReader = null;
|
|
384
|
+
const onStdinClose = () => {
|
|
385
|
+
supervisedFallback = true;
|
|
386
|
+
process.stderr.write('[headless] Warning: orchestrator stdin closed, falling back to auto-response\n');
|
|
387
|
+
};
|
|
388
|
+
if (options.supervised) {
|
|
389
|
+
process.stdin.on('close', onStdinClose);
|
|
390
|
+
}
|
|
332
391
|
// Completion promise
|
|
333
392
|
let resolveCompletion;
|
|
334
393
|
const completionPromise = new Promise((resolve) => {
|
|
@@ -347,6 +406,8 @@ async function runHeadlessOnce(options, restartCount) {
|
|
|
347
406
|
}, effectiveIdleTimeout);
|
|
348
407
|
}
|
|
349
408
|
}
|
|
409
|
+
// Precompute supervised response timeout
|
|
410
|
+
const responseTimeout = options.responseTimeout ?? 30_000;
|
|
350
411
|
// Overall timeout
|
|
351
412
|
const timeoutTimer = setTimeout(() => {
|
|
352
413
|
process.stderr.write(`[headless] Timeout after ${options.timeout / 1000}s\n`);
|
|
@@ -381,7 +442,22 @@ async function runHeadlessOnce(options, restartCount) {
|
|
|
381
442
|
if (isTerminalNotification(eventObj)) {
|
|
382
443
|
completed = true;
|
|
383
444
|
}
|
|
384
|
-
|
|
445
|
+
const method = String(eventObj.method ?? '');
|
|
446
|
+
const shouldSupervise = options.supervised && !supervisedFallback
|
|
447
|
+
&& !FIRE_AND_FORGET_METHODS.has(method);
|
|
448
|
+
if (shouldSupervise) {
|
|
449
|
+
// Interactive request in supervised mode — let orchestrator respond
|
|
450
|
+
const eventId = String(eventObj.id ?? '');
|
|
451
|
+
const timer = setTimeout(() => {
|
|
452
|
+
pendingResponseTimers.delete(eventId);
|
|
453
|
+
handleExtensionUIRequest(eventObj, stdinWriter);
|
|
454
|
+
process.stdout.write(JSON.stringify({ type: 'supervised_timeout', id: eventId, method }) + '\n');
|
|
455
|
+
}, responseTimeout);
|
|
456
|
+
pendingResponseTimers.set(eventId, timer);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
handleExtensionUIRequest(eventObj, stdinWriter);
|
|
460
|
+
}
|
|
385
461
|
// If we detected a terminal notification, resolve after responding
|
|
386
462
|
if (completed) {
|
|
387
463
|
exitCode = blocked ? 2 : 0;
|
|
@@ -432,6 +508,18 @@ async function runHeadlessOnce(options, restartCount) {
|
|
|
432
508
|
stdinWriter = (data) => {
|
|
433
509
|
internalProcess.stdin.write(data);
|
|
434
510
|
};
|
|
511
|
+
// Start supervised stdin reader for orchestrator commands
|
|
512
|
+
if (options.supervised) {
|
|
513
|
+
stopSupervisedReader = startSupervisedStdinReader(stdinWriter, client, (id) => {
|
|
514
|
+
const timer = pendingResponseTimers.get(id);
|
|
515
|
+
if (timer) {
|
|
516
|
+
clearTimeout(timer);
|
|
517
|
+
pendingResponseTimers.delete(id);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
// Ensure stdin is in flowing mode for JSONL reading
|
|
521
|
+
process.stdin.resume();
|
|
522
|
+
}
|
|
435
523
|
// Detect child process crash
|
|
436
524
|
internalProcess.on('exit', (code) => {
|
|
437
525
|
if (!completed) {
|
|
@@ -484,6 +572,10 @@ async function runHeadlessOnce(options, restartCount) {
|
|
|
484
572
|
clearTimeout(timeoutTimer);
|
|
485
573
|
if (idleTimer)
|
|
486
574
|
clearTimeout(idleTimer);
|
|
575
|
+
pendingResponseTimers.forEach((timer) => clearTimeout(timer));
|
|
576
|
+
pendingResponseTimers.clear();
|
|
577
|
+
stopSupervisedReader?.();
|
|
578
|
+
process.stdin.removeListener('close', onStdinClose);
|
|
487
579
|
process.removeListener('SIGINT', signalHandler);
|
|
488
580
|
process.removeListener('SIGTERM', signalHandler);
|
|
489
581
|
await client.stop();
|
package/dist/help-text.js
CHANGED
|
@@ -38,6 +38,8 @@ const SUBCOMMAND_HELP = {
|
|
|
38
38
|
' --timeout N Overall timeout in ms (default: 300000)',
|
|
39
39
|
' --json JSONL event stream to stdout',
|
|
40
40
|
' --model ID Override model',
|
|
41
|
+
' --supervised Forward interactive UI requests to orchestrator via stdout/stdin',
|
|
42
|
+
' --response-timeout N Timeout (ms) for orchestrator response (default: 30000)',
|
|
41
43
|
'',
|
|
42
44
|
'Commands:',
|
|
43
45
|
' auto Run all queued units continuously (default)',
|
|
@@ -59,6 +61,7 @@ const SUBCOMMAND_HELP = {
|
|
|
59
61
|
' gsd headless new-milestone --context spec.md Create milestone from file',
|
|
60
62
|
' cat spec.md | gsd headless new-milestone --context - From stdin',
|
|
61
63
|
' gsd headless new-milestone --context spec.md --auto Create + auto-execute',
|
|
64
|
+
' gsd headless --supervised auto Supervised orchestrator mode',
|
|
62
65
|
'',
|
|
63
66
|
'Exit codes: 0 = complete, 1 = error/timeout, 2 = blocked',
|
|
64
67
|
].join('\n'),
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
getGroupStatus,
|
|
53
53
|
pruneDeadProcesses,
|
|
54
54
|
cleanupAll,
|
|
55
|
+
cleanupSessionProcesses,
|
|
55
56
|
persistManifest,
|
|
56
57
|
loadManifest,
|
|
57
58
|
pushAlert,
|
|
@@ -71,7 +72,7 @@ import { toPosixPath } from "../shared/path-display.js";
|
|
|
71
72
|
// ── Re-exports for consumers ───────────────────────────────────────────────
|
|
72
73
|
|
|
73
74
|
export type { ProcessStatus, ProcessType, BgProcess, BgProcessInfo, OutputDigest, OutputLine, ProcessEvent } from "./types.js";
|
|
74
|
-
export { processes, startProcess, killProcess, restartProcess, cleanupAll } from "./process-manager.js";
|
|
75
|
+
export { processes, startProcess, killProcess, restartProcess, cleanupAll, cleanupSessionProcesses } from "./process-manager.js";
|
|
75
76
|
export { generateDigest, getHighlights, getOutput, formatDigestText } from "./output-formatter.js";
|
|
76
77
|
export { waitForReady, probePort } from "./readiness-detector.js";
|
|
77
78
|
export { sendAndWait, runOnSession, queryShellEnv } from "./interaction.js";
|
|
@@ -136,7 +137,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
136
137
|
});
|
|
137
138
|
|
|
138
139
|
// Session switch resets the agent's context.
|
|
139
|
-
pi.on("session_switch", async () => {
|
|
140
|
+
pi.on("session_switch", async (event, ctx) => {
|
|
141
|
+
latestCtx = ctx;
|
|
142
|
+
if (event.reason === "new" && event.previousSessionFile) {
|
|
143
|
+
await cleanupSessionProcesses(event.previousSessionFile);
|
|
144
|
+
syncLatestCtxCwd();
|
|
145
|
+
if (latestCtx) persistManifest(latestCtx.cwd);
|
|
146
|
+
}
|
|
140
147
|
buildProcessStateAlert("Session was switched.");
|
|
141
148
|
});
|
|
142
149
|
|
|
@@ -232,6 +239,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
232
239
|
"Use 'run' to execute a command on a persistent shell session and block until it completes — returns structured output + exit code. Shell state (env vars, cwd, virtualenvs) persists across runs.",
|
|
233
240
|
"Use 'send_and_wait' for interactive CLIs: send input and wait for expected output pattern.",
|
|
234
241
|
"Use 'env' to check the current working directory and active environment variables of a shell session — useful after cd, source, or export commands.",
|
|
242
|
+
"Background processes are session-scoped by default: a new session reaps them unless you set persist_across_sessions:true.",
|
|
235
243
|
"Use 'restart' to kill and relaunch with the same config — preserves restart count.",
|
|
236
244
|
"Background processes are auto-classified (server/build/test/watcher) based on the command.",
|
|
237
245
|
"Process crashes and errors are automatically surfaced as alerts at the start of your next turn — you don't need to poll.",
|
|
@@ -300,6 +308,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
300
308
|
group: Type.Optional(
|
|
301
309
|
Type.String({ description: "Group name for related processes (for start, group_status)" }),
|
|
302
310
|
),
|
|
311
|
+
persist_across_sessions: Type.Optional(
|
|
312
|
+
Type.Boolean({
|
|
313
|
+
description: "Keep this process running after a new session starts. Default: false.",
|
|
314
|
+
default: false,
|
|
315
|
+
}),
|
|
316
|
+
),
|
|
303
317
|
}),
|
|
304
318
|
|
|
305
319
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
@@ -318,6 +332,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
318
332
|
const bg = startProcess({
|
|
319
333
|
command: params.command,
|
|
320
334
|
cwd: ctx.cwd,
|
|
335
|
+
ownerSessionFile: ctx.sessionManager.getSessionFile() ?? null,
|
|
336
|
+
persistAcrossSessions: params.persist_across_sessions ?? false,
|
|
321
337
|
label: params.label,
|
|
322
338
|
type: params.type as ProcessType | undefined,
|
|
323
339
|
readyPattern: params.ready_pattern,
|
|
@@ -341,6 +357,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
341
357
|
text += ` cwd: ${toPosixPath(bg.cwd)}`;
|
|
342
358
|
|
|
343
359
|
if (bg.group) text += `\n group: ${bg.group}`;
|
|
360
|
+
if (bg.persistAcrossSessions) text += `\n persist_across_sessions: true`;
|
|
344
361
|
if (bg.readyPort) text += `\n ready_port: ${bg.readyPort}`;
|
|
345
362
|
if (bg.readyPattern) text += `\n ready_pattern: ${bg.readyPattern}`;
|
|
346
363
|
if (bg.ports.length > 0) text += `\n detected ports: ${bg.ports.join(", ")}`;
|
|
@@ -67,6 +67,8 @@ export function getInfo(p: BgProcess): BgProcessInfo {
|
|
|
67
67
|
label: p.label,
|
|
68
68
|
command: p.command,
|
|
69
69
|
cwd: p.cwd,
|
|
70
|
+
ownerSessionFile: p.ownerSessionFile,
|
|
71
|
+
persistAcrossSessions: p.persistAcrossSessions,
|
|
70
72
|
startedAt: p.startedAt,
|
|
71
73
|
alive: p.alive,
|
|
72
74
|
exitCode: p.exitCode,
|
|
@@ -138,6 +140,8 @@ export function startProcess(opts: StartOptions): BgProcess {
|
|
|
138
140
|
label: opts.label || command.slice(0, 60),
|
|
139
141
|
command,
|
|
140
142
|
cwd: opts.cwd,
|
|
143
|
+
ownerSessionFile: opts.ownerSessionFile ?? null,
|
|
144
|
+
persistAcrossSessions: opts.persistAcrossSessions ?? false,
|
|
141
145
|
startedAt: Date.now(),
|
|
142
146
|
proc,
|
|
143
147
|
output: [],
|
|
@@ -170,6 +174,8 @@ export function startProcess(opts: StartOptions): BgProcess {
|
|
|
170
174
|
cwd: opts.cwd,
|
|
171
175
|
label: opts.label || command.slice(0, 60),
|
|
172
176
|
processType,
|
|
177
|
+
ownerSessionFile: opts.ownerSessionFile ?? null,
|
|
178
|
+
persistAcrossSessions: opts.persistAcrossSessions ?? false,
|
|
173
179
|
readyPattern: opts.readyPattern || null,
|
|
174
180
|
readyPort: opts.readyPort || null,
|
|
175
181
|
group: opts.group || null,
|
|
@@ -312,6 +318,8 @@ export async function restartProcess(id: string): Promise<BgProcess | null> {
|
|
|
312
318
|
cwd: config.cwd,
|
|
313
319
|
label: config.label,
|
|
314
320
|
type: config.processType,
|
|
321
|
+
ownerSessionFile: config.ownerSessionFile,
|
|
322
|
+
persistAcrossSessions: config.persistAcrossSessions,
|
|
315
323
|
readyPattern: config.readyPattern || undefined,
|
|
316
324
|
readyPort: config.readyPort || undefined,
|
|
317
325
|
group: config.group || undefined,
|
|
@@ -367,6 +375,41 @@ export function cleanupAll(): void {
|
|
|
367
375
|
processes.clear();
|
|
368
376
|
}
|
|
369
377
|
|
|
378
|
+
async function waitForProcessExit(bg: BgProcess, timeoutMs: number): Promise<boolean> {
|
|
379
|
+
if (!bg.alive) return true;
|
|
380
|
+
await new Promise<void>((resolve) => {
|
|
381
|
+
const done = () => resolve();
|
|
382
|
+
const timer = setTimeout(done, timeoutMs);
|
|
383
|
+
bg.proc.once("exit", () => {
|
|
384
|
+
clearTimeout(timer);
|
|
385
|
+
resolve();
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
return !bg.alive;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export async function cleanupSessionProcesses(
|
|
392
|
+
sessionFile: string,
|
|
393
|
+
options?: { graceMs?: number },
|
|
394
|
+
): Promise<string[]> {
|
|
395
|
+
const graceMs = Math.max(0, options?.graceMs ?? 300);
|
|
396
|
+
const matches = Array.from(processes.values()).filter(
|
|
397
|
+
(bg) => bg.alive && !bg.persistAcrossSessions && bg.ownerSessionFile === sessionFile,
|
|
398
|
+
);
|
|
399
|
+
if (matches.length === 0) return [];
|
|
400
|
+
|
|
401
|
+
for (const bg of matches) {
|
|
402
|
+
killProcess(bg.id, "SIGTERM");
|
|
403
|
+
}
|
|
404
|
+
if (graceMs > 0) {
|
|
405
|
+
await Promise.all(matches.map((bg) => waitForProcessExit(bg, graceMs)));
|
|
406
|
+
}
|
|
407
|
+
for (const bg of matches) {
|
|
408
|
+
if (bg.alive) killProcess(bg.id, "SIGKILL");
|
|
409
|
+
}
|
|
410
|
+
return matches.map((bg) => bg.id);
|
|
411
|
+
}
|
|
412
|
+
|
|
370
413
|
// ── Persistence ────────────────────────────────────────────────────────────
|
|
371
414
|
|
|
372
415
|
export function getManifestPath(cwd: string): string {
|
|
@@ -384,6 +427,8 @@ export function persistManifest(cwd: string): void {
|
|
|
384
427
|
label: p.label,
|
|
385
428
|
command: p.command,
|
|
386
429
|
cwd: p.cwd,
|
|
430
|
+
ownerSessionFile: p.ownerSessionFile,
|
|
431
|
+
persistAcrossSessions: p.persistAcrossSessions,
|
|
387
432
|
startedAt: p.startedAt,
|
|
388
433
|
processType: p.processType,
|
|
389
434
|
group: p.group,
|
|
@@ -53,6 +53,10 @@ export interface BgProcess {
|
|
|
53
53
|
label: string;
|
|
54
54
|
command: string;
|
|
55
55
|
cwd: string;
|
|
56
|
+
/** Session file that created this process (used for per-session cleanup) */
|
|
57
|
+
ownerSessionFile: string | null;
|
|
58
|
+
/** Whether this process should survive a new-session boundary */
|
|
59
|
+
persistAcrossSessions: boolean;
|
|
56
60
|
startedAt: number;
|
|
57
61
|
proc: import("node:child_process").ChildProcess;
|
|
58
62
|
/** Unified chronologically-interleaved output buffer */
|
|
@@ -103,7 +107,17 @@ export interface BgProcess {
|
|
|
103
107
|
/** Restart count */
|
|
104
108
|
restartCount: number;
|
|
105
109
|
/** Original start config for restart */
|
|
106
|
-
startConfig: {
|
|
110
|
+
startConfig: {
|
|
111
|
+
command: string;
|
|
112
|
+
cwd: string;
|
|
113
|
+
label: string;
|
|
114
|
+
processType: ProcessType;
|
|
115
|
+
ownerSessionFile: string | null;
|
|
116
|
+
persistAcrossSessions: boolean;
|
|
117
|
+
readyPattern: string | null;
|
|
118
|
+
readyPort: number | null;
|
|
119
|
+
group: string | null;
|
|
120
|
+
};
|
|
107
121
|
}
|
|
108
122
|
|
|
109
123
|
export interface BgProcessInfo {
|
|
@@ -111,6 +125,8 @@ export interface BgProcessInfo {
|
|
|
111
125
|
label: string;
|
|
112
126
|
command: string;
|
|
113
127
|
cwd: string;
|
|
128
|
+
ownerSessionFile: string | null;
|
|
129
|
+
persistAcrossSessions: boolean;
|
|
114
130
|
startedAt: number;
|
|
115
131
|
alive: boolean;
|
|
116
132
|
exitCode: number | null;
|
|
@@ -133,6 +149,8 @@ export interface BgProcessInfo {
|
|
|
133
149
|
export interface StartOptions {
|
|
134
150
|
command: string;
|
|
135
151
|
cwd: string;
|
|
152
|
+
ownerSessionFile?: string | null;
|
|
153
|
+
persistAcrossSessions?: boolean;
|
|
136
154
|
label?: string;
|
|
137
155
|
type?: ProcessType;
|
|
138
156
|
readyPattern?: string;
|
|
@@ -154,6 +172,8 @@ export interface ProcessManifest {
|
|
|
154
172
|
label: string;
|
|
155
173
|
command: string;
|
|
156
174
|
cwd: string;
|
|
175
|
+
ownerSessionFile: string | null;
|
|
176
|
+
persistAcrossSessions: boolean;
|
|
157
177
|
startedAt: number;
|
|
158
178
|
processType: ProcessType;
|
|
159
179
|
group: string | null;
|