From 49b6a371c9adc77df9db24ec634e07d4ce13efc1 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Wed, 30 Sep 2026 21:32:28 +0000 Subject: [PATCH] kb init(jiuwenswarm): deepen --- adapters/jiuwenswarm/manifest.yaml | 2 +- .../components/common-core/_index.md | 2 + .../components/common-core/rules.md | 44 ++++++++++ .../components/extensions-plugins/_index.md | 2 + .../components/extensions-plugins/rules.md | 87 +++++++++++++++++++ .../components/gateway-channels/_index.md | 2 + .../components/gateway-channels/rules.md | 49 +++++++++++ 7 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 knowledge/repos/jiuwenswarm/components/common-core/rules.md create mode 100644 knowledge/repos/jiuwenswarm/components/extensions-plugins/rules.md create mode 100644 knowledge/repos/jiuwenswarm/components/gateway-channels/rules.md diff --git a/adapters/jiuwenswarm/manifest.yaml b/adapters/jiuwenswarm/manifest.yaml index 17d51483..acdf0f2d 100644 --- a/adapters/jiuwenswarm/manifest.yaml +++ b/adapters/jiuwenswarm/manifest.yaml @@ -29,7 +29,7 @@ push: # Knowledge service: declared but disabled; kb init's third PR flips it to # shadow after human review (design kb-init §9). knowledge_lifecycle: - enabled: false + enabled: true mode: shadow knowledge_dir: repos/jiuwenswarm release: diff --git a/knowledge/repos/jiuwenswarm/components/common-core/_index.md b/knowledge/repos/jiuwenswarm/components/common-core/_index.md index 7384cb2b..d0471913 100644 --- a/knowledge/repos/jiuwenswarm/components/common-core/_index.md +++ b/knowledge/repos/jiuwenswarm/components/common-core/_index.md @@ -22,3 +22,5 @@ sources: [] | PR 描述在做什么 | 精确规则 | 第一批 live 源码 | |---|---|---| | common、config、配置、模型选择、model catalog、mode、工作区、cron、updater、doctor、hooks、kv cache… | 入口 | `jiuwenswarm/common/_build_config.py`、`jiuwenswarm/common/chat_final.py`、`jiuwenswarm/common/cleanup.py` | + +- [jiuwenswarm/common 审查规则:配置读写事务、跨仓契约与持久化键稳定性](rules.md) diff --git a/knowledge/repos/jiuwenswarm/components/common-core/rules.md b/knowledge/repos/jiuwenswarm/components/common-core/rules.md new file mode 100644 index 00000000..95cea68c --- /dev/null +++ b/knowledge/repos/jiuwenswarm/components/common-core/rules.md @@ -0,0 +1,44 @@ +--- +title: "jiuwenswarm/common 审查规则:配置读写事务、跨仓契约与持久化键稳定性" +created: 2026-09-30 +updated: 2026-09-30 +type: rule +tags: [jiuwenswarm] +sources: [] +--- + +# jiuwenswarm/common 审查规则:配置读写事务、跨仓契约与持久化键稳定性 + +## JIUWENSW-I1 — `jiuwenswarm/common/config.py::update_config` 的 mutator 不能再次进入写锁,返回值决定是否写盘 + +- `_CONFIG_WRITE_LOCK`(`threading.Lock`)和 portalocker 文件锁都不可重入。如果 mutator 里调用了任何经过 `update_config` 或 `config_write_lock` 的函数(如 `update_ttse_enabled_in_config`、`save_models_candidate`、`update_permissions_*`),同一线程会一直阻塞,到 `lock_timeout` 后抛出 `TimeoutError`,写入失败。 +- mutator 的返回值才是要 dump 的对象。返回 `None` 表示不写盘(`delete_*` 没命中时就靠它跳过)。新写的 mutator 如果漏了 `return data`,修改会被悄悄丢掉。 +- mutator 内抛出异常会中止写入。校验(`raise_if_invalid`、重复 id 检查)必须放在 mutator 里面或调用之前,不能放到 `update_config` 返回之后。 +- 写盘后要读出来展示的数据,应在 `update_config` 返回后另发一次调用,不要在 mutator 里读。 + + + +## JIUWENSW-I4 — 新增的 config.yaml 写入口必须走 `update_config` 加 `dump_yaml_round_trip`,不要复制裸读改写的写法 + +- AgentServer 和 Gateway 两个进程会同时写同一个 config.yaml。`update_channel_in_config`、`update_browser_in_config`、`set_auto_memory_enabled` 这类裸 `load_yaml_round_trip` 到 `dump_yaml_round_trip` 的写法不持锁,会覆盖另一个进程的修改。新代码不要照抄这种写法;PR 改到这些旧函数时,优先迁移到 `update_config`。 +- 不要使用 `set_config`(直接 `open(..., "w")` 后 `yaml.safe_dump`),也不要自己开文件写。这种写法不是原子的,读者可能读到写了一半的文件,还会丢掉注释和引号风格。 +- `dump_yaml_round_trip` 的临时文件必须和目标文件放在同一目录(`os.replace` 不能跨文件系统),异常路径要删掉 tmp 文件。不要去掉 `_atomic_replace` 对 Windows `PermissionError` 的重试。 +- `_CONFIG_YAML_PATH`、`_load_yaml_round_trip`、`_dump_yaml_round_trip` 是下游模块在导入的兼容别名,不能删除或改名。 + + + +## JIUWENSW-I13 — 读取开关时注意字符串布尔值;evolution 开关只有一个来源 + +- 通过 `${VAR:-false}` 注入的值是字符串,而 `bool("false")` 为 True。可能来自环境变量替换的新开关应使用 `coerce_config_bool`。`is_subagent_runtime_enabled` 里的 `bool(...)` 就有这个问题,不要照抄。 +- `_get_evolution_config` 有意只读 `react.evolution`,不读顶层的 `evolution`,也不读环境变量覆盖,以免残留的部署变量改变运行中 agent 的能力。`get_skill_evolution_enabled` 和 `get_evolution_auto_save_enabled` 用 `is True` 严格判断。PR 不能把旧路径或 env 回退加回来。 + + + +## JIUWENSW-I17 — `jiuwenswarm/common/client/` 下的抽象接口是跨仓库契约,改签名必须和 Gateway 副本同步 + +- `AgentServerClient` 和 `ThirdAgent` 在 Gateway 仓库里各有一份副本,两边靠 E2A 线协议对齐。修改方法名、keyword-only 参数或返回类型时,必须同步 Gateway 副本和所有实现(WebSocket、AgentOS Router 等)。 +- 新增 `@abstractmethod` 会让所有没实现它的子类无法实例化。可选能力应提供默认实现(参考 `ThirdAgent.normalize_agent_type`)。 +- `send_request` 的 `timeout=None` 表示使用客户端默认值。实现方必须采用调用方传入的更大值(例如 cron 的 `timeout_seconds`),不能再被内层默认值提前截断。 +- `UnsupportedThirdAgent` 的返回形状(`ok` 为 False,带 `error`,`code` 为 UNSUPPORTED)是调用方依赖的约定。`normalize_agent_type` 只对内置的 jiuwenswarm 不区分大小写,registry 里的名称保留原大小写。 + + diff --git a/knowledge/repos/jiuwenswarm/components/extensions-plugins/_index.md b/knowledge/repos/jiuwenswarm/components/extensions-plugins/_index.md index c57eed47..bcc7884d 100644 --- a/knowledge/repos/jiuwenswarm/components/extensions-plugins/_index.md +++ b/knowledge/repos/jiuwenswarm/components/extensions-plugins/_index.md @@ -21,3 +21,5 @@ sources: [] | PR 描述在做什么 | 精确规则 | 第一批 live 源码 | |---|---|---| | extension、扩展、plugin、应用插件、hook、registry、loader、manifest、yuanrong、openYuanRong、cl… | 入口 | `jiuwenswarm/extensions/agent_client/`、`jiuwenswarm/extensions/application_host.py`、`jiuwenswarm/extensions/callback_compat.py` | + +- [video_duplex 全双工扩展审查规则:任务检查点、授权投递与 Provider 协议](rules.md) diff --git a/knowledge/repos/jiuwenswarm/components/extensions-plugins/rules.md b/knowledge/repos/jiuwenswarm/components/extensions-plugins/rules.md new file mode 100644 index 00000000..80970778 --- /dev/null +++ b/knowledge/repos/jiuwenswarm/components/extensions-plugins/rules.md @@ -0,0 +1,87 @@ +--- +title: "video_duplex 全双工扩展审查规则:任务检查点、授权投递与 Provider 协议" +created: 2026-09-30 +updated: 2026-09-30 +type: rule +tags: [jiuwenswarm] +sources: [] +--- + +# video_duplex 全双工扩展审查规则:任务检查点、授权投递与 Provider 协议 + +## JIUWENSW-I21 — 检查点 push/ACK 只走一次:`_pending` 要在推送前注册、在 finally 里清理,任何一侧都不许重试 + +- `RemoteTaskEndpoint.call` 必须先把 future 放进 `_pending[command_id]`,再 `await send_runtime_push(...)`。ACK 可能在推送还没返回时就到了。`finally` 里必须 `pop` 并取消还没完成的 future。 +- 每次 `call` 都会生成新的 `command_id`。在调用方或 `call` 里加重试,会绕开 `store.replay` 的去重,`claim` 这类动作就会被执行两次。超时应当直接失败。`handle_checkpoint_push` 在 ACK 发送失败时只记日志,不重做,这一行为要保留。 +- `resolve_checkpoint_ack` 在 `set_result` 之前要核对 `(user_id, session_id, execution_request_id)` 与 `identity` 是否一致。ACK 里的 `execution_request_id` 来自 `chunk.request_id`,改字段名时两端要一起改。 +- `call` 靠 `result.get("error")` 判断失败,所以 `execute` 的成功返回值里不能出现 `error` 键。 +- `_endpoints` 是 `WeakValueDictionary`。endpoint 的生命周期由 `AgentTaskExecutor.endpoint` 这个强引用维持。重构后如果不再持有这个引用,推送会报 `Task Gateway endpoint is unavailable`。 +- `handle_checkpoint_push` 遇到非 `EVENT` 帧必须返回 False,调用方靠这个返回值把帧交给普通消息投递。 + + + +## JIUWENSW-I23 — `AgentTaskExecutor.run`:执行未确认 settle 前,既不能返回,也不能抛普通异常 + +- 成功路径:`_run` 返回后,如果 `interaction.state` 还是 `pending`,必须抛 `ExecutionUncertain`。如果 `execution_bound` 为真,必须先 `wait_settled` 再返回。 +- 异常路径:已 bind 但 `execution_settled` 还没置位时,必须包装成 `ExecutionUncertain("... no work was replayed")` 抛出,不能让原始异常直接冒出去。这样才能和可以安全重跑的普通失败区分开,避免有副作用的 Agent 工作被重放。 +- `InteractionPending` 分支要先 `wait_settled`,再重新抛出。 +- 产物查询要遍历 `prior_request_ids` 加上当前 `request_id`。某一轮查询失败时只设置 `artifact_lookup_failed`,不能让整个结果失败。 +- 新增的等待必须设上限并转成 `ExecutionUncertain`,参照 `wait_settled` 的 20s。`cancel` 里轮询 `execution_settled` 的循环现在没有上限,改到这里时不要再加类似的无界等待。 + + + +## JIUWENSW-I24 — 授权链上 `ValueError` 表示永久失权,瞬时故障必须抛 `RuntimeError` + +- 每个 `handle_*` 和 `start` 都必须先通过 `self.scope`(内部调用 `authorized_scope`)拿到 `(owner, scope)`,然后才能访问 `self.service`。`task-duplex:` 作用域还要通过 AgentServer 的 `SESSION_GET_METADATA` 核对 `user_id == owner`。`handle_answer` 自己拼接 `"task-duplex:" + session_id`,也必须经过 `scope`。 +- 在 `task_identity` 里,空 owner 表示现有的匿名作用域。不要用服务器 OS 用户或其他值去填它。 +- `changed.deliver` 每次事件都会重新授权,捕获到 `ValueError` 就删除订阅,并清理对应的 `_approval_cards`。所以网络或 AgentServer 的瞬时失败必须保持为 `RuntimeError`,像 `task_agent_query` 那样;改成 `ValueError` 会把在线订阅者悄悄踢掉。反过来,身份被否决时必须抛 `ValueError`。 +- `subscribers` 的键是 `(owner, session, id(ws))`,`_approval_cards` 的键是在它后面加 task id。两者的清理条件要保持一致。 + + + +## JIUWENSW-I25 — `notification_timeout` 按 `deliver` 的最坏耗时推算,`deliver` 里增减 await 时要同步改公式 + +- `TaskService` 的 `notification_timeout=AGENT_QUERY_TIMEOUT + 2 * EVENT_SEND_TIMEOUT + 1` 对应的是:一次 `authorized_scope` 查询(`AGENT_QUERY_TIMEOUT`),加两次 `send_event`(各 `EVENT_SEND_TIMEOUT`)。 +- `deliver` 最后还调用了 `_sync_approval_card`,里面又有一次 `wait_for(..., EVENT_SEND_TIMEOUT)`,公式没把它算进去。在 `deliver` 或 `_sync_approval_card` 里新增 await、或调整这些超时时,要一起修正公式,并确认总耗时没有超出预算。 +- 所有 `channel.send_event` 都必须包在 `asyncio.wait_for(..., EVENT_SEND_TIMEOUT)` 里。不同订阅者之间要用 `gather(..., return_exceptions=True)` 并行投递(注释写明一个慢连接不能占掉其他订阅者的预算),不要改成顺序 await。 +- 只要有任意一个订阅者投递失败,就必须抛 `RuntimeError("Task notification delivery was incomplete")`,不能吞掉。 + + + +## JIUWENSW-I26 — 任务管理工具:名字集合、schema、手写类型校验、`operate` 分支四处同步改;冲突按异常类型区分 + +- `_TASK_TOOLS` 里的名字必须和 `task_management_tools()` 里的名字一一对应。只在集合里有、specs 里没有的名字,会让 `parse_qwen_omni_tool_call` 的 `next(...)` 抛 `StopIteration`;只在 specs 里有的名字会被当成不支持的工具拒绝。 +- 参数白名单来自 schema 的 `properties`/`required`,但类型校验是手写的: + - `answers` 只查是否为列表、长度是否在 1–32。 + - `revision`、`offset`、`queue_version` 必须是整数。 + - 其余属性一律当作非空字符串,长度不超过 256(`instruction` 不超过 4000)。 + - `answers` 的元素这里没有校验(schema 里声明的是 string/maxLength 4000)。 +- `VideoSearchManager.operate` 最后的 `else` 按 `jiuwen_task_modify` 处理。新增任务工具必须加显式分支,否则会被当成 modify 执行。 +- 冲突用 `QueueVersionConflict`/`TaskRevisionConflict` 捕获(`errors.py` 声明文案不属于控制协议),不要改成匹配错误字符串。这两个类继承自 `ValueError`,如果前面放了更宽的 `except ValueError`,会把冲突回执吞掉。 + + + +## JIUWENSW-I27 — 委派工具的调度字段(`independent`/`depends_on`/`resources`)和兼容字段在多处硬编码 + + - `qwen_omni_tools()` 的 schema。 + - `parse_qwen_omni_tool_call` 的排除集合 `set(arguments) - {...}` 和逐项校验。 + - `handle_qwen_tool` 里的 `scheduling` 复制。 + - `AgentTaskExecutor.run` 的上下文选择(`independent` 只带 `depends_on` 的结果,其余情况带最近 6 条)。 + - `public` 的投影。 + 漏改时,新字段要么被当成第二个 task 字段、报 `exactly one task field`,要么被悄悄丢掉。 +- `jiuwen_delegate` 的 schema 只公开 `task`,但解析时仍接受 `_DELEGATE_ARGUMENT_NAMES` 里的旧字段名。旧版 `jiuwen_research` 要求 arguments 里只有 `query` 一个键。`QwenOmniToolCall.query` 是兼容别名。这些都不要删。 +- `handle_qwen_tool` 用字面量 `{"jiuwen_delegate", "jiuwen_research"}` 判断是否委派。改 `QWEN_OMNI_DELEGATE_TOOL_NAME`/`QWEN_OMNI_RESEARCH_TOOL_NAME` 时这里要一起改。 + + + +## JIUWENSW-I31 — JoyAI 适配:动作标记与提示词、音频采样率与编码都要成对修改 + +- `parse_action` 的判定顺序固定: + - 先找 delegation 标记,标记之前是 response,之后是 delegation。 + - 然后找 silence,再找 response。 + - 空文本算 silence,没有标记的文本算 response。 + `_USER_KNOWLEDGE_GUARD` 让模型输出 ` 简短说明 ...`。改提示词或 `JOYAI_TASK_INSTRUCTIONS` 里的标记格式时,`_RESPONSE_MARKER`/`_SILENCE_MARKER`/`_DELEGATION_MARKER` 和判定顺序要一起改。 +- instruction 为空时,`ground_user_instruction` 必须返回空串,这样纯帧轮次不会带上约束。此时 `request_frame` 用 128 max_tokens,有指令时用 512。工具结果只能放在“已确认的九问工具结果”这个事实段里,不能拼进【用户原话】,以防注入。 +- ASR 的 setup 里 `sample_rate: 16_000` 必须和 `_pcm16_from_wav` 的 `target_rate` 一致,`struct.pack(">iii", -1, 0, 0)` 的帧头也要保留。TTS config 里的 `sample_rate: 24_000` 必须和 `synthesize_channel` 里 `_wav_from_pcm16(pcm, 24_000)` 一致。只改一边会得到变速或失真的音频,而且不会报错。 + + diff --git a/knowledge/repos/jiuwenswarm/components/gateway-channels/_index.md b/knowledge/repos/jiuwenswarm/components/gateway-channels/_index.md index 1befe024..8a7b39c3 100644 --- a/knowledge/repos/jiuwenswarm/components/gateway-channels/_index.md +++ b/knowledge/repos/jiuwenswarm/components/gateway-channels/_index.md @@ -20,3 +20,5 @@ sources: [] | PR 描述在做什么 | 精确规则 | 第一批 live 源码 | |---|---|---| | cli、chat、命令行、CLI 频道、gateway client、websocket、tui、renderer、jsonl、spinner、SIGINT、… | 入口 | `jiuwenswarm/channels/cli/`、`jiuwenswarm/channels/process_cli/` | + +- [Process CLI 频道(jiuwenswarm/channels/process_cli)审查规则](rules.md) diff --git a/knowledge/repos/jiuwenswarm/components/gateway-channels/rules.md b/knowledge/repos/jiuwenswarm/components/gateway-channels/rules.md new file mode 100644 index 00000000..f29ceb74 --- /dev/null +++ b/knowledge/repos/jiuwenswarm/components/gateway-channels/rules.md @@ -0,0 +1,49 @@ +--- +title: "Process CLI 频道(jiuwenswarm/channels/process_cli)审查规则" +created: 2026-09-30 +updated: 2026-09-30 +type: rule +tags: [jiuwenswarm] +sources: [] +--- + +# Process CLI 频道(jiuwenswarm/channels/process_cli)审查规则 + +## JIUWENSW-I32 — 会话 prepare 必须在 finally 中配对 _abort_prepared_session,且 abort 不得覆盖主错误 + +- 调用 `prepare_session_create`、`prepare_session_switch` 或 `prepare_session_fork` 时,必须在 `try` 之前写 `prepared = None`,在 `finally` 中写 `await _abort_prepared_session(client, prepared, sys.exception())`。新增会话操作也要照这个写法。 +- 不要把 prepare 挪到 `try` 外,也不要删掉 `finally`。否则,取消或异常落在 prepare 与 commit 之间时,还处于 PREPARED 的租约会遗留给 `client.close()`。 +- `jiuwenswarm/channels/process_cli/app.py::_abort_prepared_session` 只在 `prepared.state is SessionProvisionState.PREPARED` 时调用 abort。abort 自身抛出的 `Exception` 只记日志;只有没有主错误时,才重新抛出 `CancelledError`。改动后,abort 失败不能盖掉主操作的错误,已 commit 的会话也不能再被 abort。 + + + +## JIUWENSW-I36 — Team 流一轮结束后不会 EOF:_consume 靠终止事件返回,close 要先停掉 Team 流任务 + +- Team 模式的 Runtime 流在一轮结束后会继续存活。`_consume` 靠 `_is_terminal_team_event` 返回,判定条件是 `chat.processing_status`,且 `is_processing is False`、`is_complete is True`。如果改了这个判定或事件字段,或删掉 694 行的检查,Team 命令会一直挂到超时,返回 124。 +- `InProcessRuntimeClient.close` 必须先调用 `cancel_all_team_stream_tasks`,再调用 `self._runtime.close()`。两步各自包在 try 里,都跑完后再抛出第一个错误。不要改成先 close,也不要遇到错误就提前返回。 + + + +## JIUWENSW-I37 — LiveSessionInputController 的 pause/observe/render/resume 顺序,以及递归 _consume 的所有权 + +- 有 `live_input` 时,根流事件和 `client.answer_interaction` 返回的事件走同一个顺序:遇到交互事件或 `is_complete` 事件,先 `await live_input.pause()`,然后 `observe`,最后经 `render_root_event` 渲染。不要直接调用 `renderer.render`。 +- `handle_interaction` 要先 `pause()` 再显示提示。有 `live_input` 时,答案必须用 `live_input.read_interaction_line()` 读取,不能用 `_interaction_answer`:后者直接调用 `sys.stdin.readline()`,会和 live reader 抢 stdin。继续流或发送答案之前,要先调用 `resume_after_event()`。 +- 中断恢复时递归调用 `_consume`,必须传 `owns_live_input=False`。只有根 `_consume` 在 `finally` 里对控制器调用 `close()`,否则恢复流一结束,控制器就会被提前关闭。 + + + +## JIUWENSW-I39 — _write_worker_result 是 worker 与父 REPL 之间的接口,字段和写入时机都不能随意改 + +- worker 和父 REPL 之间没有传输层,只通过 `_session_result_file`(内容只有 session_id)和 `_worker_result_file`(JSON,字段为 `operation`、`session_id`、`mode`、`work_mode`、`project_dir`)交换已提交的状态。改字段名、删字段、换编码,都会破坏父进程这一侧的解析。 +- chat 在 `create_or_resume_session` 之后、构建请求和开始流之前,就写结果文件,这样后续失败或超时时,父进程仍然能拿到会话 ID。不要把这一步挪到流结束之后。 +- delete 写入和返回的是当前会话 `args.session`,不是被删掉的 `target`。改成 `target` 会让父 REPL 切到已删除的会话上。 + + + +## JIUWENSW-I41 — Duplex 控制输入:严格绑定当前运行,一次性令牌,出错时不回显输入 + +- 每条控制消息的 `request_id` 必须等于 `writer.request_id`;如果带了 `session_id`,也必须等于 `writer.session_id`。回答只能按 `_register_interaction` 发出的 uuid 令牌路由,而不是 Runtime 的 request_id。令牌 `pop` 一次后就失效,重放会被拒绝。 +- 任何控制错误都经 `_terminate`,并使用固定文案,不回显请求内容。`_terminate` 以第一次失败为准,`stop_input` 之后调用会被忽略。不要在 message 里拼接用户输入。 +- 输入 EOF 时如果还有待答交互,必须以 `INPUT_CLOSED` 按取消处理(退出码 130)。`harness.activate_interaction` 以及没有定位符的交互会被拒绝,报 `INTERACTION_UNSUPPORTED`;待答交互数量上限是 `_MAX_PENDING`。 + +