@xdxer/dingtalk-agent 0.1.4-beta.8 → 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 -35
- package/dist/bin/dingtalk-agent.js +1058 -10
- 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/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 +21 -3
- 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
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync, } from 'node:fs';
|
|
3
|
+
import { basename, dirname, join, parse, relative, resolve } from 'node:path';
|
|
4
|
+
import { findExecutable } from './doctor.js';
|
|
5
|
+
import { digest, stableStringify } from './events.js';
|
|
6
|
+
import { resolvePackageRoot } from './package-root.js';
|
|
7
|
+
import { loadWorkspace, workspacePath } from './workspace.js';
|
|
8
|
+
export const PROJECT_FILE = 'dingtalk-agent.json';
|
|
9
|
+
export const CURRENT_WORKSPACE_FILE = '.dingtalk-agent/current-workspace';
|
|
10
|
+
export const WORKSPACE_STATE_ROOT = '.dingtalk-agent/state/workspaces';
|
|
11
|
+
export const LEGACY_WORKSPACE_NAME = 'legacy-prepared';
|
|
12
|
+
const PROJECT_KEYS = new Set(['$schema', 'name', 'dtaVersion', 'agent', 'workspaces']);
|
|
13
|
+
const AGENT_KEYS = new Set(['definition', 'skills']);
|
|
14
|
+
const WORKSPACE_KEYS = new Set(['stage', 'provider', 'storage', 'authority']);
|
|
15
|
+
const OPENCODE_KEYS = new Set(['kind', 'model']);
|
|
16
|
+
const MULTICA_KEYS = new Set([
|
|
17
|
+
'kind', 'profile', 'workspaceIdFrom', 'runtimeIdFrom', 'agentIdFrom',
|
|
18
|
+
]);
|
|
19
|
+
const STORAGE_KEYS = new Set(['memory', 'knowledge', 'artifacts']);
|
|
20
|
+
const AUTHORITY_KEYS = new Set(['dwsProfileFrom', 'expectedUserIdFrom']);
|
|
21
|
+
const STATE_KEYS = new Set([
|
|
22
|
+
'$schema', 'workspace', 'provider', 'status', 'binding', 'desiredHash', 'observedHash',
|
|
23
|
+
'lastPlanId', 'lastReceiptId', 'lastEvalId',
|
|
24
|
+
]);
|
|
25
|
+
const STATE_STATUSES = new Set([
|
|
26
|
+
'declared', 'resolving', 'planned', 'applying', 'verifying',
|
|
27
|
+
'ready', 'drifted', 'failed', 'retired',
|
|
28
|
+
]);
|
|
29
|
+
const SECRET_KEY = /(?:secret|token|cookie|password|credentials?)$/i;
|
|
30
|
+
const NAME = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
|
|
31
|
+
const ENV_REF = /^env:[A-Z_][A-Z0-9_]*$/;
|
|
32
|
+
export function findAgentProjectRoot(start = process.cwd()) {
|
|
33
|
+
let directory = resolve(start);
|
|
34
|
+
if (existsSync(directory) && !statSync(directory).isDirectory())
|
|
35
|
+
directory = dirname(directory);
|
|
36
|
+
while (true) {
|
|
37
|
+
if (existsSync(join(directory, PROJECT_FILE)) || existsSync(workspacePath(directory))) {
|
|
38
|
+
return realpathSync(directory);
|
|
39
|
+
}
|
|
40
|
+
const parent = dirname(directory);
|
|
41
|
+
if (parent === directory || directory === parse(directory).root)
|
|
42
|
+
return null;
|
|
43
|
+
directory = parent;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function inspectAgentProject(start = process.cwd(), cliVersion = '') {
|
|
47
|
+
const loaded = loadAgentProject(start);
|
|
48
|
+
const currentWorkspace = readCurrentWorkspace(loaded.root);
|
|
49
|
+
const declared = Object.entries(loaded.manifest.workspaces)
|
|
50
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
51
|
+
.map(([name, workspace]) => declaredView(loaded, name, workspace, currentWorkspace));
|
|
52
|
+
if (loaded.legacy && !(LEGACY_WORKSPACE_NAME in loaded.manifest.workspaces)) {
|
|
53
|
+
declared.push(legacyView(loaded, loaded.legacy, currentWorkspace));
|
|
54
|
+
}
|
|
55
|
+
if (currentWorkspace && !declared.some((item) => item.name === currentWorkspace)) {
|
|
56
|
+
throw new Error(`current-workspace 指向未知 Workspace: ${currentWorkspace}`);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
$schema: 'dingtalk-agent/info@1',
|
|
60
|
+
root: loaded.root,
|
|
61
|
+
source: loaded.source,
|
|
62
|
+
manifest: loaded.manifestPath ? relative(loaded.root, loaded.manifestPath) : '',
|
|
63
|
+
manifestHash: loaded.manifestHash,
|
|
64
|
+
project: {
|
|
65
|
+
name: loaded.manifest.name,
|
|
66
|
+
dtaVersion: loaded.manifest.dtaVersion,
|
|
67
|
+
compatibleWithCli: !cliVersion || versionSatisfies(cliVersion, loaded.manifest.dtaVersion),
|
|
68
|
+
definition: loaded.manifest.agent.definition,
|
|
69
|
+
skills: [...loaded.manifest.agent.skills],
|
|
70
|
+
},
|
|
71
|
+
currentWorkspace,
|
|
72
|
+
workspaces: declared,
|
|
73
|
+
sideEffect: false,
|
|
74
|
+
dingtalkSideEffect: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function showDevelopmentWorkspace(start, name, cliVersion = '') {
|
|
78
|
+
const info = inspectAgentProject(start, cliVersion);
|
|
79
|
+
const workspace = info.workspaces.find((item) => item.name === name);
|
|
80
|
+
if (!workspace)
|
|
81
|
+
throw new Error(`Project 未声明 Workspace: ${name}`);
|
|
82
|
+
return workspace;
|
|
83
|
+
}
|
|
84
|
+
export function doctorDevelopmentWorkspace(start, name, options = {}) {
|
|
85
|
+
const cliVersion = options.cliVersion || '';
|
|
86
|
+
const env = options.env || process.env;
|
|
87
|
+
const info = inspectAgentProject(start, cliVersion);
|
|
88
|
+
const workspace = info.workspaces.find((item) => item.name === name);
|
|
89
|
+
if (!workspace)
|
|
90
|
+
throw new Error(`Project 未声明 Workspace: ${name}`);
|
|
91
|
+
const checks = [];
|
|
92
|
+
const add = (id, surface, level, blocking, summary, evidence = {}, fix) => checks.push({ id, surface, level, blocking, summary, evidence, ...(fix ? { fix } : {}) });
|
|
93
|
+
add('project.schema', 'project', 'pass', true, 'Project manifest/schema 可解析', {
|
|
94
|
+
source: info.source, manifestHash: info.manifestHash,
|
|
95
|
+
});
|
|
96
|
+
add('project.version', 'project', info.project.compatibleWithCli ? 'pass' : 'fail', true, info.project.compatibleWithCli ? 'Project 声明兼容当前 dta 版本' : 'Project dtaVersion 与当前 CLI 不兼容', { required: info.project.dtaVersion, actual: cliVersion || 'unknown' }, '升级 CLI,或在审阅兼容性后更新 dingtalk-agent.json#dtaVersion');
|
|
97
|
+
const definition = inspectLocalFile(info.root, info.project.definition);
|
|
98
|
+
add('definition.file', 'definition', definition.valid ? 'pass' : 'fail', true, definition.valid ? 'Agent Definition 位于 Project 内且可读' : 'Agent Definition 缺失、越界或不是文件', definition, `修复 ${info.project.definition}`);
|
|
99
|
+
for (const [slot, uri] of Object.entries(workspace.storage)) {
|
|
100
|
+
const local = inspectStorageRoute(info.root, uri);
|
|
101
|
+
const blocking = !uri.startsWith('dingtalk-doc:') && !(workspace.legacy && slot === 'artifacts');
|
|
102
|
+
add(`storage.${slot}`, 'storage', local.valid ? 'pass' : blocking ? 'fail' : 'warn', blocking, local.valid ? `${slot} Storage route 可解析`
|
|
103
|
+
: uri.startsWith('dingtalk-doc:') ? `${slot} 是远端语义路由,W1 不执行 DWS 回读`
|
|
104
|
+
: `${slot} 本地 Storage route 缺失或越界`, { route: uri, ...local }, local.fix);
|
|
105
|
+
}
|
|
106
|
+
if (workspace.state) {
|
|
107
|
+
add('state.provider', 'state', workspace.drift.includes('provider') ? 'fail' : 'pass', true, workspace.drift.includes('provider') ? 'State Provider 与声明不一致' : 'State Provider 与声明一致', { declared: workspace.provider.kind, observed: workspace.state.provider });
|
|
108
|
+
add('state.desired-hash', 'state', workspace.drift.includes('desiredHash') ? 'fail' : 'pass', true, workspace.drift.includes('desiredHash') ? 'State desiredHash 已漂移' : 'State desiredHash 与声明一致', { desiredHash: workspace.desiredHash, stateDesiredHash: workspace.state.desiredHash });
|
|
109
|
+
add('state.status', 'state', workspace.state.status === 'ready' ? 'pass' : 'fail', true, workspace.state.status === 'ready' ? 'Provider state 已回读为 ready'
|
|
110
|
+
: `Provider state 尚未 ready: ${workspace.state.status}`, { status: workspace.state.status });
|
|
111
|
+
const observedHashValid = /^[a-f0-9]{64}$/.test(workspace.state.observedHash);
|
|
112
|
+
add('state.observed-hash', 'state', observedHashValid ? 'pass' : 'fail', true, observedHashValid ? 'Provider observedHash 是稳定 sha256' : 'ready state 缺少有效 observedHash', { configured: Boolean(workspace.state.observedHash) });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const required = !workspace.legacy;
|
|
116
|
+
add('state.present', 'state', required ? 'fail' : 'warn', required, required ? '尚无 Provider 回读 state;Workspace 只完成声明,尚未就绪'
|
|
117
|
+
: 'Legacy adapter 不要求新的 Provider state', {
|
|
118
|
+
expectedPath: workspace.statePath,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (workspace.provider.kind === 'opencode') {
|
|
122
|
+
inspectOpenCodeWorkspace(info, workspace, env, add);
|
|
123
|
+
}
|
|
124
|
+
else if (workspace.provider.kind === 'multica') {
|
|
125
|
+
inspectDeclaredMulticaWorkspace(workspace, env, add);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
add('provider.legacy', 'provider', 'pass', true, 'Legacy Prepared Workspace 已通过 workspace@1 parser 只读加载', {
|
|
129
|
+
provider: workspace.provider.kind, state: 'adapter-only',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const missing = checks.filter((check) => check.blocking && check.level !== 'pass')
|
|
133
|
+
.map((check) => check.id);
|
|
134
|
+
return {
|
|
135
|
+
$schema: 'dingtalk-agent/workspace-doctor@1',
|
|
136
|
+
ready: missing.length === 0,
|
|
137
|
+
workspace: workspace.name,
|
|
138
|
+
provider: workspace.provider.kind,
|
|
139
|
+
desiredHash: workspace.desiredHash,
|
|
140
|
+
observedHash: workspace.observedHash,
|
|
141
|
+
status: workspace.status,
|
|
142
|
+
checks,
|
|
143
|
+
missing,
|
|
144
|
+
configSources: workspace.configSources,
|
|
145
|
+
sideEffect: false,
|
|
146
|
+
dingtalkSideEffect: false,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function loadAgentProject(start) {
|
|
150
|
+
const root = findAgentProjectRoot(start);
|
|
151
|
+
if (!root) {
|
|
152
|
+
throw new Error(`找不到 ${PROJECT_FILE} 或 ${workspacePath('<project>')};请在 Agent Project 内运行`);
|
|
153
|
+
}
|
|
154
|
+
const manifestPath = join(root, PROJECT_FILE);
|
|
155
|
+
const legacyPath = workspacePath(root);
|
|
156
|
+
if (existsSync(legacyPath) && (lstatSync(legacyPath).isSymbolicLink() || !isInside(root, realpathSync(legacyPath)))) {
|
|
157
|
+
throw new Error('Legacy workspace manifest 不能是越界符号链接');
|
|
158
|
+
}
|
|
159
|
+
const legacy = existsSync(legacyPath) ? loadWorkspace(root) : null;
|
|
160
|
+
if (!existsSync(manifestPath)) {
|
|
161
|
+
if (!legacy)
|
|
162
|
+
throw new Error(`找不到 ${PROJECT_FILE}`);
|
|
163
|
+
const manifest = syntheticLegacyProject(root, legacy);
|
|
164
|
+
return {
|
|
165
|
+
root, source: 'legacy', manifestPath: '', manifest,
|
|
166
|
+
manifestHash: digest(stableStringify(manifest)), legacy,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (lstatSync(manifestPath).isSymbolicLink()) {
|
|
170
|
+
throw new Error(`${PROJECT_FILE} 不能是符号链接`);
|
|
171
|
+
}
|
|
172
|
+
let raw;
|
|
173
|
+
let source = '';
|
|
174
|
+
try {
|
|
175
|
+
source = readFileSync(manifestPath, 'utf8');
|
|
176
|
+
raw = JSON.parse(source);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
throw new Error(`${PROJECT_FILE} 不是合法 JSON: ${error.message}`);
|
|
180
|
+
}
|
|
181
|
+
rejectSecretLikeKeys(raw, PROJECT_FILE);
|
|
182
|
+
const manifest = validateProjectManifest(raw, root);
|
|
183
|
+
return {
|
|
184
|
+
root, source: 'manifest', manifestPath: realpathSync(manifestPath), manifest,
|
|
185
|
+
manifestHash: digest(stableStringify(manifest)), legacy,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function validateProjectManifest(value, root) {
|
|
189
|
+
const input = objectValue(value, 'Project manifest');
|
|
190
|
+
assertExactKeys(input, PROJECT_KEYS, 'Project manifest');
|
|
191
|
+
if (input.$schema !== 'dingtalk-agent/project@1') {
|
|
192
|
+
throw new Error('Project manifest 缺少 dingtalk-agent/project@1 schema');
|
|
193
|
+
}
|
|
194
|
+
const name = stableName(input.name, 'Project name');
|
|
195
|
+
const dtaVersion = requiredString(input.dtaVersion, 'dtaVersion');
|
|
196
|
+
const agentInput = objectValue(input.agent, 'Project agent');
|
|
197
|
+
assertExactKeys(agentInput, AGENT_KEYS, 'Project agent');
|
|
198
|
+
const definition = safeRelativePath(root, requiredString(agentInput.definition, 'agent.definition'), 'agent.definition', false);
|
|
199
|
+
if (!Array.isArray(agentInput.skills) || agentInput.skills.length === 0) {
|
|
200
|
+
throw new Error('agent.skills 必须是非空 string[]');
|
|
201
|
+
}
|
|
202
|
+
const skills = agentInput.skills.map((item, index) => stableName(item, `agent.skills[${index}]`));
|
|
203
|
+
if (new Set(skills).size !== skills.length)
|
|
204
|
+
throw new Error('agent.skills 不能包含重复项');
|
|
205
|
+
if (!skills.includes('dingtalk-basic-behavior')) {
|
|
206
|
+
throw new Error('agent.skills 必须显式包含 dingtalk-basic-behavior');
|
|
207
|
+
}
|
|
208
|
+
const workspaceInput = objectValue(input.workspaces, 'Project workspaces');
|
|
209
|
+
const workspaces = {};
|
|
210
|
+
for (const [workspaceName, rawWorkspace] of Object.entries(workspaceInput)) {
|
|
211
|
+
stableName(workspaceName, `Workspace name ${workspaceName}`);
|
|
212
|
+
if (workspaceName === LEGACY_WORKSPACE_NAME) {
|
|
213
|
+
throw new Error(`${LEGACY_WORKSPACE_NAME} 是 legacy adapter 保留名称`);
|
|
214
|
+
}
|
|
215
|
+
workspaces[workspaceName] = validateDeclaredWorkspace(rawWorkspace, root, workspaceName);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
$schema: 'dingtalk-agent/project@1', name, dtaVersion,
|
|
219
|
+
agent: { definition, skills }, workspaces,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function validateDeclaredWorkspace(value, root, name) {
|
|
223
|
+
const input = objectValue(value, `Workspace ${name}`);
|
|
224
|
+
assertAllowedKeys(input, WORKSPACE_KEYS, `Workspace ${name}`);
|
|
225
|
+
for (const required of ['stage', 'provider', 'storage']) {
|
|
226
|
+
if (!(required in input))
|
|
227
|
+
throw new Error(`Workspace ${name} 缺少字段: ${required}`);
|
|
228
|
+
}
|
|
229
|
+
const stage = requiredString(input.stage, `Workspace ${name}.stage`);
|
|
230
|
+
if (!['dev', 'test', 'staging', 'prod'].includes(stage)) {
|
|
231
|
+
throw new Error(`Workspace ${name}.stage 非法: ${stage}`);
|
|
232
|
+
}
|
|
233
|
+
const providerInput = objectValue(input.provider, `Workspace ${name}.provider`);
|
|
234
|
+
const kind = requiredString(providerInput.kind, `Workspace ${name}.provider.kind`);
|
|
235
|
+
let provider;
|
|
236
|
+
if (kind === 'opencode') {
|
|
237
|
+
assertExactKeys(providerInput, OPENCODE_KEYS, `Workspace ${name}.provider`);
|
|
238
|
+
provider = { kind, model: requiredString(providerInput.model, `Workspace ${name}.provider.model`) };
|
|
239
|
+
}
|
|
240
|
+
else if (kind === 'multica') {
|
|
241
|
+
assertAllowedKeys(providerInput, MULTICA_KEYS, `Workspace ${name}.provider`);
|
|
242
|
+
for (const required of ['kind', 'profile', 'workspaceIdFrom', 'runtimeIdFrom']) {
|
|
243
|
+
if (!(required in providerInput)) {
|
|
244
|
+
throw new Error(`Workspace ${name}.provider 缺少字段: ${required}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
provider = {
|
|
248
|
+
kind,
|
|
249
|
+
profile: requiredString(providerInput.profile, `Workspace ${name}.provider.profile`),
|
|
250
|
+
workspaceIdFrom: envReference(providerInput.workspaceIdFrom, `${name}.workspaceIdFrom`),
|
|
251
|
+
runtimeIdFrom: envReference(providerInput.runtimeIdFrom, `${name}.runtimeIdFrom`),
|
|
252
|
+
...(providerInput.agentIdFrom === undefined ? {} : {
|
|
253
|
+
agentIdFrom: envReference(providerInput.agentIdFrom, `${name}.agentIdFrom`),
|
|
254
|
+
}),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
throw new Error(`Workspace ${name}.provider.kind 只支持 opencode / multica`);
|
|
259
|
+
}
|
|
260
|
+
const storageInput = objectValue(input.storage, `Workspace ${name}.storage`);
|
|
261
|
+
assertAllowedKeys(storageInput, STORAGE_KEYS, `Workspace ${name}.storage`);
|
|
262
|
+
const memory = storageRoute(storageInput.memory, ['local-md', 'dingtalk-doc'], `${name}.memory`, root);
|
|
263
|
+
const knowledge = storageRoute(storageInput.knowledge, ['local-md', 'dingtalk-doc'], `${name}.knowledge`, root);
|
|
264
|
+
const artifacts = storageInput.artifacts === undefined ? undefined
|
|
265
|
+
: storageRoute(storageInput.artifacts, ['local-dir'], `${name}.artifacts`, root);
|
|
266
|
+
let authority;
|
|
267
|
+
if (input.authority !== undefined) {
|
|
268
|
+
const authorityInput = objectValue(input.authority, `Workspace ${name}.authority`);
|
|
269
|
+
assertExactKeys(authorityInput, AUTHORITY_KEYS, `Workspace ${name}.authority`);
|
|
270
|
+
authority = {
|
|
271
|
+
dwsProfileFrom: envReference(authorityInput.dwsProfileFrom, `${name}.dwsProfileFrom`),
|
|
272
|
+
expectedUserIdFrom: envReference(authorityInput.expectedUserIdFrom, `${name}.expectedUserIdFrom`),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (provider.kind === 'multica' && !authority) {
|
|
276
|
+
throw new Error(`Workspace ${name} 的 multica Provider 必须声明 authority env 来源`);
|
|
277
|
+
}
|
|
278
|
+
return { stage, provider, storage: { memory, knowledge, ...(artifacts ? { artifacts } : {}) },
|
|
279
|
+
...(authority ? { authority } : {}) };
|
|
280
|
+
}
|
|
281
|
+
function declaredView(loaded, name, workspace, currentWorkspace) {
|
|
282
|
+
const artifacts = workspace.storage.artifacts ||
|
|
283
|
+
`local-dir:.dingtalk-agent/artifacts/${name}`;
|
|
284
|
+
const storage = { ...workspace.storage, artifacts };
|
|
285
|
+
const sourceHash = projectSourceHash(loaded.root, loaded.manifest);
|
|
286
|
+
const desiredHash = digest(stableStringify({
|
|
287
|
+
project: loaded.manifest.name,
|
|
288
|
+
agent: loaded.manifest.agent,
|
|
289
|
+
workspace: name,
|
|
290
|
+
stage: workspace.stage,
|
|
291
|
+
provider: workspace.provider,
|
|
292
|
+
storage,
|
|
293
|
+
authority: workspace.authority || null,
|
|
294
|
+
sourceHash,
|
|
295
|
+
}));
|
|
296
|
+
const statePath = join(loaded.root, WORKSPACE_STATE_ROOT, `${name}.json`);
|
|
297
|
+
const state = existsSync(statePath)
|
|
298
|
+
? loadDevelopmentWorkspaceState(statePath, name, loaded.root) : null;
|
|
299
|
+
const drift = [];
|
|
300
|
+
if (state && state.provider !== workspace.provider.kind)
|
|
301
|
+
drift.push('provider');
|
|
302
|
+
if (state && state.desiredHash !== desiredHash)
|
|
303
|
+
drift.push('desiredHash');
|
|
304
|
+
const status = drift.length ? 'drifted' : state?.status || 'declared';
|
|
305
|
+
return {
|
|
306
|
+
name,
|
|
307
|
+
stage: workspace.stage,
|
|
308
|
+
provider: { ...workspace.provider },
|
|
309
|
+
storage,
|
|
310
|
+
authority: workspace.authority || null,
|
|
311
|
+
sourceHash,
|
|
312
|
+
desiredHash,
|
|
313
|
+
observedHash: state?.observedHash || '',
|
|
314
|
+
status,
|
|
315
|
+
current: currentWorkspace === name,
|
|
316
|
+
legacy: false,
|
|
317
|
+
statePath: relative(loaded.root, statePath),
|
|
318
|
+
state,
|
|
319
|
+
drift,
|
|
320
|
+
configSources: collectConfigSources(workspace),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function legacyView(loaded, spec, currentWorkspace) {
|
|
324
|
+
const storage = {
|
|
325
|
+
memory: spec.mounts.memory,
|
|
326
|
+
knowledge: spec.mounts.knowledge,
|
|
327
|
+
artifacts: 'local-dir:.dingtalk-agent/artifacts/legacy-prepared',
|
|
328
|
+
};
|
|
329
|
+
const desiredHash = digest(stableStringify({ provider: 'local-prepared', workspace: spec }));
|
|
330
|
+
return {
|
|
331
|
+
name: LEGACY_WORKSPACE_NAME,
|
|
332
|
+
stage: 'dev',
|
|
333
|
+
provider: { kind: 'local-prepared', contextId: spec.contextId, fieldId: spec.fieldId },
|
|
334
|
+
storage,
|
|
335
|
+
authority: spec.dws.profile || spec.dws.expectedUserId ? {
|
|
336
|
+
dwsProfileFrom: 'legacy:workspace.dws.profile',
|
|
337
|
+
expectedUserIdFrom: 'legacy:workspace.dws.expectedUserId',
|
|
338
|
+
} : null,
|
|
339
|
+
sourceHash: digest(stableStringify({ definition: spec.mounts.profile, skills: spec.skills })),
|
|
340
|
+
desiredHash,
|
|
341
|
+
observedHash: desiredHash,
|
|
342
|
+
status: 'ready',
|
|
343
|
+
current: currentWorkspace === LEGACY_WORKSPACE_NAME,
|
|
344
|
+
legacy: true,
|
|
345
|
+
statePath: relative(loaded.root, workspacePath(loaded.root)),
|
|
346
|
+
state: null,
|
|
347
|
+
drift: [],
|
|
348
|
+
configSources: ['legacy:.dingtalk-agent/workspace.json'],
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function projectSourceHash(root, manifest) {
|
|
352
|
+
const sources = {
|
|
353
|
+
definition: localContentHash(root, manifest.agent.definition),
|
|
354
|
+
opencode: localContentHash(root, 'opencode.json'),
|
|
355
|
+
};
|
|
356
|
+
for (const name of manifest.agent.skills) {
|
|
357
|
+
const ref = name === 'dingtalk-basic-behavior'
|
|
358
|
+
? `.agents/skills/${name}` : `skills/${name}`;
|
|
359
|
+
sources[`skill:${name}`] = localContentHash(root, ref);
|
|
360
|
+
const exposure = `.agents/skills/${name}`;
|
|
361
|
+
sources[`exposure:${name}`] = localContentHash(root, exposure);
|
|
362
|
+
}
|
|
363
|
+
return digest(stableStringify(sources));
|
|
364
|
+
}
|
|
365
|
+
function localContentHash(root, ref) {
|
|
366
|
+
const candidate = resolve(root, ref);
|
|
367
|
+
if (!isInside(root, candidate) || !existsSync(candidate))
|
|
368
|
+
return '';
|
|
369
|
+
const actual = realpathSync(candidate);
|
|
370
|
+
if (!isInside(root, actual))
|
|
371
|
+
throw new Error(`Project source 真实路径越界: ${ref}`);
|
|
372
|
+
const stat = lstatSync(actual);
|
|
373
|
+
if (stat.isSymbolicLink())
|
|
374
|
+
throw new Error(`Project source 不允许符号链接: ${ref}`);
|
|
375
|
+
if (stat.isFile())
|
|
376
|
+
return sha256(readFileSync(actual));
|
|
377
|
+
if (!stat.isDirectory())
|
|
378
|
+
throw new Error(`Project source 类型非法: ${ref}`);
|
|
379
|
+
const entries = [];
|
|
380
|
+
const visit = (directory, prefix = '') => {
|
|
381
|
+
for (const entry of readdirSorted(directory)) {
|
|
382
|
+
const path = join(directory, entry.name);
|
|
383
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
384
|
+
if (entry.isSymbolicLink())
|
|
385
|
+
throw new Error(`Project source 不允许符号链接: ${ref}/${relativePath}`);
|
|
386
|
+
if (entry.isDirectory())
|
|
387
|
+
visit(path, relativePath);
|
|
388
|
+
else if (entry.isFile())
|
|
389
|
+
entries.push({ path: relativePath, hash: sha256(readFileSync(path)) });
|
|
390
|
+
else
|
|
391
|
+
throw new Error(`Project source 类型非法: ${ref}/${relativePath}`);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
visit(actual);
|
|
395
|
+
return digest(stableStringify(entries));
|
|
396
|
+
}
|
|
397
|
+
function readdirSorted(directory) {
|
|
398
|
+
return readdirSync(directory, { withFileTypes: true })
|
|
399
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
400
|
+
}
|
|
401
|
+
function sha256(value) {
|
|
402
|
+
return createHash('sha256').update(value).digest('hex');
|
|
403
|
+
}
|
|
404
|
+
function loadDevelopmentWorkspaceState(path, workspace, root) {
|
|
405
|
+
if (lstatSync(path).isSymbolicLink())
|
|
406
|
+
throw new Error(`Workspace state 不能是符号链接: ${path}`);
|
|
407
|
+
if (!isInside(root, realpathSync(path)))
|
|
408
|
+
throw new Error(`Workspace state 真实路径越出 Project: ${path}`);
|
|
409
|
+
let raw;
|
|
410
|
+
try {
|
|
411
|
+
raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
throw new Error(`Workspace state 不是合法 JSON: ${error.message}`);
|
|
415
|
+
}
|
|
416
|
+
rejectSecretLikeKeys(raw, 'Workspace state');
|
|
417
|
+
const input = objectValue(raw, 'Workspace state');
|
|
418
|
+
assertExactKeys(input, STATE_KEYS, 'Workspace state');
|
|
419
|
+
if (input.$schema !== 'dingtalk-agent/workspace-state@1') {
|
|
420
|
+
throw new Error('Workspace state 缺少 dingtalk-agent/workspace-state@1 schema');
|
|
421
|
+
}
|
|
422
|
+
if (input.workspace !== workspace) {
|
|
423
|
+
throw new Error(`Workspace state 名称不一致: ${String(input.workspace)} != ${workspace}`);
|
|
424
|
+
}
|
|
425
|
+
const provider = requiredString(input.provider, 'Workspace state provider');
|
|
426
|
+
if (!['opencode', 'multica'].includes(provider))
|
|
427
|
+
throw new Error(`Workspace state provider 非法: ${provider}`);
|
|
428
|
+
const status = requiredString(input.status, 'Workspace state status');
|
|
429
|
+
if (!STATE_STATUSES.has(status))
|
|
430
|
+
throw new Error(`Workspace state status 非法: ${status}`);
|
|
431
|
+
const bindingInput = objectValue(input.binding, 'Workspace state binding');
|
|
432
|
+
const binding = Object.fromEntries(Object.entries(bindingInput).map(([key, item]) => [
|
|
433
|
+
key, optionalString(item, `Workspace state binding.${key}`),
|
|
434
|
+
]));
|
|
435
|
+
return {
|
|
436
|
+
$schema: 'dingtalk-agent/workspace-state@1', workspace, provider, status, binding,
|
|
437
|
+
desiredHash: requiredString(input.desiredHash, 'Workspace state desiredHash'),
|
|
438
|
+
observedHash: optionalString(input.observedHash, 'Workspace state observedHash'),
|
|
439
|
+
lastPlanId: optionalString(input.lastPlanId, 'Workspace state lastPlanId'),
|
|
440
|
+
lastReceiptId: optionalString(input.lastReceiptId, 'Workspace state lastReceiptId'),
|
|
441
|
+
lastEvalId: optionalString(input.lastEvalId, 'Workspace state lastEvalId'),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function inspectOpenCodeWorkspace(info, workspace, env, add) {
|
|
445
|
+
const executable = findExecutable('opencode', env);
|
|
446
|
+
add('provider.opencode-cli', 'provider', executable ? 'pass' : 'fail', true, executable ? 'OpenCode CLI 可执行' : 'PATH 找不到 opencode', { executable: executable || '' }, '安装 OpenCode,并确认 opencode --version 可执行');
|
|
447
|
+
add('provider.opencode-model', 'provider', workspace.provider.model ? 'pass' : 'fail', true, workspace.provider.model ? 'OpenCode model 已显式声明' : 'OpenCode model 缺失', { model: workspace.provider.model || '' });
|
|
448
|
+
const basicPath = '.agents/skills/dingtalk-basic-behavior/SKILL.md';
|
|
449
|
+
const basic = inspectLocalFile(info.root, basicPath);
|
|
450
|
+
const canonicalBasic = inspectLocalFile(resolvePackageRoot(import.meta.url), 'skills/dingtalk-basic-behavior/SKILL.md');
|
|
451
|
+
const basicMatched = basic.valid && canonicalBasic.valid && basic.hash === canonicalBasic.hash;
|
|
452
|
+
add('skills.basic-exposure', 'skills', basicMatched ? 'pass' : 'fail', true, basicMatched ? 'Basic Skill exposure 与当前 canonical source 一致'
|
|
453
|
+
: 'Basic Skill exposure 缺失、越界或 hash 漂移', { exposure: basic, canonicalHash: canonicalBasic.hash || '' }, `重新物化 ${basicPath}`);
|
|
454
|
+
const configPath = join(info.root, 'opencode.json');
|
|
455
|
+
const configFile = inspectLocalFile(info.root, 'opencode.json');
|
|
456
|
+
let configError = '';
|
|
457
|
+
let instructionCount = 0;
|
|
458
|
+
let permission = '';
|
|
459
|
+
let permissions = {};
|
|
460
|
+
if (configFile.valid) {
|
|
461
|
+
try {
|
|
462
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
463
|
+
const instructions = Array.isArray(config.instructions) ? config.instructions : [];
|
|
464
|
+
instructionCount = instructions.filter((item) => item === basicPath).length;
|
|
465
|
+
const skillPermissions = config.permission?.skill;
|
|
466
|
+
permissions = skillPermissions && typeof skillPermissions === 'object' &&
|
|
467
|
+
!Array.isArray(skillPermissions)
|
|
468
|
+
? Object.fromEntries(Object.entries(skillPermissions).map(([key, value]) => [key, String(value)]))
|
|
469
|
+
: {};
|
|
470
|
+
permission = permissions['dingtalk-basic-behavior'] || '';
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
configError = error.message;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else
|
|
477
|
+
configError = configFile.exists ? 'outside-or-not-file' : 'missing';
|
|
478
|
+
const configValid = !configError && instructionCount === 1 && permission === 'allow';
|
|
479
|
+
add('skills.basic-instruction', 'skills', configValid ? 'pass' : 'fail', true, configValid ? 'OpenCode config 强制加载且允许 Basic Skill' : 'OpenCode Basic instruction/permission 合同不完整', { path: 'opencode.json', error: configError, instructionCount, permission }, '运行 compose/bind 流程生成唯一 Basic instruction 与 allow permission');
|
|
480
|
+
for (const name of info.project.skills.filter((item) => item !== 'dingtalk-basic-behavior')) {
|
|
481
|
+
const source = inspectLocalFile(info.root, `skills/${name}/SKILL.md`);
|
|
482
|
+
const exposure = inspectLocalFile(info.root, `.agents/skills/${name}/SKILL.md`);
|
|
483
|
+
const matched = source.valid && exposure.valid && source.hash === exposure.hash &&
|
|
484
|
+
permissions[name] === 'allow';
|
|
485
|
+
add(`skills.role.${name}`, 'skills', matched ? 'pass' : 'fail', true, matched ? `Role Skill ${name} source/exposure hash 与 permission 一致`
|
|
486
|
+
: `Role Skill ${name} source、exposure hash 或 permission 不一致`, { source, exposure, permission: permissions[name] || '' }, `运行 compose/bind 流程物化 ${name}`);
|
|
487
|
+
}
|
|
488
|
+
const managedRef = workspace.state?.binding.managedPath || '';
|
|
489
|
+
if (managedRef) {
|
|
490
|
+
const managedHash = localContentHash(info.root, managedRef);
|
|
491
|
+
const matched = Boolean(managedHash && managedHash === workspace.state?.observedHash &&
|
|
492
|
+
workspace.state?.binding.sourceHash === workspace.sourceHash &&
|
|
493
|
+
workspace.state?.binding.model === workspace.provider.model);
|
|
494
|
+
add('provider.managed-integrity', 'provider', matched ? 'pass' : 'fail', true, matched ? 'Managed Workspace、source/model 与 observed hash 一致'
|
|
495
|
+
: 'Managed Workspace 缺失或 source/model/observed hash 漂移', {
|
|
496
|
+
path: managedRef,
|
|
497
|
+
actualHash: managedHash,
|
|
498
|
+
expectedHash: workspace.state?.observedHash || '',
|
|
499
|
+
sourceMatched: workspace.state?.binding.sourceHash === workspace.sourceHash,
|
|
500
|
+
modelMatched: workspace.state?.binding.model === workspace.provider.model,
|
|
501
|
+
}, `重新执行 dta workspace create ${workspace.name} --yes`);
|
|
502
|
+
}
|
|
503
|
+
const loadVerified = workspace.state?.status === 'ready' && Boolean(workspace.state.lastReceiptId || workspace.state.lastEvalId);
|
|
504
|
+
add('provider.load-proof', 'provider', loadVerified ? 'pass' : 'warn', false, loadVerified
|
|
505
|
+
? 'Provider state 已绑定通过的随机 load/eval 证据;详情见 lastReceiptId/lastEvalId'
|
|
506
|
+
: '尚未取得随机 load probe;先执行 workspace run/eval', {
|
|
507
|
+
executed: Boolean(workspace.state?.lastReceiptId || workspace.state?.lastEvalId),
|
|
508
|
+
lastReceiptId: workspace.state?.lastReceiptId || '',
|
|
509
|
+
lastEvalId: workspace.state?.lastEvalId || '',
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
function inspectDeclaredMulticaWorkspace(workspace, env, add) {
|
|
513
|
+
const executable = findExecutable('multica', env);
|
|
514
|
+
add('provider.multica-cli', 'provider', executable ? 'pass' : 'fail', true, executable ? 'Multica CLI 可执行' : 'PATH 找不到 multica;仅当前 Multica Workspace 不就绪', { executable: executable || '' }, '安装 Multica CLI;其他 Provider Workspace 不受影响');
|
|
515
|
+
const refs = [
|
|
516
|
+
['workspace-id', workspace.provider.workspaceIdFrom],
|
|
517
|
+
['runtime-id', workspace.provider.runtimeIdFrom],
|
|
518
|
+
['dws-profile', workspace.authority?.dwsProfileFrom || ''],
|
|
519
|
+
['expected-user-id', workspace.authority?.expectedUserIdFrom || ''],
|
|
520
|
+
];
|
|
521
|
+
for (const [id, ref] of refs) {
|
|
522
|
+
const variable = ref.startsWith('env:') ? ref.slice(4) : '';
|
|
523
|
+
const configured = Boolean(variable && env[variable]);
|
|
524
|
+
add(`authority.${id}`, 'authority', configured ? 'pass' : 'fail', true, configured ? `${id} 来源已解析` : `${id} 环境来源未设置`, { source: ref, configured }, `设置 ${ref || '<missing-env-ref>'}`);
|
|
525
|
+
}
|
|
526
|
+
const agentRef = workspace.provider.agentIdFrom || '';
|
|
527
|
+
const agentVariable = agentRef.startsWith('env:') ? agentRef.slice(4) : '';
|
|
528
|
+
const agentConfigured = Boolean(agentVariable && env[agentVariable]);
|
|
529
|
+
add('authority.agent-id', 'authority', agentRef && agentConfigured ? 'pass' : 'warn', false, agentRef
|
|
530
|
+
? agentConfigured ? 'agent-id 可信来源已解析' : 'agent-id 来源未设置;只允许列候选,不按名称绑定'
|
|
531
|
+
: '未声明 agent-id;W3 只列候选,W4 create 后必须保存稳定 ID', { source: agentRef, configured: agentConfigured }, agentRef ? `设置 ${agentRef}` : '已有 Agent 时声明 provider.agentIdFrom;新 Agent 由 W4 apply 保存 ID');
|
|
532
|
+
const inspectionId = workspace.state?.binding.inspectionId || '';
|
|
533
|
+
add('provider.remote-inspect', 'provider', 'warn', false, inspectionId
|
|
534
|
+
? '已有脱敏 Multica 只读 inspection;W4 load/smoke 前仍不能 ready'
|
|
535
|
+
: '尚未运行 Multica 只读 inspection;不会创建或更新远端资源', {
|
|
536
|
+
executed: Boolean(inspectionId), inspectionId, remoteWrite: false,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
function inspectStorageRoute(root, uri) {
|
|
540
|
+
if (uri.startsWith('dingtalk-doc:')) {
|
|
541
|
+
return { valid: false, provider: 'dingtalk-doc', local: false, checked: false };
|
|
542
|
+
}
|
|
543
|
+
const separator = uri.indexOf(':');
|
|
544
|
+
const provider = uri.slice(0, separator);
|
|
545
|
+
const ref = uri.slice(separator + 1);
|
|
546
|
+
const inspected = provider === 'local-md'
|
|
547
|
+
? inspectLocalFile(root, ref)
|
|
548
|
+
: inspectLocalDirectory(root, ref);
|
|
549
|
+
return { ...inspected, provider, local: true, checked: true };
|
|
550
|
+
}
|
|
551
|
+
function inspectLocalFile(root, ref) {
|
|
552
|
+
const inspected = inspectLocalPath(root, ref);
|
|
553
|
+
const valid = inspected.inside && inspected.exists && inspected.type === 'file';
|
|
554
|
+
return { ...inspected, valid, fix: valid ? undefined : `创建 Project 内文件 ${ref}` };
|
|
555
|
+
}
|
|
556
|
+
function inspectLocalDirectory(root, ref) {
|
|
557
|
+
const inspected = inspectLocalPath(root, ref);
|
|
558
|
+
const valid = inspected.inside && inspected.exists && inspected.type === 'directory';
|
|
559
|
+
return { ...inspected, valid, fix: valid ? undefined : `创建 Project 内目录 ${ref}` };
|
|
560
|
+
}
|
|
561
|
+
function inspectLocalPath(root, ref) {
|
|
562
|
+
const candidate = resolve(root, ref);
|
|
563
|
+
const inside = isInside(root, candidate);
|
|
564
|
+
if (!inside || !existsSync(candidate)) {
|
|
565
|
+
return { path: ref, inside, exists: false, type: 'missing' };
|
|
566
|
+
}
|
|
567
|
+
const actual = realpathSync(candidate);
|
|
568
|
+
const actualInside = isInside(root, actual);
|
|
569
|
+
const stat = statSync(actual);
|
|
570
|
+
return {
|
|
571
|
+
path: relative(root, actual), inside: actualInside, exists: true,
|
|
572
|
+
type: stat.isFile() ? 'file' : stat.isDirectory() ? 'directory' : 'other',
|
|
573
|
+
...(stat.isFile() && actualInside ? { hash: digest(readFileSync(actual, 'utf8')) } : {}),
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function syntheticLegacyProject(root, workspace) {
|
|
577
|
+
const definition = workspace.mounts.profile.startsWith('local-md:')
|
|
578
|
+
? workspace.mounts.profile.slice('local-md:'.length) : 'WORKSPACE.md';
|
|
579
|
+
return {
|
|
580
|
+
$schema: 'dingtalk-agent/project@1',
|
|
581
|
+
name: stableName(basename(root) || 'legacy-agent', 'Legacy Project name'),
|
|
582
|
+
dtaVersion: '*',
|
|
583
|
+
agent: { definition, skills: [...workspace.skills] },
|
|
584
|
+
workspaces: {},
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function readCurrentWorkspace(root) {
|
|
588
|
+
const path = join(root, CURRENT_WORKSPACE_FILE);
|
|
589
|
+
if (!existsSync(path))
|
|
590
|
+
return '';
|
|
591
|
+
if (lstatSync(path).isSymbolicLink())
|
|
592
|
+
throw new Error('current-workspace 不能是符号链接');
|
|
593
|
+
if (!isInside(root, realpathSync(path)))
|
|
594
|
+
throw new Error('current-workspace 真实路径越出 Project');
|
|
595
|
+
const value = readFileSync(path, 'utf8').trim();
|
|
596
|
+
return value ? stableName(value, 'current-workspace') : '';
|
|
597
|
+
}
|
|
598
|
+
function collectConfigSources(workspace) {
|
|
599
|
+
const sources = [
|
|
600
|
+
`manifest:provider.kind=${workspace.provider.kind}`,
|
|
601
|
+
`manifest:stage=${workspace.stage}`,
|
|
602
|
+
];
|
|
603
|
+
if (workspace.provider.kind === 'opencode')
|
|
604
|
+
sources.push('manifest:provider.model');
|
|
605
|
+
else
|
|
606
|
+
sources.push('manifest:provider.profile', workspace.provider.workspaceIdFrom, workspace.provider.runtimeIdFrom, ...(workspace.provider.agentIdFrom ? [workspace.provider.agentIdFrom] : []));
|
|
607
|
+
if (workspace.authority)
|
|
608
|
+
sources.push(workspace.authority.dwsProfileFrom, workspace.authority.expectedUserIdFrom);
|
|
609
|
+
return [...new Set(sources)];
|
|
610
|
+
}
|
|
611
|
+
function storageRoute(value, providers, label, root) {
|
|
612
|
+
const text = requiredString(value, label);
|
|
613
|
+
const separator = text.indexOf(':');
|
|
614
|
+
const provider = separator < 0 ? '' : text.slice(0, separator);
|
|
615
|
+
const ref = separator < 0 ? '' : text.slice(separator + 1).trim();
|
|
616
|
+
if (!providers.includes(provider) || !ref) {
|
|
617
|
+
throw new Error(`${label} Storage 非法;允许 ${providers.map((item) => `${item}:<ref>`).join(' / ')}`);
|
|
618
|
+
}
|
|
619
|
+
if (provider.startsWith('local-'))
|
|
620
|
+
safeRelativePath(root, ref, label, true);
|
|
621
|
+
return `${provider}:${ref}`;
|
|
622
|
+
}
|
|
623
|
+
function safeRelativePath(root, input, label, allowMissing) {
|
|
624
|
+
if (input.includes('\0'))
|
|
625
|
+
throw new Error(`${label} 包含 NUL`); // defensive for non-POSIX callers
|
|
626
|
+
const candidate = resolve(root, input);
|
|
627
|
+
if (!isInside(root, candidate) || candidate === root)
|
|
628
|
+
throw new Error(`${label} 必须位于 Project 内: ${input}`);
|
|
629
|
+
if (existsSync(candidate)) {
|
|
630
|
+
const actual = realpathSync(candidate);
|
|
631
|
+
if (!isInside(root, actual))
|
|
632
|
+
throw new Error(`${label} 真实路径越出 Project: ${input}`);
|
|
633
|
+
}
|
|
634
|
+
else if (!allowMissing && !isInside(root, candidate)) {
|
|
635
|
+
throw new Error(`${label} 越出 Project: ${input}`);
|
|
636
|
+
}
|
|
637
|
+
return relative(root, candidate).split('\\').join('/');
|
|
638
|
+
}
|
|
639
|
+
function envReference(value, label) {
|
|
640
|
+
const text = requiredString(value, label);
|
|
641
|
+
if (!ENV_REF.test(text))
|
|
642
|
+
throw new Error(`${label} 必须是 env:UPPER_CASE_VAR 引用`);
|
|
643
|
+
return text;
|
|
644
|
+
}
|
|
645
|
+
function stableName(value, label) {
|
|
646
|
+
const text = requiredString(value, label);
|
|
647
|
+
if (!NAME.test(text))
|
|
648
|
+
throw new Error(`${label} 非法: ${text}`);
|
|
649
|
+
return text;
|
|
650
|
+
}
|
|
651
|
+
function requiredString(value, label) {
|
|
652
|
+
if (typeof value !== 'string' || !value.trim())
|
|
653
|
+
throw new Error(`${label} 必须是非空字符串`);
|
|
654
|
+
return value.trim();
|
|
655
|
+
}
|
|
656
|
+
function optionalString(value, label) {
|
|
657
|
+
if (typeof value !== 'string')
|
|
658
|
+
throw new Error(`${label} 必须是字符串`);
|
|
659
|
+
return value.trim();
|
|
660
|
+
}
|
|
661
|
+
function objectValue(value, label) {
|
|
662
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
663
|
+
throw new Error(`${label} 必须是 JSON object`);
|
|
664
|
+
}
|
|
665
|
+
return value;
|
|
666
|
+
}
|
|
667
|
+
function assertExactKeys(value, allowed, label) {
|
|
668
|
+
assertAllowedKeys(value, allowed, label);
|
|
669
|
+
const missing = [...allowed].filter((key) => !(key in value));
|
|
670
|
+
if (missing.length)
|
|
671
|
+
throw new Error(`${label} 缺少字段: ${missing.join(', ')}`);
|
|
672
|
+
}
|
|
673
|
+
function assertAllowedKeys(value, allowed, label) {
|
|
674
|
+
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
675
|
+
if (unknown.length)
|
|
676
|
+
throw new Error(`${label} 包含未知字段: ${unknown.join(', ')}`);
|
|
677
|
+
}
|
|
678
|
+
function rejectSecretLikeKeys(value, label, path = []) {
|
|
679
|
+
if (Array.isArray(value)) {
|
|
680
|
+
value.forEach((item, index) => rejectSecretLikeKeys(item, label, [...path, String(index)]));
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (!value || typeof value !== 'object')
|
|
684
|
+
return;
|
|
685
|
+
for (const [key, item] of Object.entries(value)) {
|
|
686
|
+
const next = [...path, key];
|
|
687
|
+
if (SECRET_KEY.test(key)) {
|
|
688
|
+
throw new Error(`${label} 禁止凭据字段: ${next.join('.')}`);
|
|
689
|
+
}
|
|
690
|
+
rejectSecretLikeKeys(item, label, next);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
function versionSatisfies(actualInput, rangeInput) {
|
|
694
|
+
const actual = parseVersion(actualInput);
|
|
695
|
+
const range = rangeInput.trim();
|
|
696
|
+
if (range === '*' || range === 'latest')
|
|
697
|
+
return true;
|
|
698
|
+
const operator = range.startsWith('^') ? '^' : range.startsWith('~') ? '~' : '=';
|
|
699
|
+
const expected = parseVersion(operator === '=' ? range : range.slice(1));
|
|
700
|
+
if (!actual || !expected)
|
|
701
|
+
return false;
|
|
702
|
+
if (operator === '=')
|
|
703
|
+
return actual.join('.') === expected.join('.');
|
|
704
|
+
if (compareVersion(actual, expected) < 0)
|
|
705
|
+
return false;
|
|
706
|
+
if (operator === '~')
|
|
707
|
+
return actual[0] === expected[0] && actual[1] === expected[1];
|
|
708
|
+
if (expected[0] > 0)
|
|
709
|
+
return actual[0] === expected[0];
|
|
710
|
+
if (expected[1] > 0)
|
|
711
|
+
return actual[0] === 0 && actual[1] === expected[1];
|
|
712
|
+
return actual[0] === 0 && actual[1] === 0 && actual[2] === expected[2];
|
|
713
|
+
}
|
|
714
|
+
function parseVersion(value) {
|
|
715
|
+
const match = value.trim().replace(/^v/, '').match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
716
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
717
|
+
}
|
|
718
|
+
function compareVersion(a, b) {
|
|
719
|
+
for (let index = 0; index < 3; index += 1) {
|
|
720
|
+
if (a[index] !== b[index])
|
|
721
|
+
return a[index] - b[index];
|
|
722
|
+
}
|
|
723
|
+
return 0;
|
|
724
|
+
}
|
|
725
|
+
function isInside(root, candidate) {
|
|
726
|
+
const rel = relative(root, candidate);
|
|
727
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`));
|
|
728
|
+
}
|
|
729
|
+
//# sourceMappingURL=development-workspace.js.map
|