@excitedjs/dreamux 0.2.0 → 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 (77) hide show
  1. package/README.md +155 -160
  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 +33 -51
  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 +22 -32
  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 +193 -141
  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 +52 -22
  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 +17 -50
  42. package/dist/onboard/run.js.map +1 -1
  43. package/dist/onboard/service.js +4 -8
  44. package/dist/onboard/service.js.map +1 -1
  45. package/dist/onboard/uninstall.js +47 -13
  46. package/dist/onboard/uninstall.js.map +1 -1
  47. package/dist/onboard/wizard.js +3 -27
  48. package/dist/onboard/wizard.js.map +1 -1
  49. package/dist/runtime/config.js +158 -64
  50. package/dist/runtime/config.js.map +1 -1
  51. package/dist/runtime/dispatcher-codex-home.js +14 -51
  52. package/dist/runtime/dispatcher-codex-home.js.map +1 -1
  53. package/dist/runtime/dispatcher-id.js +9 -0
  54. package/dist/runtime/dispatcher-id.js.map +1 -0
  55. package/dist/runtime/dispatcher-store.js +202 -0
  56. package/dist/runtime/dispatcher-store.js.map +1 -0
  57. package/dist/runtime/package-bin.js +41 -0
  58. package/dist/runtime/package-bin.js.map +1 -0
  59. package/dist/runtime/paths.js +82 -48
  60. package/dist/runtime/paths.js.map +1 -1
  61. package/dist/runtime/secrets.js +4 -3
  62. package/dist/runtime/secrets.js.map +1 -1
  63. package/dist/server.js +110 -36
  64. package/dist/server.js.map +1 -1
  65. package/package.json +6 -6
  66. package/skills/dispatcher/SKILL.md +107 -0
  67. package/db/migrations/0001_init.sql +0 -49
  68. package/dist/channel/outbound.js +0 -12
  69. package/dist/channel/outbound.js.map +0 -1
  70. package/dist/db/repository.js +0 -223
  71. package/dist/db/repository.js.map +0 -1
  72. package/dist/db/schema.js +0 -29
  73. package/dist/db/schema.js.map +0 -1
  74. package/dist/db/types.js +0 -2
  75. package/dist/db/types.js.map +0 -1
  76. package/dist/onboard/plugins.js +0 -202
  77. package/dist/onboard/plugins.js.map +0 -1
package/README.md CHANGED
@@ -3,56 +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
- - **Dispatcher cwd is explicit, Codex state stays local.** The Codex
34
- daemon's cwd is configured during `dreamux onboard` or
35
- `dreamux dispatcher add --codex-cwd`. Dispatcher app-server processes
36
- inherit the operator's `CODEX_HOME` (default `~/.codex`), so login state,
37
- memory, config, and plugin cache remain local to the operator. dreamux keeps
38
- only app-server sockets/logs/SQLite under its runtime directory.
39
- - **FIFO + at-most-once.** One running turn per dispatcher. After a server
40
- crash, `running` inbound rows are flipped to `unknown` (the user is told
41
- to confirm or resend); `awaiting_outbound` rows are safely retried.
42
- - **Trusted-local only.** No chat allowlist, `approval-policy=never`. Any
43
- other deployment must uplift access control first — see
44
- [issue #2 §"信任模型"](https://github.com/excitedjs/dreamux/issues/2).
45
-
46
- Explicitly **not** in MVP: approval cards, streaming outbound, per-chat
47
- 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.
48
55
 
49
56
  ## Install / build / test
50
57
 
51
- Use the monorepo (rush) path from the repo root it is the only supported
52
- install path. This package depends on `@excitedjs/feishu-transport` via the
53
- pnpm `workspace:*` protocol, which `npm` cannot resolve, so
54
- `cd packages/dreamux && npm install` no longer works (see
55
- [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:
56
61
 
57
62
  ```bash
58
63
  node common/scripts/install-run-rush.js update
@@ -60,10 +65,8 @@ node common/scripts/install-run-rush.js build
60
65
  node common/scripts/install-run-rush.js test
61
66
  ```
62
67
 
63
- CI exercises this path, so a broken `rush.json` or lockfile fails CI.
64
-
65
- The bin launchers shell out to plain `node` against the compiled `dist/`
66
- 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.
67
70
 
68
71
  ## Run the server
69
72
 
@@ -71,75 +74,37 @@ output; **no `tsx` is needed at runtime** (PR #6).
71
74
  ./bin/dreamux serve
72
75
  ```
73
76
 
74
- The launcher works from any cwd and via symlinks (PR #6 + bin-launcher tests).
77
+ The launcher works from any cwd and via symlinks.
75
78
 
76
- The server keeps operator-edited config separate from runtime state — by design (see
77
- [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:
78
81
 
79
82
  | Path | Purpose | Source of truth |
80
83
  |---|---|---|
81
- | `~/.dreamux/config.json` | User-editable global config and Feishu bot secrets — auto-created on first boot; edit and restart to apply | the operator |
82
- | `~/.codex-host/state.db` | SQLite (dispatchers + inbound buffer) | the server |
83
- | `~/.codex-host/admin.sock` | Admin Unix socket (`0600`) | the server |
84
- | dispatcher `codex_cwd` | Codex app-server cwd, configured during onboard or dispatcher registration | the operator |
85
- | operator `CODEX_HOME` (default `~/.codex`) | Codex login state, memory, config, and plugin cache | the operator |
86
- | `~/.codex-host/dispatchers/<id>/app-server-control/as.sock` | Codex app-server Unix socket | the server |
87
- | `~/.codex-host/dispatchers/<id>/*.log` | Codex stdout / stderr | the server |
88
-
89
- `rm -rf ~/.codex-host` is a safe recovery your config in `~/.dreamux/`
90
- survives. `runtime_dir` and `admin_socket` paths in the config can move
91
- the `~/.codex-host` half anywhere you like.
92
-
93
- ## Configure a dispatcher
94
-
95
- ```bash
96
- ./bin/dreamux dispatcher add \
97
- --id flow \
98
- --bot-app-id <APP_ID> \
99
- --bot-secret-ref config:flow \
100
- --codex-cwd /path/to/dispatcher/workspace
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
- ```
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 |
107
94
 
108
- For normal installs, prefer `dreamux onboard`; it writes
109
- `feishu.bots.<dispatcher-id>.app_secret` into `~/.dreamux/config.json` and
110
- registers the dispatcher with `bot_secret_ref=config:<dispatcher-id>`.
111
- `dreamux config show` redacts these secrets by default; use
112
- `dreamux config show --raw` only when you intentionally need the unredacted
113
- local file.
95
+ `rm -rf ~/.dreamux/state ~/.dreamux/logs` is a state/log recovery path; dreamux
96
+ config and global Codex auth survive.
114
97
 
115
- ## MVP verification path (issue #2 §"MVP 验收脚本")
116
-
117
- 1. `dreamux onboard --dispatcher-id flow --dispatcher-cwd <WORKSPACE> --bot-app-id <APP_ID> --bot-app-secret <APP_SECRET>`
118
- 2. `dreamux serve` — dispatcher `flow` goes to `ready`
119
- 3. Invite the bot to a Feishu group A, send `hi`
120
- 4. Server delivers it into the Codex thread; reply goes back to group A
121
- 5. Invite the same bot to a DM, ask "do you remember the 'hi' from earlier?"
122
- 6. Same thread, so the reply confirms — and goes back to the DM
123
- 7. Ask the bot to "run the test suite via tm and summarize"
124
- 8. Codex shells out to `tm`, reads stdout/stderr, replies into the source chat
125
- 9. Repeat with a **different** worktree to prove dispatcher↔worktree decoupling
126
- 10. `pkill node` to crash the server, then restart it
127
- 11. Continue chatting — Codex `thread/resume` restores context
98
+ ## Configure dispatchers
128
99
 
129
- ## Configuration reference
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.
130
103
 
131
- Precedence for every config-able value (highest wins): env var →
132
- per-dispatcher field → `~/.dreamux/config.json` → built-in default.
133
- See [the global-config decision](../../.agents/decisions/global-config-dir.md).
134
-
135
- ### Global: `~/.dreamux/config.json`
136
-
137
- Auto-created on first boot with this default shape:
104
+ Dispatcher declarations live in `config.json`:
138
105
 
139
106
  ```json
140
107
  {
141
- "runtime_dir": "~/.codex-host",
142
- "admin_socket": null,
143
108
  "codex": {
144
109
  "bin": "codex",
145
110
  "approval_policy": "never",
@@ -147,87 +112,117 @@ Auto-created on first boot with this default shape:
147
112
  "extra_args": [],
148
113
  "initialize_timeout_ms": 10000
149
114
  },
150
- "outbound": {
151
- "retries": 3,
152
- "retry_delay_ms": 1000
153
- },
154
- "feishu": {
155
- "bots": {
156
- "flow": {
115
+ "dispatchers": [
116
+ {
117
+ "id": "flow",
118
+ "cwd": "<WORKSPACE>",
119
+ "enabled": true,
120
+ "feishu": {
157
121
  "app_id": "<APP_ID>",
158
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
+ }
159
131
  }
160
132
  }
161
- }
133
+ ]
162
134
  }
163
135
  ```
164
136
 
165
- Edit and restart `dreamux serve`. JSON parse errors fail fast. If an older
166
- install still has only `~/.dreamux/config.toml`, dreamux refuses to create
167
- default JSON over it; manually create `config.json` preserving the old
168
- `runtime_dir` and other settings, add the needed `feishu.bots` entries, then
169
- move the legacy TOML file aside.
170
-
171
- ### `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.
172
141
 
173
- 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`:
174
144
 
175
145
  ```json
176
- { "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
+ }
177
160
  ```
178
161
 
179
- | Field | Default | Notes |
180
- | ---------------- | --------- | ---------------------------------------------------- |
181
- | `approvalPolicy` | inherits `codex.approval_policy` from `~/.dreamux/config.json`, else `"never"` | Must be one of `never`/`auto`/`auto-approve`/`on-failure`. Otherwise startup fails fast (issue #2 §"实现陷阱"). |
182
- | `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. |
183
- | `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. |
184
-
185
- ### Env vars (highest precedence — escape hatch)
186
-
187
- | Var | Purpose |
188
- | ---------------------------- | -------------------------------------------------- |
189
- | `CODEX_HOST_RUNTIME_DIR` | Override `runtime_dir` |
190
- | `CODEX_HOST_ADMIN_SOCKET` | Override admin Unix socket path |
191
- | `CODEX_HOST_CODEX_BIN` | Override `codex.bin` |
192
- | `DREAMUX_CONFIG_DIR` | Override `~/.dreamux` (where `config.json` lives) |
193
- | `BOT_SECRET_<NAME>` | Legacy/manual bot secrets referenced by `env:BOT_SECRET_<NAME>` |
194
- | `DREAMUX_SKIP_LIVE_CODEX` | Opt out of the live Codex app-server integration test (loud skip) |
195
-
196
- ## What this MVP does **not** do
197
-
198
- (see [issue #2 §"明确不在 MVP 范围"](https://github.com/excitedjs/dreamux/issues/2))
199
-
200
- - Multiple threads per dispatcher
201
- - Per-chat memory
202
- - Approval / Feishu approval cards
203
- - Streaming assistant deltas
204
- - tm CLI changes (`tm --json`, registry namespace)
205
- - Cross-machine coordination
206
- - Web UI / Prometheus
207
- - Migration from old claudemux dispatcher state
208
- - 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.
209
209
 
210
210
  ## Testing
211
211
 
212
212
  ```bash
213
- # from the repo root (the only supported path — see the install-model decision)
214
- node common/scripts/install-run-rush.js test # smoke + bin-launcher + codex-live
213
+ node common/scripts/install-run-rush.js test
215
214
  ```
216
215
 
217
- - `tests/smoke.test.ts` — fake-codex-driven dispatcher behavior:
218
- happy path, FIFO, crash recovery (running → unknown), thread/resume
219
- failure, outbound retry without turn re-run, approval fail-fast.
220
- - `tests/bin-launcher.test.ts` — spawns the real `dreamux` bash launcher
221
- and repo-root shim from arbitrary cwds and through symlinks; static
222
- "no tsx" assertion; manifest assertion for the single global bin.
223
- - `tests/doctor.test.ts` covers standalone doctor checks for
224
- inherited operator Codex home state, including managed-service auth
225
- visibility.
226
- - `tests/codex-live.test.ts` spawns a real `codex app-server`. CI installs
227
- `@openai/codex@latest` before running tests so this compatibility check is
228
- not skipped by default and tracks the current Codex CLI. Local developers can
229
- still opt out explicitly with `DREAMUX_SKIP_LIVE_CODEX=1` when no Codex
230
- binary is available.
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.
231
226
 
232
227
  ## License
233
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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"}