@excitedjs/dreamux 0.1.1

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +223 -0
  3. package/bin/dreamux +31 -0
  4. package/bin/server +35 -0
  5. package/bin/server-ctl +28 -0
  6. package/db/migrations/0001_init.sql +49 -0
  7. package/dist/admin/methods.js +103 -0
  8. package/dist/admin/methods.js.map +1 -0
  9. package/dist/admin/protocol.js +15 -0
  10. package/dist/admin/protocol.js.map +1 -0
  11. package/dist/admin/socket.js +251 -0
  12. package/dist/admin/socket.js.map +1 -0
  13. package/dist/cli/dreamux.js +105 -0
  14. package/dist/cli/dreamux.js.map +1 -0
  15. package/dist/cli/server-ctl.js +172 -0
  16. package/dist/cli/server-ctl.js.map +1 -0
  17. package/dist/cli/server.js +88 -0
  18. package/dist/cli/server.js.map +1 -0
  19. package/dist/codex/events.js +82 -0
  20. package/dist/codex/events.js.map +1 -0
  21. package/dist/codex/handshake.js +85 -0
  22. package/dist/codex/handshake.js.map +1 -0
  23. package/dist/codex/rpc.js +200 -0
  24. package/dist/codex/rpc.js.map +1 -0
  25. package/dist/codex/supervisor.js +184 -0
  26. package/dist/codex/supervisor.js.map +1 -0
  27. package/dist/codex/types.js +10 -0
  28. package/dist/codex/types.js.map +1 -0
  29. package/dist/db/repository.js +207 -0
  30. package/dist/db/repository.js.map +1 -0
  31. package/dist/db/schema.js +29 -0
  32. package/dist/db/schema.js.map +1 -0
  33. package/dist/db/types.js +2 -0
  34. package/dist/db/types.js.map +1 -0
  35. package/dist/dispatcher/approval.js +43 -0
  36. package/dist/dispatcher/approval.js.map +1 -0
  37. package/dist/dispatcher/runtime.js +262 -0
  38. package/dist/dispatcher/runtime.js.map +1 -0
  39. package/dist/dispatcher/turn-manager.js +167 -0
  40. package/dist/dispatcher/turn-manager.js.map +1 -0
  41. package/dist/feishu/bot.js +137 -0
  42. package/dist/feishu/bot.js.map +1 -0
  43. package/dist/feishu/content.js +108 -0
  44. package/dist/feishu/content.js.map +1 -0
  45. package/dist/feishu/render.js +600 -0
  46. package/dist/feishu/render.js.map +1 -0
  47. package/dist/feishu/types.js +9 -0
  48. package/dist/feishu/types.js.map +1 -0
  49. package/dist/runtime/codex-args.js +92 -0
  50. package/dist/runtime/codex-args.js.map +1 -0
  51. package/dist/runtime/config.js +351 -0
  52. package/dist/runtime/config.js.map +1 -0
  53. package/dist/runtime/paths.js +77 -0
  54. package/dist/runtime/paths.js.map +1 -0
  55. package/dist/runtime/secrets.js +18 -0
  56. package/dist/runtime/secrets.js.map +1 -0
  57. package/dist/server.js +234 -0
  58. package/dist/server.js.map +1 -0
  59. package/package.json +43 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 excitedjs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # @excitedjs/dreamux
2
+
3
+ The Codex-host server package. One long-running Node process hosts N
4
+ **Dispatchers**; each Dispatcher binds **1 Feishu bot + 1 Codex thread**.
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
9
+ [`.agents/root.md`](../../.agents/root.md).
10
+
11
+ Design background:
12
+ [#1 Proposal](https://github.com/excitedjs/dreamux/issues/1) ·
13
+ [#2 Engineering plan](https://github.com/excitedjs/dreamux/issues/2) ·
14
+ [#4 Monorepo + harness](https://github.com/excitedjs/dreamux/issues/4).
15
+
16
+ ## What this package ships
17
+
18
+ - The `dreamux` CLI (preferred): `dreamux server start`,
19
+ `dreamux server status`, `dreamux dispatcher add|remove|list|status|start|stop`.
20
+ - Legacy aliases: `dreamux-server` (= `dreamux server start`) and
21
+ `server-ctl` (= `dreamux <verb>`). Kept so pre-monorepo operators
22
+ don't have to rewrite PATH entries; see
23
+ [`.agents/decisions/0002-cli-and-package-naming.md`](../../.agents/decisions/0002-cli-and-package-naming.md).
24
+ - A SQLite-backed runtime (`dispatchers` + `inbound_buffer`) plus the
25
+ Feishu / Codex adapters that drive each dispatcher.
26
+
27
+ ## What this MVP does (P0)
28
+
29
+ - **One Node process, many Dispatchers.** Each Dispatcher = 1 Feishu Bot
30
+ (independent appId/secret) + 1 long-lived Codex `app-server` child + 1
31
+ Codex thread.
32
+ - **Single-thread, multi-chat fan-in.** A bot can be invited into multiple
33
+ groups and DMs; every inbound message goes into the same Codex thread.
34
+ Outbound replies are routed by the inbound's `source_chat_id`.
35
+ - **No dispatcher↔worktree binding.** Codex picks the worktree at `tm`-call
36
+ time. The Codex daemon's cwd is `~/.codex-host/dispatchers/<id>/cwd/`
37
+ (intentionally empty).
38
+ - **FIFO + at-most-once.** One running turn per dispatcher. After a server
39
+ crash, `running` inbound rows are flipped to `unknown` (the user is told
40
+ to confirm or resend); `awaiting_outbound` rows are safely retried.
41
+ - **Trusted-local only.** No chat allowlist, `approval-policy=never`. Any
42
+ other deployment must uplift access control first — see
43
+ [issue #2 §"信任模型"](https://github.com/excitedjs/dreamux/issues/2).
44
+
45
+ Explicitly **not** in MVP: approval cards, streaming outbound, per-chat
46
+ threads, tm registry isolation, cross-machine coordination, web UI.
47
+
48
+ ## Install / build / test
49
+
50
+ Two paths; both work, pick whichever fits the workflow.
51
+
52
+ **Per-package (this directory).** Matches pre-monorepo muscle memory and
53
+ runs the `prepare` hook (`tsc → dist/`):
54
+
55
+ ```bash
56
+ cd packages/dreamux
57
+ npm install
58
+ npm run build # also runs automatically via `prepare`
59
+ npm run typecheck # tsc --noEmit (no emit)
60
+ npm test # smoke + bin-launcher + (live) codex-0134 tests
61
+ ```
62
+
63
+ **Monorepo (from repo root).** Required once a second package exists; CI
64
+ exercises this path so a broken `rush.json` fails CI:
65
+
66
+ ```bash
67
+ node common/scripts/install-run-rush.js update
68
+ node common/scripts/install-run-rush.js build
69
+ node common/scripts/install-run-rush.js test
70
+ ```
71
+
72
+ The bin launchers shell out to plain `node` against the compiled `dist/`
73
+ output; **no `tsx` is needed at runtime** (PR #6).
74
+
75
+ ## Run the server
76
+
77
+ ```bash
78
+ # Preferred — unified CLI (issue #4)
79
+ ./bin/dreamux server start
80
+
81
+ # Backward-compat alias
82
+ ./bin/server
83
+ ```
84
+
85
+ Both work from any cwd and via symlinks (PR #6 + bin-launcher tests).
86
+
87
+ The server uses two separate home directories — by design (decision 0003):
88
+
89
+ | Path | Purpose | Source of truth |
90
+ |---|---|---|
91
+ | `~/.dreamux/config.toml` | User-editable global config — auto-created on first boot with sensible defaults; edit and restart to apply | the operator |
92
+ | `~/.codex-host/state.db` | SQLite (dispatchers + inbound buffer) | the server |
93
+ | `~/.codex-host/admin.sock` | Admin Unix socket (`0600`) | the server |
94
+ | `~/.codex-host/dispatchers/<id>/cwd/` | Codex app-server cwd | the server |
95
+ | `~/.codex-host/dispatchers/<id>/socket` | Codex Unix socket | the server |
96
+ | `~/.codex-host/dispatchers/<id>/*.log` | Codex stdout / stderr | the server |
97
+
98
+ `rm -rf ~/.codex-host` is a safe recovery — your config in `~/.dreamux/`
99
+ survives. `runtime_dir` and `admin_socket` paths in the config can move
100
+ the `~/.codex-host` half anywhere you like.
101
+
102
+ ## Configure a dispatcher
103
+
104
+ ```bash
105
+ # Bot secret comes from an env var the server process can see.
106
+ export BOT_SECRET_FLOW='cli_secret_XXX'
107
+
108
+ ./bin/dreamux dispatcher add \
109
+ --id flow \
110
+ --bot-app-id cli_aaa \
111
+ --bot-secret-ref env:BOT_SECRET_FLOW
112
+
113
+ # Inspect / restart
114
+ ./bin/dreamux dispatcher list
115
+ ./bin/dreamux dispatcher status --id flow
116
+ ./bin/dreamux dispatcher start --id flow # if not auto-started
117
+ ```
118
+
119
+ `./bin/server-ctl <args>` still works as an alias.
120
+
121
+ ## MVP verification path (issue #2 §"MVP 验收脚本")
122
+
123
+ 1. `dreamux dispatcher add --id flow --bot-app-id cli_aaa --bot-secret-ref env:BOT_SECRET_FLOW`
124
+ 2. `dreamux server start` — dispatcher `flow` goes to `ready`
125
+ 3. Invite the bot to a Feishu group A, send `hi`
126
+ 4. Server delivers it into the Codex thread; reply goes back to group A
127
+ 5. Invite the same bot to a DM, ask "do you remember the 'hi' from earlier?"
128
+ 6. Same thread, so the reply confirms — and goes back to the DM
129
+ 7. Ask the bot to "run the test suite via tm and summarize"
130
+ 8. Codex shells out to `tm`, reads stdout/stderr, replies into the source chat
131
+ 9. Repeat with a **different** worktree to prove dispatcher↔worktree decoupling
132
+ 10. `pkill node` to crash the server, then restart it
133
+ 11. Continue chatting — Codex `thread/resume` restores context
134
+
135
+ ## Configuration reference
136
+
137
+ Precedence for every config-able value (highest wins): env var →
138
+ per-dispatcher field → `~/.dreamux/config.toml` → built-in default.
139
+ See [decision 0003](../../.agents/decisions/0003-global-config-dir.md).
140
+
141
+ ### Global: `~/.dreamux/config.toml`
142
+
143
+ Auto-created on first boot with this default (excerpt — open the file
144
+ itself for the inline comments explaining each key):
145
+
146
+ ```toml
147
+ runtime_dir = "~/.codex-host"
148
+ # admin_socket = "~/.codex-host/admin.sock" # default: <runtime_dir>/admin.sock
149
+
150
+ [codex]
151
+ bin = "codex"
152
+ approval_policy = "never" # never | auto | auto-approve | on-failure
153
+ sandbox_mode = "workspace-write" # read-only | workspace-write | danger-full-access
154
+ extra_args = []
155
+ initialize_timeout_ms = 10000
156
+
157
+ [outbound]
158
+ retries = 3
159
+ retry_delay_ms = 1000
160
+ ```
161
+
162
+ Edit and restart `dreamux server start`. Parse errors fail-fast with a
163
+ `file:line` pointer.
164
+
165
+ ### `codex_args_json` (per-dispatcher, overrides global)
166
+
167
+ JSON object stored in `dispatchers.codex_args_json`:
168
+
169
+ ```json
170
+ { "approvalPolicy": "never", "sandboxMode": "workspace-write", "extraArgs": ["--model", "gpt-5"] }
171
+ ```
172
+
173
+ | Field | Default | Notes |
174
+ | ---------------- | --------- | ---------------------------------------------------- |
175
+ | `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 §"实现陷阱"). |
176
+ | `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. |
177
+ | `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. |
178
+
179
+ ### Env vars (highest precedence — escape hatch)
180
+
181
+ | Var | Purpose |
182
+ | ---------------------------- | -------------------------------------------------- |
183
+ | `CODEX_HOST_RUNTIME_DIR` | Override `runtime_dir` |
184
+ | `CODEX_HOST_ADMIN_SOCKET` | Override admin Unix socket path |
185
+ | `CODEX_HOST_CODEX_BIN` | Override `codex.bin` |
186
+ | `DREAMUX_CONFIG_DIR` | Override `~/.dreamux` (where `config.toml` lives) |
187
+ | `BOT_SECRET_<NAME>` | Bot secrets referenced by `env:BOT_SECRET_<NAME>` |
188
+ | `DREAMUX_SKIP_LIVE_CODEX` | Opt out of the live codex 0.134 integration test (loud skip) |
189
+
190
+ ## What this MVP does **not** do
191
+
192
+ (see [issue #2 §"明确不在 MVP 范围"](https://github.com/excitedjs/dreamux/issues/2))
193
+
194
+ - Multiple threads per dispatcher
195
+ - Per-chat memory
196
+ - Approval / Feishu approval cards
197
+ - Streaming assistant deltas
198
+ - tm CLI changes (`tm --json`, registry namespace)
199
+ - Cross-machine coordination
200
+ - Web UI / Prometheus
201
+ - Migration from old claudemux dispatcher state
202
+ - access gate / chat allowlist / pairing (D12 + Trust Model)
203
+
204
+ ## Testing
205
+
206
+ ```bash
207
+ npm test # smoke + bin-launcher + codex-0134-live
208
+ ```
209
+
210
+ - `tests/smoke.test.ts` — fake-codex-driven dispatcher behavior:
211
+ happy path, FIFO, crash recovery (running → unknown), thread/resume
212
+ failure, outbound retry without turn re-run, approval fail-fast.
213
+ - `tests/bin-launcher.test.ts` — spawns the real bash launchers
214
+ (`dreamux`, `dreamux-server`, `server-ctl`, plus the repo-root
215
+ forwarders) from arbitrary cwds and through symlinks; static "no tsx"
216
+ assertion.
217
+ - `tests/codex-0134-live.test.ts` — spawns a real `codex app-server`
218
+ (skipped loudly when `codex` is missing or wrong version; opt-in via
219
+ `DREAMUX_SKIP_LIVE_CODEX=1`).
220
+
221
+ ## License
222
+
223
+ MIT.
package/bin/dreamux ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ # Launcher for the unified `dreamux` CLI (issue #4).
3
+ # Resolves its own location through symlinks so it works from any cwd and
4
+ # via any chain of symlinks (npm-link, ~/bin shortcuts, etc.). Runs the
5
+ # compiled dist output produced by `npm install` / `rush build`.
6
+ set -eu
7
+
8
+ SOURCE="${BASH_SOURCE[0]}"
9
+ hop=0
10
+ while [ -L "$SOURCE" ]; do
11
+ hop=$((hop + 1))
12
+ if [ "$hop" -gt 40 ]; then
13
+ echo "dreamux: too many symlink hops resolving $SOURCE" >&2
14
+ exit 1
15
+ fi
16
+ TARGET="$(readlink "$SOURCE")"
17
+ if [ "${TARGET:0:1}" = "/" ]; then
18
+ SOURCE="$TARGET"
19
+ else
20
+ SOURCE="$(dirname -- "$SOURCE")/$TARGET"
21
+ fi
22
+ done
23
+
24
+ DIR="$(cd -- "$(dirname -- "$SOURCE")/.." &> /dev/null && pwd)"
25
+ TARGET="$DIR/dist/cli/dreamux.js"
26
+ if [ ! -f "$TARGET" ]; then
27
+ echo "dreamux: $TARGET is missing." >&2
28
+ echo "Run 'npm install' (it runs 'npm run build' via the prepare hook), 'npm run build', or 'rush build' from the monorepo root first." >&2
29
+ exit 1
30
+ fi
31
+ exec node "$TARGET" "$@"
package/bin/server ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ # Launcher for dreamux-server.
3
+ #
4
+ # Resolves its own location (following symlinks) so it works from any cwd
5
+ # AND when invoked via a symlink (e.g. `npm install -g` puts a symlink under
6
+ # the node bin dir). Runs the compiled dist output produced by
7
+ # `npm install` / `npm run build`.
8
+ set -eu
9
+
10
+ SOURCE="${BASH_SOURCE[0]}"
11
+ # Resolve symlinks until we find the real file. Handles both absolute and
12
+ # relative symlink targets. Bounded by a reasonable loop cap to detect cycles.
13
+ hop=0
14
+ while [ -L "$SOURCE" ]; do
15
+ hop=$((hop + 1))
16
+ if [ "$hop" -gt 40 ]; then
17
+ echo "dreamux-server: too many symlink hops resolving $SOURCE" >&2
18
+ exit 1
19
+ fi
20
+ TARGET="$(readlink "$SOURCE")"
21
+ if [ "${TARGET:0:1}" = "/" ]; then
22
+ SOURCE="$TARGET"
23
+ else
24
+ SOURCE="$(dirname -- "$SOURCE")/$TARGET"
25
+ fi
26
+ done
27
+
28
+ DIR="$(cd -- "$(dirname -- "$SOURCE")/.." &> /dev/null && pwd)"
29
+ TARGET="$DIR/dist/cli/server.js"
30
+ if [ ! -f "$TARGET" ]; then
31
+ echo "dreamux-server: $TARGET is missing." >&2
32
+ echo "Run 'npm install' (it runs 'npm run build' via the prepare hook) or 'npm run build' from $DIR first." >&2
33
+ exit 1
34
+ fi
35
+ exec node "$TARGET" "$@"
package/bin/server-ctl ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+ # Launcher for server-ctl. See bin/server for resolution details.
3
+ set -eu
4
+
5
+ SOURCE="${BASH_SOURCE[0]}"
6
+ hop=0
7
+ while [ -L "$SOURCE" ]; do
8
+ hop=$((hop + 1))
9
+ if [ "$hop" -gt 40 ]; then
10
+ echo "server-ctl: too many symlink hops resolving $SOURCE" >&2
11
+ exit 1
12
+ fi
13
+ TARGET="$(readlink "$SOURCE")"
14
+ if [ "${TARGET:0:1}" = "/" ]; then
15
+ SOURCE="$TARGET"
16
+ else
17
+ SOURCE="$(dirname -- "$SOURCE")/$TARGET"
18
+ fi
19
+ done
20
+
21
+ DIR="$(cd -- "$(dirname -- "$SOURCE")/.." &> /dev/null && pwd)"
22
+ TARGET="$DIR/dist/cli/server-ctl.js"
23
+ if [ ! -f "$TARGET" ]; then
24
+ echo "server-ctl: $TARGET is missing." >&2
25
+ echo "Run 'npm install' (it runs 'npm run build' via the prepare hook) or 'npm run build' from $DIR first." >&2
26
+ exit 1
27
+ fi
28
+ exec node "$TARGET" "$@"
@@ -0,0 +1,49 @@
1
+ -- dreamux MVP schema (issue excitedjs/dreamux#2)
2
+ -- Version tracked via PRAGMA user_version, not a schema_version table.
3
+
4
+ CREATE TABLE dispatchers (
5
+ dispatcher_id TEXT PRIMARY KEY,
6
+ bot_app_id TEXT NOT NULL UNIQUE,
7
+ bot_secret_ref TEXT NOT NULL,
8
+ codex_args_json TEXT NOT NULL DEFAULT '{}',
9
+ codex_cwd TEXT,
10
+ thread_id TEXT,
11
+ status TEXT NOT NULL DEFAULT 'declared'
12
+ CHECK (status IN ('declared','starting','ready','degraded','stopping','stopped')),
13
+ enabled INTEGER NOT NULL DEFAULT 1,
14
+ created_at INTEGER NOT NULL,
15
+ updated_at INTEGER NOT NULL,
16
+ last_started_at INTEGER,
17
+ last_ready_at INTEGER,
18
+ last_error TEXT,
19
+ last_lost_thread_id TEXT
20
+ );
21
+
22
+ CREATE TABLE inbound_buffer (
23
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
24
+ dispatcher_id TEXT NOT NULL REFERENCES dispatchers(dispatcher_id),
25
+ source_chat_id TEXT NOT NULL,
26
+ source_message_id TEXT,
27
+ sender_id TEXT,
28
+ feishu_event_json TEXT NOT NULL,
29
+ parsed_text TEXT NOT NULL,
30
+ state TEXT NOT NULL DEFAULT 'queued'
31
+ CHECK (state IN ('queued','running','awaiting_outbound',
32
+ 'completed','outbound_failed','failed','unknown')),
33
+ codex_turn_id TEXT,
34
+ assistant_text TEXT,
35
+ feishu_message_ids_json TEXT,
36
+ outbound_error TEXT,
37
+ received_at INTEGER NOT NULL,
38
+ started_at INTEGER,
39
+ completed_at INTEGER,
40
+ failed_at INTEGER,
41
+ error TEXT
42
+ );
43
+
44
+ CREATE INDEX idx_inbound_dispatcher_state
45
+ ON inbound_buffer(dispatcher_id, state, id);
46
+
47
+ CREATE UNIQUE INDEX idx_inbound_message_dedupe
48
+ ON inbound_buffer(dispatcher_id, source_message_id)
49
+ WHERE source_message_id IS NOT NULL;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Admin method handlers.
3
+ *
4
+ * Each handler takes typed params and returns the `result` payload to put on
5
+ * the wire. Throws `AdminError` for user-actionable failures (the protocol
6
+ * layer formats those as `error` responses).
7
+ */
8
+ import { AdminError } from './protocol.js';
9
+ export const adminMethods = {
10
+ 'server.status': (server) => ({
11
+ pid: process.pid,
12
+ uptimeSec: Math.floor(process.uptime()),
13
+ dispatchers: server.summarize(),
14
+ }),
15
+ 'dispatcher.add': (server, params) => {
16
+ const id = mustString(params, 'dispatcher_id');
17
+ const botAppId = mustString(params, 'bot_app_id');
18
+ const botSecretRef = mustString(params, 'bot_secret_ref');
19
+ const codexArgsJson = optionalString(params, 'codex_args_json') ?? '{}';
20
+ const codexCwd = optionalString(params, 'codex_cwd');
21
+ try {
22
+ const row = server.repos.dispatchers.create({
23
+ dispatcher_id: id,
24
+ bot_app_id: botAppId,
25
+ bot_secret_ref: botSecretRef,
26
+ codex_args_json: codexArgsJson,
27
+ codex_cwd: codexCwd ?? null,
28
+ });
29
+ return { dispatcher_id: row.dispatcher_id, status: row.status };
30
+ }
31
+ catch (err) {
32
+ if (err && typeof err === 'object') {
33
+ const code = err.code;
34
+ if (code === 'SQLITE_CONSTRAINT_UNIQUE' ||
35
+ code === 'SQLITE_CONSTRAINT_PRIMARYKEY') {
36
+ throw new AdminError('CONFLICT', `dispatcher_id or bot_app_id already exists: ${err.message}`);
37
+ }
38
+ }
39
+ throw err;
40
+ }
41
+ },
42
+ 'dispatcher.remove': async (server, params) => {
43
+ const id = mustString(params, 'dispatcher_id');
44
+ const row = server.repos.dispatchers.get(id);
45
+ if (row === null) {
46
+ throw new AdminError('DISPATCHER_NOT_FOUND', `no dispatcher with id '${id}'`);
47
+ }
48
+ await server.stopDispatcher(id);
49
+ server.repos.dispatchers.remove(id);
50
+ return { dispatcher_id: id };
51
+ },
52
+ 'dispatcher.list': (server) => ({ dispatchers: server.summarize() }),
53
+ 'dispatcher.status': (server, params) => {
54
+ const id = mustString(params, 'dispatcher_id');
55
+ const row = server.repos.dispatchers.get(id);
56
+ if (row === null) {
57
+ throw new AdminError('DISPATCHER_NOT_FOUND', `no dispatcher with id '${id}'`);
58
+ }
59
+ const runtime = server.getRuntime(id);
60
+ const counts = server.repos.inbound.countByState(id);
61
+ return {
62
+ dispatcher_id: row.dispatcher_id,
63
+ bot_app_id: row.bot_app_id,
64
+ status: runtime?.getStatus() ?? row.status,
65
+ thread_id: runtime?.getThreadId() ?? row.thread_id,
66
+ last_lost_thread_id: row.last_lost_thread_id,
67
+ last_error: row.last_error,
68
+ inbound_buffer: counts,
69
+ };
70
+ },
71
+ 'dispatcher.start': async (server, params) => {
72
+ const id = mustString(params, 'dispatcher_id');
73
+ const row = server.repos.dispatchers.get(id);
74
+ if (row === null) {
75
+ throw new AdminError('DISPATCHER_NOT_FOUND', `no dispatcher with id '${id}'`);
76
+ }
77
+ await server.startDispatcher(id);
78
+ return { dispatcher_id: id, status: server.getRuntime(id)?.getStatus() };
79
+ },
80
+ 'dispatcher.stop': async (server, params) => {
81
+ const id = mustString(params, 'dispatcher_id');
82
+ await server.stopDispatcher(id);
83
+ return { dispatcher_id: id, status: 'stopped' };
84
+ },
85
+ };
86
+ function mustString(params, key) {
87
+ if (params === undefined || typeof params[key] !== 'string') {
88
+ throw new AdminError('BAD_REQUEST', `missing or non-string param '${key}'`);
89
+ }
90
+ return params[key];
91
+ }
92
+ function optionalString(params, key) {
93
+ if (params === undefined)
94
+ return null;
95
+ const v = params[key];
96
+ if (v === undefined || v === null)
97
+ return null;
98
+ if (typeof v !== 'string') {
99
+ throw new AdminError('BAD_REQUEST', `param '${key}' must be a string`);
100
+ }
101
+ return v;
102
+ }
103
+ //# sourceMappingURL=methods.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"methods.js","sourceRoot":"","sources":["../../src/admin/methods.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * NDJSON Unix-socket protocol for admin commands (issue #2 §"管理接口").
3
+ *
4
+ * One line in / one line out. Permissions on the socket are 0600 (only the
5
+ * owner). Method names use dotted lowercase; error codes use SCREAMING_SNAKE_CASE.
6
+ */
7
+ export class AdminError extends Error {
8
+ code;
9
+ constructor(code, message) {
10
+ super(message);
11
+ this.code = code;
12
+ this.name = 'AdminError';
13
+ }
14
+ }
15
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/admin/protocol.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAsBH,MAAM,OAAO,UAAW,SAAQ,KAAK;IACP;IAA5B,YAA4B,IAAY,EAAE,OAAe;QACvD,KAAK,CAAC,OAAO,CAAC,CAAC;QADW,SAAI,GAAJ,IAAI,CAAQ;QAEtC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF"}