@whyshu/argent-engine 0.0.0-stage → 0.6.0
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/MANIFEST.json +307 -0
- package/engine/VERSION +1 -0
- package/engine/agents/catalog.json +454 -0
- package/engine/configs/_aliases.proposed.json +651 -0
- package/engine/configs/_migration-plan.proposed.json +899 -0
- package/engine/configs/account.json +272 -0
- package/engine/configs/advanced.json +205 -0
- package/engine/configs/host.json +149 -0
- package/engine/configs/hr.json +27 -0
- package/engine/configs/notify.json +264 -0
- package/engine/core/cli/argent.mjs +277 -0
- package/engine/core/contracts/argent.contracts.json +1065 -0
- package/engine/core/contracts/verbs.schema.json +2539 -0
- package/engine/core/lib/argent-datadir.mjs +223 -0
- package/engine/core/lib/audit.mjs +187 -0
- package/engine/core/lib/claim.mjs +258 -0
- package/engine/core/lib/config-catalog.mjs +1172 -0
- package/engine/core/lib/doctor-customer.mjs +577 -0
- package/engine/core/lib/flow-list.mjs +138 -0
- package/engine/core/lib/flow-registry.mjs +48 -0
- package/engine/core/lib/init.mjs +247 -0
- package/engine/core/lib/ledger.mjs +124 -0
- package/engine/core/lib/license-activate.mjs +813 -0
- package/engine/core/lib/license-device.mjs +314 -0
- package/engine/core/lib/license-file.mjs +349 -0
- package/engine/core/lib/license-guard.mjs +123 -0
- package/engine/core/lib/license-ops.mjs +1645 -0
- package/engine/core/lib/license-verdict.mjs +133 -0
- package/engine/core/lib/license.mjs +577 -0
- package/engine/core/lib/notify/dedup.mjs +101 -0
- package/engine/core/lib/notify/feishu.mjs +38 -0
- package/engine/core/lib/notify/index.mjs +95 -0
- package/engine/core/lib/notify/verb.mjs +327 -0
- package/engine/core/lib/notify/webhook.mjs +135 -0
- package/engine/core/lib/notify/wecom.mjs +36 -0
- package/engine/core/lib/python.mjs +145 -0
- package/engine/core/lib/sop.mjs +116 -0
- package/engine/core/lib/versions.mjs +205 -0
- package/engine/core/mcp/client.mjs +67 -0
- package/engine/core/mcp/http.mjs +138 -0
- package/engine/core/mcp/server.mjs +702 -0
- package/engine/scripts/_engine_rules_probe.py +15 -0
- package/engine/scripts/analyze-full-day.py +568 -0
- package/engine/scripts/archive/feishu-bridge.js +246 -0
- package/engine/scripts/argent-agents.mjs +527 -0
- package/engine/scripts/argent-capabilities.mjs +776 -0
- package/engine/scripts/argent-catalog.mjs +186 -0
- package/engine/scripts/argent-datadir.py +62 -0
- package/engine/scripts/argent-doctor.mjs +624 -0
- package/engine/scripts/argent-env-write.mjs +302 -0
- package/engine/scripts/argent-schedule.mjs +620 -0
- package/engine/scripts/argent-setup-check.mjs +369 -0
- package/engine/scripts/argent-upgrade.mjs +1046 -0
- package/engine/scripts/base-bootstrap.sh +575 -0
- package/engine/scripts/billing-mode-daily.sh +12 -0
- package/engine/scripts/build-engine-package.mjs +123 -0
- package/engine/scripts/capability-packs.lock.json +484 -0
- package/engine/scripts/capability_pack.py +1242 -0
- package/engine/scripts/check-1.0-criteria.mjs +355 -0
- package/engine/scripts/check-agent-catalog.mjs +141 -0
- package/engine/scripts/check-billing-mode.sh +377 -0
- package/engine/scripts/check-child-sessions.mjs +83 -0
- package/engine/scripts/check-config-catalog.mjs +677 -0
- package/engine/scripts/check-docs-drift.mjs +522 -0
- package/engine/scripts/check-dsh-web-expose.sh +98 -0
- package/engine/scripts/check-judgment-drift.mjs +403 -0
- package/engine/scripts/check-license.mjs +199 -0
- package/engine/scripts/check-model-auth.py +443 -0
- package/engine/scripts/check-pack-closure.mjs +661 -0
- package/engine/scripts/check-portable-symbols.mjs +116 -0
- package/engine/scripts/check-preset-landing.mjs +124 -0
- package/engine/scripts/check-production-home.mjs +602 -0
- package/engine/scripts/check-role-mapping.mjs +461 -0
- package/engine/scripts/check-sample-redaction.mjs +320 -0
- package/engine/scripts/check-skill-classification.mjs +188 -0
- package/engine/scripts/check-sop-consistency.mjs +431 -0
- package/engine/scripts/check-test-env-safety.mjs +627 -0
- package/engine/scripts/check-version-consistency.mjs +452 -0
- package/engine/scripts/ci-run.sh +558 -0
- package/engine/scripts/config-explain.mjs +278 -0
- package/engine/scripts/configure-provider.rb +29 -0
- package/engine/scripts/customer-config-migrate.mjs +179 -0
- package/engine/scripts/customer-preflight.py +892 -0
- package/engine/scripts/customer-readiness.sh +134 -0
- package/engine/scripts/daily-report-inbox.py +242 -0
- package/engine/scripts/daily-report-reconcile.mjs +312 -0
- package/engine/scripts/daily-report.mjs +1310 -0
- package/engine/scripts/daily-report.sh +46 -0
- package/engine/scripts/data/lip-product-dict.json +306 -0
- package/engine/scripts/deliver-customer.mjs +932 -0
- package/engine/scripts/delivery-faces.txt +31 -0
- package/engine/scripts/desktop-trial.sh +146 -0
- package/engine/scripts/dev-window.sh +260 -0
- package/engine/scripts/drill-clean-machine.sh +315 -0
- package/engine/scripts/dsh-api-client.mjs +215 -0
- package/engine/scripts/dsh-backup-scope.sh +83 -0
- package/engine/scripts/dsh-offline-pack.mjs +77 -0
- package/engine/scripts/dsh-overlay-pin.sh +87 -0
- package/engine/scripts/dsh-plane.mjs +153 -0
- package/engine/scripts/dsh-portforward.py +223 -0
- package/engine/scripts/dsh-preflight.mjs +179 -0
- package/engine/scripts/dsh-release-watch-daily.sh +19 -0
- package/engine/scripts/dsh-release-watch.mjs +458 -0
- package/engine/scripts/dsh-remote-check.sh +99 -0
- package/engine/scripts/dsh-restart.sh +179 -0
- package/engine/scripts/dsh-restore.sh +111 -0
- package/engine/scripts/dsh-scope-rewire.mjs +803 -0
- package/engine/scripts/dsh-snapshot.mjs +116 -0
- package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
- package/engine/scripts/dsh-upgrade.mjs +612 -0
- package/engine/scripts/dsh-url.sh +86 -0
- package/engine/scripts/dsh-web-launch.mjs +155 -0
- package/engine/scripts/ensure-store-windows.py +509 -0
- package/engine/scripts/entitlements.py +458 -0
- package/engine/scripts/env_probe.py +411 -0
- package/engine/scripts/feishu-card.mjs +252 -0
- package/engine/scripts/fix-skill-frontmatter.py +209 -0
- package/engine/scripts/flow-auto-retry.py +454 -0
- package/engine/scripts/flow-requirements.json +720 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +86 -0
- package/engine/scripts/flow-timers.sh +182 -0
- package/engine/scripts/flow-trigger-wake.mjs +284 -0
- package/engine/scripts/flow-trigger.sh +571 -0
- package/engine/scripts/flow_billing.py +354 -0
- package/engine/scripts/flow_capability_stats.py +314 -0
- package/engine/scripts/flow_case.py +194 -0
- package/engine/scripts/flow_claim.py +497 -0
- package/engine/scripts/flow_config.py +1024 -0
- package/engine/scripts/flow_failure.py +588 -0
- package/engine/scripts/flow_learn.py +105 -0
- package/engine/scripts/flow_ledger.py +235 -0
- package/engine/scripts/flow_policy.py +238 -0
- package/engine/scripts/flow_preflight.py +339 -0
- package/engine/scripts/flow_registry.py +341 -0
- package/engine/scripts/flow_retry.py +304 -0
- package/engine/scripts/flow_return_to_ops.py +191 -0
- package/engine/scripts/flow_source.py +315 -0
- package/engine/scripts/flow_sources/__init__.py +53 -0
- package/engine/scripts/flow_sources/cases_json.py +106 -0
- package/engine/scripts/flow_sources/common.py +428 -0
- package/engine/scripts/flow_sources/feishu_base.py +273 -0
- package/engine/scripts/flow_stale.py +185 -0
- package/engine/scripts/flow_status_reconcile.py +344 -0
- package/engine/scripts/flow_triage.py +849 -0
- package/engine/scripts/flow_write_verify.py +496 -0
- package/engine/scripts/fulfillment.js +586 -0
- package/engine/scripts/gen-agent-bundles.mjs +276 -0
- package/engine/scripts/gen-preset-rows.mjs +259 -0
- package/engine/scripts/init-ml.sh +2275 -0
- package/engine/scripts/install-internal-role.sh +190 -0
- package/engine/scripts/install-license.mjs +641 -0
- package/engine/scripts/install-one-shot.mjs +665 -0
- package/engine/scripts/integration_contract.py +220 -0
- package/engine/scripts/integrations/wenyao/adapter.py +98 -0
- package/engine/scripts/integrations/xzh/adapter.py +75 -0
- package/engine/scripts/invoice-mapping.example.json +40 -0
- package/engine/scripts/invoice-table.sh +194 -0
- package/engine/scripts/invoice_carrier_notice.py +279 -0
- package/engine/scripts/invoice_fail_closeout.py +297 -0
- package/engine/scripts/invoice_lip_runner.py +1508 -0
- package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
- package/engine/scripts/invoice_ready_audit.py +191 -0
- package/engine/scripts/invoice_runner.py +675 -0
- package/engine/scripts/invoice_stageb.py +516 -0
- package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
- package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
- package/engine/scripts/lease.py +377 -0
- package/engine/scripts/lib/agent-install.mjs +649 -0
- package/engine/scripts/lib/argent-datadir.mjs +16 -0
- package/engine/scripts/lib/argent_datadir.py +262 -0
- package/engine/scripts/lib/argent_filelock.py +507 -0
- package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
- package/engine/scripts/lib/cdp_ports.py +197 -0
- package/engine/scripts/lib/customer-env.mjs +102 -0
- package/engine/scripts/lib/daily-report-core.mjs +517 -0
- package/engine/scripts/lib/dep-declaration.mjs +81 -0
- package/engine/scripts/lib/deps-migrate.mjs +310 -0
- package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
- package/engine/scripts/lib/dsh-scope.mjs +500 -0
- package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
- package/engine/scripts/lib/dsh-versions.mjs +96 -0
- package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
- package/engine/scripts/lib/is-direct-run.mjs +61 -0
- package/engine/scripts/lib/patch-compose.mjs +241 -0
- package/engine/scripts/lib/patch-target.mjs +35 -0
- package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
- package/engine/scripts/lib/plugin-packages.mjs +332 -0
- package/engine/scripts/lib/preset-compat.mjs +127 -0
- package/engine/scripts/lib/preset-install.mjs +575 -0
- package/engine/scripts/lib/preset-mechanism.mjs +56 -0
- package/engine/scripts/lib/preset-sync.mjs +59 -0
- package/engine/scripts/lib/profile-retire.mjs +97 -0
- package/engine/scripts/lib/python-runtime.mjs +190 -0
- package/engine/scripts/lib/registry.mjs +72 -0
- package/engine/scripts/lib/repo-root.sh +56 -0
- package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
- package/engine/scripts/lib/schedule-probe.mjs +835 -0
- package/engine/scripts/lib/scope-pointer.mjs +164 -0
- package/engine/scripts/lib/settings-panels.mjs +816 -0
- package/engine/scripts/lib/stdio_utf8.py +86 -0
- package/engine/scripts/lib/wake-auth.mjs +392 -0
- package/engine/scripts/lib/webdriver_ipc.py +264 -0
- package/engine/scripts/lib/zstd-frames.mjs +104 -0
- package/engine/scripts/license-e2e-drill.mjs +426 -0
- package/engine/scripts/license-gate.mjs +245 -0
- package/engine/scripts/lip_backlog.py +171 -0
- package/engine/scripts/lip_table.py +185 -0
- package/engine/scripts/local-usage.mjs +214 -0
- package/engine/scripts/migrate-providers.mjs +127 -0
- package/engine/scripts/migrate-skills.sh +65 -0
- package/engine/scripts/ml-oauth-setup.mjs +550 -0
- package/engine/scripts/ml-store-ids.json +26 -0
- package/engine/scripts/ml_artifact_attribution.py +333 -0
- package/engine/scripts/ml_capacity_probe.py +1158 -0
- package/engine/scripts/ml_hub_status.py +1556 -0
- package/engine/scripts/ml_schema_check.py +115 -0
- package/engine/scripts/ml_store_ids.py +139 -0
- package/engine/scripts/npm-publish-argent.mjs +499 -0
- package/engine/scripts/npm-publish-argent.sh +20 -0
- package/engine/scripts/orchestrator.py +13811 -0
- package/engine/scripts/pack-plugins.mjs +140 -0
- package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
- package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
- package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
- package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
- package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
- package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
- package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
- package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
- package/engine/scripts/patches/verify-hardening.mjs +167 -0
- package/engine/scripts/patches/verify-session-migration.mjs +149 -0
- package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
- package/engine/scripts/plan-role-rename.mjs +125 -0
- package/engine/scripts/plugin-bootcheck.mjs +451 -0
- package/engine/scripts/plugin-composition.json +15 -0
- package/engine/scripts/poc-mcp-host.mjs +455 -0
- package/engine/scripts/poll-fulfillment.sh +293 -0
- package/engine/scripts/poll-invoice.sh +74 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +70 -0
- package/engine/scripts/provider-switch.sh +146 -0
- package/engine/scripts/provider-sync-target.check.mjs +64 -0
- package/engine/scripts/python-runtime.sh +78 -0
- package/engine/scripts/query_runner.py +310 -0
- package/engine/scripts/release-argent.sh +574 -0
- package/engine/scripts/repair-unresolved-tools.mjs +178 -0
- package/engine/scripts/run-tests.sh +256 -0
- package/engine/scripts/self-sign-license.mjs +179 -0
- package/engine/scripts/session-health-scan.mjs +63 -0
- package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
- package/engine/scripts/setup.sh +22 -0
- package/engine/scripts/skill-install.mjs +629 -0
- package/engine/scripts/sku_translate.py +148 -0
- package/engine/scripts/sync-plugins.sh +90 -0
- package/engine/scripts/sync-presets.sh +27 -0
- package/engine/scripts/sync-scripts.sh +445 -0
- package/engine/scripts/telemetry.py +873 -0
- package/engine/scripts/ui-copy-count.mjs +220 -0
- package/engine/scripts/usage-anchor.sh +136 -0
- package/engine/scripts/usage_report.py +531 -0
- package/engine/scripts/validate-preset-schema.mjs +208 -0
- package/engine/scripts/vertical-init.mjs +323 -0
- package/engine/scripts/vertical-lint.mjs +482 -0
- package/engine/scripts/wenyao-browser.sh +73 -0
- package/engine/scripts/wenyao-fixture-export.py +116 -0
- package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
- package/engine/scripts/wenyao-login.sh +34 -0
- package/engine/scripts/wenyao-template-map.json +292 -0
- package/engine/scripts/wenyao_attach.py +131 -0
- package/engine/scripts/wenyao_download_template.py +144 -0
- package/engine/scripts/wenyao_fill_template.py +568 -0
- package/engine/scripts/wenyao_login.py +145 -0
- package/engine/scripts/wenyao_portal.py +633 -0
- package/engine/scripts/wenyao_recon.py +230 -0
- package/engine/scripts/wenyao_run.py +503 -0
- package/engine/scripts/wenyao_shadow_diff.py +160 -0
- package/engine/scripts/wenyao_skill_contract.py +156 -0
- package/engine/scripts/wenyao_template_fetch.py +175 -0
- package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
- package/engine/scripts/ziniao-auth-probe.sh +268 -0
- package/engine/scripts/ziniao-keepalive.sh +216 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
- package/engine/scripts/ziniao-windows.sh +153 -0
- package/engine/version.mjs +1 -0
- package/engine/verticals/README.md +103 -0
- package/engine/verticals/_engine-words.baseline.txt +237 -0
- package/engine/verticals/_engine-words.json +19 -0
- package/engine/verticals/mercadolibre/README.md +43 -0
- package/engine/verticals/mercadolibre/config.json +709 -0
- package/engine/verticals/mercadolibre/flows.json +706 -0
- package/engine/verticals/mercadolibre/manifest.json +538 -0
- package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
- package/engine/verticals/mercadolibre/preflight.py +143 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
- package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
- package/engine/verticals/mercadolibre/prompts/full.md +91 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
- package/package.json +21 -4
- package/README.md +0 -3
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-install —— **界面内的「安装 / 升级 / 重装智能体」的唯一实现**(0.6.0 W7,2026-10-13)。
|
|
3
|
+
*
|
|
4
|
+
* ## 它是什么 / 不是什么(本文件的口径就是 W7 的契约 §3.1.3 第 8 条)
|
|
5
|
+
* **是**:① 智能体货架的**只读状态投影**(装了没 / 版本落后没 / 需不需要升级);
|
|
6
|
+
* ② **许可证前置**的服务端判定(`GET /api/argent/entitlements`,拿不到 ⇒ 拒绝,⛔ 不放行);
|
|
7
|
+
* ③ 以**子进程调用安装器本体**(`install-argent.mjs --agent <id>`)——**安装链只有那一份**;
|
|
8
|
+
* ④ 装完的**只读收尾核对**(bundle 在依赖图内 / presets 是**直接依赖**且在 bundles 条目里 /
|
|
9
|
+
* 能力包按包内声明落位 / 角色与技能按声明落位)。
|
|
10
|
+
* **不是**:⛔ 不重写任何安装步骤(不自己拼 `dsh plugin add`、不自己写 package.json、
|
|
11
|
+
* ⛔ 不自己装能力包)——那就是"第二套链",正是本批明令禁止的。
|
|
12
|
+
*
|
|
13
|
+
* ## 为什么"复用"落成"调用安装器本体"而不是"把安装器的函数抽出来 import"
|
|
14
|
+
* `install-argent.mjs` 是**单文件分发物**(官网那行 `node install-argent.mjs` 只下一个文件)⇒
|
|
15
|
+
* 它里面的每一步都跑在模块级状态(`PLUGIN_REGISTRATION` / `AGENT_BUNDLES` / `PRESET_LANDING` /
|
|
16
|
+
* 借到的包管理器 shim / 落盘账本…)之上。把这条链"抽成可 import 的函数"要么改掉它的分发形态、
|
|
17
|
+
* 要么在插件里复制一遍它的状态机 —— 两者都比"**调用同一个可执行体**"更容易制造出第二份实现。
|
|
18
|
+
* 判据(可复核):本文件里**没有任何** `dsh plugin add` / `pnpm add` / 能力包落盘代码;
|
|
19
|
+
* 唯一的写动作是 `spawn(process.execPath, [install-argent.mjs, '--agent', id, …])`。
|
|
20
|
+
*
|
|
21
|
+
* ## 幂等
|
|
22
|
+
* 已装且版本一致 ⇒ **零写入**(不 spawn、不碰任何文件),直接回「已是最新」。
|
|
23
|
+
* 升级/重装 ⇒ 只动该智能体相关面(安装器 `--agent` 的语义)。
|
|
24
|
+
*
|
|
25
|
+
* ## 纪律
|
|
26
|
+
* 纯 node 内建 + 同树 L1 模块(`scripts/argent-agents.mjs` / `scripts/lib/preset-install.mjs`)
|
|
27
|
+
* ⇒ ⛔ 零宿主 import(kernel-purity 管的就是这条)。所有函数**无模块级副作用**(可安全 import)。
|
|
28
|
+
*
|
|
29
|
+
* 测试:`scripts/tests/agent-ui-install.test.mjs`
|
|
30
|
+
*/
|
|
31
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
32
|
+
import { spawn as nodeSpawn } from 'node:child_process'
|
|
33
|
+
import { homedir } from 'node:os'
|
|
34
|
+
import { dirname, join, resolve } from 'node:path'
|
|
35
|
+
import { fileURLToPath } from 'node:url'
|
|
36
|
+
import { loadCatalog } from '../argent-agents.mjs'
|
|
37
|
+
import { resolveBundleDir, SELECTION_FILE } from './preset-install.mjs'
|
|
38
|
+
import { npmNameOf } from './plugin-packages.mjs'
|
|
39
|
+
// 数据目录(`whyshu.env` 落点)的**唯一判据**在内核 —— ⛔ 本文件不另写一条链
|
|
40
|
+
// (argent-datadir 门禁:`ARGENT_DATA_DIR ||` / `join('.dsh','argent')` 命中即红)。
|
|
41
|
+
import { resolveDataDir } from './argent-datadir.mjs'
|
|
42
|
+
|
|
43
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
44
|
+
/** 本文件所在仓库根(`scripts/lib/` → 仓库根)。 */
|
|
45
|
+
export const REPO = resolve(HERE, '..', '..')
|
|
46
|
+
|
|
47
|
+
/** 智能体 bundle 的 npm 名前缀(唯一真源仍是包自己的 `package.json#name`,这里只用于**找目录**)。 */
|
|
48
|
+
export const AGENT_DIR_PREFIX = 'argent-agent-'
|
|
49
|
+
/** 角色承载 bundle 的 npm 名 —— W7 的硬判据:它必须是 profile 的**直接依赖**且在 bundles 条目里。 */
|
|
50
|
+
export const PRESETS_NPM = npmNameOf('argent-presets') || '@whyshu/argent-presets'
|
|
51
|
+
/** 服务端判定端点(与安装器 `install-ticket` 同一身份:账号令牌 Bearer)。 */
|
|
52
|
+
export const ENTITLEMENTS_PATH = '/api/argent/entitlements'
|
|
53
|
+
/** 服务端基址:`ARGENT_API_BASE` → `whyshu.env#WHYSHU_API_URL` → 官方站点。 */
|
|
54
|
+
export const DEFAULT_API_BASE = 'https://whyshu.com'
|
|
55
|
+
|
|
56
|
+
const readJsonSafe = (p, d = null) => { try { return JSON.parse(readFileSync(p, 'utf8')) } catch { return d } }
|
|
57
|
+
const isStr = (x) => typeof x === 'string' && x.trim() !== ''
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 仓库根候选(与宿主侧既有的目录脚本解析**同一顺位**:就近优先 → 显式覆盖 → 安装克隆 → 开发仓库;
|
|
61
|
+
* 见 `plugins/argent-core/src/index.js#configRepoRoot` 与 `src/agent-install.mjs#repoRootCandidates`)。
|
|
62
|
+
* 判据 = 该根下同时有 `agents/catalog.json` 与 `scripts/lib/agent-install.mjs`。
|
|
63
|
+
* @returns {string|null} 找不到 ⇒ null(调用方**响亮**报错,⛔ 不猜)
|
|
64
|
+
*/
|
|
65
|
+
export function resolveRepoRoot({ startDir = HERE, env = process.env, home = homedir(), exists = existsSync } = {}) {
|
|
66
|
+
const cands = [
|
|
67
|
+
startDir,
|
|
68
|
+
env.ARGENT_DSH_REPO || '',
|
|
69
|
+
env.ARGENT_DISH_HOME || '',
|
|
70
|
+
join(home, '.argent-dsh'),
|
|
71
|
+
join(home, 'Code', 'argent-dsh'),
|
|
72
|
+
].filter(Boolean)
|
|
73
|
+
const marker = (r) => exists(join(r, 'agents', 'catalog.json')) && exists(join(r, 'scripts', 'lib', 'agent-install.mjs'))
|
|
74
|
+
for (const r of cands) { try { if (marker(r)) return r } catch { /* 试下一个 */ } }
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 智能体 bundle 的目录(`<repo>/plugins/argent-agent-<id>`;没有 ⇒ null)。 */
|
|
79
|
+
export function agentBundleDir({ repo, id }) {
|
|
80
|
+
const d = join(repo, 'plugins', `${AGENT_DIR_PREFIX}${String(id)}`)
|
|
81
|
+
return existsSync(join(d, 'package.json')) ? d : null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 读**包内声明**(`package.json#argent.agent`)——与安装器读的是**同一个字段**(⛔ 不读第二份映射)。
|
|
86
|
+
* @returns {{id:string,dir:string,name:string,version:string,decl:object}|null}
|
|
87
|
+
*/
|
|
88
|
+
export function readAgentBundle({ repo, id }) {
|
|
89
|
+
const dir = agentBundleDir({ repo, id })
|
|
90
|
+
if (!dir) return null
|
|
91
|
+
const m = readJsonSafe(join(dir, 'package.json'), null)
|
|
92
|
+
const decl = m && m.argent && m.argent.agent
|
|
93
|
+
if (!m || !decl || typeof decl !== 'object') return null
|
|
94
|
+
if (String(decl.id || '') !== String(id)) return null
|
|
95
|
+
return { id: String(id), dir, name: String(m.name || ''), version: String(m.version || ''), decl }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 货架(`agents/catalog.json`)⋈ 包内声明 ⇒ "本机可装的智能体"。
|
|
100
|
+
* 目录读不到 / 包缺失 ⇒ 如实进 `errors`(⛔ 不静默少几条 —— 少一条就会让"缺件"看起来像"没这个商品")。
|
|
101
|
+
* @returns {{agents: object[], errors: string[]}}
|
|
102
|
+
*/
|
|
103
|
+
export function loadAgentShelf({ repo }) {
|
|
104
|
+
const errors = []
|
|
105
|
+
const doc = loadCatalog(repo)
|
|
106
|
+
if (!doc || !Array.isArray(doc.agents)) {
|
|
107
|
+
return { agents: [], errors: [`读不到智能体货架(${join(repo, 'agents/catalog.json')})`] }
|
|
108
|
+
}
|
|
109
|
+
const agents = []
|
|
110
|
+
for (const a of doc.agents) {
|
|
111
|
+
if (!a || !isStr(a.id)) continue
|
|
112
|
+
const bundle = readAgentBundle({ repo, id: a.id })
|
|
113
|
+
if (!bundle) { errors.push(`智能体 \`${a.id}\` 在本机克隆里没有对应的 agent bundle(plugins/${AGENT_DIR_PREFIX}${a.id})`); continue }
|
|
114
|
+
if (bundle.name !== `@whyshu/${AGENT_DIR_PREFIX}${a.id}`) {
|
|
115
|
+
errors.push(`智能体 \`${a.id}\` 的包名不是 \`@whyshu/${AGENT_DIR_PREFIX}${a.id}\`(实得 ${bundle.name || '(空)'})`)
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
const payload = (bundle.decl && bundle.decl.payload) || {}
|
|
119
|
+
agents.push({
|
|
120
|
+
id: String(a.id),
|
|
121
|
+
title: String(a.title || a.id),
|
|
122
|
+
summary: String(a.summary || ''),
|
|
123
|
+
minPlan: String(a.minPlan || ''),
|
|
124
|
+
capabilities: (Array.isArray(payload.capabilities) ? payload.capabilities : []).map(String),
|
|
125
|
+
roles: (Array.isArray(bundle.decl.roles) ? bundle.decl.roles : []).map(String),
|
|
126
|
+
skills: (Array.isArray(bundle.decl.skills) ? bundle.decl.skills : []).map(String),
|
|
127
|
+
scheduler: bundle.decl.scheduler === true,
|
|
128
|
+
name: bundle.name,
|
|
129
|
+
bundleVersion: bundle.version,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
return { agents, errors }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 装机账本(`<dshHome>/argent/agents.json`)里已装的智能体 id。 */
|
|
136
|
+
export function installedAgentIds({ dshHome }) {
|
|
137
|
+
const doc = readJsonSafe(join(dshHome, 'argent', 'agents.json'), null)
|
|
138
|
+
return (doc && Array.isArray(doc.agents) ? doc.agents : []).map((a) => a && a.id).filter(Boolean).map(String)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** profile 落点(默认 `web`)。 */
|
|
142
|
+
export function profileDirOf({ dshHome, profile = 'web' }) {
|
|
143
|
+
return join(dshHome, 'profiles', profile)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** profile `package.json`(缺 ⇒ `{dependencies:{}, dsh:{profile:{bundles:[]}}}`)。 */
|
|
147
|
+
export function readProfileManifest({ dshHome, profile = 'web' }) {
|
|
148
|
+
const pkgPath = join(profileDirOf({ dshHome, profile }), 'package.json')
|
|
149
|
+
const pkg = readJsonSafe(pkgPath, null) || {}
|
|
150
|
+
const deps = (pkg && pkg.dependencies) || {}
|
|
151
|
+
const bundles = ((pkg.dsh || {}).profile || {}).bundles
|
|
152
|
+
return { pkgPath, exists: existsSync(pkgPath), pkg, deps, bundles: Array.isArray(bundles) ? bundles : [] }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** 一个包在 profile 里**落地的那份**(`node_modules/<name>/package.json`)。 */
|
|
156
|
+
export function readLandedPackage({ dshHome, profile = 'web', name }) {
|
|
157
|
+
if (!isStr(name)) return null
|
|
158
|
+
return readJsonSafe(join(profileDirOf({ dshHome, profile }), 'node_modules', ...String(name).split('/'), 'package.json'), null)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* **本机每个智能体的现状**(纯只读)。
|
|
163
|
+
* `state` 四态:`absent`(没装)/ `up-to-date`(已装且版本一致)/ `upgrade`(已装但版本落后)/
|
|
164
|
+
* `unknown`(装了但读不出落地版本 —— 也算"可重装",但**不假装知道**版本差)。
|
|
165
|
+
* @returns {{agents: object[], errors: string[]}}
|
|
166
|
+
*/
|
|
167
|
+
export function agentRuntimeStatus({ repo, dshHome, profile = 'web' }) {
|
|
168
|
+
const shelf = loadAgentShelf({ repo })
|
|
169
|
+
const installed = new Set(installedAgentIds({ dshHome }))
|
|
170
|
+
const { deps } = readProfileManifest({ dshHome, profile })
|
|
171
|
+
const agents = shelf.agents.map((a) => {
|
|
172
|
+
const inDeps = !!deps[a.name]
|
|
173
|
+
const landed = readLandedPackage({ dshHome, profile, name: a.name })
|
|
174
|
+
const installedVersion = landed && isStr(landed.version) ? String(landed.version) : ''
|
|
175
|
+
const isInstalled = installed.has(a.id) || inDeps
|
|
176
|
+
const state = !isInstalled ? 'absent'
|
|
177
|
+
: !installedVersion ? 'unknown'
|
|
178
|
+
: installedVersion === String(a.bundleVersion) ? 'up-to-date' : 'upgrade'
|
|
179
|
+
return {
|
|
180
|
+
...a,
|
|
181
|
+
installed: isInstalled,
|
|
182
|
+
inDependencies: inDeps,
|
|
183
|
+
installedVersion,
|
|
184
|
+
state,
|
|
185
|
+
/** 界面按钮的**唯一文案判据**(⛔ 前端不自己算)。 */
|
|
186
|
+
action: state === 'up-to-date' ? 'reinstall' : state === 'absent' ? 'install' : 'upgrade',
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
return { agents, errors: shelf.errors }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 动作 ⇒ 是否**短路**(幂等:已是最新且不是"重装" ⇒ 零写入)。判据只有这一处。
|
|
194
|
+
* @returns {{shortCircuit: boolean, message: string}}
|
|
195
|
+
*/
|
|
196
|
+
export function shortCircuitFor({ status, action }) {
|
|
197
|
+
if (action === 'reinstall') return { shortCircuit: false, message: '' }
|
|
198
|
+
if (status && status.state === 'up-to-date') {
|
|
199
|
+
return { shortCircuit: true, message: `已是最新(v${status.installedVersion})——本次不做任何写入` }
|
|
200
|
+
}
|
|
201
|
+
return { shortCircuit: false, message: '' }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── 许可证前置(服务端判定)────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 数据目录(`whyshu.env` 的落点)—— **委托内核的同一判据**(⛔ 不在这里重写三级链)。
|
|
208
|
+
* @returns {string}
|
|
209
|
+
*/
|
|
210
|
+
export function dataDirFor({ dshHome, env = process.env } = {}) {
|
|
211
|
+
const e = { ...env }
|
|
212
|
+
if (isStr(dshHome)) e.DSH_HOME = String(dshHome)
|
|
213
|
+
try { return resolveDataDir({ env: e }).dir } catch { return '' }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** 读 `whyshu.env` 里的一个键(落点由内核判据给定;⛔ 不猜路径)。 */
|
|
217
|
+
function readWhyShuEnvKey(key, { dshHome, env = process.env } = {}) {
|
|
218
|
+
const dir = dataDirFor({ dshHome, env })
|
|
219
|
+
if (!dir) return ''
|
|
220
|
+
const f = join(dir, 'whyshu.env')
|
|
221
|
+
if (!existsSync(f)) return ''
|
|
222
|
+
try {
|
|
223
|
+
const m = new RegExp(`^\\s*${key}\\s*=\\s*(.+?)\\s*$`, 'm').exec(readFileSync(f, 'utf8'))
|
|
224
|
+
return m && isStr(m[1]) ? m[1].replace(/^["']|["']$/g, '') : ''
|
|
225
|
+
} catch { return '' }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 账号令牌:`env.WHYSHU_API_KEY` → `<数据目录>/whyshu.env` → `<dshHome>/.credentials.yaml#refs`。
|
|
230
|
+
* ⛔ 只读;⛔ 绝不回写(写凭据的唯一入口是 dsh 官方 API / argent-core)。
|
|
231
|
+
* @returns {string} 找不到 ⇒ ''
|
|
232
|
+
*/
|
|
233
|
+
export function readAccountToken({ dshHome, env = process.env } = {}) {
|
|
234
|
+
if (isStr(env.WHYSHU_API_KEY)) return String(env.WHYSHU_API_KEY).trim()
|
|
235
|
+
const fromEnvFile = readWhyShuEnvKey('WHYSHU_API_KEY', { dshHome, env })
|
|
236
|
+
if (fromEnvFile) return fromEnvFile
|
|
237
|
+
const dh = isStr(dshHome) ? String(dshHome) : (isStr(env.DSH_HOME) ? String(env.DSH_HOME) : join(homedir(), '.dsh'))
|
|
238
|
+
const cred = join(dh, '.credentials.yaml')
|
|
239
|
+
if (existsSync(cred)) {
|
|
240
|
+
try {
|
|
241
|
+
// `version + refs + records` 三段格式:**只**在 `refs:` 段里按缩进取值(⛔ 不碰 records、⛔ 不重写)。
|
|
242
|
+
let inRefs = false
|
|
243
|
+
for (const line of readFileSync(cred, 'utf8').split('\n')) {
|
|
244
|
+
if (/^refs:\s*$/.test(line)) { inRefs = true; continue }
|
|
245
|
+
if (inRefs && /^\S/.test(line)) break // 回到顶层键 ⇒ refs 段结束
|
|
246
|
+
if (!inRefs) continue
|
|
247
|
+
const m = /^\s+WHYSHU_API_KEY\s*:\s*(.+?)\s*$/.exec(line)
|
|
248
|
+
if (m && isStr(m[1])) return m[1].replace(/^["']|["']$/g, '')
|
|
249
|
+
}
|
|
250
|
+
} catch { /* 读不到 ⇒ 继续 */ }
|
|
251
|
+
}
|
|
252
|
+
return ''
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** 服务端基址(`ARGENT_API_BASE` → `whyshu.env#WHYSHU_API_URL` → 官方站点)。 */
|
|
256
|
+
export function resolveApiBase({ dshHome, env = process.env } = {}) {
|
|
257
|
+
if (isStr(env.ARGENT_API_BASE)) return String(env.ARGENT_API_BASE).replace(/\/+$/, '')
|
|
258
|
+
const url = readWhyShuEnvKey('WHYSHU_API_URL', { dshHome, env })
|
|
259
|
+
return url ? url.replace(/\/+$/, '') : DEFAULT_API_BASE
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 向服务端要**一次许可证/档位判定**(`GET /api/argent/entitlements`)。
|
|
264
|
+
* ⛔ 判据不可得(没令牌 / 网络失败 / 非 200 / 载荷不是对象)⇒ `{ok:false}` —— 调用方**必须拒绝**。
|
|
265
|
+
* @returns {Promise<{ok:boolean, reason:string, entitlement?:object, http?:number}>}
|
|
266
|
+
*/
|
|
267
|
+
export async function fetchEntitlement({ dshHome, env = process.env, fetchImpl = globalThis.fetch, timeoutMs = 8000 } = {}) {
|
|
268
|
+
const token = readAccountToken({ dshHome, env })
|
|
269
|
+
if (!token) {
|
|
270
|
+
return { ok: false, reason: '没有账号令牌(WHYSHU_API_KEY / `~/.dsh/argent/whyshu.env` / `~/.dsh/.credentials.yaml#refs` 都没有)' }
|
|
271
|
+
}
|
|
272
|
+
if (typeof fetchImpl !== 'function') return { ok: false, reason: '本机 Node 没有 fetch(无法向服务端要判定)' }
|
|
273
|
+
const base = resolveApiBase({ dshHome, env })
|
|
274
|
+
const ac = typeof AbortController === 'function' ? new AbortController() : null
|
|
275
|
+
const timer = ac ? setTimeout(() => ac.abort(), timeoutMs) : null
|
|
276
|
+
try {
|
|
277
|
+
const resp = await fetchImpl(`${base}${ENTITLEMENTS_PATH}`, {
|
|
278
|
+
method: 'GET',
|
|
279
|
+
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
|
280
|
+
signal: ac ? ac.signal : undefined,
|
|
281
|
+
})
|
|
282
|
+
if (!resp || !resp.ok) {
|
|
283
|
+
const code = resp ? resp.status : '?'
|
|
284
|
+
let detail = ''
|
|
285
|
+
try { detail = String(await resp.text()).slice(0, 200) } catch { /* 无正文 */ }
|
|
286
|
+
return { ok: false, reason: `服务端判定不可得:GET ${base}${ENTITLEMENTS_PATH} ⇒ HTTP ${code}${detail ? `(${detail})` : ''}`, http: code }
|
|
287
|
+
}
|
|
288
|
+
const doc = await resp.json()
|
|
289
|
+
if (!doc || typeof doc !== 'object') return { ok: false, reason: '服务端判定不可得:载荷不是对象' }
|
|
290
|
+
if (doc.ok === false) return { ok: false, reason: `服务端判定不可得:${String(doc.error || doc.reason || '未知')}`, entitlement: doc }
|
|
291
|
+
return { ok: true, reason: '', entitlement: doc }
|
|
292
|
+
} catch (e) {
|
|
293
|
+
return { ok: false, reason: `服务端判定不可得:${String((e && e.message) || e)}` }
|
|
294
|
+
} finally {
|
|
295
|
+
if (timer) clearTimeout(timer)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const PLAN_RANK = { free: 0, pro: 1, enterprise: 2 }
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 判定某个智能体在当前账号下**能不能装**(判据全部来自服务端载荷,⛔ 前端零档位名单)。
|
|
303
|
+
* 三态:`allowed` true/false + `reason`(false 时**必须**是可照抄的原因)。
|
|
304
|
+
* @returns {{allowed:boolean, reason:string, needPlan:string, plan:string}}
|
|
305
|
+
*/
|
|
306
|
+
export function judgeEntitlement({ agent, entitlement }) {
|
|
307
|
+
if (!entitlement || typeof entitlement !== 'object') return { allowed: false, reason: '没有服务端判定(内部错误)', needPlan: agent.minPlan || '', plan: '' }
|
|
308
|
+
const plan = String(entitlement.plan || '')
|
|
309
|
+
const needPlan = String(agent.minPlan || '')
|
|
310
|
+
// ① 许可过期(服务端明确说 license_expired)⇒ 拒
|
|
311
|
+
if (entitlement.licenseActive === false) {
|
|
312
|
+
return { allowed: false, reason: `许可证未生效/已过期(服务端 reason=${String(entitlement.reason || 'license_expired')})`, needPlan, plan }
|
|
313
|
+
}
|
|
314
|
+
// ② 档位:packs 的 unlocked 是服务端口径;再补一条"账号档位 < 智能体档位"的兜底(两处**都取自载荷**)
|
|
315
|
+
if (needPlan && PLAN_RANK[plan] !== undefined && PLAN_RANK[plan] < (PLAN_RANK[needPlan] || 0)) {
|
|
316
|
+
return { allowed: false, reason: `未达档位:该智能体需要 ${needPlan},当前账号 ${plan}`, needPlan, plan }
|
|
317
|
+
}
|
|
318
|
+
// ③ 逐能力:服务端 packs[] 里的 unlocked
|
|
319
|
+
const packs = Array.isArray(entitlement.packs) ? entitlement.packs : []
|
|
320
|
+
const caps = Array.isArray(agent.capabilities) ? agent.capabilities : []
|
|
321
|
+
if (!packs.length) {
|
|
322
|
+
return { allowed: false, reason: '服务端判定里没有能力清单(packs 为空)——按"判定不可得"拒绝,⛔ 不离线放行', needPlan, plan }
|
|
323
|
+
}
|
|
324
|
+
for (const c of caps) {
|
|
325
|
+
const hit = packs.find((p) => p && (String(p.id) === String(c)
|
|
326
|
+
|| (Array.isArray(p.capabilities) && p.capabilities.map(String).includes(String(c)))))
|
|
327
|
+
if (!hit) return { allowed: false, reason: `未达档位:账号能力清单里没有 \`${c}\`(服务端判定)`, needPlan, plan }
|
|
328
|
+
if (hit.unlocked !== true) {
|
|
329
|
+
const need = String(hit.minPlan || needPlan || 'pro')
|
|
330
|
+
return { allowed: false, reason: `未达档位:能力 \`${c}\` 需要 ${need},当前账号 ${plan || '未知'}(服务端判定)`, needPlan, plan }
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return { allowed: true, reason: '', needPlan, plan }
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ── 调用安装器(**唯一安装链**)────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* **本次动作的落盘面**(快照/回滚的唯一判据;⛔ 不另写一份"哪些文件算落盘"的清单)。
|
|
340
|
+
*
|
|
341
|
+
* 由三部分拼成,每一部分都取自**既有真源**:
|
|
342
|
+
* ① profile 的两个清单文件(`package.json` / `cordis.patch.yml`)—— 依赖图与组合行的落点;
|
|
343
|
+
* ② 装机账本(`agents.json` / `install.json`)—— "装了哪些智能体 / 哪些能力包"的判据面;
|
|
344
|
+
* ③ **包内声明**点名的落盘物:agent bundle 的 package.json、presets 的 `presets.selection.json`、
|
|
345
|
+
* 声明的技能(`<dshHome>/skills/<s>/SKILL.md`)、以及该智能体声明的**能力包的声明文件清单**
|
|
346
|
+
* (`scripts/capability-packs.lock.json#capabilities.<c>.files`,仓库内相对路径)。
|
|
347
|
+
* ⛔ 不含整棵 node_modules / 整仓快照(那是分钟级与 GB 级)——面就是上面这些**判据文件**。
|
|
348
|
+
* @returns {{files: Array<{path:string, data:Buffer|null}>}} `data=null` ⇒ 动作前**不存在**
|
|
349
|
+
*/
|
|
350
|
+
export function snapshotAgentSurface({ repo, dshHome, profile = 'web', id }) {
|
|
351
|
+
const bundle = readAgentBundle({ repo, id })
|
|
352
|
+
const profileDir = profileDirOf({ dshHome, profile })
|
|
353
|
+
const rels = [
|
|
354
|
+
join(profileDir, 'package.json'),
|
|
355
|
+
join(profileDir, 'cordis.patch.yml'),
|
|
356
|
+
join(dshHome, 'argent', 'agents.json'),
|
|
357
|
+
join(dshHome, 'argent', 'install.json'),
|
|
358
|
+
]
|
|
359
|
+
if (bundle) {
|
|
360
|
+
rels.push(join(profileDir, 'node_modules', ...bundle.name.split('/'), 'package.json'))
|
|
361
|
+
const pdir = resolveBundleDir(profileDir)
|
|
362
|
+
rels.push(join(pdir, SELECTION_FILE))
|
|
363
|
+
for (const sk of ((bundle.decl || {}).skills || []).map(String)) rels.push(join(dshHome, 'skills', sk, 'SKILL.md'))
|
|
364
|
+
// 能力包的**声明文件清单**(唯一真源 = 能力包版本锁)
|
|
365
|
+
const lock = readJsonSafe(join(repo, 'scripts', 'capability-packs.lock.json'), null) || {}
|
|
366
|
+
for (const c of (((bundle.decl || {}).payload || {}).capabilities || []).map(String)) {
|
|
367
|
+
const files = ((lock.capabilities || {})[c] || {}).files || {}
|
|
368
|
+
for (const rel of Object.keys(files)) rels.push(join(repo, rel))
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const seen = new Set()
|
|
372
|
+
const files = []
|
|
373
|
+
for (const p of rels) {
|
|
374
|
+
if (seen.has(p)) continue
|
|
375
|
+
seen.add(p)
|
|
376
|
+
files.push({ path: p, data: existsSync(p) ? readFileSync(p) : null })
|
|
377
|
+
}
|
|
378
|
+
return { files, profileDir }
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* **回滚到动作前**(逐字节)——"⛔ 不写半截依赖图"的落点。
|
|
383
|
+
* 动作前不存在的 ⇒ 删掉(并清掉因此产生的空目录);存在的 ⇒ 写回原字节(0600,与 profile 清单同档)。
|
|
384
|
+
* @returns {{restored:string[], removed:string[]}}
|
|
385
|
+
*/
|
|
386
|
+
export function restoreAgentSurface(snap) {
|
|
387
|
+
const restored = []
|
|
388
|
+
const removed = []
|
|
389
|
+
for (const f of (snap && snap.files) || []) {
|
|
390
|
+
try {
|
|
391
|
+
if (f.data === null) {
|
|
392
|
+
if (existsSync(f.path)) { rmSync(f.path, { force: true }); removed.push(f.path) }
|
|
393
|
+
// 顺手清掉"因本次动作才出现"的空目录(只清到 profile 根/技能根为止,⛔ 不递归删有内容的目录)
|
|
394
|
+
let d = dirname(f.path)
|
|
395
|
+
for (let i = 0; i < 4 && d && d.length > 1; i += 1) {
|
|
396
|
+
try {
|
|
397
|
+
if (!existsSync(d) || readdirSync(d).length) break
|
|
398
|
+
rmSync(d, { recursive: true, force: true })
|
|
399
|
+
} catch { break }
|
|
400
|
+
d = dirname(d)
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
mkdirSync(dirname(f.path), { recursive: true })
|
|
404
|
+
writeFileSync(f.path, f.data, { mode: 0o600 })
|
|
405
|
+
restored.push(f.path)
|
|
406
|
+
}
|
|
407
|
+
} catch { /* 单点失败不掩盖原始错误:回滚尽力而为,下面构造的失败里会带上 surface 差异 */ }
|
|
408
|
+
}
|
|
409
|
+
return { restored, removed }
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** 动作前后**逐字节**比对面(空数组 = 逐字节一致)。 */
|
|
413
|
+
export function diffAgentSurface(snap) {
|
|
414
|
+
const changed = []
|
|
415
|
+
for (const f of (snap && snap.files) || []) {
|
|
416
|
+
const now = existsSync(f.path) ? readFileSync(f.path) : null
|
|
417
|
+
const before = f.data
|
|
418
|
+
if (now === null && before === null) continue
|
|
419
|
+
if (now === null || before === null || !now.equals(before)) changed.push(f.path)
|
|
420
|
+
}
|
|
421
|
+
return changed
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** 安装器 argv(判据面:测试逐字断言它含 `--agent <id>` 与 `--dsh-home <home>`)。 */
|
|
425
|
+
export function agentInstallArgv({ repo, id, dshHome, yes = true }) {
|
|
426
|
+
return [join(repo, 'install-argent.mjs'), '--agent', String(id), ...(yes ? ['--yes'] : []), '--dsh-home', String(dshHome)]
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* 装一个智能体:**调用安装器本体**(`install-argent.mjs --agent <id>`)。
|
|
431
|
+
* 幂等短路由调用方(或本函数的 `status`)决定 —— 见 {@link shortCircuitFor}。
|
|
432
|
+
* @param {{repo:string,dshHome:string,id:string,profile?:string,env?:object,cwd?:string,timeoutMs?:number,spawn?:Function,
|
|
433
|
+
* status?:object,action?:string}} o
|
|
434
|
+
* @returns {Promise<{ok:boolean, step:string, argv:string[], exitCode:number|null, stdout:string, stderr:string, out:string, fix:string, logs:string[]}>}
|
|
435
|
+
*/
|
|
436
|
+
export async function runAgentInstall({
|
|
437
|
+
repo, dshHome, id, profile = 'web', env = process.env, cwd,
|
|
438
|
+
timeoutMs = 15 * 60 * 1000, spawn = nodeSpawn, status = null, action = '',
|
|
439
|
+
}) {
|
|
440
|
+
// ① 幂等短路:已装且版本一致(且不是"重装")⇒ **零写入**
|
|
441
|
+
if (status) {
|
|
442
|
+
const sc = shortCircuitFor({ status, action })
|
|
443
|
+
if (sc.shortCircuit) {
|
|
444
|
+
return { ok: true, step: 'up-to-date', argv: [], exitCode: 0, stdout: sc.message, stderr: '', out: sc.message, fix: '', logs: [sc.message], wrote: false }
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// ② **前置判据**:本机基座(壳)已在位 —— 与安装器同一条判据(bundle 依赖壳)。
|
|
448
|
+
const { deps } = readProfileManifest({ dshHome, profile })
|
|
449
|
+
const hasCore = Object.keys(deps).some((k) => k === '@whyshu/argent-core' || k === 'argent-core')
|
|
450
|
+
const hasSkin = Object.keys(deps).some((k) => k === '@whyshu/argent-skin' || k === 'argent-skin')
|
|
451
|
+
if (!hasCore || !hasSkin) {
|
|
452
|
+
return {
|
|
453
|
+
ok: false, step: 'base-not-landed', argv: [], exitCode: null, stdout: '', stderr: '', out: '',
|
|
454
|
+
fix: '本机基座(壳)还没装配:先跑一次整装(官网那条安装命令),再回来点这个按钮。⛔ 本次没有写任何东西。',
|
|
455
|
+
logs: ['基座(core + skin)不在 profile 依赖图里 ⇒ 本次不装 agent bundle(⛔ 不写半截依赖图)'], wrote: false,
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// ③ 快照**本次落盘面**(跨进程事务;失败即逐字节回滚 —— "⛔ 不写半截依赖图")
|
|
459
|
+
const surface = snapshotAgentSurface({ repo, dshHome, profile, id })
|
|
460
|
+
// ④ 调用**安装器本体**(唯一安装链)
|
|
461
|
+
const argv = agentInstallArgv({ repo, dshHome, id })
|
|
462
|
+
const child = spawn(process.execPath, argv, { env, cwd: cwd || repo, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
463
|
+
const logs = []
|
|
464
|
+
let stdout = ''
|
|
465
|
+
let stderr = ''
|
|
466
|
+
child.stdout.on('data', (b) => { stdout += String(b); logs.push(String(b)) })
|
|
467
|
+
child.stderr.on('data', (b) => { stderr += String(b); logs.push(String(b)) })
|
|
468
|
+
const code = await new Promise((res) => {
|
|
469
|
+
const t = setTimeout(() => { try { child.kill('SIGKILL') } catch { /* 已退出 */ } }, timeoutMs)
|
|
470
|
+
child.on('error', (e) => { stderr += String((e && e.message) || e); clearTimeout(t); res(-1) })
|
|
471
|
+
child.on('close', (c) => { clearTimeout(t); res(c === null ? -1 : c) })
|
|
472
|
+
})
|
|
473
|
+
const out = `${stdout}${stderr}`.replace(/\x1b\[[0-9;]*m/g, '')
|
|
474
|
+
/** 失败收口:**回滚落盘面** → 返回结构化失败(哪一步 / 原文 / 怎么修 / 回滚痕迹)。 */
|
|
475
|
+
const fail = (step, extra = {}) => {
|
|
476
|
+
const rb = restoreAgentSurface(surface)
|
|
477
|
+
const left = diffAgentSurface(surface)
|
|
478
|
+
const fix = extra.fix || fixHintFor(out)
|
|
479
|
+
return {
|
|
480
|
+
ok: false, step, argv, exitCode: extra.exitCode === undefined ? code : extra.exitCode, stdout, stderr, out,
|
|
481
|
+
reason: extra.reason || problemsText(out, extra.problems, code),
|
|
482
|
+
fix, logs, wrote: !!(rb.restored.length || rb.removed.length), rollback: { ...rb, leftover: left },
|
|
483
|
+
...(extra.problems ? { problems: extra.problems } : {}),
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (code !== 0) {
|
|
487
|
+
return fail('installer-failed')
|
|
488
|
+
}
|
|
489
|
+
// ⑤ 安装器自己会打印"基座没装配 ⇒ 本次不装 bundle"并以 0 退出 —— ⛔ 不许把它当成功
|
|
490
|
+
if (/基座(壳)还没装配/.test(out) && /本次\*\*不装\*\*|本次\*\*不装\*\* agent bundle/.test(out)) {
|
|
491
|
+
return fail('base-not-landed', {
|
|
492
|
+
exitCode: 0,
|
|
493
|
+
fix: '本机基座(壳)还没装配:先跑一次整装(官网那条安装命令),再回来点这个按钮。',
|
|
494
|
+
})
|
|
495
|
+
}
|
|
496
|
+
// ⑥ 收尾核对(**只读**):bundle 在依赖图内 / presets 是直接依赖 + bundles 条目 / 能力包 / 角色 / 技能
|
|
497
|
+
const problems = verifyAgentLanded({ repo, dshHome, profile, id })
|
|
498
|
+
if (problems.length) {
|
|
499
|
+
return fail('selfcheck-failed', {
|
|
500
|
+
exitCode: 0,
|
|
501
|
+
fix: problems.map((p) => p.fix).filter(Boolean).join(';') || '按上面的逐条说明修好后重试',
|
|
502
|
+
problems,
|
|
503
|
+
})
|
|
504
|
+
}
|
|
505
|
+
return { ok: true, step: 'installed', argv, exitCode: 0, stdout, stderr, out, fix: '', problems: [], logs, wrote: true }
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* 失败**原文**(⛔ 不加工、不摘要):优先用收尾核对问题的原文;否则取安装器输出里最后几条
|
|
510
|
+
* `✗` 行;再否则用退出码。用户要的是"照抄这段去搜/去修",所以宁可原样给。
|
|
511
|
+
*/
|
|
512
|
+
export function problemsText(out, problems, code) {
|
|
513
|
+
if (Array.isArray(problems) && problems.length) return problems.map((p) => p.message).join('\n')
|
|
514
|
+
const lines = String(out || '').split('\n').map((l) => l.replace(/\x1b\[[0-9;]*m/g, '').trimEnd())
|
|
515
|
+
// ① 安装器自己的收尾自检块(最准:它就是判红的那一段)
|
|
516
|
+
const at = lines.findIndex((l) => /智能体 bundle 收尾自检未通过/.test(l))
|
|
517
|
+
if (at >= 0) {
|
|
518
|
+
const block = lines.slice(at, at + 12).map((l) => l.trim()).filter(Boolean)
|
|
519
|
+
return block.join('\n')
|
|
520
|
+
}
|
|
521
|
+
// ② 退一步:最后几条错误行 —— ⚠️ 排除**回执行**(如货架清单里的 `✗ <id> …(未点名)`),
|
|
522
|
+
// 那些不是错误,拿它们当"失败原因"会把用户带偏(本用例第一版现场命中)。
|
|
523
|
+
const errs = lines.map((l) => l.trim())
|
|
524
|
+
.filter((l) => /^[✗❌×]/.test(l) && !/(未点名)|\(未点名\)/.test(l))
|
|
525
|
+
if (errs.length) return errs.slice(-4).join('\n')
|
|
526
|
+
return `安装器退出码 ${code}(没有可解析的错误行,见 logs)`
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** 从安装器输出里挑一条可照抄的修法(⛔ 不编造:命中已知形态才给)。 */
|
|
530
|
+
export function fixHintFor(text) {
|
|
531
|
+
const t = String(text || '')
|
|
532
|
+
if (/pnpm was not found|ENOENT|Command failed with ENOENT/.test(t)) {
|
|
533
|
+
return '装 pnpm 并让它在 PATH 上:`corepack enable pnpm`(Node 自带),或按 pnpm 官网安装;离线/送装可用 `file:` 回退。'
|
|
534
|
+
}
|
|
535
|
+
if (/dependencies.*里\*\*没有\*\*|不在依赖图内/.test(t)) {
|
|
536
|
+
return 'bundle 没进 profile 依赖图:`cd <profile 目录> && pnpm add <包名>` 后重试;离线用 `file:<克隆>/plugins/<包>`。'
|
|
537
|
+
}
|
|
538
|
+
if (/包内声明与货架声明不一致/.test(t)) {
|
|
539
|
+
return '包内声明 ≠ 货架声明:跑 `node scripts/gen-agent-bundles.mjs`(或在开发仓库重生成 bundle)后重试。'
|
|
540
|
+
}
|
|
541
|
+
return ''
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* **装完只读核对**(与安装器收尾自检**同一条口径**;W7 追上一条它没查的:
|
|
546
|
+
* "presets 必须是**直接依赖**且在 `dsh.profile.bundles` 里" —— W2 的坑:只 `dsh plugin add @whyshu/argent-shell`
|
|
547
|
+
* 会让 presets 只剩**传递依赖** ⇒ 它的 bundle 层不激活 ⇒ **角色行挂不上**)。
|
|
548
|
+
* @returns {Array<{code:string, message:string, fix:string}>} 空数组 = 通过
|
|
549
|
+
*/
|
|
550
|
+
export function verifyAgentLanded({ repo, dshHome, profile = 'web', id }) {
|
|
551
|
+
const problems = []
|
|
552
|
+
const bundle = readAgentBundle({ repo, id })
|
|
553
|
+
if (!bundle) { problems.push({ code: 'bundle-missing', message: `本机克隆里没有 plugins/${AGENT_DIR_PREFIX}${id}`, fix: 'git pull / 重跑同步脚本' }); return problems }
|
|
554
|
+
const { deps, bundles } = readProfileManifest({ dshHome, profile })
|
|
555
|
+
const profileDir = profileDirOf({ dshHome, profile })
|
|
556
|
+
// ① bundle 在依赖图内 + 能解析
|
|
557
|
+
if (!deps[bundle.name]) {
|
|
558
|
+
problems.push({ code: 'bundle-not-in-deps', message: `profile \`dependencies\` 里没有 ${bundle.name}(实体在 node_modules ≠ 在依赖图内)`, fix: `cd ${profileDir} && pnpm add ${bundle.name}@${bundle.version}` })
|
|
559
|
+
} else if (!existsSync(join(profileDir, 'node_modules', ...bundle.name.split('/'), 'package.json'))) {
|
|
560
|
+
problems.push({ code: 'bundle-unresolvable', message: `${bundle.name} 在依赖图里、但 node_modules 里没有实体(解析不到)`, fix: `cd ${profileDir} && pnpm install` })
|
|
561
|
+
}
|
|
562
|
+
// ② **presets 必须是直接依赖 + bundles 条目**(W2 的坑;W7 的硬判据)
|
|
563
|
+
if (!deps[PRESETS_NPM]) {
|
|
564
|
+
problems.push({
|
|
565
|
+
code: 'presets-not-direct-dep',
|
|
566
|
+
message: `profile \`dependencies\` 里**没有** ${PRESETS_NPM}(只有传递依赖 ⇒ 它的 bundle 层不激活 ⇒ 角色行挂不上)`,
|
|
567
|
+
fix: `cd ${profileDir} && pnpm add file:<克隆>/plugins/argent-presets`,
|
|
568
|
+
})
|
|
569
|
+
}
|
|
570
|
+
if (!bundles.includes(PRESETS_NPM)) {
|
|
571
|
+
problems.push({
|
|
572
|
+
code: 'presets-not-in-bundles',
|
|
573
|
+
message: `\`dsh.profile.bundles\` 里**没有** ${PRESETS_NPM}(不登记 ⇒ preset 行不被 insert)`,
|
|
574
|
+
fix: `把 "${PRESETS_NPM}" 补进 ${join(profileDir, 'package.json')} 的 dsh.profile.bundles(与其它条目并列)`,
|
|
575
|
+
})
|
|
576
|
+
}
|
|
577
|
+
// ③ 能力包按**包内声明**落位(装机账本)
|
|
578
|
+
const wantCaps = (((bundle.decl || {}).payload || {}).capabilities || []).map(String)
|
|
579
|
+
const ledger = readJsonSafe(join(dshHome, 'argent', 'install.json'), null)
|
|
580
|
+
const landed = ((ledger && ledger.capabilities) || []).map((c) => String((c && c.id) || '')).filter(Boolean)
|
|
581
|
+
for (const c of wantCaps) {
|
|
582
|
+
if (!landed.includes(c)) problems.push({ code: 'capability-missing', message: `声明的能力包 \`${c}\` 不在装机账本里(${join(dshHome, 'argent', 'install.json')})`, fix: `重跑:node ${join(repo, 'install-argent.mjs')} --agent ${id} --yes --dsh-home ${dshHome}` })
|
|
583
|
+
}
|
|
584
|
+
// ④ 角色(presets.selection.json#kept)/ 技能(<dshHome>/skills/<s>/SKILL.md)
|
|
585
|
+
const bundleDir = resolveBundleDir(profileDir)
|
|
586
|
+
const sel = readJsonSafe(join(bundleDir, SELECTION_FILE), null)
|
|
587
|
+
const kept = (sel && Array.isArray(sel.kept)) ? sel.kept.map(String) : []
|
|
588
|
+
for (const r of (((bundle.decl || {}).roles) || []).map(String)) {
|
|
589
|
+
if (!kept.includes(r)) problems.push({ code: 'role-not-landed', message: `角色 \`${r}\` 不在落盘集合内(${SELECTION_FILE}#kept = ${kept.join('、') || '(空)'})`, fix: `重跑:node ${join(repo, 'install-argent.mjs')} --agent ${id} --yes --dsh-home ${dshHome}` })
|
|
590
|
+
}
|
|
591
|
+
for (const sk of (((bundle.decl || {}).skills) || []).map(String)) {
|
|
592
|
+
if (!existsSync(join(dshHome, 'skills', sk, 'SKILL.md'))) problems.push({ code: 'skill-not-landed', message: `技能 \`${sk}\` 未落位(${join(dshHome, 'skills', sk, 'SKILL.md')} 不存在)`, fix: `重跑:node ${join(repo, 'install-argent.mjs')} --agent ${id} --yes --dsh-home ${dshHome}` })
|
|
593
|
+
}
|
|
594
|
+
return problems
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// ── 编排:许可证前置 → 装 → 逐条结果(宿主工具/路由调这一个函数)──────────────
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* **界面动作的完整语义**(宿主只调它,⛔ 不自己编排):
|
|
601
|
+
* ① 读现状(只读)→ ② 幂等短路(已是最新且非重装 ⇒ **零写入**,回「已是最新」)
|
|
602
|
+
* → ③ **许可证前置**(服务端判定;不可得/未达档位 ⇒ **拒绝且零写入**)
|
|
603
|
+
* → ④ 调安装器 → ⑤ 收尾核对 → 结构化结果。
|
|
604
|
+
* @returns {Promise<{ok:boolean, action:string, step:string, message:string, reason?:string,
|
|
605
|
+
* fix?:string, logs:string[], argv:string[], wrote:boolean, status?:object, entitlement?:object}>}
|
|
606
|
+
*/
|
|
607
|
+
export async function installAgentAction({
|
|
608
|
+
repo, dshHome, id, profile = 'web', action = '', env = process.env,
|
|
609
|
+
fetchImpl = globalThis.fetch, spawn = nodeSpawn, timeoutMs,
|
|
610
|
+
} = {}) {
|
|
611
|
+
const shelf = agentRuntimeStatus({ repo, dshHome, profile })
|
|
612
|
+
const status = shelf.agents.find((a) => a.id === String(id))
|
|
613
|
+
if (!status) {
|
|
614
|
+
return { ok: false, action, step: 'unknown-agent', message: `货架上没有智能体 \`${id}\``, reason: `货架上没有智能体 \`${id}\``, logs: shelf.errors, argv: [], wrote: false }
|
|
615
|
+
}
|
|
616
|
+
const act = isStr(action) ? action : status.action
|
|
617
|
+
// ② 幂等短路(**零写入**)—— ⚠️ 判据只能用**调用方点名**的 action(空 = 自动):
|
|
618
|
+
// 用派生出来的 `status.action`(已是最新时是 `reinstall`)做判据会把短路**自己关掉**,
|
|
619
|
+
// 于是"已是最新"变成"每次都重装"(本用例 §3 现场命中)。
|
|
620
|
+
const sc = shortCircuitFor({ status, action: isStr(action) ? action : '' })
|
|
621
|
+
if (sc.shortCircuit) {
|
|
622
|
+
return { ok: true, action: 'up-to-date', step: 'up-to-date', message: sc.message, logs: [sc.message], argv: [], wrote: false, status }
|
|
623
|
+
}
|
|
624
|
+
// ③ 许可证前置(服务端判定)—— **动手之前**
|
|
625
|
+
const ent = await fetchEntitlement({ dshHome, env, fetchImpl })
|
|
626
|
+
if (!ent.ok) {
|
|
627
|
+
return {
|
|
628
|
+
ok: false, action: act, step: 'license-unavailable', message: `许可证判定不可得 ⇒ 拒绝安装(⛔ 不离线放行)`,
|
|
629
|
+
reason: ent.reason, fix: '检查账号是否已登录(WHYSHU_API_KEY)、网络是否可达 whyshu.com;必要时重跑安装器 --api-key <账号令牌>',
|
|
630
|
+
logs: [ent.reason], argv: [], wrote: false, status,
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const verdict = judgeEntitlement({ agent: status, entitlement: ent.entitlement })
|
|
634
|
+
if (!verdict.allowed) {
|
|
635
|
+
return {
|
|
636
|
+
ok: false, action: act, step: 'license-denied', message: verdict.reason, reason: verdict.reason,
|
|
637
|
+
fix: `升级到 ${verdict.needPlan || status.minPlan} 后重试(当前账号 ${verdict.plan || '未知'})`,
|
|
638
|
+
logs: [], argv: [], wrote: false, status, entitlement: ent.entitlement,
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
// ④⑤ 调用安装器(唯一安装链)+ 收尾核对
|
|
642
|
+
const r = await runAgentInstall({ repo, dshHome, id, profile, env, spawn, timeoutMs, status, action: act })
|
|
643
|
+
const after = agentRuntimeStatus({ repo, dshHome, profile }).agents.find((a) => a.id === String(id)) || null
|
|
644
|
+
return {
|
|
645
|
+
ok: !!r.ok, action: act, step: r.step, message: r.ok ? `智能体 \`${id}\` ${act === 'reinstall' ? '重装' : act === 'upgrade' ? '升级' : '安装'}完成(v${(after && after.installedVersion) || status.bundleVersion})` : `安装失败(${r.step})`,
|
|
646
|
+
reason: r.ok ? '' : (r.reason || r.fix || '安装失败'), fix: r.fix || '', logs: r.logs || [], argv: r.argv || [], wrote: r.wrote !== false,
|
|
647
|
+
problems: r.problems || [], rollback: r.rollback || null, status: after, entitlement: ent.entitlement,
|
|
648
|
+
}
|
|
649
|
+
}
|