From 3b91c02a6d5690b17365461b9b15190780ed038f Mon Sep 17 00:00:00 2001 From: DandreYang <13072547+Dandre126@users.noreply.github.com> Date: Tue, 18 Aug 2026 02:37:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E4=BA=A4=E4=BB=98=E7=89=A9=E7=90=86):=20?= =?UTF-8?q?=E5=87=86=E5=A4=87=200.7.3=20=E6=8A=8A=E7=A9=BA=E5=85=B3?= =?UTF-8?q?=E6=B3=A8=E6=94=B6=E5=9B=9E=E6=9C=AA=E8=AF=BB=E5=B9=B6=E5=8E=BB?= =?UTF-8?q?=E6=8E=89=E8=B7=AF=E5=BE=84=E6=B3=84=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Console 与换工具首页不再把未读说成没事做,不健康 next 也不再带本地根路径。 --- .github/workflows/ci.yml | 2 ++ CHANGELOG.md | 12 +++++++++++ ...6-delivery-physics-and-capability-plane.md | 2 +- docs/architecture.md | 2 +- pyproject.toml | 2 +- src/dyro/bridge/skill/SKILL.md | 2 +- src/dyro/cli.py | 17 ++++++++------- src/dyro/console/assets.py | 4 ++-- src/dyro/console/assets/app.js | 21 ++++++++++++++----- src/dyro/console/inspection.py | 2 +- src/dyro/home.py | 2 +- tests/test_cli.py | 7 +++++-- tests/test_console_assets.py | 2 ++ tests/test_console_inspection.py | 15 +++++++++++++ tests/test_release_gates.py | 2 +- tests/test_release_source.py | 3 +++ uv.lock | 2 +- 17 files changed, 75 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85b34a3..bf910d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,6 +88,7 @@ jobs: test -x "$smoke_root/wheel-venv/bin/dyro" test ! -e "$smoke_root/wheel-venv/bin/dyro-bridge" test ! -e "$smoke_root/wheel-venv/bin/dyro-mcp" + "$smoke_root/wheel-venv/bin/python" -I -c "import importlib.util; assert importlib.util.find_spec('dyro.bridge') is None" DYRO_LOCAL_AGENT_DISPATCH_HOME="$smoke_root/wheel-dispatch-home" "$smoke_root/wheel-venv/bin/dyro" dispatch doctor >"$smoke_root/dispatch-doctor.json" "$smoke_root/wheel-venv/bin/python" "$GITHUB_WORKSPACE/tools/verify_bundle_stranger.py" "$smoke_root/wheel-venv/bin/dyro" uv run python -m venv "$smoke_root/sdist-venv" @@ -99,6 +100,7 @@ jobs: test -x "$smoke_root/sdist-venv/bin/dyro" test ! -e "$smoke_root/sdist-venv/bin/dyro-bridge" test ! -e "$smoke_root/sdist-venv/bin/dyro-mcp" + "$smoke_root/sdist-venv/bin/python" -I -c "import importlib.util; assert importlib.util.find_spec('dyro.bridge') is None" "$smoke_root/sdist-venv/bin/python" "$GITHUB_WORKSPACE/tools/verify_bundle_stranger.py" "$smoke_root/sdist-venv/bin/dyro" windows-dispatch-import: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d744dd..9b8934b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +## 0.7.3 - 2026-08-18 + +- Console home no longer treats empty attention as idle. The overview + says unread, workspace cards say "摘要未列出关注项", and zero-count + attention chips are not marked success. Ready work still can say + there is nothing that needs you on the spot. +- Bare `dyro` home prints the workspace alias, not the local root. + Unhealthy `next` repair findings and commands stay path-free and + use `--workspace`. Isolated Console no longer allowlists `task next`. +- CI wheel/sdist smoke now asserts `dyro.bridge` is absent, matching + the publish gate. + ## 0.7.2 - 2026-08-17 - `objective explain` and a ready `next` now lead with a three-line diff --git a/docs/adr/0006-delivery-physics-and-capability-plane.md b/docs/adr/0006-delivery-physics-and-capability-plane.md index 26e0417..c31f153 100644 --- a/docs/adr/0006-delivery-physics-and-capability-plane.md +++ b/docs/adr/0006-delivery-physics-and-capability-plane.md @@ -64,7 +64,7 @@ - 产品叙事从「启动 agent」转为「核验完成」。 - `0.7.0` 落地 Proof、Capability Card、Host Compiler 与 `verify-bundle`。`trusted_usage` 默认 `false`;生产 `BudgetUsage` / `BudgetRequest` 从 Card 读取该字段。`objective tick` 对 automatic Objective 做预算预览(`automatic=True`),受监督 apply 仍 `automatic=False`。未信任用量只在存在 `workspace.max_provider_usage` 时硬停。Proof 仍不接入 `no_progress`。Console summary 保持 `proof_inspection=not_inspected`,不探 Git / Proof;`dyro objective attention` 走完整快照,可报 `PROOF_DECAYED`。两套入口不得写成同一套 Proof 展示。 -- Console 独立 inspect、`trigger_observation`、陌生人核验与叙事锁已在 `0.7.1` 落地。`0.7.2` 收口换工具 briefing 与 Console「现在需要你」。后续功能与产品面收口继续开发和上线,版本号保持 `0.7.x`。 +- Console 独立 inspect、`trigger_observation`、陌生人核验与叙事锁已在 `0.7.1` 落地。`0.7.2` 收口换工具 briefing 与 Console「现在需要你」。`0.7.3` 把空关注收回未读,并去掉首页与 `next` repair 的路径泄漏。后续功能与产品面收口继续开发和上线,版本号保持 `0.7.x`。 - 可携带核验的对外承诺仍是:陌生人拿着 Proof Bundle 和自己提供的 git 对象,能得到与源机**相同的完整性结论**(字节仍在、钉死 SHA 可解析)。这不是身份证明,也不是「现在工作区还能 merge」。`schema_version = 1` 的合同在 `0.7.x` 锁住语义;冻结成 `1.0.0` 身份号须另做产品决定。 - 实施成本是新的投影层与兼容层,而不是第二套调度器。 diff --git a/docs/architecture.md b/docs/architecture.md index a3b7934..c0038ea 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -241,6 +241,6 @@ Dyro 的交付拓扑与之**实质相近**:TaskGraph(`depends_on` / conflict 未来的 adapter、通知、签名规则、发布平台与审批系统应使用 Python entry point 或独立 Profile 扩展包接入;不要把某个组织的策略加入 core 默认行为。 -`0.7.0` 把已有证据物理学抽成可复验的 Proof,并把 argv adapter 升级为 Capability Card,再把定律编译为只收缩权威的宿主投影。衰减与现有 merge / 下游检查同真值;`proof verify` 看当前工作区,`verify-bundle` 只核完整性,两套结论不得混称。Console summary 与 `dyro objective attention` 不是同一套 Proof 展示。Console 独立 inspect、`trigger_observation` 与可携带核验门禁已在 `0.7.1` 落地。`0.7.2` 把换工具后的开场白收成事项加一条只读下一步,不另开会话层。后续功能继续在 `0.7.x` 开发和上线,不另开 `0.8.0` / `0.9.0` / `1.0.0` 功能号。可携带核验是 Proof Bundle 加调用方提供的 git 对象,核验完整性而不是身份,也不承诺与当前 merge 同一套 `live`。这不另造 TaskGraph 或完成状态机;见 [`交付物理学`](designs/delivery-physics.md) 与 [`ADR-0006`](adr/0006-delivery-physics-and-capability-plane.md)。 +`0.7.0` 把已有证据物理学抽成可复验的 Proof,并把 argv adapter 升级为 Capability Card,再把定律编译为只收缩权威的宿主投影。衰减与现有 merge / 下游检查同真值;`proof verify` 看当前工作区,`verify-bundle` 只核完整性,两套结论不得混称。Console summary 与 `dyro objective attention` 不是同一套 Proof 展示。Console 独立 inspect、`trigger_observation` 与可携带核验门禁已在 `0.7.1` 落地。`0.7.2` 把换工具后的开场白收成事项加一条只读下一步,不另开会话层。`0.7.3` 把 Console 空关注收回未读,并不再在首页和下一条修复命令里漏本地路径。后续功能继续在 `0.7.x` 开发和上线,不另开 `0.8.0` / `0.9.0` / `1.0.0` 功能号。可携带核验是 Proof Bundle 加调用方提供的 git 对象,核验完整性而不是身份,也不承诺与当前 merge 同一套 `live`。这不另造 TaskGraph 或完成状态机;见 [`交付物理学`](designs/delivery-physics.md) 与 [`ADR-0006`](adr/0006-delivery-physics-and-capability-plane.md)。 开发者侧的可选本地多 Agent 派发(五段式任务契约、注入前机密守卫、locator 核验、隔离 patch)与上述控制面分层并列,随 `dyro` 安装包分发(`dyro dispatch` / `import experiments.local_agent_dispatch`),但**不**替代 gates/合并。同时写多块走 Core Peer Wave(task worktree + `conflict_group`),见 [`peer-wave-execution.md`](designs/peer-wave-execution.md)、[`ADR-0002`](adr/0002-optional-local-agent-dispatch.md)、[`多智能体编排纪律`](agent-orchestration-discipline.md) 与 [`可选本地 Agent 派发设计`](designs/optional-local-agent-dispatch.md)。 diff --git a/pyproject.toml b/pyproject.toml index 8f19b74..b676254 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "dyro" -version = "0.7.2" +version = "0.7.3" description = "DyroEngineeringFlow: local-first automation and delivery control for multi-repository teams" readme = "README.md" requires-python = ">=3.11" diff --git a/src/dyro/bridge/skill/SKILL.md b/src/dyro/bridge/skill/SKILL.md index d1a5ce0..479032f 100644 --- a/src/dyro/bridge/skill/SKILL.md +++ b/src/dyro/bridge/skill/SKILL.md @@ -50,7 +50,7 @@ release, publish, console, install, or any confirmation/approval field. ```json { "protocol": {"major": 1, "minor": 0}, - "client": {"name": "dyro-agent-bridge-skill", "version": "0.7.2"}, + "client": {"name": "dyro-agent-bridge-skill", "version": "0.7.3"}, "operation": "bridge.capabilities.compact", "input": {} } diff --git a/src/dyro/cli.py b/src/dyro/cli.py index 47bba0c..f69bc9d 100644 --- a/src/dyro/cli.py +++ b/src/dyro/cli.py @@ -2433,29 +2433,32 @@ def cmd_next(args: argparse.Namespace) -> None: bool(absent_bootstrap_ids) and set(failures) == expected_bootstrap_failures ) repair_commands = ( - [_scoped_command(args, config, "bootstrap", "--yes")] + [_briefing_command(args, config, "bootstrap", "--yes")] if bootstrap_applicable else [] ) + findings = [ + _doctor_finding_payload(item, include_paths=False) for item in failures + ] if args.format == "json": _print_control_plane_json( "next_step", state="needs_repair", summary="工作区还不能开始任务。", commands=repair_commands, - diagnostic_commands=[_scoped_command(args, config, "doctor")], + diagnostic_commands=[_briefing_command(args, config, "doctor")], mutation_available=bootstrap_applicable, - findings=[_finding_payload(item) for item in failures], + findings=findings, ) return print("工作区还不能开始任务:") - for finding in failures: - print(" " + finding) - print(f"修复后运行:{_scoped_command(args, config, 'doctor')}") + for finding in findings: + print(f" {finding['status']} {finding['message']}") + print(f"修复后运行:{_briefing_command(args, config, 'doctor')}") if bootstrap_applicable: print( "缺失仓库均已配置 remote,可运行:" - + _scoped_command(args, config, "bootstrap", "--yes") + + _briefing_command(args, config, "bootstrap", "--yes") ) return lines = list_lines(config, read_budget=budget) diff --git a/src/dyro/console/assets.py b/src/dyro/console/assets.py index 2a5ff6b..5565a6b 100644 --- a/src/dyro/console/assets.py +++ b/src/dyro/console/assets.py @@ -27,8 +27,8 @@ class ConsoleAsset: ), "app.js": ( "text/javascript; charset=utf-8", - "28320497905d3c560e52f2ebd1bd3a807c1d5ae31da07ba3a9a534eabe3e081d", - 32622, + "beaa5e872359895d9af8aa0ae17c0ebbf60e369b4df4d46fd1e022164fe427af", + 33027, ), "styles.css": ( "text/css; charset=utf-8", diff --git a/src/dyro/console/assets/app.js b/src/dyro/console/assets/app.js index 2735829..c0c0c2a 100644 --- a/src/dyro/console/assets/app.js +++ b/src/dyro/console/assets/app.js @@ -291,7 +291,8 @@ function overviewState(attention, workspaces) { if (count(attention && attention.ready)) return "有工作可推进"; if (count(attention && attention.waiting)) return "等待外部条件"; if (count(attention && attention.paused)) return "存在已暂停工作"; - return "全部正常"; + if (readableWorkspaceCount(workspaces)) return "关注项未知"; + return "状态不完整"; } function workspaceMatter(summary) { @@ -308,7 +309,7 @@ function workspaceMatter(summary) { if (count(attention.ready)) return "有工作可以继续推进"; if (count(attention.waiting)) return "在等外部条件"; if (count(attention.paused)) return "有工作已暂停"; - return "可以先观察"; + return "摘要未列出关注项"; } function needsYouWorkspaces(workspaces) { @@ -336,9 +337,20 @@ function renderNeedsYou(workspaces, total) { const items = needsYouWorkspaces(workspaces); if (!items.length) { const readable = readableWorkspaceCount(workspaces); - root.append(element("p", readable + if (!readable) { + root.append(element("p", "项目状态还不完整,关注项未知。")); + return; + } + const listed = workspaces.some((summary) => { + if (text(summary.availability) !== "available") return false; + const attention = summary.attention_counts || {}; + return Boolean( + count(attention.ready) || count(attention.waiting) || count(attention.paused) + ); + }); + root.append(element("p", listed ? "现在没有需要你当场处理的项目。" - : "项目状态还不完整,关注项未知。")); + : "摘要未列出关注项。")); return; } for (const summary of items) { @@ -456,7 +468,6 @@ function renderCounts(attention) { card.className = "count"; if (key === "repair_required" && count(attention && attention[key])) card.dataset.level = "danger"; if (key === "needs_user" && count(attention && attention[key])) card.dataset.level = "warning"; - if (!count(attention && attention[key])) card.dataset.level = "success"; card.append(element("strong", String(count(attention && attention[key]))), element("span", label)); root.append(card); } diff --git a/src/dyro/console/inspection.py b/src/dyro/console/inspection.py index e92bb10..ce8bee3 100644 --- a/src/dyro/console/inspection.py +++ b/src/dyro/console/inspection.py @@ -626,7 +626,7 @@ def _safe_command(command: object, alias: object) -> bool: escaped_alias = re.escape(alias) return bool( re.fullmatch( - rf"dyro --workspace {escaped_alias}(?: (?:doctor|task next|objective (?:explain|tick|attention) [A-Za-z0-9][A-Za-z0-9._-]{{0,79}}))?", + rf"dyro --workspace {escaped_alias}(?: (?:doctor|objective (?:explain|tick|attention) [A-Za-z0-9][A-Za-z0-9._-]{{0,79}}))?", command, ) ) diff --git a/src/dyro/home.py b/src/dyro/home.py index 03e281e..36f9796 100644 --- a/src/dyro/home.py +++ b/src/dyro/home.py @@ -2165,7 +2165,7 @@ def _run_config_home( config: Config, record: WorkspaceRecord | None, dry_run: bool ) -> None: print("\n" + title(f"━━ {config.name} ━━")) - print(f"工作区 {value(config.root)}") + print(f"工作区 {value(config.name)}") failures = [finding for finding in doctor(config) if finding.startswith("FAIL")] if failures: print( diff --git a/tests/test_cli.py b/tests/test_cli.py index 72322d6..142c3bc 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1202,8 +1202,9 @@ def test_control_plane_next_only_offers_applicable_bootstrap(self) -> None: self.assertEqual(unavailable["commands"], []) self.assertEqual( unavailable["diagnostic_commands"], - [f"dyro --root {self.root.resolve()} doctor"], + ["dyro --workspace test-workspace doctor"], ) + self.assertNotIn(str(self.root.resolve()), json.dumps(unavailable)) config_path = self.root / "dyro.toml" config_path.write_text( @@ -1217,8 +1218,9 @@ def test_control_plane_next_only_offers_applicable_bootstrap(self) -> None: self.assertTrue(applicable["mutation_available"]) self.assertEqual( applicable["commands"], - [f"dyro --root {self.root.resolve()} bootstrap --yes"], + ["dyro --workspace test-workspace bootstrap --yes"], ) + self.assertNotIn(str(self.root.resolve()), json.dumps(applicable)) def test_control_plane_next_never_offers_bootstrap_through_symlink_parent( self, @@ -1667,6 +1669,7 @@ def test_bare_dyro_prints_the_same_follow_up_before_the_home_menu(self) -> None: self.assertIn("今天做什么", text) self.assertIn("做下一步,不打开编码工具", text) self.assertNotIn("objective apply", text) + self.assertNotIn(str(self.root.resolve()), text) def test_bare_dyro_without_objectives_does_not_invent_a_briefing(self) -> None: output = StringIO() diff --git a/tests/test_console_assets.py b/tests/test_console_assets.py index 2ef2a3c..f2d6952 100644 --- a/tests/test_console_assets.py +++ b/tests/test_console_assets.py @@ -84,6 +84,8 @@ def test_shell_exposes_a_semantic_command_center(self) -> None: self.assertIn("摘要未列出关注项".encode(), script.body) self.assertIn("关注项未知".encode(), script.body) self.assertNotIn("没有需要关注的事项".encode(), script.body) + self.assertNotIn("全部正常".encode(), script.body) + self.assertNotIn("可以先观察".encode(), script.body) self.assertIn("function renderTaskStatusCounts".encode(), script.body) self.assertIn("待签核".encode(), script.body) self.assertIn("尚未核验是否已合入".encode(), script.body) diff --git a/tests/test_console_inspection.py b/tests/test_console_inspection.py index 710a587..59b8db4 100644 --- a/tests/test_console_inspection.py +++ b/tests/test_console_inspection.py @@ -428,6 +428,21 @@ def test_parent_rejects_system_tool_probe_leak(self) -> None: self.assertNotIn("git", str(error.exception)) self.assertNotIn("argv", str(error.exception)) + def test_isolated_command_allowlist_rejects_task_next(self) -> None: + self.assertTrue( + IsolatedOverviewService._safe_command( + "dyro --workspace demo objective tick release", "demo" + ) + ) + self.assertTrue( + IsolatedOverviewService._safe_command("dyro --workspace demo doctor", "demo") + ) + self.assertFalse( + IsolatedOverviewService._safe_command( + "dyro --workspace demo task next", "demo" + ) + ) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_release_gates.py b/tests/test_release_gates.py index d9c2018..6fc24c6 100644 --- a/tests/test_release_gates.py +++ b/tests/test_release_gates.py @@ -32,7 +32,7 @@ def test_physics_train_refuses_published_0_6_9_tag(self) -> None: def test_0_7_release_runs_gates_without_claiming_1_0(self) -> None: stdout = StringIO() with redirect_stdout(stdout): - code = main(["--root", str(ROOT), "--release-tag", "v0.7.2"]) + code = main(["--root", str(ROOT), "--release-tag", "v0.7.3"]) self.assertEqual(code, 0) self.assertIn("0.7 gates present", stdout.getvalue()) self.assertNotIn("1.0 gates present", stdout.getvalue()) diff --git a/tests/test_release_source.py b/tests/test_release_source.py index 3ff3d45..3f0f177 100644 --- a/tests/test_release_source.py +++ b/tests/test_release_source.py @@ -108,6 +108,9 @@ def test_ci_no_longer_ships_agent_bridge_zero_effect_gate(self) -> None: self.assertIn("dyro-dispatch','SKILL.md", workflow) self.assertIn("dyro-dispatch','agents','openai.yaml", workflow) self.assertGreaterEqual(workflow.count("verify_bundle_stranger.py"), 2) + self.assertGreaterEqual( + workflow.count("find_spec('dyro.bridge') is None"), 2 + ) def test_default_wheel_stays_bridge_free(self) -> None: metadata = (ROOT / "pyproject.toml").read_text(encoding="utf-8") diff --git a/uv.lock b/uv.lock index bddbfa1..d8c846b 100644 --- a/uv.lock +++ b/uv.lock @@ -286,7 +286,7 @@ wheels = [ [[package]] name = "dyro" -version = "0.7.2" +version = "0.7.3" source = { editable = "." } dependencies = [ { name = "cryptography" },