@twinsearth/dsh-master 0.0.0-stage → 3.2.8

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 (95) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +89 -2
  3. package/client.js +263 -0
  4. package/cordis.patch.yml +71 -0
  5. package/dist-host/contract/client.js +82 -0
  6. package/dist-host/contract/constants.js +70 -0
  7. package/dist-host/contract/envelope.js +84 -0
  8. package/dist-host/contract/index.js +17 -0
  9. package/dist-host/contract/negotiate.js +87 -0
  10. package/dist-host/contract/server.js +71 -0
  11. package/dist-host/contract/transports.js +360 -0
  12. package/dist-host/index.js +260 -0
  13. package/dist-host/kernel.js +186 -0
  14. package/dist-host/plugins/cost-optimizer.js +147 -0
  15. package/dist-host/plugins/dual-manager.js +251 -0
  16. package/dist-host/plugins/superagent-router.js +499 -0
  17. package/dist-host/plugins/superagent-worker.js +73 -0
  18. package/dist-host/superagent/bridge-tokens.js +236 -0
  19. package/dist-host/superagent/dispatch.js +198 -0
  20. package/dist-host/superagent/feishu.js +115 -0
  21. package/dist-host/superagent/github.js +119 -0
  22. package/dist-host/superagent/pr-delivery.js +351 -0
  23. package/dist-host/superagent/router.js +219 -0
  24. package/dist-host/superagent/stream-adapter.js +238 -0
  25. package/dist-host/superagent/stream-tokens.js +141 -0
  26. package/dist-host/superagent/ticket-store.js +108 -0
  27. package/dist-host/superagent/trust.js +145 -0
  28. package/dist-host/superagent/types.js +144 -0
  29. package/dist-host/superagent/worker.js +95 -0
  30. package/dist-host/superagent/writeback.js +162 -0
  31. package/dist-worker/agent-loop.js +493 -0
  32. package/dist-worker/errors.js +41 -0
  33. package/dist-worker/github-pr.js +44 -0
  34. package/dist-worker/index.js +323 -0
  35. package/dist-worker/llm-http.js +351 -0
  36. package/dist-worker/main.js +45 -0
  37. package/dist-worker/prompt.js +35 -0
  38. package/dist-worker/sink-http.js +135 -0
  39. package/dist-worker/tools.js +542 -0
  40. package/dist-worker/types.js +14 -0
  41. package/docs/ARCHITECTURE.md +114 -0
  42. package/docs/CHANGELOG.md +377 -0
  43. package/docs/INTEROP.md +60 -0
  44. package/docs/REVIEW-v3.2.7.md +121 -0
  45. package/docs/ROADMAP.md +106 -0
  46. package/docs/SECURITY.md +153 -0
  47. package/docs/SMOKE-3.2.5.md +77 -0
  48. package/docs/SMOKE-3.2.7.md +73 -0
  49. package/docs/SUPERAGENT.md +141 -0
  50. package/docs/WORKER-AGENT-LOOP.md +139 -0
  51. package/icon.svg +9 -0
  52. package/locale/en.json +6 -0
  53. package/locale/zh.json +6 -0
  54. package/package.json +79 -4
  55. package/src/contract/client.js +86 -0
  56. package/src/contract/constants.js +79 -0
  57. package/src/contract/envelope.js +83 -0
  58. package/src/contract/index.js +19 -0
  59. package/src/contract/negotiate.js +106 -0
  60. package/src/contract/server.js +77 -0
  61. package/src/contract/transports.js +319 -0
  62. package/src/index.ts +265 -0
  63. package/src/kernel.ts +190 -0
  64. package/src/plugins/cost-optimizer.ts +155 -0
  65. package/src/plugins/dual-manager.ts +252 -0
  66. package/src/plugins/superagent-router.ts +465 -0
  67. package/src/plugins/superagent-worker.ts +85 -0
  68. package/src/superagent/bridge-tokens.ts +236 -0
  69. package/src/superagent/dispatch.ts +197 -0
  70. package/src/superagent/feishu.ts +104 -0
  71. package/src/superagent/github.ts +111 -0
  72. package/src/superagent/pr-delivery.ts +308 -0
  73. package/src/superagent/router.ts +201 -0
  74. package/src/superagent/stream-adapter.ts +184 -0
  75. package/src/superagent/stream-tokens.ts +142 -0
  76. package/src/superagent/ticket-store.ts +104 -0
  77. package/src/superagent/trust.ts +139 -0
  78. package/src/superagent/types.ts +135 -0
  79. package/src/superagent/worker.ts +100 -0
  80. package/src/superagent/writeback.ts +154 -0
  81. package/tsconfig.host.build.json +12 -0
  82. package/tsconfig.host.json +21 -0
  83. package/tsconfig.worker.build.json +13 -0
  84. package/tsconfig.worker.json +17 -0
  85. package/types/superagent.d.ts +99 -0
  86. package/worker/agent-loop.ts +524 -0
  87. package/worker/errors.ts +47 -0
  88. package/worker/github-pr.ts +65 -0
  89. package/worker/index.ts +334 -0
  90. package/worker/llm-http.ts +359 -0
  91. package/worker/main.ts +45 -0
  92. package/worker/prompt.ts +39 -0
  93. package/worker/sink-http.ts +159 -0
  94. package/worker/tools.ts +531 -0
  95. package/worker/types.ts +247 -0
@@ -0,0 +1,251 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
3
+ // @ts-nocheck — host JS→TS 迁移阶段:运行时行为优先,类型注解逐步补齐(worker/ 仍严格检查)。
4
+ /**
5
+ * 子插件 3:双主管(PM + PJM)。
6
+ *
7
+ * 定位:运行在母体/元主内的元 Agent;PM 把模糊需求变成结构化 PRD,PJM 把 PRD
8
+ * 拆成步骤并通过瓮城的 fangForker 调度分身执行。本子插件不直接运行生产任务:
9
+ * * 没有 fangForker.spawn 时 dispatch fail-closed;
10
+ * * 歧义澄清复用瓮城决策中心(fangDecisionCenter),不另造一套审批;
11
+ * * 角色:只允许 mother / essence(分身里没有“管理层”)。
12
+ */
13
+ import { FANG_SERVICE, ROW, SERVICE, createIdFactory, detectRole, enabledOf, masterError, roleAllowed, safeGet, } from "../kernel.js";
14
+ export const name = 'dsh-master-dual-manager';
15
+ export const inject = ['dshMaster'];
16
+ export const ROW_ID = ROW.dual;
17
+ const PRIORITIES = Object.freeze(['low', 'normal', 'high', 'critical']);
18
+ const DEFAULT_STEP_KIND = 'development';
19
+ function stringList(value) {
20
+ if (!Array.isArray(value))
21
+ return [];
22
+ return value.map((item) => String(item ?? '').trim()).filter(Boolean);
23
+ }
24
+ function missing(name) {
25
+ return masterError(`${name}-required`, `${name} is required`);
26
+ }
27
+ /**
28
+ * @param {object} ctx
29
+ * @param {{enabled?:boolean}} [config]
30
+ */
31
+ export function apply(ctx, config = {}) {
32
+ if (!enabledOf(config))
33
+ return { skipped: true, reason: 'disabled-by-config' };
34
+ const role = detectRole();
35
+ if (!roleAllowed(ROW_ID, role)) {
36
+ try {
37
+ ctx?.logger?.info?.(`[dsh-master] dual-manager skipped: role=${role} not in mother/essence`);
38
+ }
39
+ catch { /* ignore */ }
40
+ return { skipped: true, reason: `role-not-allowed:${role}` };
41
+ }
42
+ const master = safeGet(ctx, SERVICE.master);
43
+ if (!master || typeof master.registerSubplugin !== 'function') {
44
+ return { skipped: true, reason: 'master-core-unavailable' };
45
+ }
46
+ const newPrdId = createIdFactory('prd');
47
+ const newPlanId = createIdFactory('plan');
48
+ const prds = new Map();
49
+ const plans = new Map();
50
+ const decisionCenter = () => safeGet(ctx, FANG_SERVICE.decisionCenter);
51
+ const forker = () => {
52
+ const service = safeGet(ctx, FANG_SERVICE.forker);
53
+ return service && typeof service.spawn === 'function' ? service : null;
54
+ };
55
+ const pm = {
56
+ /** 需求 → 结构化 PRD(不调用模型;3.2.x 接入 PM agent 后由模型填充字段)。 */
57
+ createPrd(input = {}) {
58
+ const title = String(input.title ?? '').trim();
59
+ const goal = String(input.goal ?? '').trim();
60
+ if (!title)
61
+ return missing('title');
62
+ if (!goal)
63
+ return missing('goal');
64
+ const priority = PRIORITIES.includes(String(input.priority ?? 'normal')) ? String(input.priority ?? 'normal') : 'normal';
65
+ const prd = Object.freeze({
66
+ id: newPrdId(),
67
+ title,
68
+ goal,
69
+ priority,
70
+ constraints: Object.freeze(stringList(input.constraints)),
71
+ acceptance: Object.freeze(stringList(input.acceptance)),
72
+ requester: String(input.requester ?? 'user'),
73
+ status: 'draft',
74
+ createdAt: Date.now(),
75
+ });
76
+ prds.set(prd.id, prd);
77
+ return { ok: true, prd };
78
+ },
79
+ getPrd(id) {
80
+ const prd = prds.get(String(id ?? ''));
81
+ return prd ? { ok: true, prd } : masterError('prd-not-found', 'PRD not found', { id: String(id ?? '') });
82
+ },
83
+ listPrds() {
84
+ return { ok: true, items: [...prds.values()] };
85
+ },
86
+ /** 歧义澄清:复用瓮城决策中心,返回 ticket 或明确的 unavailable。 */
87
+ async askUser({ question, context = '', options = [], level = 'yellow' } = {}) {
88
+ const q = String(question ?? '').trim();
89
+ if (!q)
90
+ return missing('question');
91
+ const center = decisionCenter();
92
+ if (!center || typeof center.create !== 'function') {
93
+ return masterError('fang-decision-center-unavailable', 'dsh-fang decision center is required to ask the user');
94
+ }
95
+ try {
96
+ const out = await center.create({
97
+ level: ['red', 'yellow', 'blue', 'green'].includes(String(level)) ? String(level) : 'yellow',
98
+ title: q,
99
+ context: String(context),
100
+ options: Array.isArray(options) ? options : [],
101
+ source: { role: 'pm', kind: 'prd-clarification' },
102
+ });
103
+ if (out && out.ok === false)
104
+ return { ...masterError('decision-create-failed', 'decision center rejected the ticket', out) };
105
+ return { ok: true, ticket: out?.ticket ?? out };
106
+ }
107
+ catch (error) {
108
+ return masterError('decision-create-threw', String(error?.message ?? error));
109
+ }
110
+ },
111
+ };
112
+ const pjm = {
113
+ /** PRD → 可调度步骤计划。 */
114
+ plan(prdId, { steps = [] } = {}) {
115
+ const prd = prds.get(String(prdId ?? ''));
116
+ if (!prd)
117
+ return masterError('prd-not-found', 'PRD not found', { id: String(prdId ?? '') });
118
+ if (!Array.isArray(steps) || steps.length === 0)
119
+ return missing('steps');
120
+ const normalized = [];
121
+ for (const [index, step] of steps.entries()) {
122
+ const title = String(step?.title ?? '').trim();
123
+ const prompt = String(step?.prompt ?? '').trim();
124
+ if (!title)
125
+ return missing(`steps[${index}].title`);
126
+ if (!prompt)
127
+ return missing(`steps[${index}].prompt`);
128
+ normalized.push({
129
+ id: String(step?.id ?? `step-${index + 1}`),
130
+ title,
131
+ prompt,
132
+ kind: String(step?.kind ?? DEFAULT_STEP_KIND),
133
+ status: 'pending',
134
+ fork: null,
135
+ });
136
+ }
137
+ const plan = {
138
+ id: newPlanId(),
139
+ prdId: prd.id,
140
+ status: 'planned',
141
+ steps: normalized,
142
+ createdAt: Date.now(),
143
+ };
144
+ plans.set(plan.id, plan);
145
+ return { ok: true, plan };
146
+ },
147
+ getPlan(id) {
148
+ const plan = plans.get(String(id ?? ''));
149
+ return plan ? { ok: true, plan } : masterError('plan-not-found', 'Plan not found', { id: String(id ?? '') });
150
+ },
151
+ /** 调瓮城 fangForker.spawn 派生执行分身;无 forker 时 fail-closed。 */
152
+ async dispatch(planId, { stepId } = {}) {
153
+ const plan = plans.get(String(planId ?? ''));
154
+ if (!plan)
155
+ return masterError('plan-not-found', 'Plan not found', { id: String(planId ?? '') });
156
+ const step = plan.steps.find((item) => item.id === String(stepId ?? ''));
157
+ if (!step)
158
+ return masterError('step-not-found', 'Step not found', { id: String(stepId ?? '') });
159
+ if (step.fork)
160
+ return masterError('step-already-dispatched', 'Step already dispatched', { stepId: step.id, fork: step.fork });
161
+ const fangForker = forker();
162
+ if (!fangForker) {
163
+ return masterError('fang-forker-unavailable', 'dsh-fang fangForker.spawn is required to dispatch a step');
164
+ }
165
+ // 先占位再 await,避免两个并发 dispatch 同时看到 step.fork=null 而重复派生。
166
+ step.status = 'dispatching';
167
+ step.fork = { pending: true };
168
+ try {
169
+ const spawnConfig = config.dispatch && typeof config.dispatch === 'object' ? config.dispatch : {};
170
+ const fork = await fangForker.spawn({
171
+ homeDir: String(spawnConfig.homeDir ?? '') || undefined,
172
+ cliPath: String(spawnConfig.cliPath ?? '') || undefined,
173
+ profile: String(spawnConfig.profile ?? 'default') || 'default',
174
+ taskTitle: `PJM ${plan.id}/${step.id} ${step.title}`,
175
+ env: {
176
+ DSH_PJM_PRD_ID: String(plan.prdId),
177
+ DSH_PJM_PLAN_ID: String(plan.id),
178
+ DSH_PJM_STEP_ID: String(step.id),
179
+ DSH_PJM_KIND: String(step.kind),
180
+ DSH_PJM_PROMPT: String(step.prompt),
181
+ },
182
+ });
183
+ if (fork && fork.ok === false) {
184
+ step.status = 'pending';
185
+ step.fork = null;
186
+ return { ...masterError('fang-spawn-failed', 'fangForker.spawn returned an error', fork), step };
187
+ }
188
+ step.status = 'running';
189
+ step.fork = fork ?? null;
190
+ plan.status = 'running';
191
+ return { ok: true, planId: plan.id, stepId: step.id, step, fork };
192
+ }
193
+ catch (error) {
194
+ step.status = 'pending';
195
+ step.fork = null;
196
+ return masterError('fang-spawn-threw', String(error?.message ?? error));
197
+ }
198
+ },
199
+ completeStep(planId, stepId, { status = 'done', summary = '' } = {}) {
200
+ const plan = plans.get(String(planId ?? ''));
201
+ if (!plan)
202
+ return masterError('plan-not-found', 'Plan not found', { id: String(planId ?? '') });
203
+ const step = plan.steps.find((item) => item.id === String(stepId ?? ''));
204
+ if (!step)
205
+ return masterError('step-not-found', 'Step not found', { id: String(stepId ?? '') });
206
+ const next = status === 'failed' ? 'failed' : 'done';
207
+ step.status = next;
208
+ step.summary = String(summary);
209
+ const states = plan.steps.map((item) => item.status);
210
+ plan.status = states.every((state) => state === 'done')
211
+ ? 'done'
212
+ : (states.some((state) => state === 'running') ? 'running' : 'planned');
213
+ return { ok: true, plan };
214
+ },
215
+ listPlans() {
216
+ return { ok: true, items: [...plans.values()] };
217
+ },
218
+ };
219
+ const service = {
220
+ id: SERVICE.dual,
221
+ version: '3.2.5',
222
+ role,
223
+ pm,
224
+ pjm,
225
+ status() {
226
+ return {
227
+ ok: true,
228
+ id: 'dual-manager',
229
+ version: '3.2.5',
230
+ role,
231
+ pm: { prds: prds.size },
232
+ pjm: { plans: plans.size },
233
+ cooperation: {
234
+ decisionCenter: Boolean(decisionCenter()),
235
+ forker: Boolean(forker()),
236
+ },
237
+ };
238
+ },
239
+ };
240
+ try {
241
+ ctx.effect(() => ctx.provide(SERVICE.dual, service));
242
+ }
243
+ catch { /* fake ctx */ }
244
+ master.registerSubplugin({
245
+ id: 'dual-manager',
246
+ name: 'dsh-master-dual-manager',
247
+ capabilities: ['pm-prd', 'pm-ask-user', 'pjm-plan', 'pjm-dispatch'],
248
+ description: 'PM + PJM 双主管(仅 mother/essence)',
249
+ });
250
+ return service;
251
+ }