@excitedjs/dreamux 0.1.4 → 0.3.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.
Files changed (78) hide show
  1. package/README.md +170 -154
  2. package/bin/tm +30 -0
  3. package/dist/admin/client.js +98 -0
  4. package/dist/admin/client.js.map +1 -0
  5. package/dist/admin/methods.js +83 -38
  6. package/dist/admin/methods.js.map +1 -1
  7. package/dist/admin/socket.js +2 -2
  8. package/dist/admin/socket.js.map +1 -1
  9. package/dist/channel/feishu-gate.js +187 -18
  10. package/dist/channel/feishu-gate.js.map +1 -1
  11. package/dist/channel/feishu-message.js +92 -0
  12. package/dist/channel/feishu-message.js.map +1 -0
  13. package/dist/cli/doctor.js +53 -58
  14. package/dist/cli/doctor.js.map +1 -1
  15. package/dist/cli/dreamux.js +72 -61
  16. package/dist/cli/dreamux.js.map +1 -1
  17. package/dist/cli/server-ctl.js +6 -8
  18. package/dist/cli/server-ctl.js.map +1 -1
  19. package/dist/cli/server.js +25 -38
  20. package/dist/cli/server.js.map +1 -1
  21. package/dist/codex/events.js +3 -2
  22. package/dist/codex/events.js.map +1 -1
  23. package/dist/codex/mcp-config.js +24 -0
  24. package/dist/codex/mcp-config.js.map +1 -0
  25. package/dist/codex/supervisor.js +16 -0
  26. package/dist/codex/supervisor.js.map +1 -1
  27. package/dist/dispatcher/approval.js +2 -3
  28. package/dist/dispatcher/approval.js.map +1 -1
  29. package/dist/dispatcher/runtime.js +195 -143
  30. package/dist/dispatcher/runtime.js.map +1 -1
  31. package/dist/dispatcher/turn-manager.js +78 -97
  32. package/dist/dispatcher/turn-manager.js.map +1 -1
  33. package/dist/feishu/bot.js +71 -9
  34. package/dist/feishu/bot.js.map +1 -1
  35. package/dist/mcp/feishu-mcp.js +269 -0
  36. package/dist/mcp/feishu-mcp.js.map +1 -0
  37. package/dist/onboard/config-files.js +60 -87
  38. package/dist/onboard/config-files.js.map +1 -1
  39. package/dist/onboard/dispatcher-skill.js +18 -0
  40. package/dist/onboard/dispatcher-skill.js.map +1 -0
  41. package/dist/onboard/run.js +40 -79
  42. package/dist/onboard/run.js.map +1 -1
  43. package/dist/onboard/service.js +6 -5
  44. package/dist/onboard/service.js.map +1 -1
  45. package/dist/onboard/uninstall.js +161 -0
  46. package/dist/onboard/uninstall.js.map +1 -0
  47. package/dist/onboard/wizard.js +12 -69
  48. package/dist/onboard/wizard.js.map +1 -1
  49. package/dist/runtime/codex-args.js +1 -1
  50. package/dist/runtime/config.js +213 -213
  51. package/dist/runtime/config.js.map +1 -1
  52. package/dist/runtime/dispatcher-codex-home.js +21 -185
  53. package/dist/runtime/dispatcher-codex-home.js.map +1 -1
  54. package/dist/runtime/dispatcher-id.js +9 -0
  55. package/dist/runtime/dispatcher-id.js.map +1 -0
  56. package/dist/runtime/dispatcher-store.js +202 -0
  57. package/dist/runtime/dispatcher-store.js.map +1 -0
  58. package/dist/runtime/package-bin.js +41 -0
  59. package/dist/runtime/package-bin.js.map +1 -0
  60. package/dist/runtime/paths.js +87 -48
  61. package/dist/runtime/paths.js.map +1 -1
  62. package/dist/runtime/secrets.js +17 -14
  63. package/dist/runtime/secrets.js.map +1 -1
  64. package/dist/server.js +112 -38
  65. package/dist/server.js.map +1 -1
  66. package/package.json +6 -6
  67. package/skills/dispatcher/SKILL.md +107 -0
  68. package/db/migrations/0001_init.sql +0 -49
  69. package/dist/channel/outbound.js +0 -12
  70. package/dist/channel/outbound.js.map +0 -1
  71. package/dist/db/repository.js +0 -223
  72. package/dist/db/repository.js.map +0 -1
  73. package/dist/db/schema.js +0 -29
  74. package/dist/db/schema.js.map +0 -1
  75. package/dist/db/types.js +0 -2
  76. package/dist/db/types.js.map +0 -1
  77. package/dist/onboard/plugins.js +0 -202
  78. package/dist/onboard/plugins.js.map +0 -1
package/README.md CHANGED
@@ -3,54 +3,61 @@
3
3
  The Codex-host server package. One long-running Node process hosts N
4
4
  **Dispatchers**; each Dispatcher binds **1 Feishu bot + 1 Codex thread**.
5
5
 
6
- This file is the **package-level** quick start. For the monorepo layout
7
- and harness pieces, see the top-level
8
- [`README.md`](../../README.md) and
6
+ This file is the **package-level** quick start. For the monorepo layout and
7
+ knowledge base, see the top-level [`README.md`](../../README.md) and
9
8
  [`.agents/root.md`](../../.agents/root.md).
10
9
 
11
10
  Design background:
12
11
  [#1 Proposal](https://github.com/excitedjs/dreamux/issues/1) ·
13
12
  [#2 Engineering plan](https://github.com/excitedjs/dreamux/issues/2) ·
14
13
  [#4 Monorepo + harness](https://github.com/excitedjs/dreamux/issues/4) ·
15
- [#18 Global bin onboarding](https://github.com/excitedjs/dreamux/issues/18).
14
+ [#18 Global bin onboarding](https://github.com/excitedjs/dreamux/issues/18) ·
15
+ [#36 MVP tracking](https://github.com/excitedjs/dreamux/issues/36).
16
16
 
17
17
  ## What this package ships
18
18
 
19
- - One public CLI bin: `dreamux`. Implemented commands in this slice include
20
- `dreamux onboard`, `dreamux serve`, `dreamux status`, `dreamux doctor`,
21
- `dreamux dispatcher ...`, and `dreamux config path|show`.
22
- - A SQLite-backed runtime (`dispatchers` + `inbound_buffer`) plus the
23
- Feishu / Codex adapters that drive each dispatcher.
19
+ - Public CLI bins: `dreamux` and `tm`. `dreamux` owns onboarding, serving,
20
+ status, doctor, dispatcher commands, and config commands. `tm` is a wrapper
21
+ around the package-local `@excitedjs/tm` dependency for dispatcher skills.
22
+ - A bundled dispatcher Codex skill, copied by `dreamux onboard` into
23
+ `<dispatcher cwd>/.codex/skills/dispatcher/SKILL.md`.
24
+ - Config-backed dispatcher declarations, server-owned state/log paths, a
25
+ Feishu long-connection inbound channel, Codex app-server lifecycle
26
+ management, and a dispatcher-scoped Feishu MCP shim for model replies.
24
27
 
25
- ## What this MVP does (P0)
28
+ ## MVP contract
26
29
 
27
30
  - **One Node process, many Dispatchers.** Each Dispatcher = 1 Feishu Bot
28
- (independent appId/secret) + 1 long-lived Codex `app-server` child + 1
29
- Codex thread.
30
- - **Single-thread, multi-chat fan-in.** A bot can be invited into multiple
31
- groups and DMs; every inbound message goes into the same Codex thread.
32
- Outbound replies are routed by the inbound's `source_chat_id`.
33
- - **No dispatcher↔worktree binding.** Codex picks the worktree at `tm`-call
34
- time. The Codex daemon's cwd is `~/.codex-host/dispatchers/<id>/cwd/`
35
- (intentionally empty), while its private `CODEX_HOME` is
36
- `~/.codex-host/dispatchers/<id>/codex-home/`.
37
- - **FIFO + at-most-once.** One running turn per dispatcher. After a server
38
- crash, `running` inbound rows are flipped to `unknown` (the user is told
39
- to confirm or resend); `awaiting_outbound` rows are safely retried.
40
- - **Trusted-local only.** No chat allowlist, `approval-policy=never`. Any
41
- other deployment must uplift access control first see
42
- [issue #2 §"信任模型"](https://github.com/excitedjs/dreamux/issues/2).
43
-
44
- Explicitly **not** in MVP: approval cards, streaming outbound, per-chat
45
- threads, tm registry isolation, cross-machine coordination, web UI.
31
+ (`app_id`/`app_secret`) + 1 long-lived Codex `app-server` child + 1 Codex
32
+ thread.
33
+ - **One dispatcher is one trust domain.** A bot may receive multiple chats, but
34
+ all accepted messages share one Codex thread. Do not bind unrelated private
35
+ chats to the same dispatcher.
36
+ - **Dispatcher cwd is explicit, Codex state stays global.** Dispatcher
37
+ app-server processes use Codex's global default home (`~/.codex`) for auth,
38
+ memory, and config. The dispatcher skill is workspace-local under the
39
+ dispatcher cwd.
40
+ - **Inbound state is in memory.** The server keeps only process-local turn
41
+ queues, message dedupe, coalescing state, and received-reaction ownership.
42
+ Restarting the server drops unprocessed inbound messages and may leave
43
+ received reactions behind.
44
+ - **Outbound is MCP reply-only.** Assistant text emitted by Codex is never
45
+ forwarded to Feishu automatically. The model must call the dispatcher-scoped
46
+ Feishu MCP tools such as `reply` or `react`.
47
+ - **No webhook surface in MVP.** Feishu inbound uses the SDK long-connection
48
+ WebSocket path. Webhook-only verification/encryption fields are not part of
49
+ the config schema.
50
+
51
+ Explicitly **not** in MVP: per-chat threads, durable inbound buffers,
52
+ automatic assistant-text outbound, HTTP MCP listeners by default, reaction
53
+ ledgers, streaming outbound, tm registry isolation, cross-machine
54
+ coordination, and a web UI.
46
55
 
47
56
  ## Install / build / test
48
57
 
49
- Use the monorepo (rush) path from the repo root it is the only supported
50
- install path. This package depends on `@excitedjs/feishu-transport` via the
51
- pnpm `workspace:*` protocol, which `npm` cannot resolve, so
52
- `cd packages/dreamux && npm install` no longer works (see
53
- [the install-model decision](../../.agents/decisions/install-model.md)):
58
+ Use the monorepo (rush) path from the repo root. It is the only supported
59
+ install path because this package depends on workspace packages through the
60
+ pnpm `workspace:*` protocol:
54
61
 
55
62
  ```bash
56
63
  node common/scripts/install-run-rush.js update
@@ -58,10 +65,8 @@ node common/scripts/install-run-rush.js build
58
65
  node common/scripts/install-run-rush.js test
59
66
  ```
60
67
 
61
- CI exercises this path, so a broken `rush.json` or lockfile fails CI.
62
-
63
- The bin launchers shell out to plain `node` against the compiled `dist/`
64
- output; **no `tsx` is needed at runtime** (PR #6).
68
+ The bin launchers shell out to plain `node` against compiled `dist/` output;
69
+ no `tsx` is needed at runtime.
65
70
 
66
71
  ## Run the server
67
72
 
@@ -69,144 +74,155 @@ output; **no `tsx` is needed at runtime** (PR #6).
69
74
  ./bin/dreamux serve
70
75
  ```
71
76
 
72
- The launcher works from any cwd and via symlinks (PR #6 + bin-launcher tests).
77
+ The launcher works from any cwd and via symlinks.
73
78
 
74
- The server uses two separate home directories by design (see
75
- [the global-config decision](../../.agents/decisions/global-config-dir.md)):
79
+ The server keeps operator-edited config separate from server-owned state and
80
+ logs:
76
81
 
77
82
  | Path | Purpose | Source of truth |
78
83
  |---|---|---|
79
- | `~/.dreamux/config.toml` | User-editable global config auto-created on first boot with sensible defaults; edit and restart to apply | the operator |
80
- | `~/.codex-host/state.db` | SQLite (dispatchers + inbound buffer) | the server |
81
- | `~/.codex-host/admin.sock` | Admin Unix socket (`0600`) | the server |
82
- | `~/.codex-host/dispatchers/<id>/cwd/` | Codex app-server cwd | the server |
83
- | `~/.codex-host/dispatchers/<id>/codex-home/` | Dispatcher-private `CODEX_HOME` for Codex config, plugin cache, and app-server control state | the server |
84
- | `~/.codex-host/dispatchers/<id>/codex-home/app-server-control/as.sock` | Codex app-server Unix socket | the server |
85
- | `~/.codex-host/dispatchers/<id>/*.log` | Codex stdout / stderr | the server |
84
+ | `~/.dreamux/config.json` | User-editable config and Feishu bot secrets, created by `dreamux onboard`; edit and restart to apply | the operator |
85
+ | `~/.dreamux/state/server.json` | Server status snapshot | the server |
86
+ | `~/.dreamux/state/admin.sock` | Admin Unix socket | the server |
87
+ | `~/.dreamux/state/<id>/status.json` | Dispatcher runtime status and Codex thread id | the server |
88
+ | `~/.dreamux/state/<id>/access.json` | Dispatcher-local access-gate state | the server |
89
+ | `~/.dreamux/state/<id>/codex.sock` | Codex app-server Unix socket | the server |
90
+ | `~/.dreamux/logs/codex-app-server/<id>.log` | Codex app-server stdout/stderr | the server |
91
+ | `~/.dreamux/logs/feishu-channel/<id>.log` | Feishu channel logs | the server |
92
+ | `~/.codex/` | Codex global default home: auth, memory, and config | the operator / Codex |
93
+ | `<dispatcher cwd>/.codex/skills/dispatcher/SKILL.md` | Dispatcher skill copied by `dreamux onboard`; reported but not deleted by `dreamux uninstall` | dreamux installer |
86
94
 
87
- `rm -rf ~/.codex-host` is a safe recovery your config in `~/.dreamux/`
88
- survives. `runtime_dir` and `admin_socket` paths in the config can move
89
- the `~/.codex-host` half anywhere you like.
95
+ `rm -rf ~/.dreamux/state ~/.dreamux/logs` is a state/log recovery path; dreamux
96
+ config and global Codex auth survive.
90
97
 
91
- ## Configure a dispatcher
98
+ ## Configure dispatchers
92
99
 
93
- ```bash
94
- # Bot secret comes from an env var the server process can see.
95
- export BOT_SECRET_FLOW='<bot-secret>'
96
-
97
- ./bin/dreamux dispatcher add \
98
- --id flow \
99
- --bot-app-id <APP_ID> \
100
- --bot-secret-ref env:BOT_SECRET_FLOW
101
-
102
- # Inspect / restart
103
- ./bin/dreamux dispatcher list
104
- ./bin/dreamux dispatcher status --id flow
105
- ./bin/dreamux dispatcher start --id flow # if not auto-started
106
- ```
100
+ For normal installs, run `dreamux onboard`. It writes `~/.dreamux/config.json`
101
+ with mode `0600`, creates state/log directories, installs the workspace skill,
102
+ and registers a user-level service when supported.
107
103
 
108
- ## MVP verification path (issue #2 §"MVP 验收脚本")
109
-
110
- 1. `dreamux dispatcher add --id flow --bot-app-id <APP_ID> --bot-secret-ref env:BOT_SECRET_FLOW`
111
- 2. `dreamux serve` — dispatcher `flow` goes to `ready`
112
- 3. Invite the bot to a Feishu group A, send `hi`
113
- 4. Server delivers it into the Codex thread; reply goes back to group A
114
- 5. Invite the same bot to a DM, ask "do you remember the 'hi' from earlier?"
115
- 6. Same thread, so the reply confirms — and goes back to the DM
116
- 7. Ask the bot to "run the test suite via tm and summarize"
117
- 8. Codex shells out to `tm`, reads stdout/stderr, replies into the source chat
118
- 9. Repeat with a **different** worktree to prove dispatcher↔worktree decoupling
119
- 10. `pkill node` to crash the server, then restart it
120
- 11. Continue chatting — Codex `thread/resume` restores context
121
-
122
- ## Configuration reference
123
-
124
- Precedence for every config-able value (highest wins): env var →
125
- per-dispatcher field → `~/.dreamux/config.toml` → built-in default.
126
- See [the global-config decision](../../.agents/decisions/global-config-dir.md).
127
-
128
- ### Global: `~/.dreamux/config.toml`
129
-
130
- Auto-created on first boot with this default (excerpt — open the file
131
- itself for the inline comments explaining each key):
132
-
133
- ```toml
134
- runtime_dir = "~/.codex-host"
135
- # admin_socket = "~/.codex-host/admin.sock" # default: <runtime_dir>/admin.sock
136
-
137
- [codex]
138
- bin = "codex"
139
- approval_policy = "never" # never | auto | auto-approve | on-failure
140
- sandbox_mode = "workspace-write" # read-only | workspace-write | danger-full-access
141
- extra_args = []
142
- initialize_timeout_ms = 10000
143
-
144
- [outbound]
145
- retries = 3
146
- retry_delay_ms = 1000
147
- ```
104
+ Dispatcher declarations live in `config.json`:
148
105
 
149
- Edit and restart `dreamux serve`. Parse errors fail-fast with a
150
- `file:line` pointer.
106
+ ```json
107
+ {
108
+ "codex": {
109
+ "bin": "codex",
110
+ "approval_policy": "never",
111
+ "sandbox_mode": "workspace-write",
112
+ "extra_args": [],
113
+ "initialize_timeout_ms": 10000
114
+ },
115
+ "dispatchers": [
116
+ {
117
+ "id": "flow",
118
+ "cwd": "<WORKSPACE>",
119
+ "enabled": true,
120
+ "feishu": {
121
+ "app_id": "<APP_ID>",
122
+ "app_secret": "<APP_SECRET>"
123
+ },
124
+ "codex": {
125
+ "approval_policy": null,
126
+ "sandbox_mode": null,
127
+ "extra_args": [],
128
+ "extra_env": {
129
+ "EXAMPLE_FLAG": "1"
130
+ }
131
+ }
132
+ }
133
+ ]
134
+ }
135
+ ```
151
136
 
152
- ### `codex_args_json` (per-dispatcher, overrides global)
137
+ Edit and restart `dreamux serve` to apply dispatcher declaration changes.
138
+ `app_id` values must be unique across all declared dispatchers, including
139
+ disabled ones. Dispatcher ids use a path-safe character set so they map
140
+ one-to-one to state directories.
153
141
 
154
- JSON object stored in `dispatchers.codex_args_json`:
142
+ Access-gate allowlists are not part of `config.json`. Configure them in
143
+ `~/.dreamux/state/<id>/access.json`:
155
144
 
156
145
  ```json
157
- { "approvalPolicy": "never", "sandboxMode": "workspace-write", "extraArgs": ["--model", "gpt-5"] }
146
+ {
147
+ "version": 1,
148
+ "dm": {
149
+ "allow_users": ["<USER_ID>"]
150
+ },
151
+ "group": {
152
+ "allow_chats": ["<CHAT_ID>"],
153
+ "follow_users": ["<USER_ID>"],
154
+ "require_mention": true
155
+ },
156
+ "observed_chats": [],
157
+ "warnings": [],
158
+ "last_gate": null
159
+ }
158
160
  ```
159
161
 
160
- | Field | Default | Notes |
161
- | ---------------- | --------- | ---------------------------------------------------- |
162
- | `approvalPolicy` | inherits `[codex] approval_policy` from `~/.dreamux/config.toml`, else `"never"` | Must be one of `never`/`auto`/`auto-approve`/`on-failure`. Otherwise startup fails fast (issue #2 §"实现陷阱"). |
163
- | `sandboxMode` | inherits `[codex] sandbox_mode`, else `"workspace-write"` | Must be one of `read-only`/`workspace-write`/`danger-full-access` (codex 0.134 enum). Validated at dispatcher startup. |
164
- | `extraArgs` | appended *after* global `codex.extra_args` | codex's "last write wins" semantics for `-c key=value` mean a per-dispatcher entry effectively overrides a same-key global. |
165
-
166
- ### Env vars (highest precedence — escape hatch)
167
-
168
- | Var | Purpose |
169
- | ---------------------------- | -------------------------------------------------- |
170
- | `CODEX_HOST_RUNTIME_DIR` | Override `runtime_dir` |
171
- | `CODEX_HOST_ADMIN_SOCKET` | Override admin Unix socket path |
172
- | `CODEX_HOST_CODEX_BIN` | Override `codex.bin` |
173
- | `DREAMUX_CONFIG_DIR` | Override `~/.dreamux` (where `config.toml` lives) |
174
- | `BOT_SECRET_<NAME>` | Bot secrets referenced by `env:BOT_SECRET_<NAME>` |
175
- | `DREAMUX_SKIP_LIVE_CODEX` | Opt out of the live codex 0.135 integration test (loud skip) |
176
-
177
- ## What this MVP does **not** do
178
-
179
- (see [issue #2 §"明确不在 MVP 范围"](https://github.com/excitedjs/dreamux/issues/2))
180
-
181
- - Multiple threads per dispatcher
182
- - Per-chat memory
183
- - Approval / Feishu approval cards
184
- - Streaming assistant deltas
185
- - tm CLI changes (`tm --json`, registry namespace)
186
- - Cross-machine coordination
187
- - Web UI / Prometheus
188
- - Migration from old claudemux dispatcher state
189
- - access gate / chat allowlist / pairing (D12 + Trust Model)
162
+ The server reads `access.json` directly at runtime and preserves runtime
163
+ observations and warnings in the same file.
164
+
165
+ `dreamux config show`, `dreamux status`, `dreamux doctor`, and logs redact
166
+ `app_secret`. There is no CLI raw mode for printing the unredacted local file.
167
+
168
+ ## Codex configuration precedence
169
+
170
+ Precedence for Codex-related values, highest first:
171
+
172
+ 1. Environment variables, such as `CODEX_HOST_CODEX_BIN`.
173
+ 2. Per-dispatcher `dispatchers[].codex` fields.
174
+ 3. Global `codex` fields in `~/.dreamux/config.json`.
175
+ 4. Built-in defaults compiled into `src/runtime/config.ts`.
176
+
177
+ Per-dispatcher `extra_args` are appended after global `codex.extra_args`, which
178
+ matches Codex's last-write-wins behavior for repeated `-c key=value` options.
179
+ Per-dispatcher `extra_env` is merged over the server process environment before
180
+ spawning that dispatcher app-server; dreamux still removes `CODEX_HOME` so
181
+ Codex keeps using its global default home.
182
+
183
+ ## MCP reply flow
184
+
185
+ Each dispatcher injects a Feishu MCP stdio server into its Codex app-server.
186
+ The stdio shim does not read Feishu secrets. It forwards outbound tool calls to
187
+ the serve process over the admin socket, and the serve process owns the Feishu
188
+ client plus process-local received-reaction cleanup state.
189
+
190
+ The model-facing tools include:
191
+
192
+ - `reply`: send a Feishu reply to a target message or chat.
193
+ - `react`: add a model-owned reaction to a Feishu message.
194
+
195
+ If the model only emits assistant text, nothing is sent to Feishu.
196
+
197
+ ## MVP verification path
198
+
199
+ 1. `dreamux onboard --dispatcher-id flow --dispatcher-cwd <WORKSPACE> --bot-app-id <APP_ID> --bot-app-secret <APP_SECRET>`
200
+ 2. `dreamux serve` starts dispatcher `flow`.
201
+ 3. Invite the bot to a Feishu group, send a mention that passes the access gate.
202
+ 4. Server injects a `<feishu_message>` block into the Codex thread.
203
+ 5. Codex calls the Feishu MCP `reply` tool; the reply is delivered to Feishu.
204
+ 6. Send another accepted message from a different chat in the same trust
205
+ domain; it enters the same Codex thread.
206
+ 7. Ask the bot to run teammate work through the bundled `tm` wrapper.
207
+ 8. Restart the server and continue chatting; Codex `thread/resume` restores the
208
+ thread when possible, but in-flight inbound messages are not durable.
190
209
 
191
210
  ## Testing
192
211
 
193
212
  ```bash
194
- # from the repo root (the only supported path — see the install-model decision)
195
- node common/scripts/install-run-rush.js test # smoke + bin-launcher + codex-0135-live
213
+ node common/scripts/install-run-rush.js test
196
214
  ```
197
215
 
198
- - `tests/smoke.test.ts` — fake-codex-driven dispatcher behavior:
199
- happy path, FIFO, crash recovery (running → unknown), thread/resume
200
- failure, outbound retry without turn re-run, approval fail-fast.
201
- - `tests/bin-launcher.test.ts` — spawns the real `dreamux` bash launcher
202
- and repo-root shim from arbitrary cwds and through symlinks; static
203
- "no tsx" assertion; manifest assertion for the single global bin.
204
- - `tests/doctor.test.ts` covers standalone doctor checks for
205
- dispatcher-private `CODEX_HOME` state, including managed-service auth
206
- visibility.
207
- - `tests/codex-0135-live.test.ts` spawns a real `codex app-server`
208
- (skipped loudly when `codex` is missing or wrong version; opt-in via
209
- `DREAMUX_SKIP_LIVE_CODEX=1`).
216
+ - `tests/smoke.test.ts` — fake-Codex dispatcher behavior: access gate,
217
+ in-memory turn serialization/coalescing/dedupe, MCP reply-only outbound,
218
+ thread resume, app-server restart behavior, and approval fail-fast.
219
+ - `tests/bin-launcher.test.ts` — real launcher and repo-root shim behavior from
220
+ arbitrary cwd and through symlinks.
221
+ - `tests/doctor.test.ts` standalone doctor checks for config, Codex home,
222
+ services, and dispatcher workspace skill state.
223
+ - `tests/codex-0135-live.test.ts` and `tests/codex-0136-mcp-live.test.ts`
224
+ real Codex app-server compatibility checks. Set `DREAMUX_SKIP_LIVE_CODEX=1`
225
+ only when no Codex binary is available locally.
210
226
 
211
227
  ## License
212
228
 
package/bin/tm ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bash
2
+ # Launcher for the tm CLI bundled as a direct @excitedjs/dreamux dependency.
3
+ # Resolves its own location through symlinks so it works from npm global bins,
4
+ # npm-link, and source-checkout shortcuts.
5
+ set -eu
6
+
7
+ SOURCE="${BASH_SOURCE[0]}"
8
+ hop=0
9
+ while [ -L "$SOURCE" ]; do
10
+ hop=$((hop + 1))
11
+ if [ "$hop" -gt 40 ]; then
12
+ echo "tm: too many symlink hops resolving $SOURCE" >&2
13
+ exit 1
14
+ fi
15
+ TARGET="$(readlink "$SOURCE")"
16
+ if [ "${TARGET:0:1}" = "/" ]; then
17
+ SOURCE="$TARGET"
18
+ else
19
+ SOURCE="$(dirname -- "$SOURCE")/$TARGET"
20
+ fi
21
+ done
22
+
23
+ DIR="$(cd -- "$(dirname -- "$SOURCE")/.." &> /dev/null && pwd)"
24
+ TARGET="$DIR/node_modules/.bin/tm"
25
+ if [ ! -x "$TARGET" ]; then
26
+ echo "tm: $TARGET is missing." >&2
27
+ echo "Install dependencies first from the repo root: node common/scripts/install-run-rush.js update." >&2
28
+ exit 1
29
+ fi
30
+ exec "$TARGET" "$@"
@@ -0,0 +1,98 @@
1
+ import { connect } from 'node:net';
2
+ import { adminSocketPath as defaultAdminSocketPath } from '../runtime/paths.js';
3
+ export class AdminClientError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.code = code;
8
+ this.name = 'AdminClientError';
9
+ }
10
+ }
11
+ const DEFAULT_TIMEOUT_MS = 10_000;
12
+ let nextRequestId = 1;
13
+ export function sendAdminRequest(method, params, options = {}) {
14
+ const socketPath = options.socketPath ?? defaultAdminSocketPath();
15
+ const request = {
16
+ id: options.requestId ?? adminRequestId(),
17
+ method,
18
+ params,
19
+ };
20
+ return sendOne(socketPath, request, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
21
+ }
22
+ export function sendOneAdminRequest(socketPath, request, timeoutMs = DEFAULT_TIMEOUT_MS) {
23
+ return sendOne(socketPath, request, timeoutMs);
24
+ }
25
+ function sendOne(socketPath, request, timeoutMs) {
26
+ return new Promise((resolve, reject) => {
27
+ let buf = '';
28
+ let settled = false;
29
+ let sock = null;
30
+ const timer = setTimeout(() => {
31
+ settle(new Error(`admin socket request timed out after ${timeoutMs}ms`));
32
+ try {
33
+ sock?.destroy();
34
+ }
35
+ catch {
36
+ /* already gone */
37
+ }
38
+ }, timeoutMs);
39
+ timer.unref();
40
+ function settle(value, isError = true) {
41
+ if (settled)
42
+ return;
43
+ settled = true;
44
+ clearTimeout(timer);
45
+ if (isError)
46
+ reject(value);
47
+ else
48
+ resolve(value);
49
+ }
50
+ try {
51
+ sock = connect(socketPath);
52
+ }
53
+ catch (err) {
54
+ settle(err);
55
+ return;
56
+ }
57
+ sock.setEncoding('utf8');
58
+ sock.on('connect', () => {
59
+ sock.write(`${JSON.stringify(request)}\n`);
60
+ });
61
+ sock.on('data', (chunk) => {
62
+ buf += chunk;
63
+ const nl = buf.indexOf('\n');
64
+ if (nl === -1 || settled)
65
+ return;
66
+ const line = buf.slice(0, nl).trim();
67
+ try {
68
+ const response = JSON.parse(line);
69
+ if (response.ok)
70
+ settle(response.result, false);
71
+ else
72
+ settle(new AdminClientError(response.error.code, response.error.message));
73
+ }
74
+ catch (err) {
75
+ settle(err);
76
+ }
77
+ sock.end();
78
+ });
79
+ sock.on('error', (err) => {
80
+ if (settled)
81
+ return;
82
+ const code = err.code;
83
+ if (code === 'ENOENT' || code === 'ECONNREFUSED') {
84
+ settle(new Error(`cannot reach admin socket at ${socketPath} - is the server running?`));
85
+ }
86
+ else {
87
+ settle(err);
88
+ }
89
+ });
90
+ sock.on('close', () => {
91
+ settle(new Error('admin socket closed without a response'));
92
+ });
93
+ });
94
+ }
95
+ function adminRequestId() {
96
+ return `mcp-${process.pid}-${Date.now()}-${nextRequestId++}`;
97
+ }
98
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/admin/client.ts"],"names":[],"mappings":"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"}