dsh-punky-swarm 0.3.6

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 (101) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/LICENSE +661 -0
  3. package/README.en.md +281 -0
  4. package/README.md +281 -0
  5. package/cordis.patch.yml +59 -0
  6. package/lib/acps/certs.js +310 -0
  7. package/lib/acps/discovery-client.js +283 -0
  8. package/lib/acps/registry-client.js +527 -0
  9. package/lib/acps/server.js +302 -0
  10. package/lib/aip/agent-descriptor.js +170 -0
  11. package/lib/aip/identity.js +469 -0
  12. package/lib/aip/tool-descriptor.js +93 -0
  13. package/lib/api.js +266 -0
  14. package/lib/artifact-types.js +48 -0
  15. package/lib/assembly/audit-blind-review.js +170 -0
  16. package/lib/assembly/schema.js +253 -0
  17. package/lib/assembly.js +41 -0
  18. package/lib/assets.js +103 -0
  19. package/lib/bridge/trajectory.js +202 -0
  20. package/lib/client.js +704 -0
  21. package/lib/comms/acps-bridge.js +219 -0
  22. package/lib/comms/aip-format.js +141 -0
  23. package/lib/comms/budget.js +124 -0
  24. package/lib/comms/mailbox.js +221 -0
  25. package/lib/comms/topic-runtime.js +76 -0
  26. package/lib/comms/topic.js +115 -0
  27. package/lib/discovery/filter.js +199 -0
  28. package/lib/discovery/schema.js +157 -0
  29. package/lib/discovery/service.js +251 -0
  30. package/lib/hot/config-watch.js +252 -0
  31. package/lib/index.js +433 -0
  32. package/lib/lock.js +69 -0
  33. package/lib/panel/batch-detail.js +163 -0
  34. package/lib/panel/batch-list.js +84 -0
  35. package/lib/panel/locales.js +93 -0
  36. package/lib/panel/main.js +270 -0
  37. package/lib/panel/stream.js +348 -0
  38. package/lib/panel/theme.js +103 -0
  39. package/lib/panel/widgets.js +63 -0
  40. package/lib/schema.d.ts +192 -0
  41. package/lib/schema.js +259 -0
  42. package/lib/schema.ts +398 -0
  43. package/lib/state/archive.js +174 -0
  44. package/lib/state/command-exec.js +148 -0
  45. package/lib/state/constants.js +37 -0
  46. package/lib/state/corrupt-registry.js +103 -0
  47. package/lib/state/event-types.js +65 -0
  48. package/lib/state/gates.d.ts +226 -0
  49. package/lib/state/gates.js +428 -0
  50. package/lib/state/gates.ts +348 -0
  51. package/lib/state/machine-rules.d.ts +14 -0
  52. package/lib/state/machine-rules.js +76 -0
  53. package/lib/state/machine-rules.ts +88 -0
  54. package/lib/state/machine.js +68 -0
  55. package/lib/state/resume.js +201 -0
  56. package/lib/state/schema-v3.d.ts +10 -0
  57. package/lib/state/schema-v3.js +57 -0
  58. package/lib/state/schema-v3.ts +80 -0
  59. package/lib/state/store.js +607 -0
  60. package/lib/state/task-utils.js +40 -0
  61. package/lib/tools/core.js +323 -0
  62. package/lib/tools/git-utils.js +44 -0
  63. package/lib/tools/lane-tools.js +455 -0
  64. package/lib/tools/log-tools.js +173 -0
  65. package/lib/tools/mailbox-tools.js +121 -0
  66. package/lib/tools/merge-agent.js +127 -0
  67. package/lib/tools/register.js +85 -0
  68. package/lib/tools/shared.js +32 -0
  69. package/lib/types/contracts.d.ts +277 -0
  70. package/lib/types/contracts.js +17 -0
  71. package/lib/types/contracts.ts +279 -0
  72. package/lib/verify/evidence.js +249 -0
  73. package/lib/verify/gate.js +161 -0
  74. package/lib/verify/mount.js +48 -0
  75. package/lib/verify/selector.js +101 -0
  76. package/lib/watch/lane-heartbeat.js +333 -0
  77. package/lib/wave-plan.d.ts +48 -0
  78. package/lib/wave-plan.js +473 -0
  79. package/lib/wave-plan.ts +465 -0
  80. package/package.json +71 -0
  81. package/presets/jiufeng/NOTICE +5 -0
  82. package/presets/jiufeng/agent.cordis.yml +275 -0
  83. package/presets/jiufeng/preset.yml +3 -0
  84. package/skills/jiufeng-team/SKILL.md +214 -0
  85. package/skills/jiufeng-team/references/constitution.md +62 -0
  86. package/skills/jiufeng-team/references/roles/coder.md +17 -0
  87. package/skills/jiufeng-team/references/roles/coordinator.md +13 -0
  88. package/skills/jiufeng-team/references/roles/designer.md +14 -0
  89. package/skills/jiufeng-team/references/roles/doc-manager.md +13 -0
  90. package/skills/jiufeng-team/references/roles/manager.md +22 -0
  91. package/skills/jiufeng-team/references/roles/reviewer.md +19 -0
  92. package/skills/jiufeng-team/references/roles/supervisor.md +13 -0
  93. package/skills/jiufeng-team/references/roles/tester.md +13 -0
  94. package/skills/jiufeng-team/references/templates/call-chain-matrix-template.md +121 -0
  95. package/skills/jiufeng-team/references/templates/endpoint-behavior-template.md +60 -0
  96. package/skills/jiufeng-team/references/templates/gap-list.json +33 -0
  97. package/skills/jiufeng-team/references/templates/leader-decision-pack.md +92 -0
  98. package/skills/jiufeng-team/references/templates/plan-template.md +61 -0
  99. package/skills/jiufeng-team/references/templates/success-pattern-seeds.md +126 -0
  100. package/skills/jiufeng-team/references/workflow.md +98 -0
  101. package/skills/jiufeng-team/scripts/check-v3-density.mjs +98 -0
@@ -0,0 +1,302 @@
1
+ /*
2
+ Copyright (C) 2025-2026 Punky
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ // 文件 server:ACPs 对外 mTLS 服务端点(lib/acps 域,P1 lane exec-acps-server)
19
+ // 契约:
20
+ // - 独立 HTTPS 监听器(node:https + node:tls,零新依赖),默认端口 9443、绑定 127.0.0.1(config 可配);
21
+ // - TLS:minVersion TLSv1.3 + requestCert + rejectUnauthorized(=CERT_REQUIRED + TLSv1_3 语义,
22
+ // 对齐 registry-server/app/main_mtls.py:14-30);devInsecure 仅显式开发开关(默认 false);
23
+ // - 客户端证书 CN 提取 → req.peerAic(对齐 registry-server/app/core/peer_cert.py:18-38);
24
+ // 受保护端点 AIC 格式校验(复用 lib/aip/identity.js validateAic,对齐 api_atr.py:82-88);
25
+ // - 端点:POST /acps/rpc(AIP JSON-RPC,对齐 acps-sdk aip_rpc_model.py:15-57 +
26
+ // aip_base_model.py TaskCommand/TaskResult 形态)、GET /.well-known/acs.json(ACS 直取,
27
+ // 复用 lib/aip/agent-descriptor.js,对齐 beijing_food/acs.json:15-40)、GET /health
28
+ // (对齐 demo-partner/partners/main.py:110-121);
29
+ // - 路径前缀 /acps/*——与既有 /api/dsh-punky-swarm/*、/.well-known/aip 不冲突;
30
+ // - 默认关:装配层 enabled=false 时不实例化本模块(零运行时路径)。
31
+ // 纯工厂 + 纯函数,模块顶层零副作用。
32
+
33
+ import https from 'node:https';
34
+ import { readFileSync } from 'node:fs';
35
+ import { join } from 'node:path';
36
+ import { validateAic } from '../aip/identity.js';
37
+ import { buildAgentDescriptor } from '../aip/agent-descriptor.js';
38
+ import { DEFAULT_ASSEMBLY } from '../assembly.js';
39
+ import { engineVersion } from '../aip/tool-descriptor.js';
40
+ import { ensureAcpsCerts } from './certs.js';
41
+ import { AIP_COMMAND_TYPES as TASK_COMMAND_TYPES, TASK_STATES } from '../comms/aip-format.js';
42
+
43
+ // ── AIP RPC 常量(对齐参考实现 aip_base_model.py:23-49 / aip_rpc_model.py:15-57)──
44
+ export const JSONRPC_VERSION = '2.0';
45
+ export const RPC_METHOD = 'rpc';
46
+ // TaskCommandType 枚举 / TaskState 枚举:单一源 = lib/comms/aip-format.js(re-export 别名,禁止再自持定义)
47
+ export { TASK_COMMAND_TYPES, TASK_STATES };
48
+
49
+ // JSON-RPC 2.0 标准错误码(JSON-RPC 2.0 规范;参考实现 JSONRPCError 形态 aip_rpc_model.py:24-29)
50
+ export const RPC_ERRORS = Object.freeze({
51
+ PARSE: { code: -32700, message: 'Parse error' },
52
+ INVALID_REQUEST: { code: -32600, message: 'Invalid Request' },
53
+ METHOD_NOT_FOUND: { code: -32601, message: 'Method not found' },
54
+ INVALID_PARAMS: { code: -32602, message: 'Invalid params' },
55
+ INTERNAL: { code: -32603, message: 'Internal error' },
56
+ });
57
+
58
+ /**
59
+ * 解析并校验 AIP JSON-RPC 请求信封(aip_rpc_model.py:15-21,41-51 语义)
60
+ * @param {any} body 已解析的请求体
61
+ * @returns {{ ok: true, request: object, command: object } | { ok: false, error: { code, message, data? } }}
62
+ * 校验链:jsonrpc==="2.0" → method==="rpc" → params.command(TaskCommand)存在且
63
+ * type==="task-command" → command.command ∈ TaskCommandType 枚举(aip_base_model.py:154-164)。
64
+ */
65
+ export function parseRpcRequest(body) {
66
+ if (body === null || typeof body !== 'object' || Array.isArray(body)) {
67
+ return { ok: false, error: { ...RPC_ERRORS.INVALID_REQUEST, data: 'body must be a JSON object' } };
68
+ }
69
+ if (body.jsonrpc !== JSONRPC_VERSION) {
70
+ return { ok: false, error: { ...RPC_ERRORS.INVALID_REQUEST, data: 'jsonrpc must be "2.0"' } };
71
+ }
72
+ if (body.method !== RPC_METHOD) {
73
+ return { ok: false, error: { ...RPC_ERRORS.METHOD_NOT_FOUND, data: 'method must be "rpc"' } };
74
+ }
75
+ const command = body?.params?.command;
76
+ if (command === null || typeof command !== 'object' || Array.isArray(command)) {
77
+ return { ok: false, error: { ...RPC_ERRORS.INVALID_PARAMS, data: 'params.command (TaskCommand) is required' } };
78
+ }
79
+ if (command.type !== 'task-command') {
80
+ return { ok: false, error: { ...RPC_ERRORS.INVALID_PARAMS, data: 'command.type must be "task-command"' } };
81
+ }
82
+ if (typeof command.command !== 'string' || !TASK_COMMAND_TYPES.includes(command.command)) {
83
+ return { ok: false, error: { ...RPC_ERRORS.INVALID_PARAMS, data: 'command.command must be one of: ' + TASK_COMMAND_TYPES.join(', ') } };
84
+ }
85
+ return { ok: true, request: body, command };
86
+ }
87
+
88
+ /**
89
+ * JSON-RPC 响应信封(aip_rpc_model.py:32-38,54-57:result 或 error 二选一)
90
+ */
91
+ export function rpcResponse(id, result, error) {
92
+ const out = { jsonrpc: JSONRPC_VERSION, id: id ?? null };
93
+ if (error) out.error = error;
94
+ else out.result = result;
95
+ return out;
96
+ }
97
+
98
+ /**
99
+ * 默认 RPC handler:TaskCommand → TaskResult(state=accepted,TaskState 链首环)
100
+ * 对齐 aip_base_model.py:167-181(TaskResult: type/taskId/status{state,stateChangedAt})。
101
+ * 端点载体与消息形态校验;桥接注入自实现把 command 转 mailbox。
102
+ * @param {object} command TaskCommand
103
+ * @param {object} [ctx] { peerAic? }
104
+ * @returns {object} TaskResult
105
+ */
106
+ export function defaultRpcHandler(command, ctx = {}) {
107
+ const now = new Date().toISOString();
108
+ const taskId = typeof command?.taskId === 'string' && command.taskId.length > 0
109
+ ? command.taskId
110
+ : (typeof command?.id === 'string' ? command.id : 'task-' + Date.now().toString(36));
111
+ const result = {
112
+ type: 'task-result',
113
+ id: typeof command?.id === 'string' ? command.id : taskId,
114
+ sentAt: now,
115
+ senderRole: 'partner',
116
+ senderId: ctx?.peerAic ?? 'dsh-punky-swarm',
117
+ taskId,
118
+ status: { state: 'accepted', stateChangedAt: now },
119
+ commandHistory: [command],
120
+ };
121
+ return result;
122
+ }
123
+
124
+ /**
125
+ * ACS 直取内容生成(/.well-known/acs.json)——复用 lib/aip/agent-descriptor.js(ACS 14 必填键),
126
+ * 对齐 beijing_food/acs.json:15-40(securitySchemes.mutualTLS + endPoints JSONRPC + certificate.altNames.dns)。
127
+ * @param {object} config resolveAcpsConfig 输出(endpoint: { aic, host, port })
128
+ * @param {object} [opts] { assembly?, engineInfo? }(测试可注入)
129
+ */
130
+ export function buildAcs(config = {}, opts = {}) {
131
+ const endpoint = config?.endpoint ?? {};
132
+ const host = endpoint.host ?? '127.0.0.1';
133
+ const port = endpoint.port ?? 9443;
134
+ const assembly = opts.assembly ?? DEFAULT_ASSEMBLY;
135
+ const engineInfo = opts.engineInfo ?? {};
136
+ return buildAgentDescriptor(assembly, 'plan', 'coordinator', {
137
+ ...engineInfo,
138
+ version: engineInfo.version ?? engineVersion(),
139
+ aic: endpoint.aic ?? engineInfo.aic ?? `${assembly.team}.plan.coordinator`, // 派生占位;V3 注册后经 config 覆盖
140
+ // 对齐参考实现 demo acs.json(beijing_food/acs.json:15-40):
141
+ // securitySchemes.mutualTLS + endPoints[{url, transport:'JSONRPC', security:[{mtls:[]}]}] + certificate.altNames.dns
142
+ securitySchemes: {
143
+ mtls: { type: 'mutualTLS', description: '智能体间mTLS双向认证' },
144
+ },
145
+ endPoints: [{
146
+ url: `https://${host}:${port}/acps/rpc`,
147
+ transport: 'JSONRPC',
148
+ security: [{ mtls: [] }],
149
+ }],
150
+ certificate: { altNames: { dns: ['localhost'] } },
151
+ capabilities: { streaming: false, notification: false, messageQueue: [] },
152
+ });
153
+ }
154
+
155
+ // 请求体读取(与 lib/api.js readJsonBody 同形态:兼容流式 req 与预置 body)
156
+ function readBody(req) {
157
+ if (req && typeof req.body === 'string') return Promise.resolve(req.body ? JSON.parse(req.body) : {});
158
+ if (req && req.body && typeof req.body === 'object') return Promise.resolve(req.body);
159
+ if (req && typeof req.on === 'function') {
160
+ return new Promise((resolve, reject) => {
161
+ let data = '';
162
+ req.on('data', (c) => { data += c; });
163
+ req.on('end', () => {
164
+ try { resolve(data ? JSON.parse(data) : {}); } catch (e) { reject(e); }
165
+ });
166
+ req.on('error', reject);
167
+ });
168
+ }
169
+ return Promise.resolve({});
170
+ }
171
+
172
+ function sendJson(res, status, data) {
173
+ const body = JSON.stringify(data);
174
+ res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
175
+ res.end(body);
176
+ }
177
+
178
+ /**
179
+ * 创建 ACPs 对外 mTLS 服务端点(纯工厂,不自动 listen)
180
+ * @param {object} opts { config, logger?, acsProvider?, rpcHandler?, healthProvider?, certDir? }
181
+ * config = resolveAcpsConfig 输出(endpoint: { port, host, cert, key, ca, certDir, minVersion, devInsecure, aic })
182
+ * acsProvider 缺省 = buildAcs(config);rpcHandler 缺省 = defaultRpcHandler;
183
+ * healthProvider 缺省 = { agent, status:'online', tasks:{active:0}, groups:{active:0} }(partners/main.py:110-121 形态)
184
+ * @returns {{ server: import('node:https').Server, listen(port?), close(), address(), getPeerAic(req) }}
185
+ * 证书缺失/不可用时返回 { error }(装配层告警并保持禁用,不阻塞主进程)
186
+ */
187
+ export function createAcpsServer({ config = {}, logger, acsProvider, rpcHandler, healthProvider, certDir } = {}) {
188
+ const endpoint = config?.endpoint ?? {};
189
+ const agentName = endpoint.agentName ?? 'dsh-punky-swarm';
190
+
191
+ // ── 证书加载(cert/key/ca 三路径;缺省从 certDir 自动生成——幂等)──
192
+ let tls;
193
+ try {
194
+ const dir = certDir ?? endpoint.certDir;
195
+ const ca = endpoint.ca ?? (dir ? join(dir, 'ca.pem') : null);
196
+ if (!endpoint.cert || !endpoint.key || !ca) {
197
+ if (!dir) return { error: 'acps endpoint: cert/key/ca 三路径均需配置,或提供 certDir 自动生成' };
198
+ const generated = ensureAcpsCerts({ dir, aic: endpoint.aic });
199
+ tls = {
200
+ cert: endpoint.cert ? readFileSync(endpoint.cert) : readFileSync(generated.certFile),
201
+ key: endpoint.key ? readFileSync(endpoint.key) : readFileSync(generated.keyFile),
202
+ ca: readFileSync(generated.caFile),
203
+ };
204
+ } else {
205
+ tls = { cert: readFileSync(endpoint.cert), key: readFileSync(endpoint.key), ca: readFileSync(ca) };
206
+ }
207
+ } catch (e) {
208
+ return { error: 'acps endpoint: 证书加载失败——' + e.message };
209
+ }
210
+
211
+ const devInsecure = endpoint.devInsecure === true;
212
+ const server = https.createServer({
213
+ key: tls.key,
214
+ cert: tls.cert,
215
+ ca: tls.ca,
216
+ minVersion: endpoint.minVersion ?? 'TLSv1.3', // TLS 最低版本
217
+ requestCert: true, // CERT_REQUIRED 语义(main_mtls.py:28)
218
+ rejectUnauthorized: devInsecure ? false : true, // 生产不允许降级;开发模式仅显式开启
219
+ handshakeTimeout: 10_000,
220
+ }, (req, res) => {
221
+ handleRequest(req, res).catch((e) => {
222
+ logger?.warn?.('[dsh-punky-swarm] acps endpoint error: ' + String(e));
223
+ sendJson(res, 500, rpcResponse(null, null, { ...RPC_ERRORS.INTERNAL, data: String(e.message) }));
224
+ });
225
+ });
226
+
227
+ // 客户端证书 CN 提取(peer_cert.py:18-38:握手证书 subject.CN = 对端 AIC)
228
+ function getPeerAic(req) {
229
+ try {
230
+ const peer = req.socket?.getPeerCertificate?.();
231
+ return peer?.subject?.CN ?? null;
232
+ } catch {
233
+ return null;
234
+ }
235
+ }
236
+
237
+ async function handleRequest(req, res) {
238
+ const peerAic = getPeerAic(req);
239
+ req.peerAic = peerAic; // 注入请求级身份
240
+ const url = (req.url ?? '').split('?')[0];
241
+
242
+ if (req.method === 'GET' && url === '/.well-known/acs.json') {
243
+ const acs = (acsProvider ? acsProvider() : buildAcs(config)) ?? buildAcs(config);
244
+ return sendJson(res, 200, acs);
245
+ }
246
+ if (req.method === 'GET' && url === '/health') {
247
+ const health = healthProvider
248
+ ? healthProvider({ peerAic })
249
+ : { agent: agentName, status: 'online', tasks: { active: 0 }, groups: { active: 0 } };
250
+ return sendJson(res, 200, health);
251
+ }
252
+ if (req.method === 'POST' && url === '/acps/rpc') {
253
+ // 应用层身份校验(TLS 通过后):peer CN 须为合法 AIC 格式(api_atr.py:82-88 validate_aic 400 语义)
254
+ if (!peerAic || !validateAic(peerAic)) {
255
+ return sendJson(res, 400, rpcResponse(null, null, { code: 40001, message: 'Invalid client certificate identity: peer CN must be a valid AIC', data: { peerAic: peerAic ?? null } }));
256
+ }
257
+ let body;
258
+ try {
259
+ body = await readBody(req);
260
+ } catch {
261
+ return sendJson(res, 400, rpcResponse(null, null, { ...RPC_ERRORS.PARSE, data: 'invalid JSON body' }));
262
+ }
263
+ const parsed = parseRpcRequest(body);
264
+ if (!parsed.ok) {
265
+ // JSON-RPC 消息结构非法 → 应用层 400 + JSON-RPC error body(参考实现 pydantic 校验 422 语义映射,允许 4xx)
266
+ return sendJson(res, 400, rpcResponse(body?.id ?? null, null, parsed.error));
267
+ }
268
+ const handler = rpcHandler ?? defaultRpcHandler;
269
+ const result = await handler(parsed.command, { peerAic, request: parsed.request });
270
+ return sendJson(res, 200, rpcResponse(parsed.request.id ?? null, result, null));
271
+ }
272
+
273
+ return sendJson(res, 404, { error: 'not-found', path: url });
274
+ }
275
+
276
+ return {
277
+ server,
278
+ getPeerAic,
279
+ // listen:port 覆盖 config(测试用 0=临时端口);返回 promise(error 事件拒绝)
280
+ listen(port) {
281
+ const p = port ?? endpoint.port ?? 9443;
282
+ return new Promise((resolve, reject) => {
283
+ const onError = (e) => { server.off('listening', onListening); reject(e); };
284
+ const onListening = () => { server.off('error', onError); resolve(server.address()); };
285
+ server.once('error', onError);
286
+ server.once('listening', onListening);
287
+ server.listen(p, endpoint.host ?? '127.0.0.1');
288
+ });
289
+ },
290
+ close() {
291
+ return new Promise((resolve) => {
292
+ if (!server.listening) return resolve();
293
+ server.close(() => resolve());
294
+ // 长连接兜底
295
+ server.closeAllConnections?.();
296
+ });
297
+ },
298
+ address() {
299
+ return server.address();
300
+ },
301
+ };
302
+ }
@@ -0,0 +1,170 @@
1
+ /*
2
+ Copyright (C) 2025-2026 Punky
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ // 文件 agent-descriptor:国标 P4(GB/Z 185.4-2026《智能体描述》)描述生成器
19
+ // 契约:纯函数——输入装配配置(team/layer/role/skills)+ 引擎信息,输出每 role 一份描述 JSON;
20
+ // 零副作用、不消费运行时、可单测。
21
+ // ⚠ 2026-08 校准(覆盖旧 14+8 口径):字段集以 ACS(Agent Capability Specification)原文为准——
22
+ // registry-server/app/agent/acsSchema.json(JSON Schema 全文)为逐字字段来源;
23
+ // 旧「14+8 兼容映射层」(toLegacyDescriptor / toLegacySkill + ALL_TOOLS / LAYER_CAPABILITIES)
24
+ // 已移除(P2-06):ACPs 兼容路径中无调用——ACPs 描述直接消费 buildAgentDescriptor 的 ACS 格式
25
+ // (server.js:37,136 装配),描述以 ACS 格式为准。
26
+ // 派生值标记:固定值 = 本文件推导;预留 = 后续填充。
27
+ // 仅用内建能力,零新增依赖(红线:不改 node_modules)。
28
+ import { engineVersion } from './tool-descriptor.js';
29
+
30
+ // ACPs 协议版本(acsSchema.json examples:["02.01"])
31
+ export const ACS_PROTOCOL_VERSION = '02.01';
32
+
33
+ // ACS AgentCapabilitySpec 必填 14 键(acsSchema.json required 数组原文)
34
+ export const ACS_REQUIRED_FIELDS = Object.freeze([
35
+ 'aic', 'active', 'lastModifiedTime', 'protocolVersion', 'name', 'description',
36
+ 'version', 'provider', 'securitySchemes', 'endPoints', 'capabilities',
37
+ 'defaultInputModes', 'defaultOutputModes', 'skills',
38
+ ]);
39
+
40
+ // ACS AgentCapabilitySpec 可选键(acsSchema.json properties 全集 − required)
41
+ export const ACS_OPTIONAL_FIELDS = Object.freeze([
42
+ 'iconUrl', 'documentationUrl', 'webAppUrl', 'entityUserId', 'entityMeta', 'certificate',
43
+ ]);
44
+
45
+ // ACS AgentSkill 必填 5 键(acsSchema.json $defs.AgentSkill required 数组原文)
46
+ export const ACS_SKILL_REQUIRED_FIELDS = Object.freeze(['id', 'name', 'description', 'version', 'tags']);
47
+
48
+ // ACS AgentSkill 可选键
49
+ export const ACS_SKILL_OPTIONAL_FIELDS = Object.freeze(['examples', 'inputModes', 'outputModes']);
50
+
51
+ /**
52
+ * 单技能 → ACS AgentSkill(8 字段,逐字名见 acsSchema.json $defs.AgentSkill)
53
+ * 必填 5 键恒输出;可选 3 键(examples/inputModes/outputModes)仅在提供时输出(对齐参考实现
54
+ * demo acs.json 风格——真实数据才带,不臆造空数组)。
55
+ * @param {string} skillName 技能名(= assembly.skills[role] 中的名字,如 'dev-coder')
56
+ * @param {object} [meta] SKILL.md front matter 解析子集:
57
+ * { description?, version?, tags?, examples?, inputModes?, outputModes?, id? }
58
+ * @param {object} [engineInfo] { version? } 引擎信息(技能 version 缺省回退用)
59
+ * @returns {object} ACS AgentSkill
60
+ */
61
+ export function buildSkillDescriptor(skillName, meta = {}, engineInfo = {}) {
62
+ const version = meta.version ?? engineInfo.version ?? engineVersion();
63
+ const skill = {
64
+ id: meta.id ?? `dsh.skill.${skillName}`, // 派生:与 toolId 同风格(旧口径同规则);meta.id 可覆盖
65
+ name: skillName, // 透传(= 装配技能名)
66
+ description: meta.description ?? skillName, // 透传 front matter description
67
+ version, // front matter version ?? 引擎版本
68
+ tags: Array.isArray(meta.tags) ? [...meta.tags] : [], // front matter tags ?? 空数组(必填键恒在)
69
+ };
70
+ for (const k of ACS_SKILL_OPTIONAL_FIELDS) {
71
+ if (meta[k] !== undefined && meta[k] !== null) skill[k] = meta[k];
72
+ }
73
+ return skill;
74
+ }
75
+
76
+ /**
77
+ * 装配角色 → ACS AgentCapabilitySpec(Agent 描述,逐字字段名见 acsSchema.json 根对象)
78
+ * 必填 14 键恒输出(值取诚实推导默认,可经 engineInfo 覆盖);可选 6 键仅在提供时输出。
79
+ * 派生值说明(诚实标注,spec §3.2):
80
+ * - aic:注册服务分配的 OID 身份码——本引擎无 ARSP 注册,输出派生占位 `${team}.${layer}.${role}`,
81
+ * 真实 AIC 需按 spec §3.4(10 级编码 + CRC-16 校验)由注册服务分配后经 engineInfo.aic 覆盖;
82
+ * - active:默认 true(引擎装配角色即可用),engineInfo.active 可覆盖;
83
+ * - lastModifiedTime:ISO 8601;engineInfo.lastModifiedTime ?? engineInfo.generatedAt ?? 当前时刻;
84
+ * - provider:{ countryCode: 'CN', organization: owner ?? 'dsh' }(AgentProvider 全可选字段);
85
+ * - securitySchemes:默认 {}(引擎 agent 经本地文件 mailbox 通信、无网络认证声明;接入真实
86
+ * 端点时经 engineInfo.securitySchemes 注入,如 mutualTLS);
87
+ * - endPoints:默认 [](引擎 agent 非网络服务端点;接入时经 engineInfo.endPoints 注入);
88
+ * - capabilities:默认 { streaming:false, notification:false, messageQueue:[] }(如实:无 SSE/
89
+ * 异步推送/MQ),engineInfo.capabilities 可覆盖;
90
+ * - defaultInputModes / defaultOutputModes:默认 ['text/plain','application/json'](指令文本 +
91
+ * JSON 参数/回执,引擎 mailbox/工具层实际承载格式)。
92
+ * @param {object} assembly 装配配置 { team, layers: { <layer>: { roles: string[], skills: { [role]: string[] } } }, owner? }
93
+ * @param {string} layer 层名(plan/exec/audit)
94
+ * @param {string} role 角色名(如 'coder')
95
+ * @param {object} [engineInfo] ACS 覆盖 + 引擎信息:
96
+ * { version?, owner?, skillMeta?, generatedAt?, aic?, active?, lastModifiedTime?, protocolVersion?,
97
+ * iconUrl?, documentationUrl?, webAppUrl?, provider?, securitySchemes?, endPoints?,
98
+ * capabilities?, defaultInputModes?, defaultOutputModes?, entityUserId?, entityMeta?, certificate? }
99
+ * skillMeta: { [skillName]: { description?, version?, tags?, examples?, inputModes?, outputModes? } }
100
+ * @returns {object} ACS AgentCapabilitySpec
101
+ */
102
+ export function buildAgentDescriptor(assembly, layer, role, engineInfo = {}) {
103
+ const team = assembly.team ?? 'generic';
104
+ const layerCfg = assembly.layers?.[layer];
105
+ const skillNames = Array.isArray(layerCfg?.skills?.[role]) ? layerCfg.skills[role] : [];
106
+ const skillMetaMap = engineInfo.skillMeta ?? {};
107
+ const skills = skillNames.map((name) => buildSkillDescriptor(name, skillMetaMap[name] ?? {}, engineInfo));
108
+ // description:取 skills[0] 对应 SKILL.md description;无则用 role 名
109
+ const description = skills[0]?.description ?? role;
110
+ const owner = assembly.owner ?? engineInfo.owner ?? 'dsh';
111
+
112
+ const descriptor = {
113
+ aic: engineInfo.aic ?? `${team}.${layer}.${role}`, // 派生占位;真实 AIC 需注册服务分配(见上)
114
+ active: engineInfo.active ?? true,
115
+ lastModifiedTime: engineInfo.lastModifiedTime ?? engineInfo.generatedAt ?? new Date().toISOString(),
116
+ protocolVersion: engineInfo.protocolVersion ?? ACS_PROTOCOL_VERSION,
117
+ name: role, // 透传(= 角色名)
118
+ description, // 首技能 SKILL.md description ?? role 名
119
+ version: engineInfo.version ?? engineVersion(), // 引擎版本,可被 config 覆盖
120
+ provider: engineInfo.provider ?? { countryCode: 'CN', organization: owner }, // AgentProvider 派生
121
+ securitySchemes: engineInfo.securitySchemes ?? {}, // 默认空(如实:无网络认证声明)
122
+ endPoints: engineInfo.endPoints ?? [], // 默认空数组(schema default)
123
+ capabilities: engineInfo.capabilities ?? { streaming: false, notification: false, messageQueue: [] },
124
+ defaultInputModes: engineInfo.defaultInputModes ?? ['text/plain', 'application/json'],
125
+ defaultOutputModes: engineInfo.defaultOutputModes ?? ['text/plain', 'application/json'],
126
+ skills, // assembly.skills[role] 的 ACS AgentSkill 数组
127
+ };
128
+ for (const k of ACS_OPTIONAL_FIELDS) {
129
+ if (engineInfo[k] !== undefined && engineInfo[k] !== null) descriptor[k] = engineInfo[k];
130
+ }
131
+ return descriptor;
132
+ }
133
+
134
+ /**
135
+ * 装配配置 → 全部角色的 ACS AgentCapabilitySpec 列表(主入口)
136
+ * 遍历 assembly.layers[*].roles[*],每 role 生成一份描述
137
+ * @param {object} assembly 装配配置(见 buildAgentDescriptor)
138
+ * @param {object} [engineInfo](见 buildAgentDescriptor)
139
+ * @returns {Array<object>} 每 role 一份 ACS AgentCapabilitySpec
140
+ */
141
+ export function buildAgentDescriptors(assembly, engineInfo = {}) {
142
+ const layers = assembly?.layers ?? {};
143
+ const descriptors = [];
144
+ for (const layer of Object.keys(layers)) {
145
+ const roles = layers[layer]?.roles ?? [];
146
+ for (const role of roles) {
147
+ descriptors.push(buildAgentDescriptor(assembly, layer, role, engineInfo));
148
+ }
149
+ }
150
+ return descriptors;
151
+ }
152
+
153
+ /**
154
+ * 智能体描述目录快照(对齐 buildToolCatalog 形态):持有全量 ACS 描述 + 只读 list();
155
+ * 供 register.js 按 aip.enabled 门控生成、api.js 挂 /agents 只读端点。
156
+ * @param {object} assembly 装配配置
157
+ * @param {object} [engineInfo](见 buildAgentDescriptor;建议传 generatedAt 固定 lastModifiedTime)
158
+ * @returns {{ generatedAt: string, descriptors: ReadonlyArray<object>, list(): object[] }}
159
+ */
160
+ export function buildAgentCatalog(assembly, engineInfo = {}) {
161
+ const descriptors = buildAgentDescriptors(assembly, engineInfo);
162
+ return {
163
+ generatedAt: engineInfo.generatedAt ?? new Date().toISOString(),
164
+ descriptors: Object.freeze(descriptors),
165
+ list() {
166
+ return descriptors.slice();
167
+ },
168
+ };
169
+ }
170
+