ai-task-board-bridge 0.9.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 (42) hide show
  1. package/README.md +253 -0
  2. package/dist/activity-sanitizer.d.ts +5 -0
  3. package/dist/activity-sanitizer.d.ts.map +1 -0
  4. package/dist/activity-sanitizer.js +55 -0
  5. package/dist/activity-sanitizer.js.map +1 -0
  6. package/dist/app-server-client.d.ts +328 -0
  7. package/dist/app-server-client.d.ts.map +1 -0
  8. package/dist/app-server-client.js +524 -0
  9. package/dist/app-server-client.js.map +1 -0
  10. package/dist/bridge.d.ts +139 -0
  11. package/dist/bridge.d.ts.map +1 -0
  12. package/dist/bridge.js +2769 -0
  13. package/dist/bridge.js.map +1 -0
  14. package/dist/claim-retry.d.ts +10 -0
  15. package/dist/claim-retry.d.ts.map +1 -0
  16. package/dist/claim-retry.js +19 -0
  17. package/dist/claim-retry.js.map +1 -0
  18. package/dist/cli.d.ts +3 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +92 -0
  21. package/dist/cli.js.map +1 -0
  22. package/dist/history-sync.d.ts +120 -0
  23. package/dist/history-sync.d.ts.map +1 -0
  24. package/dist/history-sync.js +718 -0
  25. package/dist/history-sync.js.map +1 -0
  26. package/dist/index.d.ts +6 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +6 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/setup.d.ts +32 -0
  31. package/dist/setup.d.ts.map +1 -0
  32. package/dist/setup.js +822 -0
  33. package/dist/setup.js.map +1 -0
  34. package/dist/wake-client.d.ts +35 -0
  35. package/dist/wake-client.d.ts.map +1 -0
  36. package/dist/wake-client.js +219 -0
  37. package/dist/wake-client.js.map +1 -0
  38. package/dist/working-directories.d.ts +24 -0
  39. package/dist/working-directories.d.ts.map +1 -0
  40. package/dist/working-directories.js +158 -0
  41. package/dist/working-directories.js.map +1 -0
  42. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,253 @@
1
+ # AI Task Board Bridge
2
+
3
+ `ai-task-board-bridge` is the device-level Codex companion for AI Task
4
+ Board. One long-running Bridge process represents one device/AI Connection. It
5
+ starts the local `codex app-server` over stdio, discovers non-archived top-level
6
+ Codex threads, maps each thread to a Board Session, receives reserved work, and
7
+ streams supported progress back to the Board.
8
+
9
+ The Bridge uses the Board REST API and authenticated SSE directly. The Board
10
+ MCP server is optional and is not required for Bridge operation.
11
+
12
+ ## Interactive Linux setup
13
+
14
+ Node.js 18 or newer and a compatible, logged-in `codex` CLI are required. Run
15
+ the Bridge as the same OS user that owns the local Codex data and workspaces:
16
+
17
+ ```bash
18
+ npx --yes ai-task-board-bridge@0.9.0 setup
19
+ ```
20
+
21
+ The setup wizard asks for the Board URL, hidden Connection Token, working
22
+ directory, Codex home and executable, custom-provider credential environment
23
+ variables, thread limits, permission/approval modes, and the Web configuration
24
+ gate. It then installs and starts
25
+ `ai-task-board-bridge.service` in the effective user's systemd user
26
+ manager. Running with no command also enters setup in a TTY when either required
27
+ Board setting is missing.
28
+
29
+ The generated unit has no `User=` directive: a systemd user manager already
30
+ runs as its owning UID. Setup explicitly binds that user's `HOME` and
31
+ `CODEX_HOME`, captures an absolute Codex executable, stores secrets in a `0600`
32
+ EnvironmentFile, and copies the current package to the user's XDG data directory
33
+ so the service does not depend on an ephemeral npx cache. Do not use `sudo npx`
34
+ unless a root-owned service and root's Codex configuration are actually desired.
35
+ Each Linux user can install an independent unit with the same name; use separate
36
+ Board Connections unless only one of them should acquire the runtime lease.
37
+ Provider variables referenced by `env_key` or `env_http_headers` in
38
+ `config.toml` are detected by name and confirmed with hidden input; setup does
39
+ not copy the user's entire shell environment.
40
+
41
+ New interactive installs default to `safe` permissions and `decline` approvals.
42
+ The wizard requires an explicit selection before installing, and rerunning it
43
+ updates the configuration and restarts the service. Upgrades disable the legacy
44
+ `ai-task-board-codex-bridge.service` before starting
45
+ `ai-task-board-bridge.service`, with rollback if the new service fails to start.
46
+
47
+ ## Foreground and automation mode
48
+
49
+ The original environment-variable interface remains available and does not
50
+ install a service:
51
+
52
+ ```bash
53
+ AI_TASK_BOARD_URL='https://board.example.com' \
54
+ AI_TASK_BOARD_CONNECTION_TOKEN='atb_REPLACE_ME' \
55
+ CODEX_WORKING_DIRECTORY='/path/to/a/safe/start-directory' \
56
+ npx --yes ai-task-board-bridge@0.9.0 run
57
+ ```
58
+
59
+ > **High-risk foreground defaults:** when these values are omitted, the Bridge uses
60
+ > `CODEX_BRIDGE_PERMISSION_MODE=danger-full-access` (no sandbox) together with
61
+ > `CODEX_BRIDGE_APPROVAL_MODE=accept` (automatic device-side approval). Use
62
+ > this combination only when the workspace, Codex configuration, and Connection
63
+ > users are trusted. Set `CODEX_BRIDGE_PERMISSION_MODE=safe` explicitly when
64
+ > writes and network access must be constrained.
65
+
66
+ `CODEX_THREAD_ID` is optional. By default, `CODEX_THREAD_SCOPE=cwd` manages only
67
+ top-level threads whose recorded cwd exactly equals a locally allowlisted directory,
68
+ up to 50 recent matches. Set `CODEX_THREAD_SCOPE=all` only as an explicit,
69
+ high-risk opt-in to cross-project discovery. `CODEX_THREAD_ID` overrides the
70
+ scope with one exact existing-thread compatibility filter:
71
+
72
+ ```bash
73
+ CODEX_THREAD_ID='REPLACE_WITH_LOCAL_THREAD_ID' \
74
+ npx --yes ai-task-board-bridge@0.9.0
75
+ ```
76
+
77
+ Bridge 0.7 and later can manage several exact working directories in one process:
78
+
79
+ ```bash
80
+ CODEX_WORKING_DIRECTORIES='[{"key":"main","name":"Main App","path":"/srv/main"},{"key":"docs","name":"Docs","path":"/srv/docs"}]' \
81
+ CODEX_THREAD_SCOPE='cwd' \
82
+ npx --yes ai-task-board-bridge@0.9.0
83
+ ```
84
+
85
+ The JSON array accepts 1 to 100 unique `{key,name?,path}` entries. Its first
86
+ entry is the App Server startup and local fallback directory. The Board stores
87
+ and returns only a selected key in Web create commands; the Bridge resolves that
88
+ key against the currently effective list.
89
+
90
+ `CODEX_MAX_THREADS` controls the device-wide inventory limit (default `50`,
91
+ range `1..500` across all configured directories).
92
+ `CODEX_MAX_CONCURRENT_TURNS` remains a compatibility startup value (default
93
+ `2`) before Web configuration is applied; once enabled, the Web value directly
94
+ controls device-wide turn concurrency from `1..32`. Session names do not upload
95
+ the local thread title or first prompt by default; they use the cwd basename plus
96
+ a short thread ID. Set
97
+ `CODEX_BRIDGE_INCLUDE_THREAD_TITLES=true` only after explicitly accepting that
98
+ metadata disclosure. Inventory still uploads each thread ID, absolute working
99
+ directory, and model label to the Board Workspace.
100
+
101
+ ## Optional Web configuration
102
+
103
+ Web configuration is disabled locally by default. Enable it on the device with
104
+ `CODEX_BRIDGE_WEB_CONFIG=true`; the Bridge then checks `/api/ai/config` every
105
+ 10 seconds and applies safe runtime changes without restarting systemd. The
106
+ interval can be set with `AI_TASK_BOARD_CONFIG_POLL_INTERVAL_MS` from `1000` to
107
+ `600000` milliseconds.
108
+
109
+ The Web console may enable or pause this Bridge, hide or show thread titles,
110
+ enable bounded history sync, lower the thread and history limits, set device-wide
111
+ turn concurrency directly,
112
+ and—only after a separate local opt-in—replace the effective working-directory
113
+ list. `enabled=false` stops all Session
114
+ workers, releases their work, and uploads an authoritative empty inventory,
115
+ while keeping the device Bridge process alive so it can be re-enabled. A
116
+ concurrency reduction lets active turns finish and only delays new turns.
117
+
118
+ The device environment remains the immutable security boundary:
119
+
120
+ - Web `max_threads` is clamped to the local `CODEX_MAX_THREADS` maximum.
121
+ - Web `max_concurrent_turns` directly sets device-wide concurrency in the
122
+ supported `1..32` range; it is not clamped by a separate local maximum.
123
+ - Web title upload is denied unless
124
+ `CODEX_BRIDGE_ALLOW_REMOTE_THREAD_TITLES=true` or the device already opted in
125
+ with `CODEX_BRIDGE_INCLUDE_THREAD_TITLES=true`.
126
+ - Web history sync is denied unless the device explicitly sets
127
+ `CODEX_BRIDGE_ALLOW_HISTORY_SYNC=true`. The requested recent-turn count is
128
+ clamped to `CODEX_BRIDGE_MAX_HISTORY_TURNS` (default `50`, maximum `200`).
129
+ - Web working-directory configuration is denied unless the device explicitly
130
+ sets `CODEX_BRIDGE_ALLOW_REMOTE_WORKING_DIRECTORIES=true`. A remote list must
131
+ contain 1 to 100 unique entries with absolute paths that already exist and are
132
+ directories. Missing, null, or locally denied remote values retain the
133
+ immutable `CODEX_WORKING_DIRECTORIES` / `CODEX_WORKING_DIRECTORY` startup list.
134
+ - The Board cannot change the URL/token, Codex executable, thread scope/fixed
135
+ thread, permission mode, approval mode, or the immutable local fallback list.
136
+
137
+ The Bridge reports its effective settings, local constraints, applied version,
138
+ and any clamp/gate warning back to the Board. It also holds a renewable runtime
139
+ lease so a second Bridge using the same Connection waits instead of overwriting
140
+ status or starting duplicate workers. With Web configuration disabled, these
141
+ reports continue as lease heartbeats so a new Web console can explain the local
142
+ gate, but the Bridge ignores returned desired settings. A missing config
143
+ endpoint is tolerated in that mode. When Web configuration is explicitly
144
+ enabled, a 404 is fatal so a deployment cannot appear remotely managed when it
145
+ is not. Graceful shutdown releases the runtime lease; after an unclean exit it
146
+ expires within 30 seconds.
147
+ A replacement process waits without starting workers while that old lease is
148
+ still valid. Lease renewal runs independently from inventory/config application,
149
+ at least every 10 seconds, and a local safety deadline stops workers before a
150
+ lease can expire during a prolonged Board outage. The deprecated-Board 404
151
+ fallback cannot provide this single-runtime fence.
152
+
153
+ ## Web Thread management
154
+
155
+ Bridge 0.5 lets a Workspace owner create, rename, and delete Codex Threads from
156
+ the Web Console. The Board stores each request as a leased command; only the
157
+ Bridge process holding that connection's runtime lease may execute it. New
158
+ Threads use the effective directory selected in the hierarchy. Thread-create
159
+ commands still carry only its stable key, never an absolute path. Rename and delete commands only
160
+ target Threads already present in the Bridge's managed inventory. Fixed
161
+ `CODEX_THREAD_ID` mode rejects create and delete commands.
162
+
163
+ A delete request is accepted only when the Thread has no active or reserved
164
+ work. It immediately hides and fences the Board Session, then calls the Codex
165
+ App Server's hard-delete method. Compatible older App Server builds that lack
166
+ hard delete fall back to archive. Board audit/history rows are retained.
167
+
168
+ ## Optional bounded history sync
169
+
170
+ History upload is off by default and requires both
171
+ `CODEX_BRIDGE_ALLOW_HISTORY_SYNC=true` on the device and `sync_history=true` in
172
+ Web configuration. Bridge 0.5 scans at most the configured number of recent
173
+ completed turns for ordinary CLI/VS Code threads in a separate, cancellable,
174
+ bounded background loop. Runtime-lease renewal, inventory, and live turns do
175
+ not wait for this scan.
176
+
177
+ The user's `userMessage` and the final `agentMessage` are imported. A turn
178
+ carrying a non-empty persisted `clientUserMessageId` is skipped because it came
179
+ from Board live execution.
180
+ Images, local-image/skill paths, raw `reasoning.content`, commands and their
181
+ output, diffs, MCP arguments/results, and every other tool item are discarded
182
+ locally while persisted items are read in pages. The Bridge scans at most 10,000
183
+ raw items in one turn and retains at most 500 whitelisted activities across one
184
+ snapshot. A turn that would cross either hard safety boundary is not partially
185
+ imported; the snapshot stops with `partial` and a `local-safety-cap` marker.
186
+ That marker reports local truncation and is not a resumable App Server cursor.
187
+ Text passes through the Bridge redactor and a UTF-safe 50,000-character limit
188
+ before upload. Stable thread/turn/item references make any rescan idempotent. An
189
+ unchanged `thread.updatedAt` plus turn-limit signature is scanned only once per
190
+ Bridge process; a changed thread, changed limit, or process restart can enqueue
191
+ another idempotent scan. Batches contain at most 100 items and 512 KiB. Other
192
+ scan/import failures use bounded backoff, update only the per-Session history
193
+ status, and do not stop the main Bridge.
194
+
195
+ Imported history is append-only on the Board, and historical user prompts are
196
+ visible to members who can access the Workspace. Turning history sync off or
197
+ lowering the recent-turn limit stops later imports but does not delete content
198
+ that was already uploaded. Remove that data through the Board's applicable
199
+ Workspace/data deletion flow when required.
200
+
201
+ The Bridge forwards only agent-message deltas and completed replies. Stream
202
+ chunks are batched for roughly 500 ms or 8 KiB. Reasoning summaries, command
203
+ output, tool/file/plan events, and usage are not uploaded.
204
+ `CODEX_BRIDGE_PERMISSION_MODE=danger-full-access` is the default execution
205
+ profile. It explicitly keeps `on-request` / user-reviewed approval handling but
206
+ runs without a sandbox, so writes and network access are unrestricted within
207
+ the OS user's own permissions. It does not grant root or bypass operating-system
208
+ access controls. This default is intended to keep trusted tasks from stalling on
209
+ sandbox limits, but it is high risk.
210
+
211
+ Set `CODEX_BRIDGE_PERMISSION_MODE=safe` explicitly to use `workspace-write`,
212
+ limit writable roots to the thread's absolute cwd, exclude implicit tmp roots,
213
+ and disable network access. This primarily constrains writes and network; it
214
+ does not prevent reading files already readable by the same UID. The `inherit`
215
+ mode sends no permission or approval overrides and uses the thread/local Codex
216
+ configuration as-is. It may inherit full access, broader writable roots, or a
217
+ stricter policy, so treat it as high risk when the local configuration is not
218
+ known.
219
+
220
+ Separately, supported server-initiated approval requests correlated with the
221
+ active turn are automatically accepted on the device by the default
222
+ `CODEX_BRIDGE_APPROVAL_MODE=accept`, without per-request Web confirmation.
223
+ Uncorrelated requests are still denied, blocking `requestUserInput` continues
224
+ through the Web Console, and MCP elicitation remains denied. Approval mode does
225
+ not configure the sandbox. Use `decline` to deny approval requests or
226
+ `accept-session` to extend supported approvals to the session. Automatic
227
+ approval is high risk and is not a Web confirmation flow.
228
+
229
+ There is currently no reliable running-turn steer, Web-triggered interrupt, or
230
+ Web approval flow. Messages sent while a thread is busy queue as later Tasks.
231
+ Execution is at-least-once, so irreversible actions still need their own
232
+ idempotency or explicit human confirmation.
233
+
234
+ Version 0.6 forwards blocking Codex `requestUserInput` prompts to the Web
235
+ Console as structured controls. The App Server request, turn, task claim, and
236
+ heartbeats remain active while the Bridge polls; submitting the Web answer
237
+ resolves that same request instead of creating a later turn. Answer values are
238
+ kept out of public messages, and secret inputs are cleared when the claim ends.
239
+
240
+ Keep the Connection Token in a secret store or protected environment file. Do
241
+ not pass it as a command-line argument. The Bridge removes that token from the
242
+ App Server child environment, but processes under the same OS UID are not a
243
+ strong token-isolation boundary. Use a separate UID and/or a token proxy when
244
+ strong isolation is required. Board schema and `/api/ai/sessions/sync` must be
245
+ upgraded before starting 0.8; there is no 404 fallback to the old registration
246
+ API. On Linux, use `setup` for a pinned local runtime and systemd user service.
247
+ On other platforms, pin version `0.9.0` in launchd or another process manager.
248
+ Run only one Bridge for the same device/Connection, and
249
+ do not let another TUI, IDE, or automation writer submit turns to a managed
250
+ thread at the same time.
251
+
252
+ Use `npx --yes ai-task-board-bridge@0.9.0 --help` for the complete
253
+ environment-variable list.
@@ -0,0 +1,5 @@
1
+ /** Best-effort redaction before Harness output crosses into the shared Board. */
2
+ export declare function redactHarnessText(value: string, limit?: number): string;
3
+ export declare function sanitizeHarnessValue(value: unknown, depth?: number): unknown;
4
+ export declare function boundActivityData(value: Record<string, unknown>): Record<string, unknown>;
5
+ //# sourceMappingURL=activity-sanitizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-sanitizer.d.ts","sourceRoot":"","sources":["../src/activity-sanitizer.ts"],"names":[],"mappings":"AAYA,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAS,GAAG,MAAM,CAkBvE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,OAAO,CAkBvE;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAYzB"}
@@ -0,0 +1,55 @@
1
+ const MAX_ACTIVITY_DATA_BYTES = 240 * 1024;
2
+ const SENSITIVE_FIELD_PATTERN = /(?:^|_)(?:api_key|authorization|credential|password|private_key|secret(?:_key)?|token)$/;
3
+ function isSensitiveField(key) {
4
+ const normalized = key
5
+ .replace(/([a-z\d])([A-Z])/g, "$1_$2")
6
+ .replace(/[-\s]+/g, "_")
7
+ .toLowerCase();
8
+ return SENSITIVE_FIELD_PATTERN.test(normalized);
9
+ }
10
+ /** Best-effort redaction before Harness output crosses into the shared Board. */
11
+ export function redactHarnessText(value, limit = 20_000) {
12
+ const redacted = value
13
+ .replace(/\b(?:sk[-_][A-Za-z0-9_-]{12,}|atb_[A-Za-z0-9_-]{12,})\b/g, "[REDACTED]")
14
+ .replace(/(authorization\s*[:=]\s*bearer\s+)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s"']+)/gi, "$1[REDACTED]")
15
+ .replace(/(\b["']?(?:access[_-]?token|api[_-]?key|bearer[_-]?token|credential|password|private[_-]?key|secret(?:[_-]?key)?|token)["']?\s*[:=]\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s,;]+)/gi, "$1[REDACTED]");
16
+ if (redacted.length <= limit)
17
+ return redacted;
18
+ const suffix = "\n…[已截断;完整输出请作为附件上传]";
19
+ if (limit <= suffix.length)
20
+ return redacted.slice(0, limit);
21
+ return `${redacted.slice(0, limit - suffix.length)}${suffix}`;
22
+ }
23
+ export function sanitizeHarnessValue(value, depth = 0) {
24
+ if (depth > 6)
25
+ return "[截断:嵌套过深]";
26
+ if (typeof value === "string")
27
+ return redactHarnessText(value);
28
+ if (Array.isArray(value)) {
29
+ return value.slice(0, 100).map((item) => sanitizeHarnessValue(item, depth + 1));
30
+ }
31
+ if (value && typeof value === "object") {
32
+ const entries = Object.entries(value).slice(0, 100);
33
+ return Object.fromEntries(entries.map(([key, item]) => [
34
+ key,
35
+ isSensitiveField(key)
36
+ ? "[REDACTED]"
37
+ : sanitizeHarnessValue(item, depth + 1),
38
+ ]));
39
+ }
40
+ return value;
41
+ }
42
+ export function boundActivityData(value) {
43
+ const sanitized = sanitizeHarnessValue(value);
44
+ const encoded = JSON.stringify(sanitized);
45
+ if (new TextEncoder().encode(encoded).byteLength <= MAX_ACTIVITY_DATA_BYTES) {
46
+ return sanitized;
47
+ }
48
+ return {
49
+ truncated: true,
50
+ // 60k UTF-16 code units stay below the API's 256 KiB limit even if every
51
+ // character requires four UTF-8 bytes.
52
+ preview: redactHarnessText(encoded, 60_000),
53
+ };
54
+ }
55
+ //# sourceMappingURL=activity-sanitizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-sanitizer.js","sourceRoot":"","sources":["../src/activity-sanitizer.ts"],"names":[],"mappings":"AAAA,MAAM,uBAAuB,GAAG,GAAG,GAAG,IAAI,CAAC;AAC3C,MAAM,uBAAuB,GAC3B,yFAAyF,CAAC;AAE5F,SAAS,gBAAgB,CAAC,GAAW;IACnC,MAAM,UAAU,GAAG,GAAG;SACnB,OAAO,CAAC,mBAAmB,EAAE,OAAO,CAAC;SACrC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,WAAW,EAAE,CAAC;IACjB,OAAO,uBAAuB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAClD,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,KAAK,GAAG,MAAM;IAC7D,MAAM,QAAQ,GAAG,KAAK;SACnB,OAAO,CACN,0DAA0D,EAC1D,YAAY,CACb;SACA,OAAO,CACN,0EAA0E,EAC1E,cAAc,CACf;SACA,OAAO,CACN,+KAA+K,EAC/K,cAAc,CACf,CAAC;IACJ,IAAI,QAAQ,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,QAAQ,CAAC;IAC9C,MAAM,MAAM,GAAG,sBAAsB,CAAC;IACtC,IAAI,KAAK,IAAI,MAAM,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC5D,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAc,EAAE,KAAK,GAAG,CAAC;IAC5D,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,WAAW,CAAC;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC/D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,oBAAoB,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC/E,OAAO,MAAM,CAAC,WAAW,CACvB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;YAC3B,GAAG;YACH,gBAAgB,CAAC,GAAG,CAAC;gBACnB,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,oBAAoB,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC;SAC1C,CAAC,CACH,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAA8B;IAE9B,MAAM,SAAS,GAAG,oBAAoB,CAAC,KAAK,CAA4B,CAAC;IACzE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,UAAU,IAAI,uBAAuB,EAAE,CAAC;QAC5E,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO;QACL,SAAS,EAAE,IAAI;QACf,yEAAyE;QACzE,uCAAuC;QACvC,OAAO,EAAE,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC;KAC5C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,328 @@
1
+ export type AppServerRequestId = string | number;
2
+ export interface AppServerClientInfo {
3
+ name: string;
4
+ title?: string | null;
5
+ version: string;
6
+ }
7
+ export interface AppServerInitializeCapabilities {
8
+ experimentalApi?: boolean;
9
+ requestAttestation?: boolean;
10
+ mcpServerOpenaiFormElicitation?: boolean;
11
+ optOutNotificationMethods?: string[] | null;
12
+ extensions?: Record<string, unknown> | null;
13
+ }
14
+ export interface AppServerInitializeParams {
15
+ clientInfo: AppServerClientInfo;
16
+ capabilities?: AppServerInitializeCapabilities | null;
17
+ }
18
+ export interface AppServerInitializeResponse {
19
+ userAgent: string;
20
+ codexHome?: string;
21
+ platformFamily?: string;
22
+ platformOs?: string;
23
+ [key: string]: unknown;
24
+ }
25
+ export interface AppServerThread {
26
+ id: string;
27
+ source?: unknown;
28
+ /** Unix timestamp in seconds. */
29
+ createdAt?: number | null;
30
+ /** Unix timestamp in seconds; changes when a turn mutates the thread. */
31
+ updatedAt?: number | null;
32
+ [key: string]: unknown;
33
+ }
34
+ export type AppServerSortDirection = "asc" | "desc";
35
+ export type AppServerTurnItemsView = "notLoaded" | "summary" | "full";
36
+ export interface AppServerThreadItem {
37
+ type: string;
38
+ id?: string;
39
+ [key: string]: unknown;
40
+ }
41
+ export interface AppServerTurn {
42
+ id: string;
43
+ items?: AppServerThreadItem[];
44
+ itemsView?: AppServerTurnItemsView;
45
+ status?: "completed" | "interrupted" | "failed" | "inProgress" | string;
46
+ error?: unknown;
47
+ /** Unix timestamp in seconds. */
48
+ startedAt?: number | null;
49
+ /** Unix timestamp in seconds. */
50
+ completedAt?: number | null;
51
+ [key: string]: unknown;
52
+ }
53
+ export interface AppServerUserInput {
54
+ type: string;
55
+ [key: string]: unknown;
56
+ }
57
+ export interface AppServerThreadListParams {
58
+ cursor?: string | null;
59
+ limit?: number | null;
60
+ [key: string]: unknown;
61
+ }
62
+ export interface AppServerThreadListResponse {
63
+ data: AppServerThread[];
64
+ nextCursor: string | null;
65
+ backwardsCursor?: string | null;
66
+ [key: string]: unknown;
67
+ }
68
+ export interface AppServerModelReasoningEffort {
69
+ reasoningEffort: string;
70
+ description?: string | null;
71
+ [key: string]: unknown;
72
+ }
73
+ export interface AppServerModel {
74
+ id: string;
75
+ model: string;
76
+ displayName: string;
77
+ description?: string | null;
78
+ hidden?: boolean;
79
+ defaultReasoningEffort?: string | null;
80
+ supportedReasoningEfforts?: AppServerModelReasoningEffort[];
81
+ inputModalities?: string[];
82
+ supportsPersonality?: boolean;
83
+ isDefault?: boolean;
84
+ [key: string]: unknown;
85
+ }
86
+ export interface AppServerModelListParams {
87
+ cursor?: string | null;
88
+ limit?: number | null;
89
+ includeHidden?: boolean;
90
+ [key: string]: unknown;
91
+ }
92
+ export interface AppServerModelListResponse {
93
+ data: AppServerModel[];
94
+ nextCursor: string | null;
95
+ [key: string]: unknown;
96
+ }
97
+ export interface AppServerThreadReadParams {
98
+ threadId: string;
99
+ includeTurns?: boolean;
100
+ [key: string]: unknown;
101
+ }
102
+ export interface AppServerThreadReadResponse {
103
+ thread: AppServerThread & {
104
+ turns?: AppServerTurn[];
105
+ };
106
+ [key: string]: unknown;
107
+ }
108
+ export interface AppServerThreadTurnsListParams {
109
+ threadId: string;
110
+ cursor?: string | null;
111
+ limit?: number | null;
112
+ sortDirection?: AppServerSortDirection | null;
113
+ itemsView?: AppServerTurnItemsView | null;
114
+ [key: string]: unknown;
115
+ }
116
+ export interface AppServerThreadTurnsListResponse {
117
+ data: AppServerTurn[];
118
+ nextCursor: string | null;
119
+ backwardsCursor?: string | null;
120
+ [key: string]: unknown;
121
+ }
122
+ export interface AppServerThreadItemEntry {
123
+ turnId: string;
124
+ item: AppServerThreadItem;
125
+ [key: string]: unknown;
126
+ }
127
+ export interface AppServerThreadItemsListParams {
128
+ threadId: string;
129
+ turnId?: string | null;
130
+ cursor?: string | null;
131
+ limit?: number | null;
132
+ sortDirection?: AppServerSortDirection | null;
133
+ [key: string]: unknown;
134
+ }
135
+ export interface AppServerThreadItemsListResponse {
136
+ data: AppServerThreadItemEntry[];
137
+ nextCursor: string | null;
138
+ backwardsCursor?: string | null;
139
+ [key: string]: unknown;
140
+ }
141
+ export interface AppServerThreadStartParams {
142
+ model?: string | null;
143
+ /** Per-thread Codex config overrides, such as model_reasoning_effort. */
144
+ config?: Record<string, unknown> | null;
145
+ [key: string]: unknown;
146
+ }
147
+ export interface AppServerThreadStartResponse {
148
+ thread: AppServerThread;
149
+ /** Effective model selected by Codex for this resumed/started Thread. */
150
+ model?: string | null;
151
+ /** Effective reasoning effort selected by Codex. */
152
+ reasoningEffort?: string | null;
153
+ [key: string]: unknown;
154
+ }
155
+ export interface AppServerThreadResumeParams {
156
+ threadId: string;
157
+ model?: string | null;
158
+ /** Per-thread Codex config overrides, such as model_reasoning_effort. */
159
+ config?: Record<string, unknown> | null;
160
+ [key: string]: unknown;
161
+ }
162
+ export type AppServerThreadResumeResponse = AppServerThreadStartResponse;
163
+ export interface AppServerThreadSetNameParams {
164
+ threadId: string;
165
+ name: string;
166
+ }
167
+ export type AppServerThreadSetNameResponse = Record<string, never>;
168
+ export interface AppServerThreadDeleteParams {
169
+ threadId: string;
170
+ }
171
+ export type AppServerThreadDeleteResponse = Record<string, never>;
172
+ export type AppServerThreadArchiveParams = AppServerThreadDeleteParams;
173
+ export type AppServerThreadArchiveResponse = Record<string, never>;
174
+ export interface AppServerTurnStartParams {
175
+ threadId: string;
176
+ input: AppServerUserInput[];
177
+ /** Per-turn model override; Codex keeps it as the Thread default afterward. */
178
+ model?: string | null;
179
+ /** Per-turn reasoning override; Codex keeps it as the Thread default afterward. */
180
+ effort?: string | null;
181
+ /** Request a provider-exposed reasoning summary for this and later turns. */
182
+ summary?: "auto" | "concise" | "detailed" | "none" | null;
183
+ [key: string]: unknown;
184
+ }
185
+ export interface AppServerTurnStartResponse {
186
+ turn: AppServerTurn;
187
+ [key: string]: unknown;
188
+ }
189
+ export interface AppServerTurnSteerParams {
190
+ threadId: string;
191
+ expectedTurnId: string;
192
+ input: AppServerUserInput[];
193
+ [key: string]: unknown;
194
+ }
195
+ export interface AppServerTurnSteerResponse {
196
+ turnId: string;
197
+ [key: string]: unknown;
198
+ }
199
+ export interface AppServerTurnInterruptParams {
200
+ threadId: string;
201
+ turnId: string;
202
+ [key: string]: unknown;
203
+ }
204
+ export type AppServerTurnInterruptResponse = Record<string, never>;
205
+ export interface AppServerNotification<P = unknown> {
206
+ method: string;
207
+ params: P;
208
+ }
209
+ export interface AppServerIncomingRequest<P = unknown> {
210
+ id: AppServerRequestId;
211
+ method: string;
212
+ params: P;
213
+ }
214
+ export interface AppServerRequestOptions {
215
+ /** A value of zero disables the timeout for this request. */
216
+ timeoutMs?: number;
217
+ /** Cancels the local pending request without waiting for its timeout. */
218
+ signal?: AbortSignal;
219
+ }
220
+ export type AppServerNotificationListener = (notification: AppServerNotification) => void;
221
+ export type AppServerRequestHandler = (request: AppServerIncomingRequest) => unknown | Promise<unknown>;
222
+ export type AppServerErrorListener = (error: Error) => void;
223
+ export type AppServerStderrListener = (text: string) => void;
224
+ export interface CodexAppServerClientOptions {
225
+ /** Executable to spawn. Defaults to `codex`. */
226
+ binary?: string;
227
+ /** Complete argv for the executable. Defaults to `["app-server", "--stdio"]`. */
228
+ args?: readonly string[];
229
+ cwd?: string;
230
+ /** Environment entries are merged over the current process environment. */
231
+ env?: NodeJS.ProcessEnv;
232
+ /** Environment keys to remove after merging, before the child is spawned. */
233
+ unsetEnv?: readonly string[];
234
+ requestTimeoutMs?: number;
235
+ shutdownTimeoutMs?: number;
236
+ clientInfo?: AppServerClientInfo;
237
+ capabilities?: AppServerInitializeCapabilities | null;
238
+ serverRequestHandler?: AppServerRequestHandler;
239
+ onNotification?: AppServerNotificationListener;
240
+ onError?: AppServerErrorListener;
241
+ onStderr?: AppServerStderrListener;
242
+ }
243
+ export declare class AppServerRpcError extends Error {
244
+ readonly code: number;
245
+ readonly data?: unknown;
246
+ readonly requestId?: AppServerRequestId;
247
+ readonly method?: string;
248
+ constructor(code: number, message: string, options?: {
249
+ data?: unknown;
250
+ requestId?: AppServerRequestId;
251
+ method?: string;
252
+ });
253
+ }
254
+ export declare class AppServerRequestTimeoutError extends Error {
255
+ readonly requestId: AppServerRequestId;
256
+ readonly method: string;
257
+ readonly timeoutMs: number;
258
+ constructor(requestId: AppServerRequestId, method: string, timeoutMs: number);
259
+ }
260
+ export declare class AppServerClientClosedError extends Error {
261
+ constructor(message?: string);
262
+ }
263
+ /**
264
+ * A single-process client for the newline-delimited Codex App Server protocol.
265
+ * Constructing the client spawns the process; call `initialize()` before using
266
+ * thread/turn helpers, or use `CodexAppServerClient.connect()`.
267
+ */
268
+ export declare class CodexAppServerClient {
269
+ private readonly child;
270
+ private readonly lines;
271
+ private readonly defaultRequestTimeoutMs;
272
+ private readonly shutdownTimeoutMs;
273
+ private readonly defaultInitializeParams;
274
+ private readonly pending;
275
+ private readonly notificationListeners;
276
+ private readonly errorListeners;
277
+ private readonly stderrListeners;
278
+ private readonly exitPromise;
279
+ private resolveExit;
280
+ private requestHandler;
281
+ private nextRequestId;
282
+ private exited;
283
+ private closing;
284
+ private closePromise;
285
+ private initializePromise;
286
+ private initializeResult;
287
+ constructor(options?: CodexAppServerClientOptions);
288
+ static connect(options?: CodexAppServerClientOptions): Promise<CodexAppServerClient>;
289
+ get pid(): number | undefined;
290
+ get isClosed(): boolean;
291
+ get isInitialized(): boolean;
292
+ get pendingRequestCount(): number;
293
+ onNotification(listener: AppServerNotificationListener): () => void;
294
+ onError(listener: AppServerErrorListener): () => void;
295
+ onStderr(listener: AppServerStderrListener): () => void;
296
+ setServerRequestHandler(handler: AppServerRequestHandler | null): void;
297
+ initialize(params?: AppServerInitializeParams): Promise<AppServerInitializeResponse>;
298
+ request<T = unknown>(method: string, params?: unknown, options?: AppServerRequestOptions): Promise<T>;
299
+ notify(method: string, params?: unknown): Promise<void>;
300
+ threadList(params?: AppServerThreadListParams, options?: AppServerRequestOptions): Promise<AppServerThreadListResponse>;
301
+ modelList(params?: AppServerModelListParams, options?: AppServerRequestOptions): Promise<AppServerModelListResponse>;
302
+ threadRead(params: AppServerThreadReadParams, options?: AppServerRequestOptions): Promise<AppServerThreadReadResponse>;
303
+ threadTurnsList(params: AppServerThreadTurnsListParams, options?: AppServerRequestOptions): Promise<AppServerThreadTurnsListResponse>;
304
+ threadItemsList(params: AppServerThreadItemsListParams, options?: AppServerRequestOptions): Promise<AppServerThreadItemsListResponse>;
305
+ threadStart(params?: AppServerThreadStartParams, options?: AppServerRequestOptions): Promise<AppServerThreadStartResponse>;
306
+ threadResume(params: AppServerThreadResumeParams, options?: AppServerRequestOptions): Promise<AppServerThreadResumeResponse>;
307
+ threadSetName(params: AppServerThreadSetNameParams, options?: AppServerRequestOptions): Promise<AppServerThreadSetNameResponse>;
308
+ threadDelete(params: AppServerThreadDeleteParams, options?: AppServerRequestOptions): Promise<AppServerThreadDeleteResponse>;
309
+ threadArchive(params: AppServerThreadArchiveParams, options?: AppServerRequestOptions): Promise<AppServerThreadArchiveResponse>;
310
+ turnStart(params: AppServerTurnStartParams, options?: AppServerRequestOptions): Promise<AppServerTurnStartResponse>;
311
+ turnSteer(params: AppServerTurnSteerParams, options?: AppServerRequestOptions): Promise<AppServerTurnSteerResponse>;
312
+ turnInterrupt(params: AppServerTurnInterruptParams, options?: AppServerRequestOptions): Promise<AppServerTurnInterruptResponse>;
313
+ close(): Promise<void>;
314
+ private initializedRequest;
315
+ private allocateRequestId;
316
+ private writeMessage;
317
+ private handleLine;
318
+ private handleResponse;
319
+ private parseRpcError;
320
+ private handleServerRequest;
321
+ private rejectPending;
322
+ private failPending;
323
+ private reportError;
324
+ private handleProcessClose;
325
+ private closeProcess;
326
+ private waitForExit;
327
+ }
328
+ //# sourceMappingURL=app-server-client.d.ts.map