@zq-silk/yui 0.14.2 → 0.15.1

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.
Files changed (44) hide show
  1. package/ARCHITECTURE.md +27 -12
  2. package/README.md +85 -61
  3. package/dist/cli/commandCatalog.js +6 -6
  4. package/dist/cli/updateCommand.js +17 -9
  5. package/dist/cli/updateOrchestrator.js +81 -15
  6. package/dist/cli/updatePorts.js +72 -10
  7. package/dist/cli/upgradeCommand.js +104 -19
  8. package/dist/cli.js +2 -2
  9. package/dist/commands/agentCommands.js +13 -6
  10. package/dist/commands/controllerCommands.js +1 -1
  11. package/dist/commands/globalRoleCommands.js +11 -3
  12. package/dist/commands/roleConfiguration.js +7 -0
  13. package/dist/commands/roleRuntimeGuard.js +30 -0
  14. package/dist/commands/taskCommands.js +10 -3
  15. package/dist/controller/fileSchedulerStoreAdapter.js +4 -4
  16. package/dist/controller/runtime.js +11 -25
  17. package/dist/controller/runtimeLaunchCoordinator.js +9 -30
  18. package/dist/controller/sessionNotify.js +5 -0
  19. package/dist/core/controllerServer.js +5 -5
  20. package/dist/doctor/doctor.js +37 -14
  21. package/dist/executor/agentExecutor.js +8 -11
  22. package/dist/executor/effectiveLaunch.js +34 -17
  23. package/dist/executor/fileRoleLaunchPlanner.js +11 -8
  24. package/dist/observability/runtimeIdentity.js +48 -50
  25. package/dist/release/runtimeRelease.js +9 -1
  26. package/dist/runtime/agentHost.js +7 -0
  27. package/dist/runtime/codexInteractiveHost.js +191 -0
  28. package/dist/runtime/exactControlPlane.js +20 -29
  29. package/dist/runtime/structuredProviderHost.js +35 -0
  30. package/dist/runtime/tmuxAdapters.js +51 -9
  31. package/dist/scheduler/activeRoleTurnDelivery.js +4 -4
  32. package/dist/scheduler/leaderWakeupProcessor.js +3 -4
  33. package/dist/storage/currentTaskStore.js +6 -4
  34. package/dist/storage/sqliteSchema.js +134 -59
  35. package/dist/storage/sqliteStore.js +7 -5
  36. package/dist/storage/storageSchema.js +92 -223
  37. package/dist/storage/storageVersions.js +12 -16
  38. package/dist/storage/upgrade/upgradeOrchestrator.js +224 -62
  39. package/dist/tmux/tmuxManager.js +43 -28
  40. package/dist/version.js +3 -3
  41. package/docs/task-local-identity.md +9 -9
  42. package/i18n/README.zh-CN.md +33 -17
  43. package/package.json +1 -1
  44. package/dist/storage/upgrade/recordVersions.js +0 -82
@@ -14,6 +14,9 @@ Yui 不把 Agent 的判断固化成确定性的工作流引擎。核心只负责
14
14
 
15
15
  当前实现保留实用的 Role/Agent/session 与 CLI 框架,不恢复后期膨胀的数据维护、租约、定时调度和恢复账本体系。
16
16
 
17
+ [目标架构手册](../docs/architecture/README.md) 保存了供后续重构使用的
18
+ 2026-09-06 版设计基线,不代表当前已实现行为。
19
+
17
20
  ## 核心模型
18
21
 
19
22
  - `WorkItem`:唯一的有界工作单元,保存目标、验收条件、依赖、状态和精简结果。
@@ -101,7 +104,11 @@ export YUI_HOME=/absolute/path/to/yui-home
101
104
  yui setup
102
105
  ```
103
106
 
104
- home 中包含 `schema.json`、权威 SQLite 数据库 `yui.db`、Project Catalog、项目知识和 Controller 发现文件。稳定 Project checkout 与受管理 worktree 位于 home 外部的 workspace。运行时只接受当前存储契约:不会回退读取 `state.json`、转换旧 schema 或猜测旧 ID。
107
+ home 中包含权威 SQLite 数据库 `yui.db`、Project Catalog、项目知识和
108
+ Controller 发现文件。稳定 Project checkout 与受管理 worktree 位于 home
109
+ 外部的 workspace。旧 `schema.json` 与 `state.json` 只作为历史证据存在,
110
+ 不再是版本权威。运行时只接受当前存储契约;支持区间内的早期存储版本只允许
111
+ 通过显式升级入口。
105
112
 
106
113
  所有 Task-owned 记录族都在各自 Task 内分配单调递增的本地 ID。因此,不同
107
114
  Task 可以同时拥有 `work-item-1`、`turn-1` 或 `input-1`。受管 Task
@@ -111,8 +118,12 @@ Task 的命令(例如 `task work create`、`task integration start`)仍使
111
118
  Task 内的本地子记录 ID。Candidate 只在所属 WorkItem 内递增,并同时保存
112
119
  Task 与 WorkItem provenance。
113
120
 
114
- Yui 只支持当前 aggregate-v31 / Task-v7 schema。旧 home 不提供转换、
115
- 双读或历史记录推断;需要使用新版本时初始化全新的 `YUI_HOME`。当前引用契约见
121
+ Yui Home 只有一个存储版本,权威值是 SQLite 中连续且校验和有效的迁移
122
+ ledger 头。CLI 同时公布当前存储版本和最小支持迁移版本。普通运行时代码只读取
123
+ 最新结构,不提供双读;`yui upgrade --dry-run` 只显示迁移计划,
124
+ `yui upgrade` 会停住正在运行的 Controller、创建一致性备份、在一个事务中执行
125
+ 全部缺失迁移并校验最新结构。处于支持区间内的任意历史 Home 都可以直接跨多个
126
+ 版本升级,无需逐个安装中间版本。当前引用契约见
116
127
  [Task 本地 ID](../docs/task-local-identity.md)。
117
128
 
118
129
  ## 快速开始
@@ -611,6 +622,8 @@ Task 生命周期的交互选择只展示有效来源状态:activate 只展示
611
622
 
612
623
  Session、Activation 与 Turn 是独立身份。Session 可以跨多个 Turn 和客户端连接;Activation 只代表 Yui 当前的连接,而不是对 Provider thread 的独占所有权。每次 Provider 执行对应一个持久 Turn;写入超时或结果不明确会进入 `delivery-unknown`,不会自动重发。Codex 已存在的 active Turn 只会让 Yui 暂时等待,不会导致待投递 Turn 失败;Claude 等独立进程 Provider 继续通过 Yui 的 view/takeover 边界进行人工控制。
613
624
 
625
+ 恢复只在真的续不下去时被拦住:provider 侧没有可恢复的 Session、换了 Agent 或适配器、换了物理工作区。模型、推理强度、权限策略、Role 说明与 Skill、声明的写范围只决定下一次 activation 用什么,审查轮次、候选 commit、工作区基线这类每轮事实不影响复用。因此当 Role 存在活跃 Session 时,`task role update`、`config role update`、`config agent update` 会先报告该 Session 并要求 `--yes` 确认;需要立刻生效则先停止该 Session。
626
+
614
627
  Turn 是 Role 是否有工作正在执行的唯一持久调度状态,记录可见输入、来源/渠道与最终回复,不复制思考过程或工具调用。所有经 Yui 中转或生成的输入统一使用 `source: yui`;Provider UI 中直接输入的消息使用 `source: user`;显式 Goal continuation 使用 `source: provider`。Provider Turn 终态后 Yui 完成该 Turn,再把下一个 Turn 投递到同一 Session。TaskRole 本身只保存身份和期望启动配置,不再保存可写的运行状态;CLI/Web 展示的 Role 状态由活动 Turn 派生,并叠加 Session/Driver 生命周期事实用于诊断。
615
628
 
616
629
  Goal 是 Session 级显式 Provider 事实,可以跨越多个 Turn。Codex 通过 Goal API/事件提供,Claude 通过 `active_goal` 提供;Yui 不用静默等待来猜测 Goal 是否完成。Turn 结束不等于 Goal、WorkItem 或 Task 完成,只有 Leader 更新 WorkItem 与 Task 的持久语义。
@@ -627,13 +640,15 @@ yui task role release <task-id> <role>
627
640
 
628
641
  Codex Role thread 可在 Desktop 中直接查看和操作;Desktop 已有 active Turn 时,Yui 只保留待投递工作并等待,不会失败或重复投递。`view`、`takeover`、`release` 继续作为 Claude 等独立进程 Provider 的人工控制入口。Yui 不写入全局 Hook/config,也不启动、重启或停止共享 daemon;Codex CLI/daemon 故障由 Task 生命周期之外修复。Global Operator 与 global Role 继续使用原生交互式 CLI,不属于受管理 Task Provider 协议;Yui 在内部将 Codex 的 Global TUI 连接到同一个默认 App Server,用户不能通过 Agent 或 Role 参数覆盖该连接,Session Manifest 自带不依赖启动进程环境的 Global Context 命令,因此同一 thread 可直接切换到 Desktop 继续对话。
629
642
 
630
- 当新版本需要离线迁移 Home 时,应等待当前 Turn 完成,然后从普通 shell
631
- 执行 `yui session stop --all`,再重新执行 `yui update`。停止命令会先整体预检:
632
- 只要仍有 Session 正在运行或存在未决生命周期工作,就不会开始停止;全部空闲
633
- 时会先阻止新的 Leader 调度,停止并等待 Controller 完全退出,重新检查运行时
634
- 事实后再停止 Task Role global Role Session。成功后 Controller 保持停止,
635
- 应紧接着执行 `yui update`。如果当前安装版本还没有这条命令,应手动退出提示中
636
- 列出的全部 managed Session;新的 staged CLI 不能写入尚待迁移的旧 Home。
643
+ Global Codex 的薄 Host 与原生 TUI 位于同一个 pane,透明转发 App Server 连接,并从该 TUI 自己的 `thread/start` 或 `thread/resume` 成功响应取得 Thread ID。Yui 在首条用户消息之前通过既有启动回执登记身份,不依赖 `notify`、历史目录扫描或 bootstrap 消息;旧的 global `notify` 不能登记或修改 Session 生命周期。连接随 TUI 退出,不依赖 Controller 的持续运行。tmux 窗口存在不等于 Agent 存活:`pane_dead=0` 才是运行中,`pane_dead=1` 是保留的退出现场,读取失败则报错。状态查询不删除现场;显式启动可重建精确的死亡窗口,但不能覆盖身份未知的活 Operator。
644
+
645
+ `yui update` 会用目标版本先做只读预检,在停住精确的旧 Controller 后自动执行
646
+ 所需的离线迁移,再校验并启动新 Controller。若升级前希望结束所有 Agent
647
+ 活动,可先执行 `yui session stop --all`;这不是存储版本链的一部分。
648
+ Yui 0.15.0 建立 storage version 1 和迁移下限。更早版本(包括 0.14.2)
649
+ 创建的 Home 不在这条兼容链上:应保留给匹配的历史 Yui 版本查看,或者初始化
650
+ 新的 Home。从 0.15.0 开始,后续版本必须保留完整迁移链,因此可以由
651
+ `yui update` 直接跨版本升级。
637
652
 
638
653
  tmux 会在 pane 创建时固定其历史容量。配置该限制之前创建的 Role 会保留原容量;Yui 会在 Terminal attach 和 Web 中提示用户退出并重新进入一次,从而创建具有 100,000 行历史的新 pane。
639
654
 
@@ -675,10 +690,10 @@ yui controller restart
675
690
 
676
691
  `controller restart` 会用当前安装的 Yui 版本替换 Controller 进程及其调度循环、socket 服务,不会停止或重启已受管的 tmux/Agent 会话;普通 Session 命令按协议与存储身份兼容,不要求 Controller 与 CLI 包版本完全相同。
677
692
 
678
- 成功的 `setup`、`upgrade` 和 `update` 都会确保当前 Home 有一个运行中的
679
- Controller;如果之前没有运行,会在完成后启动。只读命令和
680
- `upgrade --dry-run` 不会启动 Controller。`update` 只有在新二进制健康检查通过后,
681
- 才会替换或启动 Controller。
693
+ 成功的 `setup` 和 `update` 会确保当前 Home 有一个运行中的 Controller。
694
+ `upgrade` 只会在迁移前存在 Controller 时恢复它;只读命令和
695
+ `upgrade --dry-run` 不会启动 Controller。`update` 只有在迁移和新二进制健康
696
+ 检查都通过后,才会替换或启动 Controller。
682
697
 
683
698
  恢复 reconciliation 默认每 120 秒执行一次。普通持久状态变化只会将 Task、Role 或 Operator key 放入队列并立即返回;固定 100ms 窗口内到达的 key 会合并触发一次不重叠的定向处理。Operator 呈现使用独立 lane,不会被 Task 的 Git/worktree 操作阻塞;周期 Git/worktree 处理只覆盖仍有持久 Task mailbox 工作的 Task,活动 Role 的存活检查合并为一次 tmux inventory。来自 Provider 原生事件或受支持 Hook 的结构化 Agent Driver observation,会经过精确 fence 后进入持久 runtime inbox。终态 Turn observation 会原子记录精确的 Turn 结果。持久 WorkMailbox 会冻结当前 processing 批次,期间的新事件合并到下一 pending 批次;失败会释放当前批次供恢复。推荐输入与 pending Turn 共用最近 deadline 选择器,不依赖恢复扫描间隔;显式 `task reconcile` 仍会立即请求恢复扫描。保留的闭环为:
684
699
 
@@ -724,6 +739,7 @@ Web 端可以通过与 Terminal 相同的持久化 CLI 路径回答 open InputRe
724
739
 
725
740
  ```sh
726
741
  yui update
742
+ yui upgrade [--dry-run]
727
743
  yui config agent add|list|show|capabilities|update|remove
728
744
  yui config role add|list|show|update|remove|bind|unbind
729
745
  yui config profile add|list|show|update|remove|reset
@@ -749,9 +765,9 @@ npm test
749
765
  npm run lint
750
766
  ```
751
767
 
752
- `npm test` 只保留秒级核心 smoke:CLI 启动、正常 SQLite Task、受支持迁移和内置
753
- Agent Driver。针对当前修改编写的 TDD、异常数据和故障复现仅作为开发期证据,需求完成后
754
- 删除,不累积为常驻回归测试。具体约束见
768
+ `npm test` 只保留秒级核心 smoke:CLI 启动、正常 SQLite Task、存储基线、
769
+ 目标驱动更新和内置 Agent Driver。针对当前修改编写的 TDD、异常数据和故障复现
770
+ 仅作为开发期证据,需求完成后删除,不累积为常驻回归测试。具体约束见
755
771
  [验证策略](../docs/testing/verification-levels.md)。
756
772
 
757
773
  如需让用户终端使用当前 checkout,可逆地接管用户级 `yui` 命令:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zq-silk/yui",
3
- "version": "0.14.2",
3
+ "version": "0.15.1",
4
4
  "description": "Local control plane for long-running native agent CLI sessions backed by tmux.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -1,82 +0,0 @@
1
- /** Current record-family versions for the single SQLite storage contract. */
2
- import { CURRENT_AGGREGATE_SCHEMA_VERSION, CURRENT_STORAGE_LAYOUT_VERSION } from "../storageVersions.js";
3
- import { CURRENT_TURN_SCHEMA_VERSION, CURRENT_AGENT_PROFILE_SCHEMA_VERSION, CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION, CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_CONFIGURED_AGENT_SCHEMA_VERSION, CURRENT_CHANGE_SET_SCHEMA_VERSION, CURRENT_CONTEXT_SNAPSHOT_SCHEMA_VERSION, CURRENT_DECISION_SCHEMA_VERSION, CURRENT_EVENT_SCHEMA_VERSION, CURRENT_GLOBAL_ROLE_SCHEMA_VERSION, CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION, CURRENT_INPUT_REQUEST_SCHEMA_VERSION, CURRENT_INTEGRATION_ATTEMPT_SCHEMA_VERSION, CURRENT_INTEGRATION_QUEUE_SCHEMA_VERSION, CURRENT_MANAGED_WORKSPACE_SCHEMA_VERSION, CURRENT_MESSAGE_SCHEMA_VERSION, CURRENT_MILESTONE_SCHEMA_VERSION, CURRENT_PUBLICATION_REFERENCE_SCHEMA_VERSION, CURRENT_PROJECT_SCHEMA_VERSION, CURRENT_RELEASE_WORKFLOW_SCHEMA_VERSION, CURRENT_REVIEW_ROUND_SCHEMA_VERSION, CURRENT_TASK_BRIEF_SCHEMA_VERSION, CURRENT_TASK_ROLE_SCHEMA_VERSION, CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION, CURRENT_TASK_SCHEMA_VERSION, CURRENT_WORK_ITEM_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION } from "../taskStore.js";
4
- import { CURRENT_LEADER_FAILURE_SCHEMA_VERSION } from "../../scheduler/leaderFailure.js";
5
- import { CURRENT_TASK_WAKE_SCHEMA_VERSION } from "../../scheduler/taskWake.js";
6
- import { CURRENT_DURABLE_JOB_SCHEMA_VERSION } from "../../job/durableJob.js";
7
- function descriptor(kind, version) {
8
- return Object.freeze({ version, path: `sqlite:${kind}` });
9
- }
10
- // Lazy construction avoids the taskStore -> storageSchema -> recordVersions
11
- // initialization cycle while keeping one canonical family list.
12
- let currentDescriptors = null;
13
- function getCurrentRecordDescriptors() {
14
- if (currentDescriptors === null) {
15
- const versions = {
16
- config: CURRENT_CONFIG_SCHEMA_VERSION,
17
- configuredAgent: CURRENT_CONFIGURED_AGENT_SCHEMA_VERSION,
18
- project: CURRENT_PROJECT_SCHEMA_VERSION,
19
- agentProfile: CURRENT_AGENT_PROFILE_SCHEMA_VERSION,
20
- globalRole: CURRENT_GLOBAL_ROLE_SCHEMA_VERSION,
21
- globalRoleSessionSet: CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION,
22
- task: CURRENT_TASK_SCHEMA_VERSION,
23
- taskBrief: CURRENT_TASK_BRIEF_SCHEMA_VERSION,
24
- taskRole: CURRENT_TASK_ROLE_SCHEMA_VERSION,
25
- managedWorkspace: CURRENT_MANAGED_WORKSPACE_SCHEMA_VERSION,
26
- taskRoleSessionSet: CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION,
27
- workItem: CURRENT_WORK_ITEM_SCHEMA_VERSION,
28
- contextSnapshot: CURRENT_CONTEXT_SNAPSHOT_SCHEMA_VERSION,
29
- turn: CURRENT_TURN_SCHEMA_VERSION,
30
- reviewRound: CURRENT_REVIEW_ROUND_SCHEMA_VERSION,
31
- changeSet: CURRENT_CHANGE_SET_SCHEMA_VERSION,
32
- integrationAttempt: CURRENT_INTEGRATION_ATTEMPT_SCHEMA_VERSION,
33
- integrationQueue: CURRENT_INTEGRATION_QUEUE_SCHEMA_VERSION,
34
- durableJob: CURRENT_DURABLE_JOB_SCHEMA_VERSION,
35
- message: CURRENT_MESSAGE_SCHEMA_VERSION,
36
- inputRequest: CURRENT_INPUT_REQUEST_SCHEMA_VERSION,
37
- decision: CURRENT_DECISION_SCHEMA_VERSION,
38
- milestone: CURRENT_MILESTONE_SCHEMA_VERSION,
39
- event: CURRENT_EVENT_SCHEMA_VERSION,
40
- taskWake: CURRENT_TASK_WAKE_SCHEMA_VERSION,
41
- capabilityGrant: CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION,
42
- releaseWorkflow: CURRENT_RELEASE_WORKFLOW_SCHEMA_VERSION,
43
- publicationReference: CURRENT_PUBLICATION_REFERENCE_SCHEMA_VERSION,
44
- leaderFailure: CURRENT_LEADER_FAILURE_SCHEMA_VERSION,
45
- workMailbox: CURRENT_WORK_MAILBOX_SCHEMA_VERSION
46
- };
47
- currentDescriptors = Object.freeze(Object.fromEntries(Object.entries(versions).map(([kind, version]) => [kind, descriptor(kind, version)])));
48
- }
49
- return currentDescriptors;
50
- }
51
- /** Defensive copy of the current record-family contract. */
52
- export function currentRecordVersions(candidate = getCurrentRecordDescriptors()) {
53
- assertRecordVersionDescriptors(candidate);
54
- return { ...candidate };
55
- }
56
- /** Reject missing, extra, stale, or non-SQLite record descriptors. */
57
- export function assertRecordVersionDescriptors(candidate = getCurrentRecordDescriptors()) {
58
- const expected = getCurrentRecordDescriptors();
59
- const expectedKinds = Object.keys(expected);
60
- const mappedKinds = Object.keys(candidate);
61
- const missing = expectedKinds.filter((kind) => !Object.hasOwn(candidate, kind));
62
- const unexpected = mappedKinds.filter((kind) => !Object.hasOwn(expected, kind));
63
- if (missing.length > 0 || unexpected.length > 0) {
64
- throw new Error(`Record version map completeness drift: missing=${missing.join(",") || "none"}; `
65
- + `unexpected=${unexpected.join(",") || "none"}.`);
66
- }
67
- for (const kind of expectedKinds) {
68
- const wanted = expected[kind];
69
- const found = candidate[kind];
70
- if (found?.version !== wanted.version || found.path !== wanted.path) {
71
- throw new Error(`Record version map drift for ${kind}: expected=${wanted.version}@${wanted.path}; `
72
- + `actual=${String(found?.version)}@${String(found?.path)}.`);
73
- }
74
- }
75
- }
76
- export function latestStorageVersionState(recordDescriptors = getCurrentRecordDescriptors()) {
77
- return {
78
- layout: CURRENT_STORAGE_LAYOUT_VERSION,
79
- aggregate: CURRENT_AGGREGATE_SCHEMA_VERSION,
80
- record: currentRecordVersions(recordDescriptors)
81
- };
82
- }