dsh-kingdom 0.3.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.
- package/LICENSE +29 -0
- package/README.md +184 -0
- package/cordis.patch.yml +6 -0
- package/lib/core/binding.js +57 -0
- package/lib/core/binding.js.map +1 -0
- package/lib/core/db.js +521 -0
- package/lib/core/db.js.map +1 -0
- package/lib/core/execution.js +88 -0
- package/lib/core/execution.js.map +1 -0
- package/lib/core/kingdom.js +117 -0
- package/lib/core/kingdom.js.map +1 -0
- package/lib/core/task-service.js +581 -0
- package/lib/core/task-service.js.map +1 -0
- package/lib/core/task.js +93 -0
- package/lib/core/task.js.map +1 -0
- package/lib/core/territory.js +47 -0
- package/lib/core/territory.js.map +1 -0
- package/lib/gui/contract.js +53 -0
- package/lib/gui/contract.js.map +1 -0
- package/lib/gui/server.js +204 -0
- package/lib/gui/server.js.map +1 -0
- package/lib/gui/snapshot.js +379 -0
- package/lib/gui/snapshot.js.map +1 -0
- package/lib/index.js +489 -0
- package/lib/index.js.map +1 -0
- package/lib/paths.js +23 -0
- package/lib/paths.js.map +1 -0
- package/lib/types/core/binding.d.ts +11 -0
- package/lib/types/core/db.d.ts +261 -0
- package/lib/types/core/execution.d.ts +51 -0
- package/lib/types/core/kingdom.d.ts +34 -0
- package/lib/types/core/task-service.d.ts +107 -0
- package/lib/types/core/task.d.ts +60 -0
- package/lib/types/core/territory.d.ts +9 -0
- package/lib/types/gui/contract.d.ts +226 -0
- package/lib/types/gui/server.d.ts +26 -0
- package/lib/types/gui/snapshot.d.ts +44 -0
- package/lib/types/index.d.ts +80 -0
- package/lib/types/paths.d.ts +6 -0
- package/lib/types/worker/dsh-subagent.d.ts +79 -0
- package/lib/types/worker/executor.d.ts +121 -0
- package/lib/worker/dsh-subagent.js +117 -0
- package/lib/worker/dsh-subagent.js.map +1 -0
- package/lib/worker/executor.js +94 -0
- package/lib/worker/executor.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { type TaskStatus } from './task.js';
|
|
3
|
+
import { type ExecutionState } from './execution.js';
|
|
4
|
+
/**
|
|
5
|
+
* 记录在 kingdoms.schema_version 上的值。
|
|
6
|
+
*
|
|
7
|
+
* Phase 2 **刻意保持 1**:本插件没有任何按版本号分支的 migration 逻辑,
|
|
8
|
+
* 建表全部幂等,任何 0.2.0 打开的旧库都会在开库瞬间收敛到同一套 6 表结构。
|
|
9
|
+
* 此时把新库标成 2、旧库留在 1,只会制造一个「同结构不同版本号」的假差异。
|
|
10
|
+
* 真正引入破坏性 migration 时再启用这个字段作为 gate。
|
|
11
|
+
*/
|
|
12
|
+
export declare const SCHEMA_VERSION = 1;
|
|
13
|
+
export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS kingdoms (\n kingdom_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n created_at TEXT NOT NULL,\n owner_id TEXT NOT NULL,\n owner_name TEXT NOT NULL,\n schema_version INTEGER NOT NULL DEFAULT 1\n);\n\nCREATE TABLE IF NOT EXISTS territories (\n territory_id TEXT PRIMARY KEY,\n kingdom_id TEXT NOT NULL,\n name TEXT NOT NULL,\n workspace_path TEXT,\n summary TEXT,\n supervisor_binding_id TEXT,\n status TEXT NOT NULL DEFAULT 'ACTIVE',\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS role_bindings (\n binding_id TEXT PRIMARY KEY,\n kingdom_id TEXT NOT NULL,\n role_type TEXT NOT NULL CHECK(role_type IN ('OWNER','CHANCELLOR','SUPERVISOR','WORKER')),\n role_name TEXT NOT NULL,\n runtime_type TEXT NOT NULL DEFAULT 'dsh',\n session_id TEXT,\n principal_id TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS tasks (\n task_id TEXT PRIMARY KEY,\n territory_id TEXT NOT NULL,\n parent_task_id TEXT,\n title TEXT NOT NULL,\n description TEXT,\n assigned_binding_id TEXT,\n status TEXT NOT NULL DEFAULT 'CREATED',\n acceptance_criteria TEXT,\n result_summary TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS events (\n event_id TEXT PRIMARY KEY,\n kingdom_id TEXT NOT NULL,\n event_type TEXT NOT NULL,\n actor_role TEXT,\n actor_id TEXT,\n target_type TEXT,\n target_id TEXT,\n payload_json TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS worker_results (\n result_id TEXT PRIMARY KEY,\n task_id TEXT NOT NULL,\n attempt_no INTEGER NOT NULL,\n worker_binding_id TEXT,\n session_id TEXT,\n outcome TEXT NOT NULL,\n result_json TEXT NOT NULL DEFAULT '{}',\n created_at TEXT NOT NULL,\n UNIQUE(task_id, attempt_no)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS territories_kingdom_name_uk\n ON territories(kingdom_id, name);\n\nCREATE TABLE IF NOT EXISTS executions (\n execution_id TEXT PRIMARY KEY,\n task_id TEXT NOT NULL,\n attempt_no INTEGER NOT NULL,\n worker_binding_id TEXT,\n session_id TEXT,\n state TEXT NOT NULL,\n detail TEXT,\n started_at TEXT NOT NULL,\n heartbeat_at TEXT,\n ended_at TEXT,\n pause_requested_at TEXT,\n UNIQUE(task_id, attempt_no)\n);\n\nCREATE INDEX IF NOT EXISTS executions_task_idx ON executions(task_id);\n";
|
|
14
|
+
export interface KingdomRow {
|
|
15
|
+
kingdom_id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
created_at: string;
|
|
18
|
+
owner_id: string;
|
|
19
|
+
owner_name: string;
|
|
20
|
+
schema_version: number;
|
|
21
|
+
}
|
|
22
|
+
export interface TerritoryRow {
|
|
23
|
+
territory_id: string;
|
|
24
|
+
kingdom_id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
workspace_path: string | null;
|
|
27
|
+
summary: string | null;
|
|
28
|
+
supervisor_binding_id: string | null;
|
|
29
|
+
status: string;
|
|
30
|
+
created_at: string;
|
|
31
|
+
}
|
|
32
|
+
export interface RoleBindingRow {
|
|
33
|
+
binding_id: string;
|
|
34
|
+
kingdom_id: string;
|
|
35
|
+
role_type: string;
|
|
36
|
+
role_name: string;
|
|
37
|
+
runtime_type: string;
|
|
38
|
+
session_id: string | null;
|
|
39
|
+
principal_id: string | null;
|
|
40
|
+
created_at: string;
|
|
41
|
+
updated_at: string;
|
|
42
|
+
}
|
|
43
|
+
export interface EventRow {
|
|
44
|
+
event_id: string;
|
|
45
|
+
kingdom_id: string;
|
|
46
|
+
event_type: string;
|
|
47
|
+
actor_role: string | null;
|
|
48
|
+
actor_id: string | null;
|
|
49
|
+
target_type: string | null;
|
|
50
|
+
target_id: string | null;
|
|
51
|
+
payload_json: string;
|
|
52
|
+
created_at: string;
|
|
53
|
+
/**
|
|
54
|
+
* 王国内单调递增的事件序号(GUI 排序与断流检测用)。
|
|
55
|
+
*
|
|
56
|
+
* 由 `appendEvent` 在 IMMEDIATE 事务里分配,保证「读 MAX + 写入」原子。
|
|
57
|
+
* GUI 用它判断:哪个事件更新、是否漏了事件、以及**旧事件不得让已停止的人物重新出现**。
|
|
58
|
+
* 旧库由 `ensureEventSequence()` 按 rowid(即插入顺序)回填。
|
|
59
|
+
*/
|
|
60
|
+
seq: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* executions 行(Phase 3 新增第 7 张表)。
|
|
64
|
+
*
|
|
65
|
+
* **与 tasks 的分工**:`tasks.status` 是治理事实(组织裁定进度),
|
|
66
|
+
* 本表是运行事实(某一次执行此刻的状况)。
|
|
67
|
+
* `Task.RUNNING` 不等于"正在执行"——REWORK 后任务立刻回 RUNNING,
|
|
68
|
+
* 但新 Execution 尚未创建。GUI 必须看本表才能决定人物是否在工作。
|
|
69
|
+
*/
|
|
70
|
+
export interface ExecutionRow {
|
|
71
|
+
execution_id: string;
|
|
72
|
+
task_id: string;
|
|
73
|
+
attempt_no: number;
|
|
74
|
+
worker_binding_id: string | null;
|
|
75
|
+
/** 该次执行的 one-shot subagent session id(每轮 REWORK 都是新的)。 */
|
|
76
|
+
session_id: string | null;
|
|
77
|
+
/** STARTING / RUNNING / PAUSED / COMPLETED / FAILED / ABORTED,见 ./execution.ts。 */
|
|
78
|
+
state: string;
|
|
79
|
+
/** 终止原因等诊断信息(宿主观察,非 Worker 自述)。 */
|
|
80
|
+
detail: string | null;
|
|
81
|
+
started_at: string;
|
|
82
|
+
/**
|
|
83
|
+
* 最近一次**状态转移**的时刻,不是心跳。
|
|
84
|
+
*
|
|
85
|
+
* 诚实说明:one-shot subagent 这个 seam 不提供任何进度回调
|
|
86
|
+
* (`SubagentRun` 只有 `{id, localAgent, result, dispose}`),
|
|
87
|
+
* 所以执行进行中没有任何可以周期性上报的信号,本字段在整个执行体内不会前进。
|
|
88
|
+
*
|
|
89
|
+
* **不要拿它做存活判定**:一次合法的长执行与一次挂死的执行,
|
|
90
|
+
* 在这个字段上完全无法区分。插件崩溃/重载导致的残骸由加载期回收兜底
|
|
91
|
+
* (见 task-service.ts 的 reclaimOrphanExecutions),那条路径不依赖本字段。
|
|
92
|
+
*/
|
|
93
|
+
heartbeat_at: string | null;
|
|
94
|
+
ended_at: string | null;
|
|
95
|
+
/**
|
|
96
|
+
* 暂停请求时间。
|
|
97
|
+
*
|
|
98
|
+
* one-shot subagent 无法在一次 turn 中途真正挂起,因此"暂停"的诚实语义是:
|
|
99
|
+
* 请求已登记,**在下一个 attempt 边界生效**。执行中的 Execution 会保持
|
|
100
|
+
* `RUNNING` 并带 `pause_requested_at`(GUI 应显示"准备休息"而不是"已睡着")。
|
|
101
|
+
*/
|
|
102
|
+
pause_requested_at: string | null;
|
|
103
|
+
}
|
|
104
|
+
/** tasks 行(Phase 1 schema,Phase 2 一字未改)。status 语义见 ./task.ts。 */
|
|
105
|
+
export interface TaskRow {
|
|
106
|
+
task_id: string;
|
|
107
|
+
territory_id: string;
|
|
108
|
+
parent_task_id: string | null;
|
|
109
|
+
title: string;
|
|
110
|
+
description: string | null;
|
|
111
|
+
assigned_binding_id: string | null;
|
|
112
|
+
/** 权威状态。只经 KingdomStore.transitionTask 写入(全库唯一 status UPDATE)。 */
|
|
113
|
+
status: string;
|
|
114
|
+
acceptance_criteria: string | null;
|
|
115
|
+
/** 最近一次 Worker Claim 的摘要。**是 Claim,不是完成事实**。 */
|
|
116
|
+
result_summary: string | null;
|
|
117
|
+
created_at: string;
|
|
118
|
+
updated_at: string;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* worker_results 行(Phase 2 新增第 6 张表,Owner 裁决 4)。
|
|
122
|
+
*
|
|
123
|
+
* **语义写死:本表保存 Worker Claim,不代表 Task Fact。**
|
|
124
|
+
* 一行 = 一个 attempt 的 Worker 自述结果。Task 是否完成由 tasks.status 决定,
|
|
125
|
+
* 而 tasks.status 只能由 Supervisor 经 kingdom_review_task 推到 DONE。
|
|
126
|
+
* outcome 是 Worker 自称的 COMPLETED/FAILED/BLOCKED,**不参与**任何自动状态决策。
|
|
127
|
+
*/
|
|
128
|
+
export interface WorkerResultRow {
|
|
129
|
+
result_id: string;
|
|
130
|
+
task_id: string;
|
|
131
|
+
/** 第几次尝试,从 1 起;REWORK 每轮 +1。UNIQUE(task_id, attempt_no)。 */
|
|
132
|
+
attempt_no: number;
|
|
133
|
+
worker_binding_id: string | null;
|
|
134
|
+
/** 该 attempt 的 one-shot subagent session id(每轮 REWORK 都是新 session)。 */
|
|
135
|
+
session_id: string | null;
|
|
136
|
+
/** Worker 自称的结果:COMPLETED / FAILED / BLOCKED。是 Claim。 */
|
|
137
|
+
outcome: string;
|
|
138
|
+
/** 完整结构化 Claim(summary/artifacts/risks),JSON 文本。 */
|
|
139
|
+
result_json: string;
|
|
140
|
+
created_at: string;
|
|
141
|
+
}
|
|
142
|
+
export declare class KingdomStore {
|
|
143
|
+
readonly db: DatabaseSync;
|
|
144
|
+
/** 已打开即存在(表已保证);记录 init 结果供 status 用 */
|
|
145
|
+
readonly existed: boolean;
|
|
146
|
+
constructor(dbPath: string);
|
|
147
|
+
/**
|
|
148
|
+
* 给 events 补上单调序号列(0.2.0 → 0.3.0 唯一一处触及既有表的变更)。
|
|
149
|
+
*
|
|
150
|
+
* 仍然是纯增量、可重复执行、无 table-rebuild:
|
|
151
|
+
* 1. `PRAGMA table_info` 做存在性 gate(`ADD COLUMN` 没有 IF NOT EXISTS);
|
|
152
|
+
* 2. 用 rowid(= 插入顺序)回填历史行,历史顺序因此可确定地重建;
|
|
153
|
+
* 3. 索引用 `IF NOT EXISTS`。
|
|
154
|
+
*
|
|
155
|
+
* 注意 SQLite 的 `ALTER TABLE ... ADD COLUMN` 是 O(1) 元数据操作,
|
|
156
|
+
* 不重写数据页,因此旧库开库仍然是瞬时收敛。
|
|
157
|
+
*/
|
|
158
|
+
private ensureEventSequence;
|
|
159
|
+
/** 关闭连接(插件卸载/重载时调用,避免句柄泄漏)。 */
|
|
160
|
+
close(): void;
|
|
161
|
+
listKingdoms(): KingdomRow[];
|
|
162
|
+
getDefaultKingdom(): KingdomRow | null;
|
|
163
|
+
insertKingdom(row: Omit<KingdomRow, 'schema_version'> & {
|
|
164
|
+
schema_version?: number;
|
|
165
|
+
}): KingdomRow;
|
|
166
|
+
listTerritories(kingdomId: string): TerritoryRow[];
|
|
167
|
+
getTerritoryByName(kingdomId: string, name: string): TerritoryRow | null;
|
|
168
|
+
getTerritoryById(territoryId: string): TerritoryRow | null;
|
|
169
|
+
insertTerritory(row: TerritoryRow): TerritoryRow;
|
|
170
|
+
listBindings(kingdomId: string): RoleBindingRow[];
|
|
171
|
+
getBindingByRole(kingdomId: string, roleType: string): RoleBindingRow | null;
|
|
172
|
+
getBindingById(bindingId: string): RoleBindingRow | null;
|
|
173
|
+
insertBinding(row: RoleBindingRow): RoleBindingRow;
|
|
174
|
+
updateBindingSession(bindingId: string, sessionId: string | null, updatedAt: string): void;
|
|
175
|
+
listEvents(kingdomId: string, limit?: number): EventRow[];
|
|
176
|
+
/**
|
|
177
|
+
* 按序号增量拉取(GUI 轮询用):返回 seq > afterSeq 的事件,**升序**。
|
|
178
|
+
*
|
|
179
|
+
* GUI 据此判断是否漏事件(收到的首个 seq 应等于 afterSeq + 1),
|
|
180
|
+
* 漏了就重新拉一次全量 snapshot,而不是拿残缺事件流去驱动动画。
|
|
181
|
+
*/
|
|
182
|
+
listEventsSince(kingdomId: string, afterSeq: number, limit?: number): EventRow[];
|
|
183
|
+
/**
|
|
184
|
+
* 王国当前 revision = 最大事件序号。
|
|
185
|
+
*
|
|
186
|
+
* 任何治理动作都会追加事件,所以这个数既是事件游标,也是"数据版本":
|
|
187
|
+
* GUI 比较 revision 就知道要不要重绘,不必 diff 整个 snapshot。
|
|
188
|
+
*/
|
|
189
|
+
revision(kingdomId: string): number;
|
|
190
|
+
/**
|
|
191
|
+
* 追加事件并分配单调 seq。
|
|
192
|
+
*
|
|
193
|
+
* 「读 MAX(seq) + INSERT」放在 IMMEDIATE 事务里,避免并发写出重复序号
|
|
194
|
+
* (SQLite 会串行化写事务)。序号在**全库**范围内单调,跨王国也不会回退。
|
|
195
|
+
*/
|
|
196
|
+
appendEvent(row: Omit<EventRow, 'seq'> & {
|
|
197
|
+
seq?: number;
|
|
198
|
+
}): EventRow;
|
|
199
|
+
getTask(taskId: string): TaskRow | null;
|
|
200
|
+
/**
|
|
201
|
+
* 列出王国内任务。tasks 无 kingdom_id 列(Phase 1 schema),
|
|
202
|
+
* 经 territories 关联收敛到王国边界。
|
|
203
|
+
*/
|
|
204
|
+
listTasks(kingdomId: string, filter?: {
|
|
205
|
+
territoryId?: string;
|
|
206
|
+
status?: string;
|
|
207
|
+
}): TaskRow[];
|
|
208
|
+
insertTask(row: TaskRow): TaskRow;
|
|
209
|
+
/**
|
|
210
|
+
* **全库唯一的 tasks.status 写入路径**(治理底线,Owner 裁决 1)。
|
|
211
|
+
*
|
|
212
|
+
* 先过 ./task.ts 的 transition() 校验,非法转移直接抛 TaskTransitionError,
|
|
213
|
+
* 一个字节都不会落库。任何工具都无法绕过它把 Task 直接置 DONE
|
|
214
|
+
* —— DONE 只能从 REVIEW 经 Supervisor 的 ACCEPT 决定到达。
|
|
215
|
+
*
|
|
216
|
+
* @param task 当前任务行(status 取自库,未知值 fail-loud)。
|
|
217
|
+
* @param to 目标状态。
|
|
218
|
+
* @param patch 与状态同事务落库的附带字段(如指派、Claim 摘要)。
|
|
219
|
+
* @returns 落库后的新任务行。
|
|
220
|
+
*/
|
|
221
|
+
transitionTask(task: TaskRow, to: TaskStatus, patch?: {
|
|
222
|
+
assigned_binding_id?: string | null;
|
|
223
|
+
result_summary?: string | null;
|
|
224
|
+
}): TaskRow;
|
|
225
|
+
listWorkerResults(taskId: string): WorkerResultRow[];
|
|
226
|
+
latestWorkerResult(taskId: string): WorkerResultRow | null;
|
|
227
|
+
/** 已落库的最大 attempt_no;无结果时为 0。下一次尝试 = 本值 + 1。 */
|
|
228
|
+
maxAttemptNo(taskId: string): number;
|
|
229
|
+
/**
|
|
230
|
+
* executions 侧的最大 attempt_no。
|
|
231
|
+
*
|
|
232
|
+
* **不能只看 worker_results 来编下一个 attempt 号**:executor 客观失败、
|
|
233
|
+
* 被 abort、以及重载后被回收的僵尸执行都只有 Execution 行、没有 Claim 行。
|
|
234
|
+
* 只按 Claim 计数会重复发号,撞上 `UNIQUE(task_id, attempt_no)`。
|
|
235
|
+
* 下一次尝试号必须取两者的最大值再 +1(见 nextAttemptNo)。
|
|
236
|
+
*/
|
|
237
|
+
maxExecutionAttemptNo(taskId: string): number;
|
|
238
|
+
/** 下一次执行应使用的 attempt 号:Claim 与 Execution 两侧取大再 +1。 */
|
|
239
|
+
nextAttemptNo(taskId: string): number;
|
|
240
|
+
insertWorkerResult(row: WorkerResultRow): WorkerResultRow;
|
|
241
|
+
getExecution(executionId: string): ExecutionRow | null;
|
|
242
|
+
listExecutions(taskId: string): ExecutionRow[];
|
|
243
|
+
latestExecution(taskId: string): ExecutionRow | null;
|
|
244
|
+
/** 王国内所有未终结的 Execution(人物应当在场的那些)。 */
|
|
245
|
+
listLiveExecutions(kingdomId: string): ExecutionRow[];
|
|
246
|
+
insertExecution(row: ExecutionRow): ExecutionRow;
|
|
247
|
+
/**
|
|
248
|
+
* **全库唯一的 executions.state 写入路径**(与 transitionTask 同构)。
|
|
249
|
+
*
|
|
250
|
+
* 先过 ./execution.ts 的 `transitionExecution()` 校验,非法转移抛错、不落库。
|
|
251
|
+
* 终态自动补 `ended_at`,避免"已结束但没有结束时间"的半截记录。
|
|
252
|
+
*/
|
|
253
|
+
transitionExecution(execution: ExecutionRow, to: ExecutionState, patch?: {
|
|
254
|
+
detail?: string | null;
|
|
255
|
+
sessionId?: string | null;
|
|
256
|
+
pauseRequestedAt?: string | null;
|
|
257
|
+
}): ExecutionRow;
|
|
258
|
+
/** 登记暂停请求(不改状态;生效点见 ExecutionRow.pause_requested_at 注释)。 */
|
|
259
|
+
setExecutionPauseRequest(executionId: string, at: string | null): void;
|
|
260
|
+
statusSummary(): string;
|
|
261
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-kingdom — Execution 生命周期(Phase 3 / GUI 适配)。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么 Execution 必须独立于 Task
|
|
5
|
+
*
|
|
6
|
+
* `Task.status === 'RUNNING'` **不能**可靠表示"骑士正在工作":
|
|
7
|
+
* REWORK 之后任务立刻回到 RUNNING,但新的 Worker 执行还没启动;
|
|
8
|
+
* 此时 GUI 若按 Task.status 播放工作动画,人物就在假装干活。
|
|
9
|
+
*
|
|
10
|
+
* 所以治理事实(Task)与运行事实(Execution)分开建模:
|
|
11
|
+
*
|
|
12
|
+
* ```text
|
|
13
|
+
* Task.status = 组织对这件事的裁定进度(CREATED..DONE/FAILED)
|
|
14
|
+
* Execution.state = 某一次具体执行此刻的运行状况(STARTING..ABORTED)
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* 一个 Task 的每个 attempt 至多一个 Execution(`UNIQUE(task_id, attempt_no)`)。
|
|
18
|
+
*
|
|
19
|
+
* 本模块与 `./task.ts` 一样**零 schema 依赖**:只描述合法转移。
|
|
20
|
+
*/
|
|
21
|
+
/** Execution 的全部状态。 */
|
|
22
|
+
export declare const EXECUTION_STATES: readonly ["STARTING", "RUNNING", "PAUSED", "COMPLETED", "FAILED", "ABORTED"];
|
|
23
|
+
export type ExecutionState = (typeof EXECUTION_STATES)[number];
|
|
24
|
+
/**
|
|
25
|
+
* 合法转移。
|
|
26
|
+
*
|
|
27
|
+
* - `STARTING → RUNNING`:宿主确认执行已真正开始。
|
|
28
|
+
* - `RUNNING ↔ PAUSED`:暂停/恢复(见 `./execution.ts` 关于 one-shot 的诚实边界说明)。
|
|
29
|
+
* - `* → COMPLETED`:执行交回了合法结构化结果(**注意:这只说明跑完了,不代表任务完成**)。
|
|
30
|
+
* - `* → FAILED`:宿主观察到执行没跑出合法结果。
|
|
31
|
+
* - `* → ABORTED`:被显式终止(会话停止/用户取消),与 FAILED 区分开。
|
|
32
|
+
*/
|
|
33
|
+
export declare const EXECUTION_TRANSITIONS: Record<ExecutionState, readonly ExecutionState[]>;
|
|
34
|
+
/** 终态:不再有后续转移,人物 Sprite 应退场。 */
|
|
35
|
+
export declare const TERMINAL_EXECUTION_STATES: readonly ExecutionState[];
|
|
36
|
+
/** 活跃态:人物应当在场(工作或休息)。 */
|
|
37
|
+
export declare const LIVE_EXECUTION_STATES: readonly ExecutionState[];
|
|
38
|
+
export declare class ExecutionTransitionError extends Error {
|
|
39
|
+
constructor(from: ExecutionState, to: string);
|
|
40
|
+
}
|
|
41
|
+
export declare class UnknownExecutionStateError extends Error {
|
|
42
|
+
constructor(value: string);
|
|
43
|
+
}
|
|
44
|
+
export declare function isExecutionState(value: string): value is ExecutionState;
|
|
45
|
+
/** 库里读到的 TEXT 收敛为 ExecutionState;不认识就抛(fail-loud,不猜)。 */
|
|
46
|
+
export declare function asExecutionState(value: string): ExecutionState;
|
|
47
|
+
export declare function isTerminalExecutionState(state: ExecutionState): boolean;
|
|
48
|
+
export declare function isLiveExecutionState(state: ExecutionState): boolean;
|
|
49
|
+
export declare function canTransitionExecution(from: ExecutionState, to: ExecutionState): boolean;
|
|
50
|
+
/** 状态机唯一入口:校验并返回目标状态,非法即抛。 */
|
|
51
|
+
export declare function transitionExecution(from: ExecutionState, to: ExecutionState): ExecutionState;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { KingdomStore } from './db.js';
|
|
2
|
+
export interface InitResult {
|
|
3
|
+
action: 'initialized' | 'attached';
|
|
4
|
+
kingdomId: string;
|
|
5
|
+
kingdomName: string;
|
|
6
|
+
ownerId: string;
|
|
7
|
+
ownerName: string;
|
|
8
|
+
territoryCount: number;
|
|
9
|
+
bindingCount: number;
|
|
10
|
+
detail: string;
|
|
11
|
+
}
|
|
12
|
+
/** 当前 OS 用户名(显式名称缺失时的兜底)。 */
|
|
13
|
+
export declare function currentOsUser(): string;
|
|
14
|
+
export interface KingdomManagerOptions {
|
|
15
|
+
/** 显式 kingdom 名称;缺省 "My Kingdom" */
|
|
16
|
+
kingdomName?: string;
|
|
17
|
+
/** 显式 owner 名称;缺省取 OS 用户名 */
|
|
18
|
+
ownerName?: string;
|
|
19
|
+
dbPath?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare class KingdomManager {
|
|
22
|
+
private readonly store;
|
|
23
|
+
private readonly opts;
|
|
24
|
+
constructor(options?: KingdomManagerOptions);
|
|
25
|
+
get storeHandle(): KingdomStore;
|
|
26
|
+
/**
|
|
27
|
+
* init:扫描本机 → 无 kingdom.db 则初始化,有则接入。
|
|
28
|
+
* 幂等:重复调用只接入,绝不覆盖既有数据。
|
|
29
|
+
*/
|
|
30
|
+
init(): InitResult;
|
|
31
|
+
/** 重新扫描接入(不删除任何数据)。 */
|
|
32
|
+
rescan(): InitResult;
|
|
33
|
+
close(): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { KingdomStore } from './db.js';
|
|
2
|
+
import type { WorkerExecutor } from '../worker/executor.js';
|
|
3
|
+
import type { AuthView, CommandResultView } from '../gui/contract.js';
|
|
4
|
+
/** 调用主体。Phase 3 的最低鉴权只认 sessionId(见 {@link AuthView})。 */
|
|
5
|
+
export interface Principal {
|
|
6
|
+
sessionId?: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface CommandContext {
|
|
9
|
+
kingdomId: string;
|
|
10
|
+
principal?: Principal;
|
|
11
|
+
auth: AuthView;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 插件加载时回收孤儿 Execution。
|
|
15
|
+
*
|
|
16
|
+
* **为什么这是安全的**:Worker 以 **one-shot in-process subagent** 执行,
|
|
17
|
+
* 它的生命周期完全绑在插件 fiber 上。插件一旦卸载/重载,那个 subagent
|
|
18
|
+
* 就不可能还活着。因此**开库时看到的任何"活跃" Execution,都必然是
|
|
19
|
+
* 上一个进程留下的残骸** —— 这不是推测,是由执行模型保证的。
|
|
20
|
+
*
|
|
21
|
+
* 不回收会怎样:Execution 永远停在 RUNNING,
|
|
22
|
+
* - GUI 看到骑士永远在工作(谎报运行状态);
|
|
23
|
+
* - 该任务再也无法 start(被"已有未结束的 Execution"挡住)。
|
|
24
|
+
*
|
|
25
|
+
* 回收判定为 `ABORTED` 而不是 `FAILED`:这是宿主观察到的**中断**,
|
|
26
|
+
* 不是"executor 没跑出合法结果",两者语义必须分开。
|
|
27
|
+
* 任务的治理状态**不动** —— 回收只处理运行事实,不替 Supervisor 做裁定。
|
|
28
|
+
*
|
|
29
|
+
* @returns 被回收的 Execution 数量。
|
|
30
|
+
*/
|
|
31
|
+
export declare function reclaimOrphanExecutions(store: KingdomStore, kingdomId: string): number;
|
|
32
|
+
export interface PlanTaskInput {
|
|
33
|
+
territoryId?: string;
|
|
34
|
+
title: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
acceptanceCriteria?: string;
|
|
37
|
+
}
|
|
38
|
+
/** 创建 Task → CREATED。要求 CHANCELLOR binding。 */
|
|
39
|
+
export declare function planTask(store: KingdomStore, ctx: CommandContext, input: PlanTaskInput): CommandResultView;
|
|
40
|
+
export interface AssignTaskInput {
|
|
41
|
+
taskId: string;
|
|
42
|
+
workerBindingId?: string;
|
|
43
|
+
}
|
|
44
|
+
/** CREATED → ASSIGNED。 */
|
|
45
|
+
export declare function assignTask(store: KingdomStore, ctx: CommandContext, input: AssignTaskInput): CommandResultView;
|
|
46
|
+
export interface StartTaskInput {
|
|
47
|
+
taskId: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 触发一轮 Worker 执行(执行期间阻塞等待 subagent)。
|
|
51
|
+
*
|
|
52
|
+
* 入口状态:`ASSIGNED`(首轮)或 `RUNNING`(Supervisor 刚判 REWORK)。
|
|
53
|
+
*
|
|
54
|
+
* Phase 3 增量:本轮执行会创建一条独立的 Execution 行。
|
|
55
|
+
* Task.status 与 Execution.state 从此分离——
|
|
56
|
+
* GUI 判断"骑士是否在工作"只看后者。
|
|
57
|
+
*
|
|
58
|
+
* 结局仍只有两种(裁决 6):
|
|
59
|
+
* - 合法结构化 Claim → 落 worker_results → **RUNNING → REVIEW**(无论 Claim 自称成败);
|
|
60
|
+
* - executor 客观失败 → **RUNNING → FAILED** + `WORKER_EXECUTION_FAILED`,且不落 Claim。
|
|
61
|
+
*/
|
|
62
|
+
export declare function startTask(store: KingdomStore, executor: WorkerExecutor, ctx: CommandContext, input: StartTaskInput): Promise<CommandResultView>;
|
|
63
|
+
export interface ReviewTaskInput {
|
|
64
|
+
taskId: string;
|
|
65
|
+
decision: string;
|
|
66
|
+
reason?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Supervisor 审查 Worker Claim,把 Claim 转成组织事实。
|
|
70
|
+
*
|
|
71
|
+
* - ACCEPT → DONE(+ TASK_ACCEPTED;不新增 task_reviews 表,裁决 4)
|
|
72
|
+
* - REWORK → RUNNING(同一 Worker Binding;下一次 start 会以 attempt+1 起新 Execution)
|
|
73
|
+
* - FAIL → FAILED(终态;只有这里能把 Worker 的失败 Claim 变成组织事实)
|
|
74
|
+
*/
|
|
75
|
+
export declare function reviewTask(store: KingdomStore, ctx: CommandContext, input: ReviewTaskInput): CommandResultView;
|
|
76
|
+
export interface ExecutionCommandInput {
|
|
77
|
+
executionId: string;
|
|
78
|
+
reason?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 请求暂停一次执行。
|
|
82
|
+
*
|
|
83
|
+
* **诚实的语义边界**:Worker 是 one-shot subagent,宿主无法在一次 turn 中途
|
|
84
|
+
* 真正挂起它。因此:
|
|
85
|
+
* - 执行**尚未真正开始**(STARTING)→ 直接转 PAUSED,人物可以睡觉;
|
|
86
|
+
* - 执行**正在进行**(RUNNING)→ 只登记 `pause_requested_at`,状态保持 RUNNING,
|
|
87
|
+
* `ExecutionView.pausePending = true`。GUI 应表现为"准备休息",
|
|
88
|
+
* **不能**直接播睡觉动画——那会谎报运行状态。
|
|
89
|
+
*/
|
|
90
|
+
export declare function pauseExecution(store: KingdomStore, ctx: CommandContext, input: ExecutionCommandInput): CommandResultView;
|
|
91
|
+
/** 恢复执行:撤销暂停请求,或把 PAUSED 转回 RUNNING。 */
|
|
92
|
+
export declare function resumeExecution(store: KingdomStore, ctx: CommandContext, input: ExecutionCommandInput): CommandResultView;
|
|
93
|
+
/**
|
|
94
|
+
* 终止一次执行(ABORTED)。
|
|
95
|
+
*
|
|
96
|
+
* 与 FAILED 区分开:ABORTED 是"被显式停止",FAILED 是"宿主观察到跑不出结果"。
|
|
97
|
+
* 终止只影响运行事实;Task 的治理状态由 Supervisor 另行裁定
|
|
98
|
+
* (任务停在 RUNNING,等待重新 start 或人工处理)。
|
|
99
|
+
* GUI 收到 SESSION_STOPPED 只移除人物 Sprite,**组织节点、姓名牌与详情保留**。
|
|
100
|
+
*/
|
|
101
|
+
export declare function abortExecution(store: KingdomStore, ctx: CommandContext, input: ExecutionCommandInput): CommandResultView;
|
|
102
|
+
export interface ListTasksInput {
|
|
103
|
+
territoryId?: string;
|
|
104
|
+
status?: string;
|
|
105
|
+
}
|
|
106
|
+
/** 列出任务真实状态,含 attempt_no 与最新 Claim 摘要(面向模型的文本形式)。 */
|
|
107
|
+
export declare function listTasks(store: KingdomStore, kingdomId: string, input?: ListTasksInput): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-kingdom — Task 状态机(Phase 2,Owner 裁决 1)。
|
|
3
|
+
*
|
|
4
|
+
* 冻结的状态机:
|
|
5
|
+
* CREATED → ASSIGNED → RUNNING → REVIEW → DONE / FAILED
|
|
6
|
+
* REVIEW → RUNNING 表示 REWORK(同一 Worker Binding,attempt_no + 1)
|
|
7
|
+
* 不引入 PLANNED。
|
|
8
|
+
*
|
|
9
|
+
* REVIEW 语义 = “Worker 已提交一个可供 Supervisor 审查的 Result Claim,
|
|
10
|
+
* 但该 Claim 尚未成为任务完成事实”。这是 Phase 2 的核心治理不变量:
|
|
11
|
+
* **Claim ≠ Fact**。
|
|
12
|
+
*
|
|
13
|
+
* 本模块**零 schema 依赖**:只描述合法转移,不碰 SQLite。
|
|
14
|
+
* 裁决 3 已现场确认 tasks.status 无 CHECK 约束,因此状态机只活在 Core 代码层,
|
|
15
|
+
* 零 migration。
|
|
16
|
+
*/
|
|
17
|
+
/** 冻结的 Task 状态全集(Phase 2 不新增状态)。 */
|
|
18
|
+
export declare const TASK_STATUSES: readonly ["CREATED", "ASSIGNED", "RUNNING", "REVIEW", "DONE", "FAILED"];
|
|
19
|
+
export type TaskStatus = (typeof TASK_STATUSES)[number];
|
|
20
|
+
/**
|
|
21
|
+
* 唯一合法转移表(Owner 裁决 1 冻结)。
|
|
22
|
+
*
|
|
23
|
+
* - RUNNING → REVIEW:结构化 Worker Claim 到达(**不论 Claim 自称成功还是失败**)。
|
|
24
|
+
* - RUNNING → FAILED:executor 客观失败(宿主观察到的运行事实,非 Worker 自述,裁决 6)。
|
|
25
|
+
* - REVIEW → DONE / RUNNING / FAILED:Supervisor 的 ACCEPT / REWORK / FAIL 决定。
|
|
26
|
+
* - DONE / FAILED 为 Phase 2 终态(裁决 6)。
|
|
27
|
+
*/
|
|
28
|
+
export declare const TASK_TRANSITIONS: Record<TaskStatus, readonly TaskStatus[]>;
|
|
29
|
+
/** Phase 2 终态:不可再转移。 */
|
|
30
|
+
export declare const TERMINAL_TASK_STATUSES: readonly TaskStatus[];
|
|
31
|
+
/** Supervisor 审查决定(冻结三选一)。 */
|
|
32
|
+
export declare const REVIEW_DECISIONS: readonly ["ACCEPT", "REWORK", "FAIL"];
|
|
33
|
+
export type ReviewDecision = (typeof REVIEW_DECISIONS)[number];
|
|
34
|
+
/** ACCEPT/REWORK/FAIL → 目标状态的唯一映射。 */
|
|
35
|
+
export declare const REVIEW_DECISION_TARGET: Record<ReviewDecision, TaskStatus>;
|
|
36
|
+
/** 非法状态转移。抛出即表示调用方试图绕过治理闭环。 */
|
|
37
|
+
export declare class TaskTransitionError extends Error {
|
|
38
|
+
readonly from: TaskStatus;
|
|
39
|
+
readonly to: string;
|
|
40
|
+
constructor(from: TaskStatus, to: string);
|
|
41
|
+
}
|
|
42
|
+
/** 未知状态字符串(库里读到不认识的值时 fail-loud,不静默降级)。 */
|
|
43
|
+
export declare class UnknownTaskStatusError extends Error {
|
|
44
|
+
constructor(value: string);
|
|
45
|
+
}
|
|
46
|
+
/** 是否为合法状态字符串。 */
|
|
47
|
+
export declare function isTaskStatus(value: string): value is TaskStatus;
|
|
48
|
+
/** 把库里的 TEXT 状态收敛为 TaskStatus;不认识就抛(不猜、不兜底)。 */
|
|
49
|
+
export declare function asTaskStatus(value: string): TaskStatus;
|
|
50
|
+
/** 是否终态。 */
|
|
51
|
+
export declare function isTerminalTaskStatus(status: TaskStatus): boolean;
|
|
52
|
+
/** 纯查询:from → to 是否合法。不抛错。 */
|
|
53
|
+
export declare function canTransition(from: TaskStatus, to: TaskStatus): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 状态机唯一入口:校验并返回目标状态,非法即抛。
|
|
56
|
+
*
|
|
57
|
+
* 治理纪律:任何写 tasks.status 的代码路径都必须先经过这里
|
|
58
|
+
* (见 KingdomStore.transitionTask —— 全库唯一的 status UPDATE)。
|
|
59
|
+
*/
|
|
60
|
+
export declare function transition(from: TaskStatus, to: TaskStatus): TaskStatus;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { KingdomStore } from './db.js';
|
|
2
|
+
export interface CreateTerritoryInput {
|
|
3
|
+
kingdomId: string;
|
|
4
|
+
name: string;
|
|
5
|
+
workspacePath?: string;
|
|
6
|
+
summary?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function createTerritory(store: KingdomStore, input: CreateTerritoryInput): string;
|
|
9
|
+
export declare function listTerritories(store: KingdomStore, kingdomId: string): string;
|