diff --git a/docs-site/docs/api/rest.md b/docs-site/docs/api/rest.md index 64651a0a0..725c3c36d 100644 --- a/docs-site/docs/api/rest.md +++ b/docs-site/docs/api/rest.md @@ -88,7 +88,7 @@ curl http://localhost:9200/health - 普通 `utok_` / `ntok_` 只看到其有权访问网络的 session;无网络成员关系时返回空对象。 ::: tip `license` 字段是 v0.6 legacy -`license: "trial"` 是 v0.6 时代 14 天试用机制的残留字段,Apache 2.0 OSS 后**不再作为商业功能门控**(自部署没有"过期"概念)。`send_task` 路径仍跑 trial 检查仅为后向兼容(verify [`server/src/tools.ts:521`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts#L521) `license_expired` 仍 emit),若命中见 [troubleshooting](/troubleshooting)。**v0.9.x / v0.10.x scope 都未动**(Recovery & Observability 主题为先),整段移除排到 v0.11+ / 未排期。 +`license: "trial"` 是 v0.6 时代 14 天试用机制的残留字段,Apache 2.0 OSS 后**不再作为商业功能门控**(自部署没有"过期"概念)。`send_task` 路径仍跑 trial 检查仅为后向兼容(verify [`server/src/tools.ts:521`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts) `license_expired` 仍 emit),若命中见 [troubleshooting](/troubleshooting)。**v0.9.x / v0.10.x scope 都未动**(Recovery & Observability 主题为先),整段移除排到 v0.11+ / 未排期。 ::: --- @@ -432,7 +432,7 @@ curl -X POST http://localhost:9200/api/networks \ | 状态 | `error` 值 | 触发条件 | |------|------------|---------| | 400 | `network name already exists` | 同一 owner 名下已有同名 network(`UNIQUE(owner_id, network_name)` 约束) | -| 400 | `quota exceeded: max N networks for free plan` | 触发 plan quota 配额限制(v0.8 起 admin 用户豁免;free plan 默认 max_networks_owned=2,**当前 quota 仍在 `auth.ts:184-189` enforced**,跟 networks 表的 `max_members` 不同:那个 dormant、这个 active) | +| 400 | `quota exceeded: max N networks for free plan` | 触发 plan quota 配额限制(v0.8 起 admin 用户豁免;free plan 默认 max_networks_owned=2,**真正会拒绝建网的是 plan 配额** —— `auth.ts` 的 `createNetwork()` 按 `max_networks_owned` 校验(free=2,admin 豁免)。注意它与 `/api/license` 的 `limits` 不是一回事:后者(trial 默认 `max_agents=5` / `max_networks=3` / `max_tasks_day=500`)**是软限额**,服务端只存储和返回、不做任何拦截(CLI 里直接标作 `Soft limits`),而且两者的 networks 数字不同(3 vs 2)—— 以实际生效的 plan 配额为准(原文钉的 `184-189` 已漂到发 token 的代码上,所以这里改钉函数名),跟 networks 表的 `max_members` 不同:那个 dormant、这个 active) | | 401 | `token required` / `invalid token` | 未提供 / 提供了无效 utok_ | --- @@ -1578,10 +1578,10 @@ curl -N "http://localhost:9200/events/代码1号?token=ntok_xxx" | `new_message` | 收到新消息(`send_message`) | `{from, message_id}` | | `new_reply` | 收到 reply(`send_reply`) | `{from, message_id, in_reply_to, status}` | | `broadcast` | 收到广播(`broadcast` 工具) | `{inbox_count}` | -| `chained_reply` | 子任务完成自动串回上游父任务发起者 ([`tools.ts:286/646`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts#L286)) | `{parent_task_id, child_task_id, child_alias}` | +| `chained_reply` | 子任务完成自动串回上游父任务发起者 ([`tools.ts:286/646`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts)) | `{parent_task_id, child_task_id, child_alias}` | | `node.renamed` | RFC-010 节点改名 COMMIT 时广播([`rename.ts:100-123`](https://github.com/sleep2agi/agent-network/blob/main/server/src/rename.ts#L100)),推给 old + new 两个 alias 流 **+ 每个网络成员的 user channel**(dashboard 订阅的是 `/events/` user channel、不是 per-alias 流,#84 SSE channel fix) | `{txn_id, alias(=new_alias), network_id, data:{old_alias, new_alias, surfaces_updated[], history_policy:"preserve"}}` | -> 旧 doc 在 `new_message` 上写过 `message` 字段、`broadcast` 上写过 `{content, from}` —— 都不对。verify [`tools.ts:571 + 911`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts#L571) 实际 payload 以上表为准。另注:`new_task` / `new_message` 在目标 alias **刚被改名**时会额外带一个 `renamed_from` 字段(指向旧 alias,`tools.ts` 的 `canonical.renamed` 分支)。 +> 旧 doc 在 `new_message` 上写过 `message` 字段、`broadcast` 上写过 `{content, from}` —— 都不对。verify [`tools.ts:571 + 911`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts) 实际 payload 以上表为准。另注:`new_task` / `new_message` 在目标 alias **刚被改名**时会额外带一个 `renamed_from` 字段(指向旧 alias,`tools.ts` 的 `canonical.renamed` 分支)。 > > **校正**:原表列过 `heartbeat` event with `{time}` payload,源码不发这个事件。[`push.ts:38-44`](https://github.com/sleep2agi/agent-network/blob/main/server/src/push.ts#L38) 实际发 SSE **comment 行** `: keepalive\n\n`(每 30s 一次,纯粹是给 proxy / LB 防 idle timeout 用),不会被 EventSource `onmessage` / `addEventListener` 触发,也不带 JSON payload。`connected` event 才是真正每次连接发一次的初始事件(agent-node 在 [`agent-node/src/cli.ts`](https://github.com/sleep2agi/agent-network/blob/main/agent-node/src/cli.ts) 显式处理它)。 @@ -2220,7 +2220,7 @@ curl http://localhost:9200/api/license { "ok": true, "license": { "type": "trial", "expires_at": "2026-04-25 12:00:00", "days_left": 12, "expired": false }, - "limits": { "max_agents": 5, "max_networks": 1, "max_tasks_day": 100 } + "limits": { "max_agents": 5, "max_networks": 3, "max_tasks_day": 500 } } ``` diff --git a/docs-site/docs/en/api/rest.md b/docs-site/docs/en/api/rest.md index 6353f9e7d..6c8af0de4 100644 --- a/docs-site/docs/en/api/rest.md +++ b/docs-site/docs/en/api/rest.md @@ -98,7 +98,7 @@ sessions from networks they may access (or an empty object when they belong to none). ::: tip The `license` field is a v0.6 legacy -`license: "trial"` is a leftover from the v0.6 era 14-day trial mechanism. After the Apache 2.0 OSS transition it is **no longer a commercial feature gate** (self-hosted has no notion of "expired"). The `send_task` path still runs the trial check only for backward compatibility (verify [`server/src/tools.ts:521`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts#L521) where `license_expired` is still emitted); if you hit it, see [troubleshooting](/en/troubleshooting). **The v0.9.x and v0.10.x scopes did not touch this** (Recovery & Observability took priority); full removal is queued for v0.11+ / unscheduled. +`license: "trial"` is a leftover from the v0.6 era 14-day trial mechanism. After the Apache 2.0 OSS transition it is **no longer a commercial feature gate** (self-hosted has no notion of "expired"). The `send_task` path still runs the trial check only for backward compatibility (verify [`server/src/tools.ts:521`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts) where `license_expired` is still emitted); if you hit it, see [troubleshooting](/en/troubleshooting). **The v0.9.x and v0.10.x scopes did not touch this** (Recovery & Observability took priority); full removal is queued for v0.11+ / unscheduled. ::: --- @@ -442,7 +442,8 @@ curl -X POST http://localhost:9200/api/networks \ | Status | `error` value | Trigger | |------|------------|---------| | 400 | `network name already exists` | Same owner already has a network with this name (`UNIQUE(owner_id, network_name)` constraint) | -| 400 | `quota exceeded: max N networks for free plan` | Plan quota gate ([`auth.ts:184-189`](https://github.com/sleep2agi/agent-network/blob/main/server/src/auth.ts#L184); admins are exempt; free plan default `max_networks_owned = 2`). Note this gate **is** enforced, unlike the `max_members` column, which is dormant | +| 400 | `quota exceeded: max N networks for free plan` | **What actually rejects a network creation is the plan quota** — enforced in [`auth.ts`](https://github.com/sleep2agi/agent-network/blob/main/server/src/auth.ts) `createNetwork()` (the old `L184-189` pin has drifted onto token-issuing code, so this now pins the function name; admins are exempt; free plan default `max_networks_owned = 2`). Note this gate **is** enforced, unlike the `max_members` column, which is dormant. +⚠️ Do not confuse it with the `limits` block from `/api/license` (trial defaults `max_agents=5` / `max_networks=3` / `max_tasks_day=500`): those are **soft limits** — the server only stores and returns them and enforces nothing (the CLI prints them as `Soft limits`). The two `networks` numbers even differ (3 vs 2); the plan quota is the one that applies | | 401 | `token required` / `invalid token` | Missing / invalid utok_ | --- @@ -1532,10 +1533,10 @@ curl -N "http://localhost:9200/events/coder-1?token=ntok_xxx" | `new_message` | New chat message (`send_message`) | `{from, message_id}` | | `new_reply` | Reply to a task (`send_reply`) | `{from, message_id, in_reply_to, status}` | | `broadcast` | Broadcast received (`broadcast` tool) | `{inbox_count}` | -| `chained_reply` | Sub-task completion routed back to the parent task's originator ([`tools.ts:286/646`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts#L286)) | `{parent_task_id, child_task_id, child_alias}` | +| `chained_reply` | Sub-task completion routed back to the parent task's originator ([`tools.ts:286/646`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts)) | `{parent_task_id, child_task_id, child_alias}` | | `node.renamed` | Broadcast on RFC-010 node-rename COMMIT ([`rename.ts:100-123`](https://github.com/sleep2agi/agent-network/blob/main/server/src/rename.ts#L100)); pushed to the old + new alias streams **plus every network member's user channel** (the dashboard subscribes to `/events/`, not per-alias streams — #84 SSE channel fix) | `{txn_id, alias(=new_alias), network_id, data:{old_alias, new_alias, surfaces_updated[], history_policy:"preserve"}}` | -> Earlier docs claimed `new_message` carried a `message` field and `broadcast` carried `{content, from}` — neither is correct. Verify [`tools.ts:571 + 911`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts#L571) for the actual payloads. Note: `new_task` / `new_message` additionally carry a `renamed_from` field (the old alias) when the target alias was just renamed — the `canonical.renamed` branch in `tools.ts`. +> Earlier docs claimed `new_message` carried a `message` field and `broadcast` carried `{content, from}` — neither is correct. Verify [`tools.ts:571 + 911`](https://github.com/sleep2agi/agent-network/blob/main/server/src/tools.ts) for the actual payloads. Note: `new_task` / `new_message` additionally carry a `renamed_from` field (the old alias) when the target alias was just renamed — the `canonical.renamed` branch in `tools.ts`. > > **Correction**: the table previously listed a `heartbeat` event with `{time}` payload. No such JSON event is emitted. [`push.ts:38-44`](https://github.com/sleep2agi/agent-network/blob/main/server/src/push.ts#L38) sends an SSE **comment line** `: keepalive\n\n` every 30s purely to defeat proxy/LB idle timeouts — comments are NOT delivered to `EventSource.onmessage` / `addEventListener` and carry no payload. The real once-per-connection initial event is `connected` (agent-node handles it explicitly at [`agent-node/src/cli.ts`](https://github.com/sleep2agi/agent-network/blob/main/agent-node/src/cli.ts)). @@ -2130,7 +2131,7 @@ curl http://localhost:9200/api/license { "ok": true, "license": { "type": "trial", "expires_at": "2026-04-25 12:00:00", "days_left": 12, "expired": false }, - "limits": { "max_agents": 5, "max_networks": 1, "max_tasks_day": 100 } + "limits": { "max_agents": 5, "max_networks": 3, "max_tasks_day": 500 } } ``` diff --git a/docs/doc-source-pins-baseline.txt b/docs/doc-source-pins-baseline.txt index 67ce7efef..20ab93321 100644 --- a/docs/doc-source-pins-baseline.txt +++ b/docs/doc-source-pins-baseline.txt @@ -13,7 +13,4 @@ # 修一条就把它从这里删掉 —— 门会检查这一点,不删会红。 # 修法见 #831:优先把行号锚点换成符号锚点(读者用 git grep 定位,重构改不坏)。 -server/src/auth.ts#L184 server/src/push.ts#L38 -server/src/tools.ts#L521 -server/src/tools.ts#L571 diff --git a/docs/tests/report-test831.txt b/docs/tests/report-test831.txt index f4446d2a6..b6015c646 100644 --- a/docs/tests/report-test831.txt +++ b/docs/tests/report-test831.txt @@ -9,17 +9,17 @@ python=Python 3.12.13 [L0] denominator listing_mode=walk scanned_doc_files=106 - pin_occurrences=35 + pin_occurrences=28 pins_on_immutable_ref=0 pin_doc_pairs=35 - unique_pins=15 + unique_pins=11 broken_pins=5 baseline_entries=5 OK: 失效 pin 5 个,基线 5 条 —— 没有新增,也没有该清的残留。 注意:这只说明已知失效的那批没变多。它抓不到「锚点指着一行正常代码、 只是不是声称的那一行」—— 实测召回率 5/10,详见本文件头部。 - OK walk 路径与 git 路径给出同一份清单(106 文件 / 15 唯一 pin / 35 处) + OK walk 路径与 git 路径给出同一份清单(106 文件 / 11 唯一 pin / 28 处) [L1] clean tree passes OK rc=0 broken_pins=5(全部在基线里) [L2] witnessed-red: a NEW broken pin must turn it red diff --git a/tests/test831-doc-source-pins/run.sh b/tests/test831-doc-source-pins/run.sh index 1907539f4..eef151484 100755 --- a/tests/test831-doc-source-pins/run.sh +++ b/tests/test831-doc-source-pins/run.sh @@ -72,10 +72,23 @@ broken=$(printf '%s' "$out" | sed -nE 's/^broken_pins=([0-9]+)$/\1/p') # 正好 3 条,18 - 3 = 15。`occ` 仍是 35(钉提交不减少"出现次数",只改变引用形式), # `files` 仍是 106 —— **只有一个数变了,而且变的原因能逐条指出来**。这正是这道 # 断言想逼出来的动作:数字变了要有人说清是进展还是扫漏,而不是把它改宽。 +# +# 2026-08-18(第二次):15 → 11,35 → 28。#810 把 docs-site 中英两版 api/rest.md 里 +# 4 个唯一 pin 的 `#L` 锚点去掉了(链接保留,只是不再钉行号),逐条数得出来: +# +# server/src/tools.ts#L521 ×2(中/英) +# server/src/tools.ts#L571 ×2 +# server/src/tools.ts#L286 ×2 +# server/src/auth.ts#L184 ×1 +# ───────────────────────────── +# 唯一 4 个,出现 7 处 +# +# 15 − 4 = 11,35 − 7 = 28,`files` 仍 106。**加回来的锚点数是 0**(diff 的 `+` 侧 +# 一个 `#L` 都没有),所以这是净减少,不是"挪了个位置"。 [[ "$files" -eq 106 ]] || fail "预期扫 106 个文档文件(= git ls-files 的结果),实际 $files" -[[ "$uniq" -eq 15 ]] || fail "预期 15 个唯一 pin,实际 $uniq" -[[ "$occ" -eq 35 ]] || fail "预期 35 处原始出现,实际 $occ" -echo " OK walk 路径与 git 路径给出同一份清单(106 文件 / 15 唯一 pin / 35 处)" +[[ "$uniq" -eq 11 ]] || fail "预期 11 个唯一 pin,实际 $uniq" +[[ "$occ" -eq 28 ]] || fail "预期 28 处原始出现,实际 $occ" +echo " OK walk 路径与 git 路径给出同一份清单(106 文件 / 11 唯一 pin / 28 处)" # --------------------------------------------------------------------------- # L1 — 干净树上必须绿 @@ -304,7 +317,10 @@ cmp -s "$BASELINE" /tmp/bl7.bak || fail "② 它拒绝了,却还是把基线写 echo " MUTATION_RED write-baseline-refuses-new-failure rc=$rc12" # ③ 引用消失时要删对,并保留表头注释 -BL_BEFORE=$(grep -cv '^\s*#\|^\s*$' "$BASELINE") +# 🔴 `grep -c` 在计数为 0 时退出码是 1,而这一段 set -e 是开着的 +# ⇒ `VAR=$(grep -c …)` 会**静默**终止整个脚本(没有任何 FAIL 输出)。 +# 而计数为 0 恰恰是这道门**清干净了**才会出现的状态 —— 又一次「门赢了自己就坏」。 +BL_BEFORE=$(grep -cv '^\s*#\|^\s*$' "$BASELINE" || true) # 🔴 造场景用的 SHA 必须是**仓里不可能出现的**合成值,不能借用一个真实提交。 # 原来这里用 22ed1886 —— 而 #834 之后 changelog 里**真的**有 # `blob/22ed1886/server/src/index.ts#L253`,于是下面的复原(全局把 @@ -333,7 +349,7 @@ print(f" (造场景:把 {target} 的 {n} 处引用改钉合成 SHA,涉及 {le PYX out13=$(python3 "$CHECK" "$ROOT" --write-baseline) || fail "③ 有该删的条目时 --write-baseline 却非零" printf '%s' "$out13" | grep -qF "已改写基线" || fail "③ 没报告改写" -BL_AFTER=$(grep -cv '^\s*#\|^\s*$' "$BASELINE") +BL_AFTER=$(grep -cv '^\s*#\|^\s*$' "$BASELINE" || true) [[ "$BL_AFTER" -lt "$BL_BEFORE" ]] || fail "③ 基线没有变小($BL_BEFORE → $BL_AFTER)" head -1 "$BASELINE" | grep -q '^#' || fail "③ 改写把表头注释弄丢了" python3 "$CHECK" "$ROOT" >/dev/null || fail "③ 改写之后门没有转绿"