@xdxer/dingtalk-agent 0.1.5-beta.8 → 0.1.5-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 +24 -0
- package/README.en.md +1 -0
- package/README.md +1 -0
- package/dist/bin/dingtalk-agent.js +115 -14
- package/dist/bin/dingtalk-agent.js.map +1 -1
- package/dist/src/agent-audit.js +108 -9
- package/dist/src/agent-audit.js.map +1 -1
- package/dist/src/development-workspace.js +37 -1
- package/dist/src/development-workspace.js.map +1 -1
- package/dist/src/dws.js +9 -0
- package/dist/src/dws.js.map +1 -1
- package/dist/src/multica-deploy.js +347 -105
- package/dist/src/multica-deploy.js.map +1 -1
- package/dist/src/multica-provider.js +151 -27
- 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/promotion.js +2 -1
- package/dist/src/promotion.js.map +1 -1
- package/dist/src/schedule-plan.js +192 -24
- package/dist/src/schedule-plan.js.map +1 -1
- package/docs/AGENT-IN-PRODUCTION.md +255 -0
- package/docs/ARCHITECTURE.md +5 -1
- package/docs/PLATFORM-GUARDRAILS.md +188 -0
- package/docs/schemas/multica-deployment-plan.schema.json +3 -1
- package/docs/schemas/multica-deployment-receipt.schema.json +15 -1
- 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-status.schema.json +2 -0
- package/docs/schemas/workspace-scaffold.schema.json +1 -0
- package/lab/project-workspace/fake-multica-provider.mjs +97 -7
- package/lab/project-workspace/multica-deploy.fixture.json +2 -2
- package/lab/project-workspace/multica-readonly.fixture.json +2 -2
- package/lab/project-workspace/project.fixture.json +1 -1
- package/lab/robot-eval/suite.json +1 -1
- package/package.json +5 -2
- package/skills/core/dta-agent-compose/SKILL.md +1 -1
- package/skills/core/dta-agent-compose/references/drive-and-schedules.md +22 -2
- package/skills/core/dta-basic-behavior/SKILL.md +1 -1
- package/skills/core/dta-basic-behavior/references/memory-and-evolution.md +1 -1
- package/skills/core/dta-people-group-memory/SKILL.md +4 -4
- package/skills/core/dta-people-group-memory/references/model.md +1 -1
- package/skills/platforms/multica-dingtalk/PLATFORM.md +2 -2
- package/skills/platforms/multica-dingtalk/dta-deploy-multica/SKILL.md +6 -5
- package/skills/platforms/multica-dingtalk/dta-deploy-multica/references/multica-deployment-contract.md +14 -4
- package/skills/platforms/multica-dingtalk/dta-ops-multica/SKILL.md +33 -7
- package/skills/platforms/multica-dingtalk/dta-ops-multica/scripts/multica_ext.py +118 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@xdxer/dingtalk-agent` are documented here. The project follows semantic versioning; prerelease entries describe release candidates and do not imply that Live canaries or registry publication have completed.
|
|
4
4
|
|
|
5
|
+
## 0.1.5-beta.9 - 2026-07-25
|
|
6
|
+
|
|
7
|
+
Fixes for the blockers recorded across three real acceptance sessions (2026-07-23/24, the `~/xdx/blockers` corpus). The common shape: fail-closed held the line, but the judge read the wrong evidence, the error named the wrong cause, or the locked state had no clean way out. Ships on the `beta` dist-tag; `latest` stays at 0.1.4. Merged as PRs #21, #27, #29, #30, #31, #32.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **The load-smoke judge could structurally never reach `ready` on a cloud runtime.** It hardcoded the tool names `skill`/`write`/`bash` and required *every* tool_use to match a whitelist, while Multica cloud runtimes surface skill loads as `read_file` ("Loading skill '<name>'") and commands as `terminal` — a smoke that actually succeeded on the platform (all skills loaded, exact JSON posted) was judged `failed` with `loadedSkills: []`. Trace reading now goes through per-runtime-provider vocabularies (`src/multica-runtime-vocabulary.ts`), skill loads are proven by positive evidence (the host-generated `**Skill loaded**` tool_result, or the vocabulary's skill tool_use — model text never counts), and the response is taken from the Issue's own comments first, which no tool rename can break. The `.every(whitelist)` negative gate is gone: one extra harmless tool no longer kills a healthy trace (that had already caused two production false negatives); only dws — as a tool name or shelled out in a command — stays fatal, with a named `smoke.forbidden-tool:dws` failure. Unknown tool names are recorded in `smoke.unrecognizedTools` instead of judged.
|
|
12
|
+
- **Cold-sandbox deploys systematically failed their first smoke.** Pushed skills take minutes to materialize into a runtime sandbox, and deploy fired the smoke immediately, reporting `Skill not found` with no way to tell "not ready yet" from "really broken" — the exact wrong signal that sent one acceptance session off rewriting the agent and swapping templates. A terminal smoke whose trace shows skill-not-found wording is now classified `skills-not-visible`: the receipt converges to `verifying` (not `failed`), apply retries with a bounded, budgeted mention inside the smoke window, and `deploy --status --execute --yes` performs one controlled retry — honestly reported as `remoteWrite: true` / `sideEffect: multica-read-write`.
|
|
13
|
+
- **Switching runtimes deadlocked deploy and retire together.** `scope.agent-binding-mismatch` blocked apply *and* the retire that would have resolved it; the only way out was editing the env back to the old runtime id by hand. Runtime drift is now an inspection diff (`agent.runtime-drift`), not a hard failure: plain apply is still refused (no silent agent migration), `--retire` proceeds (archiving is convergence, not a write to protect), and the new `deploy --rebind-runtime` migrates explicitly — plan-gated, with `previousRuntimeId` baked into the planId and `rebind: {from,to}` on the receipt.
|
|
14
|
+
- **`MULTICA_AGENT_ID` in the environment made every dta call report "Not authenticated. Run 'multica login'".** The multica CLI treats that variable as an agent-identity switch; dta passed the whole process env through, so a manifest envRef with that natural-looking name deadlocked second deploys behind a message pointing at login (one session lost 20+ minutes to it). dta now strips the reserved variable from every provider subprocess (values still resolve from the raw env first), records the removal in `envSanitized`, and `workspace doctor` warns when a manifest ref collides with a reserved name. The fixture example is renamed off the reserved name.
|
|
15
|
+
- **Deploying onto a hermes runtime "succeeded" and then every skill load failed with zero warning.** dta never checked the runtime's provider kind, and the server default for new FC runtimes is hermes — where workspace skills are never visible. Inspection now fails closed (`scope.runtime-provider-unsupported`, expected/actual in the detail) before any remote write when the target runtime's provider has no registered trace vocabulary.
|
|
16
|
+
- **Failures named the enum and hid the facts.** `scope.profile-workspace-mismatch` didn't say which workspace the profile pointed at; provider CLI stderr was hashed and discarded, so "calls all passed yet not authenticated" was undiagnosable without reading source. Scope/auth/binding failures now carry structured `failureDetails` (`code`/`expected`/`actual`/`hint` — IDs and enums only, the redaction contract is untouched), renderers print them, and a failing provider call's stderr is streamed to dta's own stderr in the moment while evidence files keep storing only hashes. Plan-invalidation errors explain the actual rule: don't run `inspect` between dry-run and apply.
|
|
17
|
+
- **`deploy --status` without `--execute` looked like the smoke fields were all null.** It returns the receipt frozen at apply time — for a `verifying` deploy that's the pending snapshot. The report now says so (`receiptFrozen`, `smokeStale`) and hands over the exact refresh command in `nextAction`; the human renderer prints the smoke block and the hint instead of leaving the fields to be misread as a broken readback.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Contract coverage for all of the above in #53/#54: cloud-vocabulary smoke with noise commands, hermes fail-closed, reserved-env sanitization on both the inspect and deploy write paths (the fake multica replicates the Go CLI's identity-switch semantics), skills-not-visible retry/convergence/recovery, frozen-status fields, rebind end-to-end (including stale-plan smuggling refusal) and retire-under-drift. The fake provider gained deterministic increasing run timestamps, an issue-comment store with `issue comment list`, cloud and cloud-spoof trace vocabulary modes, a bounded skill-not-found mode, and an async-run-creation mode (`DTA_FAKE_MULTICA_RUN_DELAY_READS`).
|
|
22
|
+
- **ops skill (`dta-ops-multica` 0.3.0, BREAKING)**: `runtime-create-fc` now injects `--provider opencode` by default — the server default is hermes, where workspace skills structurally never load; template auto-pick filters `waiting` and, with several ready candidates, fails closed listing them by `created_at`. `agent-check` **removes the single `healthy` boolean** (it reported green while users could not reach the agent at all) in favor of `infrastructureHealthy` + `userReady` with `reasons[]`, a single `nextAction`, and a `userReadyCaveat` naming the upstream blind spot (installations don't read back effective `allow_unbound`). `dingtalk-account-begin`/`-unbind` carry the server-enforced `binding_mode` (body vs query — the asymmetric wire contract is pinned in comments); `dingtalk-bind` without `--allow-unbound` now says on stderr that unbound senders (the owner included) will only get a binding prompt. PLATFORM.md's false claim that `autopilot-create` stdout carries a non-JSON prefix is corrected (it is pure JSON; the real hazard — non-idempotent create — stays). `multica_ext.py` gains its first automated regression: a fake HTTP server that records every request (#70).
|
|
23
|
+
- **scaffold / schedule**: `workspace create` now atomically creates the declared artifacts directory (with `.gitkeep`), so a fresh project's first `workspace doctor` passes instead of failing on the scaffold's own omission; output states explicitly that only the local manifest was written — no remote Multica resources. Schedule planning shares deploy's exact agent-binding fallback (`multicaAgentIdSource`): a declared `agentIdFrom` env ref wins, otherwise the agent ID saved to workspace state by the first deploy is used verbatim — the golden path no longer demands hand-wiring (#71 pins deploy/schedule consistency). First agent creation prints how the ID will be reused; `agent.unbound-name-candidates` renders the candidate IDs it always had in JSON.
|
|
24
|
+
- **gates**: `agent audit --verify-load` failures now carry a `failureSummary` (probe exitCode/durationMs/answer-match per failing surface) surfaced into the check summary and human output, and probe-execution-class failures get one fresh-session re-confirmation — recorded in `verification.attempts` with `passedOnAttempt`, so a flaky pass is visible instead of unexplainable (`--strict-single-run` opts out; static failures never retry). `check-skill-versions` closes the `git mv` blind spot: rename detection re-attaches the new directory to its old baseline (pure renames require a bump too — the directory name is the flat-namespace interface), with a frontmatter-name fallback for low-similarity rewrites (#72).
|
|
25
|
+
- **docs**: `docs/UPSTREAM-REQUIREMENTS.md` — the ledger of the 14 gaps this repo cannot fix (MUL-001..009 / DWS-001..005) with per-item evidence grades, workarounds and fixed-upstream criteria; the compose skill's drive-and-schedules reference gains the capability-vs-delivery-policy boundary (delivery targets and recipients live in the schedule wake prompt, never in `SKILL.md`).
|
|
26
|
+
|
|
27
|
+
An adversarial review pass on the first cut of this change confirmed and fixed six defects before release: load *attempts* counted as load *evidence* (a failed cold-sandbox load emits the same "Loading skill" tool_use as a successful one — attempts now only feed race classification, and the `**Skill loaded**` banner is only trusted from skill-capable tools' results, so a `terminal` echo cannot forge it); the mention retry loop judged the stale terminal run and could fire duplicate mentions (a new run now counts as arrived only when the latest taskId changes, and the loop stops rather than re-mentions when none appears); a transient failure of the retry *or* first-enqueue-recovery comment escalated into a full rollback of an already-verified deployment (both now just end the attempt and leave the receipt at `verifying`); the dws command scan missed `/usr/bin/dws`, quoted and backticked forms (now tokenized with basename matching, and the tool-name denylist is case-insensitive and segment-based so `mcp__dws__send` counts); and the reserved-env strip list gains `MULTICA_SERVER_URL` and `MULTICA_TOKEN` — a stray pre-prod URL or token in the shell must never retarget a named-profile deploy.
|
|
28
|
+
|
|
5
29
|
## 0.1.5-beta.8 - 2026-07-23
|
|
6
30
|
|
|
7
31
|
Follow-up to the beta.7 rename, from watching a real machine take the upgrade: the four core skills migrated cleanly, the two platform skills sat untouched and unannounced. The three defects below are the same shape — "clean up" was not carried through to the end. Ships on the `beta` dist-tag; `latest` stays at 0.1.4.
|
package/README.en.md
CHANGED
|
@@ -180,6 +180,7 @@ Prepared Runs expose four message atoms: `ack`, `reply`, `ask`, and `silence`. A
|
|
|
180
180
|
|---|---|
|
|
181
181
|
| Understand the Agent architecture and how the repo is divided | [Architecture](docs/ARCHITECTURE.md) |
|
|
182
182
|
| Install, upgrade, or troubleshoot | [Installation](docs/INSTALLATION.md) |
|
|
183
|
+
| Take over an agent already running in production (runtime view) | [Agent in Production](docs/AGENT-IN-PRODUCTION.md) |
|
|
183
184
|
| Understand evaluation and evidence levels | [Self-test](docs/SELF-TEST.md) |
|
|
184
185
|
| Create or audit an Agent | [Compose Skill](skills/core/dta-agent-compose/SKILL.md) |
|
|
185
186
|
| Test or debug a delivered Agent | [Eval Skill](skills/core/dta-agent-eval/SKILL.md) |
|
package/README.md
CHANGED
|
@@ -179,6 +179,7 @@ Prepared Run 只开放 `ack`、`reply`、`ask`、`silence` 四个消息原子动
|
|
|
179
179
|
|---|---|
|
|
180
180
|
| 理解 Agent 架构与仓库分工 | [Architecture](docs/ARCHITECTURE.md) |
|
|
181
181
|
| 安装、升级与排障 | [Installation](docs/INSTALLATION.md) |
|
|
182
|
+
| 接手一个已经在跑的 Agent(运行视角) | [Agent in Production](docs/AGENT-IN-PRODUCTION.md) |
|
|
182
183
|
| 理解评测与证据分层 | [Self-test](docs/SELF-TEST.md) |
|
|
183
184
|
| 创建或审计 Agent | [Compose Skill](skills/core/dta-agent-compose/SKILL.md) |
|
|
184
185
|
| 测试或调试已交付的 Agent | [Eval Skill](skills/core/dta-agent-eval/SKILL.md) |
|
|
@@ -33,7 +33,7 @@ import { runStorageEvaluation } from '../src/storage-evals.js';
|
|
|
33
33
|
import { runPersonalEventEvaluation } from '../src/personal-event-evals.js';
|
|
34
34
|
import { CHECK_LABELS, doctor } from '../src/doctor.js';
|
|
35
35
|
import { doctorDevelopmentWorkspace, inspectAgentProject, showDevelopmentWorkspace, } from '../src/development-workspace.js';
|
|
36
|
-
import { buildScheduleShow, buildSchedulePlan } from '../src/schedule-plan.js';
|
|
36
|
+
import { buildScheduleShow, buildSchedulePlan, scheduleBlockers } from '../src/schedule-plan.js';
|
|
37
37
|
import { applyOpenCodeWorkspace, evalOpenCodeWorkspace, planOpenCodeWorkspace, runOpenCodeWorkspace, useOpenCodeWorkspace, } from '../src/opencode-provider.js';
|
|
38
38
|
import { inspectMulticaWorkspace, listMulticaWorkspaceResources, planMulticaWorkspace, scaffoldMulticaWorkspace, statusMulticaWorkspace, } from '../src/multica-provider.js';
|
|
39
39
|
import { applyMulticaDeployment, listMulticaDeployments, planMulticaDeployment, runMulticaWorkspaceIssue, statusMulticaDeployment, statusMulticaWorkspaceRun, } from '../src/multica-deploy.js';
|
|
@@ -342,7 +342,7 @@ async function main() {
|
|
|
342
342
|
status: { type: 'boolean' }, list: { type: 'boolean' },
|
|
343
343
|
execute: { type: 'boolean' }, 'operation-id': { type: 'string' },
|
|
344
344
|
timeout: { type: 'string' }, 'smoke-timeout': { type: 'string' },
|
|
345
|
-
retire: { type: 'boolean' },
|
|
345
|
+
retire: { type: 'boolean' }, 'rebind-runtime': { type: 'boolean' },
|
|
346
346
|
},
|
|
347
347
|
});
|
|
348
348
|
if (positionals.length)
|
|
@@ -361,16 +361,19 @@ async function main() {
|
|
|
361
361
|
let out;
|
|
362
362
|
if (o.wait && o['no-wait'])
|
|
363
363
|
fail('deploy --wait 与 --no-wait 互斥');
|
|
364
|
+
if (o['rebind-runtime'] && o.retire)
|
|
365
|
+
fail('deploy --rebind-runtime 与 --retire 互斥');
|
|
364
366
|
if (o.list) {
|
|
365
367
|
if (o.execute || o['operation-id'] || o.retire || o['plan-id'] || o['no-wait'] || o.wait ||
|
|
366
|
-
o.timeout || o['smoke-timeout']) {
|
|
368
|
+
o.timeout || o['smoke-timeout'] || o['rebind-runtime']) {
|
|
367
369
|
fail('deploy --list 只接受 --workspace 和 --json');
|
|
368
370
|
}
|
|
369
371
|
out = listMulticaDeployments(PROJECT_START, o.workspace, PACKAGE_JSON.version);
|
|
370
372
|
}
|
|
371
373
|
else if (o.status) {
|
|
372
|
-
if (o.retire || o['plan-id'] || o['no-wait'] || o.wait || o['dry-run'] || o['smoke-timeout']
|
|
373
|
-
|
|
374
|
+
if (o.retire || o['plan-id'] || o['no-wait'] || o.wait || o['dry-run'] || o['smoke-timeout'] ||
|
|
375
|
+
o['rebind-runtime']) {
|
|
376
|
+
fail('deploy --status 不接受 retire/plan-id/no-wait/wait/dry-run/smoke-timeout/rebind-runtime');
|
|
374
377
|
}
|
|
375
378
|
if (Boolean(o.execute) !== Boolean(o.yes)) {
|
|
376
379
|
fail('deploy --status 远端 reconcile 必须同时传 --execute --yes');
|
|
@@ -387,6 +390,7 @@ async function main() {
|
|
|
387
390
|
}
|
|
388
391
|
out = planMulticaDeployment(PROJECT_START, o.workspace, {
|
|
389
392
|
action: o.retire ? 'retire' : 'apply', cliVersion: PACKAGE_JSON.version,
|
|
393
|
+
rebindRuntime: o['rebind-runtime'],
|
|
390
394
|
});
|
|
391
395
|
}
|
|
392
396
|
else {
|
|
@@ -403,6 +407,7 @@ async function main() {
|
|
|
403
407
|
out = applyMulticaDeployment(PROJECT_START, o.workspace, {
|
|
404
408
|
yes: o.yes, planId: o['plan-id'], noWait: o['no-wait'],
|
|
405
409
|
timeoutMs, smokeTimeoutMs: effectiveSmokeTimeoutMs, cliVersion: PACKAGE_JSON.version,
|
|
410
|
+
rebindRuntime: o['rebind-runtime'],
|
|
406
411
|
}, o.retire ? 'retire' : 'apply');
|
|
407
412
|
}
|
|
408
413
|
if (o.json)
|
|
@@ -521,6 +526,10 @@ async function main() {
|
|
|
521
526
|
});
|
|
522
527
|
if (sub === 'show') {
|
|
523
528
|
const out = buildScheduleShow(PROJECT_START, PACKAGE_JSON.version);
|
|
529
|
+
// 有阻断问题 = 非 0 退出,json 和人类两条路都设,否则自动化用 --json 拿到 exit 0 照样部署 → fail-open。
|
|
530
|
+
// 判据来自 scheduleBlockers(含 unverified),不在这手写,避免和 plan 那边分叉。
|
|
531
|
+
if (scheduleBlockers(out).length)
|
|
532
|
+
process.exitCode = 2;
|
|
524
533
|
if (o.json) {
|
|
525
534
|
console.log(JSON.stringify(out, null, 2));
|
|
526
535
|
return;
|
|
@@ -529,20 +538,26 @@ async function main() {
|
|
|
529
538
|
console.log(muted('本 Project 未声明任何常驻节律(schedules)。'));
|
|
530
539
|
return;
|
|
531
540
|
}
|
|
532
|
-
console.log(`${strong('
|
|
541
|
+
console.log(`${strong('声明的常驻节律(本地 manifest 声明,非线上实态)')} · ${out.count} 条`);
|
|
533
542
|
for (const s of out.schedules) {
|
|
534
|
-
|
|
543
|
+
const metered = s.mode === 'work-item' ? '记账' : '不记账';
|
|
544
|
+
console.log(`\n ${noun(s.name)} ${s.cron} ${s.timezone} ${muted(`[${s.mode}·${metered}]`)}`);
|
|
535
545
|
if (s.wake)
|
|
536
546
|
console.log(muted(` 唤醒 ${s.wake}`));
|
|
537
547
|
if (s.completion)
|
|
538
548
|
console.log(muted(` ②判据 ${s.completion}`));
|
|
539
549
|
}
|
|
540
|
-
if (
|
|
541
|
-
console.log(`\n${mark('
|
|
542
|
-
process.exitCode = 2;
|
|
550
|
+
if (out.collidingPairs.length) {
|
|
551
|
+
console.log(`\n${mark('fail', `✗ 同刻并发: ${out.collidingPairs.join('、')} —— 外发拍同一时刻 fire 会重复送第三方,请错开`)}`);
|
|
543
552
|
}
|
|
544
|
-
|
|
545
|
-
console.log(
|
|
553
|
+
if (out.missingCompletion.length) {
|
|
554
|
+
console.log(`${mark('fail', `✗ 缺完成判据: ${out.missingCompletion.join('、')} —— 没有数据派生的 completion,只能靠"跑没跑"判完成,漏拍就永久丢一期`)}`);
|
|
555
|
+
}
|
|
556
|
+
if (out.unverifiedPairs.length) {
|
|
557
|
+
console.log(`${mark('fail', `✗ 证不出是否同刻: ${out.unverifiedPairs.join('、')} —— 跨时区或 cron 扩展语法本地读不懂,一律当撞车挡住(0 9 * * * @Asia/Shanghai 和 0 1 * * * @UTC 就是同一时刻);把两条写成同一个 timezone,或换掉扩展语法`)}`);
|
|
558
|
+
}
|
|
559
|
+
if (!out.collidingPairs.length && !out.missingCompletion.length && !out.unverifiedPairs.length) {
|
|
560
|
+
console.log(`\n${mark('pass', '✓ 各节律不同刻 fire、且都有数据派生的完成判据')}`);
|
|
546
561
|
}
|
|
547
562
|
return;
|
|
548
563
|
}
|
|
@@ -552,6 +567,10 @@ async function main() {
|
|
|
552
567
|
fail('schedule plan 需要 --workspace <name>');
|
|
553
568
|
const info = inspectAgentProject(PROJECT_START, PACKAGE_JSON.version);
|
|
554
569
|
const plan = buildSchedulePlan(info, workspaceName);
|
|
570
|
+
// fail closed(json 和人类两条路都设):plan 是 operator 真正照着部署的路径,
|
|
571
|
+
// 撞车不在这拦,两条会各建一个 autopilot → 重复外发。判据同 show,见 scheduleBlockers。
|
|
572
|
+
if (scheduleBlockers(plan).length)
|
|
573
|
+
process.exitCode = 2;
|
|
555
574
|
if (o.json) {
|
|
556
575
|
console.log(JSON.stringify(plan, null, 2));
|
|
557
576
|
return;
|
|
@@ -564,8 +583,18 @@ async function main() {
|
|
|
564
583
|
return;
|
|
565
584
|
}
|
|
566
585
|
console.log(` ${plan.count} 条声明 → provider ops:\n`);
|
|
567
|
-
for (const s of plan.schedules)
|
|
568
|
-
console.log(` ${muted(`# ${s.name}`)}\n ${noun(s.applyCommand)}\n`);
|
|
586
|
+
for (const s of plan.schedules) {
|
|
587
|
+
console.log(` ${muted(`# ${s.name} [${s.metered ? '记账' : '不记账·平台看不到成本'}]`)}\n ${noun(s.applyCommand)}\n`);
|
|
588
|
+
}
|
|
589
|
+
if (plan.collidingPairs.length) {
|
|
590
|
+
console.log(`${mark('fail', `✗ 同刻并发: ${plan.collidingPairs.join('、')} —— 别照此部署,两条会各建一个 autopilot → 重复外发;先错开 fire 时刻`)}`);
|
|
591
|
+
}
|
|
592
|
+
if (plan.missingCompletion.length) {
|
|
593
|
+
console.log(`${mark('fail', `✗ 缺完成判据: ${plan.missingCompletion.join('、')} —— 补上数据派生的 completion,别靠"跑没跑"判完成`)}`);
|
|
594
|
+
}
|
|
595
|
+
if (plan.unverifiedPairs.length) {
|
|
596
|
+
console.log(`${mark('fail', `✗ 证不出是否同刻: ${plan.unverifiedPairs.join('、')} —— 别照此部署。跨时区或扩展语法本地判不了,一律当撞车;把两条写成同一个 timezone 再 plan,判得出不撞就自动放行`)}`);
|
|
597
|
+
}
|
|
569
598
|
console.log(`执行: ${noun(plan.reconcile)}`);
|
|
570
599
|
return;
|
|
571
600
|
}
|
|
@@ -1159,6 +1188,7 @@ async function main() {
|
|
|
1159
1188
|
'state-dir': { type: 'string' }, profile: { type: 'string' },
|
|
1160
1189
|
'expected-user-id': { type: 'string' }, 'require-skill': { type: 'string' },
|
|
1161
1190
|
'verify-load': { type: 'boolean' }, yes: { type: 'boolean' },
|
|
1191
|
+
'strict-single-run': { type: 'boolean' },
|
|
1162
1192
|
model: { type: 'string' }, out: { type: 'string' },
|
|
1163
1193
|
'load-report': { type: 'string' }, 'remote-report': { type: 'string' },
|
|
1164
1194
|
json: { type: 'boolean' },
|
|
@@ -1175,6 +1205,7 @@ async function main() {
|
|
|
1175
1205
|
bindings: loaded ? { path: loaded.path, hash: loaded.hash } : undefined,
|
|
1176
1206
|
requiredSkills: o['require-skill']?.split(','),
|
|
1177
1207
|
verifyLoad: o['verify-load'], yes: o.yes, model: o.model, out: o.out,
|
|
1208
|
+
strictSingleRun: o['strict-single-run'],
|
|
1178
1209
|
loadReport: o['load-report'], remoteReport: o['remote-report'],
|
|
1179
1210
|
});
|
|
1180
1211
|
if (o.json)
|
|
@@ -1747,6 +1778,22 @@ function printAgentAudit(out) {
|
|
|
1747
1778
|
for (const check of out.checks) {
|
|
1748
1779
|
const word = check.level === 'pass' ? 'PASS' : check.level === 'warn' ? 'WARN' : 'FAIL';
|
|
1749
1780
|
console.log(` ${mark(levelOf(check.level), word)} ${check.id}: ${check.summary}`);
|
|
1781
|
+
const evidence = check.evidence;
|
|
1782
|
+
// 失败可归因:把 failureSummary 的前几条与证据路径直接摆到失败行下面,
|
|
1783
|
+
// 不再让人去 JSON 深处找「为什么」。flaky 通过(第二次 attempt 才过)也如实标注。
|
|
1784
|
+
if (check.level !== 'pass' && check.blocking && Array.isArray(evidence?.failureSummary)) {
|
|
1785
|
+
for (const line of evidence.failureSummary.slice(0, 3)) {
|
|
1786
|
+
console.log(` · ${line}`);
|
|
1787
|
+
}
|
|
1788
|
+
if (typeof evidence.report === 'string' && evidence.report) {
|
|
1789
|
+
console.log(` evidence: ${evidence.report}`);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
const verification = evidence?.verification;
|
|
1793
|
+
if (verification && Array.isArray(verification.attempts) && verification.attempts.length > 1) {
|
|
1794
|
+
console.log(` attempts: ${verification.attempts.length}` +
|
|
1795
|
+
`(passedOnAttempt=${verification.passedOnAttempt || '-'})`);
|
|
1796
|
+
}
|
|
1750
1797
|
}
|
|
1751
1798
|
if (out.repairs.length) {
|
|
1752
1799
|
console.log('\n修复建议:');
|
|
@@ -2015,10 +2062,39 @@ function printMulticaWorkspaceOperation(out) {
|
|
|
2015
2062
|
console.log(` blocking=${out.blocking.join(', ')}`);
|
|
2016
2063
|
if (out.failures?.length)
|
|
2017
2064
|
console.log(` failures=${out.failures.join(', ')}`);
|
|
2065
|
+
printFailureDetails(out.failureDetails, ' ');
|
|
2066
|
+
// 同名候选就在 JSON 里,人类输出却只给一个枚举串——把出路直接摆出来(B10/C3)。
|
|
2067
|
+
if (out.failures?.includes('agent.unbound-name-candidates') &&
|
|
2068
|
+
Array.isArray(out.resources?.agentCandidates) && out.resources.agentCandidates.length) {
|
|
2069
|
+
for (const candidate of out.resources.agentCandidates) {
|
|
2070
|
+
console.log(` candidate: ${candidate.id} ${candidate.name}${candidate.archived ? ' (archived)' : ''}`);
|
|
2071
|
+
}
|
|
2072
|
+
console.log(' fix: 核对后在 manifest 声明 provider.agentIdFrom 指向正确 ID' +
|
|
2073
|
+
'(或依赖首次 deploy 写入的 state.binding.agentId);不要按名字猜绑定');
|
|
2074
|
+
}
|
|
2018
2075
|
if (out.evidencePath)
|
|
2019
2076
|
console.log(` evidence=${out.evidencePath}`);
|
|
2020
2077
|
console.log(` remoteWrite=${out.remoteWrite} dingtalkSideEffect=${out.dingtalkSideEffect}`);
|
|
2021
2078
|
}
|
|
2079
|
+
/** 期望 vs 实际 + 下一步:把 fail-closed 从「正确但沉默」升级为「正确且能自救」。 */
|
|
2080
|
+
function printFailureDetails(details, indent) {
|
|
2081
|
+
if (!Array.isArray(details))
|
|
2082
|
+
return;
|
|
2083
|
+
for (const detail of details) {
|
|
2084
|
+
if (!detail || typeof detail !== 'object')
|
|
2085
|
+
continue;
|
|
2086
|
+
const item = detail;
|
|
2087
|
+
const format = (value) => Array.isArray(value) ? value.join(', ') : typeof value === 'string' ? value : '';
|
|
2088
|
+
const expected = format(item.expected);
|
|
2089
|
+
const actual = format(item.actual);
|
|
2090
|
+
const parts = [
|
|
2091
|
+
expected ? `expected=${expected}` : '', actual ? `actual=${actual}` : '',
|
|
2092
|
+
].filter(Boolean).join(' ');
|
|
2093
|
+
console.log(`${indent}- ${item.code}${parts ? `: ${parts}` : ''}`);
|
|
2094
|
+
if (typeof item.hint === 'string' && item.hint)
|
|
2095
|
+
console.log(`${indent} → ${item.hint}`);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2022
2098
|
function printMulticaDeployment(out) {
|
|
2023
2099
|
console.log(`${strong('Multica deploy')}: ${noun(out.workspace || '<unknown>')}`);
|
|
2024
2100
|
if (out.planId)
|
|
@@ -2033,12 +2109,37 @@ function printMulticaDeployment(out) {
|
|
|
2033
2109
|
console.log(` status: ${out.status}`);
|
|
2034
2110
|
if (typeof out.providerReady === 'boolean')
|
|
2035
2111
|
console.log(` provider ready: ${out.providerReady}`);
|
|
2112
|
+
// 首次创建 Agent 后把「二次部署怎么绑定」讲在当下(B10):正常路径 ID 已存 state
|
|
2113
|
+
// 自动复用;换机器/state 丢失才需要显式声明。
|
|
2114
|
+
if (out.action === 'apply' && out.agent?.action === 'create' && out.agent?.id) {
|
|
2115
|
+
console.log(` agent created: ${out.agent.id}`);
|
|
2116
|
+
console.log(` ID 已写入 .dingtalk-agent/state/workspaces/${out.workspace}.json,二次部署与 schedule 自动复用`);
|
|
2117
|
+
console.log(' 换机器或 state 丢失时:在 manifest 声明 provider.agentIdFrom=env:<VAR> 并 export 该 ID');
|
|
2118
|
+
}
|
|
2119
|
+
if (out.rebind?.fromRuntimeId) {
|
|
2120
|
+
console.log(` rebind: ${out.rebind.fromRuntimeId} -> ${out.rebind.toRuntimeId}`);
|
|
2121
|
+
}
|
|
2122
|
+
const smoke = out.smoke || out.receipt?.smoke;
|
|
2123
|
+
if (smoke && smoke.status && smoke.status !== 'not-run') {
|
|
2124
|
+
const classified = smoke.classification ? ` (${smoke.classification})` : '';
|
|
2125
|
+
console.log(` smoke: ${smoke.status}${classified} task=${smoke.taskId || '-'}${smoke.taskStatus ? ` (${smoke.taskStatus})` : ''}`);
|
|
2126
|
+
console.log(` loaded: ${smoke.loadedSkills?.length ?? 0}/${smoke.requiredSkills?.length ?? 0} response: ${smoke.responsePassed === true}`);
|
|
2127
|
+
if (Array.isArray(smoke.unrecognizedTools) && smoke.unrecognizedTools.length) {
|
|
2128
|
+
console.log(` unrecognized tools: ${smoke.unrecognizedTools.join(', ')}`);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2036
2131
|
if (Array.isArray(out.blocking) && out.blocking.length) {
|
|
2037
2132
|
console.log(` blocking: ${out.blocking.join(', ')}`);
|
|
2038
2133
|
}
|
|
2039
2134
|
if (Array.isArray(out.failures) && out.failures.length) {
|
|
2040
2135
|
console.log(` failures: ${out.failures.join(', ')}`);
|
|
2041
2136
|
}
|
|
2137
|
+
printFailureDetails(out.failureDetails || out.receipt?.failureDetails, ' ');
|
|
2138
|
+
if (Array.isArray(smoke?.failureDetails))
|
|
2139
|
+
printFailureDetails(smoke.failureDetails, ' ');
|
|
2140
|
+
if (out.receiptFrozen && out.nextAction) {
|
|
2141
|
+
console.log(muted(` smoke 字段为 apply 时刻的冻结值;刷新: ${out.nextAction}`));
|
|
2142
|
+
}
|
|
2042
2143
|
if (out.triggerWrite === false)
|
|
2043
2144
|
console.log(' triggers: unchanged');
|
|
2044
2145
|
}
|