From be83cc03b5d308c1cc034e376707ec07444cf9a8 Mon Sep 17 00:00:00 2001 From: avabbbb <89175608+avabbbb@users.noreply.github.com> Date: Thu, 8 Oct 2026 09:07:05 +0800 Subject: [PATCH 1/8] apply 0000 159-174 --- ...dex-offeru-public-release-standalone-credential-redaction.md | 2 ++ ...-09-03-codex-offeru-public-release-tauri-runtime-identity.md | 2 ++ ...6-09-03-codex-offeru-public-release-version-health-source.md | 2 ++ ...26-09-03-codex-offeru-public-release-web-runtime-identity.md | 2 ++ ...26-09-07-codex-offeru-public-release-real-data-acceptance.md | 2 ++ .../evals/reports/2026-09-23-zero-setup-onboarding.md | 2 ++ .../reports/2026-09-29-dogfood-onboarding-profile-pipeline.md | 2 ++ .../evals/reports/2026-09-30-agent-access-implementation.md | 2 ++ .../evals/reports/2026-09-30-agent-contract-audit.md | 2 ++ .../evals/reports/2026-09-30-automation-maturity-review.md | 2 ++ .../evals/reports/2026-09-30-project-code-review.md | 2 ++ .../evals/reports/2026-10-01-embedded-agent-source-migration.md | 2 ++ .../evals/reports/2026-10-02-career-skill-reference-review.md | 2 ++ .../evals/reports/2026-10-02-owner-journey-capability-audit.md | 2 ++ .../reports/2026-10-02-pr51-assessment-and-refactor-plan.md | 2 ++ 15 files changed, 30 insertions(+) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-09-03-codex-offeru-public-release-standalone-credential-redaction.md (86%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-09-03-codex-offeru-public-release-tauri-runtime-identity.md (91%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-09-03-codex-offeru-public-release-version-health-source.md (90%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-09-03-codex-offeru-public-release-web-runtime-identity.md (90%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-09-07-codex-offeru-public-release-real-data-acceptance.md (96%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-09-23-zero-setup-onboarding.md (98%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-09-29-dogfood-onboarding-profile-pipeline.md (99%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-09-30-agent-access-implementation.md (97%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-09-30-agent-contract-audit.md (98%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-09-30-automation-maturity-review.md (98%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-09-30-project-code-review.md (99%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-10-01-embedded-agent-source-migration.md (98%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-10-02-career-skill-reference-review.md (98%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-10-02-owner-journey-capability-audit.md (99%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-10-02-pr51-assessment-and-refactor-plan.md (99%) diff --git a/docs/evals/reports/2026-09-03-codex-offeru-public-release-standalone-credential-redaction.md b/docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-standalone-credential-redaction.md similarity index 86% rename from docs/evals/reports/2026-09-03-codex-offeru-public-release-standalone-credential-redaction.md rename to docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-standalone-credential-redaction.md index 7ed864a6..3346dc7c 100644 --- a/docs/evals/reports/2026-09-03-codex-offeru-public-release-standalone-credential-redaction.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-standalone-credential-redaction.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # OfferU Public Release — Standalone Credential Redaction 日期:2026-09-03 diff --git a/docs/evals/reports/2026-09-03-codex-offeru-public-release-tauri-runtime-identity.md b/docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-tauri-runtime-identity.md similarity index 91% rename from docs/evals/reports/2026-09-03-codex-offeru-public-release-tauri-runtime-identity.md rename to docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-tauri-runtime-identity.md index 1875d5b9..2ae15d30 100644 --- a/docs/evals/reports/2026-09-03-codex-offeru-public-release-tauri-runtime-identity.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-tauri-runtime-identity.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # OfferU Public Release — desktop runtime identity guard 日期:2026-09-03 diff --git a/docs/evals/reports/2026-09-03-codex-offeru-public-release-version-health-source.md b/docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-version-health-source.md similarity index 90% rename from docs/evals/reports/2026-09-03-codex-offeru-public-release-version-health-source.md rename to docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-version-health-source.md index 84c603d1..9d0469b8 100644 --- a/docs/evals/reports/2026-09-03-codex-offeru-public-release-version-health-source.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-version-health-source.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # OfferU Public Release — health version source audit 日期:2026-09-03 diff --git a/docs/evals/reports/2026-09-03-codex-offeru-public-release-web-runtime-identity.md b/docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-web-runtime-identity.md similarity index 90% rename from docs/evals/reports/2026-09-03-codex-offeru-public-release-web-runtime-identity.md rename to docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-web-runtime-identity.md index 3ecf21ad..5fc23d50 100644 --- a/docs/evals/reports/2026-09-03-codex-offeru-public-release-web-runtime-identity.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-09-03-codex-offeru-public-release-web-runtime-identity.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # OfferU Public Release — web/runtime identity guard 日期:2026-09-03 diff --git a/docs/evals/reports/2026-09-07-codex-offeru-public-release-real-data-acceptance.md b/docs/archive/pre-2026-10/evals/reports/2026-09-07-codex-offeru-public-release-real-data-acceptance.md similarity index 96% rename from docs/evals/reports/2026-09-07-codex-offeru-public-release-real-data-acceptance.md rename to docs/archive/pre-2026-10/evals/reports/2026-09-07-codex-offeru-public-release-real-data-acceptance.md index 01122a97..b133e6c3 100644 --- a/docs/evals/reports/2026-09-07-codex-offeru-public-release-real-data-acceptance.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-09-07-codex-offeru-public-release-real-data-acceptance.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # OfferU Public Release — 2026-09-07 真实数据验收 ## 结论 diff --git a/docs/evals/reports/2026-09-23-zero-setup-onboarding.md b/docs/archive/pre-2026-10/evals/reports/2026-09-23-zero-setup-onboarding.md similarity index 98% rename from docs/evals/reports/2026-09-23-zero-setup-onboarding.md rename to docs/archive/pre-2026-10/evals/reports/2026-09-23-zero-setup-onboarding.md index 7ea50b9e..825203cf 100644 --- a/docs/evals/reports/2026-09-23-zero-setup-onboarding.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-09-23-zero-setup-onboarding.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # Zero-Setup Beta implementation record — 2026-09-23 Status: onboarding and native packaging code implemented; runtime acceptance pending. No builds, tests, runtime probes, browser checks, or release publication have been executed in this task, per the project's development instructions. This is not a public-release acceptance report. diff --git a/docs/evals/reports/2026-09-29-dogfood-onboarding-profile-pipeline.md b/docs/archive/pre-2026-10/evals/reports/2026-09-29-dogfood-onboarding-profile-pipeline.md similarity index 99% rename from docs/evals/reports/2026-09-29-dogfood-onboarding-profile-pipeline.md rename to docs/archive/pre-2026-10/evals/reports/2026-09-29-dogfood-onboarding-profile-pipeline.md index 19edc7cd..ce81f782 100644 --- a/docs/evals/reports/2026-09-29-dogfood-onboarding-profile-pipeline.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-09-29-dogfood-onboarding-profile-pipeline.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # Dogfooding 会话记录 — 接入 → 简历导入 → 岗位 → 简历优化/复刻 — 2026-09-29(终态) **性质声明**:本报告是一次 dogfooding 会话(OMP/SWE-2 作为外部 Agent Harness 驱动 OfferU)的执行记录与评估,不是 `offeru-core-v1` 正式 suite 报告,不构成 Public Release 证据。状态枚举沿用 `docs/evals/README.md`(PASS/FAIL/BLOCKED/NOT_RUN/INVALID),岗位任务用 `PARTIAL` 标注"链路跑通但数据为种子"。 diff --git a/docs/evals/reports/2026-09-30-agent-access-implementation.md b/docs/archive/pre-2026-10/evals/reports/2026-09-30-agent-access-implementation.md similarity index 97% rename from docs/evals/reports/2026-09-30-agent-access-implementation.md rename to docs/archive/pre-2026-10/evals/reports/2026-09-30-agent-access-implementation.md index 5fde7038..faec3b6c 100644 --- a/docs/evals/reports/2026-09-30-agent-access-implementation.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-09-30-agent-access-implementation.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # 用户自带 Agent:接入实施方案与第一切片 日期:2026-09-30。覆盖已经使用通用 Agent、愿意投入求职质量的大学生、研究生和社招用户。覆盖目标用户不等于所有宿主自动直连已经实现。 diff --git a/docs/evals/reports/2026-09-30-agent-contract-audit.md b/docs/archive/pre-2026-10/evals/reports/2026-09-30-agent-contract-audit.md similarity index 98% rename from docs/evals/reports/2026-09-30-agent-contract-audit.md rename to docs/archive/pre-2026-10/evals/reports/2026-09-30-agent-contract-audit.md index f748ce7c..53bb6c83 100644 --- a/docs/evals/reports/2026-09-30-agent-contract-audit.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-09-30-agent-contract-audit.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # Unified Agent access: source audit and implementation plan Baseline: `e1dbbaf`; integration branch `feat/agent-contract-desktop-binding`. User instructions on 2026-09-30 supersede the copy-a-connection-prompt default and preserve a useful built-in Agent. This report records current evidence, not a claim of universal compatibility. diff --git a/docs/evals/reports/2026-09-30-automation-maturity-review.md b/docs/archive/pre-2026-10/evals/reports/2026-09-30-automation-maturity-review.md similarity index 98% rename from docs/evals/reports/2026-09-30-automation-maturity-review.md rename to docs/archive/pre-2026-10/evals/reports/2026-09-30-automation-maturity-review.md index 21e80ea2..682222d1 100644 --- a/docs/evals/reports/2026-09-30-automation-maturity-review.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-09-30-automation-maturity-review.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # OfferU 外部求职自动化成熟度评估 审计日期:2026-09-30。代码基线:本地 HEAD `67a1d9da49d81530a83d5fe40bb40f0f5c0d070d`,含现存未提交改动;不是已发布 main 或安装包验收。此次只审计、研究和运行隔离测试,没有更改业务实现,没有访问真实邮箱、BOSS 会话,也没有对外提交、发送或联系。 diff --git a/docs/evals/reports/2026-09-30-project-code-review.md b/docs/archive/pre-2026-10/evals/reports/2026-09-30-project-code-review.md similarity index 99% rename from docs/evals/reports/2026-09-30-project-code-review.md rename to docs/archive/pre-2026-10/evals/reports/2026-09-30-project-code-review.md index 8ae17f80..1502e895 100644 --- a/docs/evals/reports/2026-09-30-project-code-review.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-09-30-project-code-review.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # OfferU 全面代码评审与 Resident Agent 接入评估 日期:2026-09-30。评审基线:`e1dbbaf`,本次 fetch 后 `HEAD...origin/main` 为 `0 0`。这是风险导向的跨模块评审,不是逐行穷尽审计,也不是 Public Release E2E 验收。此次没有修复业务代码。 diff --git a/docs/evals/reports/2026-10-01-embedded-agent-source-migration.md b/docs/archive/pre-2026-10/evals/reports/2026-10-01-embedded-agent-source-migration.md similarity index 98% rename from docs/evals/reports/2026-10-01-embedded-agent-source-migration.md rename to docs/archive/pre-2026-10/evals/reports/2026-10-01-embedded-agent-source-migration.md index 9b4d05fb..a0490265 100644 --- a/docs/evals/reports/2026-10-01-embedded-agent-source-migration.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-10-01-embedded-agent-source-migration.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # Embedded Agent source migration Date: 2026-10-01. Status: source replacement and UI adaptation implemented; diff --git a/docs/evals/reports/2026-10-02-career-skill-reference-review.md b/docs/archive/pre-2026-10/evals/reports/2026-10-02-career-skill-reference-review.md similarity index 98% rename from docs/evals/reports/2026-10-02-career-skill-reference-review.md rename to docs/archive/pre-2026-10/evals/reports/2026-10-02-career-skill-reference-review.md index 3c1250d3..629c1c9c 100644 --- a/docs/evals/reports/2026-10-02-career-skill-reference-review.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-10-02-career-skill-reference-review.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # 阿酥与其他 Skill 仓库:原始文件核对及 OfferU 验收映射 日期:2026-10-02。状态:**PUBLIC SOURCE REVIEW / NOT INSTALLED / NOT RUNTIME ACCEPTED**。 diff --git a/docs/evals/reports/2026-10-02-owner-journey-capability-audit.md b/docs/archive/pre-2026-10/evals/reports/2026-10-02-owner-journey-capability-audit.md similarity index 99% rename from docs/evals/reports/2026-10-02-owner-journey-capability-audit.md rename to docs/archive/pre-2026-10/evals/reports/2026-10-02-owner-journey-capability-audit.md index 97bb9357..0c6b3473 100644 --- a/docs/evals/reports/2026-10-02-owner-journey-capability-audit.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-10-02-owner-journey-capability-audit.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # 用户完整求职旅程:当前能力核对 日期:2026-10-02。状态:**SOURCE AUDIT COMPLETE / LIVE OWNER JOURNEY NOT_RUN**。 diff --git a/docs/evals/reports/2026-10-02-pr51-assessment-and-refactor-plan.md b/docs/archive/pre-2026-10/evals/reports/2026-10-02-pr51-assessment-and-refactor-plan.md similarity index 99% rename from docs/evals/reports/2026-10-02-pr51-assessment-and-refactor-plan.md rename to docs/archive/pre-2026-10/evals/reports/2026-10-02-pr51-assessment-and-refactor-plan.md index 0c7dc823..9d5687f3 100644 --- a/docs/evals/reports/2026-10-02-pr51-assessment-and-refactor-plan.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-10-02-pr51-assessment-and-refactor-plan.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # PR #51 设计对照评估与下一轮重构方案 日期:2026-10-02。状态:**SOURCE ASSESSMENT / REFACTOR PLAN;独立测试结果另行记录**。 From 10360598527e5ecb3a079672270a822720d158db Mon Sep 17 00:00:00 2001 From: avabbbb <89175608+avabbbb@users.noreply.github.com> Date: Thu, 8 Oct 2026 09:08:15 +0800 Subject: [PATCH 2/8] apply 0000 174-189 --- .../reports/2026-10-02-pr51-luna-verification.md | 2 ++ .../2026-10-04-proposal-v2-engineering-migration.md | 2 ++ .../reports/2026-10-05-main-readiness-review.md | 2 ++ .../pre-2026-10}/evals/reports/README.md | 2 ++ .../artifacts/data-safety-01/settings-restart.png | Bin .../artifacts/data-safety-01/settings-stage.png | Bin .../artifacts/data-safety-03/export-ui-summary.txt | 0 .../data-safety-03/settings-demo-reset.png | Bin .../pre-2026-10}/history/development-history.md | 2 ++ .../product/agentic-interaction-policy.md | 2 ++ .../pre-2026-10}/product/career-memory-contract.md | 2 ++ .../pre-2026-10}/product/career-operations-loop.md | 2 ++ .../pre-2026-10}/product/current-product.md | 2 ++ .../product/entry-onboarding-and-dogfood.md | 2 ++ .../product/offeru-career-os-blueprint.html | 0 15 files changed, 20 insertions(+) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-10-02-pr51-luna-verification.md (99%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-10-04-proposal-v2-engineering-migration.md (99%) rename docs/{ => archive/pre-2026-10}/evals/reports/2026-10-05-main-readiness-review.md (98%) rename docs/{ => archive/pre-2026-10}/evals/reports/README.md (92%) rename docs/{ => archive/pre-2026-10}/evals/reports/artifacts/data-safety-01/settings-restart.png (100%) rename docs/{ => archive/pre-2026-10}/evals/reports/artifacts/data-safety-01/settings-stage.png (100%) rename docs/{ => archive/pre-2026-10}/evals/reports/artifacts/data-safety-03/export-ui-summary.txt (100%) rename docs/{ => archive/pre-2026-10}/evals/reports/artifacts/data-safety-03/settings-demo-reset.png (100%) rename docs/{ => archive/pre-2026-10}/history/development-history.md (98%) rename docs/{ => archive/pre-2026-10}/product/agentic-interaction-policy.md (99%) rename docs/{ => archive/pre-2026-10}/product/career-memory-contract.md (98%) rename docs/{ => archive/pre-2026-10}/product/career-operations-loop.md (99%) rename docs/{ => archive/pre-2026-10}/product/current-product.md (99%) rename docs/{ => archive/pre-2026-10}/product/entry-onboarding-and-dogfood.md (98%) rename docs/{ => archive/pre-2026-10}/product/offeru-career-os-blueprint.html (100%) diff --git a/docs/evals/reports/2026-10-02-pr51-luna-verification.md b/docs/archive/pre-2026-10/evals/reports/2026-10-02-pr51-luna-verification.md similarity index 99% rename from docs/evals/reports/2026-10-02-pr51-luna-verification.md rename to docs/archive/pre-2026-10/evals/reports/2026-10-02-pr51-luna-verification.md index 51441437..2e67c2a5 100644 --- a/docs/evals/reports/2026-10-02-pr51-luna-verification.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-10-02-pr51-luna-verification.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # PR #51 Luna 独立验收报告 日期:2026-10-02。状态:**独立测试报告;不代表 Goal A/B 或 Public Release 通过。** diff --git a/docs/evals/reports/2026-10-04-proposal-v2-engineering-migration.md b/docs/archive/pre-2026-10/evals/reports/2026-10-04-proposal-v2-engineering-migration.md similarity index 99% rename from docs/evals/reports/2026-10-04-proposal-v2-engineering-migration.md rename to docs/archive/pre-2026-10/evals/reports/2026-10-04-proposal-v2-engineering-migration.md index 489e597d..6a32f1a8 100644 --- a/docs/evals/reports/2026-10-04-proposal-v2-engineering-migration.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-10-04-proposal-v2-engineering-migration.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # Proposal v2 engineering migration evidence Status: **IN_PROGRESS — NOT PRODUCT ACCEPTED**. Updated 2026-10-05. diff --git a/docs/evals/reports/2026-10-05-main-readiness-review.md b/docs/archive/pre-2026-10/evals/reports/2026-10-05-main-readiness-review.md similarity index 98% rename from docs/evals/reports/2026-10-05-main-readiness-review.md rename to docs/archive/pre-2026-10/evals/reports/2026-10-05-main-readiness-review.md index 513e12b7..dc2d421b 100644 --- a/docs/evals/reports/2026-10-05-main-readiness-review.md +++ b/docs/archive/pre-2026-10/evals/reports/2026-10-05-main-readiness-review.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # Main Readiness Review / 可合入 main 的改动梗概 Date: 2026-10-05 diff --git a/docs/evals/reports/README.md b/docs/archive/pre-2026-10/evals/reports/README.md similarity index 92% rename from docs/evals/reports/README.md rename to docs/archive/pre-2026-10/evals/reports/README.md index 6b841505..3775b698 100644 --- a/docs/evals/reports/README.md +++ b/docs/archive/pre-2026-10/evals/reports/README.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:历史证据,见 09-quality-and-release.md(位于 `docs/`)。 + # Eval 报告 本目录只接收能够证明一个可定位 commit 当前行为的正式 Eval 报告。预评估、测试计划、聊天总结、只读审计和已失效运行记录不放在这里;需要追溯时使用 Git 历史。 diff --git a/docs/evals/reports/artifacts/data-safety-01/settings-restart.png b/docs/archive/pre-2026-10/evals/reports/artifacts/data-safety-01/settings-restart.png similarity index 100% rename from docs/evals/reports/artifacts/data-safety-01/settings-restart.png rename to docs/archive/pre-2026-10/evals/reports/artifacts/data-safety-01/settings-restart.png diff --git a/docs/evals/reports/artifacts/data-safety-01/settings-stage.png b/docs/archive/pre-2026-10/evals/reports/artifacts/data-safety-01/settings-stage.png similarity index 100% rename from docs/evals/reports/artifacts/data-safety-01/settings-stage.png rename to docs/archive/pre-2026-10/evals/reports/artifacts/data-safety-01/settings-stage.png diff --git a/docs/evals/reports/artifacts/data-safety-03/export-ui-summary.txt b/docs/archive/pre-2026-10/evals/reports/artifacts/data-safety-03/export-ui-summary.txt similarity index 100% rename from docs/evals/reports/artifacts/data-safety-03/export-ui-summary.txt rename to docs/archive/pre-2026-10/evals/reports/artifacts/data-safety-03/export-ui-summary.txt diff --git a/docs/evals/reports/artifacts/data-safety-03/settings-demo-reset.png b/docs/archive/pre-2026-10/evals/reports/artifacts/data-safety-03/settings-demo-reset.png similarity index 100% rename from docs/evals/reports/artifacts/data-safety-03/settings-demo-reset.png rename to docs/archive/pre-2026-10/evals/reports/artifacts/data-safety-03/settings-demo-reset.png diff --git a/docs/history/development-history.md b/docs/archive/pre-2026-10/history/development-history.md similarity index 98% rename from docs/history/development-history.md rename to docs/archive/pre-2026-10/history/development-history.md index 02f36b39..3556349f 100644 --- a/docs/history/development-history.md +++ b/docs/archive/pre-2026-10/history/development-history.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:README.md(位于 `docs/`)。 + # OfferU Development History / 发展历史 Status: **HISTORICAL SYNTHESIS — NOT CURRENT PRODUCT AUTHORITY** diff --git a/docs/product/agentic-interaction-policy.md b/docs/archive/pre-2026-10/product/agentic-interaction-policy.md similarity index 99% rename from docs/product/agentic-interaction-policy.md rename to docs/archive/pre-2026-10/product/agentic-interaction-policy.md index 43f60db1..b7456f98 100644 --- a/docs/product/agentic-interaction-policy.md +++ b/docs/archive/pre-2026-10/product/agentic-interaction-policy.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:02-interaction-design.md(位于 `docs/`)。 + # Agentic Interaction Policy Status: **CURRENT PRODUCT AUTHORITY** diff --git a/docs/product/career-memory-contract.md b/docs/archive/pre-2026-10/product/career-memory-contract.md similarity index 98% rename from docs/product/career-memory-contract.md rename to docs/archive/pre-2026-10/product/career-memory-contract.md index 1150beda..e62dc3f7 100644 --- a/docs/product/career-memory-contract.md +++ b/docs/archive/pre-2026-10/product/career-memory-contract.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:03-module-profile.md(位于 `docs/`)。 + # Career Memory & Context Contract Status: **CURRENT PRODUCT DETAIL / IMPLEMENTATION CONTRACT** diff --git a/docs/product/career-operations-loop.md b/docs/archive/pre-2026-10/product/career-operations-loop.md similarity index 99% rename from docs/product/career-operations-loop.md rename to docs/archive/pre-2026-10/product/career-operations-loop.md index 5e26b2b9..c5b809cd 100644 --- a/docs/product/career-operations-loop.md +++ b/docs/archive/pre-2026-10/product/career-operations-loop.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:04、06、07(位于 `docs/`)。 + # OfferU Career Operations Loop Status: CURRENT PRODUCT DETAIL / OWNER-DOGFOOD CONTRACT diff --git a/docs/product/current-product.md b/docs/archive/pre-2026-10/product/current-product.md similarity index 99% rename from docs/product/current-product.md rename to docs/archive/pre-2026-10/product/current-product.md index 084acf13..ba072f72 100644 --- a/docs/product/current-product.md +++ b/docs/archive/pre-2026-10/product/current-product.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:01-overall-design.md(位于 `docs/`)。 + # OfferU Current Product North Star diff --git a/docs/product/entry-onboarding-and-dogfood.md b/docs/archive/pre-2026-10/product/entry-onboarding-and-dogfood.md similarity index 98% rename from docs/product/entry-onboarding-and-dogfood.md rename to docs/archive/pre-2026-10/product/entry-onboarding-and-dogfood.md index 46c9c395..000d646f 100644 --- a/docs/product/entry-onboarding-and-dogfood.md +++ b/docs/archive/pre-2026-10/product/entry-onboarding-and-dogfood.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:01-overall-design.md、08-module-agent-runtime.md、09-quality-and-release.md(位于 `docs/`)。 + # OfferU Entry, Onboarding & Dogfood Contract Status: **CURRENT PRODUCT DETAIL** diff --git a/docs/product/offeru-career-os-blueprint.html b/docs/archive/pre-2026-10/product/offeru-career-os-blueprint.html similarity index 100% rename from docs/product/offeru-career-os-blueprint.html rename to docs/archive/pre-2026-10/product/offeru-career-os-blueprint.html From 1f7d960303264e8ade9cfe69868c5f9e5365f473 Mon Sep 17 00:00:00 2001 From: avabbbb <89175608+avabbbb@users.noreply.github.com> Date: Thu, 8 Oct 2026 09:08:43 +0800 Subject: [PATCH 3/8] apply 0000 189-204 --- .../product/owner-career-journey.md | 2 ++ .../product/proactive-career-director.md | 2 ++ .../product/resume-canvas-design.md | 2 ++ .../skill-first-job-workspace-story.md | 2 ++ .../archive/pre-2026-10/root/ARCHITECTURE.md | 2 ++ .../archive/pre-2026-10/root/CONTEXT.md | 2 ++ .../archive/pre-2026-10/root/GOAL.md | 2 ++ .../archive/pre-2026-10/root/HANDOFF.md | 2 ++ .../archive/pre-2026-10/root/INTERNAL_BETA.md | 2 ++ .../archive/pre-2026-10/root/QUALITY_SCORE.md | 2 ++ .../archive/pre-2026-10/root/RELIABILITY.md | 2 ++ .../archive/pre-2026-10/root/STATUS.md | 2 ++ docs/evidence/README.md | 24 +++++++++++++++++++ .../evolve-bench-schema.json | 0 docs/{evals => evidence}/report-schema.json | 0 15 files changed, 48 insertions(+) rename docs/{ => archive/pre-2026-10}/product/owner-career-journey.md (99%) rename docs/{ => archive/pre-2026-10}/product/proactive-career-director.md (99%) rename docs/{ => archive/pre-2026-10}/product/resume-canvas-design.md (99%) rename docs/{ => archive/pre-2026-10}/product/skill-first-job-workspace-story.md (98%) rename ARCHITECTURE.md => docs/archive/pre-2026-10/root/ARCHITECTURE.md (97%) rename CONTEXT.md => docs/archive/pre-2026-10/root/CONTEXT.md (97%) rename GOAL.md => docs/archive/pre-2026-10/root/GOAL.md (99%) rename HANDOFF.md => docs/archive/pre-2026-10/root/HANDOFF.md (94%) rename INTERNAL_BETA.md => docs/archive/pre-2026-10/root/INTERNAL_BETA.md (97%) rename QUALITY_SCORE.md => docs/archive/pre-2026-10/root/QUALITY_SCORE.md (98%) rename RELIABILITY.md => docs/archive/pre-2026-10/root/RELIABILITY.md (99%) rename STATUS.md => docs/archive/pre-2026-10/root/STATUS.md (95%) create mode 100644 docs/evidence/README.md rename docs/{evals => evidence}/evolve-bench-schema.json (100%) rename docs/{evals => evidence}/report-schema.json (100%) diff --git a/docs/product/owner-career-journey.md b/docs/archive/pre-2026-10/product/owner-career-journey.md similarity index 99% rename from docs/product/owner-career-journey.md rename to docs/archive/pre-2026-10/product/owner-career-journey.md index 30960cbe..2a10bd27 100644 --- a/docs/product/owner-career-journey.md +++ b/docs/archive/pre-2026-10/product/owner-career-journey.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:01–07 各模块(位于 `docs/`)。 + # 真实求职用户的完整体验与验收契约 状态:**CURRENT PRODUCT DETAIL / OWNER ACCEPTANCE TARGET** diff --git a/docs/product/proactive-career-director.md b/docs/archive/pre-2026-10/product/proactive-career-director.md similarity index 99% rename from docs/product/proactive-career-director.md rename to docs/archive/pre-2026-10/product/proactive-career-director.md index 45e4dcd5..2a3f7400 100644 --- a/docs/product/proactive-career-director.md +++ b/docs/archive/pre-2026-10/product/proactive-career-director.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:08-module-agent-runtime.md(§8)、03、07(位于 `docs/`)。 + # Proactive Career Director Status: **CURRENT PRODUCT DETAIL / IMPLEMENTATION CONTRACT** diff --git a/docs/product/resume-canvas-design.md b/docs/archive/pre-2026-10/product/resume-canvas-design.md similarity index 99% rename from docs/product/resume-canvas-design.md rename to docs/archive/pre-2026-10/product/resume-canvas-design.md index 28701627..03873445 100644 --- a/docs/product/resume-canvas-design.md +++ b/docs/archive/pre-2026-10/product/resume-canvas-design.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:05-module-resume.md(位于 `docs/`)。 + # Resume Canvas — Document-first, AI-assisted Resume Experience Status: **CURRENT PRODUCT DESIGN / OWNER-DOGFOOD TARGET** diff --git a/docs/product/skill-first-job-workspace-story.md b/docs/archive/pre-2026-10/product/skill-first-job-workspace-story.md similarity index 98% rename from docs/product/skill-first-job-workspace-story.md rename to docs/archive/pre-2026-10/product/skill-first-job-workspace-story.md index 3321f436..cc464159 100644 --- a/docs/product/skill-first-job-workspace-story.md +++ b/docs/archive/pre-2026-10/product/skill-first-job-workspace-story.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:01-overall-design.md(位于 `docs/`)。 + # OfferU Skill-first Agent Entry, Job Workspace & Product Story Status: **DESIGN RATIONALE / DISTILLED REFERENCE** diff --git a/ARCHITECTURE.md b/docs/archive/pre-2026-10/root/ARCHITECTURE.md similarity index 97% rename from ARCHITECTURE.md rename to docs/archive/pre-2026-10/root/ARCHITECTURE.md index 7fb81bbe..d1118247 100644 --- a/ARCHITECTURE.md +++ b/docs/archive/pre-2026-10/root/ARCHITECTURE.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:01-overall-design.md、08-module-agent-runtime.md(位于 `docs/`)。 + # OfferU Architecture diff --git a/CONTEXT.md b/docs/archive/pre-2026-10/root/CONTEXT.md similarity index 97% rename from CONTEXT.md rename to docs/archive/pre-2026-10/root/CONTEXT.md index 694cfdde..660f6df8 100644 --- a/CONTEXT.md +++ b/docs/archive/pre-2026-10/root/CONTEXT.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:01-overall-design.md(§10 术语)(位于 `docs/`)。 + # OfferU Context Status: **CURRENT DOMAIN AUTHORITY** diff --git a/GOAL.md b/docs/archive/pre-2026-10/root/GOAL.md similarity index 99% rename from GOAL.md rename to docs/archive/pre-2026-10/root/GOAL.md index 56d07dfe..755cb062 100644 --- a/GOAL.md +++ b/docs/archive/pre-2026-10/root/GOAL.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:01-overall-design.md、09-quality-and-release.md(位于 `docs/`)。 + # OfferU Public Release Goal ## Autonomous Production Readiness Program diff --git a/HANDOFF.md b/docs/archive/pre-2026-10/root/HANDOFF.md similarity index 94% rename from HANDOFF.md rename to docs/archive/pre-2026-10/root/HANDOFF.md index 04eeb765..cc33e3e7 100644 --- a/HANDOFF.md +++ b/docs/archive/pre-2026-10/root/HANDOFF.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:RELEASE_CHECKLIST.md、KNOWN_ISSUES.md(实时状态)(位于 `docs/`)。 + # OfferU Handoff Status: **CURRENT CONTINUATION INDEX** diff --git a/INTERNAL_BETA.md b/docs/archive/pre-2026-10/root/INTERNAL_BETA.md similarity index 97% rename from INTERNAL_BETA.md rename to docs/archive/pre-2026-10/root/INTERNAL_BETA.md index 73fee74b..2ce4b680 100644 --- a/INTERNAL_BETA.md +++ b/docs/archive/pre-2026-10/root/INTERNAL_BETA.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:09-quality-and-release.md(位于 `docs/`)。 + > [!NOTE] > **HISTORICAL INTERNAL-BETA WALKTHROUGH.** Keep this for regression/demo archaeology. It is not the current onboarding or product authority; use `docs/product/current-product.md`, `STATUS.md`, and `HANDOFF.md` for current direction. diff --git a/QUALITY_SCORE.md b/docs/archive/pre-2026-10/root/QUALITY_SCORE.md similarity index 98% rename from QUALITY_SCORE.md rename to docs/archive/pre-2026-10/root/QUALITY_SCORE.md index 6c1e5105..33e47c4f 100644 --- a/QUALITY_SCORE.md +++ b/docs/archive/pre-2026-10/root/QUALITY_SCORE.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:09-quality-and-release.md(位于 `docs/`)。 + # OfferU Public Release Quality Score 更新时间:2026-09-09 diff --git a/RELIABILITY.md b/docs/archive/pre-2026-10/root/RELIABILITY.md similarity index 99% rename from RELIABILITY.md rename to docs/archive/pre-2026-10/root/RELIABILITY.md index b354e872..2a025179 100644 --- a/RELIABILITY.md +++ b/docs/archive/pre-2026-10/root/RELIABILITY.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:09-quality-and-release.md(位于 `docs/`)。 + # OfferU Public Release Reliability 更新时间:2026-09-02 diff --git a/STATUS.md b/docs/archive/pre-2026-10/root/STATUS.md similarity index 95% rename from STATUS.md rename to docs/archive/pre-2026-10/root/STATUS.md index d9ed1aac..1948f37e 100644 --- a/STATUS.md +++ b/docs/archive/pre-2026-10/root/STATUS.md @@ -1,3 +1,5 @@ +> **已归档(2026-10)**:本文不再是当前权威。当前文档:RELEASE_CHECKLIST.md、KNOWN_ISSUES.md(实时状态)(位于 `docs/`)。 + # OfferU Status Status: **CURRENT CHECKOUT REVIEW — NOT RELEASE ACCEPTANCE** diff --git a/docs/evidence/README.md b/docs/evidence/README.md new file mode 100644 index 00000000..31249fd3 --- /dev/null +++ b/docs/evidence/README.md @@ -0,0 +1,24 @@ +# 验收证据 + +本目录存放**证据,不存放设计**。设计权威见 [docs/README.md](../README.md),证据规则见 [09 · 质量与发布](../09-quality-and-release.md)。 + +| 路径 | 内容 | +| --- | --- | +| `report-schema.json` | 正式 Eval 报告中 `eval-summary` JSON 代码块的机器 schema | +| `evolve-bench-schema.json` | OfferU-EvolveBench 的 case schema | +| `reports/YYYY-MM-DD---.md` | 能证明某个可定位 commit 当前行为的正式报告 | +| `reports/artifacts///...` | 脱敏截图、trace、机器输出(默认只保留在本地,不提交) | + +## 报告最小结构 + +1. 运行身份:suite / version / run ID、commit、dirty 状态、执行宿主和模型。 +2. 环境:OS、Python、Node、CLI、宿主和 adapter、provider 和 model、fixture 隔离。 +3. 总结:各状态的数量、目标范围、明确的结论。 +4. 每个 Task 的 trials、命令或交互、退出码、耗时、轨迹证据、结果证据。 +5. 脱敏后的失败复现、限制、成本、下一步。 +6. 与正文一致、能被 JSON parser 读取的 `eval-summary` 代码块,符合 `report-schema.json`。 + +出现以下任一情况,报告即为 `INVALID`:把跳过、阻塞、模拟返回或模型自评记成 `PASS`;正文、JSON、命令摘要或附件互相矛盾;没有记录 commit 或 dirty 状态;缺少必需的轨迹或结果证据;证据里有未脱敏的凭据或个人信息。 + +**当前没有有效的正式 baseline。** 2026-10 之前的报告已归档到 `docs/archive/pre-2026-10/evals/reports/`,只作为对应 commit 当时的历史证据。 + diff --git a/docs/evals/evolve-bench-schema.json b/docs/evidence/evolve-bench-schema.json similarity index 100% rename from docs/evals/evolve-bench-schema.json rename to docs/evidence/evolve-bench-schema.json diff --git a/docs/evals/report-schema.json b/docs/evidence/report-schema.json similarity index 100% rename from docs/evals/report-schema.json rename to docs/evidence/report-schema.json From dec881f21dd10a346af27511675747f546757821 Mon Sep 17 00:00:00 2001 From: avabbbb <89175608+avabbbb@users.noreply.github.com> Date: Thu, 8 Oct 2026 09:09:32 +0800 Subject: [PATCH 4/8] apply 0000 204-209 --- docs/evidence/reports/.gitkeep | 0 extension/src/background.ts | 2 +- extension/src/rule-packs/contracts.ts | 2 +- extension/wxt.config.ts | 2 +- integrations/conformance/README.md | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 docs/evidence/reports/.gitkeep diff --git a/docs/evidence/reports/.gitkeep b/docs/evidence/reports/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/extension/src/background.ts b/extension/src/background.ts index 324c880e..e81c4e1f 100644 --- a/extension/src/background.ts +++ b/extension/src/background.ts @@ -810,7 +810,7 @@ const BOSS_COOKIE_DOMAIN = "zhipin.com"; * * 三条硬约束: * 1. Cookie 只在本次调用内存在 —— 不写 `chrome.storage.local`,不写日志, - * 也不回传给 popup(见 docs/architecture/browser-extension.md); + * 也不回传给 popup(见 docs/04-module-job-workspace.md); * 2. 缺少 wt2 时直接失败,不把"写入了别的字段"当成登录成功; * 3. 以后端回读到的状态为准,不以写入请求返回 200 判定成功。 */ diff --git a/extension/src/rule-packs/contracts.ts b/extension/src/rule-packs/contracts.ts index ea05a8ec..1c451982 100644 --- a/extension/src/rule-packs/contracts.ts +++ b/extension/src/rule-packs/contracts.ts @@ -1,6 +1,6 @@ // ============================================= // SiteRulePack v1 — 类型契约 -// 事实源:docs/architecture/site-rule-pack-v1.md +// 事实源:本文件;产品边界见 docs/04-module-job-workspace.md,原始规格见 docs/archive/pre-2026-10/architecture/site-rule-pack-v1.md // 规则是数据;复杂控件行为是扩展内置 ControlDriver 代码。 // ============================================= diff --git a/extension/wxt.config.ts b/extension/wxt.config.ts index e02c890f..f95122c2 100644 --- a/extension/wxt.config.ts +++ b/extension/wxt.config.ts @@ -27,7 +27,7 @@ export default defineConfig({ // (src/background.ts 的 syncBossScraperSession),不读取其他任何站点。 permissions: ["storage", "activeTab", "tabs", "scripting", "clipboardWrite", "offscreen", "cookies"], host_permissions: ["http://127.0.0.1/*", "http://localhost/*"], - // 招聘站点权限按需申请(见 docs/architecture/browser-extension.md), + // 招聘站点权限按需申请(见 docs/04-module-job-workspace.md), // 仅限常见招聘站点;用户连接对应站点登录态时才逐站申请,不再开放通配所有 http/https。 optional_host_permissions: [ "https://*.zhipin.com/*", diff --git a/integrations/conformance/README.md b/integrations/conformance/README.md index cd5b4e3b..66786757 100644 --- a/integrations/conformance/README.md +++ b/integrations/conformance/README.md @@ -35,4 +35,4 @@ runner 退出码:0 = 全部场景通过;1 = 任一失败。失败输出到 s - Slice 1 起把 `fake_harness.py` 替换为真实 adapter(DSH plugin / Codex app-server), 场景文件保持不变即可复跑; -- 当前 Host/Runtime 验收边界见 `docs/architecture/harness-integrations.md` 与 `docs/evals/LIVE_EVAL.md`;本目录保留较低层协议 conformance,不再依赖已移除的 migration-roadmap 文档。 +- 当前 Host/Runtime 验收边界见 `docs/08-module-agent-runtime.md` 与 `docs/09-quality-and-release.md`;本目录保留较低层协议 conformance,不再依赖已移除的 migration-roadmap 文档。 From 245363825efd9dfee2ffbdfb6aa72c768b98dba9 Mon Sep 17 00:00:00 2001 From: avabbbb <89175608+avabbbb@users.noreply.github.com> Date: Thu, 8 Oct 2026 09:10:22 +0800 Subject: [PATCH 5/8] feat: Agent input never locks and navigation preserves runs (0001) --- backend/app/routes/main_agent.py | 41 +++- backend/app/services/agent_runtime.py | 6 + backend/app/services/embedded_agent_host.py | 48 ++++ backend/tests/test_agent_never_locks.py | 103 ++++++++ docs/02-interaction-design.md | 17 +- .../components/workbench/AgentPanel.test.tsx | 113 +++++++++ .../src/components/workbench/AgentPanel.tsx | 230 ++++++++++++++++-- frontend/src/lib/api.ts | 6 + 8 files changed, 535 insertions(+), 29 deletions(-) create mode 100644 backend/tests/test_agent_never_locks.py diff --git a/backend/app/routes/main_agent.py b/backend/app/routes/main_agent.py index 38794f4d..90931312 100644 --- a/backend/app/routes/main_agent.py +++ b/backend/app/routes/main_agent.py @@ -945,12 +945,15 @@ async def run_and_record() -> dict[str, Any]: assistant_message = str(result.get("assistant_message") or "").strip() completed_conversation = conversation if assistant_message: + # Re-read: steering messages and parked sibling Runs may have + # appended to this conversation while the Run was executing. + latest_conversation = get_conversation(conversation["id"]) or conversation completed_conversation = await _ui_operation_outputs( "save_harness_conversation", { "conversation_id": conversation["id"], "messages": [ - *conversation["messages"], + *(latest_conversation.get("messages") or []), {"role": "assistant", "content": assistant_message}, ], }, @@ -1245,6 +1248,42 @@ async def resume_runtime_run(run_id: str) -> dict[str, Any]: raise HTTPException(status_code=400, detail=safe_error_message(exc)) +class RuntimeSteerBody(BaseModel): + message: str = Field(min_length=1, max_length=4000) + + +@runtime_router.post("/runtime/runs/{run_id}/steer") +async def steer_runtime_run(run_id: str, body: RuntimeSteerBody) -> dict[str, Any]: + """Deliver a mid-run user message to the live Run (D-1: input never locks). + + ``not_running`` means the Run is not executing a prompt; the client then + sends the text as a normal follow-up turn. Nothing here approves anything. + """ + try: + provider = await _provider_for_run(run_id) + steer = getattr(provider, "steer_run", None) + if steer is None: + return {"ok": True, "disposition": "not_running"} + result = await steer(run_id, message=body.message) + except ValueError as exc: + raise HTTPException(status_code=404, detail=safe_error_message(exc)) + if result.get("disposition") == "steered": + conversation_id = str((result.get("run") or {}).get("conversation_id") or "") + conversation = get_conversation(conversation_id) if conversation_id else None + if conversation is not None: + await _ui_operation_outputs( + "save_harness_conversation", + { + "conversation_id": conversation_id, + "messages": [ + *(conversation.get("messages") or []), + {"role": "user", "content": body.message}, + ], + }, + ) + return {"ok": True, "disposition": result.get("disposition") or "not_running"} + + @runtime_router.post("/runtime/runs/{run_id}/abort") async def abort_runtime_run(run_id: str) -> dict[str, Any]: try: diff --git a/backend/app/services/agent_runtime.py b/backend/app/services/agent_runtime.py index f0898bd3..3ff0e63a 100644 --- a/backend/app/services/agent_runtime.py +++ b/backend/app/services/agent_runtime.py @@ -89,6 +89,7 @@ "run.waiting_confirmation": "approval.requested", "input.required": "approval.requested", "input.answered": "approval.resolved", + "input.steered": "reasoning.status", "run.waiting_input": "approval.requested", "continuation.requested": "reasoning.status", "approval.requested": "approval.requested", @@ -294,6 +295,11 @@ async def abort_run(self, run_id: str) -> dict[str, Any]: return await abort_embedded_agent_run(run_id) + async def steer_run(self, run_id: str, *, message: str) -> dict[str, Any]: + from app.services.embedded_agent_host import steer_embedded_agent_run + + return await steer_embedded_agent_run(run_id, message) + class ReplayAgentRunProvider: """Durable, deterministic Main Agent provider for local acceptance paths.""" diff --git a/backend/app/services/embedded_agent_host.py b/backend/app/services/embedded_agent_host.py index fd5e8e8d..f2636ca2 100644 --- a/backend/app/services/embedded_agent_host.py +++ b/backend/app/services/embedded_agent_host.py @@ -45,6 +45,7 @@ from app.services.embedded_agent_worker import ( PROTOCOL_VERSION, EmbeddedAgentWorker, + EmbeddedAgentWorkerError, get_embedded_agent_worker, ) from app.services.security_redaction import safe_error_message @@ -991,6 +992,16 @@ async def _request_user_input(args: dict[str, Any]) -> dict[str, Any]: await publish("run.completed", {"status": "completed"}) await active_worker.dispose_run(run_id) current = await load_agent_run(run_id) or current + if current["status"] in _PARKED_WAITING_STATUSES and active_worker.active_run_id == run_id: + # D-1: a Run that waits on the user must not hold the single + # embedded worker. Its session file stays on disk; approvals and + # answers already resume through continuations, which restart the + # worker from that file. Parking lets the user keep chatting. + try: + await active_worker.dispose_run(run_id) + except Exception: + pass + current = await load_agent_run(run_id) or current await persist_and_publish( "guardian.reviewed", { @@ -1339,6 +1350,43 @@ async def reject_embedded_agent_action( return result +_PARKED_WAITING_STATUSES = frozenset({"waiting_confirmation", "waiting_input", "waiting_decision"}) + + +async def steer_embedded_agent_run( + run_id: str, + message: str, + *, + worker: EmbeddedAgentWorker | None = None, +) -> dict[str, Any]: + """Inject a user message into a live Run at its next tool boundary. + + Steering is collaboration input, never authorization. When the Run is not + executing a prompt right now (finished, parked or waiting on the user) the + caller gets ``not_running`` and should start a follow-up turn instead. + """ + + text = str(message or "").strip() + if not text: + raise ValueError("引导消息不能为空。") + run = await load_agent_run(run_id) + if run is None: + raise ValueError(f"Agent Run {run_id} 不存在。") + active_worker = worker or get_embedded_agent_worker() + if active_worker.active_run_id != run_id or not active_worker.prompt_active: + return {"ok": True, "disposition": "not_running", "run": run} + try: + await active_worker.steer_run(run_id, text[:4000]) + except EmbeddedAgentWorkerError: + return {"ok": True, "disposition": "not_running", "run": run} + await append_agent_run_event( + run_id, + event_type="input.steered", + payload={"characters": len(text)}, + ) + return {"ok": True, "disposition": "steered", "run": run} + + async def abort_embedded_agent_run( run_id: str, *, diff --git a/backend/tests/test_agent_never_locks.py b/backend/tests/test_agent_never_locks.py new file mode 100644 index 00000000..bc93b616 --- /dev/null +++ b/backend/tests/test_agent_never_locks.py @@ -0,0 +1,103 @@ +"""docs/02 D-1 / D-2: a Run waiting on the user never holds the Agent hostage. + +* A Run that reaches ``waiting_confirmation`` parks: it releases the single + embedded worker so the user can start another turn, while its session file + and plan stay durable for the later approval continuation. +* A mid-run user message is steered into the live Run, or reported as + ``not_running`` so the client sends it as a normal follow-up turn. +""" +from __future__ import annotations + +import asyncio +import os +import sys +import unittest +from pathlib import Path +from typing import Any + +BACKEND_DIR = Path(__file__).resolve().parents[1] +os.chdir(BACKEND_DIR) +if str(BACKEND_DIR) not in sys.path: + sys.path.insert(0, str(BACKEND_DIR)) +TESTS_DIR = Path(__file__).resolve().parent +if str(TESTS_DIR) not in sys.path: + sys.path.insert(0, str(TESTS_DIR)) + +from app.database import init_db +from app.services.agent_run_state import create_agent_run, list_agent_run_events, load_agent_run +from app.services.embedded_agent_host import start_embedded_agent_run, steer_embedded_agent_run +from test_embedded_agent_host import FakeEmbeddedWorker + +PROVIDER = {"name": "fixture", "model": "fixture"} +METADATA = {"runtime": "python_agent", "provider_id": "embedded"} + + +class SteerableWorker: + def __init__(self, run_id: str, *, prompt_active: bool) -> None: + self.active_run_id = run_id + self.prompt_active = prompt_active + self.steered: list[str] = [] + + async def steer_run(self, run_id: str, message: str) -> dict[str, Any]: + assert run_id == self.active_run_id + self.steered.append(message) + return {"run_id": run_id, "disposition": "queued"} + + +class AgentNeverLocksTests(unittest.TestCase): + def test_waiting_confirmation_parks_the_worker_but_keeps_the_run(self) -> None: + worker = FakeEmbeddedWorker() + + async def run() -> tuple[dict[str, Any], str | None, dict[str, Any] | None]: + await init_db() + from app.database import async_session + from app.models.models import Job + async with async_session() as db: + if await db.get(Job, 74291) is None: + db.add(Job(id=74291, title="Synthetic recovery role", company="Fixture", + raw_description="Public synthetic JD", hash_key="host-recovery-job")) + await db.commit() + started = await start_embedded_agent_run( + message="帮我调研这个岗位", + skill_id="company_research", + conversation_id="never-locks-park", + worker=worker, + provider_config=PROVIDER, + provider_metadata=METADATA, + ) + return started, worker.active_run_id, await load_agent_run(started["run"]["id"]) + + started, active_run_id, stored = asyncio.run(run()) + self.assertTrue(started["ok"], started) + self.assertEqual(stored["status"], "waiting_confirmation") + # Parked: the single worker is free for the user's next turn... + self.assertIsNone(active_run_id) + # ...while the Run itself is neither cancelled nor approved. + self.assertTrue(started["run"]["final_result"]["requires_confirmation"]) + + def test_steer_reaches_only_a_live_prompt(self) -> None: + async def run() -> tuple[dict[str, Any], dict[str, Any], list[str], list[str]]: + await init_db() + record = await create_agent_run(conversation_id="never-locks-steer", goal="steer fixture", mode="skill", skill_id="discovery", actions=[]) + run_id = record["id"] + live = SteerableWorker(run_id, prompt_active=True) + steered = await steer_embedded_agent_run(run_id, " 换成只看上海的岗位 ", worker=live) + idle = SteerableWorker(run_id, prompt_active=False) + not_running = await steer_embedded_agent_run(run_id, "这条应作为新一轮", worker=idle) + events = [event["type"] for event in await list_agent_run_events(run_id)] + return steered, not_running, live.steered + idle.steered, events + + steered, not_running, delivered, events = asyncio.run(run()) + self.assertEqual(steered["disposition"], "steered") + self.assertEqual(not_running["disposition"], "not_running") + self.assertEqual(delivered, ["换成只看上海的岗位"]) + self.assertEqual(events.count("input.steered"), 1) + + def test_empty_steer_is_rejected(self) -> None: + with self.assertRaises(ValueError): + asyncio.run(steer_embedded_agent_run("run_0000000000000000", " ")) + + +if __name__ == "__main__": + unittest.main() + diff --git a/docs/02-interaction-design.md b/docs/02-interaction-design.md index e67e3631..4db0cb3a 100644 --- a/docs/02-interaction-design.md +++ b/docs/02-interaction-design.md @@ -153,10 +153,10 @@ Ask 支持 `selection_mode: single | multiple`、推荐项、可跳过、自由 ## 11. 已知交互缺陷(待修复,按优先级) +> 2026-10 已修复:D-1(等待中的 Run 不再禁用输入、能力选择和快捷动作)与 D-2(新建 / 切换对话不再 abort Run,运行中的流只分离到后台)。实现见 §11.1。 + | 优先级 | 位置 | 问题 | 违反 | | --- | --- | --- | --- | -| P0 | `frontend/src/components/workbench/AgentPanel.tsx`,约 1161、1189 行 | 存在 pending action、plan review 或 interrupted run 时,能力选择器和全部快捷动作被 `disabled` | D-1 | -| P0 | 同文件 `startNewConversation` / `loadConversation`,约 551–600 行 | 有待办时调用 `agentRuntimeApi.abort(activeRunId)`,新建对话会杀掉任务 | D-2 | | P1 | `AgentAskPanel.tsx` | 单选问题被渲染成复选框 | §4.2 | | P1 | `AgentPanel.tsx` 中的 `QUICK_ACTIONS` | 写死了校招动作 | H-1 | | P1 | `components/layout/Sidebar.tsx` | 11 个入口、中英混排、三个简历入口 | N-1、N-2 | @@ -168,6 +168,19 @@ Ask 支持 `selection_mode: single | multiple`、推荐项、可跳过、自由 修复顺序:先修两个 P0 死锁,再修 P1 的策略和导航问题,最后做 P2 的拆分。每修一项都要补上对应规则的测试。 +### 11.1 D-1 / D-2 的实现方式(参考 Codex steer/queue 与 Claude Code agent view) + +| 用户动作 | 当前 Run 状态 | 行为 | +| --- | --- | --- | +| 发消息 | 正在执行 | `POST /api/agent/runtime/runs/{id}/steer`:在下一个工具边界注入当前 Run,气泡标注「已交给正在进行的任务」;返回 `not_running` 时进入待发送队列 | +| 发消息 | 等审核 / 等回答 / 已中断 | 立即开新一轮;原 Run 收进「停放」条(「之前的任务等你审核,状态已保存 · 回去处理」),不取消、不批准 | +| 发消息 | 有审核提交在途 / 后台流未结束 | 进入待发送队列;队列可见、可改、可删,空闲后按顺序发送 | +| 新建 / 切换对话 | 任意 | 只把当前流分离到后台,不调用 abort;等待中的 Run 在历史对话和统一审核入口继续可见 | + +后端配合:Run 进入 `waiting_confirmation / waiting_input / waiting_decision` 时释放(停放)唯一的内置 Worker,会话文件保留;审批和回答本来就经由 continuation 从会话文件恢复,所以停放不改变授权路径。测试:`backend/tests/test_agent_never_locks.py`、`frontend/src/components/workbench/AgentPanel.test.tsx`「Agent input never locks」。 + +已知限制:整页刷新或关闭应用仍会断开 SSE,后端会把执行中的 Run 标记为 `interrupted`(可恢复,不会丢失);彻底解决需要把 Run 执行与 SSE 连接解耦,留给 AG-UI interrupt 方向(§10)。 + ## 12. 验收指标(单个真实岗位的完整旅程) - 任务进行中,聊天输入始终可用;导航从不隐式中止 Run; diff --git a/frontend/src/components/workbench/AgentPanel.test.tsx b/frontend/src/components/workbench/AgentPanel.test.tsx index 5769e0cf..79c18354 100644 --- a/frontend/src/components/workbench/AgentPanel.test.tsx +++ b/frontend/src/components/workbench/AgentPanel.test.tsx @@ -10,6 +10,7 @@ const api = vi.hoisted(() => ({ events: vi.fn(), run: vi.fn(), abort: vi.fn(), + steer: vi.fn(), resume: vi.fn(), confirm: vi.fn(), reject: vi.fn(), @@ -27,6 +28,7 @@ vi.mock("@/lib/api", () => ({ events: api.events, run: api.run, abort: api.abort, + steer: api.steer, resume: api.resume, confirm: api.confirm, reject: api.reject, @@ -359,3 +361,114 @@ describe("Proposal Plan review routing", () => { window.removeEventListener("offeru-open-plan-review", openPlanReview); }); }); + + +/** docs/02 D-1 / D-2:等待中的 Run 不锁输入,导航不中断 Run。 */ +const waitingPlanRun = () => { + const runId = `run_${"4".repeat(32)}`; + const planId = `plan_${"5".repeat(32)}`; + const groupId = `group_${"6".repeat(32)}`; + const node = { + id: `node_${"7".repeat(32)}`, + group_id: groupId, + operation: "review_resume_proposal_items", + summary: "简历修改", + status: "pending", + display: { before: "旧", after: "新", evidence: "证据", rationale: "理由" }, + }; + return runRecord({ + id: runId, + status: "waiting_confirmation", + proposal_authority: "proposal-plan-v2", + proposal_plans: [{ + id: planId, run_id: runId, revision: 1, status: "sealed", digest: "a".repeat(64), title: "计划", + groups: [{ id: groupId, title: "组", summary: "一组", rationale: "", digest: "b".repeat(64), status: "pending", nodes: [node] }], + continuations: [], + }], + steps: [{ + ...node, tool: node.operation, risk_level: "confirm", requires_confirmation: true, args: {}, + idempotency_key: "idem", attempts: 0, plan_id: planId, group_id: groupId, projection_only: true, + }], + }); +}; + +describe("Agent input never locks", () => { + it("keeps typing, skills and quick actions usable while a plan waits, and parks the waiting run", async () => { + const waiting = waitingPlanRun(); + api.start.mockResolvedValueOnce(runResponse({ run: waiting, pending_actions: waiting.steps })); + api.start.mockResolvedValueOnce(runResponse()); + api.run.mockResolvedValue({ run: waiting }); + const user = userEvent.setup(); + renderPanel(); + await sendPrompt(user, "准备岗位化简历"); + await screen.findByTestId("agent-plan-review"); + + expect(screen.getByRole("textbox")).toBeEnabled(); + expect(screen.getByRole("combobox", { name: "当前 Agent Skill" })).toBeEnabled(); + expect(screen.getByRole("button", { name: "校招体检" })).toBeEnabled(); + + await sendPrompt(user, "顺便看看今天的新岗位"); + await waitFor(() => expect(api.start).toHaveBeenCalledTimes(2)); + expect(api.start.mock.calls[1][0].message).toBe("顺便看看今天的新岗位"); + expect(api.abort).not.toHaveBeenCalled(); + const parked = await screen.findByTestId("agent-parked-runs"); + expect(parked).toHaveTextContent("等你审核"); + + await user.click(screen.getByRole("button", { name: "回去处理" })); + expect(await screen.findByTestId("agent-plan-review")).toBeInTheDocument(); + expect(api.run).toHaveBeenCalledWith(waiting.id); + }); + + it("steers a live run with a mid-run message instead of blocking it", async () => { + let finish: (value: unknown) => void = () => {}; + api.start.mockImplementationOnce((_payload: unknown, onEvent?: (name: string, data: unknown) => void) => { + onEvent?.("run.started", { run_id: "run-1" }); + return new Promise((resolve) => { finish = resolve; }); + }); + api.steer.mockResolvedValue({ ok: true, disposition: "steered" }); + const user = userEvent.setup(); + renderPanel(); + await sendPrompt(user, "找后端岗位"); + await waitFor(() => expect(api.start).toHaveBeenCalledTimes(1)); + + await sendPrompt(user, "只要上海的"); + await waitFor(() => expect(api.steer).toHaveBeenCalledWith("run-1", "只要上海的")); + expect(await screen.findByText("已交给正在进行的任务")).toBeInTheDocument(); + expect(api.start).toHaveBeenCalledTimes(1); + await act(async () => { finish(runResponse()); }); + }); + + it("queues a message the live run cannot take and sends it once the run finishes", async () => { + let finish: (value: unknown) => void = () => {}; + api.start.mockImplementationOnce((_payload: unknown, onEvent?: (name: string, data: unknown) => void) => { + onEvent?.("run.started", { run_id: "run-1" }); + return new Promise((resolve) => { finish = resolve; }); + }); + api.start.mockResolvedValueOnce(runResponse()); + api.steer.mockResolvedValue({ ok: true, disposition: "not_running" }); + const user = userEvent.setup(); + renderPanel(); + await sendPrompt(user, "找后端岗位"); + await sendPrompt(user, "再帮我写封求职信"); + expect(await screen.findByTestId("agent-message-queue")).toHaveTextContent("再帮我写封求职信"); + + await act(async () => { finish(runResponse()); }); + await waitFor(() => expect(api.start).toHaveBeenCalledTimes(2)); + expect(api.start.mock.calls[1][0].message).toBe("再帮我写封求职信"); + await waitFor(() => expect(screen.queryByTestId("agent-message-queue")).not.toBeInTheDocument()); + }); + + it("starting a new conversation never aborts a run that waits on the user", async () => { + const waiting = waitingPlanRun(); + api.start.mockResolvedValueOnce(runResponse({ run: waiting, pending_actions: waiting.steps })); + api.run.mockResolvedValue({ run: waiting }); + const user = userEvent.setup(); + renderPanel(); + await sendPrompt(user, "准备岗位化简历"); + await screen.findByTestId("agent-plan-review"); + await user.click(screen.getByTitle("打开历史对话")); + await user.click(await screen.findByRole("button", { name: "新建" })); + await screen.findByText(/新对话已开始/); + expect(api.abort).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/src/components/workbench/AgentPanel.tsx b/frontend/src/components/workbench/AgentPanel.tsx index 1b205eb9..566af538 100644 --- a/frontend/src/components/workbench/AgentPanel.tsx +++ b/frontend/src/components/workbench/AgentPanel.tsx @@ -61,8 +61,28 @@ interface PanelMessage { role: "user" | "assistant"; content: string; response?: AgentResponse; + /** D-1:运行中发来、已在下一个工具边界注入当前任务的消息。 */ + steered?: boolean; } +interface QueuedPanelMessage { + id: string; + content: string; + skillId?: string; +} + +interface ParkedRun { + id: string; + status: string; +} + +const PARKED_RUN_LABELS: Record = { + waiting_confirmation: "等你审核", + waiting_decision: "等你决定", + waiting_input: "等你回答", + interrupted: "已中断,可恢复", +}; + const QUICK_ACTIONS = [ { label: "确认身份", @@ -293,6 +313,11 @@ export function AgentPanel() { const scrollRef = useRef(null); const abortControllerRef = useRef(null); const currentConvRef = useRef(null); + // D-2:导航只"分离"正在跑的流,不中断它;后台流结束前新消息先排队。 + const [backgroundStreams, setBackgroundStreams] = useState(0); + const [queuedMessages, setQueuedMessages] = useState([]); + const [parkedRuns, setParkedRuns] = useState([]); + const detachedControllersRef = useRef>(new Set()); const hasPendingActions = pendingActions.length > 0; const [reviewPending, setReviewPending] = useState(0); const hasPlanReview = runHasProposalPlan(activeRun); @@ -301,6 +326,11 @@ export function AgentPanel() { const hasPendingPlanReview = hasPlanReview && !["cancelled", "aborted"].includes(activeRun?.status || "") && ( planPendingGroupCount > 0 || ["waiting_confirmation", "executing"].includes(activeRun?.status || "") ); + // 当前 Run 在等用户(审核 / 回答 / 恢复)。这只决定"新消息开新一轮前先把它停放", + // 从不禁用输入、技能选择或快捷动作(docs/02 D-1)。 + const runNeedsUser = hasPendingActions || hasPendingPlanReview || Boolean(interruptedRunId) + || activeRun?.status === "waiting_input"; + const agentBusy = loading || backgroundStreams > 0 || reviewPending > 0; const latestResponse = useMemo(() => { return [...messages].reverse().find((message) => message.response)?.response; @@ -423,9 +453,59 @@ export function AgentPanel() { scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: "smooth" }); }, [messages, loading]); + /** 把等用户处理的 Run 收进"停放"条:状态已持久化,随时可回来处理,绝不隐式取消。 */ + const parkActiveRun = () => { + if (!activeRunId || !runNeedsUser) return; + const status = interruptedRunId ? "interrupted" : activeRun?.status || "waiting_confirmation"; + const parkedId = activeRunId; + setParkedRuns((current) => [ + ...current.filter((item) => item.id !== parkedId), + { id: parkedId, status }, + ]); + setActiveRunId(null); + setActiveRun(null); + setPendingActions([]); + setInterruptedRunId(null); + setPlanReviewNotice(""); + setPlanReviewLoading(false); + }; + + const enqueueMessage = (content: string, skillId?: string) => { + setQueuedMessages((current) => [ + ...current, + { id: `queued-${Date.now()}-${current.length}`, content, skillId }, + ]); + }; + const sendMessage = async (text?: string, explicitSkillId?: string) => { const content = (text ?? input).trim(); - if (!content || loading || hasPendingActions || hasPendingPlanReview || reviewPending > 0 || interruptedRunId) return; + if (!content) return; + if (text === undefined) setInput(""); + if (agentBusy) { + // 正在跑:先尝试在下一个工具边界引导当前任务;跑不到就排队,绝不丢、绝不锁。 + if (loading && activeRunId && reviewPending === 0 && !explicitSkillId) { + try { + const result = await agentRuntimeApi.steer(activeRunId, content); + if (result.disposition === "steered") { + setMessages((current) => [ + ...current, + { id: `user-steer-${Date.now()}`, role: "user", content, steered: true }, + ]); + return; + } + } catch { + // 引导失败时退回排队,消息不能丢。 + } + } + enqueueMessage(content, explicitSkillId); + return; + } + await startTurn(content, explicitSkillId); + }; + + const startTurn = async (content: string, explicitSkillId?: string) => { + if (!content || loading) return; + parkActiveRun(); const requestSkillId = explicitSkillId || selectedSkillId; const currentConversationId = conversationId; currentConvRef.current = currentConversationId; @@ -438,9 +518,10 @@ export function AgentPanel() { content, }; const nextMessages = [...messages, userMessage]; + // 被导航分离到后台的流:结果只落在它自己的对话里,绝不写进当前界面。 + const isDetached = () => detachedControllersRef.current.has(controller) || controller.signal.aborted; setMessages(nextMessages); - setInput(""); setLoading(true); setProgressText("正在启动内置助手..."); setStreamingText(""); @@ -457,7 +538,7 @@ export function AgentPanel() { conversation_id: currentConversationId, }, (event, data) => { - if (currentConvRef.current !== currentConversationId) return; + if (isDetached() || currentConvRef.current !== currentConversationId) return; setToolStream((current) => applyRuntimeToolEvent(current, event, data || {})); const eventRunId = String(data?.run_id || ""); if (eventRunId) setActiveRunId(eventRunId); @@ -508,7 +589,7 @@ export function AgentPanel() { }, controller.signal, ); - if (currentConvRef.current !== currentConversationId) return; + if (isDetached() || currentConvRef.current !== currentConversationId) return; if (!runtimeResponse.ok) { setActiveRunId(runtimeResponse.run.id); setActiveRun(runtimeResponse.run); @@ -541,6 +622,9 @@ export function AgentPanel() { setError(safeClientErrorMessage(err, "OfferU 请求失败")); setInput((current) => current || content); } finally { + if (detachedControllersRef.current.delete(controller)) { + setBackgroundStreams((count) => Math.max(0, count - 1)); + } if (abortControllerRef.current === controller) { setStreamingText(""); setLoading(false); @@ -548,20 +632,69 @@ export function AgentPanel() { } }; - const startNewConversation = async () => { - abortControllerRef.current?.abort(); + // 排队的消息:Agent 空闲且当前没有等用户处理的 Run 时自动按顺序发送; + // 有待处理事项时不抢跑,由用户在队列条上点"现在发送"(会先停放待处理 Run)。 + useEffect(() => { + if (agentBusy || runNeedsUser || queuedMessages.length === 0) return; + const [next, ...rest] = queuedMessages; + setQueuedMessages(rest); + void startTurn(next.content, next.skillId); + // startTurn 读取的是最新渲染闭包;依赖只需要驱动出队的状态。 + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [agentBusy, runNeedsUser, queuedMessages]); + + const sendQueuedNow = (id: string) => { + if (agentBusy) return; + const item = queuedMessages.find((entry) => entry.id === id); + if (!item) return; + setQueuedMessages((current) => current.filter((entry) => entry.id !== id)); + void startTurn(item.content, item.skillId); + }; + + const editQueued = (id: string) => { + const item = queuedMessages.find((entry) => entry.id === id); + if (!item) return; + setQueuedMessages((current) => current.filter((entry) => entry.id !== id)); + setInput((current) => (current ? `${current}\n${item.content}` : item.content)); + }; + + const restoreParkedRun = async (id: string) => { + if (loading) return; + try { + const { run } = await agentRuntimeApi.run(id); + parkActiveRun(); + setParkedRuns((current) => current.filter((item) => item.id !== id)); + if (!["waiting_confirmation", "waiting_decision", "waiting_input", "interrupted"].includes(run.status)) return; + setActiveRunId(run.id); + setActiveRun(run); + setPendingActions(run.status === "waiting_confirmation" ? pendingActionsFromRun(run) : []); + setInterruptedRunId(run.status === "interrupted" ? run.id : null); + if (runHasProposalPlan(run)) setPlanReviewNotice(planReviewSummary(run, false)); + } catch (err: any) { + setError(safeClientErrorMessage(err, "读取待处理任务失败")); + } + }; + + /** + * D-2:切换或新建对话只把当前流"分离"到后台,不中断 Run,也不取消等用户处理的 Run。 + * 等待中的 Run 已持久化,可在历史对话或统一审核入口继续处理。 + */ + const detachCurrentStream = () => { + const controller = abortControllerRef.current; abortControllerRef.current = null; + if (controller && loading && !controller.signal.aborted) { + setBackgroundStreams((count) => count + 1); + detachedControllersRef.current.add(controller); + } + }; + + const startNewConversation = async () => { + detachCurrentStream(); currentConvRef.current = null; setLoading(false); setStreamingText(""); setToolStream(createInitialAgentStreamState()); - if (activeRunId && (hasPendingActions || hasPendingPlanReview || reviewPending > 0 || interruptedRunId)) { - try { - await agentRuntimeApi.abort(activeRunId); - } catch { - // 新对话仍可开始;后端会让残留 Worker 冲突显式失败。 - } - } + setParkedRuns([]); setConversationId(null); setConversationTitle("新对话"); setActiveRunId(null); @@ -583,17 +716,14 @@ export function AgentPanel() { }; const loadConversation = async (id: string) => { - abortControllerRef.current?.abort(); - abortControllerRef.current = null; + detachCurrentStream(); currentConvRef.current = id; setError(""); setLoading(false); setStreamingText(""); setToolStream(createInitialAgentStreamState()); + setParkedRuns([]); try { - if (activeRunId && (hasPendingActions || hasPendingPlanReview || reviewPending > 0 || interruptedRunId)) { - await agentRuntimeApi.abort(activeRunId); - } const conversation = await agentSupportApi.conversation(id); setConversationId(conversation.id); setConversationTitle(conversation.title || "历史对话"); @@ -1158,7 +1288,6 @@ export function AgentPanel() { aria-label="当前 Agent Skill" title="默认由助手按任务自动选择 Skill" value={selectedSkillId} - disabled={loading || hasPendingActions || hasPendingPlanReview || Boolean(interruptedRunId)} onChange={(event) => setSelectedSkillId(event.target.value)} className="min-w-0 flex-1 rounded-md border border-[var(--border)] bg-[var(--surface)] px-2 py-1 text-[11.5px] text-[var(--foreground)] outline-none disabled:opacity-50" > @@ -1186,7 +1315,6 @@ export function AgentPanel() { + + ))} + + )} + + {queuedMessages.length > 0 && ( +
+

+ {agentBusy ? "待发送:助手空闲后按顺序发送" : runNeedsUser ? "待发送:当前任务在等你,处理完会自动发送" : "待发送"} +

+ {queuedMessages.map((item) => ( +
+ {item.content} + {!agentBusy && runNeedsUser && ( + + )} + + +
+ ))} +
+ )} + {/* 输入区 */}
@@ -1359,14 +1533,15 @@ export function AgentPanel() { minRows={1} maxRows={4} placeholder={ - hasPendingActions || hasPendingPlanReview || reviewPending > 0 || interruptedRunId - ? "请先处理当前 Run" - : "问 OfferU,或说你要推进哪一步..." + loading + ? "补充或纠正,助手会在下一步读到..." + : agentBusy + ? "可以继续输入,空闲后按顺序发送..." + : "问 OfferU,或说你要推进哪一步..." } variant="bordered" className="flex-1" classNames={bauhausFieldClassNames} - isDisabled={loading || hasPendingActions || hasPendingPlanReview || reviewPending > 0 || Boolean(interruptedRunId)} onKeyDown={(event) => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); @@ -1378,10 +1553,10 @@ export function AgentPanel() { isIconOnly aria-label="发送" onPress={() => sendMessage()} - isDisabled={!input.trim() || loading || hasPendingActions || hasPendingPlanReview || reviewPending > 0 || Boolean(interruptedRunId)} + isDisabled={!input.trim()} className="bauhaus-button bauhaus-button-outline !min-h-9 !min-w-9 !px-0 !py-0" > - {loading ? : } +
@@ -1409,6 +1584,9 @@ function PanelMessageBubble({ > {message.content} + {message.steered && ( +

已交给正在进行的任务

+ )} {response && (
{response.alerts && response.alerts.length > 0 && } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index be6b6d87..e1b6ff60 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1318,6 +1318,12 @@ export const agentRuntimeApi = { `/api/agent/runtime/runs/${encodeURIComponent(runId)}/resume`, { method: "POST" } ), + /** D-1:运行中发来的消息在下一个工具边界注入当前 Run;not_running 表示应作为新一轮发送。 */ + steer: (runId: string, message: string) => + request<{ ok: boolean; disposition: "steered" | "not_running" }>( + `/api/agent/runtime/runs/${encodeURIComponent(runId)}/steer`, + { method: "POST", body: JSON.stringify({ message }) } + ), abort: (runId: string) => request<{ ok: boolean; run: AgentRunRecord }>( `/api/agent/runtime/runs/${encodeURIComponent(runId)}/abort`, From 23d5539e256994e205d3ba47411b80bf91ad9ed8 Mon Sep 17 00:00:00 2001 From: avabbbb <89175608+avabbbb@users.noreply.github.com> Date: Thu, 8 Oct 2026 09:11:00 +0800 Subject: [PATCH 6/8] feat: paper resume canvas and direct editing (0002) --- docs/05-module-resume.md | 12 + frontend/src/app/resume/[id]/page.tsx | 25 +- .../app/resume/components/ResumePreview.tsx | 22 +- .../components/templates/ResumePaper.test.tsx | 117 ++++ .../components/templates/ResumePaper.tsx | 498 ++++++++++++++++++ .../components/templates/resumeTemplate.css | 168 ++++++ .../components/templates/templateSettings.ts | 32 +- 7 files changed, 863 insertions(+), 11 deletions(-) create mode 100644 frontend/src/app/resume/components/templates/ResumePaper.test.tsx create mode 100644 frontend/src/app/resume/components/templates/ResumePaper.tsx diff --git a/docs/05-module-resume.md b/docs/05-module-resume.md index 9a117741..8bc5d03d 100644 --- a/docs/05-module-resume.md +++ b/docs/05-module-resume.md @@ -86,6 +86,18 @@ Career Truth + 目标岗位 + Role Intelligence + 当前简历 + Skill 方法 - 设计系统:Editorial / Classic / Modern 三套预设。原则是克制、打印效果与屏幕一致、清晰的字体层级、关注信息密度。模板与 HTML 简历(原 Studio)作为检查器里的「设计」面板。 - PDF 优先用 managed Chromium(Python Playwright),与预览共用同一个 React 渲染器;失败时回退到 ReportLab;仍然失败就显示可重试的错误。导出的 PDF 必须对应用户实际采用的版本。 +### 8.1 「纸」模板:成品即编辑器(2026-10 已实现,切片 ①) + +- 新模板 `paper`(「纸 · 可直接编辑」),组件 `frontend/src/app/resume/components/templates/ResumePaper.tsx`。同一个组件既是导出 PDF 的成品,也是工作区画布:`editable=true` 时页面上每段文字都能直接点击修改,所以画布所见就是导出所得。 +- 编辑直接写回原始字段(`content_json` 里的 `company / position / startDate / description …`),左侧表单、AI 建议和导出读的是同一份数据;自动保存、撤销、版本沿用现有机制,不调用模型。 +- 键盘:回车提交并新增下一条要点,空要点按退格删除,Esc 撤回本次输入;悬停一条经历可删除,段落末尾「+ 添加一条」。 +- 分页可见:画布量出成品高度,超过一页时在第一页底部画出分页线并标注超出多少毫米,由用户决定删哪条,而不是自动缩字号。 +- 排版约束参考 tw93/Kami(MIT)的 design.md:单一墨蓝强调色 `#1B365D`、暖灰文字层级、衬线两字重 400/500、行高 1.45、页边距 11/13mm、无装饰线与阴影;暖纸 `#f5f4ed` / 白纸可一键切换。中文字体只用 OFL 许可的思源宋体 / Noto Serif SC,**不使用**仅限个人免费的仓耳今楷。 +- 其他模板暂时仍是只读预览,画布顶部提供「换成『纸』模板,直接在页面上改」。 +- 限制:要点按纯文本编辑,带加粗等富文本格式的那一条在被修改后会变成纯文本;未修改的条目原样保留。 +- 测试:`ResumePaper.test.tsx`(直接编辑写回原字段、Esc 还原、未改不提交、增删条目、隐藏要点索引对齐、默认排版只作用于纸模板)。 +- 下一切片:② 选中文字浮条(精炼 / 对齐岗位 / 留言给 AI,可堆叠后一次提交)与行内 diff 的采用 / 修改 / 跳过;③ 排版控件与「压到一页」;④ DOCX 导出。 + ## 9. 验收 - 一次单岗位定制:最终采用决定 ≤ 1 次;需要审阅的独立 Proposal 达到 50–80 个即判为不合格。 diff --git a/frontend/src/app/resume/[id]/page.tsx b/frontend/src/app/resume/[id]/page.tsx index 0340bd66..87ffe266 100644 --- a/frontend/src/app/resume/[id]/page.tsx +++ b/frontend/src/app/resume/[id]/page.tsx @@ -43,6 +43,7 @@ import type { ResumeDetail, ResumeSectionBlock } from "@/lib/hooks"; import SectionEditor from "../components/SectionEditor"; import ResumePreview from "../components/ResumePreview"; import ResumeDesignPanel from "../components/ResumeDesignPanel"; +import { normalizeTemplateSettings } from "../components/templates/templateSettings"; import { safeClientErrorMessage } from "@/lib/safe-error"; type DraftResume = ResumeDetail & { sections: ResumeSectionBlock[] }; @@ -224,6 +225,23 @@ export default function ResumeEditorPage() { const [exporting, setExporting] = useState(false); const [restoring, setRestoring] = useState(null); const [canUndo, setCanUndo] = useState(false); + // 「纸」画布:量出成品高度,超过一页时在第一页底部画出分页线,让用户自己决定删哪条。 + const [canvasEl, setCanvasEl] = useState(null); + const [pageOverflowMm, setPageOverflowMm] = useState(0); + const canvasRef = useCallback((node: HTMLDivElement | null) => setCanvasEl(node), []); + const pageSizeKey = draft?.style_config?.pageSize === "LETTER" ? "LETTER" : "A4"; + const pageHeightPx = ((pageSizeKey === "LETTER" ? 279.4 : 297) * 96) / 25.4; + useEffect(() => { + if (!canvasEl || typeof ResizeObserver === "undefined") { setPageOverflowMm(0); return; } + const measure = () => { + const height = canvasEl.getBoundingClientRect().height; + setPageOverflowMm(Math.max(0, ((height - pageHeightPx) * 25.4) / 96)); + }; + const observer = new ResizeObserver(measure); + observer.observe(canvasEl); + measure(); + return () => observer.disconnect(); + }, [canvasEl, pageHeightPx]); const hydratedRef = useRef(false); const skipAutosaveRef = useRef(false); const lastSavedSignatureRef = useRef(""); @@ -432,6 +450,7 @@ export default function ResumeEditorPage() { if (loadError || !workspace || !draft) return

{loadError || "简历工作区不存在"}

; const style = draft.style_config || {}; + const paperMode = normalizeTemplateSettings(style).template === "paper"; const setStyle = (key: string, value: string) => updateDraft({ style_config: { ...style, [key]: value } }); const targetLabel = workspace.job ? `${workspace.job.company} · ${workspace.job.title}` : "未绑定目标岗位"; const activeProposal = workspace.proposals.find((item) => ["ready", "in_review", "blocked"].includes(item.status)); @@ -440,7 +459,7 @@ export default function ResumeEditorPage() { return (
-

Resume Workspace

{workspace.application_packet.status === "ready" ? "已准备" : "草稿"}

{targetLabel}

+

岗位简历

{workspace.application_packet.status === "ready" ? "已准备" : "草稿"}

{targetLabel}

{saveState === "saving" && "正在保存…"}{saveState === "saved" && "已保存"}{saveState === "failed" && "保存失败"}
@@ -453,9 +472,9 @@ export default function ResumeEditorPage() {
添加内容段落
{EDITOR_SECTION_TYPES.map(([type, label]) => )}
-
Live Preview {style.pageSize === "LETTER" ? "Letter" : "A4"}
编辑即更新
+
{paperMode ? "成品" : "预览"} {style.pageSize === "LETTER" ? "Letter" : "A4"}
{paperMode ?
点任意文字直接修改 · 回车新增要点 · Esc 撤回本次输入
: }
updateDraft(patch)} onSectionChange={(section) => updateSection(section as ResumeSectionBlock)} />{paperMode && pageOverflowMm > 0 &&
第一页到此 · 超出约 {Math.round(pageOverflowMm)} mm
}
-