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
package/lib/core/task.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
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 const TASK_STATUSES = ['CREATED', 'ASSIGNED', 'RUNNING', 'REVIEW', 'DONE', 'FAILED'];
|
|
19
|
+
/**
|
|
20
|
+
* 唯一合法转移表(Owner 裁决 1 冻结)。
|
|
21
|
+
*
|
|
22
|
+
* - RUNNING → REVIEW:结构化 Worker Claim 到达(**不论 Claim 自称成功还是失败**)。
|
|
23
|
+
* - RUNNING → FAILED:executor 客观失败(宿主观察到的运行事实,非 Worker 自述,裁决 6)。
|
|
24
|
+
* - REVIEW → DONE / RUNNING / FAILED:Supervisor 的 ACCEPT / REWORK / FAIL 决定。
|
|
25
|
+
* - DONE / FAILED 为 Phase 2 终态(裁决 6)。
|
|
26
|
+
*/
|
|
27
|
+
export const TASK_TRANSITIONS = {
|
|
28
|
+
CREATED: ['ASSIGNED'],
|
|
29
|
+
ASSIGNED: ['RUNNING'],
|
|
30
|
+
RUNNING: ['REVIEW', 'FAILED'],
|
|
31
|
+
REVIEW: ['DONE', 'RUNNING', 'FAILED'],
|
|
32
|
+
DONE: [],
|
|
33
|
+
FAILED: [],
|
|
34
|
+
};
|
|
35
|
+
/** Phase 2 终态:不可再转移。 */
|
|
36
|
+
export const TERMINAL_TASK_STATUSES = ['DONE', 'FAILED'];
|
|
37
|
+
/** Supervisor 审查决定(冻结三选一)。 */
|
|
38
|
+
export const REVIEW_DECISIONS = ['ACCEPT', 'REWORK', 'FAIL'];
|
|
39
|
+
/** ACCEPT/REWORK/FAIL → 目标状态的唯一映射。 */
|
|
40
|
+
export const REVIEW_DECISION_TARGET = {
|
|
41
|
+
ACCEPT: 'DONE',
|
|
42
|
+
REWORK: 'RUNNING',
|
|
43
|
+
FAIL: 'FAILED',
|
|
44
|
+
};
|
|
45
|
+
/** 非法状态转移。抛出即表示调用方试图绕过治理闭环。 */
|
|
46
|
+
export class TaskTransitionError extends Error {
|
|
47
|
+
from;
|
|
48
|
+
to;
|
|
49
|
+
constructor(from, to) {
|
|
50
|
+
super(`非法 Task 状态转移:${from} → ${to}。`
|
|
51
|
+
+ `${from} 的合法后继为 ${TASK_TRANSITIONS[from].length > 0 ? TASK_TRANSITIONS[from].join(' / ') : '(终态,无后继)'}。`);
|
|
52
|
+
this.name = 'TaskTransitionError';
|
|
53
|
+
this.from = from;
|
|
54
|
+
this.to = to;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** 未知状态字符串(库里读到不认识的值时 fail-loud,不静默降级)。 */
|
|
58
|
+
export class UnknownTaskStatusError extends Error {
|
|
59
|
+
constructor(value) {
|
|
60
|
+
super(`未知 Task 状态 "${value}",合法值:${TASK_STATUSES.join(' / ')}。`);
|
|
61
|
+
this.name = 'UnknownTaskStatusError';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** 是否为合法状态字符串。 */
|
|
65
|
+
export function isTaskStatus(value) {
|
|
66
|
+
return TASK_STATUSES.includes(value);
|
|
67
|
+
}
|
|
68
|
+
/** 把库里的 TEXT 状态收敛为 TaskStatus;不认识就抛(不猜、不兜底)。 */
|
|
69
|
+
export function asTaskStatus(value) {
|
|
70
|
+
if (!isTaskStatus(value))
|
|
71
|
+
throw new UnknownTaskStatusError(value);
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
/** 是否终态。 */
|
|
75
|
+
export function isTerminalTaskStatus(status) {
|
|
76
|
+
return TERMINAL_TASK_STATUSES.includes(status);
|
|
77
|
+
}
|
|
78
|
+
/** 纯查询:from → to 是否合法。不抛错。 */
|
|
79
|
+
export function canTransition(from, to) {
|
|
80
|
+
return TASK_TRANSITIONS[from].includes(to);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 状态机唯一入口:校验并返回目标状态,非法即抛。
|
|
84
|
+
*
|
|
85
|
+
* 治理纪律:任何写 tasks.status 的代码路径都必须先经过这里
|
|
86
|
+
* (见 KingdomStore.transitionTask —— 全库唯一的 status UPDATE)。
|
|
87
|
+
*/
|
|
88
|
+
export function transition(from, to) {
|
|
89
|
+
if (!canTransition(from, to))
|
|
90
|
+
throw new TaskTransitionError(from, to);
|
|
91
|
+
return to;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=task.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task.js","sourceRoot":"","sources":["../../src/core/task.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,oCAAoC;AACpC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAA;AAIpG;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA8C;IACzE,OAAO,EAAE,CAAC,UAAU,CAAC;IACrB,QAAQ,EAAE,CAAC,SAAS,CAAC;IACrB,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC7B,MAAM,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC;IACrC,IAAI,EAAE,EAAE;IACR,MAAM,EAAE,EAAE;CACX,CAAA;AAED,wBAAwB;AACxB,MAAM,CAAC,MAAM,sBAAsB,GAA0B,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;AAE/E,8BAA8B;AAC9B,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAA;AAIrE,sCAAsC;AACtC,MAAM,CAAC,MAAM,sBAAsB,GAAuC;IACxE,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,QAAQ;CACf,CAAA;AAED,+BAA+B;AAC/B,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACnC,IAAI,CAAY;IAChB,EAAE,CAAQ;IAEnB,YAAY,IAAgB,EAAE,EAAU;QACtC,KAAK,CACH,gBAAgB,IAAI,MAAM,EAAE,GAAG;cAC7B,GAAG,IAAI,WAAW,gBAAgB,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,CAC3G,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAA;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAA;IACd,CAAC;CACF;AAED,2CAA2C;AAC3C,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/C,YAAY,KAAa;QACvB,KAAK,CAAC,eAAe,KAAK,SAAS,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAChE,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAA;IACtC,CAAC;CACF;AAED,kBAAkB;AAClB,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAQ,aAAmC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC7D,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,sBAAsB,CAAC,KAAK,CAAC,CAAA;IACjE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,YAAY;AACZ,MAAM,UAAU,oBAAoB,CAAC,MAAkB;IACrD,OAAO,sBAAsB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AAChD,CAAC;AAED,8BAA8B;AAC9B,MAAM,UAAU,aAAa,CAAC,IAAgB,EAAE,EAAc;IAC5D,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAgB,EAAE,EAAc;IACzD,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC;QAAE,MAAM,IAAI,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IACrE,OAAO,EAAE,CAAA;AACX,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-kingdom — Territory 基础 CRUD(Phase 1)。
|
|
3
|
+
* Territory 是长期资源/上下文边界,非权限链节点(与 Role 无关)。
|
|
4
|
+
* 状态仅 ACTIVE | ARCHIVED(简单两态,不实现复杂状态机)。
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
export function createTerritory(store, input) {
|
|
8
|
+
const name = input.name.trim();
|
|
9
|
+
if (!name)
|
|
10
|
+
return '错误:领地名称不能为空。';
|
|
11
|
+
const existing = store.getTerritoryByName(input.kingdomId, name);
|
|
12
|
+
if (existing)
|
|
13
|
+
return `领地「${name}」已存在(id=${existing.territory_id}),未重复创建。`;
|
|
14
|
+
const now = new Date().toISOString();
|
|
15
|
+
const territory = {
|
|
16
|
+
territory_id: randomUUID(),
|
|
17
|
+
kingdom_id: input.kingdomId,
|
|
18
|
+
name,
|
|
19
|
+
workspace_path: input.workspacePath?.trim() || null,
|
|
20
|
+
summary: input.summary?.trim() || null,
|
|
21
|
+
supervisor_binding_id: null,
|
|
22
|
+
status: 'ACTIVE',
|
|
23
|
+
created_at: now,
|
|
24
|
+
};
|
|
25
|
+
store.insertTerritory(territory);
|
|
26
|
+
store.appendEvent({
|
|
27
|
+
event_id: randomUUID(),
|
|
28
|
+
kingdom_id: input.kingdomId,
|
|
29
|
+
event_type: 'TERRITORY_CREATED',
|
|
30
|
+
actor_role: null,
|
|
31
|
+
actor_id: null,
|
|
32
|
+
target_type: 'territory',
|
|
33
|
+
target_id: territory.territory_id,
|
|
34
|
+
payload_json: JSON.stringify({ name, workspace_path: territory.workspace_path }),
|
|
35
|
+
created_at: now,
|
|
36
|
+
});
|
|
37
|
+
return `已创建领地「${name}」(id=${territory.territory_id}${territory.workspace_path ? `,工作区 ${territory.workspace_path}` : ''})。`;
|
|
38
|
+
}
|
|
39
|
+
export function listTerritories(store, kingdomId) {
|
|
40
|
+
const rows = store.listTerritories(kingdomId);
|
|
41
|
+
if (rows.length === 0)
|
|
42
|
+
return '当前王国还没有领地。可创建第一个,例如“给当前项目建立一个 RAG 研发领”。';
|
|
43
|
+
return rows
|
|
44
|
+
.map((t) => `- ${t.name}(${t.status}${t.workspace_path ? `,${t.workspace_path}` : ''}${t.summary ? `,${t.summary}` : ''})`)
|
|
45
|
+
.join('\n');
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=territory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"territory.js","sourceRoot":"","sources":["../../src/core/territory.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAUxC,MAAM,UAAU,eAAe,CAAC,KAAmB,EAAE,KAA2B;IAC9E,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;IAC9B,IAAI,CAAC,IAAI;QAAE,OAAO,cAAc,CAAA;IAChC,MAAM,QAAQ,GAAG,KAAK,CAAC,kBAAkB,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAChE,IAAI,QAAQ;QAAE,OAAO,MAAM,IAAI,WAAW,QAAQ,CAAC,YAAY,UAAU,CAAA;IAEzE,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACpC,MAAM,SAAS,GAAG;QAChB,YAAY,EAAE,UAAU,EAAE;QAC1B,UAAU,EAAE,KAAK,CAAC,SAAS;QAC3B,IAAI;QACJ,cAAc,EAAE,KAAK,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,IAAI;QACnD,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI;QACtC,qBAAqB,EAAE,IAAI;QAC3B,MAAM,EAAE,QAAQ;QAChB,UAAU,EAAE,GAAG;KAChB,CAAA;IACD,KAAK,CAAC,eAAe,CAAC,SAAS,CAAC,CAAA;IAChC,KAAK,CAAC,WAAW,CAAC;QAChB,QAAQ,EAAE,UAAU,EAAE;QACtB,UAAU,EAAE,KAAK,CAAC,SAAS;QAC3B,UAAU,EAAE,mBAAmB;QAC/B,UAAU,EAAE,IAAI;QAChB,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,WAAW;QACxB,SAAS,EAAE,SAAS,CAAC,YAAY;QACjC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,CAAC,cAAc,EAAE,CAAC;QAChF,UAAU,EAAE,GAAG;KAChB,CAAC,CAAA;IACF,OAAO,SAAS,IAAI,QAAQ,SAAS,CAAC,YAAY,GAAG,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,SAAS,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAA;AAC7H,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAmB,EAAE,SAAiB;IACpE,MAAM,IAAI,GAAmB,KAAK,CAAC,eAAe,CAAC,SAAS,CAAC,CAAA;IAC7D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,yCAAyC,CAAA;IACvE,OAAO,IAAI;SACR,GAAG,CAAC,CAAC,CAAe,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;SACxI,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-kingdom — 插件 ↔ GUI 的线上契约(Phase 3)。
|
|
3
|
+
*
|
|
4
|
+
* ## 唯一架构原则
|
|
5
|
+
*
|
|
6
|
+
* ```text
|
|
7
|
+
* 插件输出治理事实和活动语义
|
|
8
|
+
* GUI 决定使用哪个人物、场景和动画
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* 因此本文件里**不会**出现 `chancellor.png`、`sleep.gif`、
|
|
12
|
+
* `sprite.knight.default.forge.work.idle` 之类的美术知识。
|
|
13
|
+
* 插件只输出 `{ role, state, activity }`,这正是 GUI 端 Visual Resolver 的输入
|
|
14
|
+
* (另外两维 `skin` / `scene` 属于 GUI 部署配置,插件不参与)。
|
|
15
|
+
*
|
|
16
|
+
* 角色、模型、工具、运行时与皮肤因此保持解耦:
|
|
17
|
+
* 换一套贴图、换一个场景、把骑士换成别的形象,插件一行都不用改。
|
|
18
|
+
*
|
|
19
|
+
* ## 两类事实必须分开读
|
|
20
|
+
*
|
|
21
|
+
* - `TaskView.status` —— 治理事实:组织对这件事的裁定进度。
|
|
22
|
+
* - `ExecutionView.state` —— 运行事实:某一次执行此刻的状况。
|
|
23
|
+
*
|
|
24
|
+
* `Task.RUNNING` **不代表**人物正在工作(REWORK 后任务立刻回 RUNNING,
|
|
25
|
+
* 但新 Execution 还没创建)。GUI 判断"是否播放工作动画"必须看 Execution。
|
|
26
|
+
*/
|
|
27
|
+
/** 组织角色。GUI 自行决定每个角色用哪个人物形象。 */
|
|
28
|
+
export const ACTOR_ROLES = ['OWNER', 'CHANCELLOR', 'SUPERVISOR', 'WORKER'];
|
|
29
|
+
/**
|
|
30
|
+
* 人物状态(Resolver 的 `state` 维)。
|
|
31
|
+
*
|
|
32
|
+
* `absent` 表示该角色没有绑定:GUI 应当**保留组织节点与姓名牌**,只是不渲染人物 Sprite。
|
|
33
|
+
*
|
|
34
|
+
* 这份清单是**契约的一部分**:GUI 侧 Visual Resolver 必须逐个处理它们,
|
|
35
|
+
* 漏掉任何一个都会让人物静默退化成 idle。改动本数组前请同步 GUI。
|
|
36
|
+
*/
|
|
37
|
+
export const ACTOR_STATES = [
|
|
38
|
+
'absent',
|
|
39
|
+
'idle',
|
|
40
|
+
'planning',
|
|
41
|
+
'assigning',
|
|
42
|
+
'working',
|
|
43
|
+
'sleeping',
|
|
44
|
+
'reviewing',
|
|
45
|
+
'waiting',
|
|
46
|
+
'confused',
|
|
47
|
+
'celebrating',
|
|
48
|
+
];
|
|
49
|
+
/** GUI 契约版本。破坏性变更时递增,GUI 应拒绝不认识的版本。 */
|
|
50
|
+
export const GUI_SCHEMA_VERSION = 1;
|
|
51
|
+
/** transient 表演动作的默认存活窗口(毫秒)。GUI 轮询 1–2s,足够捕捉。 */
|
|
52
|
+
export const TRANSIENT_WINDOW_MS = 3500;
|
|
53
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/gui/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,gCAAgC;AAChC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,CAAU,CAAA;AAInF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,QAAQ;IACR,MAAM;IACN,UAAU;IACV,WAAW;IACX,SAAS;IACT,UAAU;IACV,WAAW;IACX,SAAS;IACT,UAAU;IACV,aAAa;CACL,CAAA;AA0OV,uCAAuC;AACvC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAA;AAEnC,kDAAkD;AAClD,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAA"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-kingdom — 本地 GUI 连接通道(Phase 3,P0-4)。
|
|
3
|
+
*
|
|
4
|
+
* 浏览器 GUI **不能**直接读本地 `kingdom.db`,所以由插件在本机开一个
|
|
5
|
+
* 只监听 `127.0.0.1` 的小型 HTTP 服务,把结构化 Snapshot / Task Detail /
|
|
6
|
+
* 事件增量暴露出去,并转发写命令。
|
|
7
|
+
*
|
|
8
|
+
* Beta 采用**轮询**(建议 1–2s 拉一次 `/api/snapshot`,或用 `revision` 先探再拉),
|
|
9
|
+
* 不做完整实时推送。`revision` 与事件 `seq` 让轮询也能拿到确定的顺序语义。
|
|
10
|
+
*
|
|
11
|
+
* ## 安全姿态(本地开发工具,非生产服务)
|
|
12
|
+
*
|
|
13
|
+
* - **默认关闭**:`guiPort` 缺省为 0。要用 GUI 必须显式配置端口——
|
|
14
|
+
* 插件不会在用户不知情时打开监听端口。
|
|
15
|
+
* - 只绑定 `127.0.0.1`,不监听外部网卡。
|
|
16
|
+
* - 写命令要求自定义头 `X-Kingdom-Client`:自定义头会强制浏览器发预检,
|
|
17
|
+
* 从而挡掉简单表单式 CSRF(恶意页面无法向本机端口静默提交命令)。
|
|
18
|
+
* - 可选 `guiToken`:设置后所有请求需带 `Authorization: Bearer <token>`。
|
|
19
|
+
* - 鉴权诚实度由 snapshot 的 `auth.trustLevel` 如实声明,GUI 必须显示。
|
|
20
|
+
*/
|
|
21
|
+
import { createServer } from 'node:http';
|
|
22
|
+
import { GUI_SCHEMA_VERSION } from './contract.js';
|
|
23
|
+
/** 写命令必须带的自定义头(存在即可,值不校验)。 */
|
|
24
|
+
const CLIENT_HEADER = 'x-kingdom-client';
|
|
25
|
+
const COMMANDS = new Set([
|
|
26
|
+
'plan', 'assign', 'start', 'review',
|
|
27
|
+
'execution.pause', 'execution.resume', 'execution.abort',
|
|
28
|
+
]);
|
|
29
|
+
function corsOrigin(req, allow) {
|
|
30
|
+
const origin = req.headers.origin;
|
|
31
|
+
if (allow.includes('*'))
|
|
32
|
+
return origin ?? '*';
|
|
33
|
+
if (typeof origin === 'string' && allow.includes(origin))
|
|
34
|
+
return origin;
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function applyCors(req, res, allow) {
|
|
38
|
+
const origin = corsOrigin(req, allow);
|
|
39
|
+
if (origin)
|
|
40
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
41
|
+
res.setHeader('Vary', 'Origin');
|
|
42
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
43
|
+
res.setHeader('Access-Control-Allow-Headers', `Content-Type, Authorization, ${CLIENT_HEADER}`);
|
|
44
|
+
res.setHeader('Access-Control-Max-Age', '600');
|
|
45
|
+
}
|
|
46
|
+
function sendJson(res, status, body) {
|
|
47
|
+
const text = JSON.stringify(body);
|
|
48
|
+
res.writeHead(status, {
|
|
49
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
50
|
+
'Content-Length': Buffer.byteLength(text),
|
|
51
|
+
'Cache-Control': 'no-store',
|
|
52
|
+
});
|
|
53
|
+
res.end(text);
|
|
54
|
+
}
|
|
55
|
+
function sendError(res, status, errorCode, message) {
|
|
56
|
+
sendJson(res, status, { ok: false, errorCode, message });
|
|
57
|
+
}
|
|
58
|
+
async function readJsonBody(req, limitBytes = 1_000_000) {
|
|
59
|
+
const chunks = [];
|
|
60
|
+
let size = 0;
|
|
61
|
+
for await (const chunk of req) {
|
|
62
|
+
const buf = chunk;
|
|
63
|
+
size += buf.length;
|
|
64
|
+
if (size > limitBytes)
|
|
65
|
+
throw new Error('request body too large');
|
|
66
|
+
chunks.push(buf);
|
|
67
|
+
}
|
|
68
|
+
if (size === 0)
|
|
69
|
+
return {};
|
|
70
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
71
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
72
|
+
throw new Error('request body must be a JSON object');
|
|
73
|
+
}
|
|
74
|
+
return parsed;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 启动本地 GUI 通道。
|
|
78
|
+
* @returns 关闭函数(挂到 ctx.effect,插件卸载/热重载时自动收回端口)。
|
|
79
|
+
*/
|
|
80
|
+
export function startGuiServer(handlers, options) {
|
|
81
|
+
const host = options.host ?? '127.0.0.1';
|
|
82
|
+
const allow = options.allowOrigins ?? ['*'];
|
|
83
|
+
const token = options.token?.trim() || null;
|
|
84
|
+
const server = createServer((req, res) => {
|
|
85
|
+
void handleRequest(req, res).catch((error) => {
|
|
86
|
+
if (!res.headersSent) {
|
|
87
|
+
sendError(res, 500, 'INTERNAL_ERROR', error instanceof Error ? error.message : String(error));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
res.end();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
async function handleRequest(req, res) {
|
|
95
|
+
applyCors(req, res, allow);
|
|
96
|
+
if (req.method === 'OPTIONS') {
|
|
97
|
+
res.writeHead(204);
|
|
98
|
+
res.end();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (token !== null && req.headers.authorization !== `Bearer ${token}`) {
|
|
102
|
+
sendError(res, 401, 'UNAUTHORIZED', 'missing or invalid bearer token');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const url = new URL(req.url ?? '/', `http://${host}`);
|
|
106
|
+
const path = url.pathname.replace(/\/+$/, '') || '/';
|
|
107
|
+
if (req.method === 'GET' && (path === '/' || path === '/api/health')) {
|
|
108
|
+
sendJson(res, 200, { ok: true, service: 'dsh-kingdom', schemaVersion: GUI_SCHEMA_VERSION });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (req.method === 'GET' && path === '/api/snapshot') {
|
|
112
|
+
sendJson(res, 200, handlers.snapshot());
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (req.method === 'GET' && path === '/api/events') {
|
|
116
|
+
const since = Number.parseInt(url.searchParams.get('since') ?? '0', 10);
|
|
117
|
+
const limit = Number.parseInt(url.searchParams.get('limit') ?? '200', 10);
|
|
118
|
+
sendJson(res, 200, handlers.eventsSince(Number.isFinite(since) && since > 0 ? since : 0, Number.isFinite(limit) && limit > 0 ? Math.min(limit, 1000) : 200));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const taskMatch = /^\/api\/tasks\/([^/]+)$/u.exec(path);
|
|
122
|
+
if (req.method === 'GET' && taskMatch) {
|
|
123
|
+
const detail = handlers.taskDetail(decodeURIComponent(taskMatch[1]));
|
|
124
|
+
if (!detail) {
|
|
125
|
+
sendError(res, 404, 'TASK_NOT_FOUND', 'task not found in the current kingdom');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
sendJson(res, 200, detail);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const commandMatch = /^\/api\/commands\/([a-z.]+)$/u.exec(path);
|
|
132
|
+
if (req.method === 'POST' && commandMatch) {
|
|
133
|
+
const name = commandMatch[1];
|
|
134
|
+
if (!COMMANDS.has(name)) {
|
|
135
|
+
sendError(res, 404, 'UNKNOWN_COMMAND', `unknown command "${name}"`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// 自定义头强制预检,挡掉简单表单式 CSRF。
|
|
139
|
+
if (req.headers[CLIENT_HEADER] === undefined) {
|
|
140
|
+
sendError(res, 400, 'MISSING_CLIENT_HEADER', `write commands require the ${CLIENT_HEADER} header`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
let payload;
|
|
144
|
+
try {
|
|
145
|
+
payload = await readJsonBody(req);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
sendError(res, 400, 'INVALID_BODY', error instanceof Error ? error.message : String(error));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const result = await handlers.command(name, payload);
|
|
152
|
+
sendJson(res, result.ok ? 200 : 409, result);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
sendError(res, 404, 'NOT_FOUND', `no route for ${req.method ?? 'GET'} ${path}`);
|
|
156
|
+
}
|
|
157
|
+
let retries = 0;
|
|
158
|
+
let retryTimer = null;
|
|
159
|
+
let disposed = false;
|
|
160
|
+
/**
|
|
161
|
+
* 端口占用时有界重试。
|
|
162
|
+
*
|
|
163
|
+
* HMR 的常见时序是「新实例先起、旧实例后卸」,此时端口还在旧实例手里。
|
|
164
|
+
* 不重试的话,新实例的 GUI 通道会在整个生命周期里保持死掉——
|
|
165
|
+
* 开发时表现为「改一次代码 GUI 就再也连不上,必须整体重启 DSH」。
|
|
166
|
+
* 重试是有界的(最多 ~3 秒),避免真正的端口冲突变成无限循环。
|
|
167
|
+
*/
|
|
168
|
+
const RETRY_DELAY_MS = 300;
|
|
169
|
+
const MAX_RETRIES = 10;
|
|
170
|
+
server.on('error', (error) => {
|
|
171
|
+
if (error.code === 'EADDRINUSE' && !disposed && retries < MAX_RETRIES) {
|
|
172
|
+
retries++;
|
|
173
|
+
retryTimer = setTimeout(() => {
|
|
174
|
+
retryTimer = null;
|
|
175
|
+
if (!disposed)
|
|
176
|
+
server.listen(options.port, host);
|
|
177
|
+
}, RETRY_DELAY_MS);
|
|
178
|
+
// 重试期间保持安静;只有最终放弃才告警,避免刷屏。
|
|
179
|
+
if (retries === MAX_RETRIES) {
|
|
180
|
+
options.logger?.warn(`dsh-kingdom GUI 通道:端口 ${options.port} 持续被占用,`
|
|
181
|
+
+ `已重试 ${MAX_RETRIES} 次仍失败,本次加载不提供 GUI 通道(请换 guiPort 或检查残留进程)。`);
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (!disposed) {
|
|
186
|
+
options.logger?.warn(`dsh-kingdom GUI 通道启动失败(${error.message})`);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
server.listen(options.port, host, () => {
|
|
190
|
+
retries = 0;
|
|
191
|
+
options.logger?.info(`dsh-kingdom GUI 通道已监听 http://${host}:${options.port}`
|
|
192
|
+
+ `(snapshot / tasks / events / commands${token ? ',已启用 bearer token' : ''})`);
|
|
193
|
+
});
|
|
194
|
+
return () => {
|
|
195
|
+
disposed = true;
|
|
196
|
+
if (retryTimer) {
|
|
197
|
+
clearTimeout(retryTimer);
|
|
198
|
+
retryTimer = null;
|
|
199
|
+
}
|
|
200
|
+
server.close();
|
|
201
|
+
server.closeAllConnections?.();
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/gui/server.ts"],"names":[],"mappings":"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"}
|