@harmonyos-arkts/d2h 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +40 -0
- package/agents/android-to-hmos-00-orchestrator.md +3 -315
- package/agents/d2h.md +53 -38
- package/contracts/workflow/artifacts.md +22 -0
- package/contracts/workflow/delivery.md +54 -0
- package/contracts/workflow/dispatch.md +26 -0
- package/contracts/workflow/execution.md +125 -0
- package/contracts/workflow/generation.md +40 -0
- package/contracts/workflow/orchestration.md +49 -0
- package/contracts/workflow/overview.md +58 -0
- package/contracts/workflow/plan.md +119 -0
- package/contracts/workflow/scope.md +151 -0
- package/dist/bin/install-opencode.js +34 -15
- package/dist/src/install-codex.js +131 -0
- package/dist/src/install-opencode.js +5 -5
- package/dist/src/workflow.js +54 -0
- package/docs/architecture-plan-guide.zh-CN.md +70 -0
- package/docs/incremental-spec-evaluation-design.md +670 -0
- package/docs/spec-plan-guide.md +226 -0
- package/docs/workflow-decoupling.md +59 -0
- package/opencode/interactive-tools/advance-progress-demo.mjs +21 -0
- package/opencode/interactive-tools/browser-host.ts +110 -0
- package/opencode/interactive-tools/core.ts +242 -0
- package/opencode/interactive-tools/demo-workflow.ts +55 -0
- package/opencode/interactive-tools/demo.mjs +37 -0
- package/opencode/interactive-tools/package-lock.json +1876 -0
- package/opencode/interactive-tools/package.json +23 -0
- package/opencode/interactive-tools/panel.ts +40 -0
- package/opencode/interactive-tools/prepare-demo.mjs +17 -0
- package/opencode/interactive-tools/prepare-progress-demo.mjs +13 -0
- package/opencode/interactive-tools/progress.ts +118 -0
- package/opencode/interactive-tools/server-legacy.mjs +2 -0
- package/opencode/interactive-tools/server.mjs +40 -0
- package/opencode/interactive-tools/tui.tsx +230 -0
- package/package.json +9 -1
- package/schemas/native-libs-analysis-report.schema.json +695 -0
- package/schemas/native-libs-verify-report.schema.json +566 -0
- package/schemas/spec-native-libs-shard.schema.json +110 -0
- package/skills/arkts-icon-sizing/SKILL.md +0 -10
- package/skills/cpp/d2h-native-library-analyse/SKILL.md +91 -0
- package/skills/cpp/d2h-native-library-analyse/scripts/analyse-android-so.py +630 -0
- package/skills/cpp/d2h-native-library-build/SKILL.md +403 -0
- package/skills/cpp/d2h-native-library-build/references/build-config-mapping.md +180 -0
- package/skills/cpp/d2h-native-library-build/references/jni-to-napi-mapping.md +74 -0
- package/skills/cpp/d2h-native-library-build/scripts/check-android-so.py +580 -0
- package/skills/cpp/d2h-native-library-verify/SKILL.md +111 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-build-sign-run.md +67 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/app.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/resources/base/element/string.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/README.md +25 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/build-profile.json5 +24 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/build-profile.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/hvigorfile.ts +2 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/oh-package.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/entryability/EntryAbility.ets +5 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/Index.ets +3 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/NativeVerifyDemo.ets +276 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/module.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/color.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/string.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/profile/main_pages.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/FFmpegVerify.test.ets +176 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/List.test.ets +2 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/module.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigor/hvigor-config.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigorfile.ts +2 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/oh-package.json5 +9 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-template.md +176 -0
- package/skills/cpp/d2h-native-library-verify/references/hypium-test-template.md +163 -0
- package/skills/cpp/d2h-native-library-verify/scripts/__pycache__/native_common.cpython-313.pyc +0 -0
- package/skills/cpp/d2h-native-library-verify/scripts/device-verify.py +304 -0
- package/skills/cpp/d2h-native-library-verify/scripts/native_common.py +341 -0
- package/skills/cpp/d2h-native-library-verify/scripts/verify-ohos-native.py +1099 -0
- package/skills/d2h-app-solution/SKILL.md +198 -0
- package/skills/d2h-app-solution/agents/openai.yaml +4 -0
- package/skills/d2h-app-solution/references/framework-evidence.md +117 -0
- package/skills/d2h-app-solution/references/hierarchy-rules.md +88 -0
- package/skills/d2h-app-solution/references/inventory-schema.md +305 -0
- package/skills/d2h-app-solution/references/technical-analysis.md +101 -0
- package/skills/d2h-app-solution/scripts/scan_repositories.py +350 -0
- package/skills/d2h-app-solution/scripts/validate_inventory.py +337 -0
- package/skills/d2h-architecture-plan/SKILL.md +35 -0
- package/skills/d2h-architecture-plan/references/directory-creation.md +43 -0
- package/skills/d2h-architecture-plan/references/hybrid-design.md +84 -0
- package/skills/d2h-architecture-plan/references/layout-reference.md +83 -0
- package/skills/d2h-architecture-plan/references/plan-contract.md +51 -0
- package/skills/d2h-architecture-plan/references/source-review.md +50 -0
- package/skills/d2h-cloud-knowledge/SKILL.md +28 -7
- package/skills/d2h-cloud-knowledge/kb_query.mjs +8 -0
- package/skills/d2h-dashboard/SKILL.md +56 -0
- package/skills/d2h-product-family-migration/SKILL.md +103 -0
- package/skills/d2h-product-family-migration/agents/openai.yaml +3 -0
- package/skills/d2h-product-family-migration/assets/incremental-plan.template.md +55 -0
- package/skills/d2h-product-family-migration/references/advanced/behavioral-verification.md +59 -0
- package/skills/d2h-product-family-migration/references/advanced/existing-skill-integration.md +30 -0
- package/skills/d2h-product-family-migration/references/advanced/packaging-and-verification.md +43 -0
- package/skills/d2h-product-family-migration/references/advanced/platform-probes.md +26 -0
- package/skills/d2h-product-family-migration/references/advanced/product-line-design.md +40 -0
- package/skills/d2h-product-family-migration/references/baseline-and-delta.md +50 -0
- package/skills/d2h-product-family-migration/tests/markdown-cases.md +76 -0
- package/skills/{d2h-page-develop → d2h-spec-develop}/SKILL.md +143 -132
- package/skills/d2h-spec-develop/agents/openai.yaml +7 -0
- package/skills/d2h-spec-develop/references/worker-workflow.md +26 -0
- package/skills/{d2h-page-spec-generate → d2h-spec-generate}/SKILL.md +24 -13
- package/skills/d2h-spec-generate/assets/spec-template.md +109 -0
- package/skills/d2h-spec-generate/references/from-plan.md +19 -0
- package/skills/d2h-spec-generate/references/spec-content.md +34 -0
- package/skills/{d2h-page-spec-generate → d2h-spec-generate}/scripts/build_batch_report.py +35 -84
- package/skills/d2h-spec-plan/SKILL.md +38 -0
- package/skills/d2h-spec-plan/agents/openai.yaml +4 -0
- package/skills/d2h-spec-plan/references/architecture-handoff.md +9 -0
- package/skills/d2h-spec-plan/references/plan-contract.md +13 -0
- package/skills/d2h-spec-plan/references/planning-method.md +46 -0
- package/skills/d2h-spec-plan/references/round-scope-assessment.md +49 -0
- package/skills/d2h-spec-plan/references/work-packets.md +61 -0
- package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +2 -2
- package/skills/d2h-utg-ai-generate/references/page-schema.json +2 -2
- package/skills/d2h-utg-ai-generate/references/utg-schema.json +2 -2
- package/skills/d2h-utg-static-generate/SKILL.md +2 -2
- package/skills/managing-d2h-toolchain/environment-requirements.json +1 -1
- package/skills/managing-d2h-toolchain/toolchain.json +1 -1
- package/skills/preparing-migration-workspace/SKILL.md +32 -2
- package/workflow/__pycache__/artifact_bindings.cpython-313.pyc +0 -0
- package/workflow/__pycache__/spec_runtime.cpython-313.pyc +0 -0
- package/workflow/artifact_bindings.py +224 -0
- package/workflow/assets/dashboard.html +88 -0
- package/workflow/build_batch_report.py +8 -0
- package/workflow/collect_code_metrics.py +75 -0
- package/workflow/dashboard_overview.py +109 -0
- package/workflow/delivery_view.py +82 -0
- package/workflow/feature_plan.py +234 -0
- package/workflow/page_batches.py +223 -0
- package/workflow/spec_generation.py +120 -0
- package/workflow/spec_runtime.py +890 -0
- package/workflow/work_packets.py +85 -0
- package/skills/d2h-page-develop/agents/openai.yaml +0 -7
- package/skills/harmonyos-migration-playbook/SKILL.md +0 -83
- package/skills/harmonyos-migration-playbook/agents/openai.yaml +0 -7
- package/skills/harmonyos-migration-playbook/references/arkts-compile.md +0 -24
- package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +0 -53
- package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +0 -45
- package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +0 -23
- package/skills/harmonyos-migration-playbook/references/ui-automation.md +0 -53
- package/skills/harmonyos-migration-playbook/references/windows-environment.md +0 -38
- package/skills/native-library-substitution/SKILL.md +0 -385
- package/skills/native-library-substitution/references/native-library-substitution.json +0 -56906
- package/skills/native-library-substitution/references/native-library-substitution.md +0 -163
- package/skills/porting-cmake-native-libraries/SKILL.md +0 -57
- package/skills/porting-cmake-native-libraries/references/platform-adaptation.md +0 -49
- package/skills/porting-cmake-native-libraries/references/report-and-build.md +0 -60
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# spec-plan:怎样安排开发和验收
|
|
2
|
+
|
|
3
|
+
本次职责调整:plan 只维护可评审的波次与轻量 Spec 条目;原 d2h-spec-generate 通过 from-plan 模式按选定版本生成正文。旧独立模式保留。公共交接及新生成门禁见 [生成契约](../contracts/workflow/generation.md)。
|
|
4
|
+
|
|
5
|
+
这份说明供产品、开发和项目负责人阅读。字段定义和命令用法放在文末参考中。
|
|
6
|
+
|
|
7
|
+
spec-plan 根据功能清单安排每一轮做什么、先做什么、怎样验收。目标是先交付一条能实际使用的操作流程,再补齐双方约定的其他功能。
|
|
8
|
+
|
|
9
|
+
优先按用户明确选中的范围交付:可以选择最小化实现、核心实现、全量实现,再逐项调整范围和优先级。没有明确选择时,默认交付核心功能;恢复已有任务时保留仍有效的用户选择。功能规划根据真实业务路径写入具体范围、必要依赖、阶段和默认原因;spec-plan 只规划该范围,看板读取同一计划和执行账本。MVP、核心、全量按范围累计展示,业务里程碑和内部轮次分别保留。旧清单未配置分层时显示未配置,由上游补齐交付范围后执行,不由看板自动猜测。
|
|
10
|
+
|
|
11
|
+
开发完成后要对照最初清单、最新约定和实际交付做功能 Review,新增、变更、缺失、延期和未评估项都保留。当前目标完成与全量完成分别报告;只授权 MVP 时不会自动进入全量开发。具体字段、看板导出及范围修改建议见 [交付与看板协议](../contracts/workflow/delivery.md)。
|
|
12
|
+
|
|
13
|
+
例如,要尽快做出登录后可用的首页,第一轮就先完成必要的启动流程、基本登录和首页基础内容。其他登录方式、额外的会话能力和视觉细节,按约定的范围和优先级留到后面。基本登录也必须完成真实鉴权:登录成功后,首页能读到会话;登录失败时,应用不能误认为用户已登录。
|
|
14
|
+
|
|
15
|
+
## 1. 我们希望解决什么问题
|
|
16
|
+
|
|
17
|
+
按整页开发,容易在登录页做完所有细节后才开始首页。反过来,若把每个控件都拆成任务,AI 又得反复读材料、接收任务、运行检查,交接和调度也会增加。
|
|
18
|
+
|
|
19
|
+
计划需要兼顾这些要求:
|
|
20
|
+
|
|
21
|
+
- 先交付一条用户能实际操作的路径。
|
|
22
|
+
- 延后功能保留在清单里,不能做完基础部分就算全部完成。
|
|
23
|
+
- 相关行为一起规划,同页任务连续开发,减少重复阅读和测试准备。
|
|
24
|
+
- 关键依赖及时验证,低风险检查可以合批,待验证工作不能无限积压。
|
|
25
|
+
- 用户调整优先级、需求变化或发现新依赖时,能修订计划并查到历史。
|
|
26
|
+
|
|
27
|
+
判断效果时,看首条路径用了多久、实际花了多少工时,以及重复分析、等待和返工占了多少。Spec 数量多、worker 一直在工作,都不能单独说明效率高。
|
|
28
|
+
|
|
29
|
+
## 2. spec-plan 在整体流程中的位置
|
|
30
|
+
|
|
31
|
+
| 角色或模块 | 负责的事情 |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `feature-list-analysis` | 整理要做的功能,维护范围、里程碑和用户优先级 |
|
|
34
|
+
| `d2h-architecture-plan` | 直接阅读源码,交付三套 Markdown 架构;可参考已有功能分析,向 Spec 交接选定方案、所有者、共享边界和风险;实际创建才附目录记录 |
|
|
35
|
+
| `d2h-spec-plan` | 拆分 Spec,分析依赖,安排轮次、同页批次和验证方式 |
|
|
36
|
+
| 主 Agent | 根据当前状态派发任务,核对证据,决定是否验收 |
|
|
37
|
+
| `d2h-spec-develop` / 实现 worker | 完成领取范围内的代码和检查,返回结果与缺口 |
|
|
38
|
+
| 构建与测试 CLI | 实际运行构建和设备测试,保存原始结果 |
|
|
39
|
+
|
|
40
|
+
spec-plan 制订计划,主 Agent 按实际进度调度和验收。如果规划时发现范围冲突,需要交回功能规划模块处理,不能自行改变用户优先级,也不能擅自开始用户已经延后的需求。
|
|
41
|
+
|
|
42
|
+
AI 读取需求和源码,判断应该实现什么、怎样拆分、依赖什么,并估算成本。程序检查引用是否有效、依赖是否冲突、预算是否超限,以及状态和证据文件是否对应。业务行为是否正确,还需要主 Agent 核对。
|
|
43
|
+
|
|
44
|
+
## 3. 先分清几个概念
|
|
45
|
+
|
|
46
|
+
| 概念 | 通俗理解 | 例子 |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| 功能 | 用户需要的一类能力 | 登录 |
|
|
49
|
+
| 范围项 | 功能中需要追踪的一项需求,用固定 ID 标识 | 基础登录、约定的登录态恢复 |
|
|
50
|
+
| Spec | 一份描述本次要实现哪些行为、怎样验收的任务说明 | 用真实凭据登录并进入首页,失败不建立会话 |
|
|
51
|
+
| 页面 | 用户操作的 UI,可以分几轮完善 | 登录页、首页 |
|
|
52
|
+
| 里程碑 | 用户或项目约定的阶段成果 | 首版应用交付 |
|
|
53
|
+
| 轮次 | 一个里程碑内有明确目标的一轮开发与验收 | 基础可用、业务增强、完善交付 |
|
|
54
|
+
| 页面批次 | 同一轮次、同一页面内适合一起开发的一组 Spec | 首页列表与相关空状态 |
|
|
55
|
+
| 派发窗口 | worker 这次实际领取的一组 Spec,范围和预算固定 | 从首页批次中领取当前可执行的 Spec |
|
|
56
|
+
|
|
57
|
+
一个里程碑可以分几轮完成。每轮把同页相关 Spec 归为批次,worker 再领取其中当前能做的部分。这样可以少切换页面,但每个 Spec 仍单独验收。范围项负责追踪需求有没有对应任务,避免遗漏。
|
|
58
|
+
|
|
59
|
+
页面批次不必一次做完,worker 也不能在派发窗口里自行添加任务。页面的 `spec.md` 可以包含多个增量 Spec,后续在原文档和原页面上继续补充。
|
|
60
|
+
|
|
61
|
+
## 4. 设计原则
|
|
62
|
+
|
|
63
|
+
### 先做目标必需的前置功能
|
|
64
|
+
|
|
65
|
+
如果首页需要有效会话,就先做基础登录。启动动画虽然出现在登录之前,但只要不影响首页使用,就不必先精修。判断依赖要看源码和实际行为,不能只看页面出现的顺序。
|
|
66
|
+
|
|
67
|
+
### 按完整的用户操作拆分任务
|
|
68
|
+
|
|
69
|
+
输入校验、提交时的按钮状态和基本失败反馈,通常属于一次完整的登录操作,可以写在同一个 Spec 中。如果某部分需要独立交付、提前供下游使用,或风险和交付轮次不同,再考虑拆开。
|
|
70
|
+
|
|
71
|
+
一个 Spec 可以对应多个范围项和多个检查,但同一个已授权范围项只能有一个负责实现的 Spec。已有多个独立 Spec 时,可以放进同页窗口连续开发,减少交接。
|
|
72
|
+
|
|
73
|
+
### 分清基础可用和全部完成
|
|
74
|
+
|
|
75
|
+
基础登录验收后,其他登录需求仍要留在清单里:有的对应后续 Spec,有的明确延后、尚未排期或等待澄清。用户决定不做的,也要保留排除记录。
|
|
76
|
+
|
|
77
|
+
### 先记全需求,到对应阶段再补实施细节
|
|
78
|
+
|
|
79
|
+
已授权并排期的范围必须有对应 Spec。远期尚未排期的需求先记清内容和去向,到对应阶段再补实现方案与测试用例,避免提前花太多时间规划。
|
|
80
|
+
|
|
81
|
+
### 分别检查代码、接口和行为
|
|
82
|
+
|
|
83
|
+
下游开始使用一个能力前,主 Agent 要确认接口确实存在于当前代码中,当前版本能构建,必要的契约检查也已通过。只看到代码或接口名称还不够。
|
|
84
|
+
|
|
85
|
+
### 沿用已读材料,变化后重新核对
|
|
86
|
+
|
|
87
|
+
同一窗口由同一个 worker 连续开发,共用页面摘要。切换 Spec 时,主要读取这次的差异;源码依据或接口契约变化时,再补读相关源码。摘要可以帮助定位,但不能替代源码或证明新代码已通过测试。
|
|
88
|
+
|
|
89
|
+
## 5. 从输入到交付,实际怎样运行
|
|
90
|
+
|
|
91
|
+
```mermaid
|
|
92
|
+
flowchart TD
|
|
93
|
+
A["版本化功能清单 + Facts / UTG / 源码"] --> B["明确本阶段目标和范围项"]
|
|
94
|
+
B --> C["拆行为 Spec;找必要依赖并评估优先级"]
|
|
95
|
+
C --> D["组织轮次和同页批次;安排测试与预算"]
|
|
96
|
+
D --> E["当前轮次补齐实施工作包并校验"]
|
|
97
|
+
E --> F{"开工就绪且验证积压未超限?"}
|
|
98
|
+
F -- 否 --> G["定向补缺口、验证或修复"]
|
|
99
|
+
G --> F
|
|
100
|
+
F -- 是 --> H["裁剪上下文;固定派发窗口"]
|
|
101
|
+
H --> I["同一 worker 连续实施,逐项返回结果"]
|
|
102
|
+
I --> J["真实接口确认、当前构建与即时检查"]
|
|
103
|
+
J --> K["补齐批次检查;主 Agent 独立验收"]
|
|
104
|
+
K --> L{"本轮目标通过?"}
|
|
105
|
+
L -- 否 --> G
|
|
106
|
+
L -- 是 --> M{"本次交付目标还有未完成范围?"}
|
|
107
|
+
M -- 有,计划已明确 --> F
|
|
108
|
+
M -- 有,需调整范围或计划 --> B
|
|
109
|
+
M -- 无 --> N["目标全范围回归、报告核对与交付 Review<br/>全量目标另过全迁移门禁"]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 第一步:读取已有功能清单
|
|
113
|
+
|
|
114
|
+
spec-plan 读取功能规划模块给出的清单,确认版本、范围项、阶段和用户要求的顺序,再结合 Facts、导航路径和源码核对行为。Facts 没有记录清楚的部分,继续查源码;用户新增的功能则以用户需求为依据。
|
|
115
|
+
|
|
116
|
+
### 第二步:拆分任务,确定先后顺序
|
|
117
|
+
|
|
118
|
+
先看本轮要交付什么,再找必需的前置能力。例如,首页展示登录后的用户信息,就需要先建立首页可读取的会话。首页优先级高时,它依赖的基础登录也会优先安排,但登录页上无关的功能仍按原顺序处理。
|
|
119
|
+
|
|
120
|
+
主 Agent 先筛出符合用户要求、当前轮次和依赖条件的任务,并确认待验证工作没有超限。再比较任务的有效优先级、依赖链剩余耗时,以及它能支持多少个后续任务。条件相同时,优先继续开发同一页面,之后再比较收益和成本。
|
|
121
|
+
|
|
122
|
+
评分看五件事:对里程碑的贡献、能否支持后续任务、用户价值、能否减少风险,以及能否尽早获得反馈。当前权重分别为 6、5、4、3、2,每项取 0–5 分。价值分除以预计投入,作为收益成本的参考。预计投入要算上理解需求、实现、测试、集成和预期返工的时间;分数再高,也不能跳过依赖或用户限制。
|
|
123
|
+
|
|
124
|
+
### 第三步:写清本次任务,让 worker 能够开工
|
|
125
|
+
|
|
126
|
+
工作包是一份供 worker 实施的任务说明:这次做什么、依据是什么、允许改哪些文件、输入后状态应怎样变化、哪些约束不能破坏、有哪些已知问题,以及何时运行哪些检查。仍有问题会影响正确实现时,先保留计划、补齐信息,暂不派发这个 Spec。
|
|
127
|
+
|
|
128
|
+
几个 Spec 共用状态时,要写清谁更新、谁读取、通过哪里传递、传什么数据,以及状态何时建立和失效。例如,登录成功后写入统一会话存储,首页读取会话并响应变化。如果两边各存一个互不相通的“已登录”变量,就没有完成状态交接。
|
|
129
|
+
|
|
130
|
+
### 第四步:交接本轮需要的材料,连续开发
|
|
131
|
+
|
|
132
|
+
主 Agent 从当前可执行的页面批次中确定这次领取的 Spec,只交接这些 Spec 和必要依赖的材料,并固定任务范围、计划版本、代码基线、预算和 worker。
|
|
133
|
+
|
|
134
|
+
worker 收到页面共享摘要、相对指定代码基线的改动,以及相关源码的位置,在同一窗口内逐项开发,不必为每个小功能重新分析整页。低优先级任务只有在预算允许、且不耽误其他更高优先级任务时,才能顺带领取。
|
|
135
|
+
|
|
136
|
+
### 第五步:核对代码中的接口,再供下游使用
|
|
137
|
+
|
|
138
|
+
提供前置能力的 Spec 写完代码后,主 Agent 要核对实际函数或接口的名称、参数、返回值和错误处理,并记录对应代码版本及文件哈希。接口文件变了,或确认记录已经过期,下游就不能再照旧签名开发。
|
|
139
|
+
|
|
140
|
+
主 Agent 确认接口后,还要检查当前构建和即时契约测试。哈希能帮助确认文件有没有变化,业务行为是否正确则要另外核验。
|
|
141
|
+
|
|
142
|
+
### 第六步:运行检查,验收 Spec 和本轮目标
|
|
143
|
+
|
|
144
|
+
鉴权、启动、共享契约等高风险检查要及时运行。其余低风险检查可以合批,但最迟要在所属里程碑结束前完成。待验证任务的数量、预计工作量或最长等待时间,只要有一项达到配置上限,就暂不启动新的实现任务,先测试或修复。
|
|
145
|
+
|
|
146
|
+
检查和依赖都通过后,主 Agent 独立复核并验收 Spec。本轮 Spec 全部验收后,再从真实入口验证本轮目标是否达成。每个里程碑结束前运行阶段完整回归;本次交付范围做完后,在当前 HAP 上运行覆盖整个目标及必要依赖的完整回归,并核对迁移报告。范围优先按用户选择,无明确选择时按功能计划记录的默认核心;只有全量目标才要求全应用最终回归和全迁移门禁。
|
|
147
|
+
|
|
148
|
+
检查失败时,记录原因并修复相关问题。范围、依赖或计划内容变化时才修订计划;已有明确的后续任务,就继续派发,不重新规划一遍。
|
|
149
|
+
|
|
150
|
+
## 6. 登录和首页怎样分轮完成
|
|
151
|
+
|
|
152
|
+
假设“首版交付”分三轮完成,可以这样安排。表中的扩展功能仅作示例,项目实际要做什么,仍以原应用和用户约定为准。
|
|
153
|
+
|
|
154
|
+
| 轮次 | 本轮目标 | 登录页 | 首页 | 本轮结束意味着什么 |
|
|
155
|
+
| --- | --- | --- | --- | --- |
|
|
156
|
+
| 第一轮:基础可用 | 从真实入口登录并使用首页 | 基础登录、必要失败处理与共享会话 | 主导航和核心首屏基础内容 | 首条路径通过验证,登录页仍有后续需求 |
|
|
157
|
+
| 第二轮:业务增强 | 交付优先级较高的业务功能 | 只补本轮业务必需的能力 | 高优先级列表、详情或操作 | 在原首页上继续开发,保留已有功能 |
|
|
158
|
+
| 第三轮:完善交付 | 补齐剩余约定需求 | 约定的其他登录方式或会话能力 | 剩余状态、边界和其他约定行为 | 逐项核对需求,再运行阶段完整回归 |
|
|
159
|
+
|
|
160
|
+
如果用户把“首条路径可用”单独设为里程碑,就把后续功能放到下一个里程碑,系统不能擅自扩大当前阶段。每个里程碑都有自己的基础轮次。
|
|
161
|
+
|
|
162
|
+
登录需求始终留在同一份范围清单中:
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
登录功能
|
|
166
|
+
├─ basic:基础登录 → S-LOGIN-BASIC → 第一轮已验收
|
|
167
|
+
├─ session:约定会话能力 → 后续增量 Spec 或明确延期
|
|
168
|
+
└─ other-method:约定其他登录方式 → 后续增量 Spec 或明确延期
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
后续增加 `S-LOGIN-SESSION` 时,继续使用原登录页面和基础登录能力。原 `S-LOGIN-BASIC` 的 ID、范围和历史不变,不能只因计划增加了无关需求就重置它。如果基础登录本身的需求变了,就修订原 Spec;如果原先可用的行为出了问题,就撤销当前验收状态,修复后重新验证。
|
|
172
|
+
|
|
173
|
+
## 7. 哪些条件满足后才能说完成
|
|
174
|
+
|
|
175
|
+
| 层级 | 完成条件 |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| 实现提交 | 代码已交付,可能还有未完成的验证 |
|
|
178
|
+
| 派发窗口结束 | 领取的 Spec 已实现,即时检查通过,其余批次检查可能还没运行 |
|
|
179
|
+
| Spec 验收 | 当前构建、全部检查、依赖检查和独立行为复核通过 |
|
|
180
|
+
| 轮次交付 | 本轮 Spec 全部验收,从真实入口验证本轮目标通过 |
|
|
181
|
+
| 功能完整 | 当前代码版本下,全部未排除范围项验收通过;延后、未排期和未解决项仍计入剩余需求 |
|
|
182
|
+
| 页面完整 | 已核对全部页面需求,页面所有 Spec 通过,关联功能的范围检查也通过 |
|
|
183
|
+
| 里程碑完成 | 本阶段需求和必要依赖已验收,相关轮次的结果对当前版本有效,阶段完整回归通过 |
|
|
184
|
+
| 本次交付完成 | 用户选中范围(无明确选择时为默认核心)及必要依赖已验收,当前版本目标完整回归和功能 Review 通过;范围外项目保留在报告中 |
|
|
185
|
+
| 全迁移完成 | 所有约定范围逐项交付,全应用当前版本回归及迁移报告检查通过 |
|
|
186
|
+
|
|
187
|
+
系统分别保存历史交付和当前版本的验收结果。现在采用保守处理方式:待验收的代码版本变了,旧测试就不能证明新版本通过。历史记录保留,但当前依赖和验收需要重新取得有效证据。
|
|
188
|
+
|
|
189
|
+
## 8. 计划和进度分别存在哪里
|
|
190
|
+
|
|
191
|
+
这些路径都位于具体迁移项目的工作区中:
|
|
192
|
+
|
|
193
|
+
| 文件 | 职责 |
|
|
194
|
+
| --- | --- |
|
|
195
|
+
| `.migration/feature-planning/features.json` | 记录完整需求、用户优先级和阶段;需求范围以这份清单为准 |
|
|
196
|
+
| `.migration/page-specs/pages.json` | 记录 Spec、范围对应关系、依赖、轮次、批次和验证安排;计划以它为准 |
|
|
197
|
+
| `.migration/page-specs/<页面目录>/spec.md` | 页面共享说明和每个 Spec 的行为要求,供阅读和实施 |
|
|
198
|
+
| `.migration/page-specs/execution.json` | 记录派发、实现、接口、证据、验收及历史;执行进度以它为准 |
|
|
199
|
+
| `.migration/page-specs/batch_report.json` | 从计划和账本生成的报告,不能直接修改其中的完成状态 |
|
|
200
|
+
|
|
201
|
+
功能规划模块记录需求的版本变化。修订计划前,主 Agent 保存旧版本,先处理正在执行的任务,再更新计划与账本。已有 ID 和历史不删除;受影响的任务重新验证,其他任务按规则保留。
|
|
202
|
+
|
|
203
|
+
## 9. 目前支持到哪里
|
|
204
|
+
|
|
205
|
+
- 实际只使用一个实现 worker。建议人数用于评估和复盘,目前不能让多个实现 worker 并发工作。
|
|
206
|
+
- 可以规划不同层级的测试并提前排错,但正式通过仍要求受管 Python 设备测试证据。本地静态检查或逻辑探针不能代替设备结果。
|
|
207
|
+
- 主 Agent 仍需核对原始测试结果实际覆盖了哪些 Spec 检查,脚本不能自动证明断言覆盖。
|
|
208
|
+
- 可以记录上下文准备和重复分析的成本,但没有自动测量或承诺节省多少 token。共享投入只计一次,不知道的耗时留空。
|
|
209
|
+
- 尚不能自动判断哪些代码不受改动影响,并据此复用旧版本证据。旧功能清单和计划需要显式升级,不能直接获得新检查规则或继承旧 PASS。
|
|
210
|
+
|
|
211
|
+
## 进一步阅读
|
|
212
|
+
|
|
213
|
+
- [详细设计与流程图](incremental-spec-evaluation-design.md):完整评估维度、调度原则和验收场景。
|
|
214
|
+
- [计划契约](../skills/d2h-spec-plan/references/plan-contract.md):数据字段及示例。
|
|
215
|
+
- [工作包与共享状态](../skills/d2h-spec-plan/references/work-packets.md):开工条件和实际接口交接。
|
|
216
|
+
- [运行时协议](../contracts/workflow/execution.md):事件、证据、上下文和版本修订。
|
|
217
|
+
|
|
218
|
+
## 轮次范围评估由谁输出
|
|
219
|
+
|
|
220
|
+
feature-list-analysis 交接授权功能范围、用户优先级/顺序、交付目标和分析未决项;d2h-spec-plan 输出每轮目标、范围项与 Spec/批次映射、必要前置、暂缓理由、成本风险和验收条件;主 Agent 根据实际执行触发复评。评估保存在既有 rounds 的 goal/rationale/goal_checks 及页面 spec.md 中,未新增第二份计划或执行状态。仅修复/补证据不重做规划,改变授权先回上游,改变规划语义按固定派发与 reconcile 协议处理。详见 [轮次范围评估](../skills/d2h-spec-plan/references/round-scope-assessment.md)。
|
|
221
|
+
|
|
222
|
+
## 完整 Spec 与分组
|
|
223
|
+
|
|
224
|
+
当前流程复用原页面 Spec 的七个核心章节,正式生成模板见 [spec-template.md](../skills/d2h-spec-generate/assets/spec-template.md)。每页一份 spec.md,可容纳多个有独立验收身份的 spec_id;当前轮次逐项写完整行为、必要失败分支、操作、源码/用户证据、实现路径和检查断言,后续范围保留去向并标明待细化。
|
|
225
|
+
|
|
226
|
+
page_batches 负责同轮同页的上下文复用与预算分组,实际窗口仍由运行时动态选择;不以减少 Spec 数量或缩短正文为目标。主 Agent 按 [完整内容规范](../skills/d2h-spec-generate/references/spec-content.md) 审查正文,worker 按 spec_id 定位读取。现有程序不解析 Markdown 语义,模板存在或校验通过不代替内容审查与设备证据。现有 v2 产物不隐式升级;只有显式开启 spec_generation_policy=required-v1 时才按逐成员内容绑定增加派发门禁,不重新生成无关既有 Spec。
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# 以产物契约解耦规划与实施
|
|
2
|
+
|
|
3
|
+
`agents/d2h.md` 本次保留上游原文,由人工修改。下述编排边界是入口适配建议,公共契约及工具已提供。
|
|
4
|
+
|
|
5
|
+
`d2h` 根据产物状态选择能力、传递输入并核对输出。Skill 不要求某个生产 Skill 先执行,也不读取它的安装目录;已有有效 Spec 可直接用于实施,用户范围变化只修订受影响计划。
|
|
6
|
+
|
|
7
|
+
## 公共工具
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
d2h workflow --help
|
|
11
|
+
d2h workflow contract artifacts
|
|
12
|
+
d2h workflow contract scope
|
|
13
|
+
d2h workflow contract plan
|
|
14
|
+
d2h workflow contract dispatch
|
|
15
|
+
d2h workflow validate-scope /abs/features.json
|
|
16
|
+
d2h workflow validate --plan /abs/pages.json --features /abs/features.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
公共契约在包内 `contracts/workflow/`,执行实现及视图在 `workflow/`。通过 CLI 读取契约,不要求某个 Skill 目录存在;显式依赖 D2H 工具及 Python 3.10+,解释器可由 D2H_PYTHON 指定。规划 Skill 只保留语义拆分方法、轮次评估、Spec 模板和工作包设计。
|
|
20
|
+
|
|
21
|
+
## 职责
|
|
22
|
+
|
|
23
|
+
| 参与者 | 输入 | 输出 / 决策 |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| d2h | 当前产物、用户选择、缺口和证据 | 范围版本、能力选择、固定派发、状态记录和最终验收 |
|
|
26
|
+
| 规划能力 | 版本化范围、源码/目标上下文、只读执行摘要 | 可评审的轻量 Spec、波次计划、修订差异及请求 |
|
|
27
|
+
| 规格生成能力 | 选定计划版本/成员、来源与架构上下文 | 详细 spec.md、generation.json 内容绑定或修订请求 |
|
|
28
|
+
| 实施能力 | 固定 Spec/版本、允许路径、实际接口与检查要求 | 实现、作用域提交、真实检查证据及待处理请求 |
|
|
29
|
+
| 公共工具 | 明确的产物和事件 | 契约校验、状态派生、历史/证据门禁、报告和看板 |
|
|
30
|
+
|
|
31
|
+
完整迁移由 d2h 记录用户选择为 features.json,再交规划能力。清单事实、用户范围、计划和执行事件不是同一个产物;功能分析不承担用户授权或执行状态。来源结构/字节绑定由公共契约校验,源码与行为语义仍由调用方核验,不能调用生产 Skill 的私有校验脚本替代。
|
|
32
|
+
|
|
33
|
+
## 状态与人工阅读报告
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
d2h workflow init --plan /abs/pages.json --features /abs/features.json --execution /abs/execution.json
|
|
37
|
+
d2h workflow event --plan /abs/pages.json --features /abs/features.json --execution /abs/execution.json --event /abs/event.json --expected-sequence 0
|
|
38
|
+
d2h workflow render --plan /abs/pages.json --features /abs/features.json --execution /abs/execution.json --output /abs/batch_report.json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
init/event/reconcile 成功写入账本后,同步生成同目录 batch_report.json 与 batch_report.md;Markdown 用于人工阅读,不手改状态。保留候选、证据和事件身份,不自动导入旧 PASS。渲染失败可以重试 render,但不能重放已经提交的事件。
|
|
42
|
+
|
|
43
|
+
工具拒绝覆盖旧页面 JSON schema、带历史的非自动生成 Markdown 和链接报告。旧页面流程仍可显式选择;新旧同名产物必须按 schema 分开或保存历史后迁移,不能混写。
|
|
44
|
+
|
|
45
|
+
## 从旧私有脚本迁移
|
|
46
|
+
|
|
47
|
+
- 原 `skills/d2h-spec-plan/scripts/spec_runtime.py <action>` 改为 `d2h workflow <action>`;artifact/event schema 及现有历史不变。
|
|
48
|
+
- 原功能规划私有验证器改为 `d2h workflow validate-scope`,不依赖已经删除的功能 Skill 路径。
|
|
49
|
+
- 实施 Skill 不再加载规划 Skill 的执行协议。派发字段通过 `d2h workflow contract dispatch` 获取;公共执行协议仅由 d2h 使用。
|
|
50
|
+
- 运行时与看板无需安装规划 Skill。现有调用方需先确认 `d2h workflow --help` 可用,工具缺失时向调用方返回 requiredTools,不从 Skill 内自行安装。
|
|
51
|
+
- 本次只解除 Spec 相关耦合。静态 UTG 跨目录共享实现、原生库嵌套分发和旧页面 Skill 内部领域协作等不在本次改动范围。
|
|
52
|
+
|
|
53
|
+
## 可选看板交互
|
|
54
|
+
|
|
55
|
+
看板询问不作为迁移前置条件。调用方先确认工具存在,再用 `d2h_interact_capabilities` 查询当前项目、会话的实时界面能力;缺失或不支持时跳过该流程,必要确认改用当前对话。`d2h_interact` 内部再次检查,无界面不创建请求;等待期间失联则使请求失效并返回 `unsupported`。只有接入能力心跳协议的适配器才算支持,目前已接入 OpenCode TUI,其他宿主未接入时回退对话。不把界面缺失、取消或超时视作同意;已有授权可复用,普通报告展示不设确认门禁。入口由人工将这些约定接入 `agents/d2h.md`。
|
|
56
|
+
|
|
57
|
+
## 保留原生成能力
|
|
58
|
+
|
|
59
|
+
规划只写 pages.json 和派生波次摘要,以 planning-only 校验评审。原 d2h-spec-generate 保留 legacy-page 模式,新增 from-plan 模式消费选定 v2 计划,只生成当前成员,不重新排序或初始化旧账本。生成契约由公共工具提供,详细内容要求和正式模板归生成能力维护。MVP 扩展到核心时复用有效分析/架构、已完成 Spec 和执行历史,针对差量刷新计划和正文;新策略对未生成/失效绑定阻止派发,旧模式不隐式升级。d2h.md 的入口适配仍由人工完成。
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { workflowAsset } from '../../dist/opencode/interactive-tools/progress.js';
|
|
6
|
+
|
|
7
|
+
if (!process.argv[2]) { throw new Error('需要指定演示项目路径'); }
|
|
8
|
+
const project = fs.realpathSync(process.argv[2]);
|
|
9
|
+
const config = JSON.parse(fs.readFileSync(path.join(project, '.opencode/d2h-progress.json'), 'utf8'));
|
|
10
|
+
if (config.schema !== 'd2h-progress-config/v1' || config.demo !== true) { throw new Error('只允许操作明确标记 demo 的演示配置'); }
|
|
11
|
+
const plan = path.resolve(project, config.plan);
|
|
12
|
+
const features = JSON.parse(fs.readFileSync(plan, 'utf8')).feature_source.path;
|
|
13
|
+
const execution = path.join(path.dirname(plan), 'execution.json');
|
|
14
|
+
const state = JSON.parse(fs.readFileSync(execution, 'utf8'));
|
|
15
|
+
if (state.candidate !== 'demo-progress-only' || state.tasks.polish?.implementation !== 'in_progress') { throw new Error('不是初始演示账本或已经推进过;请新建演示项目,不重置已有账本'); }
|
|
16
|
+
const event = path.join(project, `demo-event-${randomUUID()}.json`);
|
|
17
|
+
fs.writeFileSync(event, JSON.stringify({ type: 'implemented', spec_id: 'polish', commit: 'demo-progress-update-only' }), { flag: 'wx' });
|
|
18
|
+
try {
|
|
19
|
+
execFileSync(process.platform === 'win32' ? 'python' : 'python3', [workflowAsset('spec_runtime.py'), 'event', '--plan', plan, '--features', features, '--execution', execution, '--event', event, '--expected-sequence', String(state.events.length)], { cwd: project, stdio: 'ignore' });
|
|
20
|
+
} finally { fs.unlinkSync(event); }
|
|
21
|
+
console.log('演示已推进:实现 1/3 → 2/3,验收仍为 0/3;侧栏和网页约 3 秒内更新。');
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { listInteractions, interactionStatus, readInteraction, submitDecision } from './core.ts';
|
|
5
|
+
import type { Interaction } from './core.ts';
|
|
6
|
+
import { interactionHtml } from './panel.ts';
|
|
7
|
+
import { progressHtml } from './progress.ts';
|
|
8
|
+
import type { ProgressState } from './progress.ts';
|
|
9
|
+
|
|
10
|
+
/** One local host per TUI process. Every URL grants access to just one workflow/session. */
|
|
11
|
+
export async function startInteractionBrowser() {
|
|
12
|
+
const bindings = new Map<string, Interaction>();
|
|
13
|
+
const progressBindings = new Map<string, () => ProgressState>();
|
|
14
|
+
let origin = '';
|
|
15
|
+
const server = http.createServer(async (req, res) => {
|
|
16
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
17
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
18
|
+
res.setHeader('Referrer-Policy', 'no-referrer');
|
|
19
|
+
if (req.headers.host !== new URL(origin).host || (req.headers.origin && req.headers.origin !== origin)) {
|
|
20
|
+
res.writeHead(403).end(); return;
|
|
21
|
+
}
|
|
22
|
+
const url = new URL(req.url ?? '/', origin);
|
|
23
|
+
const match = /^\/([a-f0-9]{48})\/(snapshot|action)?$/.exec(url.pathname);
|
|
24
|
+
const binding = match ? bindings.get(match[1]) : undefined;
|
|
25
|
+
const progress = match ? progressBindings.get(match[1]) : undefined;
|
|
26
|
+
if ((!binding && !progress) || !match) { res.writeHead(404).end(); return; }
|
|
27
|
+
try {
|
|
28
|
+
if (progress) {
|
|
29
|
+
const state = progress();
|
|
30
|
+
if (req.method === 'GET' && !match[2]) {
|
|
31
|
+
const nonce = randomBytes(18).toString('base64');
|
|
32
|
+
res.setHeader('Content-Security-Policy', `default-src 'none'; script-src 'nonce-${nonce}'; style-src 'unsafe-inline'; connect-src 'self'`);
|
|
33
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
34
|
+
res.end(progressHtml(state).replace('<script>', `<script nonce="${nonce}">`));
|
|
35
|
+
} else if (req.method === 'GET' && match[2] === 'snapshot') {
|
|
36
|
+
res.setHeader('Content-Type', 'application/json');
|
|
37
|
+
if (state.status !== 'ready' || !state.snapshot) { res.writeHead(503).end(JSON.stringify({ error: state.error ?? '进度读取中' })); }
|
|
38
|
+
else { res.end(JSON.stringify(state.snapshot)); }
|
|
39
|
+
} else { res.writeHead(405).end(); }
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (!binding) { res.writeHead(404).end(); return; }
|
|
43
|
+
if (req.method === 'GET' && !match[2]) {
|
|
44
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
45
|
+
res.end(interactionHtml(randomBytes(18).toString('base64')));
|
|
46
|
+
} else if (req.method === 'GET' && match[2] === 'snapshot') {
|
|
47
|
+
const items = listInteractions(binding.projectRoot)
|
|
48
|
+
.filter(request => request.sessionId === binding.sessionId && request.workflowPath === binding.workflowPath)
|
|
49
|
+
.map(request => ({ request, status: interactionStatus(request) }));
|
|
50
|
+
res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ type: 'snapshot', items }));
|
|
51
|
+
} else if (req.method === 'POST' && match[2] === 'action') {
|
|
52
|
+
if (req.headers.origin !== origin || req.headers['content-type'] !== 'application/json') {
|
|
53
|
+
res.writeHead(403).end(); return;
|
|
54
|
+
}
|
|
55
|
+
const chunks: Buffer[] = []; let size = 0;
|
|
56
|
+
for await (const chunk of req) {
|
|
57
|
+
size += Buffer.byteLength(chunk);
|
|
58
|
+
if (size > 64 * 1024) { throw new Error('请求过大'); }
|
|
59
|
+
chunks.push(Buffer.from(chunk));
|
|
60
|
+
}
|
|
61
|
+
const message = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
62
|
+
if (message.type === 'submit') {
|
|
63
|
+
const request = readInteraction(binding.projectRoot, message.id);
|
|
64
|
+
if (request.sessionId !== binding.sessionId || request.workflowPath !== binding.workflowPath) { throw new Error('请求不属于当前网页会话'); }
|
|
65
|
+
submitDecision(binding.projectRoot, message.id, message.revision, message.action, message.selected, message.reason);
|
|
66
|
+
} else if (message.type !== 'ready') { throw new Error('未知动作'); }
|
|
67
|
+
res.setHeader('Content-Type', 'application/json'); res.end('{}');
|
|
68
|
+
} else { res.writeHead(404).end(); }
|
|
69
|
+
} catch (error) {
|
|
70
|
+
res.writeHead(400, { 'Content-Type': 'application/json' }).end(JSON.stringify({ error: String(error instanceof Error ? error.message : error) }));
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
await new Promise<void>((resolve, reject) => {
|
|
74
|
+
server.once('error', reject);
|
|
75
|
+
server.listen(0, '127.0.0.1', () => {
|
|
76
|
+
const address = server.address();
|
|
77
|
+
if (!address || typeof address === 'string') { reject(new Error('工作台端口不可用')); return; }
|
|
78
|
+
origin = `http://127.0.0.1:${address.port}`; resolve();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
server.unref();
|
|
82
|
+
return {
|
|
83
|
+
urlFor(request: Interaction): string {
|
|
84
|
+
const token = randomBytes(24).toString('hex');
|
|
85
|
+
bindings.set(token, request);
|
|
86
|
+
return `${origin}/${token}/?request=${encodeURIComponent(request.id)}`;
|
|
87
|
+
},
|
|
88
|
+
progressUrlFor(read: () => ProgressState): string {
|
|
89
|
+
const token = randomBytes(24).toString('hex'); progressBindings.set(token, read);
|
|
90
|
+
return `${origin}/${token}/?snapshot=${encodeURIComponent(`/${token}/snapshot`)}`;
|
|
91
|
+
},
|
|
92
|
+
async close(): Promise<void> {
|
|
93
|
+
bindings.clear(); progressBindings.clear(); server.closeAllConnections();
|
|
94
|
+
await new Promise<void>(resolve => server.close(() => resolve()));
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function openInteractionBrowser(url: string): Promise<void> {
|
|
100
|
+
const target = new URL(url);
|
|
101
|
+
if (target.protocol !== 'http:' || target.hostname !== '127.0.0.1') { throw new Error('只能打开本地工作台'); }
|
|
102
|
+
const [command, args] = process.platform === 'darwin' ? ['open', [url]]
|
|
103
|
+
: process.platform === 'win32' ? ['rundll32.exe', ['url.dll,FileProtocolHandler', url]]
|
|
104
|
+
: ['xdg-open', [url]];
|
|
105
|
+
await new Promise<void>((resolve, reject) => {
|
|
106
|
+
const child = spawn(command, args, { stdio: 'ignore', windowsHide: true });
|
|
107
|
+
child.once('error', reject);
|
|
108
|
+
child.once('spawn', () => { child.unref(); resolve(); });
|
|
109
|
+
});
|
|
110
|
+
}
|