taskflow-mcp-core 0.1.7 → 0.2.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 CHANGED
@@ -6,13 +6,14 @@
6
6
  <a href="https://www.npmjs.com/package/pi-taskflow"><img src="https://img.shields.io/npm/v/pi-taskflow?style=flat-square&color=4B4ACF&label=npm" alt="npm version"></a>
7
7
  <a href="https://www.npmjs.com/package/pi-taskflow"><img src="https://img.shields.io/npm/dm/pi-taskflow?style=flat-square&color=5A5D63&label=downloads" alt="npm downloads"></a>
8
8
  <a href="https://github.com/heggria/taskflow/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-0E8A66?style=flat-square" alt="MIT license"></a>
9
- <a href="#whats-inside"><img src="https://img.shields.io/badge/runtime%20deps-0-0E8A66?style=flat-square" alt="zero runtime dependencies"></a>
10
9
  <a href="https://github.com/heggria/taskflow/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/heggria/taskflow/ci.yml?branch=main&style=flat-square&label=CI" alt="CI status"></a>
11
- <a href="#whats-inside"><img src="https://img.shields.io/badge/tests-1140-4B4ACF?style=flat-square" alt="1140 tests"></a>
10
+ <a href="#whats-inside"><img src="https://img.shields.io/badge/tests-1500+-4B4ACF?style=flat-square" alt="1500+ tests"></a>
12
11
  <a href="#whats-inside"><img src="https://img.shields.io/badge/dogfooded-%E2%9C%93-0E8A66?style=flat-square" alt="dogfooded"></a>
13
- <a href="#run-it-on-your-agent"><img src="https://img.shields.io/badge/runs%20on-Pi%20%2B%20Codex%20%2B%20Claude%20Code%20%2B%20OpenCode-4B4ACF?style=flat-square" alt="runs on Pi, Codex, Claude Code, and OpenCode"></a>
12
+ <a href="#run-it-on-your-agent"><img src="https://img.shields.io/badge/runs%20on-Pi%20%2B%20Codex%20%2B%20Claude%20Code%20%2B%20OpenCode%20%2B%20Grok-4B4ACF?style=flat-square" alt="runs on Pi, Codex, Claude Code, OpenCode, and Grok Build"></a>
14
13
  </p>
15
14
 
15
+ <p align="center"><em>Release line <code>0.2.0</code> — monorepo packages and plugin pins are <code>0.2.0</code>; npm registry updates after the <code>v0.2.0</code> tag publish job. Badge above tracks the published npm line until then.</em></p>
16
+
16
17
  <p align="center">
17
18
  <b>English</b> ·
18
19
  <a href="https://github.com/heggria/taskflow/blob/main/README.zh-CN.md">简体中文</a>
@@ -24,7 +25,7 @@
24
25
 
25
26
  <p><strong>A declarative, verifiable <em>graph of tasks</em> for coding-agent subagents.</strong><br/>
26
27
  Not a workflow you script — a DAG you declare. Fan out · gate · loop · tournament · resume · save as a command — intermediate results stay out of your context.<br/>
27
- Runs on the <a href="https://pi.dev">Pi</a> coding agent, on <a href="https://github.com/openai/codex">OpenAI Codex</a>, on <a href="https://claude.com/product/claude-code">Claude Code</a>, and on <a href="https://opencode.ai">OpenCode</a>.</p>
28
+ Runs on the <a href="https://pi.dev">Pi</a> coding agent, on <a href="https://github.com/openai/codex">OpenAI Codex</a>, on <a href="https://claude.com/product/claude-code">Claude Code</a>, on <a href="https://opencode.ai">OpenCode</a>, and on <a href="https://docs.x.ai/build/overview">Grok Build</a>.</p>
28
29
 
29
30
  </div>
30
31
 
@@ -42,13 +43,20 @@ claude plugin install claude-taskflow@taskflow
42
43
 
43
44
  # OpenCode — add the MCP server to opencode.json (see the OpenCode guide)
44
45
  opencode mcp add taskflow -- npx -y -p opencode-taskflow opencode-taskflow-mcp
46
+
47
+ # Grok Build (published MCP package)
48
+ # First define custom taskflow-workspace/taskflow-readonly profiles extending
49
+ # workspace/read-only respectively in ~/.grok/sandbox.toml, then:
50
+ export PI_TASKFLOW_GROK_MUTATING_SANDBOX_PROFILE=taskflow-workspace
51
+ export PI_TASKFLOW_GROK_READONLY_SANDBOX_PROFILE=taskflow-readonly
52
+ grok mcp add taskflow -- npx -y -p grok-taskflow@0.2.0 grok-taskflow-mcp
45
53
  ```
46
54
 
47
55
  ---
48
56
 
49
57
  **A `workflow` flows. A `taskflow` is a *graph*.** Other orchestrators let the model *script* the work — imperative code that flows step by step, with the graph hidden inside control flow. `taskflow` does the opposite: you **declare** the work as a graph of discrete, named **task** nodes connected by `dependsOn` edges — and the runtime *verifies that graph before it spends a single token.*
50
58
 
51
- You already know your agent's built-in subagent shorthand — `task` / `tasks` / `chain`. `taskflow` speaks the *same* shorthand — so your existing delegations instantly become **tracked, resumable, and saveable by name** (on Pi, a saved flow becomes a one-word `/tf:<name>` command; on Codex, Claude Code, and OpenCode you run it by name through `taskflow_run`). When you outgrow the shorthand, the full DSL gives you a real DAG: dynamic fan-out over dozens of items, conditional routing, quality gates, human approvals, retries, loops, tournaments, and a hard spend ceiling.
59
+ You already know your agent's built-in subagent shorthand — `task` / `tasks` / `chain`. `taskflow` speaks the *same* shorthand — so your existing delegations instantly become **tracked, resumable, and saveable by name** (on Pi, a saved flow becomes a one-word `/tf:<name>` command; on Codex, Claude Code, OpenCode, and Grok Build you run it by name through `taskflow_run`). When you outgrow the shorthand, the full DSL gives you a real DAG: dynamic fan-out over dozens of items, conditional routing, quality gates, human approvals, retries, loops, tournaments, and an observed-usage budget stop-loss.
52
60
 
53
61
  And the whole time, **only the final phase reaches your conversation.** Every intermediate transcript stays in the runtime, never your context window.
54
62
 
@@ -91,7 +99,7 @@ Here's the wall you hit with raw subagents: you describe a multi-step plan in pr
91
99
  | **Conditional routing** | ✗ | **`when` guards + `join: any` OR-joins** |
92
100
  | **Fault tolerance** | ✗ | **per-phase `retry` + auto-retry on transient errors** |
93
101
  | **Human-in-the-loop** | ✗ | **`approval` phases (approve / reject / edit)** |
94
- | **Cost control** | ✗ | **run-wide `budget` (USD / token caps)** |
102
+ | **Cost control** | ✗ | **run-wide observed-usage `budget` stop-losses (USD / tokens)** |
95
103
  | **Composition** | ✗ | **`flow` phases run saved *or runtime-generated* sub-flows** |
96
104
  | **Iterative loops** | ✗ | **`loop` phases — repeat until condition, convergence, or cap** |
97
105
  | **Competitive selection** | ✗ | **`tournament` phases — N variants + judge** |
@@ -122,9 +130,9 @@ We chose the **verifiable** side on purpose. The expressivity you give up is rea
122
130
 
123
131
  The Pi ecosystem now has **20+ delegation, workflow, and orchestration extensions** — each great at what it's for. Here's an honest map of where `pi-taskflow` sits (verified against each package's latest npm release, June 2026). For the full breakdown — every package, strengths *and* weaknesses — see [`docs/internal/PI-ECOSYSTEM.md`](https://github.com/heggria/taskflow/blob/main/docs/internal/PI-ECOSYSTEM.md). For the broader, non-Pi landscape (LangGraph, Temporal, CrewAI, Mastra…) see [`docs/internal/COMPETITORS.md`](https://github.com/heggria/taskflow/blob/main/docs/internal/COMPETITORS.md).
124
132
 
125
- | Extension | Model | Custom DSL | DAG | Dynamic fan-out | Cross-session resume | Quality gate | Human approval | Save as command | Zero deps |
133
+ | Extension | Model | Custom DSL | DAG | Dynamic fan-out | Cross-session resume | Quality gate | Human approval | Save as command | Zero runtime deps |
126
134
  |---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
127
- | **taskflow** | **declarative multi-phase taskflows** | **✓** | **✓** | **✓ `map`** | **✓ phase-hash** | **✓** | **✓** | **✓ `/tf:<name>`** | **✓** |
135
+ | **taskflow** | **declarative multi-phase taskflows** | **✓** | **✓** | **✓ `map`** | **✓ phase-hash** | **✓** | **✓** | **✓ `/tf:<name>`** | **✕ (1 + peers)** |
128
136
  | [`@pi-agents/orchid`](https://www.npmjs.com/package/@pi-agents/orchid) | opinionated 9-phase pipeline + Ralph loop | fixed | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✕ (2) |
129
137
  | [`pi-crew`](https://www.npmjs.com/package/pi-crew) | role teams + git worktrees + async | partial | ✓ | ✓ | ✓ | ✓ | ✓ | – | ✕ (7) |
130
138
  | [`ultimate-pi`](https://www.npmjs.com/package/ultimate-pi) | governed plan→execute→review harness | YAML contracts | ✓ (plan-time) | ✕ | ✓ | ✓ (3-tier) | ✓ | ✓ | ✕ (16) |
@@ -139,14 +147,14 @@ The Pi ecosystem now has **20+ delegation, workflow, and orchestration extension
139
147
 
140
148
  **How to choose:**
141
149
 
142
- - **`@pi-agents/orchid`** is the most feature-complete orchestrator in the ecosystem (DAG + worktrees + Ralph loop + agent mailbox) — but its DSL is a *fixed* 9-phase pipeline, it carries runtime deps + jiti, and it's beta. Reach for `taskflow` when you want to **define your own graph** (not adopt an opinionated one) with **zero dependencies** and a one-command install.
143
- - **`pi-crew` / `ultimate-pi`** go heavier — worktree isolation, durable async teams, multi-tier governance. If you want lightweight, declarative, and zero-dependency, that's this project.
150
+ - **`@pi-agents/orchid`** is the most feature-complete orchestrator in the ecosystem (DAG + worktrees + Ralph loop + agent mailbox) — but its DSL is a *fixed* 9-phase pipeline, it carries runtime deps + jiti, and it's beta. Reach for `taskflow` when you want to **define your own graph** (not adopt an opinionated one) with **no host-SDK coupling** and a one-command install.
151
+ - **`pi-crew` / `ultimate-pi`** go heavier — worktree isolation, durable async teams, multi-tier governance. If you want a lightweight declarative engine with no host-SDK coupling, that's this project.
144
152
  - **`@zhushanwen/pi-workflow`** is the closest in spirit and also zero-dep, but it's the **imperative** side of the split above: you author workflows as **JavaScript scripts** the model writes and runs. `taskflow`'s **declarative JSON DAG** is the verifiable side — statically checkable, visualizable, safe to LLM-generate, and resumable at phase granularity rather than call-cache dedup.
145
153
  - **`@fiale-plus/pi-rogue-orchestration`** has a real **loop-until-done** (goal-driven iteration). `taskflow` now ships its own `loop` phase (v0.0.13+) plus `tournament` for competitive selection — and unlike rogue-orchestration, `taskflow` has a full DAG with gates, compositional sub-flows, and cross-session resume. For raw "keep going until the goal is met" with minimal structure, rogue-orchestration is still lighter; for structured, branching pipelines, `taskflow` covers the same ground and more.
146
154
  - **`pi-subagents` / `@gotgenes/pi-subagents`** are the mature picks for ad-hoc "use reviewer on this diff" delegation and background jobs. `taskflow` is for when those delegations need to become a *repeatable, resumable pipeline*.
147
155
  - **`pi-pipeline` / `pi-agent-flow`** ship *opinionated, fixed* flows. `taskflow` ships an *empty canvas*: you (or the model) declare the graph that fits the job.
148
156
 
149
- > The honest one-liner: **`pi-taskflow` is the only Pi extension that gives you a *declarative, verifiable, resumable* DAG of task nodes — saved as a one-word `/tf:<name>` command, with zero runtime dependencies and context isolation by design** (and the same engine runs on Codex via the `taskflow_*` MCP tools). Where code-mode workflows let the model *script* the work, `taskflow` lets it *declare a graph the runtime can prove correct before running.* Recently shipped from the roadmap: the Shared Context Tree (blackboard + supervision) and worktree isolation (see [`docs/internal/STRATEGY.md`](https://github.com/heggria/taskflow/blob/main/docs/internal/STRATEGY.md)).
157
+ > The honest one-liner: **`pi-taskflow` gives you a *declarative, verifiable, resumable* DAG of task nodes — saved as a one-word `/tf:<name>` command, with context isolation by design** (and the same engine runs on MCP hosts). The engine avoids host-SDK coupling; `typebox` is a peer dependency, the TypeScript DSL includes the compiler, and delivery packages depend on the internal taskflow packages.
150
158
 
151
159
  ## 30-second start
152
160
 
@@ -193,7 +201,7 @@ claude plugin marketplace add heggria/taskflow
193
201
  claude plugin install claude-taskflow@taskflow
194
202
  ```
195
203
 
196
- The plugin's MCP server runs via `npx` (a version-pinned `claude-taskflow`), so there's nothing else to install globally and the plugin version binds the exact code that runs. Each phase's subagent then runs as an isolated `claude -p` session. Just ask Claude Code to run a multi-phase or fan-out job and it calls the tools. See the [Claude Code guide](https://github.com/heggria/taskflow/blob/main/docs/claude-mcp.md).
204
+ The plugin's MCP server runs via `npx` (a version-pinned `claude-taskflow`), so there's nothing else to install globally and the plugin version binds the exact code that runs. Each phase's subagent then runs as an isolated `claude -p` session. **Claude Code 2.1.169+ is required** for the safe-mode isolation contract. Just ask Claude Code to run a multi-phase or fan-out job and it calls the tools. See the [Claude Code guide](https://github.com/heggria/taskflow/blob/main/docs/claude-mcp.md).
197
205
 
198
206
  ### On OpenCode
199
207
 
@@ -219,6 +227,36 @@ opencode mcp add taskflow -- npx -y -p opencode-taskflow opencode-taskflow-mcp
219
227
 
220
228
  The server runs via `npx` (a version-pinned `opencode-taskflow`), and each phase's subagent runs as an isolated `opencode run` session. OpenCode also auto-discovers the bundled routing skill (`**/SKILL.md`). Then just ask OpenCode to run a multi-phase or fan-out job and it calls the tools. See the [OpenCode guide](https://github.com/heggria/taskflow/blob/main/docs/opencode-mcp.md).
221
229
 
230
+ ### On Grok Build
231
+
232
+ The published path is the MCP package:
233
+
234
+ ```toml
235
+ # ~/.grok/sandbox.toml
236
+ [profiles.taskflow-workspace]
237
+ extends = "workspace"
238
+
239
+ [profiles.taskflow-readonly]
240
+ extends = "read-only"
241
+ ```
242
+
243
+ ```bash
244
+ export PI_TASKFLOW_GROK_MUTATING_SANDBOX_PROFILE=taskflow-workspace
245
+ export PI_TASKFLOW_GROK_READONLY_SANDBOX_PROFILE=taskflow-readonly
246
+ grok mcp add taskflow -- npx -y -p grok-taskflow@0.2.0 grok-taskflow-mcp
247
+ ```
248
+
249
+ A plugin scaffold is also available from a monorepo checkout:
250
+
251
+ ```bash
252
+ pnpm --filter grok-taskflow build
253
+ grok plugin install ./packages/grok-taskflow/plugin --trust
254
+ grok plugin enable taskflow
255
+ grok mcp add taskflow -- node "$(pwd)/packages/grok-taskflow/dist/mcp/bin.js"
256
+ ```
257
+
258
+ A public Grok plugin marketplace/source is not published yet; do not substitute a placeholder source. Each phase's subagent runs as an isolated `grok -p --output-format streaming-json` session. Mutating or omitted-tool phases require the custom profile above because Grok's built-in profiles may fail open when kernel enforcement is unavailable. Grok 0.2.93 does not report usage, so it rejects every flow that declares `budget`. Codex reports tokens but not cost, so it accepts `maxTokens` and rejects `maxUSD`; Pi, Claude Code, and OpenCode can enforce both dimensions as observed-usage stop-losses. See the [Grok Build guide](https://github.com/heggria/taskflow/blob/main/docs/grok-mcp.md).
259
+
222
260
  ### The shorthand (same shape as the built-in tool)
223
261
 
224
262
  ```jsonc
@@ -307,7 +345,7 @@ The shorthand is your onramp. The DSL is where `taskflow` earns its keep — dyn
307
345
 
308
346
  The intermediate summaries never enter your context. The runtime owns them; you get the report. **Save it once → `/tf:summarize-files dir=src` forever.**
309
347
 
310
- ### Route, gate, retry, approve, and cap the spend
348
+ ### Route, gate, retry, approve, and stop runaway spend
311
349
 
312
350
  ```jsonc
313
351
  {
@@ -331,7 +369,7 @@ The intermediate summaries never enter your context. The runtime owns them; you
331
369
 
332
370
  - **`when`** routes to `deep` *or* `quick` from the triage JSON — the other branch is skipped.
333
371
  - **`join: "any"`** lets `approve` fire the moment whichever branch ran completes (an OR-join).
334
- - **`retry`** re-runs a flaky patch with backoff; **`budget`** halts the whole run if it gets too expensive.
372
+ - **`retry`** re-runs a flaky patch with backoff; **`budget`** stops admitting new calls after reported usage crosses the threshold. A call already in flight may overshoot it.
335
373
  - **`approval`** pauses for a human (approve / reject / edit) before the final `ship`.
336
374
 
337
375
  No scripting. No JavaScript `eval`. Just data the runtime executes — safe enough to run LLM-generated definitions directly.
@@ -404,6 +442,8 @@ See [Tournament phases](#tournament-tournament) for the full reference.
404
442
  | `loop` | **iterate a task until done** — re-run a body until a condition, convergence, or a cap | `task`, `until` |
405
443
  | `tournament` | **N variants compete**, a judge picks the best (or aggregates) | `task` \| `branches` |
406
444
  | `script` | run a **shell command** — no LLM, zero tokens — capturing stdout as the phase output | `run` |
445
+ | `race` | **first successful** branch wins (optional `cancelLosers` abort) | `branches` (≥2) |
446
+ | `expand` | run a dynamic fragment (`nested` or `graft` promote) | `def` (+ `expandMode?`) |
407
447
 
408
448
  ### Common phase fields
409
449
 
@@ -433,6 +473,10 @@ Flow-level keys: `name`, `description`, `args`, `concurrency` (default 8), `agen
433
473
 
434
474
  ### Shared Context Tree (blackboard + supervision)
435
475
 
476
+ > **Host scope in 0.2.0:** `ctx_read` / `ctx_write` / `ctx_report` /
477
+ > `ctx_spawn` tool injection is available through `pi-taskflow`. The Codex,
478
+ > Claude, OpenCode, and Grok runners do not inject these tools yet.
479
+
436
480
  By default subagents are fully isolated — they share nothing and only return a
437
481
  final string. Opt a phase in with `shareContext: true` (or `contextSharing: true`
438
482
  flow-wide) to give its subagent four extra tools backed by a per-run, file-based
@@ -638,7 +682,7 @@ Condition grammar (for `when`): `== != < > <= >=`, `&& || !`, parentheses, quote
638
682
 
639
683
  ## Commands
640
684
 
641
- Saved flows become CLI shortcuts. **These `/tf` commands are Pi-only** (they run in the Pi session). On Codex, Claude Code, and OpenCode, use the `taskflow_*` MCP tools instead — `taskflow_list` / `taskflow_show` / `taskflow_run` (by `name`) / `taskflow_verify` / `taskflow_compile` / `taskflow_peek`.
685
+ Saved flows become CLI shortcuts. **These `/tf` commands are Pi-only** (they run in the Pi session). On Codex, Claude Code, OpenCode, and Grok Build, use the `taskflow_*` MCP tools instead — full set: `taskflow_run` / `list` / `show` / `verify` / `compile` / `peek` / `trace` / `replay` / `why_stale` / `recompute` (dry-run) / `save` / `search`.
642
686
 
643
687
  | Command | What it does |
644
688
  |---|---|
@@ -646,14 +690,19 @@ Saved flows become CLI shortcuts. **These `/tf` commands are Pi-only** (they run
646
690
  | `/tf run <name> [args]` | Run a saved flow (e.g. `/tf run summarize-files dir=src`) |
647
691
  | `/tf show <name>` | Print a flow's definition |
648
692
  | `/tf compile <name> [lr\|td]` | **Render the flow as a Mermaid diagram + verification overlay** — 0 tokens, no LLM; paste into a README/issue/PR |
693
+ | `/tf ir <name>` | Compile to **FlowIR** + content hash (`ir:<64-hex>`) — 0 tokens |
649
694
  | `/tf runs` | Browse recent run history (interactive TUI — **live auto-refreshes** while any run is active) |
650
695
  | `/tf resume <runId>` | Continue a paused/failed run — cached phases skip automatically |
651
696
  | `/tf peek <runId> [phaseId]` | Inspect a phase's intermediate output (the debugging escape hatch) |
697
+ | `/tf provenance <runId>` | Show observed read-sets for a completed run |
652
698
  | `/tf trace <runId> [--json]` | Show a run's **deterministic-replay event trace** (each subagent call + runtime decisions) |
699
+ | `/tf replay <runId> [--threshold phase=n] [--budget-usd n] [--json]` | **Offline what-if** re-judge of thresholds/budget from a recorded trace (zero tokens) |
700
+ | `/tf why-stale <runId> [phaseId]` | Explain the stale frontier (observed ∪ declared deps) |
701
+ | `/tf recompute <runId> <phaseId> [--apply]` | Dry-run (default) or apply minimal recompute of the stale frontier |
653
702
  | `/tf init` | **Interactively map model roles** to your enabled models (writes `~/.pi/agent/settings.json`) |
654
703
  | `/tf:<name> [args]` | Shortcut — runs the flow in one tap |
655
704
 
656
- Tool actions (used by the model on Pi): `run` (inline `define` or saved `name`), `save`, `resume`, `list`, `agents`, `init`, `verify`, `compile`, `ir`, `provenance`, `trace`, `why-stale`, `recompute`, `cache-clear`, `search`. On Codex, Claude Code, and OpenCode the exposed MCP tools are `taskflow_run` / `taskflow_list` / `taskflow_show` / `taskflow_verify` / `taskflow_compile` / `taskflow_peek` / `taskflow_trace` / `taskflow_why_stale` / `taskflow_recompute` (dry-run only) / `taskflow_save` / `taskflow_search`.
705
+ Tool actions (used by the model on Pi): `run` (inline `define` or saved `name`), `save`, `resume`, `list`, `agents`, `init`, `verify`, `compile`, `ir`, `provenance`, `trace`, `replay`, `why-stale`, `recompute`, `cache-clear`, `search`. On Codex, Claude Code, OpenCode, and Grok Build the exposed MCP tools are `taskflow_run` / `taskflow_list` / `taskflow_show` / `taskflow_verify` / `taskflow_compile` / `taskflow_peek` / `taskflow_trace` / `taskflow_replay` / `taskflow_why_stale` / `taskflow_recompute` (dry-run only) / `taskflow_save` / `taskflow_search`.
657
706
 
658
707
  ## Background (detached) execution
659
708
 
@@ -867,12 +916,12 @@ Copy one into `.pi/taskflows/<name>.json` (or `~/.pi/agent/taskflows/`) and it r
867
916
 
868
917
  <div align="center">
869
918
 
870
- **0 runtime dependencies** · **1140 tests** · **10 phase types** · **shared context tree** · **cross-session resume** · **cross-run memoization** · **per-item map caching** · **incremental recompute** · **FlowIR compile seam** · **detached execution** · **`compile` Mermaid renderer** · **~9k LOC runtime**
919
+ **Node.js ≥ 22.19.0** · **1500+ tests / 100 test files** · **12 phase types** · **shared context tree** · **cross-session resume** · **cross-run memoization** · **per-item map caching** · **incremental recompute** · **FlowIR compile seam** · **detached execution** · **MCP compile: SVG + text** · **Pi compile: Mermaid**
871
920
 
872
921
  </div>
873
922
 
874
- - **Zero runtime dependencies.** No `dependencies` field — the runtime is built entirely on Node built-ins (`fs` / `path` / `os` / `child_process` / `crypto`). The file lock is `fs.openSync("wx")`, not a third-party library.
875
- - **1140 tests across 70 test files** covering concurrency, atomic file locking (8-process race regressions), path-traversal hardening, cross-session resume, cross-run cache freshness (flow/thinking/tools key isolation, fingerprint invalidation, TTL/LRU eviction), backward-compatible cache-key migration (4-tier legacy fallback), per-phase structural sub-fingerprint (v3:phasefp — editing one phase invalidates only it and its dependents), per-item map caching (one changed item re-executes, N−1 cache hits), the `incremental` flag (run-wide cross-run default), reuse reporting, the FlowIR compile seam (determinism, declared-plane synthesis), incremental recompute (early-cutoff propagation, partial cascade strictly < full, observed ∪ declared union frontier), gate verdicts, budget caps, retry/backoff, approval flows, loop termination, tournament judging, sub-flow composition, the shared context tree (blackboard reuse, supervision spawn, subflow validation/nesting), workspace isolation (temp/dedicated/worktree lifecycle, fail-open degrade, dynamic-flow rejection), dynamic sub-flow security hardening, detached execution (PID persistence, stale detection, crash→failed, resume after failure), live run-history refresh, callback isolation, the idle watchdog, model-role init config, parseModelFromLabel with parenthesized-model-name regression, multi-fence `safeParse` recovery, host argv-contract locking (codex/claude/opencode `buildXxxArgs`), the `compile` Mermaid renderer (id-collision disambiguation, markdown-injection hardening, and full verify-overlay category coverage), plus the library Phase 1 metadata/search/store layer (phaseSignature, generality, CJK text scoring, staleness detection, sidecar persistence, A1 ghost-flow guard).
923
+ - **Accurate dependency boundary.** The MCP protocol implementation has no MCP SDK dependency and uses Node built-ins. `taskflow-core` has no direct `dependencies` but peers on `typebox`; `taskflow-dsl` depends on TypeScript; host delivery packages depend on the internal core/runner/MCP packages. All packages require Node.js ≥ 22.19.0.
924
+ - **1500+ tests across 100 test files** covering concurrency, persistence, security, resume/cache, all 12 phase kinds, FlowIR/replay, the TypeScript DSL, and host argv/MCP contracts.
876
925
  - **Hardened by design.** Path-traversal defense (lexical + `realpath` containment check), runId validation, HTML/error sanitization, atomic writes, stale-lock stealing via `rename`, and an idle watchdog that kills wedged subagents (SIGTERM → SIGKILL after 5 minutes of silence). Dynamic sub-flows additionally get breadth caps, `cwd` containment, budget clamping, nesting depth caps, and prototype-pollution defense.
877
926
  - **Dogfooded.** Every new feature has to survive the project's own `self-improve` taskflow before it ships.
878
927
 
@@ -897,7 +946,10 @@ Our `self-improve` flow is a 10-phase DAG — it audits the codebase, patches de
897
946
 
898
947
  ## Status & limits
899
948
 
900
- **v0.1.7** (current release) — **file loaders now report *why* a file failed with the parse position** (line/column) instead of a merged "not found or unparseable" message — `defineFile`, saved flows, run records, and library sidecars all distinguish *missing* from *malformed*, so a stray bare newline in a hand-authored flow is diagnosable in seconds; `safeParse` stays lenient for LLM output. Also fixes a pi-taskflow hint that re-printed every session. **Gate safety hardening (issue #54)**: a shared emphasis-tolerant marker factory now covers **all three decision markers** — `VERDICT`, `WINNER`, and `SCORE` — so Markdown-wrapped tokens (`VERDICT: **BLOCK**`, `WINNER: __3__`, `SCORE: `0.8``) are never silently mis-read (a genuine BLOCK no longer becomes PASS; a judge's pick no longer silently reverts to variant 1); **unparseable gate *model output now fails closed* (BLOCK)** instead of rubber-stamping PASS — a gate that cannot reach a verdict cannot be trusted to pass, while *config* slips (unresolved `score.target`, malformed `scorers`) stay fail-open with a warning; and free-text gates whose task omits a `VERDICT:` instruction now get the exact format suffix **auto-appended**. For the most robust decision phases, use `output: "json"` + `expect` to machine-validate the output (now the documented default for gate verdicts, tournament winners, and router branches). **v0.1.6** added **library Phase 1** (search-before-author + reusable-flow sidecar metadata), the **`defineFile`** parameter (verify/compile/run a flow from a path on disk), and **JSONC comment support** in flow definition files (`//` and `/* */` comments + trailing commas, parsed by the new zero-dependency `parseJsonc`). **v0.1.5** added **Claude Code and OpenCode as hosts**, **extracted the MCP server into its own `taskflow-mcp-core` package**, and **de-duplicated the three host runners** into a shared `runSubagentProcess`. See [CHANGELOG](https://github.com/heggria/taskflow/blob/main/CHANGELOG.md) for the full history. Baseline: **multi-host monorepo of seven packages** — the host-neutral `taskflow-core` engine, the host-neutral `taskflow-mcp-core` MCP server, the shared host-runner `taskflow-hosts`, plus `pi-taskflow` (Pi adapter), `codex-taskflow`, `claude-taskflow`, and `opencode-taskflow` (the three delivery packages re-export their runners from `taskflow-hosts` and each ships an MCP bin + plugin/config), all sharing the host-neutral MCP server in `taskflow-mcp-core`. **Library Phase 1**: save flows with `purpose`+`tags` via `taskflow_save` (MCP) or `action=save` (Pi), search them with structural + CJK-aware keyword scoring via `taskflow_search`/`action=search`, and track `reuseCount` via `reusedFromSearch`. **`defineFile`**: pass a `defineFile` path (or `{defineFile, name}`) to `action=run` (Pi) or `taskflow_run`/`taskflow_verify`/`taskflow_compile` (MCP) instead of an inline `define`, and the engine reads the flow from disk — pair it with JSONC comments to annotate saved flows. **JSONC**: flow-definition `.json` files may now carry `//` and `/* */` comments and trailing commas (parsed by `parseJsonc`, re-exported from the `taskflow-core` barrel); LLM-output parsing via `safeParse` stays strict. **Shared Context Tree**: opt-in (`shareContext` / `contextSharing`) blackboard + supervision tools (`ctx_read`/`ctx_write` horizontal reuse, `ctx_report`/`ctx_spawn` vertical supervision); `ctx_spawn` accepts a flat task **or** a dependency-bearing `subflow` (a runtime-validated nested DAG), depth-capped on a unified nesting counter with budget accounting. **Workspace isolation**: a phase's `cwd` accepts reserved keywords `temp`/`dedicated`/`worktree` — the runtime allocates an isolated dir (or a git worktree on a throwaway branch) and tears it down after the phase, fail-open, rejected in LLM-authored sub-flows. **Detached execution**: runs can execute in the background, detached from the Pi session. Prior: loop-until-done (`loop`), tournament (best-of-N with a judge), cross-run memoization (content-addressed cache with git/file/glob/env fingerprints and TTL), interactive `/tf init`, configurable built-in agents, 18 built-in agents with 6 model roles. Full control-flow & reliability layer (`when` guards, `join: any`, `retry`/backoff, `approval`, `flow` composition, `budget` caps, `onBlock: "retry"`, `eval` machine gates, idle watchdog) on top of the DSL + DAG runtime (`agent`/`parallel`/`map`/`gate`/`reduce`). Inline + saved flows, cross-session resume, live progress, and isolated context. A run executes as one streaming tool call.
949
+ **Compatibility baseline from v0.1.8:** interpolation placeholders in phase
950
+ `cwd` are rejected; the release dependency/security sweep is also retained.
951
+
952
+ **v0.2.0** (this monorepo release line — npm after `v0.2.0` tag) — adds the `taskflow-dsl` TypeScript frontend, Grok Build delivery package, 12 phase kinds with `race`/`expand`, FlowIR content hashes, event-kernel trace/fold, and offline replay. **v0.1.7** — **file loaders now report *why* a file failed with the parse position** (line/column) instead of a merged "not found or unparseable" message — `defineFile`, saved flows, run records, and library sidecars all distinguish *missing* from *malformed*, so a stray bare newline in a hand-authored flow is diagnosable in seconds; `safeParse` stays lenient for LLM output. Also fixes a pi-taskflow hint that re-printed every session. **Gate safety hardening (issue #54)**: a shared emphasis-tolerant marker factory now covers **all three decision markers** — `VERDICT`, `WINNER`, and `SCORE` — so Markdown-wrapped tokens (`VERDICT: **BLOCK**`, `WINNER: __3__`, `SCORE: `0.8``) are never silently mis-read (a genuine BLOCK no longer becomes PASS; a judge's pick no longer silently reverts to variant 1); **unparseable gate *model output now fails closed* (BLOCK)** instead of rubber-stamping PASS — a gate that cannot reach a verdict cannot be trusted to pass, while *config* slips (unresolved `score.target`, malformed `scorers`) stay fail-open with a warning; and free-text gates whose task omits a `VERDICT:` instruction now get the exact format suffix **auto-appended**. For the most robust decision phases, use `output: "json"` + `expect` to machine-validate the output (now the documented default for gate verdicts, tournament winners, and router branches). **v0.1.6** added **library Phase 1** (search-before-author + reusable-flow sidecar metadata), the **`defineFile`** parameter (verify/compile/run a flow from a path on disk), and **JSONC comment support** in flow definition files (`//` and `/* */` comments + trailing commas, parsed by the new zero-dependency `parseJsonc`). **v0.1.5** added **Claude Code and OpenCode as hosts**, **extracted the MCP server into its own `taskflow-mcp-core` package**, and **de-duplicated the three host runners** into a shared `runSubagentProcess`. See [CHANGELOG](https://github.com/heggria/taskflow/blob/main/CHANGELOG.md) for the full history. Baseline: **multi-host monorepo of nine packages** — the host-neutral `taskflow-core` engine, the host-neutral `taskflow-mcp-core` MCP server, the shared host-runner `taskflow-hosts`, the `taskflow-dsl` compiler, plus `pi-taskflow` (Pi adapter), `codex-taskflow`, `claude-taskflow`, `opencode-taskflow`, and `grok-taskflow` (the four delivery packages re-export their runners from `taskflow-hosts` and each ships an MCP bin + plugin/config), all sharing the host-neutral MCP server in `taskflow-mcp-core`. **Library Phase 1**: save flows with `purpose`+`tags` via `taskflow_save` (MCP) or `action=save` (Pi), search them with structural + CJK-aware keyword scoring via `taskflow_search`/`action=search`, and track `reuseCount` via `reusedFromSearch`. **`defineFile`**: pass a `defineFile` path (or `{defineFile, name}`) to `action=run` (Pi) or `taskflow_run`/`taskflow_verify`/`taskflow_compile` (MCP) instead of an inline `define`, and the engine reads the flow from disk — pair it with JSONC comments to annotate saved flows. **JSONC**: flow-definition `.json` files may now carry `//` and `/* */` comments and trailing commas (parsed by `parseJsonc`, re-exported from the `taskflow-core` barrel); LLM-output parsing via `safeParse` stays strict. **Shared Context Tree**: opt-in (`shareContext` / `contextSharing`) blackboard + supervision tools (`ctx_read`/`ctx_write` horizontal reuse, `ctx_report`/`ctx_spawn` vertical supervision); `ctx_spawn` accepts a flat task **or** a dependency-bearing `subflow` (a runtime-validated nested DAG), depth-capped on a unified nesting counter with budget accounting. **Workspace isolation**: a phase's `cwd` accepts reserved keywords `temp`/`dedicated`/`worktree` — the runtime allocates an isolated dir (or a git worktree on a throwaway branch) and tears it down after the phase, fail-open, rejected in LLM-authored sub-flows. **Detached execution**: runs can execute in the background, detached from the Pi session. Prior: loop-until-done (`loop`), tournament (best-of-N with a judge), cross-run memoization (content-addressed cache with git/file/glob/env fingerprints and TTL), interactive `/tf init`, configurable built-in agents, 18 built-in agents with 6 model roles. Full control-flow & reliability layer (`when` guards, `join: any`, `retry`/backoff, `approval`, `flow` composition, `budget` caps, `onBlock: "retry"`, `eval` machine gates, idle watchdog) on top of the DSL + DAG runtime (`agent`/`parallel`/`map`/`gate`/`reduce`). Inline + saved flows, cross-session resume, live progress, and isolated context. A run executes as one streaming tool call.
901
953
 
902
954
  Known boundaries (tracked, bounded — no surprises mid-flow):
903
955
 
@@ -906,31 +958,34 @@ Known boundaries (tracked, bounded — no surprises mid-flow):
906
958
  - **No `output: "file"`.** Outputs are text/JSON only — write files via an agent's `write` tool call.
907
959
  - **`map` fans out over a JSON array from a string `over`.** The `over` field is a string that either interpolates to a JSON array (e.g. `{steps.ID.json}`) or is a literal JSON-array string. Wrap a plain text list in a single-agent `output: "json"` phase first, or pass `JSON.stringify([...])` for a fixed list. (A raw literal array is rejected — emit it from a phase and reference that.)
908
960
  - **The DAG must be acyclic.** Cycles are rejected at validation.
909
- - **Cross-run cache excludes `gate`, `approval`, `loop`, `tournament`, and `script`.** These must produce a fresh result each run (a `script` phase may also have side effects).
961
+ - **Cross-run cache excludes `gate`, `approval`, `loop`, `tournament`, `script`, `race`, and `expand`.** These must produce a fresh result each run.
910
962
  - **Approval auto-rejects in detached mode.** This is a safety invariant — approval gates are never silently bypassed.
911
963
 
912
964
  ## Development
913
965
 
914
- `taskflow` is a pnpm-workspace monorepo of seven published packages:
966
+ `taskflow` is a pnpm-workspace monorepo of nine packages (eight host/core + `taskflow-dsl`):
915
967
 
916
968
  | Package | Role |
917
969
  |---------|------|
918
970
  | [`taskflow-core`](https://github.com/heggria/taskflow/blob/main/packages/taskflow-core) | Host-neutral orchestration engine (zero host-SDK deps; only `typebox`) — runtime, DSL, cache, verify |
919
971
  | [`taskflow-mcp-core`](https://github.com/heggria/taskflow/blob/main/packages/taskflow-mcp-core) | Host-neutral MCP server (stdio JSON-RPC + `taskflow_*` tools + DAG renderer); depends on core |
920
- | [`taskflow-hosts`](https://github.com/heggria/taskflow/blob/main/packages/taskflow-hosts) | Shared host-runner collection — the codex/claude/opencode `SubagentRunner` impls + their argv builders + event-stream parsers; depends on core |
972
+ | [`taskflow-hosts`](https://github.com/heggria/taskflow/blob/main/packages/taskflow-hosts) | Shared host-runner collection — the codex/claude/opencode/grok `SubagentRunner` impls + their argv builders + event-stream parsers; depends on core |
973
+ | [`taskflow-dsl`](https://github.com/heggria/taskflow/blob/main/packages/taskflow-dsl) | TypeScript DSL CLI/package — erases `.tf.ts` to Taskflow JSON and optional FlowIR; depends on core |
921
974
  | [`pi-taskflow`](https://github.com/heggria/taskflow/blob/main/packages/pi-taskflow) | Pi extension adapter — `taskflow` tool + `/tf` commands (what `pi install npm:pi-taskflow` gives you) |
922
975
  | [`codex-taskflow`](https://github.com/heggria/taskflow/blob/main/packages/codex-taskflow) | Codex MCP server + bin + [Codex plugin](https://github.com/heggria/taskflow/blob/main/packages/codex-taskflow/plugin) (re-exports the runner from `taskflow-hosts`) ([guide](https://github.com/heggria/taskflow/blob/main/docs/codex-mcp.md)) |
923
976
  | [`claude-taskflow`](https://github.com/heggria/taskflow/blob/main/packages/claude-taskflow) | Claude Code MCP server + bin + [Claude Code plugin](https://github.com/heggria/taskflow/blob/main/packages/claude-taskflow/plugin) (re-exports the runner from `taskflow-hosts`) ([guide](https://github.com/heggria/taskflow/blob/main/docs/claude-mcp.md)) |
924
977
  | [`opencode-taskflow`](https://github.com/heggria/taskflow/blob/main/packages/opencode-taskflow) | OpenCode MCP server + bin + [OpenCode config scaffold](https://github.com/heggria/taskflow/blob/main/packages/opencode-taskflow/plugin) (re-exports the runner from `taskflow-hosts`) ([guide](https://github.com/heggria/taskflow/blob/main/docs/opencode-mcp.md)) |
978
+ | [`grok-taskflow`](https://github.com/heggria/taskflow/blob/main/packages/grok-taskflow) | Grok Build MCP server + bin + [Grok plugin](https://github.com/heggria/taskflow/blob/main/packages/grok-taskflow/plugin) (re-exports the runner from `taskflow-hosts`) ([guide](https://github.com/heggria/taskflow/blob/main/docs/grok-mcp.md)) |
925
979
 
926
980
  ```bash
927
981
  pnpm install
928
982
  pnpm run typecheck # tsc --noEmit across all packages (no build needed)
929
983
  pnpm test # unit tests — no network, no process spawning
930
- pnpm run test:hosts # host-runner tests only (also: test:pi, test:codex, test:claude, test:opencode)
931
- pnpm run build # emit dist/*.js + .d.ts for all seven packages
984
+ pnpm run test:hosts # host-runner tests only (also: test:pi, test:codex, test:claude, test:opencode, test:grok)
985
+ pnpm run build # emit dist/*.js + .d.ts for all nine packages
932
986
  pnpm run test:e2e-codex # codex executor e2e (needs `codex` + model access)
933
987
  pnpm run test:e2e-codex-mcp # codex MCP server e2e
988
+ pnpm run test:e2e-grok-mcp # grok MCP server e2e (no live model required)
934
989
  ```
935
990
 
936
991
  The pi end-to-end suites spawn live `pi` subagents and are run directly (they use
@@ -30,7 +30,12 @@ export declare const RPC: {
30
30
  readonly METHOD_NOT_FOUND: -32601;
31
31
  readonly INVALID_PARAMS: -32602;
32
32
  readonly INTERNAL_ERROR: -32603;
33
+ readonly REQUEST_CANCELLED: -32800;
33
34
  };
35
+ /** Maximum time stdio shutdown waits for request wrappers after aborting them.
36
+ * Non-cooperative user handlers are detached (their eventual rejection is
37
+ * observed) so a broken handler can never hold the MCP process open forever. */
38
+ export declare const TRANSPORT_SHUTDOWN_GRACE_MS = 100;
34
39
  /** Thrown by a handler to return a structured JSON-RPC error to the client. */
35
40
  export declare class RpcError extends Error {
36
41
  code: number;
@@ -42,7 +47,13 @@ export declare class RpcError extends Error {
42
47
  * a structured failure. Returning `undefined` for a request (has id) sends
43
48
  * `result: null`; for a notification it is ignored.
44
49
  */
45
- export type RpcHandler = (params: unknown) => Promise<unknown> | unknown;
50
+ export interface RpcContext {
51
+ /** JSON-RPC request id. Notifications use null. */
52
+ requestId: string | number | null;
53
+ /** Aborted by MCP `notifications/cancelled` or transport disconnect. */
54
+ signal: AbortSignal;
55
+ }
56
+ export type RpcHandler = (params: unknown, context: RpcContext) => Promise<unknown> | unknown;
46
57
  /**
47
58
  * Run a JSON-RPC stdio loop over the given streams (defaults to process
48
59
  * stdin/stdout). Resolves when the input stream ends (client disconnect).
@@ -1 +1 @@
1
- {"version":3,"file":"jsonrpc.d.ts","sourceRoot":"","sources":["../../src/mcp/jsonrpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED,kDAAkD;AAClD,eAAO,MAAM,GAAG;;;;;;CAMN,CAAC;AAEX,+EAA+E;AAC/E,qBAAa,QAAS,SAAQ,KAAK;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;gBACH,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;CAMzD;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAEzE;;;GAGG;AACH,wBAAgB,UAAU,CACzB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACpC,EAAE,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;CAAO,GACxE,OAAO,CAAC,IAAI,CAAC,CA+Ef"}
1
+ {"version":3,"file":"jsonrpc.d.ts","sourceRoot":"","sources":["../../src/mcp/jsonrpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED,kDAAkD;AAClD,eAAO,MAAM,GAAG;aACf,WAAW,EAAE,CAAC,KAAK;aACnB,eAAe,EAAE,CAAC,KAAK;aACvB,gBAAgB,EAAE,CAAC,KAAK;aACxB,cAAc,EAAE,CAAC,KAAK;aACtB,cAAc,EAAE,CAAC,KAAK;aACtB,iBAAiB,EAAE,CAAC,KAAK;CAChB,CAAC;AAEX;;gFAEgF;AAChF,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,+EAA+E;AAC/E,qBAAa,QAAS,SAAQ,KAAK;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAKxD;CACD;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IAC1B,mDAAmD;IACnD,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAClC,wEAAwE;IACxE,MAAM,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAE9F;;;GAGG;AACH,wBAAgB,UAAU,CACzB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACpC,EAAE,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;CAAO,GACxE,OAAO,CAAC,IAAI,CAAC,CAiMf"}
@@ -19,7 +19,12 @@ export const RPC = {
19
19
  METHOD_NOT_FOUND: -32601,
20
20
  INVALID_PARAMS: -32602,
21
21
  INTERNAL_ERROR: -32603,
22
+ REQUEST_CANCELLED: -32800,
22
23
  };
24
+ /** Maximum time stdio shutdown waits for request wrappers after aborting them.
25
+ * Non-cooperative user handlers are detached (their eventual rejection is
26
+ * observed) so a broken handler can never hold the MCP process open forever. */
27
+ export const TRANSPORT_SHUTDOWN_GRACE_MS = 100;
23
28
  /** Thrown by a handler to return a structured JSON-RPC error to the client. */
24
29
  export class RpcError extends Error {
25
30
  code;
@@ -38,8 +43,24 @@ export class RpcError extends Error {
38
43
  export function serveStdio(handlers, io = {}) {
39
44
  const input = io.input ?? process.stdin;
40
45
  const output = io.output ?? process.stdout;
46
+ const activeRequests = new Map();
47
+ const activeControllers = new Set();
48
+ const pending = new Set();
49
+ const requestKey = (id) => `${typeof id}:${String(id)}`;
50
+ let transportClosed = false;
51
+ let requestTransportTeardown;
41
52
  const write = (obj) => {
42
- output.write(JSON.stringify(obj) + "\n");
53
+ if (transportClosed)
54
+ return;
55
+ try {
56
+ output.write(JSON.stringify(obj) + "\n");
57
+ }
58
+ catch {
59
+ // Some Writable implementations throw synchronously instead of reporting
60
+ // failures through their callback. Route both forms through the same
61
+ // teardown so active work is aborted and shutdown remains bounded.
62
+ requestTransportTeardown?.();
63
+ }
43
64
  };
44
65
  const respondOk = (id, result) => {
45
66
  write({ jsonrpc: "2.0", id, result: result === undefined ? null : result });
@@ -67,6 +88,17 @@ export function serveStdio(handlers, io = {}) {
67
88
  respondErr(id, { code: RPC.INVALID_REQUEST, message: "Invalid Request" });
68
89
  return;
69
90
  }
91
+ // MCP cancellation is a notification aimed at an in-flight request. Handle
92
+ // it in the transport so every method automatically receives the same
93
+ // AbortSignal and hosts do not need bespoke cancellation handlers.
94
+ if (msg.method === "notifications/cancelled") {
95
+ const params = msg.params;
96
+ const cancelledId = params?.requestId;
97
+ if (typeof cancelledId === "string" || typeof cancelledId === "number") {
98
+ activeRequests.get(requestKey(cancelledId))?.abort();
99
+ }
100
+ return;
101
+ }
70
102
  const handler = handlers[msg.method];
71
103
  if (!handler) {
72
104
  // Unknown notifications are silently ignored (e.g. notifications/*).
@@ -74,15 +106,57 @@ export function serveStdio(handlers, io = {}) {
74
106
  respondErr(id, { code: RPC.METHOD_NOT_FOUND, message: `Method not found: ${msg.method}` });
75
107
  return;
76
108
  }
109
+ const key = !isNotification && id !== null ? requestKey(id) : undefined;
110
+ if (key) {
111
+ const duplicate = activeRequests.get(key);
112
+ if (duplicate) {
113
+ // JSON-RPC ids identify one in-flight request. Never overwrite the
114
+ // original controller: abort the ambiguous first request and ignore the
115
+ // duplicate, yielding one deterministic cancellation response.
116
+ duplicate.abort();
117
+ return;
118
+ }
119
+ }
120
+ const controller = new AbortController();
121
+ activeControllers.add(controller);
122
+ if (key)
123
+ activeRequests.set(key, controller);
124
+ const ABORTED = Symbol("aborted");
125
+ let abortListener;
126
+ const aborted = new Promise((resolve) => {
127
+ abortListener = () => resolve(ABORTED);
128
+ if (controller.signal.aborted)
129
+ resolve(ABORTED);
130
+ else
131
+ controller.signal.addEventListener("abort", abortListener, { once: true });
132
+ });
133
+ // Invoke immediately so synchronous protocol handlers can complete before a
134
+ // following stdin EOF, but normalize throws and async results into one promise.
135
+ let handlerPromise;
77
136
  try {
78
- const result = await handler(msg.params);
79
- if (!isNotification)
80
- respondOk(id, result);
137
+ handlerPromise = Promise.resolve(handler(msg.params, { requestId: id, signal: controller.signal }));
138
+ }
139
+ catch (error) {
140
+ handlerPromise = Promise.reject(error);
141
+ }
142
+ // Promise.race installs a rejection observer on handlerPromise. If abort wins,
143
+ // a later handler rejection is consumed and can never become unhandled.
144
+ try {
145
+ const result = await Promise.race([handlerPromise, aborted]);
146
+ if (!isNotification) {
147
+ if (result === ABORTED || controller.signal.aborted)
148
+ respondErr(id, { code: RPC.REQUEST_CANCELLED, message: "Request cancelled" });
149
+ else
150
+ respondOk(id, result);
151
+ }
81
152
  }
82
153
  catch (e) {
83
154
  if (isNotification)
84
155
  return; // can't report errors for notifications
85
- if (e instanceof RpcError) {
156
+ if (controller.signal.aborted) {
157
+ respondErr(id, { code: RPC.REQUEST_CANCELLED, message: "Request cancelled" });
158
+ }
159
+ else if (e instanceof RpcError) {
86
160
  respondErr(id, { code: e.code, message: e.message, data: e.data });
87
161
  }
88
162
  else {
@@ -90,29 +164,84 @@ export function serveStdio(handlers, io = {}) {
90
164
  respondErr(id, { code: RPC.INTERNAL_ERROR, message });
91
165
  }
92
166
  }
167
+ finally {
168
+ if (abortListener)
169
+ controller.signal.removeEventListener("abort", abortListener);
170
+ activeControllers.delete(controller);
171
+ if (key && activeRequests.get(key) === controller)
172
+ activeRequests.delete(key);
173
+ }
93
174
  };
94
175
  return new Promise((resolve) => {
95
176
  let buffer = "";
96
- // Serialize line handling so responses are emitted in request order even
97
- // when handlers are async (MCP clients tolerate interleaving, but ordered
98
- // output is simpler to reason about and test).
99
- let chain = Promise.resolve();
100
- input.on("data", (data) => {
177
+ let finishPromise;
178
+ let resolved = false;
179
+ const track = (promise) => {
180
+ pending.add(promise);
181
+ void promise.then(() => pending.delete(promise), () => pending.delete(promise));
182
+ };
183
+ const removeTransportListeners = () => {
184
+ input.removeListener("data", onData);
185
+ input.removeListener("end", onEnd);
186
+ input.removeListener("close", onClose);
187
+ input.removeListener("error", onInputError);
188
+ output.removeListener("error", onOutputError);
189
+ requestTransportTeardown = undefined;
190
+ };
191
+ const finish = () => {
192
+ transportClosed = true;
193
+ for (const controller of activeControllers)
194
+ controller.abort();
195
+ if (!finishPromise) {
196
+ finishPromise = new Promise((done) => {
197
+ let completed = false;
198
+ const settle = () => {
199
+ if (completed)
200
+ return;
201
+ completed = true;
202
+ clearTimeout(timer);
203
+ done();
204
+ };
205
+ const timer = setTimeout(settle, TRANSPORT_SHUTDOWN_GRACE_MS);
206
+ void Promise.allSettled([...pending]).then(settle);
207
+ }).then(() => {
208
+ removeTransportListeners();
209
+ if (!resolved) {
210
+ resolved = true;
211
+ resolve();
212
+ }
213
+ });
214
+ }
215
+ return finishPromise;
216
+ };
217
+ const teardown = () => void finish();
218
+ const onData = (data) => {
219
+ if (transportClosed)
220
+ return;
101
221
  buffer += data.toString();
102
222
  let i;
103
223
  while ((i = buffer.indexOf("\n")) >= 0) {
104
224
  const line = buffer.slice(0, i);
105
225
  buffer = buffer.slice(i + 1);
106
- chain = chain.then(() => handleLine(line));
226
+ track(handleLine(line));
107
227
  }
108
- });
109
- input.on("end", () => {
110
- chain = chain.then(() => {
111
- if (buffer.trim())
112
- return handleLine(buffer);
113
- }).then(() => resolve());
114
- });
115
- input.on("close", () => resolve());
228
+ };
229
+ const onEnd = () => {
230
+ if (transportClosed)
231
+ return;
232
+ if (buffer.trim())
233
+ track(handleLine(buffer));
234
+ teardown();
235
+ };
236
+ const onClose = teardown;
237
+ const onInputError = teardown;
238
+ const onOutputError = teardown;
239
+ requestTransportTeardown = teardown;
240
+ input.on("data", onData);
241
+ input.on("end", onEnd);
242
+ input.on("close", onClose);
243
+ input.on("error", onInputError);
244
+ output.on("error", onOutputError);
116
245
  });
117
246
  }
118
247
  //# sourceMappingURL=jsonrpc.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"jsonrpc.js","sourceRoot":"","sources":["../../src/mcp/jsonrpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,kDAAkD;AAClD,MAAM,CAAC,MAAM,GAAG,GAAG;IAClB,WAAW,EAAE,CAAC,KAAK;IACnB,eAAe,EAAE,CAAC,KAAK;IACvB,gBAAgB,EAAE,CAAC,KAAK;IACxB,cAAc,EAAE,CAAC,KAAK;IACtB,cAAc,EAAE,CAAC,KAAK;CACb,CAAC;AAEX,+EAA+E;AAC/E,MAAM,OAAO,QAAS,SAAQ,KAAK;IAClC,IAAI,CAAS;IACb,IAAI,CAAW;IACf,YAAY,IAAY,EAAE,OAAe,EAAE,IAAc;QACxD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CACD;AASD;;;GAGG;AACH,MAAM,UAAU,UAAU,CACzB,QAAoC,EACpC,KAAwE,EAAE;IAE1E,MAAM,KAAK,GAA0B,EAAE,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC;IAC/D,MAAM,MAAM,GAA0B,EAAE,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAElE,MAAM,KAAK,GAAG,CAAC,GAAY,EAAE,EAAE;QAC9B,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,EAA0B,EAAE,MAAe,EAAE,EAAE;QACjE,KAAK,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,CAAC,CAAC;IACF,MAAM,UAAU,GAAG,CAAC,EAA0B,EAAE,GAAiB,EAAE,EAAE;QACpE,KAAK,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,KAAK,EAAE,IAAY,EAAiB,EAAE;QACxD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,IAAI,GAAmB,CAAC;QACxB,IAAI,CAAC;YACJ,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACR,gEAAgE;YAChE,UAAU,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;YACpE,OAAO;QACR,CAAC;QAED,MAAM,cAAc,GAAG,GAAG,CAAC,EAAE,KAAK,SAAS,IAAI,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC;QAC/D,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,IAAI,CAA2B,CAAC;QAEtD,IAAI,GAAG,CAAC,OAAO,KAAK,KAAK,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC7D,IAAI,CAAC,cAAc;gBAAE,UAAU,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,eAAe,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,CAAC;YAC/F,OAAO;QACR,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,qEAAqE;YACrE,IAAI,CAAC,cAAc;gBAAE,UAAU,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,gBAAgB,EAAE,OAAO,EAAE,qBAAqB,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAChH,OAAO;QACR,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACzC,IAAI,CAAC,cAAc;gBAAE,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACZ,IAAI,cAAc;gBAAE,OAAO,CAAC,wCAAwC;YACpE,IAAI,CAAC,YAAY,QAAQ,EAAE,CAAC;gBAC3B,UAAU,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YACpE,CAAC;iBAAM,CAAC;gBACP,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBAC3D,UAAU,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC;YACvD,CAAC;QACF,CAAC;IACF,CAAC,CAAC;IAEF,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACpC,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,yEAAyE;QACzE,0EAA0E;QAC1E,+CAA+C;QAC/C,IAAI,KAAK,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;QAC7C,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAqB,EAAE,EAAE;YAC1C,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1B,IAAI,CAAS,CAAC;YACd,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAChC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC7B,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5C,CAAC;QACF,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACpB,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE;gBACvB,IAAI,MAAM,CAAC,IAAI,EAAE;oBAAE,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;YAC9C,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"jsonrpc.js","sourceRoot":"","sources":["../../src/mcp/jsonrpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,kDAAkD;AAClD,MAAM,CAAC,MAAM,GAAG,GAAG;IAClB,WAAW,EAAE,CAAC,KAAK;IACnB,eAAe,EAAE,CAAC,KAAK;IACvB,gBAAgB,EAAE,CAAC,KAAK;IACxB,cAAc,EAAE,CAAC,KAAK;IACtB,cAAc,EAAE,CAAC,KAAK;IACtB,iBAAiB,EAAE,CAAC,KAAK;CAChB,CAAC;AAEX;;gFAEgF;AAChF,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAE/C,+EAA+E;AAC/E,MAAM,OAAO,QAAS,SAAQ,KAAK;IAClC,IAAI,CAAS;IACb,IAAI,CAAW;IACf,YAAY,IAAY,EAAE,OAAe,EAAE,IAAc;QACxD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CACD;AAgBD;;;GAGG;AACH,MAAM,UAAU,UAAU,CACzB,QAAoC,EACpC,EAAE,GAAsE,EAAE;IAE1E,MAAM,KAAK,GAA0B,EAAE,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC;IAC/D,MAAM,MAAM,GAA0B,EAAE,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAClE,MAAM,cAAc,GAAG,IAAI,GAAG,EAA2B,CAAC;IAC1D,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAmB,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;IACzC,MAAM,UAAU,GAAG,CAAC,EAAmB,EAAU,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;IACjF,IAAI,eAAe,GAAG,KAAK,CAAC;IAC5B,IAAI,wBAAkD,CAAC;IAEvD,MAAM,KAAK,GAAG,CAAC,GAAY,EAAE,EAAE;QAC9B,IAAI,eAAe;YAAE,OAAO;QAC5B,IAAI,CAAC;YACJ,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACR,yEAAyE;YACzE,qEAAqE;YACrE,mEAAmE;YACnE,wBAAwB,EAAE,EAAE,CAAC;QAC9B,CAAC;IACF,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,EAA0B,EAAE,MAAe,EAAE,EAAE;QACjE,KAAK,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,CAAC,CAAC;IACF,MAAM,UAAU,GAAG,CAAC,EAA0B,EAAE,GAAiB,EAAE,EAAE;QACpE,KAAK,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,KAAK,EAAE,IAAY,EAAiB,EAAE;QACxD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,IAAI,GAAmB,CAAC;QACxB,IAAI,CAAC;YACJ,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACR,gEAAgE;YAChE,UAAU,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;YACpE,OAAO;QACR,CAAC;QAED,MAAM,cAAc,GAAG,GAAG,CAAC,EAAE,KAAK,SAAS,IAAI,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC;QAC/D,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,IAAI,CAA2B,CAAC;QAEtD,IAAI,GAAG,CAAC,OAAO,KAAK,KAAK,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC7D,IAAI,CAAC,cAAc;gBAAE,UAAU,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,eAAe,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,CAAC;YAC/F,OAAO;QACR,CAAC;QAED,2EAA2E;QAC3E,sEAAsE;QACtE,mEAAmE;QACnE,IAAI,GAAG,CAAC,MAAM,KAAK,yBAAyB,EAAE,CAAC;YAC9C,MAAM,MAAM,GAAG,GAAG,CAAC,MAA6C,CAAC;YACjE,MAAM,WAAW,GAAG,MAAM,EAAE,SAAS,CAAC;YACtC,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;gBACxE,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;YACtD,CAAC;YACD,OAAO;QACR,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,qEAAqE;YACrE,IAAI,CAAC,cAAc;gBAAE,UAAU,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,gBAAgB,EAAE,OAAO,EAAE,qBAAqB,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAChH,OAAO;QACR,CAAC;QAED,MAAM,GAAG,GAAG,CAAC,cAAc,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,IAAI,GAAG,EAAE,CAAC;YACT,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC1C,IAAI,SAAS,EAAE,CAAC;gBACf,mEAAmE;gBACnE,wEAAwE;gBACxE,+DAA+D;gBAC/D,SAAS,CAAC,KAAK,EAAE,CAAC;gBAClB,OAAO;YACR,CAAC;QACF,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,iBAAiB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAClC,IAAI,GAAG;YAAE,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAClC,IAAI,aAAuC,CAAC;QAC5C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAiB,CAAC,OAAO,EAAE,EAAE;YACvD,aAAa,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACvC,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO,CAAC,OAAO,CAAC,CAAC;;gBAC3C,UAAU,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACjF,CAAC,CAAC,CAAC;QACH,4EAA4E;QAC5E,gFAAgF;QAChF,IAAI,cAAgC,CAAC;QACrC,IAAI,CAAC;YACJ,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACrG,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC;QACD,+EAA+E;QAC/E,wEAAwE;QACxE,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;YAC7D,IAAI,CAAC,cAAc,EAAE,CAAC;gBACrB,IAAI,MAAM,KAAK,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO;oBAClD,UAAU,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,iBAAiB,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC;;oBAC1E,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YAC5B,CAAC;QACF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACZ,IAAI,cAAc;gBAAE,OAAO,CAAC,wCAAwC;YACpE,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAC/B,UAAU,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,iBAAiB,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC;YAC/E,CAAC;iBAAM,IAAI,CAAC,YAAY,QAAQ,EAAE,CAAC;gBAClC,UAAU,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YACpE,CAAC;iBAAM,CAAC;gBACP,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBAC3D,UAAU,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC;YACvD,CAAC;QACF,CAAC;gBAAS,CAAC;YACV,IAAI,aAAa;gBAAE,UAAU,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;YACjF,iBAAiB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACrC,IAAI,GAAG,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,UAAU;gBAAE,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC/E,CAAC;IACF,CAAC,CAAC;IAEF,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACpC,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,aAAwC,CAAC;QAC7C,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,MAAM,KAAK,GAAG,CAAC,OAAsB,EAAE,EAAE;YACxC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACrB,KAAK,OAAO,CAAC,IAAI,CAChB,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAC7B,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAC7B,CAAC;QACH,CAAC,CAAC;QACF,MAAM,wBAAwB,GAAG,GAAG,EAAE;YACrC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACrC,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACnC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACvC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YAC5C,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;YAC9C,wBAAwB,GAAG,SAAS,CAAC;QACtC,CAAC,CAAC;QACF,MAAM,MAAM,GAAG,GAAkB,EAAE;YAClC,eAAe,GAAG,IAAI,CAAC;YACvB,KAAK,MAAM,UAAU,IAAI,iBAAiB;gBAAE,UAAU,CAAC,KAAK,EAAE,CAAC;YAC/D,IAAI,CAAC,aAAa,EAAE,CAAC;gBACpB,aAAa,GAAG,IAAI,OAAO,CAAO,CAAC,IAAI,EAAE,EAAE;oBAC1C,IAAI,SAAS,GAAG,KAAK,CAAC;oBACtB,MAAM,MAAM,GAAG,GAAG,EAAE;wBACnB,IAAI,SAAS;4BAAE,OAAO;wBACtB,SAAS,GAAG,IAAI,CAAC;wBACjB,YAAY,CAAC,KAAK,CAAC,CAAC;wBACpB,IAAI,EAAE,CAAC;oBACR,CAAC,CAAC;oBACF,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;oBAC9D,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACpD,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;oBACZ,wBAAwB,EAAE,CAAC;oBAC3B,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACf,QAAQ,GAAG,IAAI,CAAC;wBAChB,OAAO,EAAE,CAAC;oBACX,CAAC;gBACF,CAAC,CAAC,CAAC;YACJ,CAAC;YACD,OAAO,aAAa,CAAC;QACtB,CAAC,CAAC;QACF,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,KAAK,MAAM,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,CAAC,IAAqB,EAAE,EAAE;YACxC,IAAI,eAAe;gBAAE,OAAO;YAC5B,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1B,IAAI,CAAS,CAAC;YACd,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAChC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC7B,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;YACzB,CAAC;QACF,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,GAAG,EAAE;YAClB,IAAI,eAAe;gBAAE,OAAO;YAC5B,IAAI,MAAM,CAAC,IAAI,EAAE;gBAAE,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;YAC7C,QAAQ,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,QAAQ,CAAC;QACzB,MAAM,YAAY,GAAG,QAAQ,CAAC;QAC9B,MAAM,aAAa,GAAG,QAAQ,CAAC;QAC/B,wBAAwB,GAAG,QAAQ,CAAC;QACpC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACzB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACvB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3B,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAChC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACJ,CAAC"}