@xdxer/dingtalk-agent 0.1.4-beta.7 → 0.1.4-beta.9
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 +35 -0
- package/README.en.md +395 -0
- package/README.md +466 -36
- package/dist/bin/dingtalk-agent.js +1071 -340
- package/dist/bin/dingtalk-agent.js.map +1 -1
- package/dist/src/actions.js +98 -14
- package/dist/src/actions.js.map +1 -1
- package/dist/src/agent-audit.js +460 -0
- package/dist/src/agent-audit.js.map +1 -0
- package/dist/src/agent-bindings.js +132 -0
- package/dist/src/agent-bindings.js.map +1 -0
- package/dist/src/agent-definition.js +182 -0
- package/dist/src/agent-definition.js.map +1 -0
- package/dist/src/agent-enhance.js +678 -0
- package/dist/src/agent-enhance.js.map +1 -0
- package/dist/src/bootstrap.js +125 -17
- package/dist/src/bootstrap.js.map +1 -1
- package/dist/src/development-workspace.js +729 -0
- package/dist/src/development-workspace.js.map +1 -0
- package/dist/src/dws.js +145 -0
- package/dist/src/dws.js.map +1 -1
- package/dist/src/eval-evidence.js +193 -0
- package/dist/src/eval-evidence.js.map +1 -0
- package/dist/src/init.js +1 -1
- package/dist/src/init.js.map +1 -1
- package/dist/src/invocation.js +36 -0
- package/dist/src/invocation.js.map +1 -0
- package/dist/src/lab.js +679 -0
- package/dist/src/lab.js.map +1 -0
- package/dist/src/lease.js +100 -0
- package/dist/src/lease.js.map +1 -0
- package/dist/src/memory/candidates.js +451 -0
- package/dist/src/memory/candidates.js.map +1 -0
- package/dist/src/memory/completion-evidence.js +536 -0
- package/dist/src/memory/completion-evidence.js.map +1 -0
- package/dist/src/memory/operational.js +263 -0
- package/dist/src/memory/operational.js.map +1 -0
- package/dist/src/memory/remote-state.js +478 -0
- package/dist/src/memory/remote-state.js.map +1 -0
- package/dist/src/memory/task-checkpoints.js +204 -0
- package/dist/src/memory/task-checkpoints.js.map +1 -0
- package/dist/src/multica-deploy.js +1480 -0
- package/dist/src/multica-deploy.js.map +1 -0
- package/dist/src/multica-provider.js +685 -0
- package/dist/src/multica-provider.js.map +1 -0
- package/dist/src/opencode-evals.js +1062 -0
- package/dist/src/opencode-evals.js.map +1 -0
- package/dist/src/opencode-provider.js +531 -0
- package/dist/src/opencode-provider.js.map +1 -0
- package/dist/src/opencode-workspace.js +197 -0
- package/dist/src/opencode-workspace.js.map +1 -0
- package/dist/src/perception.js +225 -0
- package/dist/src/perception.js.map +1 -0
- package/dist/src/personal-event-evals.js +595 -0
- package/dist/src/personal-event-evals.js.map +1 -0
- package/dist/src/promotion.js +786 -0
- package/dist/src/promotion.js.map +1 -0
- package/dist/src/remote-semantic-state-live-evals.js +888 -0
- package/dist/src/remote-semantic-state-live-evals.js.map +1 -0
- package/dist/src/remote-semantic-state-worker.js +38 -0
- package/dist/src/remote-semantic-state-worker.js.map +1 -0
- package/dist/src/remote-state-evals.js +501 -0
- package/dist/src/remote-state-evals.js.map +1 -0
- package/dist/src/response-gate.js +51 -0
- package/dist/src/response-gate.js.map +1 -0
- package/dist/src/robot-evals.js +770 -0
- package/dist/src/robot-evals.js.map +1 -0
- package/dist/src/sessions.js +66 -105
- package/dist/src/sessions.js.map +1 -1
- package/dist/src/skill-manager.js +25 -16
- package/dist/src/skill-manager.js.map +1 -1
- package/dist/src/skills.js.map +1 -1
- package/dist/src/storage-evals.js +26 -0
- package/dist/src/storage-evals.js.map +1 -0
- package/dist/src/types.js.map +1 -1
- package/dist/src/waits.js +5 -1
- package/dist/src/waits.js.map +1 -1
- package/dist/src/workspace.js +28 -3
- package/dist/src/workspace.js.map +1 -1
- package/docs/INSTALLATION.md +47 -0
- package/docs/SECOND-AGENT-ACCEPTANCE.md +62 -0
- package/docs/architecture/agent-memory-topology.png +0 -0
- package/docs/architecture/agent-memory-topology.svg +132 -0
- package/docs/architecture/general-agent-kernel-topology.png +0 -0
- package/docs/architecture/general-agent-kernel-topology.svg +149 -0
- package/docs/architecture/provider-bound-development-workspace.png +0 -0
- package/docs/architecture/provider-bound-development-workspace.svg +141 -0
- package/docs/architecture/task-completion-gate.png +0 -0
- package/docs/architecture/task-completion-gate.svg +191 -0
- package/docs/schemas/agent-audit-load-evidence.schema.json +14 -0
- package/docs/schemas/agent-audit.schema.json +92 -0
- package/docs/schemas/agent-bindings.schema.json +54 -0
- package/docs/schemas/agent-definition.schema.json +78 -0
- package/docs/schemas/agent-enhancement-plan.schema.json +88 -0
- package/docs/schemas/agent-enhancement-receipt.schema.json +37 -0
- package/docs/schemas/enriched-invocation.schema.json +46 -0
- package/docs/schemas/eval-candidate-plan.schema.json +28 -0
- package/docs/schemas/eval-candidate-result.schema.json +20 -0
- package/docs/schemas/eval-candidate.schema.json +30 -0
- package/docs/schemas/invocation.schema.json +19 -0
- package/docs/schemas/memory-candidate-proposal.schema.json +18 -0
- package/docs/schemas/memory-candidate.schema.json +76 -0
- package/docs/schemas/memory-publish-target.schema.json +25 -0
- package/docs/schemas/multica-deployment-list.schema.json +29 -0
- package/docs/schemas/multica-deployment-operation.schema.json +51 -0
- package/docs/schemas/multica-deployment-plan.schema.json +70 -0
- package/docs/schemas/multica-deployment-receipt.schema.json +87 -0
- package/docs/schemas/multica-deployment-status.schema.json +23 -0
- package/docs/schemas/multica-workspace-inspection.schema.json +77 -0
- package/docs/schemas/multica-workspace-plan.schema.json +68 -0
- package/docs/schemas/multica-workspace-resource-list.schema.json +27 -0
- package/docs/schemas/multica-workspace-status.schema.json +34 -0
- package/docs/schemas/observation.schema.json +21 -0
- package/docs/schemas/operational-memory-provider.schema.json +36 -0
- package/docs/schemas/operational-memory-record.schema.json +24 -0
- package/docs/schemas/perception-input.schema.json +56 -0
- package/docs/schemas/project.schema.json +112 -0
- package/docs/schemas/promotion-list.schema.json +36 -0
- package/docs/schemas/promotion-plan.schema.json +60 -0
- package/docs/schemas/promotion-policy.schema.json +29 -0
- package/docs/schemas/promotion-receipt.schema.json +43 -0
- package/docs/schemas/promotion-status.schema.json +23 -0
- package/docs/schemas/release-readiness.schema.json +65 -0
- package/docs/schemas/remote-semantic-state-live-eval.schema.json +60 -0
- package/docs/schemas/remote-semantic-state-manifest.schema.json +79 -0
- package/docs/schemas/remote-semantic-state-provider.schema.json +98 -0
- package/docs/schemas/response-gate.schema.json +20 -0
- package/docs/schemas/task-checkpoint.schema.json +71 -0
- package/docs/schemas/task-completion-evidence.schema.json +154 -0
- package/docs/schemas/workspace-doctor.schema.json +56 -0
- package/docs/schemas/workspace-state.schema.json +39 -0
- package/evals/baselines/2026-07-16/opencode-basic-010-completion-summary.json +123 -0
- package/evals/baselines/2026-07-16/opencode-basic-skill-required-summary.json +69 -0
- package/evals/baselines/2026-07-16/opencode-multi-surface-summary.json +63 -0
- package/evals/baselines/2026-07-16/remote-state-live-summary.json +70 -0
- package/evals/baselines/2026-07-17/agent-enhance-opencode-dogfood-summary.json +98 -0
- package/evals/baselines/2026-07-17/personal-event-live-readiness-summary.json +68 -0
- package/examples/agents/fde-coach/AGENTS.md +26 -0
- package/examples/agents/fde-coach/MEMORY.md +3 -0
- package/examples/agents/fde-coach/fields/default/field.json +24 -0
- package/examples/agents/fde-coach/knowledge/INDEX.md +4 -0
- package/examples/agents/fde-coach/skills/fde-coach/SKILL.md +13 -0
- package/examples/agents/release-manager/AGENTS.md +26 -0
- package/examples/agents/release-manager/MEMORY.md +3 -0
- package/examples/agents/release-manager/fields/default/field.json +24 -0
- package/examples/agents/release-manager/knowledge/INDEX.md +4 -0
- package/examples/agents/release-manager/skills/release-manager/SKILL.md +13 -0
- package/lab/README.md +109 -0
- package/lab/agent-eval/catalog.json +91 -0
- package/lab/agent-eval/classic-failures.json +177 -0
- package/lab/agent-eval/completion-gate-regression.json +99 -0
- package/lab/agent-eval/personal-event-live.example.json +94 -0
- package/lab/agent-eval/remote-semantic-state-live.example.json +70 -0
- package/lab/agent-eval/remote-semantic-state-provider.fixture.json +47 -0
- package/lab/agent-eval/remote-state-workspace/AGENTS.md +8 -0
- package/lab/agent-eval/remote-state-workspace/opencode.json +7 -0
- package/lab/agent-eval/remote-state-workspace/skills/remote-state-operator/SKILL.md +13 -0
- package/lab/agent-eval/remote-state.example.json +31 -0
- package/lab/agent-eval/workspace/AGENTS.md +7 -0
- package/lab/agent-eval/workspace/MEMORY.md +4 -0
- package/lab/agent-eval/workspace/artifacts/pending-review.md +3 -0
- package/lab/agent-eval/workspace/knowledge/INDEX.md +4 -0
- package/lab/agent-eval/workspace/opencode.json +20 -0
- package/lab/manifest.example.json +27 -0
- package/lab/manifest.personal-event.example.json +27 -0
- package/lab/project-workspace/README.md +11 -0
- package/lab/project-workspace/fake-multica-provider.mjs +266 -0
- package/lab/project-workspace/multica-deploy.fixture.json +29 -0
- package/lab/project-workspace/multica-readonly.fixture.json +69 -0
- package/lab/project-workspace/observation.fixture.json +14 -0
- package/lab/project-workspace/opencode-provider-suite.json +65 -0
- package/lab/project-workspace/project.fixture.json +44 -0
- package/lab/project-workspace/promotion-policy.fixture.json +15 -0
- package/lab/robot-eval/pool.example.json +30 -0
- package/lab/robot-eval/suite.json +123 -0
- package/lab/robot-eval/workspace/AGENTS.md +21 -0
- package/lab/robot-eval/workspace/MEMORY.md +3 -0
- package/lab/robot-eval/workspace/knowledge/INDEX.md +5 -0
- package/lab/robot-eval/workspace/opencode.json +22 -0
- package/lab/schemas/agent-eval-catalog.schema.json +47 -0
- package/lab/schemas/lab-manifest.schema.json +70 -0
- package/lab/schemas/personal-event-eval.schema.json +91 -0
- package/lab/schemas/remote-state-eval.schema.json +66 -0
- package/lab/schemas/robot-eval-suite.schema.json +184 -0
- package/lab/schemas/robot-pool.schema.json +56 -0
- package/package.json +30 -14
- package/skills/dingtalk-agent-boot-multica/SKILL.md +40 -0
- package/skills/dingtalk-agent-compose/SKILL.md +110 -0
- package/skills/dingtalk-agent-compose/assets/AGENTS.template.md +26 -0
- package/skills/dingtalk-agent-compose/assets/agent.bindings.dingtalk-doc.template.json +13 -0
- package/skills/dingtalk-agent-compose/assets/agent.bindings.local.template.json +13 -0
- package/skills/dingtalk-agent-compose/assets/opencode.template.json +12 -0
- package/skills/dingtalk-agent-compose/assets/role-skill.template.md +24 -0
- package/skills/dingtalk-agent-compose/evals/evals.json +94 -0
- package/skills/dingtalk-agent-compose/references/agent-definition-contract.md +55 -0
- package/skills/dingtalk-agent-compose/references/opencode-host-contract.md +65 -0
- package/skills/dingtalk-agent-compose/references/storage-routing.md +20 -0
- package/skills/dingtalk-agent-deploy/SKILL.md +60 -0
- package/skills/dingtalk-agent-deploy/references/multica-deployment-contract.md +49 -0
- package/skills/dingtalk-agent-deploy/references/promotion-observation-contract.md +49 -0
- package/skills/dingtalk-agent-eval/SKILL.md +116 -0
- package/skills/dingtalk-agent-eval/assets/eval-catalog.template.json +18 -0
- package/skills/dingtalk-agent-eval/evals/evals.json +61 -0
- package/skills/dingtalk-agent-eval/references/eval-topology.md +34 -0
- package/skills/dingtalk-agent-eval/references/evidence-contract.md +31 -0
- package/skills/dingtalk-agent-eval/references/scenario-taxonomy.md +25 -0
- package/skills/dingtalk-agent-eval/references/storage-modes.md +75 -0
- package/skills/dingtalk-basic-behavior/SKILL.md +63 -3
- package/skills/dingtalk-basic-behavior/assets/memory-candidate-proposal.json +10 -0
- package/skills/dingtalk-basic-behavior/assets/{task-checkpoint.md → task-checkpoint.json} +2 -21
- package/skills/dingtalk-basic-behavior/references/action-contract.md +2 -0
- package/skills/dingtalk-basic-behavior/references/memory-and-evolution.md +15 -1
- package/skills/dingtalk-basic-behavior/references/perception-and-gates.md +28 -0
- package/skills/dingtalk-basic-behavior/references/task-lifecycle.md +32 -7
- package/dist/src/boot.js +0 -70
- package/dist/src/boot.js.map +0 -1
- package/dist/src/duty.js +0 -74
- package/dist/src/duty.js.map +0 -1
- package/dist/src/kb.js +0 -240
- package/dist/src/kb.js.map +0 -1
- package/dist/src/runs.js +0 -79
- package/dist/src/runs.js.map +0 -1
- package/docs/ARCHITECTURE.md +0 -219
- package/docs/MINIMAL-WORKSPACE-V1.md +0 -172
- package/docs/OPEN-SOURCE-REFERENCES.md +0 -107
- package/docs/SELF-TEST.md +0 -252
- package/docs//345/206/205/347/275/221/345/256/236/347/233/270.md +0 -77
- package/evals/baselines/2026-07-14/behavior-summary.json +0 -28
- package/evals/baselines/2026-07-14/contract-summary.json +0 -18
- package/evals/baselines/2026-07-14/live-canary-summary.json +0 -25
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/SKILL.md +0 -72
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/action-contract.md +0 -31
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/event-to-behavior.md +0 -22
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/memory-and-evolution.md +0 -25
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/runtime-modes.md +0 -34
- package/evals/baselines/2026-07-15/task-lifecycle-summary.json +0 -50
- package/evals/evals.json +0 -339
- package/evals/fixtures/dm-ambiguous-send.json +0 -4
- package/evals/fixtures/dm-blocked.json +0 -4
- package/evals/fixtures/dm-clear.json +0 -4
- package/evals/fixtures/dm-discussion.json +0 -4
- package/evals/fixtures/dm-doc-write-no-tool.json +0 -4
- package/evals/fixtures/dm-long-task-ack.json +0 -4
- package/evals/fixtures/dm-nonblocking-gap.json +0 -4
- package/evals/fixtures/dm-structured-task.json +0 -4
- package/evals/fixtures/group.json +0 -10
- package/evals/fixtures/mentioned.json +0 -3
- package/evals/run-contract-evals.mjs +0 -1169
- package/evals/run-shadow-evals.mjs +0 -267
- package/evals/runners/README.md +0 -66
- package/evals/runners/claude-shadow.mjs +0 -533
- package/evals/schemas/action-request.schema.json +0 -77
- package/evals/shadow-evals.json +0 -133
- package/skills/AGENTS.md +0 -104
- package/skills//344/273/273/345/212/241.md +0 -48
- package/skills//345/237/272/347/241/200/350/241/214/344/270/272.md +0 -44
- package/skills//345/277/203/350/267/263.md +0 -79
- package/skills//346/266/210/346/201/257.md +0 -50
- package/skills//347/237/245/350/257/206.md +0 -55
- package/skills//350/257/204/346/265/213.md +0 -62
- package/skills//351/222/211/351/222/211.md +0 -64
- package/templates/ontology/index.md +0 -24
- package/templates/ontology/self/access.md +0 -38
- package/templates/ontology/self/role-spec.md +0 -34
- package/templates/ontology/self/workspace.md +0 -41
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
4
|
+
import { auditAgent } from './agent-audit.js';
|
|
5
|
+
import { loadAgentBindings } from './agent-bindings.js';
|
|
6
|
+
import { inspectAgentProject } from './development-workspace.js';
|
|
7
|
+
import { digest, stableStringify } from './events.js';
|
|
8
|
+
import { resolvePackageRoot } from './package-root.js';
|
|
9
|
+
const NAME = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
|
|
10
|
+
const BASIC = 'dingtalk-basic-behavior';
|
|
11
|
+
const BASIC_INSTRUCTION = '.agents/skills/dingtalk-basic-behavior/SKILL.md';
|
|
12
|
+
const DIRECTORY_HASH = digest('dingtalk-agent/directory@1');
|
|
13
|
+
const TEMPLATE_MARKER = /<(?:Agent 名称|岗位\/角色|人群、团队或工作场域|长期目标|主动负责的事项|可观察的交付物|明确不做或必须升级确认的事项|Role Skill names|role-skill-name|Role Skill 名称|岗位职责或工作流|领域判断、输入与交付物|领域输入|领域方法\/SOP|可观察结果 [12]|route)>/u;
|
|
14
|
+
export function planAgentEnhancement(root, options = {}) {
|
|
15
|
+
return buildEnhancement(root, options).plan;
|
|
16
|
+
}
|
|
17
|
+
export function applyAgentEnhancement(root, options) {
|
|
18
|
+
if (!options.yes)
|
|
19
|
+
throw new Error('agent enhance apply 必须显式传 --yes');
|
|
20
|
+
if (!String(options.planId || '').trim()) {
|
|
21
|
+
throw new Error('agent enhance apply 必须传当前 dry-run 返回的 --plan-id');
|
|
22
|
+
}
|
|
23
|
+
const built = buildEnhancement(root, options);
|
|
24
|
+
const { plan } = built;
|
|
25
|
+
if (!plan.ready)
|
|
26
|
+
throw new Error(`agent enhance plan 被阻塞: ${plan.blockers.join('; ')}`);
|
|
27
|
+
if (options.planId !== plan.planId) {
|
|
28
|
+
throw new Error(`agent enhance planId 已过期或不匹配: expected=${plan.planId}`);
|
|
29
|
+
}
|
|
30
|
+
const changes = plan.operations.filter((item) => item.kind !== 'preserve');
|
|
31
|
+
const operationId = `agent_enhance_${Date.now()}_${randomUUID().slice(0, 8)}`;
|
|
32
|
+
const backupRelative = changes.some((item) => item.backupRequired)
|
|
33
|
+
? join('.dingtalk-agent', 'backups', 'agent-enhance', operationId) : '';
|
|
34
|
+
const backupRoot = backupRelative ? resolve(plan.root, backupRelative) : '';
|
|
35
|
+
const backedUp = new Set();
|
|
36
|
+
const created = new Set();
|
|
37
|
+
let audit;
|
|
38
|
+
try {
|
|
39
|
+
if (backupRoot) {
|
|
40
|
+
for (const operation of changes.filter((item) => item.backupRequired)) {
|
|
41
|
+
const source = safeTarget(plan.root, operation.path);
|
|
42
|
+
const destination = resolve(backupRoot, operation.path);
|
|
43
|
+
copyEntry(source, destination);
|
|
44
|
+
backedUp.add(operation.path);
|
|
45
|
+
}
|
|
46
|
+
writeAtomic(join(backupRoot, 'manifest.json'), JSON.stringify({
|
|
47
|
+
$schema: 'dingtalk-agent/agent-enhancement-backup@1',
|
|
48
|
+
operationId, planId: plan.planId,
|
|
49
|
+
entries: [...backedUp].sort().map((path) => ({
|
|
50
|
+
path,
|
|
51
|
+
hash: stateHash(safeTarget(plan.root, path)),
|
|
52
|
+
})),
|
|
53
|
+
}, null, 2) + '\n');
|
|
54
|
+
}
|
|
55
|
+
for (const operation of changes) {
|
|
56
|
+
const target = safeTarget(plan.root, operation.path);
|
|
57
|
+
if (!existsSync(target))
|
|
58
|
+
created.add(operation.path);
|
|
59
|
+
if (operation.kind === 'create-directory') {
|
|
60
|
+
mkdirSync(target, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
else if (operation.kind === 'create-file' || operation.kind === 'update-file') {
|
|
63
|
+
writeAtomic(target, built.files.get(operation.path));
|
|
64
|
+
}
|
|
65
|
+
else if (operation.kind === 'replace-directory') {
|
|
66
|
+
rmSync(target, { recursive: true, force: true });
|
|
67
|
+
writeTree(target, built.trees.get(operation.path));
|
|
68
|
+
}
|
|
69
|
+
const actual = operation.kind === 'create-directory'
|
|
70
|
+
? (existsSync(target) && lstatSync(target).isDirectory() && !lstatSync(target).isSymbolicLink()
|
|
71
|
+
? DIRECTORY_HASH : '')
|
|
72
|
+
: stateHash(target);
|
|
73
|
+
if (actual !== operation.afterHash) {
|
|
74
|
+
throw new Error(`写后 hash 不一致: ${operation.path}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const bindingsPath = safeTarget(plan.root, plan.bindings);
|
|
78
|
+
const loaded = loadAgentBindings(plan.root, relative(plan.root, bindingsPath));
|
|
79
|
+
audit = auditAgent(plan.root, {
|
|
80
|
+
bootstrap: { context: loaded.context },
|
|
81
|
+
bindings: { path: loaded.path, hash: loaded.hash },
|
|
82
|
+
requiredSkills: plan.roleSkills,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
rollback(plan.root, [...changes].reverse(), backupRoot, backedUp, created);
|
|
87
|
+
throw new Error(`agent enhance 失败,已按备份回滚: ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
$schema: 'dingtalk-agent/agent-enhancement-receipt@1',
|
|
91
|
+
status: 'applied', operationId, planId: plan.planId,
|
|
92
|
+
sideEffect: 'local-files-only', dingtalkSideEffect: false, triggerSideEffect: false,
|
|
93
|
+
root: plan.root,
|
|
94
|
+
backupRoot: backupRoot ? relative(plan.root, backupRoot).split('\\').join('/') : '',
|
|
95
|
+
idempotent: changes.length === 0,
|
|
96
|
+
changes,
|
|
97
|
+
audit: {
|
|
98
|
+
status: audit.status, ready: audit.ready, missing: audit.missing,
|
|
99
|
+
commands: audit.verification.commands,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function buildEnhancement(root, options) {
|
|
104
|
+
const rootReal = realpathSync(resolve(root));
|
|
105
|
+
assertDirectory(rootReal, 'Agent root');
|
|
106
|
+
const packageRoot = resolvePackageRoot(import.meta.url);
|
|
107
|
+
const bindingsRef = safeRelative(options.bindings || 'agent.bindings.json', 'bindings');
|
|
108
|
+
const existingProjectPath = join(rootReal, 'dingtalk-agent.json');
|
|
109
|
+
const existingProject = existsSync(existingProjectPath)
|
|
110
|
+
? readValidatedProject(rootReal, options.cliVersion || '') : null;
|
|
111
|
+
const inferredName = normalizeName(basename(rootReal));
|
|
112
|
+
const projectName = String(options.projectName || existingProject?.name || inferredName).trim();
|
|
113
|
+
const blockers = [];
|
|
114
|
+
const warnings = [];
|
|
115
|
+
if (!NAME.test(projectName))
|
|
116
|
+
blockers.push('缺少合法 project name;请传 --project-name <stable-name>');
|
|
117
|
+
if (!options.projectName && !existingProject) {
|
|
118
|
+
warnings.push(`project name 由目录名推导为 ${projectName || '(invalid)'}`);
|
|
119
|
+
}
|
|
120
|
+
const discovered = discoverRoleSkills(rootReal);
|
|
121
|
+
const requested = (options.roleSkills || []).map((item) => item.trim()).filter(Boolean);
|
|
122
|
+
const roleCandidates = [...new Set([...discovered.names, ...requested])].sort();
|
|
123
|
+
const invalidRoles = roleCandidates.filter((name) => !NAME.test(name) || name === BASIC);
|
|
124
|
+
const roleSkills = roleCandidates.filter((name) => NAME.test(name) && name !== BASIC);
|
|
125
|
+
if (invalidRoles.length)
|
|
126
|
+
blockers.push(`Role Skill name 非法: ${invalidRoles.join(', ')}`);
|
|
127
|
+
if (!roleSkills.length && !invalidRoles.length) {
|
|
128
|
+
blockers.push('没有发现 Role Skill;请传 --role-skill <name>');
|
|
129
|
+
}
|
|
130
|
+
const files = new Map();
|
|
131
|
+
const trees = new Map();
|
|
132
|
+
const directories = new Set();
|
|
133
|
+
const semanticFiles = [];
|
|
134
|
+
const bindingsPath = join(rootReal, bindingsRef);
|
|
135
|
+
const existingBindings = existsSync(bindingsPath)
|
|
136
|
+
? loadAgentBindings(rootReal, bindingsRef).value : null;
|
|
137
|
+
const existingLocalWorkspace = existingProject?.workspaces?.['local-dev'] || null;
|
|
138
|
+
if (existingLocalWorkspace && existingLocalWorkspace.provider?.kind !== 'opencode') {
|
|
139
|
+
blockers.push('现有 local-dev 不是 OpenCode workspace;拒绝覆盖其 Provider');
|
|
140
|
+
}
|
|
141
|
+
const bindingsValue = {
|
|
142
|
+
$schema: 'dingtalk-agent/agent-bindings@1',
|
|
143
|
+
agent: existingBindings?.agent || 'local-dir:.',
|
|
144
|
+
skills: existingBindings?.skills || 'local-dir:skills',
|
|
145
|
+
memory: options.memory || existingBindings?.memory ||
|
|
146
|
+
existingLocalWorkspace?.storage?.memory || 'local-md:MEMORY.md',
|
|
147
|
+
knowledge: options.knowledge || existingBindings?.knowledge ||
|
|
148
|
+
existingLocalWorkspace?.storage?.knowledge || 'local-md:knowledge/INDEX.md',
|
|
149
|
+
artifacts: options.artifacts || existingBindings?.artifacts ||
|
|
150
|
+
existingLocalWorkspace?.storage?.artifacts || 'local-dir:.dingtalk-agent/artifacts',
|
|
151
|
+
stateDir: options.stateDir || existingBindings?.stateDir || '.dingtalk-agent',
|
|
152
|
+
authority: {
|
|
153
|
+
profile: options.profile ?? existingBindings?.authority.profile ?? '',
|
|
154
|
+
expectedUserId: options.expectedUserId ?? existingBindings?.authority.expectedUserId ?? '',
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
validateBindingsCandidate(rootReal, bindingsValue);
|
|
158
|
+
const remote = [bindingsValue.memory, bindingsValue.knowledge]
|
|
159
|
+
.some((item) => item.startsWith('dingtalk-doc:'));
|
|
160
|
+
if (remote && (!bindingsValue.authority.profile || !bindingsValue.authority.expectedUserId)) {
|
|
161
|
+
blockers.push('远端 memory/knowledge 需要同时提供 --profile 与 --expected-user-id');
|
|
162
|
+
}
|
|
163
|
+
const agentsPath = join(rootReal, 'AGENTS.md');
|
|
164
|
+
if (!existsSync(agentsPath)) {
|
|
165
|
+
const body = readAsset(packageRoot, 'AGENTS.template.md')
|
|
166
|
+
.replace('<Agent 名称>', projectName || '<Agent 名称>')
|
|
167
|
+
.replace('<Role Skill names>', roleSkills.join(', ') || '<Role Skill names>');
|
|
168
|
+
files.set('AGENTS.md', body);
|
|
169
|
+
semanticFiles.push('AGENTS.md');
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
assertRegularFile(agentsPath, 'AGENTS.md');
|
|
173
|
+
const body = readFileSync(agentsPath, 'utf8');
|
|
174
|
+
files.set('AGENTS.md', body);
|
|
175
|
+
if (TEMPLATE_MARKER.test(body))
|
|
176
|
+
semanticFiles.push('AGENTS.md');
|
|
177
|
+
}
|
|
178
|
+
for (const name of roleSkills) {
|
|
179
|
+
const rolePath = `skills/${name}`;
|
|
180
|
+
const source = join(rootReal, rolePath);
|
|
181
|
+
let tree;
|
|
182
|
+
if (existsSync(source)) {
|
|
183
|
+
tree = readTree(source, `Role Skill ${name}`);
|
|
184
|
+
const skillBody = tree.files.get('SKILL.md')?.toString('utf8') || '';
|
|
185
|
+
const actualName = frontmatterName(skillBody);
|
|
186
|
+
if (actualName !== name)
|
|
187
|
+
blockers.push(`Role Skill ${name} frontmatter name=${actualName || '(missing)'}`);
|
|
188
|
+
if (TEMPLATE_MARKER.test(skillBody))
|
|
189
|
+
semanticFiles.push(`${rolePath}/SKILL.md`);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
const body = readAsset(packageRoot, 'role-skill.template.md')
|
|
193
|
+
.replaceAll('<role-skill-name>', name)
|
|
194
|
+
.replace('<Role Skill 名称>', name);
|
|
195
|
+
tree = treeFromFiles(new Map([['SKILL.md', Buffer.from(body)]]));
|
|
196
|
+
trees.set(rolePath, tree);
|
|
197
|
+
semanticFiles.push(`${rolePath}/SKILL.md`);
|
|
198
|
+
}
|
|
199
|
+
trees.set(`.agents/skills/${name}`, tree);
|
|
200
|
+
}
|
|
201
|
+
trees.set(`.agents/skills/${BASIC}`, readTree(join(packageRoot, 'skills', BASIC), 'Canonical Basic Skill'));
|
|
202
|
+
files.set(bindingsRef, JSON.stringify(bindingsValue, null, 2) + '\n');
|
|
203
|
+
const project = desiredProject(existingProject, {
|
|
204
|
+
projectName, roleSkills, model: options.model || 'deepseek/deepseek-chat',
|
|
205
|
+
modelExplicit: options.model !== undefined,
|
|
206
|
+
cliVersion: options.cliVersion || '', bindings: bindingsValue,
|
|
207
|
+
});
|
|
208
|
+
files.set('dingtalk-agent.json', JSON.stringify(project, null, 2) + '\n');
|
|
209
|
+
files.set('opencode.json', JSON.stringify(desiredOpenCode(rootReal, roleSkills), null, 2) + '\n');
|
|
210
|
+
files.set('.gitignore', desiredGitignore(rootReal, bindingsValue.stateDir));
|
|
211
|
+
for (const [label, route] of Object.entries({
|
|
212
|
+
memory: bindingsValue.memory, knowledge: bindingsValue.knowledge,
|
|
213
|
+
})) {
|
|
214
|
+
if (!route.startsWith('local-md:'))
|
|
215
|
+
continue;
|
|
216
|
+
const ref = safeRelative(route.slice('local-md:'.length), `${label} route`);
|
|
217
|
+
const path = join(rootReal, ref);
|
|
218
|
+
if (!existsSync(path)) {
|
|
219
|
+
files.set(ref, `# ${label === 'memory' ? 'Memory' : 'Knowledge'}\n\n`);
|
|
220
|
+
}
|
|
221
|
+
else
|
|
222
|
+
assertRegularFile(path, `${label} route`);
|
|
223
|
+
}
|
|
224
|
+
const artifactRef = safeLocalRoute(bindingsValue.artifacts, 'local-dir', 'artifacts');
|
|
225
|
+
if (resolve(rootReal, artifactRef) === rootReal) {
|
|
226
|
+
throw new Error('artifacts 必须是 Agent 根目录内的独立目录,不能等于根目录');
|
|
227
|
+
}
|
|
228
|
+
directories.add(artifactRef);
|
|
229
|
+
directories.add(safeRelative(bindingsValue.stateDir, 'stateDir'));
|
|
230
|
+
const operations = buildOperations(rootReal, files, trees, directories);
|
|
231
|
+
const baselineHash = digest(stableStringify(operations.map((item) => ({
|
|
232
|
+
path: item.path, beforeHash: item.beforeHash,
|
|
233
|
+
}))));
|
|
234
|
+
const desiredHash = digest(stableStringify(operations.map((item) => ({
|
|
235
|
+
path: item.path, afterHash: item.afterHash,
|
|
236
|
+
}))));
|
|
237
|
+
const source = sourceIdentity(rootReal);
|
|
238
|
+
const planSeed = {
|
|
239
|
+
schema: 'dingtalk-agent/agent-enhancement-plan@1', baselineHash, desiredHash,
|
|
240
|
+
root: rootReal, source, projectName, roleSkills, bindings: bindingsRef,
|
|
241
|
+
options: {
|
|
242
|
+
model: options.model || 'deepseek/deepseek-chat',
|
|
243
|
+
memory: bindingsValue.memory, knowledge: bindingsValue.knowledge,
|
|
244
|
+
artifacts: bindingsValue.artifacts, stateDir: bindingsValue.stateDir,
|
|
245
|
+
profileConfigured: Boolean(bindingsValue.authority.profile),
|
|
246
|
+
expectedUserConfigured: Boolean(bindingsValue.authority.expectedUserId),
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
const planId = `aep_${digest(stableStringify(planSeed)).slice(0, 32)}`;
|
|
250
|
+
const roleArgument = roleSkills.join(',') || '<role-skill>';
|
|
251
|
+
const common = [
|
|
252
|
+
...(options.projectName ? ['--project-name', shellArg(options.projectName)] : []),
|
|
253
|
+
...(roleSkills.length ? ['--role-skill', shellArg(roleSkills.join(','))] : []),
|
|
254
|
+
...(options.model ? ['--model', shellArg(options.model)] : []),
|
|
255
|
+
...(options.memory ? ['--memory', shellArg(options.memory)] : []),
|
|
256
|
+
...(options.knowledge ? ['--knowledge', shellArg(options.knowledge)] : []),
|
|
257
|
+
...(options.artifacts ? ['--artifacts', shellArg(options.artifacts)] : []),
|
|
258
|
+
...(options.stateDir ? ['--state-dir', shellArg(options.stateDir)] : []),
|
|
259
|
+
...(options.profile !== undefined ? ['--profile', '"$DTA_DWS_PROFILE"'] : []),
|
|
260
|
+
...(options.expectedUserId !== undefined
|
|
261
|
+
? ['--expected-user-id', '"$DTA_EXPECTED_USER_ID"'] : []),
|
|
262
|
+
...(bindingsRef !== 'agent.bindings.json' ? ['--bindings', shellArg(bindingsRef)] : []),
|
|
263
|
+
].join(' ');
|
|
264
|
+
const applyCommand = `dta agent enhance ${common}${common ? ' ' : ''}--plan-id ${planId} --yes --json`;
|
|
265
|
+
const plan = {
|
|
266
|
+
$schema: 'dingtalk-agent/agent-enhancement-plan@1', planId,
|
|
267
|
+
ready: blockers.length === 0, status: blockers.length ? 'blocked' : 'ready',
|
|
268
|
+
sideEffect: false, dingtalkSideEffect: false, triggerSideEffect: false,
|
|
269
|
+
root: rootReal,
|
|
270
|
+
source, projectName, roleSkills, bindings: bindingsRef,
|
|
271
|
+
baselineHash, desiredHash, operations, blockers, warnings,
|
|
272
|
+
semanticReview: {
|
|
273
|
+
required: semanticFiles.length > 0,
|
|
274
|
+
files: [...new Set(semanticFiles)].sort(),
|
|
275
|
+
why: semanticFiles.length
|
|
276
|
+
? 'CLI 只补结构,不替开发者虚构身份、职责、交付物或领域 SOP;请由 compose Skill 完成语义评审。'
|
|
277
|
+
: '现有本体与 Role Skill 未命中模板占位符。',
|
|
278
|
+
},
|
|
279
|
+
apply: { command: applyCommand, requiresCurrentPlanId: true },
|
|
280
|
+
verification: { commands: [
|
|
281
|
+
`dta bootstrap --bindings ${shellArg(bindingsRef)} --json`,
|
|
282
|
+
`dta agent audit --bindings ${shellArg(bindingsRef)} --require-skill ${shellArg(roleArgument)} --json`,
|
|
283
|
+
`dta workspace create local-dev --dry-run --json`,
|
|
284
|
+
] },
|
|
285
|
+
};
|
|
286
|
+
return { plan, files, trees, directories, bindingsValue };
|
|
287
|
+
}
|
|
288
|
+
function desiredProject(existing, input) {
|
|
289
|
+
if (existing) {
|
|
290
|
+
const next = structuredClone(existing);
|
|
291
|
+
next.name = input.projectName;
|
|
292
|
+
next.agent.skills = [...new Set([BASIC, ...(next.agent.skills || []), ...input.roleSkills])];
|
|
293
|
+
const local = next.workspaces['local-dev'];
|
|
294
|
+
if (!local) {
|
|
295
|
+
next.workspaces['local-dev'] = localOpenCodeWorkspace(input);
|
|
296
|
+
}
|
|
297
|
+
else if (local.provider?.kind === 'opencode') {
|
|
298
|
+
if (input.modelExplicit)
|
|
299
|
+
local.provider.model = input.model;
|
|
300
|
+
local.storage = {
|
|
301
|
+
...local.storage,
|
|
302
|
+
memory: input.bindings.memory,
|
|
303
|
+
knowledge: input.bindings.knowledge,
|
|
304
|
+
artifacts: input.bindings.artifacts,
|
|
305
|
+
};
|
|
306
|
+
if ([input.bindings.memory, input.bindings.knowledge]
|
|
307
|
+
.some((item) => item.startsWith('dingtalk-doc:'))) {
|
|
308
|
+
local.authority = {
|
|
309
|
+
dwsProfileFrom: 'env:DTA_DWS_PROFILE',
|
|
310
|
+
expectedUserIdFrom: 'env:DTA_EXPECTED_USER_ID',
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
delete local.authority;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return next;
|
|
318
|
+
}
|
|
319
|
+
const version = input.cliVersion.match(/^(\d+\.\d+\.\d+)/)?.[1] || '0.1.0';
|
|
320
|
+
return {
|
|
321
|
+
$schema: 'dingtalk-agent/project@1', name: input.projectName,
|
|
322
|
+
dtaVersion: `^${version}`,
|
|
323
|
+
agent: { definition: 'AGENTS.md', skills: [BASIC, ...input.roleSkills] },
|
|
324
|
+
workspaces: { 'local-dev': localOpenCodeWorkspace(input) },
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function localOpenCodeWorkspace(input) {
|
|
328
|
+
const workspace = {
|
|
329
|
+
stage: 'dev', provider: { kind: 'opencode', model: input.model },
|
|
330
|
+
storage: {
|
|
331
|
+
memory: input.bindings.memory, knowledge: input.bindings.knowledge,
|
|
332
|
+
artifacts: input.bindings.artifacts,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
if ([input.bindings.memory, input.bindings.knowledge]
|
|
336
|
+
.some((item) => item.startsWith('dingtalk-doc:'))) {
|
|
337
|
+
workspace.authority = {
|
|
338
|
+
dwsProfileFrom: 'env:DTA_DWS_PROFILE',
|
|
339
|
+
expectedUserIdFrom: 'env:DTA_EXPECTED_USER_ID',
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return workspace;
|
|
343
|
+
}
|
|
344
|
+
function desiredOpenCode(root, roles) {
|
|
345
|
+
const path = join(root, 'opencode.json');
|
|
346
|
+
let value = { $schema: 'https://opencode.ai/config.json' };
|
|
347
|
+
if (existsSync(path)) {
|
|
348
|
+
assertRegularFile(path, 'opencode.json');
|
|
349
|
+
try {
|
|
350
|
+
value = JSON.parse(readFileSync(path, 'utf8'));
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
throw new Error(`opencode.json 不是合法 JSON: ${error.message}`);
|
|
354
|
+
}
|
|
355
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
356
|
+
throw new Error('opencode.json 必须是 JSON object');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (value.instructions !== undefined && !Array.isArray(value.instructions)) {
|
|
360
|
+
throw new Error('opencode.json#instructions 必须是 array;拒绝覆盖未知结构');
|
|
361
|
+
}
|
|
362
|
+
if ((value.instructions || []).some((item) => typeof item !== 'string')) {
|
|
363
|
+
throw new Error('opencode.json#instructions 只能包含字符串;拒绝静默删除未知条目');
|
|
364
|
+
}
|
|
365
|
+
const instructions = (value.instructions || []);
|
|
366
|
+
value.instructions = [...instructions.filter((item) => item !== BASIC_INSTRUCTION), BASIC_INSTRUCTION];
|
|
367
|
+
const permission = value.permission === undefined ? {} : value.permission;
|
|
368
|
+
if (!permission || typeof permission !== 'object' || Array.isArray(permission)) {
|
|
369
|
+
throw new Error('opencode.json#permission 必须是 object;拒绝覆盖未知结构');
|
|
370
|
+
}
|
|
371
|
+
const skill = permission.skill === undefined ? {} : permission.skill;
|
|
372
|
+
if (!skill || typeof skill !== 'object' || Array.isArray(skill)) {
|
|
373
|
+
throw new Error('opencode.json#permission.skill 必须是 object;拒绝覆盖未知结构');
|
|
374
|
+
}
|
|
375
|
+
value.permission = { ...permission, skill: {
|
|
376
|
+
...skill, [BASIC]: 'allow', ...Object.fromEntries(roles.map((name) => [name, 'allow'])),
|
|
377
|
+
} };
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
function buildOperations(root, files, trees, directories) {
|
|
381
|
+
const operations = [];
|
|
382
|
+
for (const [path, body] of [...files.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
383
|
+
const target = safeTarget(root, path);
|
|
384
|
+
const beforeHash = stateHash(target);
|
|
385
|
+
const afterHash = digest(Buffer.from(body).toString('base64'));
|
|
386
|
+
operations.push({
|
|
387
|
+
id: `file:${path}`,
|
|
388
|
+
kind: !existsSync(target) ? 'create-file' : beforeHash === afterHash ? 'preserve' : 'update-file',
|
|
389
|
+
path, beforeHash, afterHash,
|
|
390
|
+
backupRequired: existsSync(target) && beforeHash !== afterHash,
|
|
391
|
+
reason: fileReason(path),
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
for (const [path, tree] of [...trees.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
395
|
+
const target = safeTarget(root, path);
|
|
396
|
+
if (existsSync(target) && (!lstatSync(target).isDirectory() || lstatSync(target).isSymbolicLink())) {
|
|
397
|
+
throw new Error(`受管 Skill exposure 必须是普通目录,拒绝覆盖: ${path}`);
|
|
398
|
+
}
|
|
399
|
+
const beforeHash = stateHash(target);
|
|
400
|
+
operations.push({
|
|
401
|
+
id: `tree:${path}`,
|
|
402
|
+
kind: beforeHash === tree.hash ? 'preserve' : 'replace-directory',
|
|
403
|
+
path, beforeHash, afterHash: tree.hash,
|
|
404
|
+
backupRequired: existsSync(target) && beforeHash !== tree.hash,
|
|
405
|
+
reason: path.startsWith('.agents/') ? '物化目标 Host 的精确 Skill exposure' : '补齐缺失的 Role Skill 模板',
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
for (const path of [...directories].sort()) {
|
|
409
|
+
const target = safeTarget(root, path);
|
|
410
|
+
if (existsSync(target) && (!lstatSync(target).isDirectory() || lstatSync(target).isSymbolicLink())) {
|
|
411
|
+
throw new Error(`本地路由必须是普通目录: ${path}`);
|
|
412
|
+
}
|
|
413
|
+
operations.push({
|
|
414
|
+
id: `directory:${path}`, kind: existsSync(target) ? 'preserve' : 'create-directory',
|
|
415
|
+
path, beforeHash: existsSync(target) ? DIRECTORY_HASH : '', afterHash: DIRECTORY_HASH,
|
|
416
|
+
backupRequired: false, reason: '建立本地 state/artifact 路由,不投影控制状态到远端',
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return operations.sort((a, b) => a.path.localeCompare(b.path));
|
|
420
|
+
}
|
|
421
|
+
function readValidatedProject(root, cliVersion) {
|
|
422
|
+
inspectAgentProject(root, cliVersion);
|
|
423
|
+
return JSON.parse(readFileSync(join(root, 'dingtalk-agent.json'), 'utf8'));
|
|
424
|
+
}
|
|
425
|
+
function discoverRoleSkills(root) {
|
|
426
|
+
const base = join(root, 'skills');
|
|
427
|
+
if (!existsSync(base))
|
|
428
|
+
return { names: [] };
|
|
429
|
+
if (!lstatSync(base).isDirectory() || lstatSync(base).isSymbolicLink()) {
|
|
430
|
+
throw new Error('skills 必须是 Agent 根目录内的普通目录');
|
|
431
|
+
}
|
|
432
|
+
const names = [];
|
|
433
|
+
for (const entry of readdirSync(base, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
434
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || entry.name === BASIC)
|
|
435
|
+
continue;
|
|
436
|
+
const file = join(base, entry.name, 'SKILL.md');
|
|
437
|
+
if (!existsSync(file))
|
|
438
|
+
continue;
|
|
439
|
+
assertRegularFile(file, `Role Skill ${entry.name}`);
|
|
440
|
+
const name = frontmatterName(readFileSync(file, 'utf8'));
|
|
441
|
+
if (name !== entry.name)
|
|
442
|
+
throw new Error(`Role Skill 目录与 frontmatter name 不一致: ${entry.name}/${name || '(missing)'}`);
|
|
443
|
+
names.push(name);
|
|
444
|
+
}
|
|
445
|
+
return { names };
|
|
446
|
+
}
|
|
447
|
+
function validateBindingsCandidate(root, value) {
|
|
448
|
+
for (const [label, route] of Object.entries({
|
|
449
|
+
agent: value.agent, skills: value.skills, memory: value.memory,
|
|
450
|
+
knowledge: value.knowledge, artifacts: value.artifacts,
|
|
451
|
+
})) {
|
|
452
|
+
const separator = route.indexOf(':');
|
|
453
|
+
if (separator < 1 || !route.slice(separator + 1).trim())
|
|
454
|
+
throw new Error(`${label} route 非法`);
|
|
455
|
+
if (!route.startsWith('local-'))
|
|
456
|
+
continue;
|
|
457
|
+
safeRelative(route.slice(separator + 1), `${label} route`);
|
|
458
|
+
}
|
|
459
|
+
safeRelative(value.stateDir, 'stateDir');
|
|
460
|
+
if (resolve(root, value.stateDir) === root)
|
|
461
|
+
throw new Error('stateDir 不能等于 Agent 根目录');
|
|
462
|
+
}
|
|
463
|
+
function desiredGitignore(root, stateDir) {
|
|
464
|
+
const path = join(root, '.gitignore');
|
|
465
|
+
const state = safeRelative(stateDir, 'stateDir');
|
|
466
|
+
const entries = ['.dingtalk-agent/'];
|
|
467
|
+
if (state !== '.dingtalk-agent' && !state.startsWith('.dingtalk-agent/')) {
|
|
468
|
+
entries.push(`${state.replace(/\/+$/, '')}/`);
|
|
469
|
+
}
|
|
470
|
+
if (!existsSync(path))
|
|
471
|
+
return `${entries.join('\n')}\n`;
|
|
472
|
+
assertRegularFile(path, '.gitignore');
|
|
473
|
+
let body = readFileSync(path, 'utf8');
|
|
474
|
+
const lines = new Set(body.split(/\r?\n/).map((line) => line.trim().replace(/\/+$/, '')));
|
|
475
|
+
for (const entry of entries) {
|
|
476
|
+
if (lines.has(entry.replace(/\/+$/, '')))
|
|
477
|
+
continue;
|
|
478
|
+
body = `${body}${body && !body.endsWith('\n') ? '\n' : ''}${entry}\n`;
|
|
479
|
+
lines.add(entry.replace(/\/+$/, ''));
|
|
480
|
+
}
|
|
481
|
+
return body;
|
|
482
|
+
}
|
|
483
|
+
function sourceIdentity(root) {
|
|
484
|
+
const git = join(root, '.git');
|
|
485
|
+
if (!existsSync(git))
|
|
486
|
+
return { kind: 'local-folder', revision: '' };
|
|
487
|
+
let gitDirectory = git;
|
|
488
|
+
if (!lstatSync(git).isDirectory()) {
|
|
489
|
+
const ref = readFileSync(git, 'utf8').trim().match(/^gitdir:\s*(.+)$/)?.[1];
|
|
490
|
+
if (!ref)
|
|
491
|
+
return { kind: 'git-checkout', revision: '' };
|
|
492
|
+
gitDirectory = resolve(root, ref);
|
|
493
|
+
}
|
|
494
|
+
if (!gitDirectory || !existsSync(gitDirectory) || !statSync(gitDirectory).isDirectory()) {
|
|
495
|
+
return { kind: 'git-checkout', revision: '' };
|
|
496
|
+
}
|
|
497
|
+
let revision = '';
|
|
498
|
+
const head = join(gitDirectory, 'HEAD');
|
|
499
|
+
if (existsSync(head) && statSync(head).isFile()) {
|
|
500
|
+
const headValue = readFileSync(head, 'utf8').trim();
|
|
501
|
+
const ref = headValue.match(/^ref:\s*(.+)$/)?.[1] || '';
|
|
502
|
+
if (!ref)
|
|
503
|
+
revision = headValue;
|
|
504
|
+
else {
|
|
505
|
+
const commonDirFile = join(gitDirectory, 'commondir');
|
|
506
|
+
const commonDir = existsSync(commonDirFile)
|
|
507
|
+
? resolve(gitDirectory, readFileSync(commonDirFile, 'utf8').trim()) : gitDirectory;
|
|
508
|
+
const refPath = join(commonDir, ref);
|
|
509
|
+
if (existsSync(refPath))
|
|
510
|
+
revision = readFileSync(refPath, 'utf8').trim();
|
|
511
|
+
else {
|
|
512
|
+
const packed = join(commonDir, 'packed-refs');
|
|
513
|
+
if (existsSync(packed)) {
|
|
514
|
+
revision = readFileSync(packed, 'utf8').split(/\r?\n/)
|
|
515
|
+
.find((line) => line.endsWith(` ${ref}`))?.split(' ')[0] || '';
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return { kind: 'git-checkout', revision };
|
|
521
|
+
}
|
|
522
|
+
function fileReason(path) {
|
|
523
|
+
if (path === 'AGENTS.md')
|
|
524
|
+
return '补齐本体结构;语义内容仍需开发者评审';
|
|
525
|
+
if (path === 'agent.bindings.json')
|
|
526
|
+
return '固化本体、语义存储与宿主私有状态路由';
|
|
527
|
+
if (path === 'dingtalk-agent.json')
|
|
528
|
+
return '把目录升级为可发现、可版本化的 Agent Project';
|
|
529
|
+
if (path === 'opencode.json')
|
|
530
|
+
return '合并唯一 Basic instruction 与 Role Skill permission';
|
|
531
|
+
if (path === '.gitignore')
|
|
532
|
+
return '防止私有 state、Receipt 与原始证据进入 Git';
|
|
533
|
+
return '补齐本地语义介质入口';
|
|
534
|
+
}
|
|
535
|
+
function readAsset(packageRoot, name) {
|
|
536
|
+
return readFileSync(join(packageRoot, 'skills', 'dingtalk-agent-compose', 'assets', name), 'utf8');
|
|
537
|
+
}
|
|
538
|
+
function readTree(path, label) {
|
|
539
|
+
if (!existsSync(path) || !lstatSync(path).isDirectory() || lstatSync(path).isSymbolicLink()) {
|
|
540
|
+
throw new Error(`${label} 必须是普通目录: ${path}`);
|
|
541
|
+
}
|
|
542
|
+
const files = new Map();
|
|
543
|
+
const visit = (directory) => {
|
|
544
|
+
for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
545
|
+
const candidate = join(directory, entry.name);
|
|
546
|
+
if (entry.isSymbolicLink())
|
|
547
|
+
throw new Error(`${label} 不允许 symlink: ${candidate}`);
|
|
548
|
+
if (entry.isDirectory())
|
|
549
|
+
visit(candidate);
|
|
550
|
+
else if (entry.isFile())
|
|
551
|
+
files.set(relative(path, candidate).split('\\').join('/'), readFileSync(candidate));
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
visit(path);
|
|
555
|
+
if (!files.has('SKILL.md'))
|
|
556
|
+
throw new Error(`${label} 缺少 SKILL.md`);
|
|
557
|
+
return treeFromFiles(files);
|
|
558
|
+
}
|
|
559
|
+
function treeFromFiles(files) {
|
|
560
|
+
const index = [...files.entries()].sort(([a], [b]) => a.localeCompare(b))
|
|
561
|
+
.map(([path, body]) => ({ path, hash: digest(body.toString('base64')) }));
|
|
562
|
+
return { files, hash: digest(stableStringify(index)) };
|
|
563
|
+
}
|
|
564
|
+
function writeTree(target, tree) {
|
|
565
|
+
mkdirSync(target, { recursive: true });
|
|
566
|
+
for (const [path, body] of tree.files) {
|
|
567
|
+
const out = resolve(target, path);
|
|
568
|
+
if (!isInside(target, out))
|
|
569
|
+
throw new Error(`Skill tree path 越界: ${path}`);
|
|
570
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
571
|
+
writeFileSync(out, body, { flag: 'wx' });
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function copyEntry(source, destination) {
|
|
575
|
+
if (!existsSync(source))
|
|
576
|
+
throw new Error(`备份源不存在: ${source}`);
|
|
577
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
578
|
+
cpSync(source, destination, {
|
|
579
|
+
recursive: lstatSync(source).isDirectory(), errorOnExist: true,
|
|
580
|
+
filter: (path) => {
|
|
581
|
+
if (lstatSync(path).isSymbolicLink())
|
|
582
|
+
throw new Error(`备份拒绝 symlink: ${path}`);
|
|
583
|
+
return true;
|
|
584
|
+
},
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
function rollback(root, operations, backupRoot, backedUp, created) {
|
|
588
|
+
for (const operation of operations) {
|
|
589
|
+
const target = safeTarget(root, operation.path);
|
|
590
|
+
if (created.has(operation.path))
|
|
591
|
+
rmSync(target, { recursive: true, force: true });
|
|
592
|
+
else if (backedUp.has(operation.path)) {
|
|
593
|
+
rmSync(target, { recursive: true, force: true });
|
|
594
|
+
copyEntry(resolve(backupRoot, operation.path), target);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function stateHash(path) {
|
|
599
|
+
if (!existsSync(path))
|
|
600
|
+
return '';
|
|
601
|
+
const stat = lstatSync(path);
|
|
602
|
+
if (stat.isSymbolicLink())
|
|
603
|
+
throw new Error(`受管路径不允许 symlink: ${path}`);
|
|
604
|
+
if (stat.isFile())
|
|
605
|
+
return digest(readFileSync(path).toString('base64'));
|
|
606
|
+
if (!stat.isDirectory())
|
|
607
|
+
throw new Error(`受管路径类型不支持: ${path}`);
|
|
608
|
+
const files = new Map();
|
|
609
|
+
const visit = (directory) => {
|
|
610
|
+
for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
611
|
+
const candidate = join(directory, entry.name);
|
|
612
|
+
if (entry.isSymbolicLink())
|
|
613
|
+
throw new Error(`受管目录不允许 symlink: ${candidate}`);
|
|
614
|
+
if (entry.isDirectory())
|
|
615
|
+
visit(candidate);
|
|
616
|
+
else if (entry.isFile())
|
|
617
|
+
files.set(relative(path, candidate).split('\\').join('/'), readFileSync(candidate));
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
visit(path);
|
|
621
|
+
return treeFromFiles(files).hash;
|
|
622
|
+
}
|
|
623
|
+
function writeAtomic(path, body) {
|
|
624
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
625
|
+
const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
626
|
+
writeFileSync(temporary, body, { flag: 'wx' });
|
|
627
|
+
renameSync(temporary, path);
|
|
628
|
+
}
|
|
629
|
+
function assertDirectory(path, label) {
|
|
630
|
+
if (!existsSync(path) || !statSync(path).isDirectory())
|
|
631
|
+
throw new Error(`${label} 不存在或不是目录`);
|
|
632
|
+
}
|
|
633
|
+
function assertRegularFile(path, label) {
|
|
634
|
+
if (!existsSync(path) || !lstatSync(path).isFile() || lstatSync(path).isSymbolicLink()) {
|
|
635
|
+
throw new Error(`${label} 必须是普通文件`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function safeTarget(root, path) {
|
|
639
|
+
const ref = safeRelative(path, 'managed path');
|
|
640
|
+
const target = resolve(root, ref);
|
|
641
|
+
if (!isInside(root, target))
|
|
642
|
+
throw new Error(`受管路径越出 Agent root: ${path}`);
|
|
643
|
+
let current = root;
|
|
644
|
+
for (const part of ref.split('/').slice(0, -1)) {
|
|
645
|
+
current = join(current, part);
|
|
646
|
+
if (existsSync(current) && lstatSync(current).isSymbolicLink()) {
|
|
647
|
+
throw new Error(`受管路径不能穿过 symlink: ${path}`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return target;
|
|
651
|
+
}
|
|
652
|
+
function safeRelative(value, label) {
|
|
653
|
+
const text = String(value || '').trim().split('\\').join('/');
|
|
654
|
+
if (!text || text.startsWith('/') || text.split('/').some((item) => !item || item === '..')) {
|
|
655
|
+
throw new Error(`${label} 必须是 Agent 根目录内的相对路径: ${value}`);
|
|
656
|
+
}
|
|
657
|
+
return text.replace(/^\.\//, '');
|
|
658
|
+
}
|
|
659
|
+
function safeLocalRoute(route, provider, label) {
|
|
660
|
+
if (!route.startsWith(`${provider}:`))
|
|
661
|
+
throw new Error(`${label} 必须使用 ${provider}`);
|
|
662
|
+
return safeRelative(route.slice(provider.length + 1), label);
|
|
663
|
+
}
|
|
664
|
+
function frontmatterName(body) {
|
|
665
|
+
const block = body.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1] || '';
|
|
666
|
+
return block.match(/^name:\s*["']?([^"'\r\n]+)["']?\s*$/m)?.[1]?.trim() || '';
|
|
667
|
+
}
|
|
668
|
+
function normalizeName(value) {
|
|
669
|
+
return value.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 64);
|
|
670
|
+
}
|
|
671
|
+
function shellArg(value) {
|
|
672
|
+
return /^[A-Za-z0-9_./:=@^+-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
673
|
+
}
|
|
674
|
+
function isInside(root, candidate) {
|
|
675
|
+
const rel = relative(root, candidate);
|
|
676
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`));
|
|
677
|
+
}
|
|
678
|
+
//# sourceMappingURL=agent-enhance.js.map
|