@harrylabsj/kiwi 0.7.22 → 0.9.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/README.md +45 -10
- package/dist/a2a/negotiate.d.ts +6 -2
- package/dist/a2a/negotiate.js +10 -3
- package/dist/a2a/negotiate.js.map +1 -1
- package/dist/a2a/node.d.ts +4 -1
- package/dist/a2a/node.js +57 -4
- package/dist/a2a/node.js.map +1 -1
- package/dist/a2a/server/merchant-handler.d.ts +11 -3
- package/dist/a2a/server/merchant-handler.js +84 -26
- package/dist/a2a/server/merchant-handler.js.map +1 -1
- package/dist/agent/buyer/buyer-tools.js +17 -10
- package/dist/agent/buyer/buyer-tools.js.map +1 -1
- package/dist/agent/buyer/scheduler.d.ts +1 -0
- package/dist/agent/buyer/scheduler.js +5 -1
- package/dist/agent/buyer/scheduler.js.map +1 -1
- package/dist/agent/buyer/task-store.js +5 -2
- package/dist/agent/buyer/task-store.js.map +1 -1
- package/dist/agent/chat-tools.js +10 -3
- package/dist/agent/chat-tools.js.map +1 -1
- package/dist/agent/context/fencing.d.ts +27 -0
- package/dist/agent/context/fencing.js +126 -0
- package/dist/agent/context/fencing.js.map +1 -0
- package/dist/agent/context/grounding.d.ts +18 -0
- package/dist/agent/context/grounding.js +22 -0
- package/dist/agent/context/grounding.js.map +1 -0
- package/dist/agent/host/events.d.ts +31 -0
- package/dist/agent/host/events.js +62 -0
- package/dist/agent/host/events.js.map +1 -0
- package/dist/agent/host/tui-renderer.d.ts +17 -0
- package/dist/agent/host/tui-renderer.js +125 -0
- package/dist/agent/host/tui-renderer.js.map +1 -0
- package/dist/agent/host/web-renderer.d.ts +71 -0
- package/dist/agent/host/web-renderer.js +185 -0
- package/dist/agent/host/web-renderer.js.map +1 -0
- package/dist/agent/kernel-builder.d.ts +3 -1
- package/dist/agent/kernel-builder.js +4 -1
- package/dist/agent/kernel-builder.js.map +1 -1
- package/dist/agent/kernel.d.ts +20 -1
- package/dist/agent/kernel.js +255 -14
- package/dist/agent/kernel.js.map +1 -1
- package/dist/agent/memory/schema.d.ts +1 -1
- package/dist/agent/memory/schema.js +9 -1
- package/dist/agent/memory/schema.js.map +1 -1
- package/dist/agent/merchant/action-candidate.d.ts +18 -0
- package/dist/agent/merchant/action-candidate.js +51 -0
- package/dist/agent/merchant/action-candidate.js.map +1 -1
- package/dist/agent/merchant/fake-merchant-client.js +6 -1
- package/dist/agent/merchant/fake-merchant-client.js.map +1 -1
- package/dist/agent/merchant/intelligence/backend.d.ts +40 -0
- package/dist/agent/merchant/intelligence/backend.js +2 -0
- package/dist/agent/merchant/intelligence/backend.js.map +1 -0
- package/dist/agent/merchant/intelligence/default-backend.d.ts +38 -0
- package/dist/agent/merchant/intelligence/default-backend.js +477 -0
- package/dist/agent/merchant/intelligence/default-backend.js.map +1 -0
- package/dist/agent/merchant/intelligence/http-analytics-source.d.ts +55 -0
- package/dist/agent/merchant/intelligence/http-analytics-source.js +193 -0
- package/dist/agent/merchant/intelligence/http-analytics-source.js.map +1 -0
- package/dist/agent/merchant/intelligence/types.d.ts +109 -0
- package/dist/agent/merchant/intelligence/types.js +2 -0
- package/dist/agent/merchant/intelligence/types.js.map +1 -0
- package/dist/agent/merchant/merchant-client.d.ts +50 -1
- package/dist/agent/merchant/merchant-client.js +157 -8
- package/dist/agent/merchant/merchant-client.js.map +1 -1
- package/dist/agent/merchant/merchant-enrichment.d.ts +10 -0
- package/dist/agent/merchant/merchant-enrichment.js +38 -0
- package/dist/agent/merchant/merchant-enrichment.js.map +1 -0
- package/dist/agent/merchant/merchant-grounding.d.ts +5 -0
- package/dist/agent/merchant/merchant-grounding.js +76 -0
- package/dist/agent/merchant/merchant-grounding.js.map +1 -0
- package/dist/agent/merchant/merchant-presentations.d.ts +8 -0
- package/dist/agent/merchant/merchant-presentations.js +207 -0
- package/dist/agent/merchant/merchant-presentations.js.map +1 -0
- package/dist/agent/merchant/merchant-tools.d.ts +8 -0
- package/dist/agent/merchant/merchant-tools.js +291 -178
- package/dist/agent/merchant/merchant-tools.js.map +1 -1
- package/dist/agent/merchant/types.d.ts +6 -2
- package/dist/agent/merchant/types.js +45 -0
- package/dist/agent/merchant/types.js.map +1 -1
- package/dist/agent/presentation/registry.d.ts +14 -0
- package/dist/agent/presentation/registry.js +24 -0
- package/dist/agent/presentation/registry.js.map +1 -0
- package/dist/agent/presentation/runner.d.ts +11 -0
- package/dist/agent/presentation/runner.js +17 -0
- package/dist/agent/presentation/runner.js.map +1 -0
- package/dist/agent/presentation/sanitize.d.ts +16 -0
- package/dist/agent/presentation/sanitize.js +37 -0
- package/dist/agent/presentation/sanitize.js.map +1 -0
- package/dist/agent/presentation/types.d.ts +28 -0
- package/dist/agent/presentation/types.js +8 -0
- package/dist/agent/presentation/types.js.map +1 -0
- package/dist/agent/session.d.ts +1 -1
- package/dist/agent/session.js +2 -2
- package/dist/agent/session.js.map +1 -1
- package/dist/agent/skills/registry.d.ts +11 -0
- package/dist/agent/skills/registry.js +119 -0
- package/dist/agent/skills/registry.js.map +1 -0
- package/dist/agent/skills/tools.d.ts +6 -0
- package/dist/agent/skills/tools.js +36 -0
- package/dist/agent/skills/tools.js.map +1 -0
- package/dist/agent/skills/types.d.ts +9 -0
- package/dist/agent/skills/types.js +2 -0
- package/dist/agent/skills/types.js.map +1 -0
- package/dist/agent/supplier/scheduler.d.ts +2 -0
- package/dist/agent/supplier/scheduler.js +101 -22
- package/dist/agent/supplier/scheduler.js.map +1 -1
- package/dist/agent/supplier/store.d.ts +2 -0
- package/dist/agent/supplier/store.js +44 -0
- package/dist/agent/supplier/store.js.map +1 -1
- package/dist/agent/system-prompt.d.ts +5 -1
- package/dist/agent/system-prompt.js +20 -3
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/auth/merchant-authorization.d.ts +49 -0
- package/dist/auth/merchant-authorization.js +61 -0
- package/dist/auth/merchant-authorization.js.map +1 -0
- package/dist/auth/merchant-oauth.d.ts +255 -0
- package/dist/auth/merchant-oauth.js +764 -0
- package/dist/auth/merchant-oauth.js.map +1 -0
- package/dist/auth/merchant-pairing.d.ts +62 -0
- package/dist/auth/merchant-pairing.js +199 -0
- package/dist/auth/merchant-pairing.js.map +1 -0
- package/dist/auth/merchant-sessions.d.ts +71 -0
- package/dist/auth/merchant-sessions.js +168 -0
- package/dist/auth/merchant-sessions.js.map +1 -0
- package/dist/buyer-core/build-service.d.ts +5 -0
- package/dist/buyer-core/build-service.js +12 -0
- package/dist/buyer-core/build-service.js.map +1 -1
- package/dist/buyer-core/errors.d.ts +1 -1
- package/dist/buyer-core/errors.js +2 -0
- package/dist/buyer-core/errors.js.map +1 -1
- package/dist/buyer-core/merchant-index.d.ts +19 -4
- package/dist/buyer-core/merchant-index.js +137 -16
- package/dist/buyer-core/merchant-index.js.map +1 -1
- package/dist/buyer-core/negotiator.d.ts +0 -10
- package/dist/buyer-core/negotiator.js +12 -1
- package/dist/buyer-core/negotiator.js.map +1 -1
- package/dist/buyer-core/quote-fetcher.js +2 -1
- package/dist/buyer-core/quote-fetcher.js.map +1 -1
- package/dist/buyer-core/service.d.ts +104 -0
- package/dist/buyer-core/service.js +105 -1
- package/dist/buyer-core/service.js.map +1 -1
- package/dist/cli.d.ts +4 -2
- package/dist/cli.js +644 -32
- package/dist/cli.js.map +1 -1
- package/dist/config/profile.d.ts +51 -0
- package/dist/config/profile.js +185 -61
- package/dist/config/profile.js.map +1 -1
- package/dist/demo/demo-runner.js +12 -9
- package/dist/demo/demo-runner.js.map +1 -1
- package/dist/discovery/catalog-source/buyer-follows.d.ts +82 -0
- package/dist/discovery/catalog-source/buyer-follows.js +228 -0
- package/dist/discovery/catalog-source/buyer-follows.js.map +1 -0
- package/dist/discovery/catalog-source/connector-identity.d.ts +75 -0
- package/dist/discovery/catalog-source/connector-identity.js +175 -0
- package/dist/discovery/catalog-source/connector-identity.js.map +1 -0
- package/dist/discovery/catalog-source/errors.d.ts +1 -1
- package/dist/discovery/catalog-source/errors.js +2 -0
- package/dist/discovery/catalog-source/errors.js.map +1 -1
- package/dist/discovery/catalog-source/kiwi-record.d.ts +2 -0
- package/dist/discovery/catalog-source/kiwi-record.js.map +1 -1
- package/dist/discovery/catalog-source/kiwi-source.js +2 -0
- package/dist/discovery/catalog-source/kiwi-source.js.map +1 -1
- package/dist/discovery/catalog-source/merchant-publications.d.ts +65 -0
- package/dist/discovery/catalog-source/merchant-publications.js +207 -0
- package/dist/discovery/catalog-source/merchant-publications.js.map +1 -0
- package/dist/discovery/catalog-source/register.d.ts +24 -0
- package/dist/discovery/catalog-source/register.js +46 -0
- package/dist/discovery/catalog-source/register.js.map +1 -1
- package/dist/discovery/catalog-source/source.js +2 -1
- package/dist/discovery/catalog-source/source.js.map +1 -1
- package/dist/fs/atomic-write.d.ts +18 -0
- package/dist/fs/atomic-write.js +29 -0
- package/dist/fs/atomic-write.js.map +1 -0
- package/dist/handoff/authorization.js +1 -1
- package/dist/http/merchant-server.d.ts +65 -0
- package/dist/http/merchant-server.js +346 -0
- package/dist/http/merchant-server.js.map +1 -0
- package/dist/http/server.js +4 -1
- package/dist/http/server.js.map +1 -1
- package/dist/mcp/cli.d.ts +2 -0
- package/dist/mcp/cli.js +7 -6
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/merchant-auth.d.ts +78 -0
- package/dist/mcp/merchant-auth.js +146 -0
- package/dist/mcp/merchant-auth.js.map +1 -0
- package/dist/mcp/merchant-dirs.d.ts +26 -0
- package/dist/mcp/merchant-dirs.js +44 -0
- package/dist/mcp/merchant-dirs.js.map +1 -0
- package/dist/mcp/merchant-resources.d.ts +38 -0
- package/dist/mcp/merchant-resources.js +146 -0
- package/dist/mcp/merchant-resources.js.map +1 -0
- package/dist/mcp/merchant-server.d.ts +106 -0
- package/dist/mcp/merchant-server.js +615 -0
- package/dist/mcp/merchant-server.js.map +1 -0
- package/dist/mcp/merchant-tools.d.ts +103 -0
- package/dist/mcp/merchant-tools.js +517 -0
- package/dist/mcp/merchant-tools.js.map +1 -0
- package/dist/mcp/server.js +4 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts +5 -3
- package/dist/mcp/tools.js +102 -5
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts +1 -1
- package/dist/mcp/types.js +4 -0
- package/dist/mcp/types.js.map +1 -1
- package/dist/merchant/ops.d.ts +0 -11
- package/dist/merchant/ops.js +13 -1
- package/dist/merchant/ops.js.map +1 -1
- package/dist/merchant/stats-store.d.ts +7 -0
- package/dist/merchant/stats-store.js +11 -0
- package/dist/merchant/stats-store.js.map +1 -1
- package/dist/merchant/workbench-service.d.ts +189 -0
- package/dist/merchant/workbench-service.js +417 -0
- package/dist/merchant/workbench-service.js.map +1 -0
- package/dist/merchant-admin/pending-page.d.ts +35 -0
- package/dist/merchant-admin/pending-page.js +69 -0
- package/dist/merchant-admin/pending-page.js.map +1 -0
- package/dist/merchant-core/commands.d.ts +99 -0
- package/dist/merchant-core/commands.js +179 -0
- package/dist/merchant-core/commands.js.map +1 -0
- package/dist/merchant-core/executor.d.ts +69 -0
- package/dist/merchant-core/executor.js +218 -0
- package/dist/merchant-core/executor.js.map +1 -0
- package/dist/merchant-core/negotiation-adapters.d.ts +55 -0
- package/dist/merchant-core/negotiation-adapters.js +55 -0
- package/dist/merchant-core/negotiation-adapters.js.map +1 -0
- package/dist/merchant-core/network-checks.d.ts +65 -0
- package/dist/merchant-core/network-checks.js +111 -0
- package/dist/merchant-core/network-checks.js.map +1 -0
- package/dist/merchant-core/operations.d.ts +64 -0
- package/dist/merchant-core/operations.js +136 -0
- package/dist/merchant-core/operations.js.map +1 -0
- package/dist/merchant-core/policy-runtime.d.ts +70 -0
- package/dist/merchant-core/policy-runtime.js +181 -0
- package/dist/merchant-core/policy-runtime.js.map +1 -0
- package/dist/merchant-core/product-import.d.ts +67 -0
- package/dist/merchant-core/product-import.js +160 -0
- package/dist/merchant-core/product-import.js.map +1 -0
- package/dist/merchant-core/service.d.ts +203 -0
- package/dist/merchant-core/service.js +402 -0
- package/dist/merchant-core/service.js.map +1 -0
- package/dist/merchant-gateway/catalog-publications.d.ts +71 -0
- package/dist/merchant-gateway/catalog-publications.js +175 -0
- package/dist/merchant-gateway/catalog-publications.js.map +1 -0
- package/dist/merchant-gateway/catalog-tools.d.ts +46 -0
- package/dist/merchant-gateway/catalog-tools.js +277 -0
- package/dist/merchant-gateway/catalog-tools.js.map +1 -0
- package/dist/merchant-gateway/cli.d.ts +75 -0
- package/dist/merchant-gateway/cli.js +458 -0
- package/dist/merchant-gateway/cli.js.map +1 -0
- package/dist/merchant-gateway/credential-vault.d.ts +44 -0
- package/dist/merchant-gateway/credential-vault.js +111 -0
- package/dist/merchant-gateway/credential-vault.js.map +1 -0
- package/dist/merchant-gateway/entry-server.d.ts +117 -0
- package/dist/merchant-gateway/entry-server.js +710 -0
- package/dist/merchant-gateway/entry-server.js.map +1 -0
- package/dist/merchant-gateway/instance-registration.d.ts +143 -0
- package/dist/merchant-gateway/instance-registration.js +382 -0
- package/dist/merchant-gateway/instance-registration.js.map +1 -0
- package/dist/merchant-gateway/instance-tools.d.ts +58 -0
- package/dist/merchant-gateway/instance-tools.js +184 -0
- package/dist/merchant-gateway/instance-tools.js.map +1 -0
- package/dist/merchant-gateway/mcp-proxy.d.ts +25 -0
- package/dist/merchant-gateway/mcp-proxy.js +192 -0
- package/dist/merchant-gateway/mcp-proxy.js.map +1 -0
- package/dist/merchant-gateway/pinned-fetch.d.ts +52 -0
- package/dist/merchant-gateway/pinned-fetch.js +212 -0
- package/dist/merchant-gateway/pinned-fetch.js.map +1 -0
- package/dist/merchant-gateway/tenant-registry.d.ts +82 -0
- package/dist/merchant-gateway/tenant-registry.js +190 -0
- package/dist/merchant-gateway/tenant-registry.js.map +1 -0
- package/dist/merchant-gateway/tool-bundle.d.ts +26 -0
- package/dist/merchant-gateway/tool-bundle.js +56 -0
- package/dist/merchant-gateway/tool-bundle.js.map +1 -0
- package/dist/merchant-runtime/backup.d.ts +48 -0
- package/dist/merchant-runtime/backup.js +226 -0
- package/dist/merchant-runtime/backup.js.map +1 -0
- package/dist/merchant-runtime/health.d.ts +99 -0
- package/dist/merchant-runtime/health.js +206 -0
- package/dist/merchant-runtime/health.js.map +1 -0
- package/dist/merchant-runtime/jobs.d.ts +64 -0
- package/dist/merchant-runtime/jobs.js +107 -0
- package/dist/merchant-runtime/jobs.js.map +1 -0
- package/dist/merchant-runtime/manager.d.ts +94 -0
- package/dist/merchant-runtime/manager.js +305 -0
- package/dist/merchant-runtime/manager.js.map +1 -0
- package/dist/merchant-runtime/services.d.ts +30 -0
- package/dist/merchant-runtime/services.js +51 -0
- package/dist/merchant-runtime/services.js.map +1 -0
- package/dist/negotiation/action-candidate.js +1 -1
- package/dist/net/url.d.ts +26 -0
- package/dist/net/url.js +32 -0
- package/dist/net/url.js.map +1 -0
- package/dist/operator/types.d.ts +1 -1
- package/dist/product-cli.d.ts +2 -2
- package/dist/product-cli.js +27 -6
- package/dist/product-cli.js.map +1 -1
- package/dist/product-compat.d.ts +9 -1
- package/dist/product-compat.js +12 -1
- package/dist/product-compat.js.map +1 -1
- package/dist/product-init.js +17 -5
- package/dist/product-init.js.map +1 -1
- package/dist/product-publish.js +2 -1
- package/dist/product-publish.js.map +1 -1
- package/dist/product-supplier.d.ts +73 -0
- package/dist/product-supplier.js +224 -5
- package/dist/product-supplier.js.map +1 -1
- package/dist/runtime/fake-model.d.ts +1 -1
- package/dist/trust/identity/jwk.d.ts +11 -0
- package/dist/trust/identity/jwk.js +2 -0
- package/dist/trust/identity/jwk.js.map +1 -0
- package/dist/trust/identity/jws.d.ts +2 -1
- package/dist/trust/identity/jws.js.map +1 -1
- package/dist/trust/identity/keys.d.ts +2 -1
- package/dist/trust/identity/keys.js.map +1 -1
- package/dist/trust/identity/message-signature.d.ts +1 -14
- package/dist/trust/identity/message-signature.js.map +1 -1
- package/dist/weixin/channel.d.ts +3 -0
- package/dist/weixin/channel.js +18 -2
- package/dist/weixin/channel.js.map +1 -1
- package/examples/profiles/merchant.deepseek.yaml +6 -0
- package/examples/profiles/merchant.fake.yaml +6 -0
- package/examples/profiles/merchant.local.yaml +6 -0
- package/package.json +9 -7
- package/skills/kiwi-buyer/SKILL.md +68 -0
- package/skills/merchant/catalog-operations/SKILL.md +17 -0
- package/skills/merchant/change-approval/SKILL.md +16 -0
- package/skills/merchant/human-review/SKILL.md +17 -0
- package/skills/merchant/inventory-operations/SKILL.md +17 -0
- package/skills/merchant/negotiation-review/SKILL.md +18 -0
- package/skills/merchant/performance-insights/SKILL.md +19 -0
- package/contracts/candidate-agent-dto-1.0.README.md +0 -43
- package/contracts/conformance/python-service/README.md +0 -19
package/dist/cli.js
CHANGED
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
* Managed-local product lifecycle.
|
|
29
29
|
*/
|
|
30
30
|
import { spawn, spawnSync } from "node:child_process";
|
|
31
|
-
import {
|
|
31
|
+
import { DatabaseSync } from "node:sqlite";
|
|
32
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync, } from "node:fs";
|
|
32
33
|
import { homedir, userInfo } from "node:os";
|
|
33
34
|
import path from "node:path";
|
|
34
35
|
import { fileURLToPath } from "node:url";
|
|
@@ -52,19 +53,86 @@ import { runTui } from "./operator/tui.js";
|
|
|
52
53
|
import { runInit } from "./supervisor/init.js";
|
|
53
54
|
import { runMcpServe } from "./mcp/cli.js";
|
|
54
55
|
import { runHttpServe } from "./http/cli.js";
|
|
56
|
+
import { runGatewayServe } from "./merchant-gateway/cli.js";
|
|
57
|
+
import { DEFAULT_MERCHANT_MCP_HOST, DEFAULT_MERCHANT_MCP_PATH, DEFAULT_MERCHANT_MCP_PORT, startMerchantMcpServer, } from "./mcp/merchant-server.js";
|
|
58
|
+
import { assertMerchantMcpAuthPolicy, CompositeMerchantMcpVerifier, PairedCredentialVerifier, resolveMerchantMcpVerifier, } from "./mcp/merchant-auth.js";
|
|
59
|
+
import { resolveMerchantMcpDirs } from "./mcp/merchant-dirs.js";
|
|
60
|
+
import { MerchantOAuthServer, MerchantOAuthStore } from "./auth/merchant-oauth.js";
|
|
61
|
+
import { MerchantOAuthVerifier } from "./auth/merchant-authorization.js";
|
|
62
|
+
import { isLoopbackHost } from "./a2a/client/url-policy.js";
|
|
63
|
+
import { MerchantRuntimeManager } from "./merchant-runtime/manager.js";
|
|
64
|
+
import { buildRuntimeServiceSpecs } from "./merchant-runtime/services.js";
|
|
65
|
+
import { collectMerchantHealth } from "./merchant-runtime/health.js";
|
|
66
|
+
import { MerchantJobs, standardMerchantJobs } from "./merchant-runtime/jobs.js";
|
|
67
|
+
import { runBackup } from "./merchant-runtime/backup.js";
|
|
68
|
+
import { MerchantCoreService } from "./merchant-core/service.js";
|
|
69
|
+
import { MerchantPolicyRuntime } from "./merchant-core/policy-runtime.js";
|
|
70
|
+
import { buildMerchantPresentationResources } from "./mcp/merchant-resources.js";
|
|
71
|
+
import { merchantAdminSurface } from "./merchant-admin/pending-page.js";
|
|
72
|
+
import { MerchantAdminSessions, writeAdminCredentials } from "./auth/merchant-sessions.js";
|
|
73
|
+
import { createPairingCode, revokePairedCredential } from "./auth/merchant-pairing.js";
|
|
74
|
+
import { MerchantOperationStore } from "./merchant-core/operations.js";
|
|
75
|
+
/** 读能力探测落盘记录的 listing_pause(F08 接线;记录缺失/损坏 → undefined 不判定)。 */
|
|
76
|
+
function probeCapabilitiesListingPause(dataDir) {
|
|
77
|
+
try {
|
|
78
|
+
const probe = JSON.parse(readFileSync(path.join(dataDir, "capability-probe.json"), "utf8"));
|
|
79
|
+
return probe.capabilities?.listing_pause;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function readRegistrationStatus(dataDir) {
|
|
86
|
+
try {
|
|
87
|
+
const value = JSON.parse(readFileSync(path.join(dataDir, "registration.json"), "utf8"));
|
|
88
|
+
return {
|
|
89
|
+
ok: value.ok === true,
|
|
90
|
+
...(typeof value.error === "string" ? { error: value.error } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function countPendingMerchantCommands(dataDir) {
|
|
98
|
+
try {
|
|
99
|
+
const db = new DatabaseSync(path.join(dataDir, "state.sqlite"));
|
|
100
|
+
try {
|
|
101
|
+
const row = db
|
|
102
|
+
.prepare("SELECT COUNT(*) AS count FROM action_candidates WHERE status = 'pending_approval'")
|
|
103
|
+
.get();
|
|
104
|
+
return typeof row?.count === "number" ? row.count : 0;
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
db.close();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
import { agentDirName, DEFAULT_AGENTS_ROOT, ensurePathsForDir, openAgentDatabase } from "./agent/agent-db.js";
|
|
115
|
+
import { writeFileAtomic } from "./fs/atomic-write.js";
|
|
116
|
+
import { WriteApprovalCandidateStore } from "./agent/merchant/action-candidate.js";
|
|
117
|
+
import { ProfileCredentialBroker } from "./agent/merchant/credential-broker.js";
|
|
118
|
+
import { FakeMerchantClient, fakeMerchantProduct } from "./agent/merchant/fake-merchant-client.js";
|
|
119
|
+
import { HttpMerchantClient } from "./agent/merchant/merchant-client.js";
|
|
120
|
+
import { DefaultMerchantIntelligenceBackend } from "./agent/merchant/intelligence/default-backend.js";
|
|
121
|
+
import { MemoryStore } from "./agent/memory/store.js";
|
|
122
|
+
import { PrivateVault } from "./agent/memory/vault.js";
|
|
55
123
|
import { runDown, runStatus, runUp, SupervisorError } from "./supervisor/manage.js";
|
|
56
124
|
import { parseLogLines, runLogs } from "./supervisor/logs.js";
|
|
57
125
|
import { StackConfigError } from "./supervisor/stack-config.js";
|
|
58
126
|
import { cmdProductDoctor, productHelp, PRODUCT_VERSION, DEFAULT_CATALOG_URL, DEFAULT_PROFILE_PATH, } from "./product-cli.js";
|
|
59
127
|
import { cmdWeixin, weixinUsage } from "./weixin/cli-weixin.js";
|
|
60
|
-
import { DEFAULT_SHOPPING_DB_PATH, loadMerchantCredentials, merchantInit, slugifyMerchantId } from "./product-init.js";
|
|
128
|
+
import { DEFAULT_SHOPPING_DB_PATH, loadMerchantCredentials, merchantInit, slugifyMerchantId, } from "./product-init.js";
|
|
61
129
|
import readline from "node:readline";
|
|
62
130
|
import { buyerInit, buyerSearch, buyerTasks } from "./product-buyer.js";
|
|
63
|
-
import { supplierList, supplierPause, supplierPrefer, supplierRemove, supplierSave, supplierWatch, } from "./product-supplier.js";
|
|
131
|
+
import { supplierList, supplierMetrics, supplierPause, supplierPrefer, supplierRemove, supplierSave, supplierWatch, } from "./product-supplier.js";
|
|
64
132
|
import { merchantStats } from "./product-merchant.js";
|
|
65
133
|
import { merchantPublish } from "./product-publish.js";
|
|
66
134
|
import { catalogServe } from "./product-catalog.js";
|
|
67
|
-
import { extractPublicDomain, runMerchantSetupPublic, SetupPublicError, validatePublicDomain } from "./product-setup-public.js";
|
|
135
|
+
import { extractPublicDomain, runMerchantSetupPublic, SetupPublicError, validatePublicDomain, } from "./product-setup-public.js";
|
|
68
136
|
const USAGE = `kiwi ${PRODUCT_VERSION} — commerce negotiation agent runtime
|
|
69
137
|
|
|
70
138
|
Usage:
|
|
@@ -652,10 +720,12 @@ async function cmdTui(args) {
|
|
|
652
720
|
* 覆盖 Agent Card / UCP / catalog 注册广告的公网地址(配合 Caddy/Nginx 反代)。
|
|
653
721
|
*/
|
|
654
722
|
/** 审查 P1-09:agent serve 的 A2A 状态目录。缺省用稳定路径
|
|
655
|
-
* `<cwd>/.kiwi/agents
|
|
656
|
-
*
|
|
723
|
+
* `<cwd>/.kiwi/agents/<消毒后 agent_id>`——与写端(merchant mcp serve 的
|
|
724
|
+
* policy-overrides / agent-db)同一消毒口径(审查 P2:此前读端用原始
|
|
725
|
+
* agent_id、写端消毒,非 ASCII agent_id 下策略热更新对 A2A 静默不生效);
|
|
726
|
+
* 重启后 Ledger/幂等/终态可恢复;显式 --data-dir 覆盖。绝不回退到临时目录。 */
|
|
657
727
|
export function resolveServeDataDir(dataDir, agentId) {
|
|
658
|
-
return dataDir ?? path.resolve(
|
|
728
|
+
return dataDir ?? path.resolve(DEFAULT_AGENTS_ROOT, agentDirName(agentId));
|
|
659
729
|
}
|
|
660
730
|
async function cmdAgentServe(args) {
|
|
661
731
|
const profile = requireProfileOrDefault(args);
|
|
@@ -671,12 +741,20 @@ async function cmdAgentServe(args) {
|
|
|
671
741
|
// negotiation 可重开、已发 conditional offer 返回 offer_unknown。稳定目录
|
|
672
742
|
// 让重启可恢复状态(resolveServeDataDir 见上)。
|
|
673
743
|
const serveDataDir = resolveServeDataDir(args.dataDir, profile.agent_id);
|
|
744
|
+
// BUG-07:A2A 进程的策略读端——与 MCP 写端共享 <dataDir>/policy-overrides.json,
|
|
745
|
+
// 每次报价 stat 文件 mtime,变化才重载(策略热更新跨进程立即生效)。
|
|
746
|
+
const policyRuntime = new MerchantPolicyRuntime({
|
|
747
|
+
basePolicy: profile.merchant_policy,
|
|
748
|
+
file: path.join(serveDataDir, "policy-overrides.json"),
|
|
749
|
+
now: () => new Date().toISOString(),
|
|
750
|
+
});
|
|
674
751
|
const profilePublicUrl = profile.merchant_public?.public_url;
|
|
675
752
|
let node = await startA2aNode({
|
|
676
753
|
profile,
|
|
677
754
|
catalog,
|
|
678
755
|
preferredPort: args.port ?? profile.merchant_public?.a2a_port,
|
|
679
756
|
dataDir: serveDataDir,
|
|
757
|
+
merchantPolicy: () => policyRuntime.current().policy,
|
|
680
758
|
...(profilePublicUrl ? { publicBaseUrl: `https://${profilePublicUrl}` } : {}),
|
|
681
759
|
...(merchantToken ? { ownerToken: merchantToken } : {}),
|
|
682
760
|
ownerTokenSecret: process.env.KIWI_CATALOG_OWNER_TOKEN_SECRET,
|
|
@@ -700,6 +778,7 @@ async function cmdAgentServe(args) {
|
|
|
700
778
|
catalog,
|
|
701
779
|
preferredPort: args.port,
|
|
702
780
|
dataDir: serveDataDir,
|
|
781
|
+
merchantPolicy: () => policyRuntime.current().policy,
|
|
703
782
|
...(merchantToken ? { ownerToken: merchantToken } : {}),
|
|
704
783
|
ownerTokenSecret: process.env.KIWI_CATALOG_OWNER_TOKEN_SECRET,
|
|
705
784
|
});
|
|
@@ -741,7 +820,14 @@ async function cmdAgentServe(args) {
|
|
|
741
820
|
process.on("SIGTERM", shutdown);
|
|
742
821
|
console.log("[agent serve] A2A server + 内置对话(/quit 退出整个进程)");
|
|
743
822
|
try {
|
|
744
|
-
return await runChatTui({
|
|
823
|
+
return await runChatTui({
|
|
824
|
+
kernel,
|
|
825
|
+
input: process.stdin,
|
|
826
|
+
output: process.stdout,
|
|
827
|
+
reload,
|
|
828
|
+
a2aNode,
|
|
829
|
+
catalog,
|
|
830
|
+
});
|
|
745
831
|
}
|
|
746
832
|
finally {
|
|
747
833
|
await a2aNode.stop();
|
|
@@ -961,7 +1047,10 @@ async function cmdBuyerSupplier(args) {
|
|
|
961
1047
|
return EXIT.CONFIG;
|
|
962
1048
|
}
|
|
963
1049
|
if (action === "save") {
|
|
964
|
-
printJson({
|
|
1050
|
+
printJson({
|
|
1051
|
+
ok: true,
|
|
1052
|
+
relationship: await supplierSave({ ...base, merchantId: id, catalogUrl: catalog }),
|
|
1053
|
+
});
|
|
965
1054
|
}
|
|
966
1055
|
else if (action === "watch") {
|
|
967
1056
|
const intervalSeconds = args.interval !== undefined ? Number(args.interval) : undefined;
|
|
@@ -978,6 +1067,7 @@ async function cmdBuyerSupplier(args) {
|
|
|
978
1067
|
...(args.query !== undefined ? { query: args.query } : {}),
|
|
979
1068
|
...(args.region !== undefined ? { region: args.region } : {}),
|
|
980
1069
|
...(intervalSeconds !== undefined ? { intervalSeconds } : {}),
|
|
1070
|
+
...(args.expires !== undefined ? { expires: args.expires } : {}),
|
|
981
1071
|
...consent,
|
|
982
1072
|
}),
|
|
983
1073
|
});
|
|
@@ -1002,6 +1092,10 @@ async function cmdBuyerSupplier(args) {
|
|
|
1002
1092
|
printJson({ ok: true, count: relationships.length, relationships });
|
|
1003
1093
|
return EXIT.OK;
|
|
1004
1094
|
}
|
|
1095
|
+
if (action === "metrics") {
|
|
1096
|
+
printJson({ ok: true, metrics: await supplierMetrics(base) });
|
|
1097
|
+
return EXIT.OK;
|
|
1098
|
+
}
|
|
1005
1099
|
if (action === "pause" || action === "remove") {
|
|
1006
1100
|
if (id === undefined || id === "") {
|
|
1007
1101
|
process.stderr.write(`supplier ${action} 需要 <relationship-id>\n`);
|
|
@@ -1013,7 +1107,7 @@ async function cmdBuyerSupplier(args) {
|
|
|
1013
1107
|
printJson({ ok: true, relationship });
|
|
1014
1108
|
return EXIT.OK;
|
|
1015
1109
|
}
|
|
1016
|
-
process.stderr.write(`unknown supplier command: ${action ?? ""}(save|watch|prefer|list|pause|remove)\n`);
|
|
1110
|
+
process.stderr.write(`unknown supplier command: ${action ?? ""}(save|watch|prefer|list|metrics|pause|remove)\n`);
|
|
1017
1111
|
return EXIT.CONFIG;
|
|
1018
1112
|
}
|
|
1019
1113
|
catch (err) {
|
|
@@ -1098,6 +1192,10 @@ async function routeMerchant(sub, args) {
|
|
|
1098
1192
|
}
|
|
1099
1193
|
if (sub === "start")
|
|
1100
1194
|
return await cmdAgentServe(args);
|
|
1195
|
+
if (sub === "mcp")
|
|
1196
|
+
return await cmdMerchantMcp(args);
|
|
1197
|
+
if (sub === "runtime")
|
|
1198
|
+
return await cmdMerchantRuntime(args);
|
|
1101
1199
|
if (sub === "init")
|
|
1102
1200
|
return await cmdMerchantInit(args);
|
|
1103
1201
|
if (sub === "publish")
|
|
@@ -1117,6 +1215,482 @@ async function routeMerchant(sub, args) {
|
|
|
1117
1215
|
process.stderr.write(`unknown merchant command: ${sub}\n`);
|
|
1118
1216
|
return EXIT.CONFIG;
|
|
1119
1217
|
}
|
|
1218
|
+
/**
|
|
1219
|
+
* `kiwi merchant runtime start|stop|status|health`:Merchant 实例运行时管理
|
|
1220
|
+
* (V2 阶段一 src/merchant-runtime/)。A2A 接待服务与 MCP 管理服务作为受管
|
|
1221
|
+
* 子进程,异常退出自动重启(退避);停止 A2A 后本管理入口可重启它。
|
|
1222
|
+
*
|
|
1223
|
+
* kiwi merchant runtime start [--profile <file>] [--data-dir <dir>] 前台监控(Ctrl+C 停全部)
|
|
1224
|
+
* kiwi merchant runtime stop 停全部受管服务
|
|
1225
|
+
* kiwi merchant runtime status 进程状态(JSON)
|
|
1226
|
+
* kiwi merchant runtime health 分项健康报告(JSON)
|
|
1227
|
+
*/
|
|
1228
|
+
async function cmdMerchantRuntime(args) {
|
|
1229
|
+
const action = args.command[2];
|
|
1230
|
+
if (action === undefined || !["start", "stop", "status", "health"].includes(action)) {
|
|
1231
|
+
process.stderr.write("usage: kiwi merchant runtime <start|stop|status|health> [--profile <file>] [--data-dir <dir>]\n");
|
|
1232
|
+
return EXIT.CONFIG;
|
|
1233
|
+
}
|
|
1234
|
+
const profile = requireProfileOrDefault(args);
|
|
1235
|
+
if (profile.role !== "merchant") {
|
|
1236
|
+
process.stderr.write("kiwi merchant runtime 需要 merchant profile(role: merchant)\n");
|
|
1237
|
+
return EXIT.CONFIG;
|
|
1238
|
+
}
|
|
1239
|
+
const dirs = resolveMerchantMcpDirs({
|
|
1240
|
+
...(args.dataDir !== undefined ? { dataDir: args.dataDir } : {}),
|
|
1241
|
+
agentId: profile.agent_id,
|
|
1242
|
+
});
|
|
1243
|
+
const cliEntry = fileURLToPath(import.meta.url);
|
|
1244
|
+
const profileArgs = args.profile !== undefined ? ["--profile", args.profile] : [];
|
|
1245
|
+
const manager = new MerchantRuntimeManager({
|
|
1246
|
+
dir: dirs.merchantDataDir,
|
|
1247
|
+
// BUG-04:子进程显式携带统一 --data-dir 与确定 cwd(三槽位解析结果)。
|
|
1248
|
+
services: buildRuntimeServiceSpecs({ dirs, cliEntry, profileArgs }),
|
|
1249
|
+
});
|
|
1250
|
+
if (action === "status") {
|
|
1251
|
+
process.stdout.write(`${JSON.stringify(manager.status(), null, 2)}\n`);
|
|
1252
|
+
return EXIT.OK;
|
|
1253
|
+
}
|
|
1254
|
+
if (action === "health") {
|
|
1255
|
+
const registration = readRegistrationStatus(dirs.merchantDataDir);
|
|
1256
|
+
const pendingCommands = countPendingMerchantCommands(dirs.merchantDataDir);
|
|
1257
|
+
const report = collectMerchantHealth({
|
|
1258
|
+
dataDir: dirs.merchantDataDir,
|
|
1259
|
+
services: manager.status(),
|
|
1260
|
+
...(registration !== undefined ? { registration } : {}),
|
|
1261
|
+
...(pendingCommands !== undefined ? { pendingCommands } : {}),
|
|
1262
|
+
});
|
|
1263
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
1264
|
+
return report.ok ? EXIT.OK : EXIT.TRANSIENT;
|
|
1265
|
+
}
|
|
1266
|
+
if (action === "stop") {
|
|
1267
|
+
for (const state of [...manager.status()].reverse()) {
|
|
1268
|
+
await manager.stop(state.name);
|
|
1269
|
+
}
|
|
1270
|
+
process.stdout.write("[merchant runtime] 全部受管服务已停止\n");
|
|
1271
|
+
return EXIT.OK;
|
|
1272
|
+
}
|
|
1273
|
+
// start:前台监控,异常退出自动重启;Ctrl+C 优雅关闭全部。
|
|
1274
|
+
// 周期任务(V2 阶段四 + BUG-05):健康轮询先**实时**执行能力探测(不只读
|
|
1275
|
+
// 启动时落盘的旧记录),再产出健康报告;持续备份。
|
|
1276
|
+
// fake provider(离线演示)无真实上游:探测写 ok 桩并注明。
|
|
1277
|
+
const probeClient = isFakeProvider(profile)
|
|
1278
|
+
? undefined
|
|
1279
|
+
: new HttpMerchantClient(profile.commerce.base_url, new ProfileCredentialBroker(profile));
|
|
1280
|
+
const probePath = path.join(dirs.merchantDataDir, "capability-probe.json");
|
|
1281
|
+
const jobs = new MerchantJobs();
|
|
1282
|
+
for (const job of standardMerchantJobs({
|
|
1283
|
+
healthPoll: async () => {
|
|
1284
|
+
if (probeClient !== undefined) {
|
|
1285
|
+
await probeClient.probeCapabilities({ persistPath: probePath });
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
writeFileAtomic(probePath, `${JSON.stringify({ ok: true, version: "fake", probed_at: new Date().toISOString(), verdict: "compatible", capabilities: { catalog_read: true, catalog_write: true, inventory_write: true, listing_pause: true, resolve_review: true } }, null, 2)}\n`, { mode: 0o600 });
|
|
1289
|
+
}
|
|
1290
|
+
const registration = readRegistrationStatus(dirs.merchantDataDir);
|
|
1291
|
+
const pendingCommands = countPendingMerchantCommands(dirs.merchantDataDir);
|
|
1292
|
+
const report = collectMerchantHealth({
|
|
1293
|
+
dataDir: dirs.merchantDataDir,
|
|
1294
|
+
services: manager.status(),
|
|
1295
|
+
...(registration !== undefined ? { registration } : {}),
|
|
1296
|
+
...(pendingCommands !== undefined ? { pendingCommands } : {}),
|
|
1297
|
+
});
|
|
1298
|
+
// 审查 P2:原子写(health.json 被监控/一次性命令并发读,防撕裂读假告警)。
|
|
1299
|
+
writeFileAtomic(path.join(dirs.merchantDataDir, "runtime", "health.json"), `${JSON.stringify(report, null, 2)}\n`, { mode: 0o600 });
|
|
1300
|
+
},
|
|
1301
|
+
backupHook: async () => {
|
|
1302
|
+
runBackup({
|
|
1303
|
+
dataDir: dirs.merchantDataDir,
|
|
1304
|
+
backupsDir: path.join(dirs.merchantDataDir, "backups"),
|
|
1305
|
+
});
|
|
1306
|
+
},
|
|
1307
|
+
healthIntervalMs: 60_000,
|
|
1308
|
+
backupIntervalMs: 5 * 60_000,
|
|
1309
|
+
})) {
|
|
1310
|
+
jobs.register(job);
|
|
1311
|
+
}
|
|
1312
|
+
jobs.start();
|
|
1313
|
+
// Node.js 在没有活动 I/O 句柄时会退出;runtime manager 的受管子进程
|
|
1314
|
+
// 使用 detached + unref,单靠下面的 never-resolving Promise 不能维持
|
|
1315
|
+
// systemd 主进程。显式保活,确保 7×24 监控/备份/自动重启持续运行。
|
|
1316
|
+
const keepAlive = setInterval(() => undefined, 60_000);
|
|
1317
|
+
console.log("[merchant runtime] 监控 a2a + mcp(异常退出自动重启;健康轮询 + 持续备份;Ctrl+C 停全部)");
|
|
1318
|
+
await manager.supervise();
|
|
1319
|
+
const shutdown = async () => {
|
|
1320
|
+
clearInterval(keepAlive);
|
|
1321
|
+
jobs.stop();
|
|
1322
|
+
await manager.shutdown();
|
|
1323
|
+
process.exit(0);
|
|
1324
|
+
};
|
|
1325
|
+
process.on("SIGINT", shutdown);
|
|
1326
|
+
process.on("SIGTERM", shutdown);
|
|
1327
|
+
await new Promise(() => { });
|
|
1328
|
+
return EXIT.OK;
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* `kiwi merchant mcp serve`:Merchant Workbench 标准远程 MCP server
|
|
1332
|
+
* (WorkBuddy Buddy 应用开发计划 阶段二;官方 @modelcontextprotocol/sdk
|
|
1333
|
+
* streamableHttp,独立端口/路径,不复用 A2A 端点)。
|
|
1334
|
+
*
|
|
1335
|
+
* 单商家一服务(MVP):租户 = profile.owner_id。认证 = Bearer token
|
|
1336
|
+
* (merchant_mcp.token_env 指向的环境变量,缺省 KIWI_MERCHANT_MCP_TOKEN);
|
|
1337
|
+
* fail-closed:非 loopback 监听且未配置 token 时拒绝启动。
|
|
1338
|
+
*
|
|
1339
|
+
* 审批闭环(阶段四):draft 候选的执行钩子是进程级的——本进程生成的候选
|
|
1340
|
+
* 直接可批准执行;启动时为库内遗留的 draft_product_change 候选重建钩子
|
|
1341
|
+
* (recoverPendingDrafts),其他工具的遗留候选按 expireForRecovery 语义失效。
|
|
1342
|
+
*
|
|
1343
|
+
* kiwi merchant mcp serve [--profile <file>] [--host <host>] [--port N] [--data-dir <dir>]
|
|
1344
|
+
*/
|
|
1345
|
+
async function cmdMerchantMcp(args) {
|
|
1346
|
+
// `kiwi merchant mcp admin-passwd`:初始化/重置管理员口令(BUG-01 账户来源:
|
|
1347
|
+
// 单商家实例管理员 = 本实例 principal)。新口令只从环境变量读取
|
|
1348
|
+
// (KIWI_MERCHANT_ADMIN_PASSWORD),绝不出现在命令行/日志;落盘为 scrypt 哈希。
|
|
1349
|
+
if (args.command[2] === "admin-passwd") {
|
|
1350
|
+
const profile = requireProfileOrDefault(args);
|
|
1351
|
+
if (profile.role !== "merchant") {
|
|
1352
|
+
process.stderr.write("kiwi merchant mcp admin-passwd 需要 merchant profile\n");
|
|
1353
|
+
return EXIT.CONFIG;
|
|
1354
|
+
}
|
|
1355
|
+
const password = (process.env.KIWI_MERCHANT_ADMIN_PASSWORD ?? "").trim();
|
|
1356
|
+
if (password === "") {
|
|
1357
|
+
process.stderr.write("请通过环境变量 KIWI_MERCHANT_ADMIN_PASSWORD 提供新口令(至少 8 位;不要写在命令行)\n");
|
|
1358
|
+
return EXIT.CONFIG;
|
|
1359
|
+
}
|
|
1360
|
+
const dirs = resolveMerchantMcpDirs({
|
|
1361
|
+
...(args.dataDir !== undefined ? { dataDir: args.dataDir } : {}),
|
|
1362
|
+
agentId: profile.agent_id,
|
|
1363
|
+
});
|
|
1364
|
+
try {
|
|
1365
|
+
writeAdminCredentials(dirs.merchantDataDir, {
|
|
1366
|
+
principalId: profile.agent_id,
|
|
1367
|
+
merchantId: profile.owner_id,
|
|
1368
|
+
password,
|
|
1369
|
+
force: args.force,
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
catch (err) {
|
|
1373
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
1374
|
+
return EXIT.CONFIG;
|
|
1375
|
+
}
|
|
1376
|
+
process.stdout.write("管理员口令已设置(哈希落盘 admin-credentials.json,0600)\n");
|
|
1377
|
+
return EXIT.OK;
|
|
1378
|
+
}
|
|
1379
|
+
// `kiwi merchant mcp pair`:生成一次性配对码(§8.4 第二期)。商家把它填进
|
|
1380
|
+
// 商家连接器网关的绑定页,网关据此兑换内部凭据——不必把长期令牌贴进表单。
|
|
1381
|
+
if (args.command[2] === "pair") {
|
|
1382
|
+
const profile = requireProfileOrDefault(args);
|
|
1383
|
+
if (profile.role !== "merchant") {
|
|
1384
|
+
process.stderr.write("kiwi merchant mcp pair 需要 merchant profile(role: merchant)\n");
|
|
1385
|
+
return EXIT.CONFIG;
|
|
1386
|
+
}
|
|
1387
|
+
const dirs = resolveMerchantMcpDirs({
|
|
1388
|
+
...(args.dataDir !== undefined ? { dataDir: args.dataDir } : {}),
|
|
1389
|
+
agentId: profile.agent_id,
|
|
1390
|
+
});
|
|
1391
|
+
const { code, expiresAt } = createPairingCode(dirs.merchantDataDir);
|
|
1392
|
+
const publicUrl = profile.merchant_mcp?.public_url?.replace(/\/+$/, "");
|
|
1393
|
+
const port = args.port ?? profile.merchant_mcp?.port ?? DEFAULT_MERCHANT_MCP_PORT;
|
|
1394
|
+
const mcpUrl = publicUrl !== undefined ? `${publicUrl}/mcp` : `http://127.0.0.1:${port}/mcp`;
|
|
1395
|
+
const ttlMinutes = Math.max(1, Math.round((Date.parse(expiresAt) - Date.now()) / 60000));
|
|
1396
|
+
process.stdout.write([
|
|
1397
|
+
"一次性配对码已生成(本码只显示一次,用完或过期即失效):",
|
|
1398
|
+
"",
|
|
1399
|
+
` 实例 MCP 地址:${mcpUrl}`,
|
|
1400
|
+
` 配对码: ${code}`,
|
|
1401
|
+
` 有效期: 约 ${ttlMinutes} 分钟(至 ${expiresAt})`,
|
|
1402
|
+
"",
|
|
1403
|
+
"在商家连接器网关的实例绑定页(https://<网关域名>/instance)选择「用配对码绑定」,",
|
|
1404
|
+
"填入上面的地址与配对码即可;网关会用它兑换一份**新签发的**配对凭据"
|
|
1405
|
+
+ "(由本实例生成并持有,重配对即轮换),并加密保存。",
|
|
1406
|
+
"配对码只证明「你在实例所在机器上」,商家身份仍以网关侧的目录 OAuth 连接为准。",
|
|
1407
|
+
"",
|
|
1408
|
+
].join("\n"));
|
|
1409
|
+
return EXIT.OK;
|
|
1410
|
+
}
|
|
1411
|
+
// `kiwi merchant mcp unpair`:吊销配对凭据(网关随后的调用会 401,直到重新配对)。
|
|
1412
|
+
if (args.command[2] === "unpair") {
|
|
1413
|
+
const profile = requireProfileOrDefault(args);
|
|
1414
|
+
if (profile.role !== "merchant") {
|
|
1415
|
+
process.stderr.write("kiwi merchant mcp unpair 需要 merchant profile(role: merchant)\n");
|
|
1416
|
+
return EXIT.CONFIG;
|
|
1417
|
+
}
|
|
1418
|
+
const dirs = resolveMerchantMcpDirs({
|
|
1419
|
+
...(args.dataDir !== undefined ? { dataDir: args.dataDir } : {}),
|
|
1420
|
+
agentId: profile.agent_id,
|
|
1421
|
+
});
|
|
1422
|
+
const revoked = revokePairedCredential(dirs.merchantDataDir);
|
|
1423
|
+
process.stdout.write(revoked
|
|
1424
|
+
? "已吊销配对凭据:网关侧需重新配对才能继续调用本实例。\n"
|
|
1425
|
+
: "没有已签发的配对凭据(无需吊销)。\n");
|
|
1426
|
+
return EXIT.OK;
|
|
1427
|
+
}
|
|
1428
|
+
if (args.command[2] !== "serve") {
|
|
1429
|
+
process.stderr.write("usage: kiwi merchant mcp serve [--profile <file>] [--host <host>] [--port N] [--data-dir <dir>]\n" +
|
|
1430
|
+
" kiwi merchant mcp pair [--profile <file>] [--data-dir <dir>]\n" +
|
|
1431
|
+
" kiwi merchant mcp unpair [--profile <file>] [--data-dir <dir>]\n" +
|
|
1432
|
+
" kiwi merchant mcp admin-passwd [--profile <file>] [--data-dir <dir>]\n");
|
|
1433
|
+
return EXIT.CONFIG;
|
|
1434
|
+
}
|
|
1435
|
+
const profile = requireProfileOrDefault(args);
|
|
1436
|
+
if (profile.role !== "merchant") {
|
|
1437
|
+
process.stderr.write("kiwi merchant mcp serve 需要 merchant profile(role: merchant)\n");
|
|
1438
|
+
return EXIT.CONFIG;
|
|
1439
|
+
}
|
|
1440
|
+
loadMerchantCredentials(); // 加载 init 写入的 credentials.env(KIWI_MERCHANT_TOKEN)
|
|
1441
|
+
const mcpConfig = profile.merchant_mcp;
|
|
1442
|
+
if (mcpConfig?.enabled === false) {
|
|
1443
|
+
process.stderr.write("profile merchant_mcp.enabled=false:MCP server 未启用\n");
|
|
1444
|
+
return EXIT.CONFIG;
|
|
1445
|
+
}
|
|
1446
|
+
// 正式 WorkBuddy Connector 省略 auth_mode 即走 OAuth;显式 token 仅保留给
|
|
1447
|
+
// 过渡连接器。无 public_url 时的 OAuth 默认只监听 loopback,避免意外把
|
|
1448
|
+
// 未配置公网 issuer 的服务暴露在 0.0.0.0。
|
|
1449
|
+
const authMode = mcpConfig?.auth_mode ?? "oauth";
|
|
1450
|
+
const host = args.catalogHost ??
|
|
1451
|
+
mcpConfig?.host ??
|
|
1452
|
+
(authMode === "oauth" && mcpConfig?.public_url === undefined
|
|
1453
|
+
? "127.0.0.1"
|
|
1454
|
+
: DEFAULT_MERCHANT_MCP_HOST);
|
|
1455
|
+
const port = args.port ?? mcpConfig?.port ?? DEFAULT_MERCHANT_MCP_PORT;
|
|
1456
|
+
const mcpPath = mcpConfig?.path ?? DEFAULT_MERCHANT_MCP_PATH;
|
|
1457
|
+
// 数据目录接线(V2 §5.1):merchant/principal/transportSessionId 显式注入,
|
|
1458
|
+
// 路径是 (dataDir, agentId) 的纯函数——重启稳定,传输会话不影响目录。
|
|
1459
|
+
const dirs = resolveMerchantMcpDirs({
|
|
1460
|
+
...(args.dataDir !== undefined ? { dataDir: args.dataDir } : {}),
|
|
1461
|
+
agentId: profile.agent_id,
|
|
1462
|
+
});
|
|
1463
|
+
// 认证模式(V2):oauth = 自建 OAuth 2.1 授权服务器(正式);
|
|
1464
|
+
// token = V1 静态 Bearer(过渡)。fail-closed 判定见下。
|
|
1465
|
+
let oauth;
|
|
1466
|
+
let oauthDb;
|
|
1467
|
+
let oauthStore;
|
|
1468
|
+
let verifier;
|
|
1469
|
+
// 会话与一次性确认凭证的存储**与认证模式无关**:写操作确认页(/admin/*)
|
|
1470
|
+
// 在两种模式下都挂载——网关路由形态要求实例接受静态内部令牌(token 模式),
|
|
1471
|
+
// 而商家仍必须能批准写候选,否则 prepare_* 只能等到期。
|
|
1472
|
+
{
|
|
1473
|
+
// 数据目录可能尚未创建(例如从未跑过 admin-passwd):先建 0700,再预建库文件。
|
|
1474
|
+
mkdirSync(dirs.merchantDataDir, { recursive: true, mode: 0o700 });
|
|
1475
|
+
const oauthDbPath = path.join(dirs.merchantDataDir, "oauth.sqlite");
|
|
1476
|
+
// 审查 P2:先以 0600 预建空文件再打开,消除「库已建、chmod 未执行」的
|
|
1477
|
+
// 短暂默认权限窗口。
|
|
1478
|
+
if (!existsSync(oauthDbPath)) {
|
|
1479
|
+
writeFileSync(oauthDbPath, "", { mode: 0o600 });
|
|
1480
|
+
}
|
|
1481
|
+
oauthDb = new DatabaseSync(oauthDbPath);
|
|
1482
|
+
chmodSync(oauthDbPath, 0o600);
|
|
1483
|
+
oauthStore = new MerchantOAuthStore({ db: oauthDb });
|
|
1484
|
+
}
|
|
1485
|
+
if (authMode === "oauth") {
|
|
1486
|
+
// issuer:public_url(生产 https)优先;loopback 开发推导为 http://127.0.0.1:<port>。
|
|
1487
|
+
const issuer = mcpConfig?.public_url ?? (isLoopbackHost(host) ? `http://127.0.0.1:${port}` : undefined);
|
|
1488
|
+
if (issuer === undefined) {
|
|
1489
|
+
process.stderr.write("merchant_mcp.auth_mode=oauth 且监听非 loopback 地址时必须配置 merchant_mcp.public_url(https)作为 OAuth issuer\n");
|
|
1490
|
+
return EXIT.CONFIG;
|
|
1491
|
+
}
|
|
1492
|
+
// 授权码/token/客户端注册同样落 oauth.sqlite(上面已按 0600 打开)。
|
|
1493
|
+
oauth = new MerchantOAuthServer({
|
|
1494
|
+
store: oauthStore,
|
|
1495
|
+
issuer,
|
|
1496
|
+
resource: `${issuer}${mcpPath}`,
|
|
1497
|
+
connectorSource: "kiwi-merchant",
|
|
1498
|
+
merchantName: profile.name ?? profile.owner_id,
|
|
1499
|
+
merchantId: profile.owner_id,
|
|
1500
|
+
});
|
|
1501
|
+
verifier = new MerchantOAuthVerifier({
|
|
1502
|
+
store: oauthStore,
|
|
1503
|
+
expectedMerchantId: profile.owner_id,
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
else {
|
|
1507
|
+
verifier = resolveMerchantMcpVerifier(mcpConfig?.token_env);
|
|
1508
|
+
// token 模式(含网关路由形态:实例只接受网关的静态内部令牌)下,写操作
|
|
1509
|
+
// 确认页同样挂载,商家在 /admin/pending 批准候选。
|
|
1510
|
+
process.stderr.write("ℹ️ [kiwi] merchant_mcp.auth_mode=token:/mcp 使用静态内部令牌认证" +
|
|
1511
|
+
"(网关路由形态的推荐配置);写操作确认页仍挂载在 /admin/*,\n" +
|
|
1512
|
+
" 需先 `kiwi merchant mcp admin-passwd` 设置管理员口令。\n");
|
|
1513
|
+
}
|
|
1514
|
+
let authWarning;
|
|
1515
|
+
try {
|
|
1516
|
+
// 策略按**静态 / OAuth** 校验器判定:组合校验器恒存在,若拿它判定会让
|
|
1517
|
+
// 「非 loopback 且未配置任何凭据」的实例误判为已受保护而启动。
|
|
1518
|
+
authWarning = assertMerchantMcpAuthPolicy(host, verifier);
|
|
1519
|
+
}
|
|
1520
|
+
catch (err) {
|
|
1521
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
1522
|
+
return EXIT.CONFIG;
|
|
1523
|
+
}
|
|
1524
|
+
if (authWarning !== undefined)
|
|
1525
|
+
process.stderr.write(`${authWarning}\n`);
|
|
1526
|
+
// 配对凭据(§8.4 第二期,最小授权):由本实例签发、网关持有;与静态令牌
|
|
1527
|
+
// 并存,任一通过即放行。商家重配对即轮换,`unpair` 即吊销。
|
|
1528
|
+
verifier = new CompositeMerchantMcpVerifier([
|
|
1529
|
+
...(verifier !== undefined ? [verifier] : []),
|
|
1530
|
+
new PairedCredentialVerifier(dirs.merchantDataDir),
|
|
1531
|
+
]);
|
|
1532
|
+
// 依赖装配与 chat kernel 同一套:agent data dir + state.sqlite + 审批候选 store。
|
|
1533
|
+
// 审批候选与对话内核共享同一 DB——MCP 生成的 draft 候选在内核侧 /pending 可见。
|
|
1534
|
+
const paths = ensurePathsForDir(dirs.merchantDataDir);
|
|
1535
|
+
const db = openAgentDatabase(paths.db);
|
|
1536
|
+
const now = () => new Date().toISOString();
|
|
1537
|
+
const store = new MemoryStore({ db, vault: new PrivateVault(), now });
|
|
1538
|
+
const principal = store.ensurePrincipal({
|
|
1539
|
+
principal_id: profile.agent_id,
|
|
1540
|
+
owner_id: profile.owner_id,
|
|
1541
|
+
role: profile.role,
|
|
1542
|
+
});
|
|
1543
|
+
store.bindPrincipal(principal.principal_id);
|
|
1544
|
+
const approvals = new WriteApprovalCandidateStore({
|
|
1545
|
+
db,
|
|
1546
|
+
principalId: principal.principal_id,
|
|
1547
|
+
now,
|
|
1548
|
+
});
|
|
1549
|
+
// 执行钩子是进程级的:重启后遗留的 pending 候选无法被本进程执行。按
|
|
1550
|
+
// expireForRecovery 语义把非本服务可恢复的候选先失效(防虚报死候选);
|
|
1551
|
+
// draft_product_change 候选的参数在库内、钩子可确定性重建,留给
|
|
1552
|
+
// service.recoverPendingDrafts() 恢复(阶段四审批闭环)。
|
|
1553
|
+
for (const candidate of approvals.listPending()) {
|
|
1554
|
+
if (candidate.tool !== "draft_product_change")
|
|
1555
|
+
approvals.expireCandidate(candidate.candidate_id);
|
|
1556
|
+
}
|
|
1557
|
+
// merchantClient:fake provider 走离线 Fake,否则真实网关(同 kernel-builder)。
|
|
1558
|
+
let merchantClient;
|
|
1559
|
+
if (isFakeProvider(profile)) {
|
|
1560
|
+
merchantClient = new FakeMerchantClient({ products: [fakeMerchantProduct()] });
|
|
1561
|
+
}
|
|
1562
|
+
else {
|
|
1563
|
+
const broker = new ProfileCredentialBroker(profile);
|
|
1564
|
+
const httpClient = new HttpMerchantClient(profile.commerce.base_url, broker);
|
|
1565
|
+
merchantClient = httpClient;
|
|
1566
|
+
// 能力探测(V2 阶段一/P0-5;协议协商升级):结果落盘供版本组合锁定。
|
|
1567
|
+
// verdict=incompatible(网关通告不含 Kiwi 需要的协议版本)→ 硬拒绝启动;
|
|
1568
|
+
// indeterminate/unhealthy(不可达/协商不可用且不可判定/健康未过)→ 警示
|
|
1569
|
+
// 但不阻塞(网关可能仍在启动,报价路径本身 fail-closed,不产生报价)。
|
|
1570
|
+
const probe = await httpClient.probeCapabilities({
|
|
1571
|
+
persistPath: path.join(dirs.merchantDataDir, "capability-probe.json"),
|
|
1572
|
+
});
|
|
1573
|
+
if (probe.verdict === "incompatible") {
|
|
1574
|
+
process.stderr.write(`[kiwi] shopping-cli 协议不兼容,拒绝启动:${probe.error ?? "未知原因"}。\n` +
|
|
1575
|
+
"升级或更换 shopping-cli 网关(需支持 shopping.negotiation/0.1)后重试。\n");
|
|
1576
|
+
return EXIT.CONFIG;
|
|
1577
|
+
}
|
|
1578
|
+
if (!probe.ok) {
|
|
1579
|
+
process.stderr.write(`⚠️ [kiwi] shopping-cli 能力探测未通过:${probe.error ?? "未知原因"};` +
|
|
1580
|
+
"相关读取/报价在上游恢复前 fail-closed。\n");
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
const intelligence = profile.merchant_experience?.enabled === true &&
|
|
1584
|
+
profile.merchant_experience.intelligence !== false
|
|
1585
|
+
? new DefaultMerchantIntelligenceBackend({
|
|
1586
|
+
merchant_id: profile.owner_id,
|
|
1587
|
+
data_dir: paths.dir,
|
|
1588
|
+
// presentation context 的 principalId 用 owner_id(商家读取口径)——
|
|
1589
|
+
// backend 的 principal 校验键与之对齐(MCP 服务侧统一 owner 口径)。
|
|
1590
|
+
principal_id: profile.owner_id,
|
|
1591
|
+
merchant_client: merchantClient,
|
|
1592
|
+
approvals,
|
|
1593
|
+
now,
|
|
1594
|
+
})
|
|
1595
|
+
: undefined;
|
|
1596
|
+
// BUG-07:运行中策略提供器(覆盖层 <merchantDataDir>/policy-overrides.json)。
|
|
1597
|
+
// 本进程(MCP)为写端:校验 patch + 原子写完整生效策略;A2A 子进程为读端:
|
|
1598
|
+
// 按文件 mtime 读取——策略变更跨进程立即生效,不再只落盘不生效。
|
|
1599
|
+
const policyRuntime = new MerchantPolicyRuntime({
|
|
1600
|
+
basePolicy: profile.merchant_policy,
|
|
1601
|
+
file: path.join(dirs.merchantDataDir, "policy-overrides.json"),
|
|
1602
|
+
now,
|
|
1603
|
+
});
|
|
1604
|
+
// 共享业务入口(V2 阶段二):merchant-core 包装 V1 facade(facade 语义不变),
|
|
1605
|
+
// MCP 工具层经 core 调用;私密读取审计目录落 merchantDataDir/private-audit。
|
|
1606
|
+
const service = new MerchantCoreService({
|
|
1607
|
+
profile,
|
|
1608
|
+
merchantClient,
|
|
1609
|
+
approvals,
|
|
1610
|
+
// MCP 写工具一律 force_pending 只产候选,mode 不影响执行安全;固定 supervised。
|
|
1611
|
+
mode: () => "supervised",
|
|
1612
|
+
now,
|
|
1613
|
+
// 商家 A2A 节点 ledger 基础目录(LedgerStore 会再拼 /ledger)。
|
|
1614
|
+
a2aLedgerDir: path.join(paths.dir, "a2a"),
|
|
1615
|
+
...(intelligence !== undefined ? { intelligence } : {}),
|
|
1616
|
+
auditDir: path.join(dirs.merchantDataDir, "private-audit"),
|
|
1617
|
+
// 命令记录授权主体 = 审批 store principal(批准/拒绝主体一致性校验)。
|
|
1618
|
+
commandPrincipalId: principal.principal_id,
|
|
1619
|
+
// BUG-02:一次性确认凭证存储(OAuth 模式;execute/reject 必须携带有效凭证)。
|
|
1620
|
+
...(oauthStore !== undefined ? { confirmations: oauthStore } : {}),
|
|
1621
|
+
// 长任务 operation store(与命令记录同一 state.sqlite,单 owner 写)。
|
|
1622
|
+
operations: new MerchantOperationStore({ db, now }),
|
|
1623
|
+
// F08 能力接线:能力探测落盘记录中 listing_pause=false 时 fail-closed「不可得」。
|
|
1624
|
+
...(probeCapabilitiesListingPause(dirs.merchantDataDir) !== undefined
|
|
1625
|
+
? { capabilities: { listing_pause: probeCapabilitiesListingPause(dirs.merchantDataDir) } }
|
|
1626
|
+
: {}),
|
|
1627
|
+
// F17/BUG-07 策略热更新:经 MerchantPolicyRuntime 校验 + 原子写完整生效
|
|
1628
|
+
// 策略(版本/digest 回执进命令记录);A2A 进程按同一文件 mtime 读取生效。
|
|
1629
|
+
applyPolicyOverride: (patch) => policyRuntime.apply(patch),
|
|
1630
|
+
// 运行中策略读取:执行器硬策略(底价兜底)按当前生效策略校验。
|
|
1631
|
+
currentPolicy: () => policyRuntime.current().policy,
|
|
1632
|
+
});
|
|
1633
|
+
// 审批闭环(阶段三推广版):恢复全部已注册写工具的 pending 命令(覆盖 V1
|
|
1634
|
+
// recoverPendingDrafts 语义);未注册工具的死候选标 expired。
|
|
1635
|
+
const recovered = service.recoverPendingCommands();
|
|
1636
|
+
// 七类 presentation → MCP 资源(V2 阶段二;私密类不进资源)。
|
|
1637
|
+
const presentations = buildMerchantPresentationResources({
|
|
1638
|
+
context: {
|
|
1639
|
+
profile,
|
|
1640
|
+
// presentation 的 enrich 以 principalId 作为商家读取口径(目录/intelligence
|
|
1641
|
+
// 都按 merchant_id 校验)——用 owner_id,不用进程 principal(agent_id)。
|
|
1642
|
+
principalId: profile.owner_id,
|
|
1643
|
+
merchantClient,
|
|
1644
|
+
approvals,
|
|
1645
|
+
...(intelligence !== undefined ? { intelligence } : {}),
|
|
1646
|
+
},
|
|
1647
|
+
});
|
|
1648
|
+
// 配对兑换:两种认证模式都挂载——凭据由本实例在兑换时新签,不依赖环境变量。
|
|
1649
|
+
const handle = await startMerchantMcpServer({
|
|
1650
|
+
service,
|
|
1651
|
+
host,
|
|
1652
|
+
port,
|
|
1653
|
+
path: mcpPath,
|
|
1654
|
+
pairing: {
|
|
1655
|
+
dir: dirs.merchantDataDir,
|
|
1656
|
+
instance: () => ({
|
|
1657
|
+
ownerId: profile.owner_id,
|
|
1658
|
+
principalId: profile.agent_id,
|
|
1659
|
+
}),
|
|
1660
|
+
},
|
|
1661
|
+
...(verifier !== undefined ? { auth: verifier } : {}),
|
|
1662
|
+
...(oauth !== undefined ? { oauth } : {}),
|
|
1663
|
+
presentations,
|
|
1664
|
+
// 配套商家确认页面(BUG-01/03):cookie 会话 + 一次性确认凭证,两种认证
|
|
1665
|
+
// 模式下都挂载(会话/凭证存 oauth.sqlite,与 OAuth 授权服务器同库不同表)。
|
|
1666
|
+
...(oauthDb !== undefined && oauthStore !== undefined
|
|
1667
|
+
? {
|
|
1668
|
+
admin: {
|
|
1669
|
+
merchantName: profile.name ?? profile.owner_id,
|
|
1670
|
+
surface: merchantAdminSurface(service),
|
|
1671
|
+
sessions: new MerchantAdminSessions({ db: oauthDb }),
|
|
1672
|
+
store: oauthStore,
|
|
1673
|
+
adminDir: dirs.merchantDataDir,
|
|
1674
|
+
secureCookies: (mcpConfig?.public_url ?? "").startsWith("https://"),
|
|
1675
|
+
},
|
|
1676
|
+
}
|
|
1677
|
+
: {}),
|
|
1678
|
+
serverInfo: { name: "kiwi-merchant", version: PRODUCT_VERSION },
|
|
1679
|
+
});
|
|
1680
|
+
console.log(`[merchant mcp] merchant ${profile.agent_id} MCP server: ${handle.url}` +
|
|
1681
|
+
`(auth: ${verifier?.name ?? "none(loopback-only)"}${authMode === "oauth" ? ",OAuth 端点已挂载" : ""};kiwi_merchant_* 工具 + 展示资源)` +
|
|
1682
|
+
(recovered.recovered > 0 ? `;恢复 ${recovered.recovered} 个待批准写命令` : ""));
|
|
1683
|
+
const shutdown = async () => {
|
|
1684
|
+
await handle.close().catch(() => undefined);
|
|
1685
|
+
oauthDb?.close();
|
|
1686
|
+
db.close();
|
|
1687
|
+
process.exit(0);
|
|
1688
|
+
};
|
|
1689
|
+
process.on("SIGINT", shutdown);
|
|
1690
|
+
process.on("SIGTERM", shutdown);
|
|
1691
|
+
await new Promise(() => { });
|
|
1692
|
+
return EXIT.OK;
|
|
1693
|
+
}
|
|
1120
1694
|
/**
|
|
1121
1695
|
* `kiwi merchant stats`:商家侧运营统计(本地数据,不上报)——
|
|
1122
1696
|
* 多少个不同的买家联系过我、讨论了哪些 SKU。数据由 merchant A2A 节点
|
|
@@ -1248,12 +1822,17 @@ async function cmdMerchantPublish(args) {
|
|
|
1248
1822
|
}
|
|
1249
1823
|
const ownerTokenSecret = process.env.KIWI_CATALOG_OWNER_TOKEN_SECRET;
|
|
1250
1824
|
const merchantTokenEnv = profile.merchant_public?.merchant_token_env;
|
|
1251
|
-
const merchantToken = (merchantTokenEnv ? process.env[merchantTokenEnv] : undefined) ||
|
|
1825
|
+
const merchantToken = (merchantTokenEnv ? process.env[merchantTokenEnv] : undefined) ||
|
|
1826
|
+
process.env.KIWI_MERCHANT_TOKEN ||
|
|
1827
|
+
"";
|
|
1252
1828
|
if (!merchantToken && !ownerTokenSecret) {
|
|
1253
1829
|
process.stderr.write("需要 KIWI_MERCHANT_TOKEN(随机 token,推荐)或 KIWI_CATALOG_OWNER_TOKEN_SECRET(legacy HMAC)\n");
|
|
1254
1830
|
return EXIT.CONFIG;
|
|
1255
1831
|
}
|
|
1256
|
-
const catalog = args.catalog ??
|
|
1832
|
+
const catalog = args.catalog ??
|
|
1833
|
+
profile.merchant_public?.catalog_url ??
|
|
1834
|
+
process.env.KIWI_CATALOG_URL ??
|
|
1835
|
+
DEFAULT_CATALOG_URL;
|
|
1257
1836
|
const report = await merchantPublish({
|
|
1258
1837
|
profile,
|
|
1259
1838
|
catalogBaseUrl: catalog,
|
|
@@ -1264,9 +1843,7 @@ async function cmdMerchantPublish(args) {
|
|
|
1264
1843
|
...(args.shoppingCliMerchant !== undefined
|
|
1265
1844
|
? { shoppingCliMerchant: args.shoppingCliMerchant }
|
|
1266
1845
|
: {}),
|
|
1267
|
-
...(args.allowEmptyProjectionReconcile
|
|
1268
|
-
? { allowEmptyProjectionReconcile: true }
|
|
1269
|
-
: {}),
|
|
1846
|
+
...(args.allowEmptyProjectionReconcile ? { allowEmptyProjectionReconcile: true } : {}),
|
|
1270
1847
|
});
|
|
1271
1848
|
printJson(report);
|
|
1272
1849
|
return report.ok ? EXIT.OK : EXIT.CONFIG;
|
|
@@ -1308,10 +1885,15 @@ async function cmdDemo(args) {
|
|
|
1308
1885
|
*/
|
|
1309
1886
|
async function cmdMerchantSetupPublic(args) {
|
|
1310
1887
|
const profile = requireProfileOrDefault(args); // 无 --profile 时回退缺省 ~/.kiwi/kiwi.yaml
|
|
1311
|
-
const port = args.port ??
|
|
1888
|
+
const port = args.port ??
|
|
1889
|
+
profile.merchant_public?.a2a_port ??
|
|
1890
|
+
(Number(process.env.KIWI_A2A_PORT ?? "") || 9000); // 与 startA2aNode 缺省一致
|
|
1312
1891
|
// 单一来源:--domain 显式 > profile.merchant_public.public_url(init 引导写入)>
|
|
1313
1892
|
// KIWI_A2A_PUBLIC_URL env 提取;TTY 下再交互提示。
|
|
1314
|
-
let domain = args.domain ??
|
|
1893
|
+
let domain = args.domain ??
|
|
1894
|
+
profile.merchant_public?.public_url ??
|
|
1895
|
+
extractPublicDomain(process.env.KIWI_A2A_PUBLIC_URL) ??
|
|
1896
|
+
"";
|
|
1315
1897
|
if (domain === "" && process.stdin.isTTY) {
|
|
1316
1898
|
domain = await promptLine("公网域名(如 merchant.example.com,TLS 证书就用它): ", "");
|
|
1317
1899
|
}
|
|
@@ -1378,12 +1960,17 @@ async function cmdMerchantUp(args) {
|
|
|
1378
1960
|
}
|
|
1379
1961
|
loadMerchantCredentials(); // 加载 init 写入的 credentials.env(KIWI_MERCHANT_TOKEN)
|
|
1380
1962
|
// ── 域名 / 端口 / Caddyfile(profile 兜底)──
|
|
1381
|
-
const domain = args.domain ??
|
|
1963
|
+
const domain = args.domain ??
|
|
1964
|
+
profile.merchant_public?.public_url ??
|
|
1965
|
+
extractPublicDomain(process.env.KIWI_A2A_PUBLIC_URL) ??
|
|
1966
|
+
"";
|
|
1382
1967
|
if (domain === "") {
|
|
1383
1968
|
process.stderr.write("需要公网域名——先 `kiwi merchant init` 填公网域名,或 --domain <域名>\n");
|
|
1384
1969
|
return EXIT.CONFIG;
|
|
1385
1970
|
}
|
|
1386
|
-
const port = args.port ??
|
|
1971
|
+
const port = args.port ??
|
|
1972
|
+
profile.merchant_public?.a2a_port ??
|
|
1973
|
+
(Number(process.env.KIWI_A2A_PORT ?? "") || 9000);
|
|
1387
1974
|
const caddyfilePath = args.caddyfile ?? "Caddyfile.kiwi";
|
|
1388
1975
|
// ── setup-public:检测 IP / DNS 检查 / 生成 Caddyfile(幂等)──
|
|
1389
1976
|
await runMerchantSetupPublic({
|
|
@@ -1403,7 +1990,10 @@ async function cmdMerchantUp(args) {
|
|
|
1403
1990
|
// ── 起 Caddy 反代(子进程,日志透传)──
|
|
1404
1991
|
const caddy = spawn("caddy", ["run", "--config", caddyfilePath], { stdio: "inherit" });
|
|
1405
1992
|
// ── 起 A2A 节点(与 merchant start --no-chat 同一路径)──
|
|
1406
|
-
const catalog = args.catalog ??
|
|
1993
|
+
const catalog = args.catalog ??
|
|
1994
|
+
profile.merchant_public?.catalog_url ??
|
|
1995
|
+
process.env.KIWI_CATALOG_URL ??
|
|
1996
|
+
DEFAULT_CATALOG_URL;
|
|
1407
1997
|
const merchantToken = process.env.KIWI_MERCHANT_TOKEN || "";
|
|
1408
1998
|
const serveDataDir = resolveServeDataDir(args.dataDir, profile.agent_id);
|
|
1409
1999
|
let node = await startA2aNode({
|
|
@@ -1431,7 +2021,7 @@ async function cmdMerchantUp(args) {
|
|
|
1431
2021
|
/**
|
|
1432
2022
|
* `kiwi setup-hermes`(买家侧)——一键把 Kiwi 买家连接器接入 Hermes:
|
|
1433
2023
|
* 配置 kiwi-buyer-mcp MCP server + 安装 kiwi-buyer skill。之后 Hermes 重启
|
|
1434
|
-
* 即可调用
|
|
2024
|
+
* 即可调用 13 个采购工具。等价于手动执行:
|
|
1435
2025
|
* hermes mcp add kiwi-buyer-mcp --command node --args <cli> mcp serve ...
|
|
1436
2026
|
* hermes skills install <SKILL.md URL>
|
|
1437
2027
|
*/
|
|
@@ -1452,7 +2042,8 @@ async function cmdSetupHermes() {
|
|
|
1452
2042
|
username = process.env.USER ?? "user";
|
|
1453
2043
|
}
|
|
1454
2044
|
const dbPath = path.join(homedir(), ".kiwi", "buyer.sqlite");
|
|
1455
|
-
const skillUrl = "https://raw.githubusercontent.com/harrylabsj/kiwi/main/
|
|
2045
|
+
const skillUrl = "https://raw.githubusercontent.com/harrylabsj/kiwi/main/skills/kiwi-buyer/SKILL.md";
|
|
2046
|
+
const bundledSkillPath = fileURLToPath(new URL("../skills/kiwi-buyer/SKILL.md", import.meta.url));
|
|
1456
2047
|
const skillPath = path.join(homedir(), ".hermes", "skills", "kiwi-buyer", "SKILL.md");
|
|
1457
2048
|
const configured = [];
|
|
1458
2049
|
const already = [];
|
|
@@ -1487,29 +2078,45 @@ async function cmdSetupHermes() {
|
|
|
1487
2078
|
}
|
|
1488
2079
|
configured.push("MCP server kiwi-buyer-mcp");
|
|
1489
2080
|
}
|
|
1490
|
-
// 4. kiwi-buyer skill
|
|
2081
|
+
// 4. kiwi-buyer skill:优先使用 npm 包内置版本,避免依赖网络;否则从公共 URL 安装
|
|
1491
2082
|
if (existsSync(skillPath)) {
|
|
1492
2083
|
already.push("skill kiwi-buyer");
|
|
1493
2084
|
}
|
|
1494
2085
|
else {
|
|
1495
2086
|
process.stdout.write("[setup-hermes] 安装 kiwi-buyer skill ...\n");
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
// 兜底:直接 fetch SKILL.md 写入 ~/.hermes/skills/kiwi-buyer/(Hermes skills 为 local 存储)
|
|
1499
|
-
process.stdout.write("[setup-hermes] hermes skills install 不可用,改用直接写入 ...\n");
|
|
2087
|
+
if (existsSync(bundledSkillPath)) {
|
|
2088
|
+
process.stdout.write("[setup-hermes] 使用 npm 包内置 skill ...\n");
|
|
1500
2089
|
try {
|
|
1501
|
-
const res = await fetch(skillUrl, { signal: AbortSignal.timeout(15000) });
|
|
1502
|
-
if (!res.ok)
|
|
1503
|
-
throw new Error(`HTTP ${res.status}`);
|
|
1504
|
-
const content = await res.text();
|
|
1505
2090
|
mkdirSync(path.dirname(skillPath), { recursive: true, mode: 0o700 });
|
|
1506
|
-
|
|
2091
|
+
copyFileSync(bundledSkillPath, skillPath);
|
|
2092
|
+
chmodSync(skillPath, 0o600);
|
|
1507
2093
|
}
|
|
1508
2094
|
catch (err) {
|
|
1509
2095
|
process.stderr.write(`安装 kiwi-buyer skill 失败:${err instanceof Error ? err.message : String(err)}\n`);
|
|
1510
2096
|
return EXIT.CONFIG;
|
|
1511
2097
|
}
|
|
1512
2098
|
}
|
|
2099
|
+
else {
|
|
2100
|
+
const skill = spawnSync("hermes", ["skills", "install", skillUrl, "--yes"], {
|
|
2101
|
+
stdio: "inherit",
|
|
2102
|
+
});
|
|
2103
|
+
if (skill.status !== 0 || !existsSync(skillPath)) {
|
|
2104
|
+
// 兜底:直接 fetch SKILL.md 写入 ~/.hermes/skills/kiwi-buyer/(Hermes skills 为 local 存储)
|
|
2105
|
+
process.stdout.write("[setup-hermes] hermes skills install 不可用,改用直接写入 ...\n");
|
|
2106
|
+
try {
|
|
2107
|
+
const res = await fetch(skillUrl, { signal: AbortSignal.timeout(15000) });
|
|
2108
|
+
if (!res.ok)
|
|
2109
|
+
throw new Error(`HTTP ${res.status}`);
|
|
2110
|
+
const content = await res.text();
|
|
2111
|
+
mkdirSync(path.dirname(skillPath), { recursive: true, mode: 0o700 });
|
|
2112
|
+
writeFileSync(skillPath, content, { mode: 0o600 });
|
|
2113
|
+
}
|
|
2114
|
+
catch (err) {
|
|
2115
|
+
process.stderr.write(`安装 kiwi-buyer skill 失败:${err instanceof Error ? err.message : String(err)}\n`);
|
|
2116
|
+
return EXIT.CONFIG;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
1513
2120
|
configured.push("skill kiwi-buyer");
|
|
1514
2121
|
}
|
|
1515
2122
|
// 5. 报告
|
|
@@ -1556,6 +2163,11 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1556
2163
|
if (argv[0] === "buyer-api" && argv[1] === "serve") {
|
|
1557
2164
|
return await runHttpServe(argv.slice(2));
|
|
1558
2165
|
}
|
|
2166
|
+
// `kiwi merchant gateway serve`:商家连接器远程入口,独立 flag 集
|
|
2167
|
+
// (--public-url / --catalog-url / --source / --tls-* 等,见 merchant-gateway/cli.ts)。
|
|
2168
|
+
if (argv[0] === "merchant" && argv[1] === "gateway") {
|
|
2169
|
+
return await runGatewayServe(argv.slice(2));
|
|
2170
|
+
}
|
|
1559
2171
|
let args;
|
|
1560
2172
|
try {
|
|
1561
2173
|
args = parseArgs(argv);
|