@xdxer/dingtalk-agent 0.1.4 → 0.1.5-beta.10
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 +232 -0
- package/README.en.md +115 -89
- package/README.md +111 -86
- package/dist/bin/dingtalk-agent.js +742 -152
- package/dist/bin/dingtalk-agent.js.map +1 -1
- package/dist/src/actions.js +3 -2
- package/dist/src/actions.js.map +1 -1
- package/dist/src/agent-audit.js +202 -85
- package/dist/src/agent-audit.js.map +1 -1
- package/dist/src/agent-definition.js +7 -3
- package/dist/src/agent-definition.js.map +1 -1
- package/dist/src/agent-enhance.js +51 -32
- package/dist/src/agent-enhance.js.map +1 -1
- package/dist/src/agent-platform.js +4 -4
- package/dist/src/agent-platform.js.map +1 -1
- package/dist/src/bootstrap.js +6 -2
- package/dist/src/bootstrap.js.map +1 -1
- package/dist/src/development-workspace.js +210 -34
- package/dist/src/development-workspace.js.map +1 -1
- package/dist/src/doctor.js +65 -9
- package/dist/src/doctor.js.map +1 -1
- package/dist/src/dws.js +67 -3
- package/dist/src/dws.js.map +1 -1
- package/dist/src/init.js +2 -1
- package/dist/src/init.js.map +1 -1
- package/dist/src/memory/noop-receipt.js +306 -0
- package/dist/src/memory/noop-receipt.js.map +1 -0
- package/dist/src/memory/operational.js +27 -3
- package/dist/src/memory/operational.js.map +1 -1
- package/dist/src/memory/remote-state.js +2 -1
- package/dist/src/memory/remote-state.js.map +1 -1
- package/dist/src/multica-deploy.js +692 -125
- package/dist/src/multica-deploy.js.map +1 -1
- package/dist/src/multica-provider.js +303 -25
- package/dist/src/multica-provider.js.map +1 -1
- package/dist/src/multica-runtime-vocabulary.js +110 -0
- package/dist/src/multica-runtime-vocabulary.js.map +1 -0
- package/dist/src/opencode-evals.js +6 -6
- package/dist/src/opencode-evals.js.map +1 -1
- package/dist/src/opencode-provider.js +21 -7
- package/dist/src/opencode-provider.js.map +1 -1
- package/dist/src/opencode-workspace.js +3 -3
- package/dist/src/opencode-workspace.js.map +1 -1
- package/dist/src/personal-event-evals.js +4 -2
- package/dist/src/personal-event-evals.js.map +1 -1
- package/dist/src/promotion.js +2 -1
- package/dist/src/promotion.js.map +1 -1
- package/dist/src/remote-semantic-state-live-evals.js +14 -8
- package/dist/src/remote-semantic-state-live-evals.js.map +1 -1
- package/dist/src/remote-state-evals.js +2 -2
- package/dist/src/remote-state-evals.js.map +1 -1
- package/dist/src/robot-evals.js +3 -3
- package/dist/src/robot-evals.js.map +1 -1
- package/dist/src/schedule-plan.js +380 -0
- package/dist/src/schedule-plan.js.map +1 -0
- package/dist/src/sessions.js +1 -1
- package/dist/src/sessions.js.map +1 -1
- package/dist/src/skill-manager.js +145 -13
- package/dist/src/skill-manager.js.map +1 -1
- package/dist/src/skills.js +2 -0
- package/dist/src/skills.js.map +1 -1
- package/dist/src/tui.js +369 -0
- package/dist/src/tui.js.map +1 -0
- package/dist/src/upgrade.js +113 -33
- package/dist/src/upgrade.js.map +1 -1
- package/dist/src/waits.js +2 -1
- package/dist/src/waits.js.map +1 -1
- package/dist/src/workspace.js +12 -7
- package/dist/src/workspace.js.map +1 -1
- package/docs/AGENT-IN-PRODUCTION.md +255 -0
- package/docs/ARCHITECTURE.md +366 -0
- package/docs/INSTALLATION.md +8 -8
- package/docs/PLATFORM-GUARDRAILS.md +188 -0
- package/docs/PRIOR-ART.md +126 -0
- package/docs/SELF-TEST.md +182 -0
- package/docs/architecture/agent-platform-connection-layer.svg +120 -0
- package/docs/architecture/digital-employee-composition.svg +92 -0
- package/docs/architecture/dingtalk-agent-architecture.svg +125 -0
- package/docs/assets/digital-employee-at-work.svg +77 -0
- package/docs/schemas/multica-deployment-plan.schema.json +3 -1
- package/docs/schemas/multica-deployment-receipt.schema.json +17 -3
- package/docs/schemas/multica-deployment-status.schema.json +6 -2
- package/docs/schemas/multica-workspace-inspection.schema.json +16 -0
- package/docs/schemas/multica-workspace-run-plan.schema.json +31 -0
- package/docs/schemas/multica-workspace-run.schema.json +161 -0
- package/docs/schemas/multica-workspace-status.schema.json +2 -0
- package/docs/schemas/project.schema.json +54 -3
- package/docs/schemas/workspace-scaffold.schema.json +38 -0
- package/examples/agents/README.md +45 -0
- package/examples/agents/fde-coach/AGENTS.md +2 -25
- package/examples/agents/fde-coach/agent/AGENTS.md +35 -0
- package/examples/agents/fde-coach/agent.bindings.json +10 -0
- package/examples/agents/release-manager/AGENTS.md +2 -25
- package/examples/agents/release-manager/agent/AGENTS.md +35 -0
- package/examples/agents/release-manager/agent.bindings.json +10 -0
- package/lab/agent-eval/catalog.json +5 -5
- package/lab/agent-eval/classic-failures.json +4 -4
- package/lab/agent-eval/completion-gate-regression.json +9 -9
- package/lab/agent-eval/personal-event-live.example.json +3 -3
- package/lab/agent-eval/remote-semantic-state-live.example.json +1 -1
- package/lab/agent-eval/workspace/opencode.json +2 -2
- package/lab/project-workspace/fake-multica-provider.mjs +171 -17
- package/lab/project-workspace/multica-deploy.fixture.json +2 -2
- package/lab/project-workspace/multica-readonly.fixture.json +4 -16
- package/lab/project-workspace/opencode-provider-suite.json +3 -3
- package/lab/project-workspace/project.fixture.json +2 -6
- package/lab/robot-eval/suite.json +1 -1
- package/lab/robot-eval/workspace/AGENTS.md +1 -1
- package/lab/robot-eval/workspace/opencode.json +2 -2
- package/lab/schemas/personal-event-eval.schema.json +1 -1
- package/package.json +18 -11
- package/skills/README.md +10 -8
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/SKILL.md +49 -24
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/AGENTS.template.md +1 -1
- package/skills/core/dta-agent-compose/assets/REPOSITORY.template.md +10 -0
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/agent.bindings.dingtalk-doc.template.json +2 -2
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/agent.bindings.local.template.json +2 -2
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/hosts/opencode/opencode.template.json +3 -2
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/evals/evals.json +4 -4
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/agent-definition-contract.md +7 -7
- package/skills/core/dta-agent-compose/references/drive-and-schedules.md +166 -0
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/host-loading-contract.md +11 -13
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/hosts/claude-code.md +13 -12
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/hosts/opencode.md +14 -13
- package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/SKILL.md +28 -3
- package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/assets/eval-catalog.template.json +1 -1
- package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/evals/evals.json +1 -1
- package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/eval-topology.md +3 -3
- package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/interactive-debug-channels.md +10 -4
- package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/SKILL.md +21 -5
- package/skills/core/dta-basic-behavior/references/event-to-behavior.md +38 -0
- package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/memory-and-evolution.md +3 -1
- package/skills/core/dta-basic-behavior/references/perception-and-gates.md +87 -0
- package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/risk-authority-and-privacy.md +12 -0
- package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/truth-and-recovery.md +4 -2
- package/skills/core/dta-people-group-memory/COMPLETENESS.md +36 -0
- package/skills/core/dta-people-group-memory/SKILL.md +69 -0
- package/skills/core/dta-people-group-memory/references/adapters.md +273 -0
- package/skills/core/dta-people-group-memory/references/assembly-guidance.md +40 -0
- package/skills/core/dta-people-group-memory/references/binding.md +110 -0
- package/skills/core/dta-people-group-memory/references/cold-start.md +70 -0
- package/skills/core/dta-people-group-memory/references/config-binding.md +89 -0
- package/skills/core/dta-people-group-memory/references/consent-and-visibility.md +83 -0
- package/skills/core/dta-people-group-memory/references/consolidation.md +162 -0
- package/skills/core/dta-people-group-memory/references/event-ingest.md +103 -0
- package/skills/core/dta-people-group-memory/references/guided-setup.md +70 -0
- package/skills/core/dta-people-group-memory/references/model.md +148 -0
- package/skills/core/dta-people-group-memory/references/storage-port.md +107 -0
- package/skills/platforms/deap/PLATFORM.md +30 -1
- package/skills/platforms/multica-dingtalk/PLATFORM.md +35 -9
- package/skills/platforms/multica-dingtalk/{dingtalk-agent-deploy-multica → dta-deploy-multica}/SKILL.md +10 -8
- package/skills/platforms/multica-dingtalk/dta-deploy-multica/references/multica-deployment-contract.md +67 -0
- package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/SKILL.md +81 -11
- package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/scripts/bootstrap.sh +2 -2
- package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/scripts/multica_ext.py +264 -16
- package/dist/src/map.js +0 -157
- package/dist/src/map.js.map +0 -1
- package/docs/SECOND-AGENT-ACCEPTANCE.md +0 -62
- package/docs/architecture/agent-memory-topology.png +0 -0
- package/docs/architecture/agent-memory-topology.svg +0 -132
- package/docs/architecture/dingtalk-agent-blueprint.png +0 -0
- package/docs/architecture/durable-async-agent-runtime.png +0 -0
- package/docs/architecture/general-agent-kernel-topology.png +0 -0
- package/docs/architecture/provider-bound-development-workspace.png +0 -0
- package/docs/architecture/task-completion-gate.png +0 -0
- package/docs/assets/agent-delivery-lifecycle.svg +0 -103
- package/skills/core/dingtalk-basic-behavior/references/event-to-behavior.md +0 -24
- package/skills/core/dingtalk-basic-behavior/references/perception-and-gates.md +0 -28
- package/skills/platforms/deap/README.md +0 -3
- package/skills/platforms/multica-dingtalk/dingtalk-agent-boot-multica/SKILL.md +0 -40
- package/skills/platforms/multica-dingtalk/dingtalk-agent-deploy-multica/references/multica-deployment-contract.md +0 -49
- /package/examples/agents/fde-coach/{skills → agent/skills}/fde-coach/SKILL.md +0 -0
- /package/examples/agents/release-manager/{skills → agent/skills}/release-manager/SKILL.md +0 -0
- /package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/role-skill.template.md +0 -0
- /package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/storage-routing.md +0 -0
- /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/evidence-contract.md +0 -0
- /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/failure-to-case.md +0 -0
- /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/local-connector-smoke.md +0 -0
- /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/scenario-taxonomy.md +0 -0
- /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/storage-modes.md +0 -0
- /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/assets/memory-candidate-proposal.json +0 -0
- /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/assets/task-checkpoint.json +0 -0
- /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/action-contract.md +0 -0
- /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/runtime-modes.md +0 -0
- /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/task-lifecycle.md +0 -0
- /package/skills/platforms/multica-dingtalk/{dingtalk-agent-deploy-multica → dta-deploy-multica}/references/promotion-observation-contract.md +0 -0
|
@@ -2,15 +2,33 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync, } from 'node:fs';
|
|
4
4
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
5
|
-
import { WORKSPACE_STATE_ROOT, inspectAgentProject, showDevelopmentWorkspace, } from './development-workspace.js';
|
|
5
|
+
import { RESERVED_MULTICA_ENV, WORKSPACE_STATE_ROOT, inspectAgentProject, multicaAgentIdSource, showDevelopmentWorkspace, } from './development-workspace.js';
|
|
6
6
|
import { digest, stableStringify } from './events.js';
|
|
7
|
+
import { supportedRuntimeProviders, vocabulariesForRuntimeProvider, } from './multica-runtime-vocabulary.js';
|
|
7
8
|
export const MULTICA_EVIDENCE_ROOT = '.dingtalk-agent/development-workspace-evidence';
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Strip env variables the provider CLI reinterprets as mode switches (see
|
|
11
|
+
* RESERVED_MULTICA_ENV) from a child-process environment. dta reads manifest env refs from
|
|
12
|
+
* the raw env *before* this runs, so a ref named after a reserved variable still resolves —
|
|
13
|
+
* only the multica subprocess stops seeing it.
|
|
14
|
+
*/
|
|
15
|
+
export function sanitizeProviderEnv(env) {
|
|
16
|
+
const removed = RESERVED_MULTICA_ENV
|
|
17
|
+
.filter((key) => env[key] !== undefined).sort();
|
|
18
|
+
if (!removed.length)
|
|
19
|
+
return { env, removed };
|
|
20
|
+
const child = { ...env };
|
|
21
|
+
for (const key of removed)
|
|
22
|
+
delete child[key];
|
|
23
|
+
return { env: child, removed };
|
|
24
|
+
}
|
|
9
25
|
class MulticaReadFailure extends Error {
|
|
10
26
|
category;
|
|
11
|
-
|
|
27
|
+
detail;
|
|
28
|
+
constructor(category, detail) {
|
|
12
29
|
super(category);
|
|
13
30
|
this.category = category;
|
|
31
|
+
this.detail = detail;
|
|
14
32
|
}
|
|
15
33
|
}
|
|
16
34
|
export function planMulticaWorkspace(start, name, options = {}) {
|
|
@@ -72,6 +90,172 @@ export function planMulticaWorkspace(start, name, options = {}) {
|
|
|
72
90
|
dingtalkSideEffect: false,
|
|
73
91
|
};
|
|
74
92
|
}
|
|
93
|
+
const SCAFFOLD_STAGES = new Set(['dev', 'test', 'staging', 'prod']);
|
|
94
|
+
const SCAFFOLD_NAME = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
|
|
95
|
+
/**
|
|
96
|
+
* Author a multica-provider Workspace entry into dingtalk-agent.json so callers never hand-copy
|
|
97
|
+
* the schema. Env-referenced IDs are namespaced by Workspace name; authority is filled in and
|
|
98
|
+
* `agentIdFrom` deliberately omitted (first deploy creates the Agent and saves its ID). The
|
|
99
|
+
* written manifest is re-parsed to fail closed on any invalid result.
|
|
100
|
+
*/
|
|
101
|
+
export function scaffoldMulticaWorkspace(start, name, options = {}) {
|
|
102
|
+
const cliVersion = options.cliVersion || '';
|
|
103
|
+
if (!SCAFFOLD_NAME.test(name))
|
|
104
|
+
throw new Error(`Workspace 名不合法: ${name}`);
|
|
105
|
+
if (options.stage !== undefined && !SCAFFOLD_STAGES.has(options.stage)) {
|
|
106
|
+
throw new Error(`--stage 只支持 dev/test/staging/prod,收到 ${options.stage}`);
|
|
107
|
+
}
|
|
108
|
+
const info = inspectAgentProject(start, cliVersion);
|
|
109
|
+
const existing = info.workspaces.find((item) => item.name === name);
|
|
110
|
+
if (existing && existing.provider.kind !== 'multica') {
|
|
111
|
+
throw new Error(`Workspace ${name} 已声明为 ${existing.provider.kind},不能改写成 multica`);
|
|
112
|
+
}
|
|
113
|
+
const alreadyDeclared = Boolean(existing);
|
|
114
|
+
let stage;
|
|
115
|
+
let profile;
|
|
116
|
+
let envRefs;
|
|
117
|
+
let block;
|
|
118
|
+
if (existing) {
|
|
119
|
+
// Report the REAL declared config, not this call's args: a manually-authored Workspace or one
|
|
120
|
+
// using non-default env names must not be misrepresented (nor its --profile/--stage silently
|
|
121
|
+
// "changed" in the output while nothing is written).
|
|
122
|
+
const stripEnv = (ref) => (ref.startsWith('env:') ? ref.slice(4) : ref);
|
|
123
|
+
stage = existing.stage;
|
|
124
|
+
profile = existing.provider.profile || '';
|
|
125
|
+
envRefs = {
|
|
126
|
+
workspaceId: stripEnv(existing.provider.workspaceIdFrom || ''),
|
|
127
|
+
runtimeId: stripEnv(existing.provider.runtimeIdFrom || ''),
|
|
128
|
+
dwsProfile: stripEnv(existing.authority?.dwsProfileFrom || ''),
|
|
129
|
+
expectedUserId: stripEnv(existing.authority?.expectedUserIdFrom || ''),
|
|
130
|
+
};
|
|
131
|
+
block = {
|
|
132
|
+
stage: existing.stage,
|
|
133
|
+
provider: { ...existing.provider },
|
|
134
|
+
...(existing.authority ? { authority: { ...existing.authority } } : {}),
|
|
135
|
+
storage: { ...existing.storage },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
stage = options.stage || 'test';
|
|
140
|
+
profile = String(options.profile || '').trim();
|
|
141
|
+
if (!profile) {
|
|
142
|
+
throw new Error('multica Workspace 需要 --profile <multica CLI profile>(profile 不接受 env 引用)');
|
|
143
|
+
}
|
|
144
|
+
// Namespace env refs by Workspace name AND a stable hash of the raw name so names differing
|
|
145
|
+
// only in separators (foo-bar / foo_bar / foo.bar) never collapse to the same env variables.
|
|
146
|
+
const sanitized = name.replace(/[^a-zA-Z0-9]+/g, '_').toUpperCase();
|
|
147
|
+
const envPrefix = `DTA_${sanitized}_${digest(name).slice(0, 6).toUpperCase()}`;
|
|
148
|
+
envRefs = {
|
|
149
|
+
workspaceId: `${envPrefix}_WORKSPACE_ID`,
|
|
150
|
+
runtimeId: `${envPrefix}_RUNTIME_ID`,
|
|
151
|
+
dwsProfile: `${envPrefix}_DWS_PROFILE`,
|
|
152
|
+
expectedUserId: `${envPrefix}_EXPECTED_USER_ID`,
|
|
153
|
+
};
|
|
154
|
+
block = {
|
|
155
|
+
stage,
|
|
156
|
+
provider: {
|
|
157
|
+
kind: 'multica',
|
|
158
|
+
profile,
|
|
159
|
+
workspaceIdFrom: `env:${envRefs.workspaceId}`,
|
|
160
|
+
runtimeIdFrom: `env:${envRefs.runtimeId}`,
|
|
161
|
+
},
|
|
162
|
+
authority: {
|
|
163
|
+
dwsProfileFrom: `env:${envRefs.dwsProfile}`,
|
|
164
|
+
expectedUserIdFrom: `env:${envRefs.expectedUserId}`,
|
|
165
|
+
},
|
|
166
|
+
storage: {
|
|
167
|
+
memory: 'local-md:MEMORY.md',
|
|
168
|
+
knowledge: 'local-md:knowledge/INDEX.md',
|
|
169
|
+
artifacts: `local-dir:.dingtalk-agent/artifacts/${name}`,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const result = (applied, createdPaths = []) => ({
|
|
174
|
+
$schema: 'dingtalk-agent/workspace-scaffold@1',
|
|
175
|
+
workspace: name,
|
|
176
|
+
provider: 'multica',
|
|
177
|
+
stage,
|
|
178
|
+
profile,
|
|
179
|
+
applied,
|
|
180
|
+
alreadyDeclared,
|
|
181
|
+
manifest: 'dingtalk-agent.json',
|
|
182
|
+
block,
|
|
183
|
+
envRefs,
|
|
184
|
+
createdPaths,
|
|
185
|
+
nextSteps: alreadyDeclared
|
|
186
|
+
? [
|
|
187
|
+
`Workspace ${name} 已声明(未改写);以下是它当前真实的配置`,
|
|
188
|
+
...(options.profile && options.profile.trim() && options.profile.trim() !== profile
|
|
189
|
+
? [`注意:本次 --profile=${options.profile.trim()} 与已声明 profile=${profile} 不同,未生效;要改请手动编辑 dingtalk-agent.json`]
|
|
190
|
+
: []),
|
|
191
|
+
...(options.stage && options.stage !== stage
|
|
192
|
+
? [`注意:本次 --stage=${options.stage} 与已声明 stage=${stage} 不同,未生效`]
|
|
193
|
+
: []),
|
|
194
|
+
`设置 4 个 env 后运行 dta workspace doctor ${name}`,
|
|
195
|
+
`export ${envRefs.workspaceId}=<multica workspace uuid>`,
|
|
196
|
+
`export ${envRefs.runtimeId}=<multica runtime uuid>`,
|
|
197
|
+
`export ${envRefs.dwsProfile}=<dws profile 或 corpId>`,
|
|
198
|
+
`export ${envRefs.expectedUserId}=<dws user id>`,
|
|
199
|
+
]
|
|
200
|
+
: [
|
|
201
|
+
applied
|
|
202
|
+
? '已写入 dingtalk-agent.json;只改了本地 manifest(含 artifacts 目录),' +
|
|
203
|
+
'未创建任何 Multica 远端资源——远端 workspace/runtime 用 ops 技能供给,Agent 由 dta deploy 创建'
|
|
204
|
+
: '预览(加 --yes 落盘);本命令只写本地 manifest,不创建任何 Multica 远端资源',
|
|
205
|
+
`export ${envRefs.workspaceId}=<multica workspace uuid>`,
|
|
206
|
+
`export ${envRefs.runtimeId}=<multica runtime uuid>`,
|
|
207
|
+
`export ${envRefs.dwsProfile}=<dws profile 或 corpId>`,
|
|
208
|
+
`export ${envRefs.expectedUserId}=<dws user id>`,
|
|
209
|
+
`dta workspace doctor ${name}`,
|
|
210
|
+
`dta workspace plan ${name} --json`,
|
|
211
|
+
`dta workspace inspect ${name} --execute --yes --json # 代理环境先套 env -u ...PROXY`,
|
|
212
|
+
`dta deploy --workspace ${name} --dry-run --json`,
|
|
213
|
+
],
|
|
214
|
+
sideEffect: applied ? 'local-manifest' : false,
|
|
215
|
+
dingtalkSideEffect: false,
|
|
216
|
+
});
|
|
217
|
+
if (alreadyDeclared || !options.apply)
|
|
218
|
+
return result(false);
|
|
219
|
+
const manifestPath = join(info.root, 'dingtalk-agent.json');
|
|
220
|
+
if (!existsSync(manifestPath) || lstatSync(manifestPath).isSymbolicLink()) {
|
|
221
|
+
throw new Error('未找到可写的 dingtalk-agent.json(或它是 symlink);无法脚手架 Workspace');
|
|
222
|
+
}
|
|
223
|
+
const raw = readFileSync(manifestPath, 'utf8');
|
|
224
|
+
const manifest = JSON.parse(raw);
|
|
225
|
+
if (!manifest.workspaces || typeof manifest.workspaces !== 'object')
|
|
226
|
+
manifest.workspaces = {};
|
|
227
|
+
if (name in manifest.workspaces)
|
|
228
|
+
return result(false);
|
|
229
|
+
manifest.workspaces[name] = block;
|
|
230
|
+
const stamp = `${process.pid}-${digest(raw).slice(0, 8)}`;
|
|
231
|
+
const staging = `${manifestPath}.tmp-${stamp}`;
|
|
232
|
+
writeFileSync(staging, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
233
|
+
renameSync(staging, manifestPath);
|
|
234
|
+
try {
|
|
235
|
+
showDevelopmentWorkspace(info.root, name, cliVersion);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
// Restore the exact original bytes atomically (staging + rename), never a partial write:
|
|
239
|
+
// a kill mid-restore must not leave a truncated shared manifest.
|
|
240
|
+
const restore = `${manifestPath}.restore-${stamp}`;
|
|
241
|
+
writeFileSync(restore, raw);
|
|
242
|
+
renameSync(restore, manifestPath);
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
// 声明的路径必须原子性地存在:manifest 里写了 artifacts 目录却不创建它,新项目
|
|
246
|
+
// 第一次 doctor 就挂在 storage.artifacts(E-01)——doctor 不该要求用户补脚手架遗漏。
|
|
247
|
+
// .gitkeep 让空目录进 git,fresh clone 后 doctor 依然通过。回滚路径不清理该目录
|
|
248
|
+
// (幂等无害,保持回滚逻辑简单)。
|
|
249
|
+
const artifactsDir = join(info.root, '.dingtalk-agent', 'artifacts', name);
|
|
250
|
+
mkdirSync(artifactsDir, { recursive: true });
|
|
251
|
+
const gitkeep = join(artifactsDir, '.gitkeep');
|
|
252
|
+
if (!existsSync(gitkeep))
|
|
253
|
+
writeFileSync(gitkeep, '');
|
|
254
|
+
return result(true, [
|
|
255
|
+
relative(info.root, artifactsDir).split('\\').join('/'),
|
|
256
|
+
relative(info.root, gitkeep).split('\\').join('/'),
|
|
257
|
+
]);
|
|
258
|
+
}
|
|
75
259
|
export function inspectMulticaWorkspace(start, name, options = {}) {
|
|
76
260
|
const plan = planMulticaWorkspace(start, name, options);
|
|
77
261
|
if (!options.execute)
|
|
@@ -148,6 +332,8 @@ export function statusMulticaWorkspace(start, name, cliVersion = '') {
|
|
|
148
332
|
observedHash: report.observedHash,
|
|
149
333
|
diff: [...report.diff],
|
|
150
334
|
failures: [...report.failures],
|
|
335
|
+
failureDetails: Array.isArray(report.failureDetails) ? [...report.failureDetails] : [],
|
|
336
|
+
observedAgentRuntimeId: report.resources?.agent?.runtimeId || '',
|
|
151
337
|
remoteRead: false,
|
|
152
338
|
remoteWrite: false,
|
|
153
339
|
sideEffect: false,
|
|
@@ -155,9 +341,11 @@ export function statusMulticaWorkspace(start, name, cliVersion = '') {
|
|
|
155
341
|
};
|
|
156
342
|
}
|
|
157
343
|
function executeInspection(start, name, plan, options, persist) {
|
|
158
|
-
const
|
|
159
|
-
const target = resolveMulticaTarget(start, name,
|
|
160
|
-
const
|
|
344
|
+
const rawEnv = options.env || process.env;
|
|
345
|
+
const target = resolveMulticaTarget(start, name, rawEnv, options.cliVersion || '', options.agentIdOverride);
|
|
346
|
+
const sanitized = sanitizeProviderEnv(rawEnv);
|
|
347
|
+
const env = sanitized.env;
|
|
348
|
+
const command = options.command || rawEnv.DTA_MULTICA_BIN || 'multica';
|
|
161
349
|
const timeoutMs = positiveTimeout(options.timeoutMs);
|
|
162
350
|
if (persist) {
|
|
163
351
|
const plannedEvidence = options.out || join(MULTICA_EVIDENCE_ROOT, safeName(name), 'multica-inspections', 'planned.json');
|
|
@@ -167,6 +355,7 @@ function executeInspection(start, name, plan, options, persist) {
|
|
|
167
355
|
}
|
|
168
356
|
const calls = [];
|
|
169
357
|
const failures = [];
|
|
358
|
+
const failureDetails = [];
|
|
170
359
|
const diff = [];
|
|
171
360
|
let cliVersion = '';
|
|
172
361
|
let principalHash = '';
|
|
@@ -179,12 +368,23 @@ function executeInspection(start, name, plan, options, persist) {
|
|
|
179
368
|
const parsedConfig = parseConfig(config.stdout, target.profile);
|
|
180
369
|
serverHash = parsedConfig.server ? digest(parsedConfig.server) : '';
|
|
181
370
|
if (parsedConfig.workspaceId && parsedConfig.workspaceId !== target.workspaceId) {
|
|
182
|
-
throw new MulticaReadFailure('scope.profile-workspace-mismatch'
|
|
371
|
+
throw new MulticaReadFailure('scope.profile-workspace-mismatch', {
|
|
372
|
+
code: 'scope.profile-workspace-mismatch',
|
|
373
|
+
expected: target.workspaceId,
|
|
374
|
+
actual: safeDisplay(parsedConfig.workspaceId),
|
|
375
|
+
hint: `profile ${target.profile} 的 workspace_id 指向另一个 Workspace;` +
|
|
376
|
+
'为目标 Workspace 建专属具名 profile(复制 config 只改 workspace_id),或修正现有 profile',
|
|
377
|
+
});
|
|
183
378
|
}
|
|
184
379
|
const auth = runRead(command, profileArgs(target, ['auth', 'status']), 'auth.status', target.root, env, timeoutMs, calls);
|
|
185
380
|
const principal = parseAuth(auth.stdout + '\n' + auth.stderr);
|
|
186
381
|
if (!principal)
|
|
187
|
-
throw new MulticaReadFailure('auth.not-authenticated'
|
|
382
|
+
throw new MulticaReadFailure('auth.not-authenticated', {
|
|
383
|
+
code: 'auth.not-authenticated',
|
|
384
|
+
hint: `确认 multica --profile ${target.profile} 已登录;` +
|
|
385
|
+
'dta 调用 CLI 时已剔除保留变量(MULTICA_AGENT_ID/MULTICA_SERVER_URL/MULTICA_TOKEN),' +
|
|
386
|
+
'身份与 endpoint 只认具名 profile',
|
|
387
|
+
});
|
|
188
388
|
principalHash = digest(principal);
|
|
189
389
|
const workspaceList = jsonArray(runRead(command, profileArgs(target, ['workspace', 'list', '--output', 'json']), 'workspace.list', target.root, env, timeoutMs, calls).stdout, 'workspace list');
|
|
190
390
|
const workspaceMatches = workspaceList.filter((item) => idOf(item) === target.workspaceId);
|
|
@@ -215,6 +415,27 @@ function executeInspection(start, name, plan, options, persist) {
|
|
|
215
415
|
provider: safeDisplay(runtime.provider),
|
|
216
416
|
status: safeDisplay(runtime.status),
|
|
217
417
|
};
|
|
418
|
+
// Skill 装载合同只在注册过 trace 词汇的 runtime provider 上成立。hermes 上部署会
|
|
419
|
+
// "成功"、Skill 显示 enabled,agent 一加载却 100% `Skill not found` 且零告警——
|
|
420
|
+
// 这里 fail-closed,把确定性死局挡在任何远端写入之前。
|
|
421
|
+
const runtimeProvider = resources.runtime.provider;
|
|
422
|
+
if (!runtimeProvider) {
|
|
423
|
+
throw new MulticaReadFailure('scope.runtime-provider-unknown', {
|
|
424
|
+
code: 'scope.runtime-provider-unknown',
|
|
425
|
+
expected: supportedRuntimeProviders(),
|
|
426
|
+
actual: '(empty)',
|
|
427
|
+
hint: '远端 runtime 未报告 provider 字段;用 multica runtime get 核实后再部署',
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
if (!vocabulariesForRuntimeProvider(runtimeProvider).length) {
|
|
431
|
+
throw new MulticaReadFailure('scope.runtime-provider-unsupported', {
|
|
432
|
+
code: 'scope.runtime-provider-unsupported',
|
|
433
|
+
expected: supportedRuntimeProviders(),
|
|
434
|
+
actual: runtimeProvider,
|
|
435
|
+
hint: 'dta 的 Skill 装载合同只在这些 runtime provider 上成立' +
|
|
436
|
+
'(如 hermes 上 workspace skill 一律加载不到);换用受支持 provider 的 runtime',
|
|
437
|
+
});
|
|
438
|
+
}
|
|
218
439
|
const agents = jsonArray(runRead(command, scopedArgs(target, ['agent', 'list', '--include-archived', '--output', 'json']), 'agent.list', target.root, env, timeoutMs, calls).stdout, 'agent list');
|
|
219
440
|
resources.agentCandidates = agents
|
|
220
441
|
.filter((item) => optionalField(item.name) === target.projectName)
|
|
@@ -240,9 +461,26 @@ function executeInspection(start, name, plan, options, persist) {
|
|
|
240
461
|
const agent = jsonObject(runRead(command, scopedArgs(target, ['agent', 'get', target.agentId, '--output', 'json']), 'agent.get', target.root, env, timeoutMs, calls).stdout, 'agent get');
|
|
241
462
|
const agentWorkspace = optionalField(agent.workspace_id);
|
|
242
463
|
const agentRuntime = optionalField(agent.runtime_id);
|
|
243
|
-
if (idOf(agent) !== target.agentId || agentWorkspace !== target.workspaceId
|
|
244
|
-
|
|
245
|
-
|
|
464
|
+
if (idOf(agent) !== target.agentId || agentWorkspace !== target.workspaceId) {
|
|
465
|
+
throw new MulticaReadFailure('scope.agent-binding-mismatch', {
|
|
466
|
+
code: 'scope.agent-binding-mismatch',
|
|
467
|
+
expected: `workspace=${target.workspaceId}`,
|
|
468
|
+
actual: `workspace=${safeDisplay(agentWorkspace) || '(empty)'}`,
|
|
469
|
+
hint: 'Agent 不属于目标 Workspace;Workspace 边界不允许跨越',
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
// Runtime 归属漂移不再当 inspection 硬失败:那会把 deploy 与 --retire 一起锁死
|
|
473
|
+
// (retire 被它要解开的锁挡住)。漂移记录成 diff;apply 层负责在没有显式
|
|
474
|
+
// --rebind-runtime 时拒绝,retire 不受它阻挡。
|
|
475
|
+
if (agentRuntime !== target.runtimeId) {
|
|
476
|
+
diff.push('agent.runtime-drift');
|
|
477
|
+
failureDetails.push({
|
|
478
|
+
code: 'agent.runtime-drift',
|
|
479
|
+
expected: target.runtimeId,
|
|
480
|
+
actual: safeDisplay(agentRuntime) || '(empty)',
|
|
481
|
+
hint: '迁移 runtime 用 dta deploy --rebind-runtime(先 --dry-run);' +
|
|
482
|
+
'退役用 deploy --retire,不受漂移阻挡',
|
|
483
|
+
});
|
|
246
484
|
}
|
|
247
485
|
resources.agent = {
|
|
248
486
|
id: target.agentId,
|
|
@@ -260,7 +498,12 @@ function executeInspection(start, name, plan, options, persist) {
|
|
|
260
498
|
})).sort(compareNamedResource);
|
|
261
499
|
}
|
|
262
500
|
else if (resources.agentCandidates.length > 0) {
|
|
263
|
-
throw new MulticaReadFailure('agent.unbound-name-candidates'
|
|
501
|
+
throw new MulticaReadFailure('agent.unbound-name-candidates', {
|
|
502
|
+
code: 'agent.unbound-name-candidates',
|
|
503
|
+
actual: resources.agentCandidates.map((item) => `${item.id}${item.archived ? ' (archived)' : ''}`),
|
|
504
|
+
hint: '远端已有同名 Agent,dta 不按名字猜绑定;核对候选后在 manifest 声明 ' +
|
|
505
|
+
'provider.agentIdFrom 指向正确 ID(或依赖首次 deploy 写入的 state.binding.agentId)',
|
|
506
|
+
});
|
|
264
507
|
}
|
|
265
508
|
else {
|
|
266
509
|
diff.push('agent.create');
|
|
@@ -279,7 +522,13 @@ function executeInspection(start, name, plan, options, persist) {
|
|
|
279
522
|
}
|
|
280
523
|
}
|
|
281
524
|
catch (error) {
|
|
282
|
-
|
|
525
|
+
if (error instanceof MulticaReadFailure) {
|
|
526
|
+
failures.push(error.category);
|
|
527
|
+
if (error.detail)
|
|
528
|
+
failureDetails.push(error.detail);
|
|
529
|
+
}
|
|
530
|
+
else
|
|
531
|
+
failures.push('provider.read-failed');
|
|
283
532
|
}
|
|
284
533
|
const canonical = {
|
|
285
534
|
cliVersion,
|
|
@@ -319,6 +568,8 @@ function executeInspection(start, name, plan, options, persist) {
|
|
|
319
568
|
resources,
|
|
320
569
|
diff: [...diff].sort(),
|
|
321
570
|
failures: [...failures].sort(),
|
|
571
|
+
failureDetails,
|
|
572
|
+
envSanitized: sanitized.removed,
|
|
322
573
|
calls,
|
|
323
574
|
evidencePath,
|
|
324
575
|
remoteRead: true,
|
|
@@ -408,20 +659,28 @@ function resolveMulticaTarget(start, name, env, cliVersion, agentIdOverride = ''
|
|
|
408
659
|
blocking.push('agent-id-override.invalid');
|
|
409
660
|
}
|
|
410
661
|
}
|
|
411
|
-
else
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
662
|
+
else {
|
|
663
|
+
// 来源选择收口到 multicaAgentIdSource(schedule 共用同一份,防两处漂移)。
|
|
664
|
+
// A declared-but-empty agentIdFrom means "not yet bound" on a workspace whose Agent
|
|
665
|
+
// has not been created. Treat it like an omitted agentIdFrom (createable, no blocker):
|
|
666
|
+
// the deploy W4 create path saves the real ID to state, and the run/retire paths add
|
|
667
|
+
// their own `agent-id.missing` blocker. A malformed (non-empty, invalid) value still blocks.
|
|
668
|
+
const source = multicaAgentIdSource(workspace);
|
|
669
|
+
if (source.kind === 'declared')
|
|
670
|
+
agentId = resolveRef(source.ref, 'agent-id', true);
|
|
671
|
+
else if (source.kind === 'state') {
|
|
672
|
+
try {
|
|
673
|
+
agentId = resourceId(source.agentId, 'state.agent-id');
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
blocking.push('state.agent-id.invalid');
|
|
677
|
+
}
|
|
419
678
|
}
|
|
420
679
|
}
|
|
421
680
|
return {
|
|
422
681
|
root: info.root,
|
|
423
682
|
projectName: info.project.displayName,
|
|
424
|
-
skills: [...new Set(
|
|
683
|
+
skills: [...new Set(info.project.skills)].sort(),
|
|
425
684
|
workspace,
|
|
426
685
|
profile: provider.profile,
|
|
427
686
|
workspaceId,
|
|
@@ -461,7 +720,7 @@ function scopedArgs(target, args) {
|
|
|
461
720
|
function runRead(command, argv, id, cwd, env, timeoutMs, calls) {
|
|
462
721
|
const run = spawnSync(command, argv, {
|
|
463
722
|
cwd,
|
|
464
|
-
env,
|
|
723
|
+
env: sanitizeProviderEnv(env).env,
|
|
465
724
|
encoding: 'utf8',
|
|
466
725
|
timeout: timeoutMs,
|
|
467
726
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -479,10 +738,27 @@ function runRead(command, argv, id, cwd, env, timeoutMs, calls) {
|
|
|
479
738
|
stderrHash: sha256(stderr),
|
|
480
739
|
passed,
|
|
481
740
|
});
|
|
482
|
-
if (!passed)
|
|
483
|
-
|
|
741
|
+
if (!passed) {
|
|
742
|
+
emitProviderDiagnostic(id, exitCode, stderr || stdout);
|
|
743
|
+
throw new MulticaReadFailure(`command.${id}`, {
|
|
744
|
+
code: `command.${id}`,
|
|
745
|
+
actual: `exit=${exitCode}`,
|
|
746
|
+
hint: 'provider CLI 原始 stderr 已透传到终端(证据文件只存 hash)',
|
|
747
|
+
});
|
|
748
|
+
}
|
|
484
749
|
return { stdout, stderr };
|
|
485
750
|
}
|
|
751
|
+
/**
|
|
752
|
+
* Provider CLI failures used to vanish into a hash: the receipt recorded `stderrHash` and the
|
|
753
|
+
* user saw only `command.<id>`. The raw text still must never enter evidence files, so it goes
|
|
754
|
+
* straight to dta's own stderr — visible in the moment, never persisted.
|
|
755
|
+
*/
|
|
756
|
+
export function emitProviderDiagnostic(id, exitCode, output) {
|
|
757
|
+
const text = output.trim();
|
|
758
|
+
const body = !text ? '(无 stderr 输出)'
|
|
759
|
+
: text.length > 2048 ? `${text.slice(0, 2048)}…` : text;
|
|
760
|
+
process.stderr.write(`[multica ${id}] exit=${exitCode} ${body}\n`);
|
|
761
|
+
}
|
|
486
762
|
function parseMulticaVersion(value) {
|
|
487
763
|
const match = value.match(/\bmultica\s+([0-9]+\.[0-9]+\.[0-9]+(?:[-+][A-Za-z0-9.-]+)?)/i);
|
|
488
764
|
if (!match)
|
|
@@ -603,6 +879,8 @@ function emptyStatus(workspace) {
|
|
|
603
879
|
observedHash: workspace.observedHash,
|
|
604
880
|
diff: [],
|
|
605
881
|
failures: ['inspection.missing'],
|
|
882
|
+
failureDetails: [],
|
|
883
|
+
observedAgentRuntimeId: '',
|
|
606
884
|
remoteRead: false,
|
|
607
885
|
remoteWrite: false,
|
|
608
886
|
sideEffect: false,
|