commandmate 0.3.5 → 0.4.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/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +19 -23
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +5 -5
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/react-loadable-manifest.json +69 -55
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/route.js +8 -8
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route.js +1 -2
- package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/route.js +2 -2
- package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/login/page.js +1 -1
- package/.next/server/app/login/page.js.nft.json +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page.js +3 -3
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js +4 -4
- package/.next/server/app/worktrees/[id]/files/[...path]/page.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +6 -6
- package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page.js +2 -4
- package/.next/server/app/worktrees/[id]/terminal/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +8 -8
- package/.next/server/chunks/{3294.js → 1628.js} +3 -3
- package/.next/server/chunks/185.js +36 -0
- package/.next/server/chunks/3860.js +1 -1
- package/.next/server/chunks/4893.js +2 -2
- package/.next/server/chunks/4952.js +1 -1
- package/.next/server/chunks/5488.js +6 -6
- package/.next/server/chunks/7425.js +34 -31
- package/.next/server/chunks/7566.js +2 -2
- package/.next/server/chunks/8199.js +1 -0
- package/.next/server/chunks/8585.js +1 -1
- package/.next/server/chunks/8693.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/12-00c528d46a0a0a1d.js +1 -0
- package/.next/static/chunks/{13.feeafc7cc620f8c4.js → 13.b9521543496f4468.js} +1 -1
- package/.next/static/chunks/1334.bfedf44ee9fe2761.js +1 -0
- package/.next/static/chunks/143.eb6b4671490cd223.js +1 -0
- package/.next/static/chunks/{3574.7a94c27e6a496a56.js → 1442.74b5f4de9a4b4e1b.js} +1 -1
- package/.next/static/chunks/2083-b5bed0c77cc53281.js +1 -0
- package/.next/static/chunks/2725.eb2d236c8030711c.js +1 -0
- package/.next/static/chunks/3398-3d40a17387bd554b.js +1 -0
- package/.next/static/chunks/3516.3c576047408cae6b.js +1 -0
- package/.next/static/chunks/3559.422c6ca760b85750.js +1 -0
- package/.next/static/chunks/3956.52c5b9a0071a641d.js +1 -0
- package/.next/static/chunks/4012.32b576a4fa621774.js +1 -0
- package/.next/static/chunks/4212.e7ba1009bc1da62d.js +131 -0
- package/.next/static/chunks/4303.caf91e86105d5e70.js +1 -0
- package/.next/static/chunks/4327.4dcda9b6fab6a385.js +82 -0
- package/.next/static/chunks/4671.d86d21d0dfdace41.js +1 -0
- package/.next/static/chunks/5518.ec88dcb5a27b17fe.js +1 -0
- package/.next/static/chunks/6434.08d262283371d333.js +1 -0
- package/.next/static/chunks/{656.5e2de0173f5a06bd.js → 656.dc26b973d07d9627.js} +5 -5
- package/.next/static/chunks/7119.01777af21b55740c.js +1 -0
- package/.next/static/chunks/7293.fb88bb102af4aa04.js +1 -0
- package/.next/static/chunks/8913-40625650292eb3d0.js +1 -0
- package/.next/static/chunks/8977.fc18b8260cd8bc1f.js +1 -0
- package/.next/static/chunks/9552.d959149efd41e84b.js +1 -0
- package/.next/static/chunks/app/layout-7198a7a49aa21a97.js +1 -0
- package/.next/static/chunks/app/page-7498cf75e69d9227.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-0599f64a8e80d255.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-94ad7a1ce1f0c440.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/terminal/page-175b618c047bc992.js +1 -0
- package/.next/static/chunks/d3ac728e.daf595a898e9b720.js +1 -0
- package/.next/static/chunks/webpack-f7111aab807d73b9.js +1 -0
- package/.next/static/css/f7dc01350168df01.css +3 -0
- package/.next/trace +5 -5
- package/README.md +66 -56
- package/dist/server/server.js +5 -0
- package/dist/server/src/lib/auto-yes-manager.js +58 -18
- package/dist/server/src/lib/claude-session.js +9 -3
- package/dist/server/src/lib/cli-session.js +60 -10
- package/dist/server/src/lib/cli-tools/codex.js +7 -7
- package/dist/server/src/lib/cli-tools/gemini.js +3 -0
- package/dist/server/src/lib/cli-tools/opencode-config.js +179 -33
- package/dist/server/src/lib/cli-tools/opencode.js +5 -0
- package/dist/server/src/lib/cli-tools/vibe-local.js +3 -0
- package/dist/server/src/lib/cmate-parser.js +7 -7
- package/dist/server/src/lib/db-migrations.js +18 -1
- package/dist/server/src/lib/errors.js +153 -0
- package/dist/server/src/lib/prompt-answer-sender.js +3 -0
- package/dist/server/src/lib/prompt-detector.js +49 -7
- package/dist/server/src/lib/resource-cleanup.js +257 -0
- package/dist/server/src/lib/schedule-manager.js +269 -83
- package/dist/server/src/lib/tmux-capture-cache.js +221 -0
- package/dist/server/src/lib/tmux.js +41 -20
- package/dist/server/src/types/markdown-editor.js +9 -1
- package/package.json +11 -8
- package/.next/server/chunks/539.js +0 -35
- package/.next/server/chunks/7458.js +0 -1
- package/.next/server/chunks/7808.js +0 -1
- package/.next/static/chunks/1038-3509435b68c0967e.js +0 -1
- package/.next/static/chunks/1098.49268c9fe1b028fa.js +0 -1
- package/.next/static/chunks/2335-98a211e00b94c7ac.js +0 -1
- package/.next/static/chunks/3559.f073f72c4466ce0e.js +0 -1
- package/.next/static/chunks/3843.3fdda732987f7bb8.js +0 -1
- package/.next/static/chunks/4212.52c1bb34fc97d0d0.js +0 -131
- package/.next/static/chunks/4327.157a4c226d919531.js +0 -60
- package/.next/static/chunks/4362.7bd6f0282e49d79b.js +0 -1
- package/.next/static/chunks/4721.40615a5f4f32b5fb.js +0 -1
- package/.next/static/chunks/5112.17318d1c6b28044b.js +0 -1
- package/.next/static/chunks/6406.9653f0d41ab85059.js +0 -1
- package/.next/static/chunks/6792.3c01ac4dda4b5c6d.js +0 -1
- package/.next/static/chunks/8091-d65d2ab6daed23c6.js +0 -1
- package/.next/static/chunks/8125.245a9df052d274fb.js +0 -1
- package/.next/static/chunks/8522.1607e96011c66877.js +0 -1
- package/.next/static/chunks/8841.dadeb1ece8e46004.js +0 -1
- package/.next/static/chunks/8885.f8d9912b40d74811.js +0 -1
- package/.next/static/chunks/9178-88850a7c48deea07.js +0 -1
- package/.next/static/chunks/9552.b7dfb7903ead934b.js +0 -1
- package/.next/static/chunks/app/layout-9110f9a5e41c6bf4.js +0 -1
- package/.next/static/chunks/app/page-9e523a8f415bc707.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-4a3c0861367e0391.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-8fb4dc30b58a5681.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/terminal/page-5d85a7e508ce36d3.js +0 -1
- package/.next/static/chunks/d3ac728e.6c9c508274d4d2d5.js +0 -1
- package/.next/static/chunks/webpack-81c97591dd5567ac.js +0 -1
- package/.next/static/css/45b3a41370668314.css +0 -3
- /package/.next/static/chunks/{30d07d85-393352a92199f695.js → 30d07d85.1dc99a921fc18e34.js} +0 -0
- /package/.next/static/{p3hosTZoJ22r35fWwUoLr → dwGMLEU53HOvFOWqiZOT0}/_buildManifest.js +0 -0
- /package/.next/static/{p3hosTZoJ22r35fWwUoLr → dwGMLEU53HOvFOWqiZOT0}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -10,45 +10,70 @@
|
|
|
10
10
|
[English](./README.md) | [日本語](./docs/ja/README.md)
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
<img src="./docs/images/demo-
|
|
13
|
+
<img src="./docs/images/demo-desktop.gif" width="600" alt="CommandMate Desktop Demo" />
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
|
-
> **
|
|
16
|
+
> **Move issues forward, not terminal tabs.**
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
CommandMate is an IDE for issue-driven AI development.
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
npx commandmate
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
**From install to
|
|
24
|
+
**From install to your first session in 60 seconds.** macOS / Linux · Node.js v20+ · npm · git · tmux
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
Close the lid and every session dies.
|
|
30
|
-
**CommandMate keeps it alive, and puts the controls on your phone.**
|
|
28
|
+
Instead of jumping straight into implementation, you define an issue, refine it with AI, review the direction, generate a plan, and then let your coding agent execute. CommandMate helps you run multiple issues in parallel with Git worktrees, choose the right agent for each issue, and keep work moving even when you leave your desk.
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
If your workflow is shifting from "writing code yourself" to "defining issues, reviewing direction, and accepting outcomes," CommandMate can become the center of your development workflow.
|
|
33
31
|
|
|
34
32
|
<p align="center">
|
|
35
|
-
<img src="./docs/images/demo-
|
|
33
|
+
<img src="./docs/images/demo-mobile.gif" width="300" alt="CommandMate Mobile Demo" />
|
|
36
34
|
</p>
|
|
37
35
|
|
|
36
|
+
Works on desktop and mobile — monitor and steer sessions from any browser, including your phone.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Issue-Driven Development
|
|
41
|
+
|
|
42
|
+
CommandMate recommends the following development method. By adopting this process, humans can focus on defining issues and verifying final outputs.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Define Issue → Refine with AI → Review Direction → Generate Plan → Agent Executes
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Step | Command | What happens |
|
|
49
|
+
|------|---------|-------------|
|
|
50
|
+
| Refine the issue | `/issue-enhance` | AI asks clarifying questions and fills in missing details |
|
|
51
|
+
| Review the issue | `/multi-stage-issue-review` | Multi-stage review (consistency, impact scope) with automated fixes |
|
|
52
|
+
| Review the design | `/multi-stage-design-review` | 4-stage review (general → consistency → impact → security) |
|
|
53
|
+
| Plan the work | `/work-plan` | Generates a task breakdown with dependencies |
|
|
54
|
+
| Implement via TDD | `/tdd-impl` | Red-Green-Refactor cycle, automated |
|
|
55
|
+
| Verify acceptance | `/acceptance-test` | Validates all acceptance criteria from the issue |
|
|
56
|
+
| Create the PR | `/create-pr` | Auto-generates title, description, and labels |
|
|
57
|
+
| Dev (full) | `/pm-auto-dev` | TDD implementation → acceptance test → refactoring → progress report |
|
|
58
|
+
| Issue → Dev (full) | `/pm-auto-issue2dev` | Issue review → design review → work plan → TDD → acceptance test → refactoring → progress report |
|
|
59
|
+
| Design → Dev (full) | `/pm-auto-design2dev` | Design review → work plan → TDD → acceptance test → refactoring → progress report |
|
|
60
|
+
|
|
61
|
+
For details, see the [issues](https://github.com/Kewton/CommandMate/issues), [dev reports](./dev-reports/issue/), and [workflow examples](./docs/en/user-guide/workflow-examples.md) in the CommandMate repository.
|
|
62
|
+
|
|
38
63
|
---
|
|
39
64
|
|
|
40
65
|
## Key Features
|
|
41
66
|
|
|
42
67
|
| Feature | What it does | Why it matters |
|
|
43
68
|
|---------|-------------|----------------|
|
|
44
|
-
| **
|
|
45
|
-
| **
|
|
46
|
-
| **
|
|
47
|
-
| **
|
|
48
|
-
| **Markdown Editor** |
|
|
69
|
+
| **Git Worktree Sessions** | One session per worktree, parallel execution | Multiple issues progress simultaneously without interference |
|
|
70
|
+
| **Multi-Agent Support** | Choose Claude Code, Codex, Gemini, or local models per issue | Pick the right agent for each task |
|
|
71
|
+
| **Auto Yes Mode** | Agent runs without stopping for confirmations | No babysitting — the agent keeps working while you're away |
|
|
72
|
+
| **Web UI (Desktop & Mobile)** | Full session control from any browser | Monitor and steer from your desk or your phone |
|
|
73
|
+
| **File Viewer & Markdown Editor** | Browse and edit worktree files in the browser | Review changes and update AI instructions without opening an IDE |
|
|
49
74
|
| **Screenshot Instructions** | Attach images to your prompts | Snap a bug → "Fix this" — the agent sees the screenshot |
|
|
75
|
+
| **Scheduled Execution** | Cron-based auto-run via CMATE.md | Daily reviews, nightly tests — agents work on a schedule |
|
|
50
76
|
| **Token Authentication** | SHA-256 hashed token + HTTPS + rate limiting | Secure remote access — no credentials leaked, brute-force protected |
|
|
51
|
-
| **Scheduled Execution** | Cron-based auto-run via CMATE.md | Daily reviews, nightly tests — Claude Code works on a schedule |
|
|
52
77
|
|
|
53
78
|
---
|
|
54
79
|
|
|
@@ -56,48 +81,11 @@ Of course, it works great on desktop too — the two-column layout gives you a f
|
|
|
56
81
|
|
|
57
82
|
| Scenario | How CommandMate helps |
|
|
58
83
|
|----------|----------------------|
|
|
59
|
-
| **
|
|
60
|
-
| **
|
|
61
|
-
| **Overnight
|
|
84
|
+
| **Parallel issue development** | Run multiple issues in separate worktrees, each with its own agent session |
|
|
85
|
+
| **Issue refinement** | Define an issue, let AI fill gaps, review before any code is written |
|
|
86
|
+
| **Overnight execution** | Queue issues with scheduled execution — check progress in the morning |
|
|
87
|
+
| **Mobile review** | Review AI-generated changes and steer direction from your phone |
|
|
62
88
|
| **Visual bug fix** | Snap a UI bug on your phone, send it with "Fix this" |
|
|
63
|
-
| **Parallel tasks** | Run multiple worktree sessions, manage them all from one dashboard |
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## Comparison
|
|
68
|
-
|
|
69
|
-
| Feature | CommandMate | Remote Control (Official) | Happy Coder | claude-squad | Omnara |
|
|
70
|
-
|---------|:-----------:|:------------------------:|:-----------:|:------------:|:------:|
|
|
71
|
-
| Auto Yes Mode | Yes | No | No | Yes (TUI only) | No |
|
|
72
|
-
| Git Worktree Management | Yes | No | No | Yes (TUI only) | No |
|
|
73
|
-
| Parallel Sessions | Yes | **No (1 only)** | Yes | Yes | No |
|
|
74
|
-
| Mobile Web UI | Yes | Yes (claude.ai) | Yes | **No** | Yes |
|
|
75
|
-
| File Viewer | Yes | No | No | No | No |
|
|
76
|
-
| Markdown Editor | Yes | No | No | No | No |
|
|
77
|
-
| Screenshot Instructions | Yes | No | No | Not possible | No |
|
|
78
|
-
| Scheduled Execution | Yes | No | No | No | No |
|
|
79
|
-
| Survives Laptop Close | Yes (daemon) | **No (terminal must stay open)** | Yes | Yes | Yes |
|
|
80
|
-
| Token Authentication | Yes | N/A (Anthropic account) | N/A (app) | No | N/A (cloud) |
|
|
81
|
-
| Free / OSS | Yes | Requires Pro/Max | Free + Paid | Yes | $20/mo |
|
|
82
|
-
| Runs 100% Locally | Yes | Via Anthropic API | Server-routed | Yes | Cloud fallback |
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## Screenshots
|
|
87
|
-
|
|
88
|
-
### Desktop
|
|
89
|
-
|
|
90
|
-

|
|
91
|
-
|
|
92
|
-
### Mobile
|
|
93
|
-
|
|
94
|
-
| Top Page | Worktree (History) | Worktree (Terminal) |
|
|
95
|
-
|----------|-------------------|-------------------|
|
|
96
|
-
|  |  |  |
|
|
97
|
-
|
|
98
|
-
### Worktree Detail (Desktop)
|
|
99
|
-
|
|
100
|
-

|
|
101
89
|
|
|
102
90
|
---
|
|
103
91
|
|
|
@@ -302,6 +290,28 @@ npm start
|
|
|
302
290
|
|
|
303
291
|
---
|
|
304
292
|
|
|
293
|
+
<details>
|
|
294
|
+
<summary><strong>Comparison</strong></summary>
|
|
295
|
+
|
|
296
|
+
| Feature | CommandMate | Remote Control (Official) | Happy Coder | claude-squad | Omnara |
|
|
297
|
+
|---------|:-----------:|:------------------------:|:-----------:|:------------:|:------:|
|
|
298
|
+
| Auto Yes Mode | Yes | No | No | Yes (TUI only) | No |
|
|
299
|
+
| Git Worktree Management | Yes | No | No | Yes (TUI only) | No |
|
|
300
|
+
| Parallel Sessions | Yes | **No (1 only)** | Yes | Yes | No |
|
|
301
|
+
| Mobile Web UI | Yes | Yes (claude.ai) | Yes | **No** | Yes |
|
|
302
|
+
| File Viewer | Yes | No | No | No | No |
|
|
303
|
+
| Markdown Editor | Yes | No | No | No | No |
|
|
304
|
+
| Screenshot Instructions | Yes | No | No | Not possible | No |
|
|
305
|
+
| Scheduled Execution | Yes | No | No | No | No |
|
|
306
|
+
| Survives Laptop Close | Yes (daemon) | **No (terminal must stay open)** | Yes | Yes | Yes |
|
|
307
|
+
| Token Authentication | Yes | N/A (Anthropic account) | N/A (app) | No | N/A (cloud) |
|
|
308
|
+
| Free / OSS | Yes | Requires Pro/Max | Free + Paid | Yes | $20/mo |
|
|
309
|
+
| Runs 100% Locally | Yes | Via Anthropic API | Server-routed | Yes | Cloud fallback |
|
|
310
|
+
|
|
311
|
+
</details>
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
305
315
|
## Documentation
|
|
306
316
|
|
|
307
317
|
| Document | Description |
|
package/dist/server/server.js
CHANGED
|
@@ -40,6 +40,7 @@ const db_instance_1 = require("./src/lib/db-instance");
|
|
|
40
40
|
const response_poller_1 = require("./src/lib/response-poller");
|
|
41
41
|
const auto_yes_manager_1 = require("./src/lib/auto-yes-manager");
|
|
42
42
|
const schedule_manager_1 = require("./src/lib/schedule-manager");
|
|
43
|
+
const resource_cleanup_1 = require("./src/lib/resource-cleanup");
|
|
43
44
|
const db_migrations_1 = require("./src/lib/db-migrations");
|
|
44
45
|
const env_1 = require("./src/lib/env");
|
|
45
46
|
const db_repository_1 = require("./src/lib/db-repository");
|
|
@@ -230,6 +231,8 @@ app.prepare().then(() => {
|
|
|
230
231
|
await initializeWorktrees();
|
|
231
232
|
// [S3-010] Initialize schedule manager AFTER worktrees are ready
|
|
232
233
|
(0, schedule_manager_1.initScheduleManager)();
|
|
234
|
+
// Issue #404: Initialize resource cleanup AFTER schedule manager
|
|
235
|
+
(0, resource_cleanup_1.initResourceCleanup)();
|
|
233
236
|
});
|
|
234
237
|
// Graceful shutdown with timeout
|
|
235
238
|
let isShuttingDown = false;
|
|
@@ -246,6 +249,8 @@ app.prepare().then(() => {
|
|
|
246
249
|
(0, auto_yes_manager_1.stopAllAutoYesPolling)();
|
|
247
250
|
// Issue #294: Stop all scheduled executions (SIGKILL fire-and-forget)
|
|
248
251
|
(0, schedule_manager_1.stopAllSchedules)();
|
|
252
|
+
// Issue #404: Stop resource cleanup timer
|
|
253
|
+
(0, resource_cleanup_1.stopResourceCleanup)();
|
|
249
254
|
// Close WebSocket connections immediately (don't wait)
|
|
250
255
|
(0, ws_server_1.closeWebSocket)();
|
|
251
256
|
// Force exit after 3 seconds if graceful shutdown fails
|
|
@@ -29,6 +29,9 @@ exports.detectAndRespondToPrompt = detectAndRespondToPrompt;
|
|
|
29
29
|
exports.startAutoYesPolling = startAutoYesPolling;
|
|
30
30
|
exports.stopAutoYesPolling = stopAutoYesPolling;
|
|
31
31
|
exports.stopAllAutoYesPolling = stopAllAutoYesPolling;
|
|
32
|
+
exports.deleteAutoYesState = deleteAutoYesState;
|
|
33
|
+
exports.getAutoYesStateWorktreeIds = getAutoYesStateWorktreeIds;
|
|
34
|
+
exports.getAutoYesPollerWorktreeIds = getAutoYesPollerWorktreeIds;
|
|
32
35
|
const cli_session_1 = require("./cli-session");
|
|
33
36
|
const prompt_detector_1 = require("./prompt-detector");
|
|
34
37
|
const auto_yes_resolver_1 = require("./auto-yes-resolver");
|
|
@@ -37,6 +40,8 @@ const manager_1 = require("./cli-tools/manager");
|
|
|
37
40
|
const cli_patterns_1 = require("./cli-patterns");
|
|
38
41
|
const auto_yes_config_1 = require("../config/auto-yes-config");
|
|
39
42
|
const prompt_key_1 = require("./prompt-key");
|
|
43
|
+
const errors_1 = require("./errors");
|
|
44
|
+
const tmux_capture_cache_1 = require("./tmux-capture-cache");
|
|
40
45
|
// =============================================================================
|
|
41
46
|
// Constants (Issue #138)
|
|
42
47
|
// =============================================================================
|
|
@@ -62,16 +67,6 @@ exports.MAX_CONCURRENT_POLLERS = 50;
|
|
|
62
67
|
exports.THINKING_CHECK_LINE_COUNT = 50;
|
|
63
68
|
/** Worktree ID validation pattern (security: prevent command injection) */
|
|
64
69
|
const WORKTREE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
65
|
-
/**
|
|
66
|
-
* Extract error message from unknown error type.
|
|
67
|
-
* Provides consistent error message extraction across the module (DRY).
|
|
68
|
-
*
|
|
69
|
-
* @param error - Unknown error object
|
|
70
|
-
* @returns Error message string, or 'Unknown error' for non-Error values
|
|
71
|
-
*/
|
|
72
|
-
function getErrorMessage(error) {
|
|
73
|
-
return error instanceof Error ? error.message : 'Unknown error';
|
|
74
|
-
}
|
|
75
70
|
/** In-memory storage for auto-yes states (globalThis for hot reload persistence) */
|
|
76
71
|
const autoYesStates = globalThis.__autoYesStates ??
|
|
77
72
|
(globalThis.__autoYesStates = new Map());
|
|
@@ -485,12 +480,18 @@ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, clea
|
|
|
485
480
|
const manager = manager_1.CLIToolManager.getInstance();
|
|
486
481
|
const cliTool = manager.getTool(cliToolId);
|
|
487
482
|
const sessionName = cliTool.getSessionName(worktreeId);
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
483
|
+
try {
|
|
484
|
+
await (0, prompt_answer_sender_1.sendPromptAnswer)({
|
|
485
|
+
sessionName,
|
|
486
|
+
answer,
|
|
487
|
+
cliToolId,
|
|
488
|
+
promptData: promptDetection.promptData,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
finally {
|
|
492
|
+
// Issue #405: Ensure cache invalidation even if sendPromptAnswer throws
|
|
493
|
+
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
494
|
+
}
|
|
494
495
|
// 5. Update timestamp and reset error count
|
|
495
496
|
updateLastServerResponseTimestamp(worktreeId, Date.now());
|
|
496
497
|
resetErrorCount(worktreeId);
|
|
@@ -505,7 +506,7 @@ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, clea
|
|
|
505
506
|
// incrementErrorCount is called here, and this function never throws,
|
|
506
507
|
// preventing double incrementErrorCount in the outer pollAutoYes() catch.
|
|
507
508
|
incrementErrorCount(worktreeId);
|
|
508
|
-
console.warn(`[Auto-Yes Poller] Error in detectAndRespondToPrompt for worktree ${worktreeId}: ${getErrorMessage(error)}`);
|
|
509
|
+
console.warn(`[Auto-Yes Poller] Error in detectAndRespondToPrompt for worktree ${worktreeId}: ${(0, errors_1.getErrorMessage)(error)}`);
|
|
509
510
|
return 'error';
|
|
510
511
|
}
|
|
511
512
|
}
|
|
@@ -557,7 +558,7 @@ async function pollAutoYes(worktreeId, cliToolId) {
|
|
|
557
558
|
// errors only. detectAndRespondToPrompt() catches its own errors and returns
|
|
558
559
|
// 'error' instead of throwing (preventing double incrementErrorCount).
|
|
559
560
|
incrementErrorCount(worktreeId);
|
|
560
|
-
console.warn(`[Auto-Yes Poller] Error for worktree ${worktreeId}: ${getErrorMessage(error)}`);
|
|
561
|
+
console.warn(`[Auto-Yes Poller] Error for worktree ${worktreeId}: ${(0, errors_1.getErrorMessage)(error)}`);
|
|
561
562
|
}
|
|
562
563
|
// Schedule next poll (catch block fallthrough or other paths)
|
|
563
564
|
scheduleNextPoll(worktreeId, cliToolId);
|
|
@@ -656,3 +657,42 @@ function stopAllAutoYesPolling() {
|
|
|
656
657
|
}
|
|
657
658
|
autoYesPollerStates.clear();
|
|
658
659
|
}
|
|
660
|
+
// =============================================================================
|
|
661
|
+
// Cleanup Functions (Issue #404: Resource leak prevention)
|
|
662
|
+
// =============================================================================
|
|
663
|
+
/**
|
|
664
|
+
* Delete the auto-yes state for a worktree.
|
|
665
|
+
* Used during worktree deletion to prevent memory leaks in the autoYesStates Map.
|
|
666
|
+
*
|
|
667
|
+
* [SEC-404-001] Validates worktreeId before deletion.
|
|
668
|
+
*
|
|
669
|
+
* @param worktreeId - Worktree identifier (must pass isValidWorktreeId)
|
|
670
|
+
* @returns true if worktreeId was valid (deletion attempted), false if invalid
|
|
671
|
+
*/
|
|
672
|
+
function deleteAutoYesState(worktreeId) {
|
|
673
|
+
if (!isValidWorktreeId(worktreeId)) {
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
autoYesStates.delete(worktreeId);
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Get all worktree IDs that have auto-yes state entries.
|
|
681
|
+
* Used by periodic resource cleanup to detect orphaned entries.
|
|
682
|
+
*
|
|
683
|
+
* @internal Exported for resource-cleanup and testing purposes.
|
|
684
|
+
* @returns Array of worktree IDs present in the autoYesStates Map
|
|
685
|
+
*/
|
|
686
|
+
function getAutoYesStateWorktreeIds() {
|
|
687
|
+
return Array.from(autoYesStates.keys());
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Get all worktree IDs that have active auto-yes poller entries.
|
|
691
|
+
* Used by periodic resource cleanup to detect orphaned entries.
|
|
692
|
+
*
|
|
693
|
+
* @internal Exported for resource-cleanup and testing purposes.
|
|
694
|
+
* @returns Array of worktree IDs present in the autoYesPollerStates Map
|
|
695
|
+
*/
|
|
696
|
+
function getAutoYesPollerWorktreeIds() {
|
|
697
|
+
return Array.from(autoYesPollerStates.keys());
|
|
698
|
+
}
|
|
@@ -20,6 +20,7 @@ exports.restartClaudeSession = restartClaudeSession;
|
|
|
20
20
|
const tmux_1 = require("./tmux");
|
|
21
21
|
const cli_patterns_1 = require("./cli-patterns");
|
|
22
22
|
const pasted_text_helper_1 = require("./pasted-text-helper");
|
|
23
|
+
const tmux_capture_cache_1 = require("./tmux-capture-cache");
|
|
23
24
|
const child_process_1 = require("child_process");
|
|
24
25
|
const util_1 = require("util");
|
|
25
26
|
const promises_1 = require("fs/promises");
|
|
@@ -256,8 +257,9 @@ async function getCleanPaneOutput(sessionName, lines = 50) {
|
|
|
256
257
|
* Verify that Claude CLI is actually running inside a tmux session
|
|
257
258
|
* Detects broken sessions where tmux exists but Claude failed to start
|
|
258
259
|
*
|
|
259
|
-
* @internal
|
|
260
|
-
*
|
|
260
|
+
* Issue #405: Promoted from @internal to production export.
|
|
261
|
+
* Used by worktrees/route.ts and worktrees/[id]/route.ts for
|
|
262
|
+
* health-aware session status with listSessions() batch optimization.
|
|
261
263
|
*
|
|
262
264
|
* @param sessionName - tmux session name
|
|
263
265
|
* @returns HealthCheckResult with healthy status and optional reason
|
|
@@ -627,6 +629,8 @@ async function sendMessageToClaude(worktreeId, message) {
|
|
|
627
629
|
if (message.includes('\n')) {
|
|
628
630
|
await (0, pasted_text_helper_1.detectAndResendIfPastedText)(sessionName);
|
|
629
631
|
}
|
|
632
|
+
// Issue #405: Invalidate cache after sending message
|
|
633
|
+
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
630
634
|
console.log(`Sent message to Claude session: ${sessionName}`);
|
|
631
635
|
}
|
|
632
636
|
/**
|
|
@@ -675,7 +679,9 @@ async function stopClaudeSession(worktreeId) {
|
|
|
675
679
|
if (exists) {
|
|
676
680
|
await (0, tmux_1.sendKeys)(sessionName, '', false);
|
|
677
681
|
// Send Ctrl+D (ASCII 4)
|
|
678
|
-
await
|
|
682
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'C-d');
|
|
683
|
+
// Issue #405: Invalidate cache after sending stop signal
|
|
684
|
+
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
679
685
|
// Wait a moment for Claude to exit
|
|
680
686
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
681
687
|
}
|
|
@@ -2,14 +2,20 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Generic CLI session management
|
|
4
4
|
* Manages CLI tool sessions (Claude, Codex, Gemini) within tmux
|
|
5
|
+
*
|
|
6
|
+
* Issue #405: Cache integration via tmux-capture-cache.ts
|
|
7
|
+
* - captureSessionOutput() uses getOrFetchCapture() for cache-backed capture
|
|
8
|
+
* - captureSessionOutputFresh() bypasses cache for prompt-response verification
|
|
5
9
|
*/
|
|
6
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
11
|
exports.isSessionRunning = isSessionRunning;
|
|
8
12
|
exports.captureSessionOutput = captureSessionOutput;
|
|
13
|
+
exports.captureSessionOutputFresh = captureSessionOutputFresh;
|
|
9
14
|
exports.getSessionName = getSessionName;
|
|
10
15
|
const tmux_1 = require("./tmux");
|
|
11
16
|
const manager_1 = require("./cli-tools/manager");
|
|
12
17
|
const logger_1 = require("./logger");
|
|
18
|
+
const tmux_capture_cache_1 = require("./tmux-capture-cache");
|
|
13
19
|
const logger = (0, logger_1.createLogger)('cli-session');
|
|
14
20
|
/**
|
|
15
21
|
* Check if CLI tool session is running
|
|
@@ -25,7 +31,10 @@ async function isSessionRunning(worktreeId, cliToolId) {
|
|
|
25
31
|
return await (0, tmux_1.hasSession)(sessionName);
|
|
26
32
|
}
|
|
27
33
|
/**
|
|
28
|
-
* Capture CLI session output
|
|
34
|
+
* Capture CLI session output (cache-backed)
|
|
35
|
+
*
|
|
36
|
+
* Issue #405: Uses getOrFetchCapture() for TTL-based caching with singleflight
|
|
37
|
+
* deduplication. Interface is unchanged for backward compatibility.
|
|
29
38
|
*
|
|
30
39
|
* @param worktreeId - Worktree ID
|
|
31
40
|
* @param cliToolId - CLI tool ID (claude, codex, gemini)
|
|
@@ -38,27 +47,68 @@ async function captureSessionOutput(worktreeId, cliToolId, lines = 1000) {
|
|
|
38
47
|
const manager = manager_1.CLIToolManager.getInstance();
|
|
39
48
|
const cliTool = manager.getTool(cliToolId);
|
|
40
49
|
const sessionName = cliTool.getSessionName(worktreeId);
|
|
41
|
-
// Check if session exists
|
|
42
|
-
const exists = await (0, tmux_1.hasSession)(sessionName);
|
|
43
|
-
if (!exists) {
|
|
44
|
-
log.debug('captureSessionOutput:sessionNotFound', { sessionName });
|
|
45
|
-
throw new Error(`${cliTool.name} session ${sessionName} does not exist`);
|
|
46
|
-
}
|
|
47
50
|
try {
|
|
48
|
-
const output = await (0,
|
|
49
|
-
|
|
51
|
+
const output = await (0, tmux_capture_cache_1.getOrFetchCapture)(sessionName, lines, async () => {
|
|
52
|
+
// fetchFn: check session existence then capture
|
|
53
|
+
const exists = await (0, tmux_1.hasSession)(sessionName);
|
|
54
|
+
if (!exists) {
|
|
55
|
+
throw new Error(`${cliTool.name} session ${sessionName} does not exist`);
|
|
56
|
+
}
|
|
57
|
+
return await (0, tmux_1.capturePane)(sessionName, { startLine: -tmux_capture_cache_1.CACHE_MAX_CAPTURE_LINES });
|
|
58
|
+
});
|
|
50
59
|
log.debug('captureSessionOutput:success', {
|
|
51
|
-
actualLines,
|
|
60
|
+
actualLines: output.split('\n').length,
|
|
52
61
|
lastFewLines: output.split('\n').slice(-3).join(' | '),
|
|
53
62
|
});
|
|
54
63
|
return output;
|
|
55
64
|
}
|
|
56
65
|
catch (error) {
|
|
57
66
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
67
|
+
// Preserve original error messages (session not found vs capture failure)
|
|
68
|
+
if (errorMessage.includes('does not exist')) {
|
|
69
|
+
log.debug('captureSessionOutput:sessionNotFound', { sessionName });
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
58
72
|
log.error('captureSessionOutput:failed', { error: errorMessage });
|
|
59
73
|
throw new Error(`Failed to capture ${cliTool.name} output: ${errorMessage}`);
|
|
60
74
|
}
|
|
61
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Capture CLI session output bypassing cache (fresh capture).
|
|
78
|
+
*
|
|
79
|
+
* Issue #405: Used by prompt-response endpoint to ensure fresh output
|
|
80
|
+
* for prompt re-verification. Writes back to cache on success.
|
|
81
|
+
*
|
|
82
|
+
* @param worktreeId - Worktree ID
|
|
83
|
+
* @param cliToolId - CLI tool ID
|
|
84
|
+
* @param lines - Number of lines to capture (default: 5000)
|
|
85
|
+
* @returns Captured output
|
|
86
|
+
*/
|
|
87
|
+
async function captureSessionOutputFresh(worktreeId, cliToolId, lines = 5000) {
|
|
88
|
+
const log = logger.withContext({ worktreeId, cliToolId });
|
|
89
|
+
log.debug('captureSessionOutputFresh:start', { requestedLines: lines });
|
|
90
|
+
const manager = manager_1.CLIToolManager.getInstance();
|
|
91
|
+
const cliTool = manager.getTool(cliToolId);
|
|
92
|
+
const sessionName = cliTool.getSessionName(worktreeId);
|
|
93
|
+
try {
|
|
94
|
+
const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -lines });
|
|
95
|
+
// Write back to cache if non-empty [SEC4-007]
|
|
96
|
+
if (output.length > 0) {
|
|
97
|
+
(0, tmux_capture_cache_1.setCachedCapture)(sessionName, output, lines);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
101
|
+
}
|
|
102
|
+
return (0, tmux_capture_cache_1.sliceOutput)(output, lines);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
// [DA3-005] Invalidate cache on error (TOCTOU safety)
|
|
106
|
+
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
107
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
108
|
+
log.error('captureSessionOutputFresh:failed', { error: errorMessage });
|
|
109
|
+
throw new Error(`Failed to capture ${cliTool.name} output: ${errorMessage}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
62
112
|
/**
|
|
63
113
|
* Get session name for a CLI tool and worktree
|
|
64
114
|
*
|
|
@@ -7,10 +7,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.CodexTool = void 0;
|
|
8
8
|
const base_1 = require("./base");
|
|
9
9
|
const tmux_1 = require("../tmux");
|
|
10
|
-
const child_process_1 = require("child_process");
|
|
11
|
-
const util_1 = require("util");
|
|
12
10
|
const pasted_text_helper_1 = require("../pasted-text-helper");
|
|
13
|
-
const
|
|
11
|
+
const tmux_capture_cache_1 = require("../tmux-capture-cache");
|
|
14
12
|
/**
|
|
15
13
|
* Extract error message from unknown error type (DRY)
|
|
16
14
|
* Same pattern as claude-session.ts getErrorMessage()
|
|
@@ -80,9 +78,9 @@ class CodexTool extends base_1.BaseCLITool {
|
|
|
80
78
|
await new Promise((resolve) => setTimeout(resolve, CODEX_MODEL_SELECT_WAIT_MS));
|
|
81
79
|
// T2.6: Skip model selection dialog by sending Down arrow + Enter
|
|
82
80
|
// This selects the default model and proceeds to the prompt
|
|
83
|
-
await
|
|
81
|
+
await (0, tmux_1.sendSpecialKeys)(sessionName, ['Down']);
|
|
84
82
|
await new Promise((resolve) => setTimeout(resolve, CODEX_MODEL_SELECT_WAIT_MS));
|
|
85
|
-
await
|
|
83
|
+
await (0, tmux_1.sendSpecialKeys)(sessionName, ['Enter']);
|
|
86
84
|
await new Promise((resolve) => setTimeout(resolve, CODEX_MODEL_SELECT_WAIT_MS));
|
|
87
85
|
console.log(`✓ Started Codex session: ${sessionName}`);
|
|
88
86
|
}
|
|
@@ -110,7 +108,7 @@ class CodexTool extends base_1.BaseCLITool {
|
|
|
110
108
|
// Wait a moment for the text to be typed
|
|
111
109
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
112
110
|
// Send Enter key separately
|
|
113
|
-
await
|
|
111
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'C-m');
|
|
114
112
|
// Wait a moment for the message to be processed
|
|
115
113
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
116
114
|
// Issue #212: Detect [Pasted text] and resend Enter for multi-line messages
|
|
@@ -118,6 +116,8 @@ class CodexTool extends base_1.BaseCLITool {
|
|
|
118
116
|
if (message.includes('\n')) {
|
|
119
117
|
await (0, pasted_text_helper_1.detectAndResendIfPastedText)(sessionName);
|
|
120
118
|
}
|
|
119
|
+
// Issue #405: Invalidate cache after sending message
|
|
120
|
+
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
121
121
|
console.log(`✓ Sent message to Codex session: ${sessionName}`);
|
|
122
122
|
}
|
|
123
123
|
catch (error) {
|
|
@@ -137,7 +137,7 @@ class CodexTool extends base_1.BaseCLITool {
|
|
|
137
137
|
const exists = await (0, tmux_1.hasSession)(sessionName);
|
|
138
138
|
if (exists) {
|
|
139
139
|
// Send Ctrl+D (ASCII 4)
|
|
140
|
-
await
|
|
140
|
+
await (0, tmux_1.sendSpecialKey)(sessionName, 'C-d');
|
|
141
141
|
// Wait a moment for Codex to exit
|
|
142
142
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
143
143
|
}
|
|
@@ -13,6 +13,7 @@ exports.GeminiTool = void 0;
|
|
|
13
13
|
const base_1 = require("./base");
|
|
14
14
|
const tmux_1 = require("../tmux");
|
|
15
15
|
const pasted_text_helper_1 = require("../pasted-text-helper");
|
|
16
|
+
const tmux_capture_cache_1 = require("../tmux-capture-cache");
|
|
16
17
|
/**
|
|
17
18
|
* Extract error message from unknown error type (DRY)
|
|
18
19
|
*/
|
|
@@ -141,6 +142,8 @@ class GeminiTool extends base_1.BaseCLITool {
|
|
|
141
142
|
if (message.includes('\n')) {
|
|
142
143
|
await (0, pasted_text_helper_1.detectAndResendIfPastedText)(sessionName);
|
|
143
144
|
}
|
|
145
|
+
// Issue #405: Invalidate cache after sending message
|
|
146
|
+
(0, tmux_capture_cache_1.invalidateCache)(sessionName);
|
|
144
147
|
console.log(`✓ Sent message to Gemini session: ${sessionName}`);
|
|
145
148
|
}
|
|
146
149
|
catch (error) {
|