@twinsearth/dsh-master 3.3.3 → 3.3.5
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/README.md +17 -0
- package/client.js +126 -1
- package/cordis.patch.yml +51 -0
- package/dist-host/agents/pjm-agent.js +62 -0
- package/dist-host/agents/pm-agent.js +57 -0
- package/dist-host/agents/prompts.js +71 -0
- package/dist-host/agents/toolkit.js +64 -0
- package/dist-host/channel/abort-registry.js +93 -0
- package/dist-host/channel/protocol.js +114 -0
- package/dist-host/channel/server.js +349 -0
- package/dist-host/channel/stream-state.js +343 -0
- package/dist-host/channel/ws.js +199 -0
- package/dist-host/dual/documents.js +143 -0
- package/dist-host/dual/orchestrator.js +214 -0
- package/dist-host/dual/schema.js +367 -0
- package/dist-host/engine/atomic-switch.js +159 -0
- package/dist-host/engine/health-probe.js +86 -0
- package/dist-host/engine/index.js +18 -0
- package/dist-host/engine/llm-stream.js +215 -0
- package/dist-host/engine/plugin-updater.js +392 -0
- package/dist-host/engine/safe-update.js +150 -0
- package/dist-host/index.js +154 -3
- package/dist-host/kernel.js +27 -1
- package/dist-host/plugins/cost-optimizer.js +3 -3
- package/dist-host/plugins/dual-manager.js +374 -77
- package/docs/AGENTS-PM-PJM.md +207 -0
- package/docs/ARCHITECTURE.md +2 -0
- package/docs/CHANGELOG.md +74 -0
- package/docs/CHANNEL-WEBSOCKET.md +169 -0
- package/docs/DESIGN-command-center.md +147 -0
- package/docs/ENGINE-PRIMITIVES.md +215 -0
- package/docs/GAP-ANALYSIS-fang-session.md +75 -0
- package/docs/REVIEW-PM-PJM-3.3.4.md +179 -0
- package/docs/examples/command-center/README.md +167 -0
- package/package.json +5 -1
- package/src/agents/pjm-agent.ts +62 -0
- package/src/agents/pm-agent.ts +58 -0
- package/src/agents/prompts.ts +74 -0
- package/src/agents/toolkit.ts +64 -0
- package/src/channel/abort-registry.ts +85 -0
- package/src/channel/protocol.ts +110 -0
- package/src/channel/server.ts +289 -0
- package/src/channel/stream-state.ts +349 -0
- package/src/channel/ws.ts +182 -0
- package/src/dual/documents.ts +142 -0
- package/src/dual/orchestrator.ts +223 -0
- package/src/dual/schema.ts +355 -0
- package/src/engine/atomic-switch.ts +146 -0
- package/src/engine/health-probe.ts +84 -0
- package/src/engine/index.ts +20 -0
- package/src/engine/llm-stream.ts +189 -0
- package/src/engine/plugin-updater.ts +359 -0
- package/src/engine/safe-update.ts +148 -0
- package/src/index.ts +143 -4
- package/src/kernel.ts +28 -1
- package/src/plugins/cost-optimizer.ts +3 -3
- package/src/plugins/dual-manager.ts +337 -76
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
/**
|
|
5
|
+
* 快照 → 更新 → 冒烟 → 失败回滚(PluginSafeUpdater 的可编译实现)。
|
|
6
|
+
*
|
|
7
|
+
* 关键设计:**不假装有 VM**。执行能力由调用方注入一个 `executor`:
|
|
8
|
+
*
|
|
9
|
+
* executor.snapshot(name) → 建快照
|
|
10
|
+
* executor.restore(name) → 回滚到快照
|
|
11
|
+
* executor.run(argv, options) → 跑一条命令,返回 { ok, stdout, stderr, code }
|
|
12
|
+
*
|
|
13
|
+
* 房主本身没有 vm-sandbox;真实接入要么由瓮城的 VM 层提供 executor,
|
|
14
|
+
* 要么由宿主进程管理器提供。这里只保证**顺序、门禁与回滚语义**正确。
|
|
15
|
+
*/
|
|
16
|
+
function asString(value) { return String(value ?? '').trim(); }
|
|
17
|
+
function executorOf(input) {
|
|
18
|
+
const executor = input && typeof input === 'object' ? input.executor : null;
|
|
19
|
+
if (!executor || typeof executor.snapshot !== 'function' || typeof executor.restore !== 'function' || typeof executor.run !== 'function') {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return executor;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* @param {{
|
|
26
|
+
* executor?: {snapshot:Function, restore:Function, run:Function},
|
|
27
|
+
* snapshotName?: string,
|
|
28
|
+
* update?: string[], // 更新命令 argv
|
|
29
|
+
* smoke?: string[], // 冒烟命令 argv(可选)
|
|
30
|
+
* restart?: string[], // 回滚后重启命令 argv(可选)
|
|
31
|
+
* now?: () => number,
|
|
32
|
+
* }} input
|
|
33
|
+
*/
|
|
34
|
+
export async function runSafeUpdate(input = {}) {
|
|
35
|
+
const executor = executorOf(input);
|
|
36
|
+
if (!executor) {
|
|
37
|
+
return { ok: false, code: 'executor-required', error: 'an executor with snapshot/restore/run is required; no VM is assumed' };
|
|
38
|
+
}
|
|
39
|
+
const now = typeof input.now === 'function' ? input.now : () => Date.now();
|
|
40
|
+
const snapshotName = asString(input.snapshotName) || `pre-update-${now()}`;
|
|
41
|
+
const steps = [];
|
|
42
|
+
const record = (step, detail) => { steps.push({ step, at: now(), ...detail }); };
|
|
43
|
+
// 1. 快照是硬前置:拿不到快照就不允许更新,否则失败时无法回滚。
|
|
44
|
+
try {
|
|
45
|
+
const snapshot = await executor.snapshot(snapshotName);
|
|
46
|
+
if (snapshot && snapshot.ok === false) {
|
|
47
|
+
record('snapshot', { ok: false, detail: snapshot });
|
|
48
|
+
return { ok: false, code: 'snapshot-failed', error: 'snapshot failed; refusing to update', snapshotName, steps };
|
|
49
|
+
}
|
|
50
|
+
record('snapshot', { ok: true });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
record('snapshot', { ok: false, error: String(error?.message ?? error) });
|
|
54
|
+
return { ok: false, code: 'snapshot-threw', error: String(error?.message ?? error), snapshotName, steps };
|
|
55
|
+
}
|
|
56
|
+
const rollback = async (reason, detail) => {
|
|
57
|
+
record('rollback-start', { reason });
|
|
58
|
+
let restored = null;
|
|
59
|
+
try {
|
|
60
|
+
restored = await executor.restore(snapshotName);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
restored = { ok: false, error: String(error?.message ?? error) };
|
|
64
|
+
}
|
|
65
|
+
record('restore', { ok: restored?.ok !== false, detail: restored });
|
|
66
|
+
if (Array.isArray(input.restart) && input.restart.length > 0) {
|
|
67
|
+
try {
|
|
68
|
+
const restarted = await executor.run(input.restart, { phase: 'restart' });
|
|
69
|
+
record('restart', { ok: restarted?.ok !== false, detail: restarted });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
record('restart', { ok: false, error: String(error?.message ?? error) });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { ok: false, code: reason, error: detail, snapshotName, steps, rolledBack: restored?.ok !== false };
|
|
76
|
+
};
|
|
77
|
+
// 2. 更新
|
|
78
|
+
const update = Array.isArray(input.update) && input.update.length > 0 ? input.update : null;
|
|
79
|
+
if (!update)
|
|
80
|
+
return await rollback('update-command-required', 'update argv is required');
|
|
81
|
+
try {
|
|
82
|
+
const updated = await executor.run(update, { phase: 'update' });
|
|
83
|
+
if (updated && updated.ok === false) {
|
|
84
|
+
record('update', { ok: false, detail: updated });
|
|
85
|
+
return await rollback('update-failed', 'update command failed');
|
|
86
|
+
}
|
|
87
|
+
record('update', { ok: true });
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
record('update', { ok: false, error: String(error?.message ?? error) });
|
|
91
|
+
return await rollback('update-threw', String(error?.message ?? error));
|
|
92
|
+
}
|
|
93
|
+
// 3. 冒烟(可选但推荐):不通过同样回滚,绝不“更新成功但起不来”。
|
|
94
|
+
if (Array.isArray(input.smoke) && input.smoke.length > 0) {
|
|
95
|
+
try {
|
|
96
|
+
const smoke = await executor.run(input.smoke, { phase: 'smoke' });
|
|
97
|
+
if (smoke && smoke.ok === false) {
|
|
98
|
+
record('smoke', { ok: false, detail: smoke });
|
|
99
|
+
return await rollback('smoke-failed', 'smoke test failed after update');
|
|
100
|
+
}
|
|
101
|
+
record('smoke', { ok: true });
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
record('smoke', { ok: false, error: String(error?.message ?? error) });
|
|
105
|
+
return await rollback('smoke-threw', String(error?.message ?? error));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { ok: true, code: 'updated', snapshotName, steps, rolledBack: false };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 组合使用:切指针 + 健康检查 + 失败回滚。适合蓝绿切换后的验证。
|
|
112
|
+
* 指针切换成功但健康检查失败时,把指针切回旧目标。
|
|
113
|
+
*/
|
|
114
|
+
export async function switchAndVerify(input = {}) {
|
|
115
|
+
const { readPointer, switchWithRollback } = await import("./atomic-switch.js");
|
|
116
|
+
const { probeHealth } = await import("./health-probe.js");
|
|
117
|
+
// 先记住旧目标:健康检查失败时要切回它,而不是切到“刚切过去的新目标”。
|
|
118
|
+
const previous = await readPointer({ linkPath: input.linkPath, fs: input.fs });
|
|
119
|
+
const switched = await switchWithRollback({
|
|
120
|
+
linkPath: input.linkPath,
|
|
121
|
+
targetDir: input.targetDir,
|
|
122
|
+
fs: input.fs,
|
|
123
|
+
platform: input.platform,
|
|
124
|
+
now: input.now,
|
|
125
|
+
});
|
|
126
|
+
if (!switched.ok)
|
|
127
|
+
return { ok: false, code: switched.code, error: switched.error, switch: switched, health: null, previous };
|
|
128
|
+
const health = await probeHealth({
|
|
129
|
+
url: input.healthUrl,
|
|
130
|
+
fetchImpl: input.fetchImpl,
|
|
131
|
+
timeoutMs: input.timeoutMs,
|
|
132
|
+
intervalMs: input.intervalMs,
|
|
133
|
+
maxAttempts: input.maxAttempts,
|
|
134
|
+
now: input.now,
|
|
135
|
+
sleepImpl: input.sleepImpl,
|
|
136
|
+
});
|
|
137
|
+
if (health.ok)
|
|
138
|
+
return { ok: true, code: 'switched-and-healthy', switch: switched, health, previous };
|
|
139
|
+
if (!previous.ok || !previous.resolved) {
|
|
140
|
+
return { ok: false, code: 'health-failed-after-switch', switch: switched, health, previous, rollback: null };
|
|
141
|
+
}
|
|
142
|
+
const back = await switchWithRollback({
|
|
143
|
+
linkPath: input.linkPath,
|
|
144
|
+
targetDir: previous.resolved,
|
|
145
|
+
fs: input.fs,
|
|
146
|
+
platform: input.platform,
|
|
147
|
+
now: input.now,
|
|
148
|
+
}).catch((error) => ({ ok: false, error: String(error?.message ?? error) }));
|
|
149
|
+
return { ok: false, code: 'health-failed-after-switch', switch: switched, health, previous, rollback: back };
|
|
150
|
+
}
|
package/dist-host/index.js
CHANGED
|
@@ -17,13 +17,17 @@
|
|
|
17
17
|
* 协作只走服务键(见 kernel.mjs FANG_SERVICE):房主不 import 瓮城代码;瓮城服务
|
|
18
18
|
* 不可用时子插件必须 fail-closed,绝不本地降级执行。
|
|
19
19
|
*/
|
|
20
|
-
import { FANG_CONTRACT_MIN, FANG_SERVICE, MASTER_VERSION, ROLE_MATRIX, ROW, SERVICE, SUBPLUGIN_CATALOG, bearerOf, detectRole, enabledOf, jsonResponse, masterError, readJsonBody, roleAllowed, safeGet, } from "./kernel.js";
|
|
20
|
+
import { AGENT_CATALOG, FANG_CONTRACT_MIN, FANG_SERVICE, MASTER_VERSION, ROLE_MATRIX, ROW, SERVICE, SUBPLUGIN_CATALOG, bearerOf, detectRole, enabledOf, jsonResponse, masterError, readJsonBody, roleAllowed, safeGet, } from "./kernel.js";
|
|
21
|
+
import { randomBytes } from 'node:crypto';
|
|
21
22
|
import { BRIDGE_PATH, filterEnv } from "./superagent/dispatch.js";
|
|
22
23
|
import { createBridgeTokenManager } from "./superagent/bridge-tokens.js";
|
|
24
|
+
import { attachCommandCenterChannel, CHANNEL_PATH } from "./channel/server.js";
|
|
23
25
|
export const name = 'dsh-master';
|
|
24
26
|
export const inject = [];
|
|
25
27
|
/** 状态路由(Client UI 读取;只读、非敏感、loopback-only 由宿主 webServer 保证)。 */
|
|
26
28
|
export const STATUS_ROUTE = '/dsh-master/api/status';
|
|
29
|
+
/** 3.3.5:智囊团只读快照(交接/计划/反馈/复核)。 */
|
|
30
|
+
export const DUAL_ROUTE = '/dsh-master/api/dual';
|
|
27
31
|
/**
|
|
28
32
|
* 房主核心 apply。
|
|
29
33
|
* @param {object} ctx Cordis context
|
|
@@ -88,8 +92,104 @@ export function apply(ctx, config = {}) {
|
|
|
88
92
|
blockedReason: active || allowed ? null : `role-not-allowed:${role}`,
|
|
89
93
|
};
|
|
90
94
|
});
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
/**
|
|
96
|
+
* 3.3.4:两个智能体的**可选择性**(不是“已激活”)。
|
|
97
|
+
* preset 的激活由 agentPresets 服务在会话创建时决定,这里只如实报告宿主是否有该服务。
|
|
98
|
+
*/
|
|
99
|
+
const agentCards = () => {
|
|
100
|
+
const presets = safeGet(ctx, 'agentPresets');
|
|
101
|
+
const available = Boolean(presets) && typeof presets?.list === 'function';
|
|
102
|
+
return AGENT_CATALOG.map((entry) => ({
|
|
103
|
+
...entry,
|
|
104
|
+
presetServiceAvailable: available,
|
|
105
|
+
selectable: available,
|
|
106
|
+
hint: available
|
|
107
|
+
? '新会话创建时选择该 preset;已有会话不会被改变。'
|
|
108
|
+
: '宿主缺少 agentPresets 服务(DSH web bundle 才有),本 Host 无法选择该 preset。',
|
|
109
|
+
}));
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* 3.3.5:智囊团只读快照(面板数据源)。
|
|
113
|
+
* 只暴露“谁在等什么”,不暴露 token、不提供写入口;dual 未装配时给 available:false。
|
|
114
|
+
*/
|
|
115
|
+
const dualSnapshot = () => {
|
|
116
|
+
const dual = safeGet(ctx, SERVICE.dual);
|
|
117
|
+
if (!dual || typeof dual.status !== 'function') {
|
|
118
|
+
return { ok: true, available: false, reason: 'dual-service-unavailable', updatedAt: Date.now(), handoffs: [], plans: [], feedbacks: [], reviews: [], loop: null };
|
|
119
|
+
}
|
|
120
|
+
const safe = (fn, fallback) => { try {
|
|
121
|
+
return fn();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return fallback;
|
|
125
|
+
} };
|
|
126
|
+
const handoffs = safe(() => dual.pm.listHandoffs().items, []);
|
|
127
|
+
const plans = safe(() => dual.pjm.listPlans().items, []);
|
|
128
|
+
const feedbacks = safe(() => dual.pjm.listFeedback().items, []);
|
|
129
|
+
const reviews = safe(() => dual.pm.listReviews().items, []);
|
|
130
|
+
return {
|
|
131
|
+
ok: true,
|
|
132
|
+
available: true,
|
|
133
|
+
updatedAt: Date.now(),
|
|
134
|
+
agents: { pm: 'master-pm', pjm: 'master-pjm' },
|
|
135
|
+
loop: safe(() => dual.status().loop, null),
|
|
136
|
+
handoffs: handoffs.map((item) => ({
|
|
137
|
+
id: item.id,
|
|
138
|
+
prdId: item.prdId,
|
|
139
|
+
title: item.title,
|
|
140
|
+
status: item.status,
|
|
141
|
+
reason: item.reason ?? null,
|
|
142
|
+
openQuestions: (item.openQuestions ?? []).slice(0, 10),
|
|
143
|
+
acceptance: (item.acceptance ?? []).slice(0, 10),
|
|
144
|
+
planId: item.planId ?? null,
|
|
145
|
+
preparedAt: item.preparedAt ?? null,
|
|
146
|
+
})),
|
|
147
|
+
plans: plans.map((plan) => {
|
|
148
|
+
const progress = safe(() => dual.pjm.progress(plan.id).progress, null);
|
|
149
|
+
const lastReport = Array.isArray(plan.reports) && plan.reports.length > 0 ? plan.reports.at(-1) : null;
|
|
150
|
+
return {
|
|
151
|
+
id: plan.id,
|
|
152
|
+
prdId: plan.prdId,
|
|
153
|
+
title: plan.title,
|
|
154
|
+
status: plan.status,
|
|
155
|
+
handoffId: plan.handoffId ?? null,
|
|
156
|
+
progress,
|
|
157
|
+
lastReport: lastReport ? { status: lastReport.status, summary: lastReport.summary, blocker: lastReport.blocker ?? null, at: lastReport.at } : null,
|
|
158
|
+
pmReview: plan.pmReview ?? null,
|
|
159
|
+
feedbackId: plan.feedbackId ?? null,
|
|
160
|
+
};
|
|
161
|
+
}),
|
|
162
|
+
feedbacks: feedbacks.map((item) => ({
|
|
163
|
+
id: item.id,
|
|
164
|
+
planId: item.planId,
|
|
165
|
+
prdId: item.prdId ?? null,
|
|
166
|
+
createdAt: item.createdAt ?? null,
|
|
167
|
+
summary: String(item.summary ?? '').slice(0, 500),
|
|
168
|
+
metrics: item.metrics ?? null,
|
|
169
|
+
openIssues: (item.openIssues ?? []).slice(0, 10),
|
|
170
|
+
})),
|
|
171
|
+
reviews: reviews.map((review) => ({
|
|
172
|
+
id: review.id,
|
|
173
|
+
feedbackId: review.feedbackId,
|
|
174
|
+
planId: review.planId,
|
|
175
|
+
verdict: review.verdict,
|
|
176
|
+
createdAt: review.createdAt ?? null,
|
|
177
|
+
failed: (review.acceptanceChecks ?? []).filter((check) => check.met !== true).map((check) => check.criterion),
|
|
178
|
+
})),
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* 智能体通道 token(3.3.5):每次加载随机生成;只读 status 暴露给同源面板。
|
|
183
|
+
* 跨站页面由 Origin 校验挡住;token 只是纵深防御,不是唯一门槛。
|
|
184
|
+
*/
|
|
185
|
+
const channelToken = String(config.channelToken ?? '').trim() || randomBytes(24).toString('hex');
|
|
186
|
+
const safeOrigin = (fn) => { try {
|
|
187
|
+
return String(fn() ?? '');
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return '';
|
|
191
|
+
} };
|
|
192
|
+
const service = { id: SERVICE.master,
|
|
93
193
|
name: 'dsh-master',
|
|
94
194
|
version: MASTER_VERSION,
|
|
95
195
|
role,
|
|
@@ -135,6 +235,31 @@ export function apply(ctx, config = {}) {
|
|
|
135
235
|
uptimeMs: Date.now() - startedAt,
|
|
136
236
|
cooperation: cooperation(),
|
|
137
237
|
subplugins: catalog(),
|
|
238
|
+
/** 3.3.4:PM / PJM 智能体(Agent preset)清单;与子插件行分开报告。 */
|
|
239
|
+
agents: agentCards(),
|
|
240
|
+
/**
|
|
241
|
+
* 3.3.4:智囊团只读摘要。dual-manager 未装配时给 null —— 不构造空壳假装成功。
|
|
242
|
+
*/
|
|
243
|
+
dual: (() => {
|
|
244
|
+
const dual = safeGet(ctx, SERVICE.dual);
|
|
245
|
+
if (!dual || typeof dual.status !== 'function')
|
|
246
|
+
return null;
|
|
247
|
+
try {
|
|
248
|
+
const snapshot = dual.status();
|
|
249
|
+
return {
|
|
250
|
+
ok: snapshot.ok === true,
|
|
251
|
+
agents: snapshot.agents ?? null,
|
|
252
|
+
pm: snapshot.pm ?? null,
|
|
253
|
+
pjm: snapshot.pjm ?? null,
|
|
254
|
+
cooperation: snapshot.cooperation ?? null,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
})(),
|
|
261
|
+
/** 3.3.5:智囊团 WebSocket 通道(同源面板用;chat 目前返回具名缺口)。 */
|
|
262
|
+
channel: { path: CHANNEL_PATH, token: channelToken },
|
|
138
263
|
roleMatrix: ROLE_MATRIX,
|
|
139
264
|
bridge: {
|
|
140
265
|
enabled: Boolean(String(config.bridgeToken ?? '').trim()) || bridgeTokens.describe().tokens.length > 0,
|
|
@@ -192,6 +317,32 @@ export function apply(ctx, config = {}) {
|
|
|
192
317
|
catch (error) {
|
|
193
318
|
logWarn(`[dsh-master] status route registration failed: ${String(error?.message ?? error)}`);
|
|
194
319
|
}
|
|
320
|
+
// v3.3.5:智囊团只读数据源(交接 / 计划 / 反馈 / 复核)。无写口、无 token。
|
|
321
|
+
try {
|
|
322
|
+
webServer.register({
|
|
323
|
+
kind: 'exact',
|
|
324
|
+
path: DUAL_ROUTE,
|
|
325
|
+
method: 'GET',
|
|
326
|
+
handler: async (req, res) => jsonResponse(res, 200, dualSnapshot()),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
logWarn(`[dsh-master] dual route registration failed: ${String(error?.message ?? error)}`);
|
|
331
|
+
}
|
|
332
|
+
// v3.3.5:智囊团 WebSocket 通道(双向;当前只推快照,chat 返回具名缺口)。
|
|
333
|
+
try {
|
|
334
|
+
const channel = attachCommandCenterChannel({
|
|
335
|
+
webServer,
|
|
336
|
+
token: channelToken,
|
|
337
|
+
dualSnapshot,
|
|
338
|
+
allowedOrigin: safeOrigin(() => webServer.getOrigin?.()),
|
|
339
|
+
logger: { info: logWarn, warn: logWarn, error: logWarn },
|
|
340
|
+
});
|
|
341
|
+
logWarn(`[dsh-master] command-center channel: ${channel.attached ? channel.path : `not attached (${channel.reason})`}`);
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
logWarn(`[dsh-master] command-center channel failed: ${String(error?.message ?? error)}`);
|
|
345
|
+
}
|
|
195
346
|
// v3.2.3:原身 Router → 母体 forker 的跨进程派发桥。
|
|
196
347
|
try {
|
|
197
348
|
webServer.register({
|
package/dist-host/kernel.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 与瓮城 dsh-Fang 的协作只走 DSH 服务键,不 import fang 代码。
|
|
8
8
|
*/
|
|
9
9
|
import crypto from 'node:crypto';
|
|
10
|
-
export const MASTER_VERSION = '3.3.
|
|
10
|
+
export const MASTER_VERSION = '3.3.5';
|
|
11
11
|
export const FANG_CONTRACT_MIN = '3.1.14';
|
|
12
12
|
export const SERVICE = Object.freeze({
|
|
13
13
|
master: 'dshMaster',
|
|
@@ -57,6 +57,32 @@ export const ROW = Object.freeze({
|
|
|
57
57
|
cost: 'master-cost-optimizer',
|
|
58
58
|
dual: 'master-dual-manager',
|
|
59
59
|
});
|
|
60
|
+
/**
|
|
61
|
+
* 3.3.4:两个可被新会话选择的智能体(Agent preset,不是子插件行)。
|
|
62
|
+
*
|
|
63
|
+
* 与 ROLE_MATRIX 的区别:preset 决定“这个会话是谁”,角色矩阵决定“这一行在哪个角色里装配”。
|
|
64
|
+
* 两者都不改变对方;智能体自身不授予任何执行权限,工具边界写在 cordis.patch.yml 的 preset 里。
|
|
65
|
+
*/
|
|
66
|
+
export const AGENT_CATALOG = Object.freeze([
|
|
67
|
+
Object.freeze({
|
|
68
|
+
id: 'master-pm',
|
|
69
|
+
preset: 'master-pm',
|
|
70
|
+
name: '产品经理(PM)',
|
|
71
|
+
nameEn: 'Product Manager',
|
|
72
|
+
responsibilities: ['需求澄清与选择题请示', 'WHY/WHAT 与 PRD', '六类现状盘点(证据驱动)', '交接给 PJM', '复核/复盘/总结'],
|
|
73
|
+
tools: ['pm_create_prd', 'pm_create_assessment', 'pm_write_prd', 'pm_list_prds', 'pm_handoff_to_pjm', 'pm_review_feedback', 'pm_pipeline', 'pm_next_action'],
|
|
74
|
+
boundary: '只读代码与文档 + 向用户提问 + 写 PRD/盘点/复核;不执行、不派发、不发布。',
|
|
75
|
+
}),
|
|
76
|
+
Object.freeze({
|
|
77
|
+
id: 'master-pjm',
|
|
78
|
+
preset: 'master-pjm',
|
|
79
|
+
name: '项目经理(PJM)',
|
|
80
|
+
nameEn: 'Project Manager',
|
|
81
|
+
responsibilities: ['WBS 与七类项目管理', '接单与隔离派发', '节点自反馈', '测试/交付/验收与结案'],
|
|
82
|
+
tools: ['pjm_create_plan', 'pjm_take_handoff', 'pjm_dispatch_step', 'pjm_complete_step', 'pjm_project_status', 'pjm_check_step', 'pjm_report', 'pjm_submit_feedback', 'pjm_write_project', 'pjm_close_project', 'pjm_next_action'],
|
|
83
|
+
boundary: '只读代码 + 计划/派发/反馈;派发必须经 fangForker.spawn,无 forker 即 fail-closed;不替 PM 决定需求。',
|
|
84
|
+
}),
|
|
85
|
+
]);
|
|
60
86
|
export const ROLE = Object.freeze({ mother: 'mother', essence: 'essence', fork: 'fork' });
|
|
61
87
|
/**
|
|
62
88
|
* 角色矩阵:
|
|
@@ -108,7 +108,7 @@ export function apply(ctx, config = {}) {
|
|
|
108
108
|
};
|
|
109
109
|
const service = {
|
|
110
110
|
id: SERVICE.cost,
|
|
111
|
-
version: '3.3.
|
|
111
|
+
version: '3.3.5',
|
|
112
112
|
role,
|
|
113
113
|
get mode() {
|
|
114
114
|
return system.policy.describe().mode;
|
|
@@ -354,7 +354,7 @@ export function apply(ctx, config = {}) {
|
|
|
354
354
|
return {
|
|
355
355
|
ok: true,
|
|
356
356
|
id: 'cost-optimizer',
|
|
357
|
-
version: '3.3.
|
|
357
|
+
version: '3.3.5',
|
|
358
358
|
role,
|
|
359
359
|
mode: current.mode,
|
|
360
360
|
requestedMode: current.requestedMode,
|
|
@@ -396,7 +396,7 @@ export function apply(ctx, config = {}) {
|
|
|
396
396
|
doubleCountGuard: report.doubleCountGuard,
|
|
397
397
|
};
|
|
398
398
|
})(),
|
|
399
|
-
note: '3.3.
|
|
399
|
+
note: '3.3.5:系统层记账/预算/灰度/审计 + A1 工具输出紧凑化(默认 shadow)+ 瓮城回流出口(contract 1.1.1 真实投递,回执计数修复);工具面 A2–A3 与决策复用 B1–B4 已接入(shadow 默认);3.3.3 起角色矩阵母体全开、分身挂双主管;3.3.4 起内置 PM/PJM 两个智能体 preset;3.3.5 起补齐引擎原语、可中断 LLM 流、WS 通道与流式管线。',
|
|
400
400
|
};
|
|
401
401
|
},
|
|
402
402
|
};
|