@heretyc/subagent-mcp 2.9.2 → 2.10.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 +207 -193
- package/dist/concurrency.js +96 -0
- package/dist/config-scaffold.js +2 -0
- package/dist/global-concurrency.jsonc +29 -0
- package/dist/index.js +117 -58
- package/dist/setup.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,193 +1,207 @@
|
|
|
1
|
-
# subagent-mcp
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@heretyc/subagent-mcp)
|
|
4
|
-
[](LICENSE)
|
|
5
|
-
[](https://www.npmjs.com/package/@heretyc/subagent-mcp)
|
|
6
|
-
[](https://github.com/Heretyc/subagent-mcp/actions/workflows/claude-routine.yml)
|
|
7
|
-
|
|
8
|
-
MCP server that launches and manages always-interactive Claude Code and Codex sub-agent sessions — on macOS, Linux, and Windows. No direct API calls. No API keys.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Install
|
|
13
|
-
|
|
14
|
-
### Prerequisites (runtime)
|
|
15
|
-
|
|
16
|
-
To run the published CLI you need:
|
|
17
|
-
|
|
18
|
-
- Node.js >= 18 (`node --version`)
|
|
19
|
-
- `claude` CLI — globally installed and authenticated (`claude --version`)
|
|
20
|
-
- `codex` CLI — globally installed and authenticated (`codex --version`; optional if you only use Claude paths)
|
|
21
|
-
|
|
22
|
-
Building from source needs additional developer tooling — see [CONTRIBUTING.md § Prerequisites](CONTRIBUTING.md#prerequisites).
|
|
23
|
-
|
|
24
|
-
### npmjs (default)
|
|
25
|
-
|
|
26
|
-
No authentication, no `.npmrc` configuration, and no PAT required. The package is publicly available on [npmjs.com](https://www.npmjs.com/package/@heretyc/subagent-mcp).
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npm install -g @heretyc/subagent-mcp
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
**Use this path for all standard installs.**
|
|
33
|
-
|
|
34
|
-
### GitHub Packages (org-internal pin / supply-chain auditing)
|
|
35
|
-
|
|
36
|
-
Use this path when your organization's `.npmrc` already routes the `@heretyc` scope through GitHub Packages, when you need supply-chain audit tracing against the org-internal artifact, or when you run inside a GitHub Actions workflow whose `GITHUB_TOKEN` carries `read:packages`. Otherwise prefer npmjs above — it is simpler and needs no credentials.
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
# One-time machine setup
|
|
40
|
-
echo "@heretyc:registry=https://npm.pkg.github.com" >> ~/.npmrc
|
|
41
|
-
echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT" >> ~/.npmrc
|
|
42
|
-
# PAT must be a classic PAT with the read:packages scope
|
|
43
|
-
|
|
44
|
-
npm install -g @heretyc/subagent-mcp
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
> **Note:** GitHub Packages requires authentication even for public packages. If you see `401 Unauthorized`, verify your PAT has the `read:packages` scope and has not expired. GitHub Packages does not render a README page — the live documentation appears on [npmjs.com](https://www.npmjs.com/package/@heretyc/subagent-mcp).
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## Quick Start
|
|
52
|
-
|
|
53
|
-
**1. Install** (see [Install](#install) above):
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
npm install -g @heretyc/subagent-mcp
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
**2. Register the MCP server + orchestration hooks:**
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
subagent-mcp setup
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Installing the package only ships the binary — it does **not** auto-wire anything. `subagent-mcp setup` auto-detects Claude Code / Codex and registers the MCP server plus the per-turn orchestration-mode hooks. Preview with `subagent-mcp setup --dry-run`.
|
|
66
|
-
|
|
67
|
-
**3. Restart your host.**
|
|
68
|
-
|
|
69
|
-
Restart your Claude Code or Codex session so it picks up the new binary. On Codex, run `/hooks` and trust the new hook.
|
|
70
|
-
|
|
71
|
-
**4. Initialize the orchestration invariant (recommended):**
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
subagent-mcp init --global
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
`init --global` writes the managed invariant block into the providers' official global user-config files — `~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, `~/.gemini/GEMINI.md` — set up once, works across every project. For a single project instead, use `subagent-mcp init --root /path/to/project` to write into that project's instruction files. `--global` and `--root` are mutually exclusive.
|
|
78
|
-
|
|
79
|
-
For manual wiring, Gemini CLI, Claude Desktop, and developer install from source, see [docs/registration.md](docs/registration.md).
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Features
|
|
84
|
-
|
|
85
|
-
- Start Claude or Codex interactive sessions as managed sub-agents from any MCP host
|
|
86
|
-
- Poll status, stream stdout/stderr tails, and enqueue follow-up messages to live sessions
|
|
87
|
-
- Concurrency caps: 5 concurrent Claude + 5 concurrent Codex agents (only `processing` agents count toward the cap; `stalled` agents do not hold a slot)
|
|
88
|
-
- Liveness tracking via the visible provider stream (Claude SDK events, Codex app-server JSONL): agents with no parsed visible provider stream item for 10 minutes enter `stalled` state (still alive, just quiet — thinking or awaiting a temp-file handoff), and recover to `processing` if the visible stream resumes
|
|
89
|
-
- Ultracode mode for Opus 4.8 via `--settings {"ultracode":true}` — see [docs/usage.md](docs/usage.md)
|
|
90
|
-
- Cross-platform exe resolution (Windows: npm-prefix .exe paths; macOS/Linux: PATH + Homebrew/usr-local fallbacks); immediate `taskkill /t /f` (Windows) / `SIGKILL` (POSIX) force-kill; no graceful shutdown period
|
|
91
|
-
- stdio MCP transport; built with `@modelcontextprotocol/sdk` + `zod`
|
|
92
|
-
- `orchestration-mode` tool — toggles orchestrator directives injected by bundled Claude Code / Codex hooks; Claude also gets a deterministic `PreToolUse` gate
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## Orchestration Mode
|
|
97
|
-
|
|
98
|
-
**ON:** the agent operates as an orchestrator — hook injection governs each session turn, preventing inline reads or writes, and all work is delegated through sub-agent tools.
|
|
99
|
-
|
|
100
|
-
**OFF:** the agent operates normally, with no orchestration constraints.
|
|
101
|
-
|
|
102
|
-
Toggle with the `orchestration-mode` tool. Desktop hosts toggle the mode but receive no hook injection. See [docs/spec/dev-loop/orchestration-directive-architecture.md](docs/spec/dev-loop/orchestration-directive-architecture.md) for full semantics.
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## Auto Mode
|
|
107
|
-
|
|
108
|
-
Pass `task_category` to `launch_agent` and the server picks the best provider, model, and effort for that category, with automatic fallback.
|
|
109
|
-
|
|
110
|
-
`launch_agent` supports **auto mode**: pass `prompt` + `task_category` and the server picks the best provider/model/effort for that category from its routing table, silently falling back to the next-best candidate on any launch-time failure.
|
|
111
|
-
|
|
112
|
-
`provider`, `model`, and `effort` are optional overrides — omit them to get the auto-selected best combination. Rules: passing `model` requires `provider`; passing `effort` requires both `provider` and `model`.
|
|
113
|
-
|
|
114
|
-
**task_category** (required) — pick one:
|
|
115
|
-
|
|
116
|
-
| Category | What it is |
|
|
117
|
-
|---|---|
|
|
118
|
-
| `math_proof` | deliverable is a proof/derivation/formally-checkable result |
|
|
119
|
-
| `security_review` | security verdict, threat assessment, or demonstrated exploit |
|
|
120
|
-
| `debugging` | verified fix/root-cause; requires an observed failure as precondition |
|
|
121
|
-
| `quality_review` | evaluative verdict on existing artifact (review, A-vs-B, validate-vs-spec) |
|
|
122
|
-
| `architecture` | cross-module design/plan, no code, no execution loop |
|
|
123
|
-
| `agentic_execution` | end-state via act/observe/adapt loop (run/deploy/provision/browse) |
|
|
124
|
-
| `data_analysis` | empirical finding about structured dataset (query, stat, model) |
|
|
125
|
-
| `coding` | bounded runnable code artifact, one-pass (implement, test, refactor) |
|
|
126
|
-
| `knowledge_synthesis` | novel integrated prose over sources (synthesize, summarize, draft) |
|
|
127
|
-
| `mechanical` | deterministic single-pass transform, exact-match checkable (grep, rename, reformat) |
|
|
128
|
-
| `prompt_engineering` | designed/optimized prompt or prompt-system steering an LLM/agent (composite-inferred) |
|
|
129
|
-
| `vulnerability_research` | discovery + PoC of a novel vulnerability (composite-inferred) |
|
|
130
|
-
| `molecular_biology` | reasoned molecular/computational-biology result over sequences, structures, or -omics data (composite-inferred) |
|
|
131
|
-
| `ml_accelerator_design` | hardware/software design for ML acceleration — dataflow, kernel, roofline (composite-inferred) |
|
|
132
|
-
| `fallback_default` | no category matches with confidence; prefer splitting work instead |
|
|
133
|
-
|
|
134
|
-
The last four are **composite-inferred**: they carry no dedicated benchmark and their routing competency is composed from parent categories rather than measured directly.
|
|
135
|
-
|
|
136
|
-
**Atomic-split guidance:** if you are unsure which category fits, do NOT submit one large amorphous task. Break the work into smaller atomic steps each mapping to a single category and launch one agent per step.
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
##
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
|
164
|
-
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
|
178
|
-
|---|---|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
##
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
1
|
+
# subagent-mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@heretyc/subagent-mcp)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/@heretyc/subagent-mcp)
|
|
6
|
+
[](https://github.com/Heretyc/subagent-mcp/actions/workflows/claude-routine.yml)
|
|
7
|
+
|
|
8
|
+
MCP server that launches and manages always-interactive Claude Code and Codex sub-agent sessions — on macOS, Linux, and Windows. No direct API calls. No API keys.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
### Prerequisites (runtime)
|
|
15
|
+
|
|
16
|
+
To run the published CLI you need:
|
|
17
|
+
|
|
18
|
+
- Node.js >= 18 (`node --version`)
|
|
19
|
+
- `claude` CLI — globally installed and authenticated (`claude --version`)
|
|
20
|
+
- `codex` CLI — globally installed and authenticated (`codex --version`; optional if you only use Claude paths)
|
|
21
|
+
|
|
22
|
+
Building from source needs additional developer tooling — see [CONTRIBUTING.md § Prerequisites](CONTRIBUTING.md#prerequisites).
|
|
23
|
+
|
|
24
|
+
### npmjs (default)
|
|
25
|
+
|
|
26
|
+
No authentication, no `.npmrc` configuration, and no PAT required. The package is publicly available on [npmjs.com](https://www.npmjs.com/package/@heretyc/subagent-mcp).
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install -g @heretyc/subagent-mcp
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Use this path for all standard installs.**
|
|
33
|
+
|
|
34
|
+
### GitHub Packages (org-internal pin / supply-chain auditing)
|
|
35
|
+
|
|
36
|
+
Use this path when your organization's `.npmrc` already routes the `@heretyc` scope through GitHub Packages, when you need supply-chain audit tracing against the org-internal artifact, or when you run inside a GitHub Actions workflow whose `GITHUB_TOKEN` carries `read:packages`. Otherwise prefer npmjs above — it is simpler and needs no credentials.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# One-time machine setup
|
|
40
|
+
echo "@heretyc:registry=https://npm.pkg.github.com" >> ~/.npmrc
|
|
41
|
+
echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT" >> ~/.npmrc
|
|
42
|
+
# PAT must be a classic PAT with the read:packages scope
|
|
43
|
+
|
|
44
|
+
npm install -g @heretyc/subagent-mcp
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
> **Note:** GitHub Packages requires authentication even for public packages. If you see `401 Unauthorized`, verify your PAT has the `read:packages` scope and has not expired. GitHub Packages does not render a README page — the live documentation appears on [npmjs.com](https://www.npmjs.com/package/@heretyc/subagent-mcp).
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
**1. Install** (see [Install](#install) above):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install -g @heretyc/subagent-mcp
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**2. Register the MCP server + orchestration hooks:**
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
subagent-mcp setup
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Installing the package only ships the binary — it does **not** auto-wire anything. `subagent-mcp setup` auto-detects Claude Code / Codex and registers the MCP server plus the per-turn orchestration-mode hooks. Preview with `subagent-mcp setup --dry-run`.
|
|
66
|
+
|
|
67
|
+
**3. Restart your host.**
|
|
68
|
+
|
|
69
|
+
Restart your Claude Code or Codex session so it picks up the new binary. On Codex, run `/hooks` and trust the new hook.
|
|
70
|
+
|
|
71
|
+
**4. Initialize the orchestration invariant (recommended):**
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
subagent-mcp init --global
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`init --global` writes the managed invariant block into the providers' official global user-config files — `~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, `~/.gemini/GEMINI.md` — set up once, works across every project. For a single project instead, use `subagent-mcp init --root /path/to/project` to write into that project's instruction files. `--global` and `--root` are mutually exclusive.
|
|
78
|
+
|
|
79
|
+
For manual wiring, Gemini CLI, Claude Desktop, and developer install from source, see [docs/registration.md](docs/registration.md).
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Features
|
|
84
|
+
|
|
85
|
+
- Start Claude or Codex interactive sessions as managed sub-agents from any MCP host
|
|
86
|
+
- Poll status, stream stdout/stderr tails, and enqueue follow-up messages to live sessions
|
|
87
|
+
- Concurrency caps: 5 concurrent Claude + 5 concurrent Codex agents (only `processing` agents count toward the cap; `stalled` agents do not hold a slot)
|
|
88
|
+
- Liveness tracking via the visible provider stream (Claude SDK events, Codex app-server JSONL): agents with no parsed visible provider stream item for 10 minutes enter `stalled` state (still alive, just quiet — thinking or awaiting a temp-file handoff), and recover to `processing` if the visible stream resumes
|
|
89
|
+
- Ultracode mode for Opus 4.8 via `--settings {"ultracode":true}` — see [docs/usage.md](docs/usage.md)
|
|
90
|
+
- Cross-platform exe resolution (Windows: npm-prefix .exe paths; macOS/Linux: PATH + Homebrew/usr-local fallbacks); immediate `taskkill /t /f` (Windows) / `SIGKILL` (POSIX) force-kill; no graceful shutdown period
|
|
91
|
+
- stdio MCP transport; built with `@modelcontextprotocol/sdk` + `zod`
|
|
92
|
+
- `orchestration-mode` tool — toggles orchestrator directives injected by bundled Claude Code / Codex hooks; Claude also gets a deterministic `PreToolUse` gate
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Orchestration Mode
|
|
97
|
+
|
|
98
|
+
**ON:** the agent operates as an orchestrator — hook injection governs each session turn, preventing inline reads or writes, and all work is delegated through sub-agent tools.
|
|
99
|
+
|
|
100
|
+
**OFF:** the agent operates normally, with no orchestration constraints.
|
|
101
|
+
|
|
102
|
+
Toggle with the `orchestration-mode` tool. Desktop hosts toggle the mode but receive no hook injection. See [docs/spec/dev-loop/orchestration-directive-architecture.md](docs/spec/dev-loop/orchestration-directive-architecture.md) for full semantics.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Auto Mode
|
|
107
|
+
|
|
108
|
+
Pass `task_category` to `launch_agent` and the server picks the best provider, model, and effort for that category, with automatic fallback.
|
|
109
|
+
|
|
110
|
+
`launch_agent` supports **auto mode**: pass `prompt` + `task_category` and the server picks the best provider/model/effort for that category from its routing table, silently falling back to the next-best candidate on any launch-time failure.
|
|
111
|
+
|
|
112
|
+
`provider`, `model`, and `effort` are optional overrides — omit them to get the auto-selected best combination. Rules: passing `model` requires `provider`; passing `effort` requires both `provider` and `model`.
|
|
113
|
+
|
|
114
|
+
**task_category** (required) — pick one:
|
|
115
|
+
|
|
116
|
+
| Category | What it is |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `math_proof` | deliverable is a proof/derivation/formally-checkable result |
|
|
119
|
+
| `security_review` | security verdict, threat assessment, or demonstrated exploit |
|
|
120
|
+
| `debugging` | verified fix/root-cause; requires an observed failure as precondition |
|
|
121
|
+
| `quality_review` | evaluative verdict on existing artifact (review, A-vs-B, validate-vs-spec) |
|
|
122
|
+
| `architecture` | cross-module design/plan, no code, no execution loop |
|
|
123
|
+
| `agentic_execution` | end-state via act/observe/adapt loop (run/deploy/provision/browse) |
|
|
124
|
+
| `data_analysis` | empirical finding about structured dataset (query, stat, model) |
|
|
125
|
+
| `coding` | bounded runnable code artifact, one-pass (implement, test, refactor) |
|
|
126
|
+
| `knowledge_synthesis` | novel integrated prose over sources (synthesize, summarize, draft) |
|
|
127
|
+
| `mechanical` | deterministic single-pass transform, exact-match checkable (grep, rename, reformat) |
|
|
128
|
+
| `prompt_engineering` | designed/optimized prompt or prompt-system steering an LLM/agent (composite-inferred) |
|
|
129
|
+
| `vulnerability_research` | discovery + PoC of a novel vulnerability (composite-inferred) |
|
|
130
|
+
| `molecular_biology` | reasoned molecular/computational-biology result over sequences, structures, or -omics data (composite-inferred) |
|
|
131
|
+
| `ml_accelerator_design` | hardware/software design for ML acceleration — dataflow, kernel, roofline (composite-inferred) |
|
|
132
|
+
| `fallback_default` | no category matches with confidence; prefer splitting work instead |
|
|
133
|
+
|
|
134
|
+
The last four are **composite-inferred**: they carry no dedicated benchmark and their routing competency is composed from parent categories rather than measured directly.
|
|
135
|
+
|
|
136
|
+
**Atomic-split guidance:** if you are unsure which category fits, do NOT submit one large amorphous task. Break the work into smaller atomic steps each mapping to a single category and launch one agent per step.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Configuration
|
|
141
|
+
|
|
142
|
+
### Global concurrent-subagent cap
|
|
143
|
+
|
|
144
|
+
subagent-mcp enforces a machine-global cap on the number of **live concurrent subagents** across all agentic sessions and MCP server processes on the machine. The count includes agents started by other active sessions and the whole recursive descendant tree; slots free as agents finish or are killed.
|
|
145
|
+
|
|
146
|
+
The cap is configured in `global-concurrency.jsonc`, a dedicated dist-sibling file in the install directory. It is separate from the advanced routing directives file, ships as a commented template, and is retained across installs and updates by the same preserve-user-edits mechanism as the advanced routing directives.
|
|
147
|
+
|
|
148
|
+
Set `globalConcurrentSubagents` in that file. The default is `20`; the minimum valid value is `10`. Validation is forced: `0`, unset, missing, or invalid values reset to `20`, and values `1` through `9` are pinned up to `10`. There is no environment-variable override; the file is the sole source of truth. The file is re-read on every `launch_agent` call, so edits take effect on the next launch with no server restart.
|
|
149
|
+
|
|
150
|
+
When the cap is reached, `launch_agent` is rejected immediately; it never queues. Because the count includes other active sessions and the entire descendant tree, free slots manually with `list_agents` and `kill_agent` before retrying. There is no automatic cleanup or zombie reaping.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Tools
|
|
155
|
+
|
|
156
|
+
Eight tools are exposed over the stdio MCP transport.
|
|
157
|
+
|
|
158
|
+
| Tool | Description |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `launch_agent` | Start a new sub-agent session with a prompt and optional task category / overrides |
|
|
161
|
+
| `poll_agent` | Check status and stream tail output for a running agent |
|
|
162
|
+
| `kill_agent` | Terminate a running or stalled agent |
|
|
163
|
+
| `send_message` | Enqueue a message into a running agent's stdin |
|
|
164
|
+
| `list_agents` | List all tracked agents and their current status |
|
|
165
|
+
| `wait` | Block until a specific agent reaches a terminal state |
|
|
166
|
+
| `orchestration-mode` | Toggle orchestration-mode ON/OFF; controls whether hook directives are injected into agent sessions |
|
|
167
|
+
| `model-selection-mode` | Control model selection: `smart` (auto-pick) or `user-approved-overrides` (30-min override window) |
|
|
168
|
+
|
|
169
|
+
Full parameters, return shapes, and the `alive` / `idle_seconds` / `hint` / `recent_stream` fields are in [docs/tools.md](docs/tools.md).
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Agent Lifecycle
|
|
174
|
+
|
|
175
|
+
Each agent transitions through these states:
|
|
176
|
+
|
|
177
|
+
| Status | Meaning |
|
|
178
|
+
|---|---|
|
|
179
|
+
| `processing` | Driver alive with a visible provider-stream heartbeat in the last 10 minutes — actively working. Launch time counts as the initial heartbeat |
|
|
180
|
+
| `stalled` | Driver STILL ALIVE but no parsed visible provider stream item for >= 10 minutes — working, thinking, or awaiting a temp-file handoff (not a failure). Recovers to `processing` if the visible stream resumes |
|
|
181
|
+
| `finished` | Current turn completed, or driver exited with code 0 |
|
|
182
|
+
| `errored` | Process exited with non-zero code |
|
|
183
|
+
| `stopped` | Terminated by `kill_agent` |
|
|
184
|
+
|
|
185
|
+
`processing` and `stalled` are live. `stalled` means the driver is alive but quiet (>= 10 min no visible stream); it recovers automatically and is never auto-killed — do not `kill_agent` a stalled agent. `wait` does not return on `stalled`. Full semantics: [docs/reference/status-lifecycle.md](docs/reference/status-lifecycle.md).
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Documentation
|
|
190
|
+
|
|
191
|
+
| Document | Contents |
|
|
192
|
+
|---|---|
|
|
193
|
+
| [docs/registration.md](docs/registration.md) | Per-platform setup: Claude Code, Codex, Gemini CLI, Claude Desktop; manual wiring; developer install from source |
|
|
194
|
+
| [docs/tools.md](docs/tools.md) | Full tool reference — all eight tools, parameters, return shapes |
|
|
195
|
+
| [docs/usage.md](docs/usage.md) | Model & effort matrix, ultracode mode, CLI invocations, usage examples |
|
|
196
|
+
| [docs/SPEC.md](docs/SPEC.md) | Full technical specification — architecture, schemas, status lifecycle, error catalogue |
|
|
197
|
+
| [docs/spec/interactive-drivers.md](docs/spec/interactive-drivers.md) | Always-interactive Claude/Codex driver model |
|
|
198
|
+
| [docs/release-notes.md](docs/release-notes.md) | Operator-facing release notes |
|
|
199
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | Contributor guide — dev environment, build, test, publish |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
Apache-2.0 — Copyright 2026 Lexi Blackburn
|
|
206
|
+
|
|
207
|
+
See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { platform } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { CONCURRENCY_SCAFFOLD } from "./config-scaffold.js";
|
|
6
|
+
export const DEFAULT_CAP = 20;
|
|
7
|
+
export const MIN_CAP = 10;
|
|
8
|
+
export const CONFIG_FILENAME = "global-concurrency.jsonc";
|
|
9
|
+
export function clampCap(raw) {
|
|
10
|
+
if (!Number.isInteger(raw))
|
|
11
|
+
return DEFAULT_CAP;
|
|
12
|
+
const v = raw;
|
|
13
|
+
if (v <= 0)
|
|
14
|
+
return DEFAULT_CAP;
|
|
15
|
+
if (v < MIN_CAP)
|
|
16
|
+
return MIN_CAP;
|
|
17
|
+
return v;
|
|
18
|
+
}
|
|
19
|
+
export function stripJsoncComments(text) {
|
|
20
|
+
return text.replace(/^\s*\/\/.*$/gm, "");
|
|
21
|
+
}
|
|
22
|
+
export function parseConcurrencyConfig(text) {
|
|
23
|
+
let raw;
|
|
24
|
+
try {
|
|
25
|
+
raw = JSON.parse(stripJsoncComments(text))?.globalConcurrentSubagents;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
raw = undefined;
|
|
29
|
+
}
|
|
30
|
+
return clampCap(raw);
|
|
31
|
+
}
|
|
32
|
+
export function defaultConfigPath() {
|
|
33
|
+
return fileURLToPath(new URL("./" + CONFIG_FILENAME, import.meta.url));
|
|
34
|
+
}
|
|
35
|
+
export function ensureConcurrencyConfig(path = defaultConfigPath()) {
|
|
36
|
+
try {
|
|
37
|
+
if (existsSync(path))
|
|
38
|
+
return;
|
|
39
|
+
writeFileSync(path, CONCURRENCY_SCAFFOLD);
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
42
|
+
}
|
|
43
|
+
export function readGlobalCap(path = defaultConfigPath()) {
|
|
44
|
+
try {
|
|
45
|
+
ensureConcurrencyConfig(path);
|
|
46
|
+
return parseConcurrencyConfig(readFileSync(path, "utf8"));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return DEFAULT_CAP;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function slotDir() {
|
|
53
|
+
if (platform() === "win32") {
|
|
54
|
+
return join(process.env.ProgramData || process.env.ALLUSERSPROFILE || "C:\\ProgramData", "subagent-mcp", "slots");
|
|
55
|
+
}
|
|
56
|
+
return "/tmp/subagent-mcp/slots";
|
|
57
|
+
}
|
|
58
|
+
export function countSlots(dir = slotDir()) {
|
|
59
|
+
try {
|
|
60
|
+
return readdirSync(dir).filter((f) => f.startsWith("slot-")).length;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function reserveSlot(agentId, max, dir = slotDir()) {
|
|
67
|
+
try {
|
|
68
|
+
mkdirSync(dir, { recursive: true, mode: 0o1777 });
|
|
69
|
+
const slotPath = join(dir, `slot-${agentId}.json`);
|
|
70
|
+
writeFileSync(slotPath, JSON.stringify({ pid: process.pid, cwd: process.cwd(), startedAt: new Date().toISOString() }), { mode: 0o600 });
|
|
71
|
+
const n = countSlots(dir);
|
|
72
|
+
if (n > max) {
|
|
73
|
+
try {
|
|
74
|
+
unlinkSync(slotPath);
|
|
75
|
+
}
|
|
76
|
+
catch { }
|
|
77
|
+
return { ok: false, current: n - 1, max };
|
|
78
|
+
}
|
|
79
|
+
return { ok: true, slotPath, current: n, max };
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
console.error(`[concurrency] reserve failed, failing open: ${e instanceof Error ? e.message : String(e)}`);
|
|
83
|
+
return { ok: true, current: 0, max, slotPath: null };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function releaseSlot(slotPath) {
|
|
87
|
+
if (!slotPath)
|
|
88
|
+
return;
|
|
89
|
+
try {
|
|
90
|
+
unlinkSync(slotPath);
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
}
|
|
94
|
+
export function globalCapMessage(current, max, configPath = defaultConfigPath()) {
|
|
95
|
+
return `Global concurrent-subagent limit reached: ${current} of ${max} live subagents are already running across all sessions on this machine. This global count includes agents started by OTHER active agentic sessions and the ENTIRE recursive descendant tree, not just this session's direct children. launch_agent was REJECTED — this cap never queues or blocks; no slot frees itself by waiting. Free a slot manually first: call list_agents to see live agents, then kill_agent to terminate ones you no longer need, and retry. The limit is "globalConcurrentSubagents" in ${configPath} (default 20, minimum 10).`;
|
|
96
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-ruleset-scaffold.mjs from src/global-concurrency.jsonc — DO NOT EDIT.
|
|
2
|
+
export const CONCURRENCY_SCAFFOLD = "// subagent-mcp — Global Concurrent Subagent Cap\r\n// ------------------------------------------------------------------\r\n// SOLE source of truth for the machine-wide limit on how many subagents\r\n// may be ALIVE AT ONCE across EVERY session, process, and user on this\r\n// machine. There is NO environment-variable override.\r\n//\r\n// The whole recursive descendant tree counts toward this ONE number: a\r\n// subagent that itself launches subagents adds to the same machine-wide\r\n// total, and OTHER active agentic sessions count too.\r\n//\r\n// RE-READ on every launch_agent call — edits take effect immediately, no\r\n// server restart required.\r\n//\r\n// Value rules (forcibly applied to the number below):\r\n// - missing / unset / non-integer / 0 / negative -> reset to default 20\r\n// - 1 through 9 -> forced UP to minimum 10\r\n// - 10 or greater -> used as-is\r\n//\r\n// When the cap is reached, launch_agent is REJECTED (never queued). Free a\r\n// slot with list_agents + kill_agent, then retry.\r\n//\r\n// Slots free automatically as agents finish or are killed. There is NO\r\n// zombie reaping. If a server process CRASHES, its slots stay counted until\r\n// you delete the stale slot files by hand:\r\n// Windows: %ProgramData%\\subagent-mcp\\slots\r\n// macOS/Linux: /tmp/subagent-mcp/slots\r\n{\r\n \"globalConcurrentSubagents\": 20\r\n}\r\n";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// subagent-mcp — Global Concurrent Subagent Cap
|
|
2
|
+
// ------------------------------------------------------------------
|
|
3
|
+
// SOLE source of truth for the machine-wide limit on how many subagents
|
|
4
|
+
// may be ALIVE AT ONCE across EVERY session, process, and user on this
|
|
5
|
+
// machine. There is NO environment-variable override.
|
|
6
|
+
//
|
|
7
|
+
// The whole recursive descendant tree counts toward this ONE number: a
|
|
8
|
+
// subagent that itself launches subagents adds to the same machine-wide
|
|
9
|
+
// total, and OTHER active agentic sessions count too.
|
|
10
|
+
//
|
|
11
|
+
// RE-READ on every launch_agent call — edits take effect immediately, no
|
|
12
|
+
// server restart required.
|
|
13
|
+
//
|
|
14
|
+
// Value rules (forcibly applied to the number below):
|
|
15
|
+
// - missing / unset / non-integer / 0 / negative -> reset to default 20
|
|
16
|
+
// - 1 through 9 -> forced UP to minimum 10
|
|
17
|
+
// - 10 or greater -> used as-is
|
|
18
|
+
//
|
|
19
|
+
// When the cap is reached, launch_agent is REJECTED (never queued). Free a
|
|
20
|
+
// slot with list_agents + kill_agent, then retry.
|
|
21
|
+
//
|
|
22
|
+
// Slots free automatically as agents finish or are killed. There is NO
|
|
23
|
+
// zombie reaping. If a server process CRASHES, its slots stay counted until
|
|
24
|
+
// you delete the stale slot files by hand:
|
|
25
|
+
// Windows: %ProgramData%\subagent-mcp\slots
|
|
26
|
+
// macOS/Linux: /tmp/subagent-mcp/slots
|
|
27
|
+
{
|
|
28
|
+
"globalConcurrentSubagents": 20
|
|
29
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { consumeStreamChunk, flushStream, isTurnCompletedLine, retainLastN, } fr
|
|
|
18
18
|
import { loadRoutingTable, buildCandidates, validatePresence, TASK_CATEGORIES, AUTO_HINT, SPLIT_HINT, } from "./routing.js";
|
|
19
19
|
import { createDeadlockWindow } from "./deadlock.js";
|
|
20
20
|
import { createRulesetGate, RULESET_HARD_FAIL_MSG, } from "./ruleset.js";
|
|
21
|
+
import { CONFIG_FILENAME, defaultConfigPath, globalCapMessage, readGlobalCap, releaseSlot, reserveSlot, } from "./concurrency.js";
|
|
21
22
|
import * as orchestrationMarker from "./orchestration/marker.js";
|
|
22
23
|
import * as modelMode from "./orchestration/model-mode.js";
|
|
23
24
|
import { startLivenessHeartbeat } from "./orchestration/liveness.js";
|
|
@@ -143,7 +144,7 @@ const ORCHESTRATION_INSTRUCTIONS = "subagent-mcp - CANONICAL OPERATING MODEL (fu
|
|
|
143
144
|
const SUBAGENT_INSTRUCTIONS = "SUB-AGENT SESSION: you are a child process launched by subagent-mcp. Follow the parent prompt. Do not treat yourself as the orchestrator, do not re-trigger orchestration carryover, and do not launch further sub-agents unless the parent prompt explicitly assigns that.\n\nMODEL SELECTION MODE (parallel to orchestration-mode, set via the model-selection-mode tool). DEFAULT is \"smart\" and is used whenever unset: in smart, launch_agent REJECTS any call supplying provider/model/effort selectors and the server auto-picks the best model. \"user-approved-overrides\" opens a 30-MINUTE window where selectors are HONORED, enforced LAZILY (the mode reverts to smart on the next launch_agent call after 30 minutes) and re-enabling does NOT extend an active window. HONOR-BASED: you MUST NOT set \"user-approved-overrides\" without explicit interactive USER authorization via the structured-question tool (AskUserQuestion on Claude / request-user-input on Codex); never enable it on your own initiative.";
|
|
144
145
|
const server = new McpServer({
|
|
145
146
|
name: "subagent-mcp",
|
|
146
|
-
version: "2.
|
|
147
|
+
version: "2.10.0",
|
|
147
148
|
description: "Launches always-interactive local Claude and Codex sub-agent sessions. Claude uses the Claude Agent SDK over the local Claude Code executable; Codex uses `codex app-server` over stdio. The server does not call Anthropic or OpenAI HTTP APIs directly.",
|
|
148
149
|
}, {
|
|
149
150
|
instructions: process.env.SUBAGENT_MCP_SUBAGENT === "1"
|
|
@@ -359,6 +360,8 @@ async function tryLaunchCandidate(candidate, prompt, agentCwd, routingTier, rule
|
|
|
359
360
|
}
|
|
360
361
|
// Always clean up ultracode settings file on close
|
|
361
362
|
cleanupUcSettings(agentState);
|
|
363
|
+
releaseSlot(agentState.slotPath ?? null);
|
|
364
|
+
agentState.slotPath = null;
|
|
362
365
|
// Always record actual close time (unless already finalized)
|
|
363
366
|
if (agentState.exitedAt === null)
|
|
364
367
|
agentState.exitedAt = Date.now();
|
|
@@ -599,69 +602,94 @@ server.tool("launch_agent", "Spawn a sub-agent session. AUTO MODE (mandatory fir
|
|
|
599
602
|
}
|
|
600
603
|
// 6. Attempt loop: best→worst. Register on first successful driver start; silently
|
|
601
604
|
// advance on launch-time failure. Sub-agent task outcome is NEVER a trigger.
|
|
605
|
+
const cap = readGlobalCap();
|
|
606
|
+
const reservationId = randomUUID();
|
|
607
|
+
const reservation = reserveSlot(reservationId, cap);
|
|
608
|
+
if (!reservation.ok) {
|
|
609
|
+
return errorResult(globalCapMessage(reservation.current, cap, defaultConfigPath()));
|
|
610
|
+
}
|
|
602
611
|
const skipped = [];
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
+
let launched = false;
|
|
613
|
+
try {
|
|
614
|
+
for (const candidate of candidates) {
|
|
615
|
+
const outcome = await tryLaunchCandidate(candidate, prompt, agentCwd, routingTier, rulesetApplied && rulesetOriginalSelection !== undefined
|
|
616
|
+
? { applied: true, originalSelection: rulesetOriginalSelection }
|
|
617
|
+
: undefined);
|
|
618
|
+
if ("agentId" in outcome) {
|
|
619
|
+
if (branch === "performance") {
|
|
620
|
+
deadlockWindow.consume();
|
|
621
|
+
}
|
|
612
622
|
const registeredAgent = agents.get(outcome.agentId);
|
|
613
623
|
if (registeredAgent) {
|
|
614
|
-
registeredAgent.
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
624
|
+
registeredAgent.slotPath = reservation.slotPath;
|
|
625
|
+
// The child can reach a terminal state DURING tryLaunchCandidate's
|
|
626
|
+
// awaits — before slotPath was set — so its close handler ran
|
|
627
|
+
// releaseSlot(undefined) (a no-op) and left the slot leaked. Detect
|
|
628
|
+
// that here and release now. slotPath is unique per reservation, so
|
|
629
|
+
// this never frees another agent's slot.
|
|
630
|
+
if (registeredAgent.exitCode !== null) {
|
|
631
|
+
releaseSlot(registeredAgent.slotPath);
|
|
632
|
+
registeredAgent.slotPath = null;
|
|
633
|
+
}
|
|
634
|
+
if (skipped.length > 0) {
|
|
635
|
+
registeredAgent.failoverFrom = skipped.map((s) => ({
|
|
636
|
+
provider: s.provider,
|
|
637
|
+
model: s.model,
|
|
638
|
+
effort: s.effort,
|
|
639
|
+
failure_type: s.failure_type,
|
|
640
|
+
}));
|
|
641
|
+
}
|
|
620
642
|
}
|
|
643
|
+
launched = true;
|
|
644
|
+
return {
|
|
645
|
+
content: [
|
|
646
|
+
{
|
|
647
|
+
type: "text",
|
|
648
|
+
text: JSON.stringify({
|
|
649
|
+
agent_id: outcome.agentId,
|
|
650
|
+
status: "processing",
|
|
651
|
+
provider: candidate.provider,
|
|
652
|
+
model: candidate.model,
|
|
653
|
+
effort: candidate.effort,
|
|
654
|
+
task_category,
|
|
655
|
+
...(rulesetApplied
|
|
656
|
+
? {
|
|
657
|
+
ruleset_applied: true,
|
|
658
|
+
ruleset_original_selection: rulesetOriginalSelection,
|
|
659
|
+
}
|
|
660
|
+
: {}),
|
|
661
|
+
...(skipped.length > 0
|
|
662
|
+
? {
|
|
663
|
+
failover_occurred: true,
|
|
664
|
+
failover_from: skipped.map((s) => ({
|
|
665
|
+
provider: s.provider,
|
|
666
|
+
model: s.model,
|
|
667
|
+
effort: s.effort,
|
|
668
|
+
failure_type: s.failure_type,
|
|
669
|
+
})),
|
|
670
|
+
failover_note: buildFailoverNote(skipped, candidate),
|
|
671
|
+
}
|
|
672
|
+
: {}),
|
|
673
|
+
}),
|
|
674
|
+
},
|
|
675
|
+
],
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
skipped.push({
|
|
679
|
+
model: candidate.model,
|
|
680
|
+
effort: candidate.effort,
|
|
681
|
+
provider: candidate.provider,
|
|
682
|
+
reason: outcome.reason,
|
|
683
|
+
failure_type: outcome.failure_type,
|
|
684
|
+
});
|
|
685
|
+
if (!pureAuto) {
|
|
686
|
+
break;
|
|
621
687
|
}
|
|
622
|
-
return {
|
|
623
|
-
content: [
|
|
624
|
-
{
|
|
625
|
-
type: "text",
|
|
626
|
-
text: JSON.stringify({
|
|
627
|
-
agent_id: outcome.agentId,
|
|
628
|
-
status: "processing",
|
|
629
|
-
provider: candidate.provider,
|
|
630
|
-
model: candidate.model,
|
|
631
|
-
effort: candidate.effort,
|
|
632
|
-
task_category,
|
|
633
|
-
...(rulesetApplied
|
|
634
|
-
? {
|
|
635
|
-
ruleset_applied: true,
|
|
636
|
-
ruleset_original_selection: rulesetOriginalSelection,
|
|
637
|
-
}
|
|
638
|
-
: {}),
|
|
639
|
-
...(skipped.length > 0
|
|
640
|
-
? {
|
|
641
|
-
failover_occurred: true,
|
|
642
|
-
failover_from: skipped.map((s) => ({
|
|
643
|
-
provider: s.provider,
|
|
644
|
-
model: s.model,
|
|
645
|
-
effort: s.effort,
|
|
646
|
-
failure_type: s.failure_type,
|
|
647
|
-
})),
|
|
648
|
-
failover_note: buildFailoverNote(skipped, candidate),
|
|
649
|
-
}
|
|
650
|
-
: {}),
|
|
651
|
-
}),
|
|
652
|
-
},
|
|
653
|
-
],
|
|
654
|
-
};
|
|
655
688
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
reason: outcome.reason,
|
|
661
|
-
failure_type: outcome.failure_type,
|
|
662
|
-
});
|
|
663
|
-
if (!pureAuto) {
|
|
664
|
-
break;
|
|
689
|
+
}
|
|
690
|
+
finally {
|
|
691
|
+
if (!launched) {
|
|
692
|
+
releaseSlot(reservation.slotPath);
|
|
665
693
|
}
|
|
666
694
|
}
|
|
667
695
|
// 7. All candidates failed. Override selector modes are single-attempt hard
|
|
@@ -790,6 +818,8 @@ server.tool("kill_agent", "Terminate a live agent/session (status `processing`,
|
|
|
790
818
|
// POSIX, SIGKILL the process (close handler records the real exit code).
|
|
791
819
|
agent.status = "stopped";
|
|
792
820
|
agent.driver.kill();
|
|
821
|
+
releaseSlot(agent.slotPath ?? null);
|
|
822
|
+
agent.slotPath = null;
|
|
793
823
|
if (isWindows && agent.process.pid) {
|
|
794
824
|
spawn("taskkill", ["/pid", String(agent.process.pid), "/t", "/f"], {
|
|
795
825
|
windowsHide: true,
|
|
@@ -1154,7 +1184,9 @@ if (isMain) {
|
|
|
1154
1184
|
}
|
|
1155
1185
|
const installRoot = join(npmRoot.stdout.toString().trim(), ...pkg.name.split("/"));
|
|
1156
1186
|
const rulesetPath = join(installRoot, "dist", "advanced-ruleset.py");
|
|
1187
|
+
const cfgPath = join(installRoot, "dist", CONFIG_FILENAME);
|
|
1157
1188
|
let previousRuleset = null;
|
|
1189
|
+
let previousCfg = null;
|
|
1158
1190
|
if (existsSync(rulesetPath)) {
|
|
1159
1191
|
previousRuleset = readFileSync(rulesetPath);
|
|
1160
1192
|
const backupPath = join(tmpdir(), `advanced-ruleset.py.bak-update-${Date.now()}`);
|
|
@@ -1167,6 +1199,18 @@ if (isMain) {
|
|
|
1167
1199
|
process.exit(1);
|
|
1168
1200
|
}
|
|
1169
1201
|
}
|
|
1202
|
+
if (existsSync(cfgPath)) {
|
|
1203
|
+
previousCfg = readFileSync(cfgPath);
|
|
1204
|
+
const backupPath = join(tmpdir(), `${CONFIG_FILENAME}.bak-update-${Date.now()}`);
|
|
1205
|
+
try {
|
|
1206
|
+
writeFileSync(backupPath, previousCfg);
|
|
1207
|
+
console.log(`backed up user ${CONFIG_FILENAME} to ${backupPath}`);
|
|
1208
|
+
}
|
|
1209
|
+
catch (e) {
|
|
1210
|
+
console.error(`update refused before install: failed to back up ${CONFIG_FILENAME}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1211
|
+
process.exit(1);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1170
1214
|
const r = spawnNpm(npmArgs, "inherit");
|
|
1171
1215
|
if (r.error) {
|
|
1172
1216
|
console.error(`update failed to start npm: ${r.error.message}`);
|
|
@@ -1189,6 +1233,21 @@ if (isMain) {
|
|
|
1189
1233
|
process.exit(1);
|
|
1190
1234
|
}
|
|
1191
1235
|
}
|
|
1236
|
+
if (previousCfg !== null) {
|
|
1237
|
+
try {
|
|
1238
|
+
const freshCfg = existsSync(cfgPath)
|
|
1239
|
+
? readFileSync(cfgPath)
|
|
1240
|
+
: null;
|
|
1241
|
+
if (freshCfg === null || !previousCfg.equals(freshCfg)) {
|
|
1242
|
+
writeFileSync(cfgPath, previousCfg);
|
|
1243
|
+
console.log(`restored user ${CONFIG_FILENAME} (package update never overwrites user edits)`);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
catch (e) {
|
|
1247
|
+
console.error(`update failed to restore ${CONFIG_FILENAME}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1248
|
+
process.exit(1);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1192
1251
|
console.log("Update complete. Restart your CLI sessions so the MCP server picks up the new build.");
|
|
1193
1252
|
}
|
|
1194
1253
|
process.exit(code);
|
package/dist/setup.js
CHANGED
|
@@ -227,6 +227,7 @@ export function verifyInstall(root = INSTALL_ROOT) {
|
|
|
227
227
|
const required = [
|
|
228
228
|
"dist/index.js",
|
|
229
229
|
"dist/advanced-ruleset.py",
|
|
230
|
+
"dist/global-concurrency.jsonc",
|
|
230
231
|
"dist/hooks/orchestration-claude.js",
|
|
231
232
|
"dist/hooks/orchestration-claude-pretool.js",
|
|
232
233
|
"dist/hooks/orchestration-codex.js",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heretyc/subagent-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "MCP server that launches and manages always-interactive Claude Code and Codex sub-agent sessions (no direct Anthropic/OpenAI API).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"postinstall": "node scripts/postinstall.mjs",
|
|
38
38
|
"prepare": "npm run build",
|
|
39
39
|
"prepublishOnly": "npm test",
|
|
40
|
-
"test": "npm run check:versions && node test/effort.test.mjs && node test/drivers.test.mjs && node test/platform.test.mjs && node test/wait.test.mjs && node test/status.test.mjs && node test/output.test.mjs && node test/stream.test.mjs && node test/routing.test.mjs && node test/deadlock.test.mjs && node test/handler-validation.test.mjs && node test/index-handler.test.mjs && node test/ruleset.test.mjs && node test/ruleset-exec.test.mjs && node test/ruleset-handler.test.mjs && node test/failover.test.mjs && node test/orchestration-marker.test.mjs && node test/orchestration-hook-core.test.mjs && node test/orchestration-adapters.test.mjs && node test/orchestration-pretool.test.mjs && node test/orchestration-directives.test.mjs && node test/no-five-call.test.mjs && node test/check-worktree-subagent.test.mjs && node test/launch-agent-upsert.test.mjs && node test/claude-session-limit.test.mjs && node test/init-migration.test.mjs && node test/init-global.test.mjs && node test/mirror-fragments.test.mjs && node test/performance-tier-effort.test.mjs && node test/setup-repair.test.mjs && node test/setup-quoting.test.mjs && node test/setup-wire.test.mjs && node test/setup-cli-integration.test.mjs && node test/init.test.mjs && node test/model-selection-mode.test.mjs && node test/cli-args.test.mjs && node scripts/validate_provider.mjs && node scripts/validate_seed_sites.mjs && node scripts/validate_routing_audit.mjs && node test/seed-sites.test.mjs && node test/mcp-compliance.test.mjs"
|
|
40
|
+
"test": "npm run check:versions && node test/effort.test.mjs && node test/drivers.test.mjs && node test/platform.test.mjs && node test/wait.test.mjs && node test/status.test.mjs && node test/output.test.mjs && node test/stream.test.mjs && node test/routing.test.mjs && node test/deadlock.test.mjs && node test/handler-validation.test.mjs && node test/index-handler.test.mjs && node test/ruleset.test.mjs && node test/ruleset-exec.test.mjs && node test/ruleset-handler.test.mjs && node test/failover.test.mjs && node test/global-concurrency-cap.test.mjs && node test/orchestration-marker.test.mjs && node test/orchestration-hook-core.test.mjs && node test/orchestration-adapters.test.mjs && node test/orchestration-pretool.test.mjs && node test/orchestration-directives.test.mjs && node test/no-five-call.test.mjs && node test/check-worktree-subagent.test.mjs && node test/launch-agent-upsert.test.mjs && node test/claude-session-limit.test.mjs && node test/init-migration.test.mjs && node test/init-global.test.mjs && node test/mirror-fragments.test.mjs && node test/performance-tier-effort.test.mjs && node test/setup-repair.test.mjs && node test/setup-quoting.test.mjs && node test/setup-wire.test.mjs && node test/setup-cli-integration.test.mjs && node test/init.test.mjs && node test/model-selection-mode.test.mjs && node test/cli-args.test.mjs && node scripts/validate_provider.mjs && node scripts/validate_seed_sites.mjs && node scripts/validate_routing_audit.mjs && node test/seed-sites.test.mjs && node test/mcp-compliance.test.mjs"
|
|
41
41
|
},
|
|
42
42
|
"author": "Lexi Blackburn",
|
|
43
43
|
"license": "Apache-2.0",
|