@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,685 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync, } from 'node:fs';
|
|
4
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
5
|
+
import { WORKSPACE_STATE_ROOT, inspectAgentProject, showDevelopmentWorkspace, } from './development-workspace.js';
|
|
6
|
+
import { digest, stableStringify } from './events.js';
|
|
7
|
+
export const MULTICA_EVIDENCE_ROOT = '.dingtalk-agent/development-workspace-evidence';
|
|
8
|
+
export const MULTICA_BOOT_SKILL = 'dingtalk-agent-boot-multica';
|
|
9
|
+
class MulticaReadFailure extends Error {
|
|
10
|
+
category;
|
|
11
|
+
constructor(category) {
|
|
12
|
+
super(category);
|
|
13
|
+
this.category = category;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function planMulticaWorkspace(start, name, options = {}) {
|
|
17
|
+
const target = resolveMulticaTarget(start, name, options.env || process.env, options.cliVersion || '', options.agentIdOverride);
|
|
18
|
+
const state = target.workspace.state;
|
|
19
|
+
const expected = {
|
|
20
|
+
workspaceId: target.workspaceId,
|
|
21
|
+
runtimeId: target.runtimeId,
|
|
22
|
+
agentId: target.agentId,
|
|
23
|
+
skills: [...target.skills].sort(),
|
|
24
|
+
};
|
|
25
|
+
const observed = {
|
|
26
|
+
workspaceId: state?.binding.remoteWorkspaceId || '',
|
|
27
|
+
runtimeId: state?.binding.runtimeId || '',
|
|
28
|
+
agentId: state?.binding.agentId || '',
|
|
29
|
+
skillSetHash: state?.binding.observedSkillSetHash || '',
|
|
30
|
+
};
|
|
31
|
+
const expectedSkillSetHash = digest(stableStringify(expected.skills));
|
|
32
|
+
const diff = [];
|
|
33
|
+
if (observed.workspaceId !== expected.workspaceId)
|
|
34
|
+
diff.push('workspace.scope');
|
|
35
|
+
if (observed.runtimeId !== expected.runtimeId)
|
|
36
|
+
diff.push('runtime.binding');
|
|
37
|
+
if (expected.agentId) {
|
|
38
|
+
if (observed.agentId !== expected.agentId)
|
|
39
|
+
diff.push('agent.binding');
|
|
40
|
+
}
|
|
41
|
+
else if (!observed.agentId)
|
|
42
|
+
diff.push('agent.create');
|
|
43
|
+
if (observed.skillSetHash !== expectedSkillSetHash)
|
|
44
|
+
diff.push('skills.assignments');
|
|
45
|
+
const operations = readOperations(target);
|
|
46
|
+
const planId = `multica_plan_${digest(stableStringify({
|
|
47
|
+
workspace: name,
|
|
48
|
+
desiredHash: target.workspace.desiredHash,
|
|
49
|
+
expected,
|
|
50
|
+
observed,
|
|
51
|
+
diff: [...diff].sort(),
|
|
52
|
+
blocking: [...target.blocking].sort(),
|
|
53
|
+
operations,
|
|
54
|
+
})).slice(0, 24)}`;
|
|
55
|
+
return {
|
|
56
|
+
$schema: 'dingtalk-agent/multica-workspace-plan@1',
|
|
57
|
+
planId,
|
|
58
|
+
workspace: name,
|
|
59
|
+
provider: 'multica',
|
|
60
|
+
profile: target.profile,
|
|
61
|
+
desiredHash: target.workspace.desiredHash,
|
|
62
|
+
currentState: target.workspace.status,
|
|
63
|
+
expected,
|
|
64
|
+
observed,
|
|
65
|
+
diff: [...diff].sort(),
|
|
66
|
+
blocking: [...target.blocking].sort(),
|
|
67
|
+
operations,
|
|
68
|
+
writes: [],
|
|
69
|
+
remoteRead: false,
|
|
70
|
+
remoteWrite: false,
|
|
71
|
+
sideEffect: false,
|
|
72
|
+
dingtalkSideEffect: false,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function inspectMulticaWorkspace(start, name, options = {}) {
|
|
76
|
+
const plan = planMulticaWorkspace(start, name, options);
|
|
77
|
+
if (!options.execute)
|
|
78
|
+
return plan;
|
|
79
|
+
requireReadConfirmation(options, 'workspace inspect');
|
|
80
|
+
if (plan.blocking.length) {
|
|
81
|
+
throw new Error(`Multica inspect 前置条件不完整: ${plan.blocking.join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
return executeInspection(start, name, plan, options, options.persist !== false);
|
|
84
|
+
}
|
|
85
|
+
export function listMulticaWorkspaceResources(start, name, options = {}) {
|
|
86
|
+
const plan = planMulticaWorkspace(start, name, options);
|
|
87
|
+
if (!options.execute)
|
|
88
|
+
return plan;
|
|
89
|
+
requireReadConfirmation(options, 'workspace remote-list');
|
|
90
|
+
if (plan.blocking.length) {
|
|
91
|
+
throw new Error(`Multica remote-list 前置条件不完整: ${plan.blocking.join(', ')}`);
|
|
92
|
+
}
|
|
93
|
+
const inspection = executeInspection(start, name, plan, options, false);
|
|
94
|
+
return {
|
|
95
|
+
$schema: 'dingtalk-agent/multica-workspace-resource-list@1',
|
|
96
|
+
passed: inspection.passed,
|
|
97
|
+
workspace: inspection.workspace,
|
|
98
|
+
provider: 'multica',
|
|
99
|
+
profile: inspection.profile,
|
|
100
|
+
inspectionId: inspection.inspectionId,
|
|
101
|
+
resources: inspection.resources,
|
|
102
|
+
failures: inspection.failures,
|
|
103
|
+
calls: inspection.calls,
|
|
104
|
+
remoteRead: true,
|
|
105
|
+
remoteWrite: false,
|
|
106
|
+
sideEffect: 'remote-read',
|
|
107
|
+
dingtalkSideEffect: false,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export function statusMulticaWorkspace(start, name, cliVersion = '') {
|
|
111
|
+
const info = inspectAgentProject(start, cliVersion);
|
|
112
|
+
const workspace = requireMulticaWorkspace(info.root, name, cliVersion);
|
|
113
|
+
const latestPath = latestPointerPath(info.root, name);
|
|
114
|
+
if (!existsSync(latestPath))
|
|
115
|
+
return emptyStatus(workspace);
|
|
116
|
+
const pointer = readJsonObject(latestPath, 'Multica inspection latest pointer');
|
|
117
|
+
if (pointer.$schema !== 'dingtalk-agent/multica-inspection-pointer@1') {
|
|
118
|
+
throw new Error('Multica inspection latest pointer schema 非法');
|
|
119
|
+
}
|
|
120
|
+
const evidencePath = stringField(pointer.evidencePath, 'pointer.evidencePath');
|
|
121
|
+
const reportPath = safeProjectPath(info.root, evidencePath, true);
|
|
122
|
+
const reportSource = readFileSync(reportPath, 'utf8');
|
|
123
|
+
const evidenceIntegrity = sha256(reportSource) === stringField(pointer.reportHash, 'pointer.reportHash');
|
|
124
|
+
const report = JSON.parse(reportSource);
|
|
125
|
+
if (report.$schema !== 'dingtalk-agent/multica-workspace-inspection@1' || report.workspace !== name) {
|
|
126
|
+
throw new Error('Multica inspection report 与 Workspace 不匹配');
|
|
127
|
+
}
|
|
128
|
+
assertRedacted(report);
|
|
129
|
+
const state = workspace.state;
|
|
130
|
+
const stateMatched = Boolean(evidenceIntegrity && report.passed && state &&
|
|
131
|
+
state.provider === 'multica' && ['verifying', 'ready'].includes(state.status) &&
|
|
132
|
+
state.desiredHash === workspace.desiredHash && state.observedHash === report.observedHash &&
|
|
133
|
+
state.binding.inspectionId === report.inspectionId &&
|
|
134
|
+
state.binding.evidencePath === evidencePath);
|
|
135
|
+
return {
|
|
136
|
+
$schema: 'dingtalk-agent/multica-workspace-status@1',
|
|
137
|
+
workspace: name,
|
|
138
|
+
provider: 'multica',
|
|
139
|
+
available: true,
|
|
140
|
+
inspectionId: report.inspectionId,
|
|
141
|
+
passed: report.passed,
|
|
142
|
+
readyForApply: report.readyForApply && stateMatched,
|
|
143
|
+
providerReady: false,
|
|
144
|
+
evidencePath,
|
|
145
|
+
evidenceIntegrity,
|
|
146
|
+
stateMatched,
|
|
147
|
+
desiredHash: workspace.desiredHash,
|
|
148
|
+
observedHash: report.observedHash,
|
|
149
|
+
diff: [...report.diff],
|
|
150
|
+
failures: [...report.failures],
|
|
151
|
+
remoteRead: false,
|
|
152
|
+
remoteWrite: false,
|
|
153
|
+
sideEffect: false,
|
|
154
|
+
dingtalkSideEffect: false,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function executeInspection(start, name, plan, options, persist) {
|
|
158
|
+
const env = options.env || process.env;
|
|
159
|
+
const target = resolveMulticaTarget(start, name, env, options.cliVersion || '', options.agentIdOverride);
|
|
160
|
+
const command = options.command || env.DTA_MULTICA_BIN || 'multica';
|
|
161
|
+
const timeoutMs = positiveTimeout(options.timeoutMs);
|
|
162
|
+
if (persist) {
|
|
163
|
+
const plannedEvidence = options.out || join(MULTICA_EVIDENCE_ROOT, safeName(name), 'multica-inspections', 'planned.json');
|
|
164
|
+
safeProjectPath(target.root, plannedEvidence, false);
|
|
165
|
+
latestPointerPath(target.root, name);
|
|
166
|
+
safeProjectPath(target.root, join(WORKSPACE_STATE_ROOT, `${name}.json`), false);
|
|
167
|
+
}
|
|
168
|
+
const calls = [];
|
|
169
|
+
const failures = [];
|
|
170
|
+
const diff = [];
|
|
171
|
+
let cliVersion = '';
|
|
172
|
+
let principalHash = '';
|
|
173
|
+
let serverHash = '';
|
|
174
|
+
let resources = emptyResources();
|
|
175
|
+
try {
|
|
176
|
+
const version = runRead(command, ['--version'], 'cli.version', target.root, env, timeoutMs, calls);
|
|
177
|
+
cliVersion = parseMulticaVersion(version.stdout || version.stderr);
|
|
178
|
+
const config = runRead(command, profileArgs(target, ['config', 'show']), 'profile.config', target.root, env, timeoutMs, calls);
|
|
179
|
+
const parsedConfig = parseConfig(config.stdout, target.profile);
|
|
180
|
+
serverHash = parsedConfig.server ? digest(parsedConfig.server) : '';
|
|
181
|
+
if (parsedConfig.workspaceId && parsedConfig.workspaceId !== target.workspaceId) {
|
|
182
|
+
throw new MulticaReadFailure('scope.profile-workspace-mismatch');
|
|
183
|
+
}
|
|
184
|
+
const auth = runRead(command, profileArgs(target, ['auth', 'status']), 'auth.status', target.root, env, timeoutMs, calls);
|
|
185
|
+
const principal = parseAuth(auth.stdout + '\n' + auth.stderr);
|
|
186
|
+
if (!principal)
|
|
187
|
+
throw new MulticaReadFailure('auth.not-authenticated');
|
|
188
|
+
principalHash = digest(principal);
|
|
189
|
+
const workspaceList = jsonArray(runRead(command, profileArgs(target, ['workspace', 'list', '--output', 'json']), 'workspace.list', target.root, env, timeoutMs, calls).stdout, 'workspace list');
|
|
190
|
+
const workspaceMatches = workspaceList.filter((item) => idOf(item) === target.workspaceId);
|
|
191
|
+
if (workspaceMatches.length !== 1)
|
|
192
|
+
throw new MulticaReadFailure('scope.workspace-list-mismatch');
|
|
193
|
+
const workspaceGet = jsonObject(runRead(command, profileArgs(target, ['workspace', 'get', target.workspaceId, '--output', 'json']), 'workspace.get', target.root, env, timeoutMs, calls).stdout, 'workspace get');
|
|
194
|
+
if (idOf(workspaceGet) !== target.workspaceId) {
|
|
195
|
+
throw new MulticaReadFailure('scope.workspace-get-mismatch');
|
|
196
|
+
}
|
|
197
|
+
resources.workspace = {
|
|
198
|
+
id: resourceId(idOf(workspaceGet), 'workspace.id'),
|
|
199
|
+
name: safeDisplay(workspaceGet.name),
|
|
200
|
+
slug: safeDisplay(workspaceGet.slug),
|
|
201
|
+
};
|
|
202
|
+
const runtimes = jsonArray(runRead(command, scopedArgs(target, ['runtime', 'list', '--output', 'json']), 'runtime.list', target.root, env, timeoutMs, calls).stdout, 'runtime list');
|
|
203
|
+
const runtimeMatches = runtimes.filter((item) => idOf(item) === target.runtimeId);
|
|
204
|
+
if (runtimeMatches.length !== 1)
|
|
205
|
+
throw new MulticaReadFailure('scope.runtime-mismatch');
|
|
206
|
+
const runtime = runtimeMatches[0];
|
|
207
|
+
const runtimeWorkspace = optionalField(runtime.workspace_id);
|
|
208
|
+
if (runtimeWorkspace && runtimeWorkspace !== target.workspaceId) {
|
|
209
|
+
throw new MulticaReadFailure('scope.runtime-workspace-mismatch');
|
|
210
|
+
}
|
|
211
|
+
resources.runtime = {
|
|
212
|
+
id: resourceId(idOf(runtime), 'runtime.id'),
|
|
213
|
+
workspaceId: runtimeWorkspace || target.workspaceId,
|
|
214
|
+
name: safeDisplay(runtime.name),
|
|
215
|
+
provider: safeDisplay(runtime.provider),
|
|
216
|
+
status: safeDisplay(runtime.status),
|
|
217
|
+
};
|
|
218
|
+
const agents = jsonArray(runRead(command, scopedArgs(target, ['agent', 'list', '--include-archived', '--output', 'json']), 'agent.list', target.root, env, timeoutMs, calls).stdout, 'agent list');
|
|
219
|
+
resources.agentCandidates = agents
|
|
220
|
+
.filter((item) => optionalField(item.name) === target.projectName)
|
|
221
|
+
.map((item) => ({
|
|
222
|
+
id: resourceId(idOf(item), 'agent candidate.id'),
|
|
223
|
+
name: safeDisplay(item.name),
|
|
224
|
+
archived: Boolean(optionalField(item.archived_at)),
|
|
225
|
+
}))
|
|
226
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
227
|
+
const catalog = jsonArray(runRead(command, scopedArgs(target, ['skill', 'list', '--output', 'json']), 'skill.list', target.root, env, timeoutMs, calls).stdout, 'skill list');
|
|
228
|
+
resources.catalogSkills = catalog.map((item) => ({
|
|
229
|
+
id: resourceId(idOf(item), 'skill.id'),
|
|
230
|
+
workspaceId: optionalField(item.workspace_id) || target.workspaceId,
|
|
231
|
+
name: safeDisplay(item.name),
|
|
232
|
+
})).sort(compareNamedResource);
|
|
233
|
+
if (resources.catalogSkills.some((item) => item.workspaceId !== target.workspaceId)) {
|
|
234
|
+
throw new MulticaReadFailure('scope.skill-workspace-mismatch');
|
|
235
|
+
}
|
|
236
|
+
if (target.agentId) {
|
|
237
|
+
const agentMatches = agents.filter((item) => idOf(item) === target.agentId);
|
|
238
|
+
if (agentMatches.length !== 1)
|
|
239
|
+
throw new MulticaReadFailure('scope.agent-list-mismatch');
|
|
240
|
+
const agent = jsonObject(runRead(command, scopedArgs(target, ['agent', 'get', target.agentId, '--output', 'json']), 'agent.get', target.root, env, timeoutMs, calls).stdout, 'agent get');
|
|
241
|
+
const agentWorkspace = optionalField(agent.workspace_id);
|
|
242
|
+
const agentRuntime = optionalField(agent.runtime_id);
|
|
243
|
+
if (idOf(agent) !== target.agentId || agentWorkspace !== target.workspaceId ||
|
|
244
|
+
agentRuntime !== target.runtimeId) {
|
|
245
|
+
throw new MulticaReadFailure('scope.agent-binding-mismatch');
|
|
246
|
+
}
|
|
247
|
+
resources.agent = {
|
|
248
|
+
id: target.agentId,
|
|
249
|
+
workspaceId: agentWorkspace,
|
|
250
|
+
runtimeId: agentRuntime,
|
|
251
|
+
name: safeDisplay(agent.name),
|
|
252
|
+
status: safeDisplay(agent.status),
|
|
253
|
+
archived: Boolean(optionalField(agent.archived_at)),
|
|
254
|
+
};
|
|
255
|
+
const assigned = jsonArray(runRead(command, scopedArgs(target, ['agent', 'skills', 'list', target.agentId, '--output', 'json']), 'agent.skills', target.root, env, timeoutMs, calls).stdout, 'agent skills list');
|
|
256
|
+
resources.assignedSkills = assigned.map((item) => ({
|
|
257
|
+
id: resourceId(idOf(item), 'assigned skill.id'),
|
|
258
|
+
name: safeDisplay(item.name),
|
|
259
|
+
enabled: item.enabled === undefined ? true : Boolean(item.enabled),
|
|
260
|
+
})).sort(compareNamedResource);
|
|
261
|
+
}
|
|
262
|
+
else if (resources.agentCandidates.length > 0) {
|
|
263
|
+
throw new MulticaReadFailure('agent.unbound-name-candidates');
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
diff.push('agent.create');
|
|
267
|
+
}
|
|
268
|
+
for (const name of target.skills) {
|
|
269
|
+
const candidates = resources.catalogSkills.filter((item) => item.name === name);
|
|
270
|
+
if (candidates.length > 1)
|
|
271
|
+
throw new MulticaReadFailure(`skills.duplicate:${name}`);
|
|
272
|
+
if (candidates.length === 0) {
|
|
273
|
+
diff.push(`skill.create:${name}`);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (target.agentId && !resources.assignedSkills.some((item) => item.id === candidates[0].id && item.enabled)) {
|
|
277
|
+
diff.push(`skill.assign:${name}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
failures.push(error instanceof MulticaReadFailure ? error.category : 'provider.read-failed');
|
|
283
|
+
}
|
|
284
|
+
const canonical = {
|
|
285
|
+
cliVersion,
|
|
286
|
+
profile: target.profile,
|
|
287
|
+
principalHash,
|
|
288
|
+
serverHash,
|
|
289
|
+
expected: plan.expected,
|
|
290
|
+
resources,
|
|
291
|
+
};
|
|
292
|
+
const observedHash = digest(stableStringify(canonical));
|
|
293
|
+
const passed = failures.length === 0;
|
|
294
|
+
const inspectionId = `multica_inspect_${digest(stableStringify({
|
|
295
|
+
desiredHash: target.workspace.desiredHash,
|
|
296
|
+
observedHash,
|
|
297
|
+
passed,
|
|
298
|
+
diff: [...diff].sort(),
|
|
299
|
+
failures,
|
|
300
|
+
})).slice(0, 24)}`;
|
|
301
|
+
const defaultEvidencePath = join(MULTICA_EVIDENCE_ROOT, safeName(name), 'multica-inspections', `${inspectionId}.json`);
|
|
302
|
+
const evidencePath = options.out
|
|
303
|
+
? relative(target.root, safeProjectPath(target.root, options.out, false))
|
|
304
|
+
: defaultEvidencePath;
|
|
305
|
+
const report = {
|
|
306
|
+
$schema: 'dingtalk-agent/multica-workspace-inspection@1',
|
|
307
|
+
passed,
|
|
308
|
+
readyForApply: passed,
|
|
309
|
+
providerReady: false,
|
|
310
|
+
inspectionId,
|
|
311
|
+
workspace: name,
|
|
312
|
+
provider: 'multica',
|
|
313
|
+
profile: target.profile,
|
|
314
|
+
cliVersion,
|
|
315
|
+
desiredHash: target.workspace.desiredHash,
|
|
316
|
+
observedHash,
|
|
317
|
+
expected: plan.expected,
|
|
318
|
+
auth: { authenticated: Boolean(principalHash), principalHash, serverHash },
|
|
319
|
+
resources,
|
|
320
|
+
diff: [...diff].sort(),
|
|
321
|
+
failures: [...failures].sort(),
|
|
322
|
+
calls,
|
|
323
|
+
evidencePath,
|
|
324
|
+
remoteRead: true,
|
|
325
|
+
remoteWrite: false,
|
|
326
|
+
sideEffect: 'remote-read',
|
|
327
|
+
dingtalkSideEffect: false,
|
|
328
|
+
};
|
|
329
|
+
assertRedacted(report);
|
|
330
|
+
if (persist)
|
|
331
|
+
persistInspection(target, plan, report);
|
|
332
|
+
return report;
|
|
333
|
+
}
|
|
334
|
+
function persistInspection(target, plan, report) {
|
|
335
|
+
const reportPath = safeProjectPath(target.root, report.evidencePath, false);
|
|
336
|
+
atomicJson(reportPath, report);
|
|
337
|
+
const reportSource = readFileSync(reportPath, 'utf8');
|
|
338
|
+
const pointer = {
|
|
339
|
+
$schema: 'dingtalk-agent/multica-inspection-pointer@1',
|
|
340
|
+
inspectionId: report.inspectionId,
|
|
341
|
+
evidencePath: report.evidencePath,
|
|
342
|
+
reportHash: sha256(reportSource),
|
|
343
|
+
};
|
|
344
|
+
atomicJson(latestPointerPath(target.root, target.workspace.name), pointer);
|
|
345
|
+
if (!report.passed)
|
|
346
|
+
return;
|
|
347
|
+
const expectedSkillSetHash = digest(stableStringify([...target.skills].sort()));
|
|
348
|
+
const observedSkillSetHash = digest(stableStringify(report.resources.assignedSkills
|
|
349
|
+
.filter((item) => item.enabled).map((item) => item.name).sort()));
|
|
350
|
+
const state = {
|
|
351
|
+
$schema: 'dingtalk-agent/workspace-state@1',
|
|
352
|
+
workspace: target.workspace.name,
|
|
353
|
+
provider: 'multica',
|
|
354
|
+
status: 'verifying',
|
|
355
|
+
binding: {
|
|
356
|
+
profile: target.profile,
|
|
357
|
+
remoteWorkspaceId: target.workspaceId,
|
|
358
|
+
runtimeId: target.runtimeId,
|
|
359
|
+
agentId: target.agentId,
|
|
360
|
+
cliVersion: report.cliVersion,
|
|
361
|
+
principalHash: report.auth.principalHash,
|
|
362
|
+
serverHash: report.auth.serverHash,
|
|
363
|
+
inspectionId: report.inspectionId,
|
|
364
|
+
evidencePath: report.evidencePath,
|
|
365
|
+
sourceHash: target.workspace.sourceHash,
|
|
366
|
+
expectedSkillSetHash,
|
|
367
|
+
observedSkillSetHash,
|
|
368
|
+
},
|
|
369
|
+
desiredHash: target.workspace.desiredHash,
|
|
370
|
+
observedHash: report.observedHash,
|
|
371
|
+
lastPlanId: plan.planId,
|
|
372
|
+
lastReceiptId: '',
|
|
373
|
+
lastEvalId: '',
|
|
374
|
+
};
|
|
375
|
+
atomicJson(safeProjectPath(target.root, join(WORKSPACE_STATE_ROOT, `${target.workspace.name}.json`), false), state);
|
|
376
|
+
}
|
|
377
|
+
function resolveMulticaTarget(start, name, env, cliVersion, agentIdOverride = '') {
|
|
378
|
+
const info = inspectAgentProject(start, cliVersion);
|
|
379
|
+
const workspace = requireMulticaWorkspace(info.root, name, cliVersion);
|
|
380
|
+
const provider = workspace.provider;
|
|
381
|
+
const blocking = [];
|
|
382
|
+
const resolveRef = (ref, label, optional = false) => {
|
|
383
|
+
if (!ref)
|
|
384
|
+
return '';
|
|
385
|
+
const variable = ref.startsWith('env:') ? ref.slice(4) : '';
|
|
386
|
+
const value = variable ? String(env[variable] || '').trim() : '';
|
|
387
|
+
if (!value) {
|
|
388
|
+
if (!optional)
|
|
389
|
+
blocking.push(label);
|
|
390
|
+
return '';
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
return resourceId(value, label);
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
blocking.push(`${label}.invalid`);
|
|
397
|
+
return '';
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
const workspaceId = resolveRef(provider.workspaceIdFrom, 'workspace-id');
|
|
401
|
+
const runtimeId = resolveRef(provider.runtimeIdFrom, 'runtime-id');
|
|
402
|
+
let agentId = '';
|
|
403
|
+
if (agentIdOverride) {
|
|
404
|
+
try {
|
|
405
|
+
agentId = resourceId(agentIdOverride, 'agent-id-override');
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
blocking.push('agent-id-override.invalid');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else if (provider.agentIdFrom)
|
|
412
|
+
agentId = resolveRef(provider.agentIdFrom, 'agent-id');
|
|
413
|
+
else if (workspace.state?.binding.agentId) {
|
|
414
|
+
try {
|
|
415
|
+
agentId = resourceId(workspace.state.binding.agentId, 'state.agent-id');
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
blocking.push('state.agent-id.invalid');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
root: info.root,
|
|
423
|
+
projectName: info.project.name,
|
|
424
|
+
skills: [...new Set([...info.project.skills, MULTICA_BOOT_SKILL])].sort(),
|
|
425
|
+
workspace,
|
|
426
|
+
profile: provider.profile,
|
|
427
|
+
workspaceId,
|
|
428
|
+
runtimeId,
|
|
429
|
+
agentId,
|
|
430
|
+
blocking,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function requireMulticaWorkspace(start, name, cliVersion) {
|
|
434
|
+
const workspace = showDevelopmentWorkspace(start, name, cliVersion);
|
|
435
|
+
if (workspace.provider.kind !== 'multica' || workspace.legacy) {
|
|
436
|
+
throw new Error(`Workspace ${name} 不是 multica Provider;W3 不执行 ${workspace.provider.kind}`);
|
|
437
|
+
}
|
|
438
|
+
return workspace;
|
|
439
|
+
}
|
|
440
|
+
function readOperations(target) {
|
|
441
|
+
const operations = [
|
|
442
|
+
{ id: 'cli.version', argv: ['--version'] },
|
|
443
|
+
{ id: 'profile.config', argv: profileArgs(target, ['config', 'show']) },
|
|
444
|
+
{ id: 'auth.status', argv: profileArgs(target, ['auth', 'status']) },
|
|
445
|
+
{ id: 'workspace.list', argv: profileArgs(target, ['workspace', 'list', '--output', 'json']) },
|
|
446
|
+
{ id: 'workspace.get', argv: profileArgs(target, ['workspace', 'get', target.workspaceId || '<workspace-id>', '--output', 'json']) },
|
|
447
|
+
{ id: 'runtime.list', argv: scopedArgs(target, ['runtime', 'list', '--output', 'json']) },
|
|
448
|
+
{ id: 'agent.list', argv: scopedArgs(target, ['agent', 'list', '--include-archived', '--output', 'json']) },
|
|
449
|
+
{ id: 'skill.list', argv: scopedArgs(target, ['skill', 'list', '--output', 'json']) },
|
|
450
|
+
];
|
|
451
|
+
if (target.agentId)
|
|
452
|
+
operations.push({ id: 'agent.get', argv: scopedArgs(target, ['agent', 'get', target.agentId, '--output', 'json']) }, { id: 'agent.skills', argv: scopedArgs(target, ['agent', 'skills', 'list', target.agentId, '--output', 'json']) });
|
|
453
|
+
return operations;
|
|
454
|
+
}
|
|
455
|
+
function profileArgs(target, args) {
|
|
456
|
+
return ['--profile', target.profile, ...args];
|
|
457
|
+
}
|
|
458
|
+
function scopedArgs(target, args) {
|
|
459
|
+
return ['--profile', target.profile, '--workspace-id', target.workspaceId || '<workspace-id>', ...args];
|
|
460
|
+
}
|
|
461
|
+
function runRead(command, argv, id, cwd, env, timeoutMs, calls) {
|
|
462
|
+
const run = spawnSync(command, argv, {
|
|
463
|
+
cwd,
|
|
464
|
+
env,
|
|
465
|
+
encoding: 'utf8',
|
|
466
|
+
timeout: timeoutMs,
|
|
467
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
468
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
469
|
+
});
|
|
470
|
+
const stdout = String(run.stdout || '');
|
|
471
|
+
const stderr = String(run.stderr || '');
|
|
472
|
+
const exitCode = typeof run.status === 'number' ? run.status : 1;
|
|
473
|
+
const passed = !run.error && exitCode === 0;
|
|
474
|
+
calls.push({
|
|
475
|
+
id,
|
|
476
|
+
argv: argv.map(safeArg),
|
|
477
|
+
exitCode,
|
|
478
|
+
stdoutHash: sha256(stdout),
|
|
479
|
+
stderrHash: sha256(stderr),
|
|
480
|
+
passed,
|
|
481
|
+
});
|
|
482
|
+
if (!passed)
|
|
483
|
+
throw new MulticaReadFailure(`command.${id}`);
|
|
484
|
+
return { stdout, stderr };
|
|
485
|
+
}
|
|
486
|
+
function parseMulticaVersion(value) {
|
|
487
|
+
const match = value.match(/\bmultica\s+([0-9]+\.[0-9]+\.[0-9]+(?:[-+][A-Za-z0-9.-]+)?)/i);
|
|
488
|
+
if (!match)
|
|
489
|
+
throw new MulticaReadFailure('cli.version-invalid');
|
|
490
|
+
return match[1];
|
|
491
|
+
}
|
|
492
|
+
function parseConfig(value, profile) {
|
|
493
|
+
const fields = new Map();
|
|
494
|
+
for (const line of value.split(/\r?\n/)) {
|
|
495
|
+
const match = line.match(/^([^:]+):\s*(.*)$/);
|
|
496
|
+
if (match)
|
|
497
|
+
fields.set(match[1].trim().toLowerCase(), match[2].trim());
|
|
498
|
+
}
|
|
499
|
+
if ((fields.get('profile') || '') !== profile)
|
|
500
|
+
throw new MulticaReadFailure('profile.mismatch');
|
|
501
|
+
const normalize = (item) => item && item !== '(not set)' ? item : '';
|
|
502
|
+
return {
|
|
503
|
+
server: normalize(fields.get('server_url')),
|
|
504
|
+
workspaceId: normalize(fields.get('workspace_id')),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
function parseAuth(value) {
|
|
508
|
+
if (/not authenticated|invalid or expired/i.test(value))
|
|
509
|
+
return '';
|
|
510
|
+
const user = value.split(/\r?\n/).find((line) => /^User:\s+/i.test(line.trim())) || '';
|
|
511
|
+
const server = value.split(/\r?\n/).find((line) => /^Server:\s+/i.test(line.trim())) || '';
|
|
512
|
+
const token = value.split(/\r?\n/).find((line) => /^Token:\s+/i.test(line.trim())) || '';
|
|
513
|
+
return user && server && token ? `${user.trim()}|${server.trim()}` : '';
|
|
514
|
+
}
|
|
515
|
+
function jsonArray(value, label) {
|
|
516
|
+
let parsed;
|
|
517
|
+
try {
|
|
518
|
+
parsed = JSON.parse(value);
|
|
519
|
+
}
|
|
520
|
+
catch {
|
|
521
|
+
throw new MulticaReadFailure(`json.${label}`);
|
|
522
|
+
}
|
|
523
|
+
if (!Array.isArray(parsed) || parsed.some((item) => !item || typeof item !== 'object' || Array.isArray(item))) {
|
|
524
|
+
throw new MulticaReadFailure(`shape.${label}`);
|
|
525
|
+
}
|
|
526
|
+
return parsed;
|
|
527
|
+
}
|
|
528
|
+
function jsonObject(value, label) {
|
|
529
|
+
let parsed;
|
|
530
|
+
try {
|
|
531
|
+
parsed = JSON.parse(value);
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
throw new MulticaReadFailure(`json.${label}`);
|
|
535
|
+
}
|
|
536
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
537
|
+
throw new MulticaReadFailure(`shape.${label}`);
|
|
538
|
+
}
|
|
539
|
+
return parsed;
|
|
540
|
+
}
|
|
541
|
+
function readJsonObject(path, label) {
|
|
542
|
+
if (lstatSync(path).isSymbolicLink())
|
|
543
|
+
throw new Error(`${label} 不能是符号链接`);
|
|
544
|
+
return jsonObject(readFileSync(path, 'utf8'), label);
|
|
545
|
+
}
|
|
546
|
+
function idOf(value) {
|
|
547
|
+
return optionalField(value.id);
|
|
548
|
+
}
|
|
549
|
+
function stringField(value, label) {
|
|
550
|
+
if (typeof value !== 'string' || !value)
|
|
551
|
+
throw new Error(`${label} 必须是非空字符串`);
|
|
552
|
+
return value;
|
|
553
|
+
}
|
|
554
|
+
function optionalField(value) {
|
|
555
|
+
return typeof value === 'string' ? value : '';
|
|
556
|
+
}
|
|
557
|
+
function resourceId(value, label) {
|
|
558
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$/.test(value) ||
|
|
559
|
+
/^(?:mul_|mcn_|sk-|bearer)/i.test(value)) {
|
|
560
|
+
throw new Error(`${label} 不是安全的稳定资源 ID`);
|
|
561
|
+
}
|
|
562
|
+
return value;
|
|
563
|
+
}
|
|
564
|
+
function safeDisplay(value) {
|
|
565
|
+
const text = typeof value === 'string' ? value : '';
|
|
566
|
+
if (!text)
|
|
567
|
+
return '';
|
|
568
|
+
if (/(?:mul_|mcn_|sk-[A-Za-z0-9]|bearer\s|access[_-]?token|client[_-]?secret|password)/i.test(text)) {
|
|
569
|
+
return `[redacted:${digest(text).slice(0, 12)}]`;
|
|
570
|
+
}
|
|
571
|
+
return text.length > 160 ? `${text.slice(0, 157)}...` : text;
|
|
572
|
+
}
|
|
573
|
+
function safeArg(value) {
|
|
574
|
+
return safeDisplay(value);
|
|
575
|
+
}
|
|
576
|
+
function compareNamedResource(a, b) {
|
|
577
|
+
return a.name.localeCompare(b.name) || a.id.localeCompare(b.id);
|
|
578
|
+
}
|
|
579
|
+
function emptyResources() {
|
|
580
|
+
return {
|
|
581
|
+
workspace: null,
|
|
582
|
+
runtime: null,
|
|
583
|
+
agent: null,
|
|
584
|
+
agentCandidates: [],
|
|
585
|
+
catalogSkills: [],
|
|
586
|
+
assignedSkills: [],
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function emptyStatus(workspace) {
|
|
590
|
+
return {
|
|
591
|
+
$schema: 'dingtalk-agent/multica-workspace-status@1',
|
|
592
|
+
workspace: workspace.name,
|
|
593
|
+
provider: 'multica',
|
|
594
|
+
available: false,
|
|
595
|
+
inspectionId: '',
|
|
596
|
+
passed: false,
|
|
597
|
+
readyForApply: false,
|
|
598
|
+
providerReady: false,
|
|
599
|
+
evidencePath: '',
|
|
600
|
+
evidenceIntegrity: false,
|
|
601
|
+
stateMatched: false,
|
|
602
|
+
desiredHash: workspace.desiredHash,
|
|
603
|
+
observedHash: workspace.observedHash,
|
|
604
|
+
diff: [],
|
|
605
|
+
failures: ['inspection.missing'],
|
|
606
|
+
remoteRead: false,
|
|
607
|
+
remoteWrite: false,
|
|
608
|
+
sideEffect: false,
|
|
609
|
+
dingtalkSideEffect: false,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function latestPointerPath(root, name) {
|
|
613
|
+
return safeProjectPath(root, join(MULTICA_EVIDENCE_ROOT, safeName(name), 'multica-inspections', 'latest.json'), false);
|
|
614
|
+
}
|
|
615
|
+
function safeName(value) {
|
|
616
|
+
if (!/^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/.test(value)) {
|
|
617
|
+
throw new Error(`Workspace name 非法: ${value}`);
|
|
618
|
+
}
|
|
619
|
+
return value;
|
|
620
|
+
}
|
|
621
|
+
function safeProjectPath(root, ref, mustExist) {
|
|
622
|
+
const rootReal = realpathSync(resolve(root));
|
|
623
|
+
const candidate = resolve(rootReal, ref);
|
|
624
|
+
const rel = relative(rootReal, candidate);
|
|
625
|
+
if (rel === '..' || rel.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`)) {
|
|
626
|
+
throw new Error(`Multica Provider 路径必须位于 Project 内: ${ref}`);
|
|
627
|
+
}
|
|
628
|
+
if (mustExist && !existsSync(candidate))
|
|
629
|
+
throw new Error(`Multica evidence 不存在: ${ref}`);
|
|
630
|
+
if (existsSync(candidate)) {
|
|
631
|
+
if (lstatSync(candidate).isSymbolicLink()) {
|
|
632
|
+
throw new Error(`Multica Provider 路径不能是符号链接: ${ref}`);
|
|
633
|
+
}
|
|
634
|
+
if (!isInside(rootReal, realpathSync(candidate))) {
|
|
635
|
+
throw new Error(`Multica Provider 真实路径越出 Project: ${ref}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
let ancestor = dirname(candidate);
|
|
639
|
+
while (!existsSync(ancestor)) {
|
|
640
|
+
const parent = dirname(ancestor);
|
|
641
|
+
if (parent === ancestor)
|
|
642
|
+
break;
|
|
643
|
+
ancestor = parent;
|
|
644
|
+
}
|
|
645
|
+
if (!existsSync(ancestor) || !isInside(rootReal, realpathSync(ancestor))) {
|
|
646
|
+
throw new Error(`Multica Provider 父目录真实路径越出 Project: ${ref}`);
|
|
647
|
+
}
|
|
648
|
+
return candidate;
|
|
649
|
+
}
|
|
650
|
+
function isInside(root, candidate) {
|
|
651
|
+
const rel = relative(root, candidate);
|
|
652
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`));
|
|
653
|
+
}
|
|
654
|
+
function atomicJson(path, value) {
|
|
655
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
656
|
+
const temp = `${path}.tmp-${process.pid}`;
|
|
657
|
+
const source = `${JSON.stringify(value, null, 2)}\n`;
|
|
658
|
+
writeFileSync(temp, source, { mode: 0o600 });
|
|
659
|
+
renameSync(temp, path);
|
|
660
|
+
if (readFileSync(path, 'utf8') !== source)
|
|
661
|
+
throw new Error(`原子写后回读不一致: ${path}`);
|
|
662
|
+
}
|
|
663
|
+
function assertRedacted(value) {
|
|
664
|
+
const source = JSON.stringify(value);
|
|
665
|
+
if (/(?:mul_|mcn_|sk-[A-Za-z0-9]|bearer\s|access[_-]?token|client[_-]?secret|password)/i.test(source)) {
|
|
666
|
+
throw new Error('Multica inspection 脱敏失败:检测到疑似凭据');
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function positiveTimeout(value) {
|
|
670
|
+
if (value === undefined)
|
|
671
|
+
return 30_000;
|
|
672
|
+
if (!Number.isFinite(value) || value < 100 || value > 300_000) {
|
|
673
|
+
throw new Error('Multica read timeout 必须在 100..300000 ms');
|
|
674
|
+
}
|
|
675
|
+
return Math.floor(value);
|
|
676
|
+
}
|
|
677
|
+
function requireReadConfirmation(options, command) {
|
|
678
|
+
if (!options.execute || !options.yes) {
|
|
679
|
+
throw new Error(`${command} 会读取当前 Multica 身份和远端 scope;必须同时传 --execute --yes`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function sha256(value) {
|
|
683
|
+
return createHash('sha256').update(value).digest('hex');
|
|
684
|
+
}
|
|
685
|
+
//# sourceMappingURL=multica-provider.js.map
|