@baize-ai/core 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.
Files changed (44) hide show
  1. package/.dockerignore +8 -18
  2. package/CHANGELOG.md +24 -1
  3. package/Dockerfile +66 -12
  4. package/README.md +85 -6
  5. package/README.zh-CN.md +63 -3
  6. package/assets/logo.png +0 -0
  7. package/cli/baize.js +6 -0
  8. package/cli/commands/a2a.js +124 -0
  9. package/cli/commands/add.js +126 -86
  10. package/cli/commands/component.js +27 -4
  11. package/cli/commands/doctor.js +2 -2
  12. package/cli/commands/init.js +28 -0
  13. package/cli/commands/self-uninstall.js +1 -1
  14. package/cli/lib/__tests__/instruction-split.test.js +1 -1
  15. package/cli/lib/a2a.js +235 -0
  16. package/cli/lib/lock.js +3 -3
  17. package/cli/lib/self-upgrade.js +1 -1
  18. package/docker-compose.yml +1 -1
  19. package/docs/docker.md +18 -4
  20. package/docs/release.md +150 -0
  21. package/package.json +1 -1
  22. package/scripts/docker-publish.sh +70 -0
  23. package/scripts/install.sh +4 -4
  24. package/scripts/pack-release.sh +361 -0
  25. package/skills/comm-bridge/package.json +7 -3
  26. package/skills/comm-bridge/scripts/c4-receive.js +23 -2
  27. package/skills/scheduler/package.json +2 -2
  28. package/skills/web-console/SKILL.md +34 -0
  29. package/skills/web-console/package.json +2 -2
  30. package/skills/web-console/public/app.js +967 -4
  31. package/skills/web-console/public/index.html +141 -0
  32. package/skills/web-console/public/styles.css +136 -0
  33. package/skills/web-console/scripts/a2a-admin.js +533 -0
  34. package/skills/web-console/scripts/channel-admin.js +99 -16
  35. package/skills/web-console/scripts/server.js +395 -1
  36. package/skills/web-console/scripts/skill-catalog.js +179 -0
  37. package/templates/claude-system.md +22 -0
  38. package/templates/pm2/ecosystem.config.cjs +4 -1
  39. package/test/a2a-cli.test.js +180 -0
  40. package/test/agent-card-api.test.js +261 -0
  41. package/test/channel-admin.test.js +87 -0
  42. package/test/component-lock.test.js +216 -0
  43. package/test/skill-catalog.test.js +118 -0
  44. package/test/web-console-routes.test.js +512 -1
@@ -0,0 +1,361 @@
1
+ #!/usr/bin/env bash
2
+ # ────────────────────────────────────────────────────────────────────────────
3
+ # pack-release.sh — Baize A2A 套件私有交付打包脚本(D25 U4)
4
+ #
5
+ # 定位:baize-ai 根目录不是 git 仓库(aiagentpro 根仓库 .gitignore 忽略
6
+ # /baize-ai/),故本脚本随 baize-core 仓库维护,运行时自动定位 baize-ai 根。
7
+ #
8
+ # 产出(默认 dist = <baize-ai>/dist/):
9
+ # baize-a2a-<v>.tgz A2A channel 包(npm-pack 兼容结构:
10
+ # package/package.json 在包根,name=@baize-ai/baize-a2a;
11
+ # 兼容 web 控制台上传与 `baize add --file` 两种安装)
12
+ # baize-admin-workspace-<v>.tgz admin 套件源码:server 源码 + admin-ui 构建产物 +
13
+ # Dockerfile + 部署说明(自建镜像 / 自托管用)
14
+ # baize-admin-workspace-<v>.tar docker save 的 admin 镜像(docker load 直接运行,推荐)
15
+ # INSTALL.md 客户侧安装说明(上传 / --file 安装 + 切集群 + admin 部署)
16
+ #
17
+ # 用法:
18
+ # ./pack-release.sh [version] [dist-dir]
19
+ # version 版本号;缺省读取 baize-a2a/package.json 的 version
20
+ # dist-dir 输出目录;缺省 <baize-ai>/dist(可用环境变量 DIST_DIR 覆盖)
21
+ #
22
+ # 依赖:node/npm(a2a 包优先 npm pack)、docker(admin 镜像 docker save)、tar、jq
23
+ # ────────────────────────────────────────────────────────────────────────────
24
+ set -euo pipefail
25
+
26
+ # ── 定位 baize-ai 根(本脚本位于 baize-ai/baize-core/scripts/)───────────────
27
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+ BAIZE_AI_ROOT="${BAIZE_AI_ROOT:-$(cd "${SCRIPT_DIR}/../.." && pwd)}"
29
+ if [ ! -f "${BAIZE_AI_ROOT}/baize-a2a/package.json" ]; then
30
+ echo "!! 找不到 baize-ai 根目录(期望 ${BAIZE_AI_ROOT}/baize-a2a/package.json)" >&2
31
+ echo " 可设置 BAIZE_AI_ROOT 指向 baize-ai 目录" >&2
32
+ exit 1
33
+ fi
34
+ A2A_DIR="${BAIZE_AI_ROOT}/baize-a2a"
35
+ ADMIN_DIR="${BAIZE_AI_ROOT}/baize-admin-workspace"
36
+
37
+ # ── 版本 ────────────────────────────────────────────────────────────────────
38
+ VERSION="${1:-}"
39
+ if [ -z "${VERSION}" ]; then
40
+ VERSION="$(jq -r '.version' "${A2A_DIR}/package.json")"
41
+ fi
42
+ if [[ ! "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
43
+ echo "!! 非法版本号: ${VERSION}(期望形如 0.1.0)" >&2
44
+ exit 1
45
+ fi
46
+ # 套件版本一致性:显式版本必须与 a2a / admin 两个 package.json 的 version 一致,
47
+ # 否则产物 manifest 与文件名不符(npm pack 以 package.json 版本为准)。
48
+ for _pkg in "${A2A_DIR}/package.json" "${ADMIN_DIR}/package.json"; do
49
+ _pv="$(jq -r '.version' "${_pkg}")"
50
+ if [ "${_pv}" != "${VERSION}" ]; then
51
+ echo "!! 版本不一致: ${_pkg} 声明 ${_pv},请求打包 ${VERSION}" >&2
52
+ echo " 请先同步 package.json 版本,或省略版本参数使用默认版本(${_pv})" >&2
53
+ exit 1
54
+ fi
55
+ done
56
+
57
+ DIST_DIR="${DIST_DIR:-${BAIZE_AI_ROOT}/dist}"
58
+ if [ $# -ge 2 ] && [ -n "${2:-}" ]; then DIST_DIR="${2}"; fi
59
+ mkdir -p "${DIST_DIR}"
60
+
61
+ # 清理同版本旧产物,避免新旧混留
62
+ rm -f "${DIST_DIR}/baize-a2a-${VERSION}.tgz" \
63
+ "${DIST_DIR}/baize-admin-workspace-${VERSION}.tgz" \
64
+ "${DIST_DIR}/baize-admin-workspace-${VERSION}.tar"
65
+
66
+ TMP="$(mktemp -d "${TMPDIR:-/tmp}/pack-release.XXXXXX")"
67
+ CREATED_NPMIGNORE=0
68
+ cleanup() {
69
+ rm -rf "${TMP}"
70
+ if [ "${CREATED_NPMIGNORE}" = "1" ]; then
71
+ rm -f "${A2A_DIR}/.npmignore"
72
+ fi
73
+ }
74
+ trap cleanup EXIT
75
+
76
+ log() { printf '%s\n' "$*"; }
77
+ step() { printf '\n==> %s\n' "$*"; }
78
+ size() { du -h "$1" | awk '{print $1}'; }
79
+
80
+ # ── 1. A2A channel 包 ───────────────────────────────────────────────────────
81
+ build_a2a_tgz() {
82
+ step "打包 A2A channel 包 v${VERSION}"
83
+ local out="${DIST_DIR}/baize-a2a-${VERSION}.tgz"
84
+
85
+ # 优先 npm pack:临时 .npmignore 限定包内容(结束后删除,不留在 a2a 仓库)
86
+ if command -v npm >/dev/null 2>&1; then
87
+ if [ ! -f "${A2A_DIR}/.npmignore" ]; then
88
+ cat > "${A2A_DIR}/.npmignore" <<'EOF'
89
+ test/
90
+ docs/
91
+ .npm-cache/
92
+ *.tgz
93
+ .gitignore
94
+ EOF
95
+ CREATED_NPMIGNORE=1
96
+ fi
97
+ if (cd "${A2A_DIR}" && npm pack --json --pack-destination "${TMP}" >"${TMP}/npm-pack.json" 2>"${TMP}/npm.err"); then
98
+ local packed
99
+ packed="$(jq -r '.[0].filename // empty' "${TMP}/npm-pack.json")"
100
+ if [ -n "${packed}" ] && [ -f "${TMP}/${packed}" ]; then
101
+ # npm ≥9 的 npm pack 默认不再包含 package-lock.json —— 补回(交付规范要求
102
+ # package-lock.json 随包,客户安装可离线 npm ci)
103
+ if ! tar -tzf "${TMP}/${packed}" | grep -q '^package/package-lock.json$'; then
104
+ log " npm pack 未含 package-lock.json,补回"
105
+ local fixdir="${TMP}/a2a-fix"
106
+ mkdir -p "${fixdir}"
107
+ tar -xzf "${TMP}/${packed}" -C "${fixdir}"
108
+ cp "${A2A_DIR}/package-lock.json" "${fixdir}/package/package-lock.json"
109
+ rm -f "${TMP}/${packed}"
110
+ tar -czf "${TMP}/${packed}" -C "${fixdir}" package
111
+ fi
112
+ mv "${TMP}/${packed}" "${out}"
113
+ log " npm pack → ${out} ($(size "${out}"))"
114
+ return 0
115
+ fi
116
+ log " npm pack 输出解析失败,降级手工打包"
117
+ else
118
+ log " npm pack 失败($(tail -1 "${TMP}/npm.err")),降级手工打包"
119
+ fi
120
+ else
121
+ log " npm 不可用,降级手工打包(结构一致)"
122
+ fi
123
+
124
+ # 降级:tar 手工打包,结构与 npm pack 一致(package/ 前缀包装)
125
+ local stage="${TMP}/a2a-stage"
126
+ mkdir -p "${stage}/package"
127
+ cp -R "${A2A_DIR}/src" "${A2A_DIR}/scripts" "${stage}/package/"
128
+ cp "${A2A_DIR}/SKILL.md" "${A2A_DIR}/ecosystem.config.cjs" \
129
+ "${A2A_DIR}/package.json" "${A2A_DIR}/package-lock.json" "${stage}/package/"
130
+ if [ -f "${A2A_DIR}/README.md" ]; then
131
+ cp "${A2A_DIR}/README.md" "${stage}/package/"
132
+ fi
133
+ tar -czf "${out}" -C "${stage}" package
134
+ log " tar 打包 → ${out} ($(size "${out}"))"
135
+ }
136
+
137
+ # ── 2. Admin 套件源码 tgz ───────────────────────────────────────────────────
138
+ build_admin_tgz() {
139
+ step "打包 admin 套件源码 v${VERSION}"
140
+ local out="${DIST_DIR}/baize-admin-workspace-${VERSION}.tgz"
141
+ local stage="${TMP}/admin-stage/baize-admin-workspace-${VERSION}"
142
+ mkdir -p "${stage}"
143
+
144
+ cp "${ADMIN_DIR}/package.json" "${ADMIN_DIR}/package-lock.json" \
145
+ "${ADMIN_DIR}/Dockerfile" "${ADMIN_DIR}/README.md" "${stage}/"
146
+ cp -R "${ADMIN_DIR}/server" "${stage}/server" # server 源码(src + test)
147
+ cp -R "${ADMIN_DIR}/scripts" "${stage}/scripts"
148
+ mkdir -p "${stage}/admin-ui"
149
+ cp -R "${ADMIN_DIR}/admin-ui/dist" "${stage}/admin-ui/dist" # 构建产物
150
+ if [ -f "${ADMIN_DIR}/admin-ui/package.json" ]; then
151
+ cp "${ADMIN_DIR}/admin-ui/package.json" "${stage}/admin-ui/"
152
+ fi
153
+
154
+ tar -czf "${out}" -C "${TMP}/admin-stage" "baize-admin-workspace-${VERSION}"
155
+ log " → ${out} ($(size "${out}"))"
156
+ }
157
+
158
+ # ── 3. Admin Docker 镜像(docker save)──────────────────────────────────────
159
+ save_admin_image() {
160
+ step "导出 admin Docker 镜像"
161
+ local image="baize-admin-workspace:test"
162
+ if ! docker image inspect "${image}" >/dev/null 2>&1; then
163
+ echo "!! 镜像 ${image} 不存在(先构建:cd ${ADMIN_DIR} && docker build -t ${image} .)" >&2
164
+ exit 1
165
+ fi
166
+ local out="${DIST_DIR}/baize-admin-workspace-${VERSION}.tar"
167
+ docker save -o "${out}" "${image}"
168
+ log " docker save ${image} → ${out} ($(size "${out}"))"
169
+ }
170
+
171
+ # ── 4. INSTALL.md ───────────────────────────────────────────────────────────
172
+ write_install_md() {
173
+ step "生成 INSTALL.md"
174
+ local out="${DIST_DIR}/INSTALL.md"
175
+ cat > "${TMP}/INSTALL.md.tpl" <<'INSTALL_EOF'
176
+ # Baize A2A 套件安装说明(私有交付 v__VERSION__)
177
+
178
+ > 私有交付物:不通过 npm / 公开镜像分发,由厂商人工发放。
179
+ > 适用:安装 A2A 通道包(每实例组件)+ 部署 Admin 控制面(集群控制面)。
180
+
181
+ ## 交付物清单
182
+
183
+ | 文件 | 用途 |
184
+ |------|------|
185
+ | `baize-a2a-__VERSION__.tgz` | A2A 通道包(channel)。安装到本机 baize 环境:web 控制台上传 或 `baize add --file` |
186
+ | `baize-admin-workspace-__VERSION__.tgz` | Admin 套件源码:server 源码 + admin-ui 构建产物 + Dockerfile + 部署说明(自建镜像 / 自托管用) |
187
+ | `baize-admin-workspace-__VERSION__.tar` | Admin Docker 镜像(docker save 产物):`docker load` 后直接运行(推荐) |
188
+ | `INSTALL.md` | 本说明 |
189
+
190
+ ## 一、安装 A2A 通道包
191
+
192
+ ### 方式 A:web 控制台上传(推荐)
193
+
194
+ 1. 打开 Baize web 控制台,进入「A2A」页;
195
+ 2. 在「模块安装」区选择 `baize-a2a-__VERSION__.tgz` 并上传;
196
+ 3. 上传通过基础校验(manifest:package.json name=`@baize-ai/baize-a2a` + 版本;大小 / 路径穿越检查)后完成安装。
197
+
198
+ ### 方式 B:CLI(baize add --file)
199
+
200
+ ```bash
201
+ baize add a2a --file baize-a2a-__VERSION__.tgz
202
+ ```
203
+
204
+ 校验安装:
205
+
206
+ ```bash
207
+ baize a2a status
208
+ ```
209
+
210
+ ## 二、切集群(启用 A2A 互联)
211
+
212
+ ### 方式 A:web 控制台
213
+
214
+ A2A 页「模式卡」→ 切换为集群 → 填写 `admin_url` / `advertise_url` → 确认。模式变为「集群·在线」。
215
+
216
+ ### 方式 B:CLI
217
+
218
+ ```bash
219
+ baize a2a enable --admin https://<admin主机>:<端口> --advertise https://<本机>:<端口>
220
+ baize a2a status # 期望:集群·在线
221
+ ```
222
+
223
+ 说明:
224
+
225
+ - `enable` = 切集群事务:写入 `mode: cluster` → 启动 daemon(PM2 优先,不可用则 nohup)→ TCP 就绪探测(≤30s)→ 注册 admin;
226
+ - 未配置证书时自动生成自签证书(SAN 含 advertise_url 主机名 + 127.0.0.1 + localhost);
227
+ - 任一步失败自动回滚为单机模式,本地能力不受影响。
228
+
229
+ ### 切回单机
230
+
231
+ ```bash
232
+ baize a2a disable # 停 daemon,保留配置与注册数据,模式回 standalone
233
+ ```
234
+
235
+ ## 三、部署 Admin(控制面)
236
+
237
+ ### 方式 A:Docker 镜像(推荐)
238
+
239
+ ```bash
240
+ docker load -i baize-admin-workspace-__VERSION__.tar
241
+ docker run -d --name baize-admin -p 8080:8080 \
242
+ -v baize-admin-data:/data \
243
+ -e BAIZE_ADMIN_TOKEN='<初始口令>' \
244
+ baize-admin-workspace:test
245
+ ```
246
+
247
+ - 访问 `http://<主机>:8080`,首次登录:`admin` + 初始口令(日志打印或 `BAIZE_ADMIN_TOKEN`);
248
+ - 登录后立即修改口令;数据全部落在 `/data` 卷(SQLite 库与签名密钥),务必持久化;
249
+ - 公网部署建议放在 HTTPS 反代后面,`baize a2a enable --admin` 使用对外 HTTPS 地址。
250
+
251
+ ### 方式 B:源码套件(自建镜像 / 自托管)
252
+
253
+ ```bash
254
+ tar xzf baize-admin-workspace-__VERSION__.tgz
255
+ cd baize-admin-workspace-__VERSION__
256
+ docker build -t baize-admin-workspace . # 多阶段:node:22 构建 admin-ui → node:22-slim 运行
257
+ docker run -d --name baize-admin -p 8080:8080 -v baize-admin-data:/data baize-admin-workspace
258
+ ```
259
+
260
+ - 本地运行:`npm ci && npm start`(Node ≥ 20.11,默认 8080);
261
+ - 前端构建产物已含在 `admin-ui/dist`(容器内 `/app/dist`),可与 API 同源静态提供。
262
+
263
+ ## 四、卸载
264
+
265
+ ```bash
266
+ baize a2a disable # 先切回单机 / 停服务
267
+ baize uninstall a2a confirm
268
+ ```
269
+
270
+ ## 五、常见问题
271
+
272
+ - **上传校验失败**(manifest / 大小 / 路径穿越):确认使用的是厂商交付的原包 `baize-a2a-__VERSION__.tgz`,未被篡改;
273
+ - **enable 失败**:daemon 起不来或注册 admin 失败会自动回滚单机;查看 `baize a2a status` 与 daemon 日志;
274
+ - **admin 无法连接**:确认 admin 已部署(`/healthz` 可访问)、`--admin` 地址与端口正确、网络互通。
275
+ INSTALL_EOF
276
+ sed "s/__VERSION__/${VERSION}/g" "${TMP}/INSTALL.md.tpl" > "${out}"
277
+ log " → ${out}"
278
+ }
279
+
280
+ # ── 5. 产物校验 ─────────────────────────────────────────────────────────────
281
+ verify() {
282
+ step "校验产物"
283
+ local rc=0
284
+ local a2a_tgz="${DIST_DIR}/baize-a2a-${VERSION}.tgz"
285
+ local admin_tgz="${DIST_DIR}/baize-admin-workspace-${VERSION}.tgz"
286
+ local admin_tar="${DIST_DIR}/baize-admin-workspace-${VERSION}.tar"
287
+
288
+ # A2A tgz:可解压 + package.json 存在且 name/version 正确 + 关键文件齐全
289
+ if tar -tzf "${a2a_tgz}" >/dev/null 2>&1 \
290
+ && tar -tzf "${a2a_tgz}" | grep -q '^package/package.json$' \
291
+ && [ "$(tar -xOzf "${a2a_tgz}" package/package.json 2>/dev/null | jq -r '.name // empty')" = "@baize-ai/baize-a2a" ] \
292
+ && [ "$(tar -xOzf "${a2a_tgz}" package/package.json 2>/dev/null | jq -r '.version // empty')" = "${VERSION}" ]; then
293
+ local entries
294
+ entries="$(tar -tzf "${a2a_tgz}" | tr '\n' ' ')"
295
+ local missing=0
296
+ for f in package/SKILL.md package/ecosystem.config.cjs package/package-lock.json package/scripts/cli.js; do
297
+ case " ${entries} " in
298
+ *" ${f} "*) ;;
299
+ *) echo " !! ${a2a_tgz} 缺少 ${f}" >&2; missing=1 ;;
300
+ esac
301
+ done
302
+ if [ "${missing}" = "0" ]; then
303
+ log " OK ${a2a_tgz} ($(size "${a2a_tgz}")) 解压 OK · name=@baize-ai/baize-a2a · version=${VERSION}"
304
+ else
305
+ rc=1
306
+ fi
307
+ else
308
+ echo " !! ${a2a_tgz} 校验失败(解压或 manifest 不正确)" >&2
309
+ rc=1
310
+ fi
311
+
312
+ # Admin 套件 tgz:可解压 + package.json 存在且 name 正确 + 关键文件齐全
313
+ local admin_ent
314
+ admin_ent="baize-admin-workspace-${VERSION}"
315
+ if tar -tzf "${admin_tgz}" >/dev/null 2>&1 \
316
+ && tar -tzf "${admin_tgz}" | grep -q "^${admin_ent}/package.json$" \
317
+ && [ "$(tar -xOzf "${admin_tgz}" "${admin_ent}/package.json" 2>/dev/null | jq -r '.name // empty')" = "@baize-ai/baize-admin-workspace" ]; then
318
+ local a_entries missing2=0
319
+ a_entries="$(tar -tzf "${admin_tgz}" | tr '\n' ' ')"
320
+ for f in "${admin_ent}/Dockerfile" "${admin_ent}/README.md" "${admin_ent}/server/src/index.js" "${admin_ent}/admin-ui/dist/index.html"; do
321
+ case " ${a_entries} " in
322
+ *" ${f} "*) ;;
323
+ *) echo " !! ${admin_tgz} 缺少 ${f}" >&2; missing2=1 ;;
324
+ esac
325
+ done
326
+ if [ "${missing2}" = "0" ]; then
327
+ log " OK ${admin_tgz} ($(size "${admin_tgz}")) 解压 OK · name=@baize-ai/baize-admin-workspace"
328
+ else
329
+ rc=1
330
+ fi
331
+ else
332
+ echo " !! ${admin_tgz} 校验失败(解压或 manifest 不正确)" >&2
333
+ rc=1
334
+ fi
335
+
336
+ # docker save 产物:存在且非空
337
+ if [ -s "${admin_tar}" ]; then
338
+ log " OK ${admin_tar} ($(size "${admin_tar}")) 存在且非空"
339
+ else
340
+ echo " !! ${admin_tar} 不存在或为空" >&2
341
+ rc=1
342
+ fi
343
+
344
+ [ -f "${DIST_DIR}/INSTALL.md" ] && log " OK ${DIST_DIR}/INSTALL.md ($(size "${DIST_DIR}/INSTALL.md"))"
345
+ return "${rc}"
346
+ }
347
+
348
+ # ── 执行 ────────────────────────────────────────────────────────────────────
349
+ log "Baize A2A 套件私有交付打包"
350
+ log " 版本: ${VERSION} 输出: ${DIST_DIR}"
351
+ build_a2a_tgz
352
+ build_admin_tgz
353
+ save_admin_image
354
+ write_install_md
355
+ verify
356
+
357
+ step "完成 — 交付物清单(${DIST_DIR})"
358
+ ls -lh "${DIST_DIR}"/baize-a2a-"${VERSION}".tgz \
359
+ "${DIST_DIR}"/baize-admin-workspace-"${VERSION}".tgz \
360
+ "${DIST_DIR}"/baize-admin-workspace-"${VERSION}".tar \
361
+ "${DIST_DIR}"/INSTALL.md
@@ -8,10 +8,14 @@
8
8
  "init": "node scripts/c4-db.js init",
9
9
  "test": "node --test scripts/__tests__/*.test.js"
10
10
  },
11
- "keywords": ["baize", "ai-agent", "communication"],
11
+ "keywords": [
12
+ "baize",
13
+ "ai-agent",
14
+ "communication"
15
+ ],
12
16
  "author": "Baize AI",
13
17
  "license": "Apache-2.0",
14
18
  "dependencies": {
15
- "better-sqlite3": "^12.6.2"
19
+ "better-sqlite3": "^13.0.3"
16
20
  }
17
- }
21
+ }
@@ -24,7 +24,7 @@ import {
24
24
  const __filename = fileURLToPath(import.meta.url);
25
25
  const __dirname = path.dirname(__filename);
26
26
  const AM_SOCKET_PATH = path.join(ACTIVITY_MONITOR_DIR, 'am.sock');
27
- const ROUTER_IPC_TIMEOUT_MS = 30000;
27
+ const ROUTER_IPC_TIMEOUT_MS = 3000;
28
28
  const STATUS_NOTICE_COOLDOWN_SECONDS = Number.parseInt(process.env.C4_STATUS_NOTICE_COOLDOWN_SECONDS || '600', 10);
29
29
 
30
30
  function printUsage() {
@@ -346,7 +346,28 @@ async function main() {
346
346
  }
347
347
  }
348
348
 
349
- const route = await queryRoute(channel, endpoint, noReply);
349
+ // Route decision via activity-monitor IPC; on IPC failure/timeout degrade to
350
+ // the local health file (readHealthStatusFile) instead of blocking intake for
351
+ // ROUTER_IPC_TIMEOUT_MS — a slow/unresponsive monitor must not stall message
352
+ // delivery to C4 (D21 container test: monitor IPC took 19-25s without Claude
353
+ // auth, stalling every inbound message past the 20s c4-receive timeout).
354
+ let route;
355
+ try {
356
+ route = await queryRoute(channel, endpoint, noReply);
357
+ } catch {
358
+ const status = readHealthStatusFile();
359
+ const health = publicHealth(status?.health);
360
+ route =
361
+ health === 'ok'
362
+ ? { recovered: true, health: 'ok', fallback: true }
363
+ : {
364
+ recovered: false,
365
+ health,
366
+ reason: status?.unavailable_reason || health,
367
+ userMessage: buildFallbackMessage(status),
368
+ fallback: true,
369
+ };
370
+ }
350
371
  const replyEndpoint = noReply ? null : endpoint;
351
372
  let dbContent = content;
352
373
  const dbStatus = route.recovered ? 'pending' : 'delivered';
@@ -13,9 +13,9 @@
13
13
  "test": "node --test scripts/__tests__/*.test.js"
14
14
  },
15
15
  "dependencies": {
16
- "better-sqlite3": "^12.6.2",
16
+ "better-sqlite3": "^13.0.3",
17
17
  "chrono-node": "^2.7.7",
18
18
  "cron-parser": "^4.9.0",
19
19
  "dotenv": "^16.6.1"
20
20
  }
21
- }
21
+ }
@@ -60,6 +60,40 @@ Browser ──► Web Console Server ──► C4 Bridge ──► Claude
60
60
  | `/api/poll?since_id=N` | GET | Poll for new messages |
61
61
  | `/api/health` | GET | Server health check |
62
62
 
63
+ ## A2A Admin (agent card + call authorization)
64
+
65
+ The web console also hosts the A2A admin surface (identity registration, peer
66
+ directory, task board, message log). Agent card and call-authorization policy
67
+ are persisted in the same `config.json` the baize-a2a daemon reads
68
+ (`BAIZE_A2A_CONFIG`, default `~/baize/components/a2a/config.json`; polled every
69
+ 3s → hot reload).
70
+
71
+ | Endpoint | Method | Description |
72
+ |----------|--------|-------------|
73
+ | `/api/admin/a2a/card` | GET/PUT | Agent card (name / description / skills) |
74
+ | `/api/admin/a2a/card/skills` | GET | Installed skill catalogue for the card |
75
+ | `/api/admin/a2a/authz` | GET/PUT | Call-authorization policy (see below) |
76
+
77
+ ### Authz policy semantics
78
+
79
+ ```jsonc
80
+ // config.json "authz" field
81
+ "authz": {
82
+ "mode": "open", // "open" | "allowlist"
83
+ "allow": ["<agent_id>"], // allowlist mode: peers allowed to call
84
+ "block": ["<agent_id>"] // open mode: peers blocked from calling
85
+ }
86
+ ```
87
+
88
+ - `open` (default): accept every approved agent except the `block` list.
89
+ - `allowlist`: only the `allow` list may call (empty list = nobody may call).
90
+ - The check applies to calls from other agents (message/task/cancel); query
91
+ endpoints keep their existing caller matching.
92
+ - `PUT /api/admin/a2a/authz` validates: `mode` must be `open` or `allowlist`;
93
+ `allow`/`block` must be arrays of non-empty strings (entries are trimmed and
94
+ deduplicated; duplicates/empty/whitespace entries are rejected with 400).
95
+ Other `config.json` fields are preserved.
96
+
63
97
  ## Files
64
98
 
65
99
  ```
@@ -8,7 +8,7 @@
8
8
  "start": "node server.js"
9
9
  },
10
10
  "dependencies": {
11
- "better-sqlite3": "^12.6.2",
11
+ "better-sqlite3": "^13.0.3",
12
12
  "dotenv": "^16.6.1",
13
13
  "express": "^4.18.2",
14
14
  "multer": "^2.1.1",
@@ -18,4 +18,4 @@
18
18
  "path-to-regexp": "0.1.13",
19
19
  "qs": "6.15.2"
20
20
  }
21
- }
21
+ }