zcode-acp-server 0.2.0 → 0.3.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.
- package/README.md +101 -15
- package/README.zh-CN.md +68 -13
- package/dist/bin/hub.d.ts +16 -0
- package/dist/bin/hub.d.ts.map +1 -0
- package/dist/bin/hub.js +41 -0
- package/dist/bin/hub.js.map +1 -0
- package/dist/handlers/account.d.ts +43 -0
- package/dist/handlers/account.d.ts.map +1 -0
- package/dist/handlers/account.js +59 -0
- package/dist/handlers/account.js.map +1 -0
- package/dist/handlers/io.d.ts +20 -1
- package/dist/handlers/io.d.ts.map +1 -1
- package/dist/handlers/io.js +57 -2
- package/dist/handlers/io.js.map +1 -1
- package/dist/handlers/replay.d.ts +79 -0
- package/dist/handlers/replay.d.ts.map +1 -0
- package/dist/handlers/replay.js +256 -0
- package/dist/handlers/replay.js.map +1 -0
- package/dist/handlers/session.d.ts.map +1 -1
- package/dist/handlers/session.js +83 -68
- package/dist/handlers/session.js.map +1 -1
- package/dist/handlers/slash.d.ts +23 -1
- package/dist/handlers/slash.d.ts.map +1 -1
- package/dist/handlers/slash.js +67 -6
- package/dist/handlers/slash.js.map +1 -1
- package/dist/index.js +47 -17
- package/dist/index.js.map +1 -1
- package/dist/remote/broadcast.d.ts +47 -0
- package/dist/remote/broadcast.d.ts.map +1 -0
- package/dist/remote/broadcast.js +121 -0
- package/dist/remote/broadcast.js.map +1 -0
- package/dist/remote/config.d.ts +32 -0
- package/dist/remote/config.d.ts.map +1 -0
- package/dist/remote/config.js +65 -0
- package/dist/remote/config.js.map +1 -0
- package/dist/remote/endpoint.d.ts +43 -0
- package/dist/remote/endpoint.d.ts.map +1 -0
- package/dist/remote/endpoint.js +222 -0
- package/dist/remote/endpoint.js.map +1 -0
- package/dist/remote/hub-server.d.ts +41 -0
- package/dist/remote/hub-server.d.ts.map +1 -0
- package/dist/remote/hub-server.js +346 -0
- package/dist/remote/hub-server.js.map +1 -0
- package/dist/server.d.ts +62 -7
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +99 -11
- package/dist/server.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +17 -1
- package/dist/utils.js.map +1 -1
- package/docs/ARCHITECTURE.md +47 -15
- package/docs/BACKLOG.md +3 -1
- package/docs/DEVELOPMENT.md +26 -0
- package/docs/PROTOCOL.md +67 -27
- package/docs/REMOTE-CLIENTS.md +264 -0
- package/docs/REPLAY-GUIDE.md +131 -0
- package/docs/TROUBLESHOOTING.md +51 -6
- package/docs/adr/0001-bridge-lifetime-follows-primary-client.md +14 -0
- package/docs/adr/0002-stateless-hub-over-per-bridge-acp-endpoints.md +23 -0
- package/docs/adr/0003-tail-replay-meta-and-cursor-pagination.md +40 -0
- package/docs/proposals/0001-tail-session-replay.md +136 -0
- package/docs/proposals/0002-plan-quota-usage.md +81 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -11,7 +11,9 @@ The server launches the ZCode headless app-server (`zcode app-server --stdio`) a
|
|
|
11
11
|
|
|
12
12
|
## Status
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
In active development. Core bridging, slash commands and ZCode extensions,
|
|
15
|
+
auto-compaction, remote access for mobile/web clients, and the quota APIs are
|
|
16
|
+
in place; see the project board for what's next.
|
|
15
17
|
|
|
16
18
|
## Requirements
|
|
17
19
|
|
|
@@ -79,14 +81,94 @@ automatically. Point `ZCODE_BIN` at the bundled `zcode.cjs`:
|
|
|
79
81
|
|
|
80
82
|
## Environment variables
|
|
81
83
|
|
|
82
|
-
| Variable
|
|
83
|
-
|
|
|
84
|
-
| `ZCODE_BIN`
|
|
85
|
-
| `ZCODE_NODE`
|
|
86
|
-
| `ZCODE_MODEL`
|
|
87
|
-
| `ZCODE_BASE_URL`
|
|
88
|
-
| `ZCODE_ACP_AUTO_COMPACT_THRESHOLD` | _(unset)_
|
|
89
|
-
| `ZCODE_ACP_DEBUG`
|
|
84
|
+
| Variable | Default | Purpose |
|
|
85
|
+
| ---------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `ZCODE_BIN` | `zcode` | Path to the ZCode CLI binary or its `.cjs` entry |
|
|
87
|
+
| `ZCODE_NODE` | _(discovered)_ | Explicit Node binary to run `ZCODE_BIN` with (must support `node:sqlite`) |
|
|
88
|
+
| `ZCODE_MODEL` | _(from config)_ | Override the active model id |
|
|
89
|
+
| `ZCODE_BASE_URL` | _(from config)_ | Override the provider base URL |
|
|
90
|
+
| `ZCODE_ACP_AUTO_COMPACT_THRESHOLD` | _(unset)_ | Absolute token count that triggers automatic context compaction. After each successful turn (`end_turn`), if `contextUsed >= threshold`, the server invokes `session/compact` to free up context before the next prompt. Set to `0` or leave unset to disable (default). Example: `240000` triggers compaction at 240K tokens. The compaction target itself is decided by the ZCode backend. |
|
|
91
|
+
| `ZCODE_ACP_DEBUG` | _(unset)_ | Set to `1` to enable verbose diagnostic logs (event flow, probe loops, status updates). Default is quiet — only warnings (backend pipe errors, command/permission failures, lock timeouts) are emitted. Enable this when diagnosing bridge issues; the logs appear in `Zed.log` prefixed with `[zcode-acp]`. |
|
|
92
|
+
| `ZCODE_ACP_REMOTE` | _(unset)_ | Set to `1` to enable [remote access](#remote-access) — serve the same sessions to additional ACP clients over WebSocket. |
|
|
93
|
+
| `ZCODE_ACP_REMOTE_TOKEN` | _(unset)_ | Auth token for remote access. **Mandatory** when `ZCODE_ACP_REMOTE=1`; remote stays disabled without it. |
|
|
94
|
+
| `ZCODE_ACP_HUB_PORT` | `8377` | Port of the machine-level `zcode-acp-hub`. Map exactly this one port in your tunnel. |
|
|
95
|
+
| `ZCODE_ACP_HUB_HOST` | `127.0.0.1` | Hub bind address. `0.0.0.0` exposes a token-only, unencrypted surface — only for a containerized tunnel agent on a private interface (see [Remote Access](#remote-access)). |
|
|
96
|
+
| `ZCODE_ACP_REMOTE_PORT` | `8378` | First loopback port for the bridge's ACP endpoint. Each bridge (each editor window) auto-increments to the next free port. |
|
|
97
|
+
|
|
98
|
+
## Remote Access
|
|
99
|
+
|
|
100
|
+
With `ZCODE_ACP_REMOTE=1` the bridge additionally accepts ACP connections over
|
|
101
|
+
WebSocket, so a phone or browser can watch and drive the **same sessions** as
|
|
102
|
+
your editor. Zed (or any ACP editor over stdio) remains the primary client and
|
|
103
|
+
owns the process: when the editor disconnects, the bridge — and every remote
|
|
104
|
+
attachment — exits with it.
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
phone / browser ──WS── tunnel ── hub (127.0.0.1:8377, single entry)
|
|
108
|
+
│ byte-level proxy
|
|
109
|
+
▼
|
|
110
|
+
bridge ACP endpoint (127.0.0.1:8378+n)
|
|
111
|
+
│ same AgentApp as stdio
|
|
112
|
+
Zed ──────── stdio ────────────────┘
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Enable it per-agent in Zed's settings (Zed merges these into the agent's
|
|
116
|
+
environment):
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
"agents": {
|
|
120
|
+
"ZCode": {
|
|
121
|
+
"command": "zcode-acp-server",
|
|
122
|
+
"env": {
|
|
123
|
+
"ZCODE_ACP_REMOTE": "1",
|
|
124
|
+
"ZCODE_ACP_REMOTE_TOKEN": "<a-long-random-secret>"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Hub.** The first bridge with remote enabled spawns `zcode-acp-hub` as a
|
|
131
|
+
detached, machine-level singleton on `ZCODE_ACP_HUB_PORT` (it can also be run
|
|
132
|
+
manually). It does three things only: token auth, instance discovery, and
|
|
133
|
+
byte-level WebSocket proxying — no session state, no ACP semantics. It exits
|
|
134
|
+
after ~10 idle minutes and is re-spawned on demand. Each bridge registers
|
|
135
|
+
every 10s as a heartbeat and drops out of discovery ~30s after it stops.
|
|
136
|
+
|
|
137
|
+
**Discovery API** (for client authors; fields are additive-only):
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
GET /api/instances → [{"id","port","pid","startedAt","workspace",
|
|
141
|
+
"sessions":[{"sessionId","title?","updatedAt"}]}]
|
|
142
|
+
GET /api/instances?probe=1 → same list, but unreachable bridges are pruned first
|
|
143
|
+
WS /acp?instance=<id> → proxied to that bridge's endpoint
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Auth is `Authorization: Bearer <token>` or `?token=` (browsers cannot set WS
|
|
147
|
+
headers); `/api/*` sends `Access-Control-Allow-Origin: *` — the token is the
|
|
148
|
+
security boundary. A proxied connection stays bound to one instance; switching
|
|
149
|
+
instances means reconnecting. Remote clients can also pull plan quota via the
|
|
150
|
+
non-standard `account/usage_stats` ACP method (no session required).
|
|
151
|
+
|
|
152
|
+
Building a remote client — web, mobile, or CLI? The full integration contract
|
|
153
|
+
(endpoints, framing, lifecycle timings, failure recovery, platform notes)
|
|
154
|
+
lives in [docs/REMOTE-CLIENTS.md](docs/REMOTE-CLIENTS.md).
|
|
155
|
+
|
|
156
|
+
**Semantics.** All agent notifications are broadcast to every client.
|
|
157
|
+
Permission / elicitation requests go to every client and the **first answer
|
|
158
|
+
wins**; losing clients receive `$/cancel_request` so their dialogs close.
|
|
159
|
+
Concurrent prompts for one session are serialized exactly as they are for a
|
|
160
|
+
single editor. Capabilities declared by any client are OR-merged.
|
|
161
|
+
|
|
162
|
+
**Tunnels.** Designed for one-port tunnels (Cloudflare Tunnel, frp): map the
|
|
163
|
+
hub port only. frp's `tcp` mode passes WebSocket as-is; Cloudflare Tunnel
|
|
164
|
+
drops idle WebSocket connections, so the hub sends 30s keepalive pings on both
|
|
165
|
+
legs. The bridge endpoint itself is loopback-only and never exposed.
|
|
166
|
+
|
|
167
|
+
**Binding beyond loopback.** The hub speaks plain HTTP/WS — the token travels
|
|
168
|
+
and authorizes in cleartext, so `ZCODE_ACP_HUB_HOST=0.0.0.0` (needed only when
|
|
169
|
+
the tunnel agent runs in its own container) is exactly as safe as the network
|
|
170
|
+
it lands on. Keep the bind loopback unless that interface is private to the
|
|
171
|
+
tunnel agent, and put TLS in front before mapping it anywhere untrusted.
|
|
90
172
|
|
|
91
173
|
## Standalone Quota CLI
|
|
92
174
|
|
|
@@ -201,6 +283,8 @@ The server is organised in layers that mirror the ACP protocol:
|
|
|
201
283
|
- `interaction/` — bridge ZCode `interaction/*` server requests to ACP, preferring `elicitation/create` and falling back to `session/request_permission` (tool auth, ExitPlanMode, AskUserQuestion)
|
|
202
284
|
- `handlers/` — ACP method handlers (`session/new`, `session/prompt`, ...) and the turn engine
|
|
203
285
|
- `config/` — model / mode / thought-level configOptions and runtime model switching
|
|
286
|
+
- `remote/` — opt-in remote access: loopback ACP endpoint, multi-client broadcast, `zcode-acp-hub` registration
|
|
287
|
+
- `quota/` — GLM Coding Plan / Opencode Go usage API client (`/quota` command, `zcode-quota` bin)
|
|
204
288
|
- `server.ts` — shared state and handler registration
|
|
205
289
|
- `index.ts` — stdio wiring via the ACP SDK
|
|
206
290
|
|
|
@@ -218,6 +302,8 @@ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full architecture docum
|
|
|
218
302
|
|
|
219
303
|
- [Architecture](docs/ARCHITECTURE.md) — event stream, dual-path deduplication, module responsibilities
|
|
220
304
|
- [Protocol](docs/PROTOCOL.md) — ZCode JSON-RPC protocol details
|
|
305
|
+
- [Remote Clients](docs/REMOTE-CLIENTS.md) — remote access integration contract (discovery, transport, recovery)
|
|
306
|
+
- [Replay Guide](docs/REPLAY-GUIDE.md) — building a client UI on tail replay
|
|
221
307
|
- [Development](docs/DEVELOPMENT.md) — local development, debugging, adding extension methods
|
|
222
308
|
- [Troubleshooting](docs/TROUBLESHOOTING.md) — common-issue troubleshooting
|
|
223
309
|
|
|
@@ -247,12 +333,12 @@ ZCode backend over **local pipes**; that data reaches the GLM cloud API only
|
|
|
247
333
|
because the ZCode backend itself sends it there for inference — this server
|
|
248
334
|
adds no extra destinations.
|
|
249
335
|
|
|
250
|
-
| Concern
|
|
251
|
-
|
|
|
252
|
-
| Network
|
|
253
|
-
| Credentials | API key read from `~/.zcode/v2/config.json` to authenticate the ZCode subprocess and quota request. Never logged, never written elsewhere. OAuth handled entirely by the ZCode subprocess
|
|
254
|
-
| Disk
|
|
255
|
-
| Logging
|
|
336
|
+
| Concern | What & why |
|
|
337
|
+
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
338
|
+
| Network | Only one outbound request in the whole codebase: the quota GET (`open.bigmodel.cn` / `api.z.ai`), carrying just your API key — needed to fetch your usage numbers, sends no user content |
|
|
339
|
+
| Credentials | API key read from `~/.zcode/v2/config.json` to authenticate the ZCode subprocess and quota request. Never logged, never written elsewhere. OAuth handled entirely by the ZCode subprocess |
|
|
340
|
+
| Disk | No new files created. Writes only to the existing `~/.zcode/v2/tasks-index.sqlite` — this **syncs sessions to the ZCode app** so they appear in its history list and full-text search (stores the session title and first prompt) |
|
|
341
|
+
| Logging | Diagnostics to stderr for troubleshooting bridge issues. Even with `ZCODE_ACP_DEBUG=1`, no prompts/code/keys are ever logged |
|
|
256
342
|
|
|
257
343
|
## License
|
|
258
344
|
|
package/README.zh-CN.md
CHANGED
|
@@ -78,14 +78,69 @@ ZCode CLI 内置于桌面应用中,默认不会加到 `PATH`。用 `ZCODE_BIN`
|
|
|
78
78
|
|
|
79
79
|
## 环境变量
|
|
80
80
|
|
|
81
|
-
| 变量
|
|
82
|
-
|
|
|
83
|
-
| `ZCODE_BIN`
|
|
84
|
-
| `ZCODE_NODE`
|
|
85
|
-
| `ZCODE_MODEL`
|
|
86
|
-
| `ZCODE_BASE_URL`
|
|
87
|
-
| `ZCODE_ACP_AUTO_COMPACT_THRESHOLD` | _(未设置)
|
|
88
|
-
| `ZCODE_ACP_DEBUG`
|
|
81
|
+
| 变量 | 默认值 | 用途 |
|
|
82
|
+
| ---------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
83
|
+
| `ZCODE_BIN` | `zcode` | ZCode CLI 二进制文件路径或其 `.cjs` 入口 |
|
|
84
|
+
| `ZCODE_NODE` | _(自动发现)_ | 显式指定运行 `ZCODE_BIN` 的 Node 二进制(必须支持 `node:sqlite`) |
|
|
85
|
+
| `ZCODE_MODEL` | _(来自 config) | 覆盖当前使用的模型 id |
|
|
86
|
+
| `ZCODE_BASE_URL` | _(来自 config) | 覆盖 provider 的 base URL |
|
|
87
|
+
| `ZCODE_ACP_AUTO_COMPACT_THRESHOLD` | _(未设置) | 触发自动压缩的绝对 token 阈值。每次回合成功完成后(`end_turn`),若 `contextUsed >= 阈值`,服务端会自动调用 `session/compact` 压缩上下文,为下一个 prompt 腾出空间。设为 `0` 或不设置则禁用(默认)。例如 `240000` 表示上下文达 24 万 token 时触发压缩。压缩目标由 ZCode 后端决定。 |
|
|
88
|
+
| `ZCODE_ACP_DEBUG` | _(未设置) | 设为 `1` 可开启详细诊断日志(事件流、探测循环、状态更新)。默认安静——只输出警告类日志(后端管道错误、命令/权限失败、锁等待超时)。诊断桥接问题时开启;日志出现在 `Zed.log` 中,前缀为 `[zcode-acp]`。 |
|
|
89
|
+
| `ZCODE_ACP_REMOTE` | _(未设置)_ | 设为 `1` 启用[远程访问](#远程访问)——通过 WebSocket 向更多 ACP 客户端提供相同会话。 |
|
|
90
|
+
| `ZCODE_ACP_REMOTE_TOKEN` | _(未设置)_ | 远程访问的鉴权 token。启用 `ZCODE_ACP_REMOTE=1` 时**必填**;缺失则远程保持禁用。 |
|
|
91
|
+
| `ZCODE_ACP_HUB_PORT` | `8377` | 机器级 `zcode-acp-hub` 的端口。隧道只映射这一个端口。 |
|
|
92
|
+
| `ZCODE_ACP_HUB_HOST` | `127.0.0.1` | hub 绑定地址。`0.0.0.0` 会暴露仅 token 保护的明文面——只用于容器化隧道 agent 所在的私网接口(见[远程访问](#远程访问))。 |
|
|
93
|
+
| `ZCODE_ACP_REMOTE_PORT` | `8378` | bridge ACP 端点的起始回环端口。每个 bridge(每个编辑器窗口)自动递增取下一个空闲端口。 |
|
|
94
|
+
|
|
95
|
+
## 远程访问
|
|
96
|
+
|
|
97
|
+
设置 `ZCODE_ACP_REMOTE=1` 后,bridge 会额外通过 WebSocket 接收 ACP 连接,
|
|
98
|
+
手机或浏览器即可观看并驱动与编辑器**相同的会话**。Zed(或任何 stdio ACP
|
|
99
|
+
编辑器)仍是主客户端并拥有进程:编辑器断开时,bridge 连同所有远程连接
|
|
100
|
+
一起退出。
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
phone / browser ──WS── 隧道 ── hub (127.0.0.1:8377, 唯一入口)
|
|
104
|
+
│ 字节级代理
|
|
105
|
+
▼
|
|
106
|
+
bridge ACP 端点 (127.0.0.1:8378+n)
|
|
107
|
+
│ 与 stdio 同一个 AgentApp
|
|
108
|
+
Zed ──────── stdio ────────────┘
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
启用方式:在上文「配置 Zed」的 `env` 里追加(Zed 会把这些合并进 agent
|
|
112
|
+
的环境变量):
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
"ZCODE_ACP_REMOTE": "1",
|
|
116
|
+
"ZCODE_ACP_REMOTE_TOKEN": "<一段足够长的随机密钥>"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
相关环境变量(详见上文表格):`ZCODE_ACP_REMOTE`(开关)、
|
|
120
|
+
`ZCODE_ACP_REMOTE_TOKEN`(必填 token)、`ZCODE_ACP_HUB_PORT`(hub 端口,
|
|
121
|
+
默认 8377)、`ZCODE_ACP_HUB_HOST`(hub 绑定地址)、
|
|
122
|
+
`ZCODE_ACP_REMOTE_PORT`(bridge 端点起始端口,默认 8378)。
|
|
123
|
+
|
|
124
|
+
**Hub。** 第一个启用远程的 bridge 会以 detached 方式拉起机器级单例
|
|
125
|
+
`zcode-acp-hub`(监听 `ZCODE_ACP_HUB_PORT`,也可手动运行)。它只做三件事:
|
|
126
|
+
token 鉴权、实例发现、字节级 WebSocket 代理——不保存会话状态、不解析 ACP。
|
|
127
|
+
空闲约 10 分钟后退出,需要时再被拉起。每个 bridge 每 10 秒注册一次作为
|
|
128
|
+
心跳,心跳停止约 30 秒后从发现列表移除;客户端刷新时也可调用
|
|
129
|
+
`GET /api/instances?probe=1` 主动探测,立即清理不可达的实例。
|
|
130
|
+
|
|
131
|
+
**语义。** 所有 agent 通知广播给每个已连接客户端;权限 / elicitation 请求
|
|
132
|
+
发给所有客户端,**先应答者生效**,其余客户端收到 `$/cancel_request` 关闭
|
|
133
|
+
对话框。同一会话的并发 prompt 与单编辑器一样串行化。任一客户端声明的能力
|
|
134
|
+
按 OR 合并。
|
|
135
|
+
|
|
136
|
+
**隧道。** 面向单端口隧道(Cloudflare Tunnel、frp)设计:只映射 hub 端口。
|
|
137
|
+
frp 的 `tcp` 模式原样透传 WebSocket;Cloudflare Tunnel 会断开空闲连接,
|
|
138
|
+
hub 因此在两段链路上每 30 秒发送 keepalive ping。bridge 端点本身只监听
|
|
139
|
+
回环地址,永不直接暴露。
|
|
140
|
+
|
|
141
|
+
要构建远程客户端(Web、移动端或 CLI)?完整的集成契约(端点、帧格式、
|
|
142
|
+
生命周期时序、故障恢复、平台注意事项)见
|
|
143
|
+
[docs/REMOTE-CLIENTS.md](docs/REMOTE-CLIENTS.md)。
|
|
89
144
|
|
|
90
145
|
## 独立配额查询 CLI(zcode-quota)
|
|
91
146
|
|
|
@@ -227,12 +282,12 @@ commit 约定和 PR 检查清单。重要变更记录在 [CHANGELOG.md](CHANGELO
|
|
|
227
282
|
你的提示词、代码、文件内容通过**本地管道**在编辑器与 ZCode 后端之间中转;这些数据会到达
|
|
228
283
|
GLM 云端 API,仅因 ZCode 后端本身为推理而发送——本服务端不增加任何额外去向。
|
|
229
284
|
|
|
230
|
-
| 方面 | 做什么 & 为什么
|
|
231
|
-
| ---- |
|
|
232
|
-
| 网络 | 全代码库仅一处对外请求:配额 GET(`open.bigmodel.cn` / `api.z.ai`),只带 API key —— 为查询用量数字,不发送用户内容
|
|
233
|
-
| 凭据 | API key 从 `~/.zcode/v2/config.json` 读取,用于认证 ZCode 子进程和配额请求。从不记录日志、从不写入别处。OAuth 完全由 ZCode 子进程处理
|
|
285
|
+
| 方面 | 做什么 & 为什么 |
|
|
286
|
+
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
287
|
+
| 网络 | 全代码库仅一处对外请求:配额 GET(`open.bigmodel.cn` / `api.z.ai`),只带 API key —— 为查询用量数字,不发送用户内容 |
|
|
288
|
+
| 凭据 | API key 从 `~/.zcode/v2/config.json` 读取,用于认证 ZCode 子进程和配额请求。从不记录日志、从不写入别处。OAuth 完全由 ZCode 子进程处理 |
|
|
234
289
|
| 磁盘 | 不创建任何新文件。只写入已存在的 `~/.zcode/v2/tasks-index.sqlite` —— 这是**将会话同步到 ZCode App**,使其出现在历史列表和全文搜索中(存会话标题和首条提示词) |
|
|
235
|
-
| 日志 | 诊断信息输出到 stderr,用于排查桥接问题。即使开启 `ZCODE_ACP_DEBUG=1`,也绝不记录提示词/代码/密钥
|
|
290
|
+
| 日志 | 诊断信息输出到 stderr,用于排查桥接问题。即使开启 `ZCODE_ACP_DEBUG=1`,也绝不记录提示词/代码/密钥 |
|
|
236
291
|
|
|
237
292
|
## 许可证
|
|
238
293
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone zcode-acp-hub daemon entry.
|
|
4
|
+
*
|
|
5
|
+
* Usually spawned detached by the first bridge that enables remote access
|
|
6
|
+
* (see src/remote/endpoint.ts); running it manually is also fine, e.g. under
|
|
7
|
+
* launchd/systemd or directly for debugging:
|
|
8
|
+
*
|
|
9
|
+
* ZCODE_ACP_REMOTE_TOKEN=<secret> zcode-acp-hub
|
|
10
|
+
*
|
|
11
|
+
* Refuses to start without ZCODE_ACP_REMOTE_TOKEN — the hub is the only public
|
|
12
|
+
* entry point and never runs unauthenticated. Exits 0 on EADDRINUSE: another
|
|
13
|
+
* hub already owns the port, which is the desired machine-singleton behaviour.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=hub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub.d.ts","sourceRoot":"","sources":["../../src/bin/hub.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG"}
|
package/dist/bin/hub.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone zcode-acp-hub daemon entry.
|
|
4
|
+
*
|
|
5
|
+
* Usually spawned detached by the first bridge that enables remote access
|
|
6
|
+
* (see src/remote/endpoint.ts); running it manually is also fine, e.g. under
|
|
7
|
+
* launchd/systemd or directly for debugging:
|
|
8
|
+
*
|
|
9
|
+
* ZCODE_ACP_REMOTE_TOKEN=<secret> zcode-acp-hub
|
|
10
|
+
*
|
|
11
|
+
* Refuses to start without ZCODE_ACP_REMOTE_TOKEN — the hub is the only public
|
|
12
|
+
* entry point and never runs unauthenticated. Exits 0 on EADDRINUSE: another
|
|
13
|
+
* hub already owns the port, which is the desired machine-singleton behaviour.
|
|
14
|
+
*/
|
|
15
|
+
import process from "node:process";
|
|
16
|
+
import { parseHubConfig } from "../remote/config.js";
|
|
17
|
+
import { startHub } from "../remote/hub-server.js";
|
|
18
|
+
import { warn } from "../utils.js";
|
|
19
|
+
async function main() {
|
|
20
|
+
const config = parseHubConfig();
|
|
21
|
+
if (!config)
|
|
22
|
+
process.exit(1);
|
|
23
|
+
const hub = await startHub({
|
|
24
|
+
port: config.hubPort,
|
|
25
|
+
host: config.hubHost,
|
|
26
|
+
token: config.token,
|
|
27
|
+
onIdleExit: () => process.exit(0),
|
|
28
|
+
});
|
|
29
|
+
process.on("SIGTERM", () => void hub.close().then(() => process.exit(0)));
|
|
30
|
+
process.on("SIGINT", () => void hub.close().then(() => process.exit(0)));
|
|
31
|
+
}
|
|
32
|
+
main().catch((err) => {
|
|
33
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
34
|
+
if (err?.code === "EADDRINUSE") {
|
|
35
|
+
// Another hub already listens on this port — nothing to do.
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
warn(`hub: fatal: ${message}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=hub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub.js","sourceRoot":"","sources":["../../src/bin/hub.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG;AAEH,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC,OAAO;QACpB,IAAI,EAAE,MAAM,CAAC,OAAO;QACpB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;KAClC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,IAAK,GAA6B,EAAE,IAAI,KAAK,YAAY,EAAE,CAAC;QAC1D,4DAA4D;QAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,CAAC,eAAe,OAAO,EAAE,CAAC,CAAC;IAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account-level usage stats — Proposal 0002 (`account/usage_stats`).
|
|
3
|
+
*
|
|
4
|
+
* Exposes the combined dual-provider quota behind the `zcode-quota` CLI
|
|
5
|
+
* (GLM Coding Plan + Opencode Go) to remote clients as a pull-only ACP
|
|
6
|
+
* method, callable any time after `initialize` (no session required — quota
|
|
7
|
+
* is account-level, so it fits no `session/update` kind).
|
|
8
|
+
*
|
|
9
|
+
* The response mirrors the CLI card's data model so clients can reproduce it
|
|
10
|
+
* exactly: one GLM section (plan level + per-window items with per-model
|
|
11
|
+
* details) and one Opencode Go section (rolling/weekly/monthly windows, the
|
|
12
|
+
* relative reset countdown converted to an absolute timestamp). Provider
|
|
13
|
+
* failures are reported per-section as `kind` strings rather than throwing —
|
|
14
|
+
* the client renders the same status line the CLI would (a `not_configured`
|
|
15
|
+
* Go section is simply omitted, matching the CLI).
|
|
16
|
+
*/
|
|
17
|
+
import type { GoQueryResult, GoWindowKey } from "../quota/opencode-go/types.js";
|
|
18
|
+
import type { QuotaItem, QuotaResult } from "../quota/types.js";
|
|
19
|
+
/** GLM section — `items` present only on success. */
|
|
20
|
+
export interface GlmUsageStats {
|
|
21
|
+
kind: QuotaResult["kind"];
|
|
22
|
+
level?: string;
|
|
23
|
+
items?: QuotaItem[];
|
|
24
|
+
}
|
|
25
|
+
/** One Opencode Go window with the reset countdown resolved to epoch ms. */
|
|
26
|
+
export interface GoWindowEntry {
|
|
27
|
+
key: GoWindowKey;
|
|
28
|
+
label: string;
|
|
29
|
+
usagePercent: number;
|
|
30
|
+
resetsAt: number;
|
|
31
|
+
}
|
|
32
|
+
/** Opencode Go section — `windows` present only on success. */
|
|
33
|
+
export interface GoUsageStats {
|
|
34
|
+
kind: GoQueryResult["kind"];
|
|
35
|
+
windows?: GoWindowEntry[];
|
|
36
|
+
}
|
|
37
|
+
export interface UsageStatsResult {
|
|
38
|
+
glm: GlmUsageStats;
|
|
39
|
+
opencode: GoUsageStats;
|
|
40
|
+
}
|
|
41
|
+
/** `account/usage_stats` handler — both providers, queried in parallel. */
|
|
42
|
+
export declare function accountUsageStats(): Promise<UsageStatsResult>;
|
|
43
|
+
//# sourceMappingURL=account.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../src/handlers/account.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhE,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,WAAW,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,+DAA+D;AAC/D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,aAAa,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAsCD,2EAA2E;AAC3E,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAGnE"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account-level usage stats — Proposal 0002 (`account/usage_stats`).
|
|
3
|
+
*
|
|
4
|
+
* Exposes the combined dual-provider quota behind the `zcode-quota` CLI
|
|
5
|
+
* (GLM Coding Plan + Opencode Go) to remote clients as a pull-only ACP
|
|
6
|
+
* method, callable any time after `initialize` (no session required — quota
|
|
7
|
+
* is account-level, so it fits no `session/update` kind).
|
|
8
|
+
*
|
|
9
|
+
* The response mirrors the CLI card's data model so clients can reproduce it
|
|
10
|
+
* exactly: one GLM section (plan level + per-window items with per-model
|
|
11
|
+
* details) and one Opencode Go section (rolling/weekly/monthly windows, the
|
|
12
|
+
* relative reset countdown converted to an absolute timestamp). Provider
|
|
13
|
+
* failures are reported per-section as `kind` strings rather than throwing —
|
|
14
|
+
* the client renders the same status line the CLI would (a `not_configured`
|
|
15
|
+
* Go section is simply omitted, matching the CLI).
|
|
16
|
+
*/
|
|
17
|
+
import { queryCombined } from "../quota/combined.js";
|
|
18
|
+
/** Window labels matching the CLI's card (`5h` / `Week` / `Month`). */
|
|
19
|
+
const GO_WINDOW_LABELS = {
|
|
20
|
+
rolling: "5h",
|
|
21
|
+
weekly: "Week",
|
|
22
|
+
monthly: "Month",
|
|
23
|
+
};
|
|
24
|
+
/** GLM items pass through verbatim — the client renders the CLI layout. */
|
|
25
|
+
function toGlmStats(result) {
|
|
26
|
+
if (result.kind !== "success")
|
|
27
|
+
return { kind: result.kind };
|
|
28
|
+
return { kind: "success", level: result.level, items: result.items };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Go windows with the same absolute-reset math the CLI formatter uses:
|
|
32
|
+
* subtract the elapsed time since the fetch snapshot from `resetInSec`.
|
|
33
|
+
*/
|
|
34
|
+
function toGoStats(result, now = Date.now()) {
|
|
35
|
+
if (result.kind !== "success")
|
|
36
|
+
return { kind: result.kind };
|
|
37
|
+
const elapsedSec = Math.max(0, (now - result.fetchedAt) / 1000);
|
|
38
|
+
const windows = ["rolling", "weekly", "monthly"].flatMap((key) => {
|
|
39
|
+
const w = result[key];
|
|
40
|
+
if (!w)
|
|
41
|
+
return [];
|
|
42
|
+
const remainingSec = Math.max(0, w.resetInSec - elapsedSec);
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
key,
|
|
46
|
+
label: GO_WINDOW_LABELS[key],
|
|
47
|
+
usagePercent: w.usagePercent,
|
|
48
|
+
resetsAt: result.fetchedAt + remainingSec * 1000,
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
});
|
|
52
|
+
return { kind: "success", windows };
|
|
53
|
+
}
|
|
54
|
+
/** `account/usage_stats` handler — both providers, queried in parallel. */
|
|
55
|
+
export async function accountUsageStats() {
|
|
56
|
+
const { glm, go } = await queryCombined("all");
|
|
57
|
+
return { glm: toGlmStats(glm), opencode: toGoStats(go) };
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../../src/handlers/account.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA8BrD,uEAAuE;AACvE,MAAM,gBAAgB,GAAgC;IACpD,OAAO,EAAE,IAAI;IACb,MAAM,EAAE,MAAM;IACd,OAAO,EAAE,OAAO;CACjB,CAAC;AAEF,2EAA2E;AAC3E,SAAS,UAAU,CAAC,MAAmB;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IAC5D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;AACvE,CAAC;AAED;;;GAGG;AACH,SAAS,SAAS,CAAC,MAAqB,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IACxD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IAC5D,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;IAChE,MAAM,OAAO,GAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAW,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QAC1E,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,CAAC;YAAE,OAAO,EAAE,CAAC;QAClB,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC;QAC5D,OAAO;YACL;gBACE,GAAG;gBACH,KAAK,EAAE,gBAAgB,CAAC,GAAG,CAAC;gBAC5B,YAAY,EAAE,CAAC,CAAC,YAAY;gBAC5B,QAAQ,EAAE,MAAM,CAAC,SAAS,GAAG,YAAY,GAAG,IAAI;aACjD;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AACtC,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;IAC/C,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;AAC3D,CAAC"}
|
package/dist/handlers/io.d.ts
CHANGED
|
@@ -8,8 +8,27 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type * as acp from "@agentclientprotocol/sdk";
|
|
10
10
|
import type { ZcodeAcpServer } from "../server.js";
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Send a `session/update` notification to the client, serialized through the
|
|
13
|
+
* per-session replay guard (see `enqueueSessionSend`).
|
|
14
|
+
*/
|
|
12
15
|
export declare function sendSessionUpdate(cx: acp.AgentContext, sessionId: string, update: acp.SessionUpdate): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Run one client-notification send through the per-session replay guard:
|
|
18
|
+
* while a replay batch (`withReplayBatch`) is in flight for this session, the
|
|
19
|
+
* send queues behind it so a batch is never interleaved with live updates —
|
|
20
|
+
* this applies to background-task emissions too, not just handler dispatch.
|
|
21
|
+
* Sessions that never replay take the lock-free fast path.
|
|
22
|
+
*/
|
|
23
|
+
export declare function enqueueSessionSend(sessionId: string, send: () => Promise<void>): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Run one replay batch for a session under exclusive use of its guard.
|
|
26
|
+
* While the batch runs, `sendSessionUpdate` calls for the SAME session (live
|
|
27
|
+
* turn dispatch) queue behind it; the batch's own sends go through
|
|
28
|
+
* `replayMessages`, which notifies directly — that bypass is what makes the
|
|
29
|
+
* batch atomic without a re-entrant lock. Concurrent batches serialize.
|
|
30
|
+
*/
|
|
31
|
+
export declare function withReplayBatch<T>(sessionId: string, fn: () => Promise<T>): Promise<T>;
|
|
13
32
|
/** Send an `agent_message_chunk` text notification. */
|
|
14
33
|
export declare function sendTextChunk(cx: acp.AgentContext, sessionId: string, text: string, messageId: string): Promise<void>;
|
|
15
34
|
/** Shape of a slash command entry (matches ACP's AvailableCommand). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../../src/handlers/io.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,KAAK,GAAG,MAAM,0BAA0B,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnD
|
|
1
|
+
{"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../../src/handlers/io.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,KAAK,GAAG,MAAM,0BAA0B,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,GAAG,CAAC,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,GAAG,CAAC,aAAa,GACxB,OAAO,CAAC,IAAI,CAAC,CAEf;AAkBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9F;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAuB5F;AAED,uDAAuD;AACvD,wBAAgB,aAAa,CAC3B,EAAE,EAAE,GAAG,CAAC,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,uEAAuE;AACvE,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1B;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,GAAG,CAAC,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,aAAa,CAAC,iBAAiB,CAAC,GACzC,OAAO,CAAC,IAAI,CAAC,CAYf;AAWD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAC3C,EAAE,EAAE,GAAG,CAAC,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,aAAa,CAAC,iBAAiB,CAAC,GACzC,IAAI,CAkCN;AAED,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAE/D;AAED,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,cAAc,CAAC;CACxB"}
|
package/dist/handlers/io.js
CHANGED
|
@@ -8,9 +8,64 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { RequestError } from "@agentclientprotocol/sdk";
|
|
10
10
|
import { warn } from "../utils.js";
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Send a `session/update` notification to the client, serialized through the
|
|
13
|
+
* per-session replay guard (see `enqueueSessionSend`).
|
|
14
|
+
*/
|
|
12
15
|
export function sendSessionUpdate(cx, sessionId, update) {
|
|
13
|
-
return cx.notify("session/update", { sessionId, update });
|
|
16
|
+
return enqueueSessionSend(sessionId, () => cx.notify("session/update", { sessionId, update }));
|
|
17
|
+
}
|
|
18
|
+
const replayGuards = new Map();
|
|
19
|
+
/** Append a job to a guard's chain; a rejected job never breaks later sends. */
|
|
20
|
+
function enqueue(guard, job) {
|
|
21
|
+
const run = guard.tail.then(job);
|
|
22
|
+
guard.tail = run.then(() => undefined, () => undefined);
|
|
23
|
+
return run;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Run one client-notification send through the per-session replay guard:
|
|
27
|
+
* while a replay batch (`withReplayBatch`) is in flight for this session, the
|
|
28
|
+
* send queues behind it so a batch is never interleaved with live updates —
|
|
29
|
+
* this applies to background-task emissions too, not just handler dispatch.
|
|
30
|
+
* Sessions that never replay take the lock-free fast path.
|
|
31
|
+
*/
|
|
32
|
+
export function enqueueSessionSend(sessionId, send) {
|
|
33
|
+
const guard = replayGuards.get(sessionId);
|
|
34
|
+
if (!guard)
|
|
35
|
+
return send();
|
|
36
|
+
return enqueue(guard, send);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Run one replay batch for a session under exclusive use of its guard.
|
|
40
|
+
* While the batch runs, `sendSessionUpdate` calls for the SAME session (live
|
|
41
|
+
* turn dispatch) queue behind it; the batch's own sends go through
|
|
42
|
+
* `replayMessages`, which notifies directly — that bypass is what makes the
|
|
43
|
+
* batch atomic without a re-entrant lock. Concurrent batches serialize.
|
|
44
|
+
*/
|
|
45
|
+
export async function withReplayBatch(sessionId, fn) {
|
|
46
|
+
let guard = replayGuards.get(sessionId);
|
|
47
|
+
if (!guard) {
|
|
48
|
+
guard = { tail: Promise.resolve() };
|
|
49
|
+
replayGuards.set(sessionId, guard);
|
|
50
|
+
}
|
|
51
|
+
// Take ownership: later senders (including other batches) chain behind us.
|
|
52
|
+
const prev = guard.tail;
|
|
53
|
+
let release;
|
|
54
|
+
const held = new Promise((resolve) => (release = resolve));
|
|
55
|
+
guard.tail = held;
|
|
56
|
+
await prev;
|
|
57
|
+
try {
|
|
58
|
+
return await fn();
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
release();
|
|
62
|
+
// Drop the entry when nobody chained behind us. enqueue and
|
|
63
|
+
// withReplayBatch are synchronous up to their first await, so a concurrent
|
|
64
|
+
// taker always swaps guard.tail before this check runs — no race window.
|
|
65
|
+
if (replayGuards.get(sessionId) === guard && guard.tail === held) {
|
|
66
|
+
replayGuards.delete(sessionId);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
14
69
|
}
|
|
15
70
|
/** Send an `agent_message_chunk` text notification. */
|
|
16
71
|
export function sendTextChunk(cx, sessionId, text, messageId) {
|
package/dist/handlers/io.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"io.js","sourceRoot":"","sources":["../../src/handlers/io.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC
|
|
1
|
+
{"version":3,"file":"io.js","sourceRoot":"","sources":["../../src/handlers/io.ts"],"names":[],"mappings":"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"}
|