@xdxer/dingtalk-agent 0.1.4-beta.7 → 0.1.4-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.en.md +395 -0
- package/README.md +466 -36
- package/dist/bin/dingtalk-agent.js +1071 -340
- package/dist/bin/dingtalk-agent.js.map +1 -1
- package/dist/src/actions.js +98 -14
- package/dist/src/actions.js.map +1 -1
- package/dist/src/agent-audit.js +460 -0
- package/dist/src/agent-audit.js.map +1 -0
- package/dist/src/agent-bindings.js +132 -0
- package/dist/src/agent-bindings.js.map +1 -0
- package/dist/src/agent-definition.js +182 -0
- package/dist/src/agent-definition.js.map +1 -0
- package/dist/src/agent-enhance.js +678 -0
- package/dist/src/agent-enhance.js.map +1 -0
- package/dist/src/bootstrap.js +125 -17
- package/dist/src/bootstrap.js.map +1 -1
- package/dist/src/development-workspace.js +729 -0
- package/dist/src/development-workspace.js.map +1 -0
- package/dist/src/dws.js +145 -0
- package/dist/src/dws.js.map +1 -1
- package/dist/src/eval-evidence.js +193 -0
- package/dist/src/eval-evidence.js.map +1 -0
- package/dist/src/init.js +1 -1
- package/dist/src/init.js.map +1 -1
- package/dist/src/invocation.js +36 -0
- package/dist/src/invocation.js.map +1 -0
- package/dist/src/lab.js +679 -0
- package/dist/src/lab.js.map +1 -0
- package/dist/src/lease.js +100 -0
- package/dist/src/lease.js.map +1 -0
- package/dist/src/memory/candidates.js +451 -0
- package/dist/src/memory/candidates.js.map +1 -0
- package/dist/src/memory/completion-evidence.js +536 -0
- package/dist/src/memory/completion-evidence.js.map +1 -0
- package/dist/src/memory/operational.js +263 -0
- package/dist/src/memory/operational.js.map +1 -0
- package/dist/src/memory/remote-state.js +478 -0
- package/dist/src/memory/remote-state.js.map +1 -0
- package/dist/src/memory/task-checkpoints.js +204 -0
- package/dist/src/memory/task-checkpoints.js.map +1 -0
- package/dist/src/multica-deploy.js +1480 -0
- package/dist/src/multica-deploy.js.map +1 -0
- package/dist/src/multica-provider.js +685 -0
- package/dist/src/multica-provider.js.map +1 -0
- package/dist/src/opencode-evals.js +1062 -0
- package/dist/src/opencode-evals.js.map +1 -0
- package/dist/src/opencode-provider.js +531 -0
- package/dist/src/opencode-provider.js.map +1 -0
- package/dist/src/opencode-workspace.js +197 -0
- package/dist/src/opencode-workspace.js.map +1 -0
- package/dist/src/perception.js +225 -0
- package/dist/src/perception.js.map +1 -0
- package/dist/src/personal-event-evals.js +595 -0
- package/dist/src/personal-event-evals.js.map +1 -0
- package/dist/src/promotion.js +786 -0
- package/dist/src/promotion.js.map +1 -0
- package/dist/src/remote-semantic-state-live-evals.js +888 -0
- package/dist/src/remote-semantic-state-live-evals.js.map +1 -0
- package/dist/src/remote-semantic-state-worker.js +38 -0
- package/dist/src/remote-semantic-state-worker.js.map +1 -0
- package/dist/src/remote-state-evals.js +501 -0
- package/dist/src/remote-state-evals.js.map +1 -0
- package/dist/src/response-gate.js +51 -0
- package/dist/src/response-gate.js.map +1 -0
- package/dist/src/robot-evals.js +770 -0
- package/dist/src/robot-evals.js.map +1 -0
- package/dist/src/sessions.js +66 -105
- package/dist/src/sessions.js.map +1 -1
- package/dist/src/skill-manager.js +25 -16
- package/dist/src/skill-manager.js.map +1 -1
- package/dist/src/skills.js.map +1 -1
- package/dist/src/storage-evals.js +26 -0
- package/dist/src/storage-evals.js.map +1 -0
- package/dist/src/types.js.map +1 -1
- package/dist/src/waits.js +5 -1
- package/dist/src/waits.js.map +1 -1
- package/dist/src/workspace.js +28 -3
- package/dist/src/workspace.js.map +1 -1
- package/docs/INSTALLATION.md +47 -0
- package/docs/SECOND-AGENT-ACCEPTANCE.md +62 -0
- package/docs/architecture/agent-memory-topology.png +0 -0
- package/docs/architecture/agent-memory-topology.svg +132 -0
- package/docs/architecture/general-agent-kernel-topology.png +0 -0
- package/docs/architecture/general-agent-kernel-topology.svg +149 -0
- package/docs/architecture/provider-bound-development-workspace.png +0 -0
- package/docs/architecture/provider-bound-development-workspace.svg +141 -0
- package/docs/architecture/task-completion-gate.png +0 -0
- package/docs/architecture/task-completion-gate.svg +191 -0
- package/docs/schemas/agent-audit-load-evidence.schema.json +14 -0
- package/docs/schemas/agent-audit.schema.json +92 -0
- package/docs/schemas/agent-bindings.schema.json +54 -0
- package/docs/schemas/agent-definition.schema.json +78 -0
- package/docs/schemas/agent-enhancement-plan.schema.json +88 -0
- package/docs/schemas/agent-enhancement-receipt.schema.json +37 -0
- package/docs/schemas/enriched-invocation.schema.json +46 -0
- package/docs/schemas/eval-candidate-plan.schema.json +28 -0
- package/docs/schemas/eval-candidate-result.schema.json +20 -0
- package/docs/schemas/eval-candidate.schema.json +30 -0
- package/docs/schemas/invocation.schema.json +19 -0
- package/docs/schemas/memory-candidate-proposal.schema.json +18 -0
- package/docs/schemas/memory-candidate.schema.json +76 -0
- package/docs/schemas/memory-publish-target.schema.json +25 -0
- package/docs/schemas/multica-deployment-list.schema.json +29 -0
- package/docs/schemas/multica-deployment-operation.schema.json +51 -0
- package/docs/schemas/multica-deployment-plan.schema.json +70 -0
- package/docs/schemas/multica-deployment-receipt.schema.json +87 -0
- package/docs/schemas/multica-deployment-status.schema.json +23 -0
- package/docs/schemas/multica-workspace-inspection.schema.json +77 -0
- package/docs/schemas/multica-workspace-plan.schema.json +68 -0
- package/docs/schemas/multica-workspace-resource-list.schema.json +27 -0
- package/docs/schemas/multica-workspace-status.schema.json +34 -0
- package/docs/schemas/observation.schema.json +21 -0
- package/docs/schemas/operational-memory-provider.schema.json +36 -0
- package/docs/schemas/operational-memory-record.schema.json +24 -0
- package/docs/schemas/perception-input.schema.json +56 -0
- package/docs/schemas/project.schema.json +112 -0
- package/docs/schemas/promotion-list.schema.json +36 -0
- package/docs/schemas/promotion-plan.schema.json +60 -0
- package/docs/schemas/promotion-policy.schema.json +29 -0
- package/docs/schemas/promotion-receipt.schema.json +43 -0
- package/docs/schemas/promotion-status.schema.json +23 -0
- package/docs/schemas/release-readiness.schema.json +65 -0
- package/docs/schemas/remote-semantic-state-live-eval.schema.json +60 -0
- package/docs/schemas/remote-semantic-state-manifest.schema.json +79 -0
- package/docs/schemas/remote-semantic-state-provider.schema.json +98 -0
- package/docs/schemas/response-gate.schema.json +20 -0
- package/docs/schemas/task-checkpoint.schema.json +71 -0
- package/docs/schemas/task-completion-evidence.schema.json +154 -0
- package/docs/schemas/workspace-doctor.schema.json +56 -0
- package/docs/schemas/workspace-state.schema.json +39 -0
- package/evals/baselines/2026-07-16/opencode-basic-010-completion-summary.json +123 -0
- package/evals/baselines/2026-07-16/opencode-basic-skill-required-summary.json +69 -0
- package/evals/baselines/2026-07-16/opencode-multi-surface-summary.json +63 -0
- package/evals/baselines/2026-07-16/remote-state-live-summary.json +70 -0
- package/evals/baselines/2026-07-17/agent-enhance-opencode-dogfood-summary.json +98 -0
- package/evals/baselines/2026-07-17/personal-event-live-readiness-summary.json +68 -0
- package/examples/agents/fde-coach/AGENTS.md +26 -0
- package/examples/agents/fde-coach/MEMORY.md +3 -0
- package/examples/agents/fde-coach/fields/default/field.json +24 -0
- package/examples/agents/fde-coach/knowledge/INDEX.md +4 -0
- package/examples/agents/fde-coach/skills/fde-coach/SKILL.md +13 -0
- package/examples/agents/release-manager/AGENTS.md +26 -0
- package/examples/agents/release-manager/MEMORY.md +3 -0
- package/examples/agents/release-manager/fields/default/field.json +24 -0
- package/examples/agents/release-manager/knowledge/INDEX.md +4 -0
- package/examples/agents/release-manager/skills/release-manager/SKILL.md +13 -0
- package/lab/README.md +109 -0
- package/lab/agent-eval/catalog.json +91 -0
- package/lab/agent-eval/classic-failures.json +177 -0
- package/lab/agent-eval/completion-gate-regression.json +99 -0
- package/lab/agent-eval/personal-event-live.example.json +94 -0
- package/lab/agent-eval/remote-semantic-state-live.example.json +70 -0
- package/lab/agent-eval/remote-semantic-state-provider.fixture.json +47 -0
- package/lab/agent-eval/remote-state-workspace/AGENTS.md +8 -0
- package/lab/agent-eval/remote-state-workspace/opencode.json +7 -0
- package/lab/agent-eval/remote-state-workspace/skills/remote-state-operator/SKILL.md +13 -0
- package/lab/agent-eval/remote-state.example.json +31 -0
- package/lab/agent-eval/workspace/AGENTS.md +7 -0
- package/lab/agent-eval/workspace/MEMORY.md +4 -0
- package/lab/agent-eval/workspace/artifacts/pending-review.md +3 -0
- package/lab/agent-eval/workspace/knowledge/INDEX.md +4 -0
- package/lab/agent-eval/workspace/opencode.json +20 -0
- package/lab/manifest.example.json +27 -0
- package/lab/manifest.personal-event.example.json +27 -0
- package/lab/project-workspace/README.md +11 -0
- package/lab/project-workspace/fake-multica-provider.mjs +266 -0
- package/lab/project-workspace/multica-deploy.fixture.json +29 -0
- package/lab/project-workspace/multica-readonly.fixture.json +69 -0
- package/lab/project-workspace/observation.fixture.json +14 -0
- package/lab/project-workspace/opencode-provider-suite.json +65 -0
- package/lab/project-workspace/project.fixture.json +44 -0
- package/lab/project-workspace/promotion-policy.fixture.json +15 -0
- package/lab/robot-eval/pool.example.json +30 -0
- package/lab/robot-eval/suite.json +123 -0
- package/lab/robot-eval/workspace/AGENTS.md +21 -0
- package/lab/robot-eval/workspace/MEMORY.md +3 -0
- package/lab/robot-eval/workspace/knowledge/INDEX.md +5 -0
- package/lab/robot-eval/workspace/opencode.json +22 -0
- package/lab/schemas/agent-eval-catalog.schema.json +47 -0
- package/lab/schemas/lab-manifest.schema.json +70 -0
- package/lab/schemas/personal-event-eval.schema.json +91 -0
- package/lab/schemas/remote-state-eval.schema.json +66 -0
- package/lab/schemas/robot-eval-suite.schema.json +184 -0
- package/lab/schemas/robot-pool.schema.json +56 -0
- package/package.json +30 -14
- package/skills/dingtalk-agent-boot-multica/SKILL.md +40 -0
- package/skills/dingtalk-agent-compose/SKILL.md +110 -0
- package/skills/dingtalk-agent-compose/assets/AGENTS.template.md +26 -0
- package/skills/dingtalk-agent-compose/assets/agent.bindings.dingtalk-doc.template.json +13 -0
- package/skills/dingtalk-agent-compose/assets/agent.bindings.local.template.json +13 -0
- package/skills/dingtalk-agent-compose/assets/opencode.template.json +12 -0
- package/skills/dingtalk-agent-compose/assets/role-skill.template.md +24 -0
- package/skills/dingtalk-agent-compose/evals/evals.json +94 -0
- package/skills/dingtalk-agent-compose/references/agent-definition-contract.md +55 -0
- package/skills/dingtalk-agent-compose/references/opencode-host-contract.md +65 -0
- package/skills/dingtalk-agent-compose/references/storage-routing.md +20 -0
- package/skills/dingtalk-agent-deploy/SKILL.md +60 -0
- package/skills/dingtalk-agent-deploy/references/multica-deployment-contract.md +49 -0
- package/skills/dingtalk-agent-deploy/references/promotion-observation-contract.md +49 -0
- package/skills/dingtalk-agent-eval/SKILL.md +116 -0
- package/skills/dingtalk-agent-eval/assets/eval-catalog.template.json +18 -0
- package/skills/dingtalk-agent-eval/evals/evals.json +61 -0
- package/skills/dingtalk-agent-eval/references/eval-topology.md +34 -0
- package/skills/dingtalk-agent-eval/references/evidence-contract.md +31 -0
- package/skills/dingtalk-agent-eval/references/scenario-taxonomy.md +25 -0
- package/skills/dingtalk-agent-eval/references/storage-modes.md +75 -0
- package/skills/dingtalk-basic-behavior/SKILL.md +63 -3
- package/skills/dingtalk-basic-behavior/assets/memory-candidate-proposal.json +10 -0
- package/skills/dingtalk-basic-behavior/assets/{task-checkpoint.md → task-checkpoint.json} +2 -21
- package/skills/dingtalk-basic-behavior/references/action-contract.md +2 -0
- package/skills/dingtalk-basic-behavior/references/memory-and-evolution.md +15 -1
- package/skills/dingtalk-basic-behavior/references/perception-and-gates.md +28 -0
- package/skills/dingtalk-basic-behavior/references/task-lifecycle.md +32 -7
- package/dist/src/boot.js +0 -70
- package/dist/src/boot.js.map +0 -1
- package/dist/src/duty.js +0 -74
- package/dist/src/duty.js.map +0 -1
- package/dist/src/kb.js +0 -240
- package/dist/src/kb.js.map +0 -1
- package/dist/src/runs.js +0 -79
- package/dist/src/runs.js.map +0 -1
- package/docs/ARCHITECTURE.md +0 -219
- package/docs/MINIMAL-WORKSPACE-V1.md +0 -172
- package/docs/OPEN-SOURCE-REFERENCES.md +0 -107
- package/docs/SELF-TEST.md +0 -252
- package/docs//345/206/205/347/275/221/345/256/236/347/233/270.md +0 -77
- package/evals/baselines/2026-07-14/behavior-summary.json +0 -28
- package/evals/baselines/2026-07-14/contract-summary.json +0 -18
- package/evals/baselines/2026-07-14/live-canary-summary.json +0 -25
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/SKILL.md +0 -72
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/action-contract.md +0 -31
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/event-to-behavior.md +0 -22
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/memory-and-evolution.md +0 -25
- package/evals/baselines/2026-07-15/dingtalk-basic-behavior-0.3.0/references/runtime-modes.md +0 -34
- package/evals/baselines/2026-07-15/task-lifecycle-summary.json +0 -50
- package/evals/evals.json +0 -339
- package/evals/fixtures/dm-ambiguous-send.json +0 -4
- package/evals/fixtures/dm-blocked.json +0 -4
- package/evals/fixtures/dm-clear.json +0 -4
- package/evals/fixtures/dm-discussion.json +0 -4
- package/evals/fixtures/dm-doc-write-no-tool.json +0 -4
- package/evals/fixtures/dm-long-task-ack.json +0 -4
- package/evals/fixtures/dm-nonblocking-gap.json +0 -4
- package/evals/fixtures/dm-structured-task.json +0 -4
- package/evals/fixtures/group.json +0 -10
- package/evals/fixtures/mentioned.json +0 -3
- package/evals/run-contract-evals.mjs +0 -1169
- package/evals/run-shadow-evals.mjs +0 -267
- package/evals/runners/README.md +0 -66
- package/evals/runners/claude-shadow.mjs +0 -533
- package/evals/schemas/action-request.schema.json +0 -77
- package/evals/shadow-evals.json +0 -133
- package/skills/AGENTS.md +0 -104
- package/skills//344/273/273/345/212/241.md +0 -48
- package/skills//345/237/272/347/241/200/350/241/214/344/270/272.md +0 -44
- package/skills//345/277/203/350/267/263.md +0 -79
- package/skills//346/266/210/346/201/257.md +0 -50
- package/skills//347/237/245/350/257/206.md +0 -55
- package/skills//350/257/204/346/265/213.md +0 -62
- package/skills//351/222/211/351/222/211.md +0 -64
- package/templates/ontology/index.md +0 -24
- package/templates/ontology/self/access.md +0 -38
- package/templates/ontology/self/role-spec.md +0 -34
- package/templates/ontology/self/workspace.md +0 -41
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
4
|
+
import { digest, stableStringify } from '../events.js';
|
|
5
|
+
import { acquireLease } from '../lease.js';
|
|
6
|
+
export const COMPLETION_EVIDENCE_ID_PATTERN = /^evd_[a-f0-9]{32}$/;
|
|
7
|
+
export const ACTION_RECEIPT_EVIDENCE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
8
|
+
const HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
9
|
+
const RUN_ID_PATTERN = /^run_[a-f0-9]{20}$/;
|
|
10
|
+
const ACTION_ID_PATTERN = /^act_[a-f0-9]{20}$/;
|
|
11
|
+
const ARTIFACT_CONDITION = /(?:文件|文档|产物|工作区|保存|创建|写入|file|document|artifact|workspace|write|saved|created)/iu;
|
|
12
|
+
const MESSAGE_DELIVERY_CONDITION = /(?:回复|消息|发送|送达|回读|reply|message|send|sent|deliver|readback)/iu;
|
|
13
|
+
/**
|
|
14
|
+
* Host-only issuance API. It is intentionally not wired to the public CLI: callers must already
|
|
15
|
+
* hold a trusted Session/Run or an explicit host identity.
|
|
16
|
+
*/
|
|
17
|
+
export function issueCompletionEvidence(root, cfg, claim, options) {
|
|
18
|
+
const context = resolveEvidenceContext(root, cfg, options.sessionPath);
|
|
19
|
+
const issuer = resolveIssuer(context, options);
|
|
20
|
+
const issuedAt = parseNow(options.now).toISOString();
|
|
21
|
+
const doneWhen = requireText(claim.doneWhen, 'Completion evidence doneWhen');
|
|
22
|
+
const conditionHash = digest(doneWhen);
|
|
23
|
+
let reference;
|
|
24
|
+
let observed;
|
|
25
|
+
if (claim.kind === 'artifact') {
|
|
26
|
+
const artifact = observeArtifact(root, claim.artifact.path);
|
|
27
|
+
const expected = String(claim.artifact.expectedSha256 || '').trim().toLowerCase();
|
|
28
|
+
if (expected && (!HASH_PATTERN.test(expected) || expected !== artifact.sha256)) {
|
|
29
|
+
throw new Error(`Completion Artifact hash 不一致:expected=${expected || '-'}, actual=${artifact.sha256}`);
|
|
30
|
+
}
|
|
31
|
+
reference = {
|
|
32
|
+
type: 'artifact', locator: artifact.locator,
|
|
33
|
+
sha256: artifact.sha256, bytes: artifact.bytes,
|
|
34
|
+
};
|
|
35
|
+
observed = { status: 'verified', hash: artifact.sha256 };
|
|
36
|
+
}
|
|
37
|
+
else if (claim.kind === 'action-receipt') {
|
|
38
|
+
assertReceiptCondition(doneWhen, claim.receipt.action);
|
|
39
|
+
const receipt = observeActionReceipt(root, cfg, context, claim.receipt.runId, claim.receipt.actionId, claim.receipt.action, new Date(issuedAt));
|
|
40
|
+
reference = receipt.reference;
|
|
41
|
+
observed = { status: 'verified', hash: receipt.hash };
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const confirmation = observeConfirmation(root, cfg, context, issuer, conditionHash, claim.confirmation);
|
|
45
|
+
reference = confirmation.reference;
|
|
46
|
+
observed = { status: 'confirmed', hash: confirmation.hash };
|
|
47
|
+
}
|
|
48
|
+
const evidence = {
|
|
49
|
+
$schema: 'dingtalk-agent/task-completion-evidence@1',
|
|
50
|
+
evidenceId: `evd_${randomUUID().replaceAll('-', '')}`,
|
|
51
|
+
kind: claim.kind,
|
|
52
|
+
taskId: context.session.id,
|
|
53
|
+
scopeId: context.session.contextId,
|
|
54
|
+
sessionId: context.session.id,
|
|
55
|
+
issuer,
|
|
56
|
+
condition: { ref: 'doneWhen', hash: conditionHash },
|
|
57
|
+
reference,
|
|
58
|
+
observed,
|
|
59
|
+
issuedAt,
|
|
60
|
+
};
|
|
61
|
+
validateCompletionEvidence(evidence);
|
|
62
|
+
verifyEvidenceSource(root, cfg, context, evidence, new Date(issuedAt));
|
|
63
|
+
const registryPath = completionEvidenceRegistryPath(root, cfg, context.session);
|
|
64
|
+
const release = acquireLease(`${registryPath}.lock`, `Task ${context.session.id} completion evidence 正在签发`, {
|
|
65
|
+
waitMs: 3_000, staleAfterMs: 2 * 60_000,
|
|
66
|
+
});
|
|
67
|
+
try {
|
|
68
|
+
const current = readRegistry(registryPath, context, true);
|
|
69
|
+
if (current.evidence.some((item) => item.evidenceId === evidence.evidenceId)) {
|
|
70
|
+
throw new Error(`Completion evidenceId 碰撞: ${evidence.evidenceId}`);
|
|
71
|
+
}
|
|
72
|
+
const next = {
|
|
73
|
+
...current,
|
|
74
|
+
revision: current.revision + 1,
|
|
75
|
+
evidence: [...current.evidence, evidence],
|
|
76
|
+
updatedAt: issuedAt,
|
|
77
|
+
};
|
|
78
|
+
writeJsonAtomic(registryPath, next);
|
|
79
|
+
const readback = readRegistry(registryPath, context, false);
|
|
80
|
+
if (stableStringify(readback) !== stableStringify(next)) {
|
|
81
|
+
throw new Error('Completion evidence registry 原子写后回读不一致');
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
$schema: 'dingtalk-agent/task-completion-evidence-result@1',
|
|
85
|
+
evidenceId: evidence.evidenceId,
|
|
86
|
+
registryRevision: readback.revision,
|
|
87
|
+
evidence: clone(evidence),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
release();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Resolve every opaque ID from trusted host state and re-verify its source before completion. */
|
|
95
|
+
export function resolveCompletionEvidence(root, cfg, sessionPath, doneWhen, evidenceIds, options = {}) {
|
|
96
|
+
const context = resolveEvidenceContext(root, cfg, sessionPath);
|
|
97
|
+
const conditionHash = digest(requireText(doneWhen, 'Task doneWhen'));
|
|
98
|
+
if (!Array.isArray(evidenceIds) || !evidenceIds.length) {
|
|
99
|
+
throw new Error('Task completed 状态必须引用宿主签发的 completion evidenceId');
|
|
100
|
+
}
|
|
101
|
+
if (new Set(evidenceIds).size !== evidenceIds.length) {
|
|
102
|
+
throw new Error('Task completion evidenceId 不能重复');
|
|
103
|
+
}
|
|
104
|
+
for (const evidenceId of evidenceIds) {
|
|
105
|
+
if (!isCompletionEvidenceId(evidenceId)) {
|
|
106
|
+
throw new Error(`Task evidence 只能引用宿主签发的 evidenceId: ${evidenceId || '-'}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const registryPath = completionEvidenceRegistryPath(root, cfg, context.session);
|
|
110
|
+
if (!existsSync(registryPath)) {
|
|
111
|
+
throw new Error(`当前 Session 没有宿主 completion evidence registry`);
|
|
112
|
+
}
|
|
113
|
+
const registry = readRegistry(registryPath, context, false);
|
|
114
|
+
const byId = new Map(registry.evidence.map((item) => [item.evidenceId, item]));
|
|
115
|
+
const now = parseNow(options.now);
|
|
116
|
+
return evidenceIds.map((evidenceId) => {
|
|
117
|
+
const evidence = byId.get(evidenceId);
|
|
118
|
+
if (!evidence)
|
|
119
|
+
throw new Error(`找不到宿主签发的 completion evidenceId: ${evidenceId}`);
|
|
120
|
+
validateCompletionEvidence(evidence);
|
|
121
|
+
if (evidence.taskId !== context.session.id ||
|
|
122
|
+
evidence.sessionId !== context.session.id ||
|
|
123
|
+
evidence.scopeId !== context.session.contextId) {
|
|
124
|
+
throw new Error(`Completion evidence ${evidenceId} 与当前 Session/Task 作用域不一致`);
|
|
125
|
+
}
|
|
126
|
+
if (evidence.condition.ref !== 'doneWhen' || evidence.condition.hash !== conditionHash) {
|
|
127
|
+
throw new Error(`Completion evidence ${evidenceId} 与当前 doneWhen 不一致`);
|
|
128
|
+
}
|
|
129
|
+
verifyEvidenceSource(root, cfg, context, evidence, now);
|
|
130
|
+
return clone(evidence);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
export function isCompletionEvidenceId(value) {
|
|
134
|
+
return COMPLETION_EVIDENCE_ID_PATTERN.test(String(value || ''));
|
|
135
|
+
}
|
|
136
|
+
function verifyEvidenceSource(root, cfg, context, evidence, now) {
|
|
137
|
+
if (evidence.issuer.type === 'run') {
|
|
138
|
+
const index = findRunIndex(root, cfg, evidence.issuer.id);
|
|
139
|
+
assertIndexScope(index, context, `签发 Run ${evidence.issuer.id}`);
|
|
140
|
+
}
|
|
141
|
+
if (evidence.kind === 'artifact') {
|
|
142
|
+
if (evidence.reference.type !== 'artifact')
|
|
143
|
+
throw new Error('Artifact evidence reference 非法');
|
|
144
|
+
const actual = observeArtifact(root, evidence.reference.locator);
|
|
145
|
+
if (actual.sha256 !== evidence.reference.sha256 ||
|
|
146
|
+
actual.sha256 !== evidence.observed.hash ||
|
|
147
|
+
actual.bytes !== evidence.reference.bytes) {
|
|
148
|
+
throw new Error(`Completion Artifact ${evidence.reference.locator} 内容/hash 已漂移`);
|
|
149
|
+
}
|
|
150
|
+
if (evidence.observed.status !== 'verified') {
|
|
151
|
+
throw new Error(`Completion Artifact ${evidence.evidenceId} 尚未 verified`);
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (evidence.kind === 'action-receipt') {
|
|
156
|
+
if (evidence.reference.type !== 'action-receipt') {
|
|
157
|
+
throw new Error('Action Receipt evidence reference 非法');
|
|
158
|
+
}
|
|
159
|
+
assertReceiptConditionHash(evidence);
|
|
160
|
+
const receipt = observeActionReceipt(root, cfg, context, evidence.reference.runId, evidence.reference.actionId, evidence.reference.action, now);
|
|
161
|
+
if (receipt.hash !== evidence.reference.receiptHash ||
|
|
162
|
+
receipt.hash !== evidence.observed.hash ||
|
|
163
|
+
receipt.reference.completedAt !== evidence.reference.completedAt ||
|
|
164
|
+
receipt.reference.validUntil !== evidence.reference.validUntil) {
|
|
165
|
+
throw new Error(`Action Receipt ${evidence.reference.actionId} 内容/hash 已漂移`);
|
|
166
|
+
}
|
|
167
|
+
if (evidence.observed.status !== 'verified') {
|
|
168
|
+
throw new Error(`Action Receipt ${evidence.reference.actionId} 尚未 verified`);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (evidence.reference.type !== 'confirmation') {
|
|
173
|
+
throw new Error('Confirmation evidence reference 非法');
|
|
174
|
+
}
|
|
175
|
+
const observed = reobserveConfirmation(root, cfg, context, evidence);
|
|
176
|
+
if (observed !== evidence.reference.recordHash || observed !== evidence.observed.hash ||
|
|
177
|
+
evidence.observed.status !== 'confirmed') {
|
|
178
|
+
throw new Error(`Confirmation ${evidence.reference.confirmationId} 未确认或记录已漂移`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function observeArtifact(root, input) {
|
|
182
|
+
const locator = requireText(input, 'Completion Artifact locator');
|
|
183
|
+
if (isAbsolute(locator) || locator.split(/[\\/]/).includes('..')) {
|
|
184
|
+
throw new Error(`Completion Artifact 必须是 Workspace 内相对路径: ${locator}`);
|
|
185
|
+
}
|
|
186
|
+
const workspace = realpathSync(resolve(root));
|
|
187
|
+
const candidate = resolve(workspace, locator);
|
|
188
|
+
assertInside(workspace, candidate, 'Completion Artifact');
|
|
189
|
+
if (!existsSync(candidate))
|
|
190
|
+
throw new Error(`Completion Artifact 不存在: ${locator}`);
|
|
191
|
+
const stat = lstatSync(candidate);
|
|
192
|
+
if (stat.isSymbolicLink())
|
|
193
|
+
throw new Error(`Completion Artifact 不接受符号链接: ${locator}`);
|
|
194
|
+
if (!stat.isFile())
|
|
195
|
+
throw new Error(`Completion Artifact 必须是文件: ${locator}`);
|
|
196
|
+
const actual = realpathSync(candidate);
|
|
197
|
+
assertInside(workspace, actual, 'Completion Artifact');
|
|
198
|
+
const body = readFileSync(actual);
|
|
199
|
+
return {
|
|
200
|
+
locator: relative(workspace, actual).split('\\').join('/'),
|
|
201
|
+
sha256: createHash('sha256').update(body).digest('hex'),
|
|
202
|
+
bytes: body.byteLength,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function observeActionReceipt(root, cfg, context, runId, actionId, action, now) {
|
|
206
|
+
if (!RUN_ID_PATTERN.test(runId) || !ACTION_ID_PATTERN.test(actionId)) {
|
|
207
|
+
throw new Error(`Action Receipt reference 非法: run=${runId || '-'}, action=${actionId || '-'}`);
|
|
208
|
+
}
|
|
209
|
+
if (action !== 'reply') {
|
|
210
|
+
throw new Error(`Action Receipt ${action} 不能作为 completed 证据`);
|
|
211
|
+
}
|
|
212
|
+
const index = findRunIndex(root, cfg, runId);
|
|
213
|
+
assertIndexScope(index, context, `Action Receipt Run ${runId}`);
|
|
214
|
+
const indexRoot = resolve(root, cfg.runtime?.eventIndexRoot || '.dingtalk-agent/event-index');
|
|
215
|
+
const path = join(indexRoot, 'actions', runId, actionId, 'receipt.json');
|
|
216
|
+
if (!existsSync(path))
|
|
217
|
+
throw new Error(`找不到 Action Receipt: ${actionId}`);
|
|
218
|
+
const receipt = readJson(path);
|
|
219
|
+
if (receipt.$schema !== 'dingtalk-agent/action-receipt@1' ||
|
|
220
|
+
receipt.actionId !== actionId || receipt.runId !== runId || receipt.kind !== action) {
|
|
221
|
+
throw new Error(`Action Receipt ${actionId} 身份或动作类型不一致`);
|
|
222
|
+
}
|
|
223
|
+
if (receipt.verified !== true || receipt.outcome !== 'confirmed' ||
|
|
224
|
+
!String(receipt.verification || '').trim() ||
|
|
225
|
+
receipt.verification === 'local-no-side-effect') {
|
|
226
|
+
throw new Error(`Action Receipt ${actionId} 未 verified/confirmed`);
|
|
227
|
+
}
|
|
228
|
+
const completedAt = parseTimestamp(receipt.completedAt, `Action Receipt ${actionId} completedAt`);
|
|
229
|
+
const validUntil = new Date(completedAt.getTime() + ACTION_RECEIPT_EVIDENCE_TTL_MS);
|
|
230
|
+
if (now.getTime() > validUntil.getTime()) {
|
|
231
|
+
throw new Error(`Action Receipt ${actionId} 已过期`);
|
|
232
|
+
}
|
|
233
|
+
const hash = digest(stableStringify(receipt));
|
|
234
|
+
return {
|
|
235
|
+
reference: {
|
|
236
|
+
type: 'action-receipt', runId, actionId, action,
|
|
237
|
+
receiptHash: hash,
|
|
238
|
+
completedAt: completedAt.toISOString(),
|
|
239
|
+
validUntil: validUntil.toISOString(),
|
|
240
|
+
},
|
|
241
|
+
hash,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function observeConfirmation(root, cfg, context, issuer, conditionHash, confirmation) {
|
|
245
|
+
const confirmationId = requireText(confirmation.confirmationId, 'Confirmation ID');
|
|
246
|
+
if (confirmation.source === 'trusted-event') {
|
|
247
|
+
const eventId = requireText(confirmation.eventId, 'Confirmation eventId');
|
|
248
|
+
const index = readEventIndex(root, cfg, eventId);
|
|
249
|
+
assertIndexScope(index, context, `Confirmation event ${eventId}`);
|
|
250
|
+
const hash = trustedEventConfirmationHash(index, confirmationId);
|
|
251
|
+
return {
|
|
252
|
+
reference: {
|
|
253
|
+
type: 'confirmation', source: 'trusted-event', confirmationId,
|
|
254
|
+
eventId, recordHash: hash,
|
|
255
|
+
},
|
|
256
|
+
hash,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (issuer.type !== 'host') {
|
|
260
|
+
throw new Error('Host confirmation 必须由显式 host identity 签发');
|
|
261
|
+
}
|
|
262
|
+
const hash = hostConfirmationHash(confirmationId, issuer.id, context.session.id, context.session.contextId, conditionHash);
|
|
263
|
+
return {
|
|
264
|
+
reference: {
|
|
265
|
+
type: 'confirmation', source: 'host-record', confirmationId,
|
|
266
|
+
hostIdentity: issuer.id, recordHash: hash,
|
|
267
|
+
},
|
|
268
|
+
hash,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function reobserveConfirmation(root, cfg, context, evidence) {
|
|
272
|
+
if (evidence.reference.type !== 'confirmation')
|
|
273
|
+
throw new Error('Confirmation reference 非法');
|
|
274
|
+
if (evidence.reference.source === 'trusted-event') {
|
|
275
|
+
const index = readEventIndex(root, cfg, evidence.reference.eventId);
|
|
276
|
+
assertIndexScope(index, context, `Confirmation event ${evidence.reference.eventId}`);
|
|
277
|
+
return trustedEventConfirmationHash(index, evidence.reference.confirmationId);
|
|
278
|
+
}
|
|
279
|
+
if (evidence.issuer.type !== 'host' || evidence.issuer.id !== evidence.reference.hostIdentity) {
|
|
280
|
+
throw new Error('Host confirmation issuer 身份不一致');
|
|
281
|
+
}
|
|
282
|
+
return hostConfirmationHash(evidence.reference.confirmationId, evidence.reference.hostIdentity, evidence.sessionId, evidence.scopeId, evidence.condition.hash);
|
|
283
|
+
}
|
|
284
|
+
function trustedEventConfirmationHash(index, confirmationId) {
|
|
285
|
+
return digest(stableStringify({
|
|
286
|
+
confirmationId,
|
|
287
|
+
eventId: index.eventId,
|
|
288
|
+
eventHash: index.eventHash,
|
|
289
|
+
sessionId: index.sessionId,
|
|
290
|
+
contextId: index.contextId,
|
|
291
|
+
runId: index.runId,
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
function hostConfirmationHash(confirmationId, hostIdentity, sessionId, scopeId, conditionHash) {
|
|
295
|
+
return digest(stableStringify({
|
|
296
|
+
confirmationId, hostIdentity, sessionId, scopeId, conditionHash,
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
function assertReceiptCondition(doneWhen, action) {
|
|
300
|
+
if (action !== 'reply') {
|
|
301
|
+
throw new Error(`Action Receipt ${action} 不能作为 completed 证据`);
|
|
302
|
+
}
|
|
303
|
+
if (ARTIFACT_CONDITION.test(doneWhen) || !MESSAGE_DELIVERY_CONDITION.test(doneWhen)) {
|
|
304
|
+
throw new Error('reply 的消息送达 Receipt 只能证明回复动作;不能证明文件、文档或 Artifact 已完成');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function assertReceiptConditionHash(evidence) {
|
|
308
|
+
if (evidence.reference.type !== 'action-receipt' || evidence.reference.action !== 'reply') {
|
|
309
|
+
throw new Error('只有 verified reply Receipt 可作为首批消息动作完成证据');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function resolveEvidenceContext(root, cfg, sessionInput) {
|
|
313
|
+
const configuredRoot = realpathSync(resolve(root, cfg.runtime.sessionRoot || '.dingtalk-agent/sessions'));
|
|
314
|
+
const input = requireText(sessionInput, 'Completion evidence Session path');
|
|
315
|
+
const sessionPath = realpathSync(resolve(input));
|
|
316
|
+
assertInside(configuredRoot, sessionPath, 'Completion evidence Session');
|
|
317
|
+
const session = readJson(join(sessionPath, 'session.json'));
|
|
318
|
+
if (!session.id || !session.contextId)
|
|
319
|
+
throw new Error('Completion evidence Session 缺少 id/contextId');
|
|
320
|
+
if (session.id !== String(session.id) || !/^ses_[a-f0-9]{20}$/.test(session.id)) {
|
|
321
|
+
throw new Error(`Completion evidence Session ID 非法: ${session.id || '-'}`);
|
|
322
|
+
}
|
|
323
|
+
return { sessionPath, session };
|
|
324
|
+
}
|
|
325
|
+
function resolveIssuer(context, options) {
|
|
326
|
+
if (options.runPath) {
|
|
327
|
+
const runPath = realpathSync(resolve(options.runPath));
|
|
328
|
+
assertInside(join(context.sessionPath, 'runs'), runPath, 'Completion evidence Run');
|
|
329
|
+
const run = readJson(join(runPath, 'run.json'));
|
|
330
|
+
if (run.sessionId !== context.session.id || run.contextId !== context.session.contextId ||
|
|
331
|
+
!RUN_ID_PATTERN.test(String(run.id || ''))) {
|
|
332
|
+
throw new Error('Completion evidence Run 与 Session 身份不一致');
|
|
333
|
+
}
|
|
334
|
+
return { type: 'run', id: run.id };
|
|
335
|
+
}
|
|
336
|
+
return { type: 'host', id: requireText(options.hostIdentity, 'Completion evidence host identity') };
|
|
337
|
+
}
|
|
338
|
+
function completionEvidenceRegistryPath(root, cfg, session) {
|
|
339
|
+
const indexRoot = resolve(root, cfg.runtime.eventIndexRoot || '.dingtalk-agent/event-index');
|
|
340
|
+
return join(indexRoot, 'completion-evidence', digest(session.id), 'registry.json');
|
|
341
|
+
}
|
|
342
|
+
function readRegistry(path, context, create) {
|
|
343
|
+
const empty = {
|
|
344
|
+
$schema: 'dingtalk-agent/task-completion-evidence-registry@1',
|
|
345
|
+
taskId: context.session.id,
|
|
346
|
+
scopeId: context.session.contextId,
|
|
347
|
+
sessionId: context.session.id,
|
|
348
|
+
revision: 0,
|
|
349
|
+
evidence: [],
|
|
350
|
+
updatedAt: '',
|
|
351
|
+
};
|
|
352
|
+
if (!existsSync(path)) {
|
|
353
|
+
if (create)
|
|
354
|
+
return empty;
|
|
355
|
+
throw new Error('当前 Session 没有宿主 completion evidence registry');
|
|
356
|
+
}
|
|
357
|
+
const input = readJson(path);
|
|
358
|
+
if (input.$schema !== empty.$schema || input.taskId !== empty.taskId ||
|
|
359
|
+
input.scopeId !== empty.scopeId || input.sessionId !== empty.sessionId ||
|
|
360
|
+
!Number.isInteger(input.revision) || input.revision < 1 ||
|
|
361
|
+
!Array.isArray(input.evidence) || typeof input.updatedAt !== 'string') {
|
|
362
|
+
throw new Error('Completion evidence registry 身份、revision 或结构非法');
|
|
363
|
+
}
|
|
364
|
+
const ids = new Set();
|
|
365
|
+
for (const value of input.evidence) {
|
|
366
|
+
validateCompletionEvidence(value);
|
|
367
|
+
if (value.taskId !== empty.taskId || value.scopeId !== empty.scopeId ||
|
|
368
|
+
value.sessionId !== empty.sessionId) {
|
|
369
|
+
throw new Error(`Completion evidence ${value.evidenceId} 与 registry 作用域不一致`);
|
|
370
|
+
}
|
|
371
|
+
if (ids.has(value.evidenceId))
|
|
372
|
+
throw new Error(`Completion evidenceId 重复: ${value.evidenceId}`);
|
|
373
|
+
ids.add(value.evidenceId);
|
|
374
|
+
}
|
|
375
|
+
return clone(input);
|
|
376
|
+
}
|
|
377
|
+
function validateCompletionEvidence(value) {
|
|
378
|
+
if (!isRecord(value))
|
|
379
|
+
throw new Error('Completion evidence 必须是 JSON object');
|
|
380
|
+
const allowed = new Set([
|
|
381
|
+
'$schema', 'evidenceId', 'kind', 'taskId', 'scopeId', 'sessionId',
|
|
382
|
+
'issuer', 'condition', 'reference', 'observed', 'issuedAt',
|
|
383
|
+
]);
|
|
384
|
+
assertNoExtra(value, allowed, 'Completion evidence');
|
|
385
|
+
if (value.$schema !== 'dingtalk-agent/task-completion-evidence@1' ||
|
|
386
|
+
!isCompletionEvidenceId(String(value.evidenceId || '')) ||
|
|
387
|
+
!['artifact', 'action-receipt', 'confirmation'].includes(String(value.kind || '')) ||
|
|
388
|
+
!String(value.taskId || '').trim() || !String(value.scopeId || '').trim() ||
|
|
389
|
+
!String(value.sessionId || '').trim()) {
|
|
390
|
+
throw new Error('Completion evidence schema、ID、kind 或作用域非法');
|
|
391
|
+
}
|
|
392
|
+
if (!isRecord(value.issuer))
|
|
393
|
+
throw new Error('Completion evidence issuer 非法');
|
|
394
|
+
assertNoExtra(value.issuer, new Set(['type', 'id']), 'Completion evidence issuer');
|
|
395
|
+
if (!['run', 'host'].includes(String(value.issuer.type || '')) ||
|
|
396
|
+
!String(value.issuer.id || '').trim())
|
|
397
|
+
throw new Error('Completion evidence issuer 非法');
|
|
398
|
+
if (!isRecord(value.condition))
|
|
399
|
+
throw new Error('Completion evidence condition 非法');
|
|
400
|
+
assertNoExtra(value.condition, new Set(['ref', 'hash']), 'Completion evidence condition');
|
|
401
|
+
if (value.condition.ref !== 'doneWhen' || !HASH_PATTERN.test(String(value.condition.hash || ''))) {
|
|
402
|
+
throw new Error('Completion evidence condition hash 非法');
|
|
403
|
+
}
|
|
404
|
+
if (!isRecord(value.observed))
|
|
405
|
+
throw new Error('Completion evidence observed 非法');
|
|
406
|
+
assertNoExtra(value.observed, new Set(['status', 'hash']), 'Completion evidence observed');
|
|
407
|
+
if (!['verified', 'confirmed'].includes(String(value.observed.status || '')) ||
|
|
408
|
+
!HASH_PATTERN.test(String(value.observed.hash || ''))) {
|
|
409
|
+
throw new Error('Completion evidence observed status/hash 非法');
|
|
410
|
+
}
|
|
411
|
+
parseTimestamp(value.issuedAt, 'Completion evidence issuedAt');
|
|
412
|
+
validateReference(value.kind, value.reference);
|
|
413
|
+
}
|
|
414
|
+
function validateReference(kind, reference) {
|
|
415
|
+
if (!isRecord(reference))
|
|
416
|
+
throw new Error('Completion evidence reference 非法');
|
|
417
|
+
if (kind === 'artifact') {
|
|
418
|
+
assertNoExtra(reference, new Set(['type', 'locator', 'sha256', 'bytes']), 'Artifact reference');
|
|
419
|
+
if (reference.type !== 'artifact' || !String(reference.locator || '').trim() ||
|
|
420
|
+
!HASH_PATTERN.test(String(reference.sha256 || '')) ||
|
|
421
|
+
!Number.isInteger(reference.bytes) || Number(reference.bytes) < 0) {
|
|
422
|
+
throw new Error('Artifact reference 非法');
|
|
423
|
+
}
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (kind === 'action-receipt') {
|
|
427
|
+
assertNoExtra(reference, new Set([
|
|
428
|
+
'type', 'runId', 'actionId', 'action', 'receiptHash', 'completedAt', 'validUntil',
|
|
429
|
+
]), 'Action Receipt reference');
|
|
430
|
+
if (reference.type !== 'action-receipt' || !RUN_ID_PATTERN.test(String(reference.runId || '')) ||
|
|
431
|
+
!ACTION_ID_PATTERN.test(String(reference.actionId || '')) || reference.action !== 'reply' ||
|
|
432
|
+
!HASH_PATTERN.test(String(reference.receiptHash || ''))) {
|
|
433
|
+
throw new Error('Action Receipt reference 非法');
|
|
434
|
+
}
|
|
435
|
+
parseTimestamp(reference.completedAt, 'Action Receipt reference completedAt');
|
|
436
|
+
parseTimestamp(reference.validUntil, 'Action Receipt reference validUntil');
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
assertNoExtra(reference, new Set([
|
|
440
|
+
'type', 'source', 'confirmationId', 'eventId', 'hostIdentity', 'recordHash',
|
|
441
|
+
]), 'Confirmation reference');
|
|
442
|
+
if (reference.type !== 'confirmation' ||
|
|
443
|
+
!['trusted-event', 'host-record'].includes(String(reference.source || '')) ||
|
|
444
|
+
!String(reference.confirmationId || '').trim() ||
|
|
445
|
+
!HASH_PATTERN.test(String(reference.recordHash || ''))) {
|
|
446
|
+
throw new Error('Confirmation reference 非法');
|
|
447
|
+
}
|
|
448
|
+
if (reference.source === 'trusted-event' && !String(reference.eventId || '').trim()) {
|
|
449
|
+
throw new Error('Trusted-event confirmation 缺少 eventId');
|
|
450
|
+
}
|
|
451
|
+
if (reference.source === 'host-record' && !String(reference.hostIdentity || '').trim()) {
|
|
452
|
+
throw new Error('Host confirmation 缺少 hostIdentity');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function findRunIndex(root, cfg, runId) {
|
|
456
|
+
const indexRoot = resolve(root, cfg.runtime.eventIndexRoot || '.dingtalk-agent/event-index');
|
|
457
|
+
if (!existsSync(indexRoot))
|
|
458
|
+
throw new Error(`找不到签发 Run: ${runId}`);
|
|
459
|
+
const matches = readdirSync(indexRoot, { withFileTypes: true })
|
|
460
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
|
|
461
|
+
.map((entry) => readJson(join(indexRoot, entry.name)))
|
|
462
|
+
.filter((index) => index.$schema === 'dingtalk-agent/event-index@1' && index.runId === runId);
|
|
463
|
+
if (matches.length !== 1)
|
|
464
|
+
throw new Error(`签发 Run ${runId} 的 event index 数量非法: ${matches.length}`);
|
|
465
|
+
validateEventIndex(matches[0], `签发 Run ${runId}`);
|
|
466
|
+
return matches[0];
|
|
467
|
+
}
|
|
468
|
+
function readEventIndex(root, cfg, eventId) {
|
|
469
|
+
const indexRoot = resolve(root, cfg.runtime.eventIndexRoot || '.dingtalk-agent/event-index');
|
|
470
|
+
const path = join(indexRoot, `${digest(eventId)}.json`);
|
|
471
|
+
if (!existsSync(path))
|
|
472
|
+
throw new Error(`找不到可信 Confirmation event: ${eventId}`);
|
|
473
|
+
const index = readJson(path);
|
|
474
|
+
validateEventIndex(index, `Confirmation event ${eventId}`);
|
|
475
|
+
if (index.eventId !== eventId)
|
|
476
|
+
throw new Error(`Confirmation event ${eventId} 身份不一致`);
|
|
477
|
+
return index;
|
|
478
|
+
}
|
|
479
|
+
function validateEventIndex(index, label) {
|
|
480
|
+
if (index.$schema !== 'dingtalk-agent/event-index@1' ||
|
|
481
|
+
index.eventHash !== index.authority?.eventHash ||
|
|
482
|
+
index.runId !== index.control?.id ||
|
|
483
|
+
digest(stableStringify(index.authority)) !== index.authorityHash) {
|
|
484
|
+
throw new Error(`${label} 权威 event index 损坏`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function assertIndexScope(index, context, label) {
|
|
488
|
+
if (index.sessionId !== context.session.id || index.contextId !== context.session.contextId ||
|
|
489
|
+
realpathSync(resolve(index.sessionPath)) !== context.sessionPath) {
|
|
490
|
+
throw new Error(`${label} 与当前 Session/Task 作用域不一致`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function assertInside(root, path, label) {
|
|
494
|
+
const rootPath = existsSync(root) ? realpathSync(resolve(root)) : resolve(root);
|
|
495
|
+
const rel = relative(rootPath, resolve(path));
|
|
496
|
+
if (rel === '..' || rel.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`)) {
|
|
497
|
+
throw new Error(`${label} 必须位于可信根目录内`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function assertNoExtra(value, allowed, label) {
|
|
501
|
+
const extra = Object.keys(value).filter((key) => !allowed.has(key));
|
|
502
|
+
if (extra.length)
|
|
503
|
+
throw new Error(`${label} 含未知字段: ${extra.join(', ')}`);
|
|
504
|
+
}
|
|
505
|
+
function parseNow(value) {
|
|
506
|
+
return value === undefined ? new Date() : parseTimestamp(value, 'Completion evidence now');
|
|
507
|
+
}
|
|
508
|
+
function parseTimestamp(value, label) {
|
|
509
|
+
const text = String(value || '').trim();
|
|
510
|
+
const time = Date.parse(text);
|
|
511
|
+
if (!text || !Number.isFinite(time))
|
|
512
|
+
throw new Error(`${label} 必须是合法时间`);
|
|
513
|
+
return new Date(time);
|
|
514
|
+
}
|
|
515
|
+
function requireText(value, label) {
|
|
516
|
+
const text = String(value || '').trim();
|
|
517
|
+
if (!text)
|
|
518
|
+
throw new Error(`${label} 不能为空`);
|
|
519
|
+
return text;
|
|
520
|
+
}
|
|
521
|
+
function isRecord(value) {
|
|
522
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
523
|
+
}
|
|
524
|
+
function readJson(path) {
|
|
525
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
526
|
+
}
|
|
527
|
+
function writeJsonAtomic(path, value) {
|
|
528
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
529
|
+
const temp = `${path}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
530
|
+
writeFileSync(temp, JSON.stringify(value, null, 2) + '\n', { flag: 'wx' });
|
|
531
|
+
renameSync(temp, path);
|
|
532
|
+
}
|
|
533
|
+
function clone(value) {
|
|
534
|
+
return JSON.parse(JSON.stringify(value));
|
|
535
|
+
}
|
|
536
|
+
//# sourceMappingURL=completion-evidence.js.map
|