@archsight/aios 1.0.0 → 1.1.0
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/CHANGELOG.md +80 -8
- package/LICENSE +184 -21
- package/README.md +163 -135
- package/agents/README.md +2 -1
- package/agents/euclid/constraints.md +2 -1
- package/agents/euclid/responsibilities.md +1 -1
- package/agents/euclid/role.md +1 -1
- package/agents/euclid/system-prompt.md +5 -2
- package/agents/euclid/workflow.md +3 -3
- package/bin/archsight-aios.mjs +436 -1
- package/docs/quickstart.md +2 -1
- package/governance/README.md +3 -0
- package/governance/arbitration-protocol.md +153 -0
- package/package.json +68 -68
- package/runtime/README.md +7 -0
- package/runtime/agent-routing.md +98 -72
- package/runtime/archsight-aios.manifest.json +312 -290
- package/runtime/capability-adapters.json +27 -0
- package/runtime/capability-registry.json +458 -0
- package/runtime/capability-registry.schema.json +135 -0
- package/runtime/skill-routing.md +61 -55
- package/skills/README.md +54 -30
- package/skills/aios-arch/SKILL.md +195 -149
- package/skills/aios-arch/agents/openai.yaml +3 -3
- package/skills/aios-ceo/SKILL.md +180 -89
- package/skills/aios-ceo/agents/openai.yaml +4 -4
- package/skills/aios-design/SKILL.md +107 -99
- package/skills/aios-design/agents/openai.yaml +4 -4
- package/skills/aios-exec/SKILL.md +69 -58
- package/skills/aios-exec/agents/openai.yaml +3 -3
- package/skills/aios-knowledge/SKILL.md +65 -54
- package/skills/aios-knowledge/agents/openai.yaml +3 -3
- package/skills/aios-plan/SKILL.md +93 -75
- package/skills/aios-plan/agents/openai.yaml +3 -3
- package/skills/aios-review/SKILL.md +72 -61
- package/skills/aios-review/agents/openai.yaml +3 -3
- package/skills/aios-runtime/SKILL.md +69 -58
- package/skills/aios-runtime/agents/openai.yaml +3 -3
- package/skills/aios-structural/SKILL.md +67 -0
- package/skills/aios-structural/agents/openai.yaml +4 -0
- package/templates/project-ai/.ai/ARCHSIGHT_AIOS_RULES.md +30 -25
- package/templates/project-ai/.ai/agent-routing.md +48 -42
- package/templates/project-ai/.ai/skills.md +38 -32
- package/templates/project-ai/.ai/workflows.md +38 -35
- package/templates/project-ai/AGENTS.md +25 -25
- package/templates/project-ai/CLAUDE.md +25 -25
- package/templates/project-ai/GEMINI.md +25 -25
- package/workflows/README.md +2 -0
- package/workflows/architecture-review.md +103 -79
- package/workflows/bug-fixing.md +63 -62
- package/workflows/code-review.md +55 -54
- package/workflows/feature-development.md +64 -56
- package/workflows/rag-pipeline.md +9 -5
- package/workflows/review.md +74 -70
package/agents/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
| 规范知识库 / GraphRAG 项目 | `--profile rag-knowledge` |
|
|
17
17
|
| 看某类任务怎么做 | `skills/` 和 `workflows/` |
|
|
18
18
|
|
|
19
|
-
维护者才需要关心 Agent 定义。每个 Agent 不应只保存 prompt,而应保存职责、边界、输入、输出、禁止事项、参与 workflow
|
|
19
|
+
维护者才需要关心 Agent 定义。每个 Agent 不应只保存 prompt,而应保存职责、边界、输入、输出、禁止事项、参与 workflow、模型路由和可用 Capability。
|
|
20
20
|
|
|
21
21
|
当前采用三层管理:
|
|
22
22
|
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
| Source | `role.md` / `responsibilities.md` / `constraints.md` / `workflow.md` | 长期维护角色资产 |
|
|
26
26
|
| Runtime | `system-prompt.md` | 运行时可加载的最小提示词 |
|
|
27
27
|
| Instance / Adapter | Codex、Claude Code、Gemini、Hermes、飞书等 | 实际对话或协作入口 |
|
|
28
|
+
| Capability | `runtime/capability-registry.json` | 外部工具、结构化知识和确定性证据接口 |
|
|
28
29
|
|
|
29
30
|
当前与规划 Agent:
|
|
30
31
|
|
package/agents/euclid/role.md
CHANGED
|
@@ -7,17 +7,20 @@
|
|
|
7
7
|
- 评审结构力学、荷载、约束、FEM 和计算流程。
|
|
8
8
|
- 明确假设、单位、边界条件和验证路径。
|
|
9
9
|
- 对不确定或需要专业签审的内容标注待核验。
|
|
10
|
+
- 将关键数值计算转为 Capability / 求解器输入,不用 LLM 直接口算工程结论。
|
|
10
11
|
|
|
11
12
|
边界:
|
|
12
13
|
|
|
13
14
|
- 不替代结构工程师签章。
|
|
14
15
|
- 不在缺少关键条件时输出确定安全结论。
|
|
15
16
|
- 不绕过测试或规范核验。
|
|
17
|
+
- 不把未执行的工具调用或样例公式包装成已验证结果。
|
|
16
18
|
|
|
17
19
|
输出:
|
|
18
20
|
|
|
19
21
|
1. 结论
|
|
20
22
|
2. 已知条件
|
|
21
23
|
3. 假设与缺口
|
|
22
|
-
4.
|
|
23
|
-
5.
|
|
24
|
+
4. Capability 调用计划或工具结果
|
|
25
|
+
5. 建模 / 计算建议
|
|
26
|
+
6. 待核验项
|
package/bin/archsight-aios.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import os from "node:os";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -43,6 +44,7 @@ const skillAliases = {
|
|
|
43
44
|
"aios-bim-domain-modeling",
|
|
44
45
|
"archsight-bim-domain-modeling"
|
|
45
46
|
],
|
|
47
|
+
"aios-structural": ["aios-structural-review", "archsight-structural-review"],
|
|
46
48
|
"aios-runtime": [
|
|
47
49
|
"aios-runtime-design",
|
|
48
50
|
"archsight-runtime-design",
|
|
@@ -62,6 +64,7 @@ function usage() {
|
|
|
62
64
|
" archsight-aios doctor",
|
|
63
65
|
" archsight-aios init [--cwd <path>] [--mode <auto|full|linked|ai-only>] [--profile <name>]",
|
|
64
66
|
" archsight-aios validate [--cwd <path>] [--profile <name>] [--temp]",
|
|
67
|
+
" archsight-aios capability:call --capability <id> --agent <id> --skill <id> --input <json-file>",
|
|
65
68
|
" archsight-aios hermes:validate",
|
|
66
69
|
" archsight-aios hermes:sync-dry-run",
|
|
67
70
|
" archsight-aios hermes:detect-drift",
|
|
@@ -72,6 +75,7 @@ function usage() {
|
|
|
72
75
|
" doctor Check repository assets and user-level installation.",
|
|
73
76
|
" init Add AI rules and .ai governance files to a project.",
|
|
74
77
|
" validate Validate the project AI template output.",
|
|
78
|
+
" capability:call Authorize and call a registered local Capability adapter.",
|
|
75
79
|
" hermes:* Validate or dry-run Hermes runtime prompt sync.",
|
|
76
80
|
"",
|
|
77
81
|
"Examples:",
|
|
@@ -94,7 +98,15 @@ function parseArgs(argv) {
|
|
|
94
98
|
profile: undefined,
|
|
95
99
|
cwd: process.cwd(),
|
|
96
100
|
help,
|
|
97
|
-
temp: false
|
|
101
|
+
temp: false,
|
|
102
|
+
capability: undefined,
|
|
103
|
+
agent: undefined,
|
|
104
|
+
skill: undefined,
|
|
105
|
+
input: undefined,
|
|
106
|
+
mcpCwd: undefined,
|
|
107
|
+
mcpCommand: undefined,
|
|
108
|
+
mcpArgs: [],
|
|
109
|
+
timeoutMs: undefined
|
|
98
110
|
};
|
|
99
111
|
|
|
100
112
|
for (let i = 0; i < rest.length; i += 1) {
|
|
@@ -111,6 +123,22 @@ function parseArgs(argv) {
|
|
|
111
123
|
options.profile = rest[++i];
|
|
112
124
|
} else if (arg === "--temp") {
|
|
113
125
|
options.temp = true;
|
|
126
|
+
} else if (arg === "--capability") {
|
|
127
|
+
options.capability = rest[++i];
|
|
128
|
+
} else if (arg === "--agent") {
|
|
129
|
+
options.agent = rest[++i];
|
|
130
|
+
} else if (arg === "--skill") {
|
|
131
|
+
options.skill = rest[++i];
|
|
132
|
+
} else if (arg === "--input") {
|
|
133
|
+
options.input = path.resolve(rest[++i]);
|
|
134
|
+
} else if (arg === "--mcp-cwd") {
|
|
135
|
+
options.mcpCwd = path.resolve(rest[++i]);
|
|
136
|
+
} else if (arg === "--mcp-command") {
|
|
137
|
+
options.mcpCommand = rest[++i];
|
|
138
|
+
} else if (arg === "--mcp-arg") {
|
|
139
|
+
options.mcpArgs.push(rest[++i]);
|
|
140
|
+
} else if (arg === "--timeout-ms") {
|
|
141
|
+
options.timeoutMs = Number(rest[++i]);
|
|
114
142
|
} else if (arg === "--help" || arg === "-h") {
|
|
115
143
|
options.help = true;
|
|
116
144
|
} else {
|
|
@@ -213,6 +241,373 @@ async function readJson(filePath) {
|
|
|
213
241
|
return JSON.parse(raw);
|
|
214
242
|
}
|
|
215
243
|
|
|
244
|
+
async function readCapabilityRegistry() {
|
|
245
|
+
const manifest = await readManifest();
|
|
246
|
+
const registryPath = path.join(repoRoot, manifest.capabilityRegistry.registryPath);
|
|
247
|
+
return readJson(registryPath);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function readCapabilityAdapters() {
|
|
251
|
+
const manifest = await readManifest();
|
|
252
|
+
const adapterPath = manifest.capabilityRegistry?.adapterPath;
|
|
253
|
+
if (!adapterPath) {
|
|
254
|
+
return { schema: 1, adapters: [] };
|
|
255
|
+
}
|
|
256
|
+
return readJson(path.join(repoRoot, adapterPath));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function jsonTypeMatches(schemaType, value) {
|
|
260
|
+
if (schemaType === "object") {
|
|
261
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
262
|
+
}
|
|
263
|
+
if (schemaType === "array") {
|
|
264
|
+
return Array.isArray(value);
|
|
265
|
+
}
|
|
266
|
+
if (schemaType === "integer") {
|
|
267
|
+
return Number.isInteger(value);
|
|
268
|
+
}
|
|
269
|
+
if (schemaType === "number") {
|
|
270
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
271
|
+
}
|
|
272
|
+
if (schemaType === "string") {
|
|
273
|
+
return typeof value === "string";
|
|
274
|
+
}
|
|
275
|
+
if (schemaType === "boolean") {
|
|
276
|
+
return typeof value === "boolean";
|
|
277
|
+
}
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function valueAtPath(value, fieldPath) {
|
|
282
|
+
return fieldPath.split(".").reduce((current, key) => {
|
|
283
|
+
if (current === null || typeof current !== "object") {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
return current[key];
|
|
287
|
+
}, value);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function validateJsonSchemaSubset(schema, value, label = "$", errors = []) {
|
|
291
|
+
if (!schema || typeof schema !== "object") {
|
|
292
|
+
return errors;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (schema.type && !jsonTypeMatches(schema.type, value)) {
|
|
296
|
+
errors.push(`${label} expected ${schema.type}`);
|
|
297
|
+
return errors;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
301
|
+
errors.push(`${label} expected one of ${schema.enum.join(", ")}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (typeof schema.minimum === "number" && typeof value === "number" && value < schema.minimum) {
|
|
305
|
+
errors.push(`${label} must be >= ${schema.minimum}`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (schema.type === "object" && value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
309
|
+
for (const requiredField of schema.required ?? []) {
|
|
310
|
+
if (!Object.hasOwn(value, requiredField)) {
|
|
311
|
+
errors.push(`${label}.${requiredField} is required`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
for (const [property, propertySchema] of Object.entries(schema.properties ?? {})) {
|
|
316
|
+
if (Object.hasOwn(value, property)) {
|
|
317
|
+
validateJsonSchemaSubset(propertySchema, value[property], `${label}.${property}`, errors);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
323
|
+
value.forEach((item, index) => validateJsonSchemaSubset(schema.items, item, `${label}[${index}]`, errors));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return errors;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function findCapability(registry, capabilityId) {
|
|
330
|
+
return (registry.capabilities ?? []).find((capability) => capability.id === capabilityId);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function findCapabilityAdapter(adapters, capabilityId) {
|
|
334
|
+
return (adapters.adapters ?? []).find((adapter) => (adapter.capabilityIds ?? []).includes(capabilityId));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function authorizeCapability(capability, options) {
|
|
338
|
+
if (!options.agent) {
|
|
339
|
+
throw new Error("--agent is required for Capability calls");
|
|
340
|
+
}
|
|
341
|
+
if (!options.skill) {
|
|
342
|
+
throw new Error("--skill is required for Capability calls");
|
|
343
|
+
}
|
|
344
|
+
if (!capability.ownerAgents.includes(options.agent)) {
|
|
345
|
+
throw new Error(`Capability denied: agent ${options.agent} cannot call ${capability.id}`);
|
|
346
|
+
}
|
|
347
|
+
if ((capability.allowedSkills ?? []).length > 0 && !capability.allowedSkills.includes(options.skill)) {
|
|
348
|
+
throw new Error(`Capability denied: skill ${options.skill} cannot call ${capability.id}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function normalizeExpectedValue(rawValue) {
|
|
353
|
+
const trimmed = rawValue.trim();
|
|
354
|
+
if (trimmed === "true") {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
if (trimmed === "false") {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
361
|
+
return Number(trimmed);
|
|
362
|
+
}
|
|
363
|
+
return trimmed.replace(/^["']|["']$/g, "");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function evaluateRuleCondition(condition, result) {
|
|
367
|
+
const match = condition.match(/^([A-Za-z0-9_.]+)\s*==\s*(.+)$/);
|
|
368
|
+
if (!match) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
const actual = valueAtPath(result, match[1]);
|
|
372
|
+
const expected = normalizeExpectedValue(match[2]);
|
|
373
|
+
return actual === expected;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function evaluateCapabilityDecision(capability, result, validationErrors) {
|
|
377
|
+
const missingEvidence = (capability.evidenceContract?.requiredFields ?? [])
|
|
378
|
+
.filter((field) => valueAtPath(result, field) === undefined);
|
|
379
|
+
const matchedRules = (capability.blockingRules ?? [])
|
|
380
|
+
.filter((rule) => evaluateRuleCondition(rule.when, result));
|
|
381
|
+
|
|
382
|
+
if (validationErrors.length > 0 || missingEvidence.length > 0) {
|
|
383
|
+
return {
|
|
384
|
+
action: "hold",
|
|
385
|
+
severity: "P1",
|
|
386
|
+
matchedRules,
|
|
387
|
+
missingEvidence,
|
|
388
|
+
validationErrors
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (matchedRules.length === 0) {
|
|
393
|
+
return {
|
|
394
|
+
action: "proceed",
|
|
395
|
+
severity: "none",
|
|
396
|
+
matchedRules,
|
|
397
|
+
missingEvidence,
|
|
398
|
+
validationErrors
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const actionRank = { block: 4, human_escalation: 3, hold: 2, revise: 1 };
|
|
403
|
+
const severityRank = { P0: 3, P1: 2, P2: 1 };
|
|
404
|
+
const sorted = [...matchedRules].sort((left, right) => {
|
|
405
|
+
const severityDiff = (severityRank[right.severity] ?? 0) - (severityRank[left.severity] ?? 0);
|
|
406
|
+
if (severityDiff !== 0) {
|
|
407
|
+
return severityDiff;
|
|
408
|
+
}
|
|
409
|
+
return (actionRank[right.action] ?? 0) - (actionRank[left.action] ?? 0);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
action: sorted[0].action,
|
|
414
|
+
severity: sorted[0].severity,
|
|
415
|
+
matchedRules,
|
|
416
|
+
missingEvidence,
|
|
417
|
+
validationErrors
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function defaultAdapterCwd(adapter, options) {
|
|
422
|
+
if (options.mcpCwd) {
|
|
423
|
+
return options.mcpCwd;
|
|
424
|
+
}
|
|
425
|
+
if (adapter.cwdEnv && process.env[adapter.cwdEnv]) {
|
|
426
|
+
return path.resolve(process.env[adapter.cwdEnv]);
|
|
427
|
+
}
|
|
428
|
+
if (adapter.defaultSiblingDir) {
|
|
429
|
+
return path.resolve(repoRoot, "..", adapter.defaultSiblingDir);
|
|
430
|
+
}
|
|
431
|
+
return repoRoot;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function resolveCapabilityAdapter(adapter, capabilityId, options) {
|
|
435
|
+
return {
|
|
436
|
+
command: options.mcpCommand || adapter.command,
|
|
437
|
+
args: options.mcpArgs.length > 0 ? options.mcpArgs : adapter.args ?? [],
|
|
438
|
+
cwd: defaultAdapterCwd(adapter, options),
|
|
439
|
+
toolName: adapter.toolNameMap?.[capabilityId] ?? capabilityId.split(".").at(-1),
|
|
440
|
+
timeoutMs: Number(options.timeoutMs || adapter.timeoutMs || 30000)
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function callMcpStdio({ command, args, cwd, toolName, input, timeoutMs }) {
|
|
445
|
+
return new Promise((resolve, reject) => {
|
|
446
|
+
const child = spawn(command, args, {
|
|
447
|
+
cwd,
|
|
448
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
449
|
+
windowsHide: true
|
|
450
|
+
});
|
|
451
|
+
let stdout = "";
|
|
452
|
+
let stderr = "";
|
|
453
|
+
let settled = false;
|
|
454
|
+
|
|
455
|
+
function settle(callback, value) {
|
|
456
|
+
if (settled) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
settled = true;
|
|
460
|
+
clearTimeout(timer);
|
|
461
|
+
callback(value);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const timer = setTimeout(() => {
|
|
465
|
+
child.kill();
|
|
466
|
+
settle(reject, new Error(`MCP call timed out after ${timeoutMs}ms`));
|
|
467
|
+
}, timeoutMs);
|
|
468
|
+
|
|
469
|
+
child.stdout.on("data", (chunk) => {
|
|
470
|
+
stdout += chunk.toString("utf8");
|
|
471
|
+
});
|
|
472
|
+
child.stderr.on("data", (chunk) => {
|
|
473
|
+
stderr += chunk.toString("utf8");
|
|
474
|
+
});
|
|
475
|
+
child.on("error", (error) => {
|
|
476
|
+
settle(reject, error);
|
|
477
|
+
});
|
|
478
|
+
child.on("close", (exitCode) => {
|
|
479
|
+
if (exitCode !== 0) {
|
|
480
|
+
settle(reject, new Error(`MCP server exited with ${exitCode}: ${stderr.trim()}`));
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
const responses = stdout
|
|
486
|
+
.split(/\r?\n/)
|
|
487
|
+
.filter((line) => line.trim().length > 0)
|
|
488
|
+
.map((line) => JSON.parse(line));
|
|
489
|
+
const callResponse = responses.find((response) => response.id === 2);
|
|
490
|
+
if (!callResponse) {
|
|
491
|
+
throw new Error("MCP tools/call response was not returned");
|
|
492
|
+
}
|
|
493
|
+
if (callResponse.error) {
|
|
494
|
+
throw new Error(`MCP tools/call failed: ${callResponse.error.message}`);
|
|
495
|
+
}
|
|
496
|
+
settle(resolve, {
|
|
497
|
+
initialize: responses.find((response) => response.id === 1)?.result,
|
|
498
|
+
call: callResponse.result,
|
|
499
|
+
stderr
|
|
500
|
+
});
|
|
501
|
+
} catch (error) {
|
|
502
|
+
settle(reject, new Error(`${error.message}. stdout: ${stdout.trim()}`));
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const initialize = {
|
|
507
|
+
jsonrpc: "2.0",
|
|
508
|
+
id: 1,
|
|
509
|
+
method: "initialize",
|
|
510
|
+
params: {
|
|
511
|
+
protocolVersion: "2025-06-18",
|
|
512
|
+
capabilities: {},
|
|
513
|
+
clientInfo: { name: "archsight-aios", version: "1.0.1" }
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
const callTool = {
|
|
517
|
+
jsonrpc: "2.0",
|
|
518
|
+
id: 2,
|
|
519
|
+
method: "tools/call",
|
|
520
|
+
params: {
|
|
521
|
+
name: toolName,
|
|
522
|
+
arguments: input
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
child.stdin.write(`${JSON.stringify(initialize)}\n`);
|
|
526
|
+
child.stdin.write(`${JSON.stringify(callTool)}\n`);
|
|
527
|
+
child.stdin.end();
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function capabilityCall(options) {
|
|
532
|
+
if (!options.capability) {
|
|
533
|
+
throw new Error("--capability is required");
|
|
534
|
+
}
|
|
535
|
+
if (!options.input) {
|
|
536
|
+
throw new Error("--input is required");
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const registry = await readCapabilityRegistry();
|
|
540
|
+
const capability = findCapability(registry, options.capability);
|
|
541
|
+
if (!capability) {
|
|
542
|
+
throw new Error(`Unknown Capability: ${options.capability}`);
|
|
543
|
+
}
|
|
544
|
+
authorizeCapability(capability, options);
|
|
545
|
+
|
|
546
|
+
const adapters = await readCapabilityAdapters();
|
|
547
|
+
const adapter = findCapabilityAdapter(adapters, capability.id);
|
|
548
|
+
if (!adapter) {
|
|
549
|
+
throw new Error(`No local adapter registered for Capability: ${capability.id}`);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const input = await readJson(options.input);
|
|
553
|
+
const inputErrors = validateJsonSchemaSubset(capability.inputSchema, input, "$.input");
|
|
554
|
+
if (inputErrors.length > 0) {
|
|
555
|
+
throw new Error(`Capability input validation failed: ${inputErrors.join("; ")}`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const resolvedAdapter = resolveCapabilityAdapter(adapter, capability.id, options);
|
|
559
|
+
if (!resolvedAdapter.command) {
|
|
560
|
+
throw new Error(`Capability adapter ${adapter.id} does not define a command`);
|
|
561
|
+
}
|
|
562
|
+
if (!(await exists(resolvedAdapter.cwd))) {
|
|
563
|
+
throw new Error(`MCP adapter cwd not found: ${resolvedAdapter.cwd}`);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const mcp = await callMcpStdio({
|
|
567
|
+
command: resolvedAdapter.command,
|
|
568
|
+
args: resolvedAdapter.args,
|
|
569
|
+
cwd: resolvedAdapter.cwd,
|
|
570
|
+
toolName: resolvedAdapter.toolName,
|
|
571
|
+
input,
|
|
572
|
+
timeoutMs: resolvedAdapter.timeoutMs
|
|
573
|
+
});
|
|
574
|
+
const structuredContent = mcp.call?.structuredContent;
|
|
575
|
+
if (!structuredContent || typeof structuredContent !== "object") {
|
|
576
|
+
throw new Error("MCP tool result did not include structuredContent");
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const outputErrors = validateJsonSchemaSubset(capability.outputSchema, structuredContent, "$.toolResult");
|
|
580
|
+
const decision = evaluateCapabilityDecision(capability, structuredContent, outputErrors);
|
|
581
|
+
const envelope = {
|
|
582
|
+
schema: 1,
|
|
583
|
+
capabilityId: capability.id,
|
|
584
|
+
agent: options.agent,
|
|
585
|
+
skill: options.skill,
|
|
586
|
+
authorized: true,
|
|
587
|
+
inputValidated: true,
|
|
588
|
+
adapter: {
|
|
589
|
+
id: adapter.id,
|
|
590
|
+
transport: adapter.transport,
|
|
591
|
+
cwd: resolvedAdapter.cwd,
|
|
592
|
+
command: resolvedAdapter.command,
|
|
593
|
+
args: resolvedAdapter.args,
|
|
594
|
+
toolName: resolvedAdapter.toolName
|
|
595
|
+
},
|
|
596
|
+
toolResult: structuredContent,
|
|
597
|
+
decision,
|
|
598
|
+
evidence: {
|
|
599
|
+
level: capability.authorityLevel,
|
|
600
|
+
source: "mcp.structuredContent",
|
|
601
|
+
serverInfo: mcp.initialize?.serverInfo
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
606
|
+
if (["block", "hold", "human_escalation"].includes(decision.action)) {
|
|
607
|
+
process.exitCode = 2;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
216
611
|
function routeAgentName(manifest, agentId) {
|
|
217
612
|
return manifest.agents.find((agent) => agent.id === agentId)?.displayName ?? agentId;
|
|
218
613
|
}
|
|
@@ -624,6 +1019,44 @@ async function doctor() {
|
|
|
624
1019
|
await check("hermes sync record template", path.join(repoRoot, manifest.hermes.syncRecordTemplatePath));
|
|
625
1020
|
}
|
|
626
1021
|
|
|
1022
|
+
if (manifest.capabilityRegistry) {
|
|
1023
|
+
const registryPath = path.join(repoRoot, manifest.capabilityRegistry.registryPath);
|
|
1024
|
+
await check("capability registry schema", path.join(repoRoot, manifest.capabilityRegistry.schemaPath));
|
|
1025
|
+
await check("capability registry", registryPath);
|
|
1026
|
+
if (manifest.capabilityRegistry.adapterPath) {
|
|
1027
|
+
await check("capability adapters", path.join(repoRoot, manifest.capabilityRegistry.adapterPath));
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const registry = await readJson(registryPath);
|
|
1031
|
+
checkCondition("capability registry schema version", registry.schema === 1, "schema === 1");
|
|
1032
|
+
checkCondition("capability registry has capabilities", registry.capabilities?.length > 0, "capabilities.length > 0");
|
|
1033
|
+
|
|
1034
|
+
const capabilityIds = new Set();
|
|
1035
|
+
for (const capability of registry.capabilities ?? []) {
|
|
1036
|
+
checkCondition(`capability id unique ${capability.id}`, !capabilityIds.has(capability.id), capability.id);
|
|
1037
|
+
capabilityIds.add(capability.id);
|
|
1038
|
+
checkCondition(`capability authority ${capability.id}`, ["L1", "L2", "L3"].includes(capability.authorityLevel), capability.authorityLevel);
|
|
1039
|
+
checkCondition(`capability has blocking rules ${capability.id}`, capability.blockingRules?.length > 0, "blockingRules.length > 0");
|
|
1040
|
+
for (const agentId of capability.ownerAgents ?? []) {
|
|
1041
|
+
checkCondition(`capability owner exists ${capability.id}/${agentId}`, agentIds.has(agentId), agentId);
|
|
1042
|
+
}
|
|
1043
|
+
for (const skillId of capability.allowedSkills ?? []) {
|
|
1044
|
+
checkCondition(`capability skill exists ${capability.id}/${skillId}`, skillIds.has(skillId), skillId);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
if (manifest.capabilityRegistry.adapterPath) {
|
|
1049
|
+
const adapters = await readJson(path.join(repoRoot, manifest.capabilityRegistry.adapterPath));
|
|
1050
|
+
checkCondition("capability adapters schema version", adapters.schema === 1, "schema === 1");
|
|
1051
|
+
for (const adapter of adapters.adapters ?? []) {
|
|
1052
|
+
checkCondition(`capability adapter transport ${adapter.id}`, adapter.transport === "stdio-mcp", adapter.transport);
|
|
1053
|
+
for (const capabilityId of adapter.capabilityIds ?? []) {
|
|
1054
|
+
checkCondition(`capability adapter target exists ${adapter.id}/${capabilityId}`, capabilityIds.has(capabilityId), capabilityId);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
627
1060
|
await check("gemini support assets", geminiRoot);
|
|
628
1061
|
await check("gemini support skills", path.join(geminiRoot, "skills"));
|
|
629
1062
|
await check("gemini support workflows", path.join(geminiRoot, "workflows"));
|
|
@@ -942,6 +1375,8 @@ async function main() {
|
|
|
942
1375
|
await initProject(options);
|
|
943
1376
|
} else if (options.command === "validate") {
|
|
944
1377
|
await validateProjectTemplate(options);
|
|
1378
|
+
} else if (options.command === "capability:call") {
|
|
1379
|
+
await capabilityCall(options);
|
|
945
1380
|
} else if (options.command === "hermes:validate") {
|
|
946
1381
|
await validateHermesRegistry();
|
|
947
1382
|
} else if (options.command === "hermes:sync-dry-run") {
|
package/docs/quickstart.md
CHANGED
package/governance/README.md
CHANGED
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
- [AI Review Policy](ai-review-policy.md)
|
|
9
9
|
- [Security Policy](security-policy.md)
|
|
10
10
|
- [Agent Boundary Policy](agent-boundary.md)
|
|
11
|
+
- [Capability-Backed Arbitration Protocol](arbitration-protocol.md)
|
|
11
12
|
- [Delivery Policy](delivery-policy.md)
|
|
12
13
|
- [Context Policy](context-policy.md)
|
|
13
14
|
- [Memory Policy](memory-policy.md)
|
|
14
15
|
|
|
15
16
|
治理目标是防止 agent 乱调用、prompt 泄露、上下文爆炸、AI 瞎改代码、权限失控和未经评审的自动交付。
|
|
17
|
+
|
|
18
|
+
多 Agent 产生逻辑冲突时,优先按 `arbitration-protocol.md` 的证据等级处理:确定性工具、项目事实和结构化知识优先于 Agent 自然语言判断;涉及生产授权、法规合规最终结论、结构安全结论和商业范围取舍时升级给人类负责人。
|