@harrylabsj/kiwi 0.6.0 → 0.6.2

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.
Files changed (167) hide show
  1. package/README.md +21 -0
  2. package/contracts/candidate-agent-dto-1.0.README.md +12 -8
  3. package/contracts/conformance/python-service/README.md +19 -0
  4. package/contracts/conformance/python-service/run.py +134 -0
  5. package/contracts/kiwi-catalog/1.0/listing-record.schema.json +11 -1
  6. package/contracts/kiwi-contracts.lock.json +7 -0
  7. package/contracts/manifest.json +61 -0
  8. package/contracts/vectors/candidate-agent.invalid-private-field.json +7 -0
  9. package/contracts/vectors/candidate-agent.valid.json +26 -0
  10. package/dist/a2a/client/client.js +39 -34
  11. package/dist/a2a/client/client.js.map +1 -1
  12. package/dist/a2a/client/url-policy.d.ts +8 -1
  13. package/dist/a2a/client/url-policy.js +18 -2
  14. package/dist/a2a/client/url-policy.js.map +1 -1
  15. package/dist/a2a/clock.d.ts +30 -0
  16. package/dist/a2a/clock.js +39 -0
  17. package/dist/a2a/clock.js.map +1 -0
  18. package/dist/a2a/negotiate.d.ts +6 -0
  19. package/dist/a2a/negotiate.js +10 -12
  20. package/dist/a2a/negotiate.js.map +1 -1
  21. package/dist/a2a/node.d.ts +23 -3
  22. package/dist/a2a/node.js +128 -18
  23. package/dist/a2a/node.js.map +1 -1
  24. package/dist/a2a/server/merchant-handler.d.ts +4 -0
  25. package/dist/a2a/server/merchant-handler.js +238 -25
  26. package/dist/a2a/server/merchant-handler.js.map +1 -1
  27. package/dist/a2a/server/pipeline.d.ts +17 -0
  28. package/dist/a2a/server/pipeline.js +89 -1
  29. package/dist/a2a/server/pipeline.js.map +1 -1
  30. package/dist/a2a/server/ucp.d.ts +30 -2
  31. package/dist/a2a/server/ucp.js +23 -14
  32. package/dist/a2a/server/ucp.js.map +1 -1
  33. package/dist/agent/agent-db.js +5 -0
  34. package/dist/agent/agent-db.js.map +1 -1
  35. package/dist/agent/buyer/buyer-tools.d.ts +5 -0
  36. package/dist/agent/buyer/buyer-tools.js +8 -1
  37. package/dist/agent/buyer/buyer-tools.js.map +1 -1
  38. package/dist/agent/buyer/types.d.ts +0 -25
  39. package/dist/agent/buyer/types.js.map +1 -1
  40. package/dist/agent/kernel-builder.d.ts +27 -0
  41. package/dist/agent/kernel-builder.js +180 -0
  42. package/dist/agent/kernel-builder.js.map +1 -0
  43. package/dist/agent/kernel.d.ts +6 -0
  44. package/dist/agent/kernel.js +95 -8
  45. package/dist/agent/kernel.js.map +1 -1
  46. package/dist/agent/memory/store.d.ts +5 -0
  47. package/dist/agent/memory/store.js +29 -1
  48. package/dist/agent/memory/store.js.map +1 -1
  49. package/dist/agent/merchant/action-candidate.js +15 -1
  50. package/dist/agent/merchant/action-candidate.js.map +1 -1
  51. package/dist/agent/merchant/merchant-client.js +22 -4
  52. package/dist/agent/merchant/merchant-client.js.map +1 -1
  53. package/dist/agent/merchant/types.d.ts +5 -2
  54. package/dist/agent/merchant/types.js +6 -2
  55. package/dist/agent/merchant/types.js.map +1 -1
  56. package/dist/agent/negotiation-chat.js +34 -14
  57. package/dist/agent/negotiation-chat.js.map +1 -1
  58. package/dist/cli.d.ts +56 -0
  59. package/dist/cli.js +141 -156
  60. package/dist/cli.js.map +1 -1
  61. package/dist/commerce/data-source.d.ts +7 -3
  62. package/dist/commerce/data-source.js.map +1 -1
  63. package/dist/commerce/http-client.js +35 -31
  64. package/dist/commerce/http-client.js.map +1 -1
  65. package/dist/commerce/shopping-cli-source.js +66 -48
  66. package/dist/commerce/shopping-cli-source.js.map +1 -1
  67. package/dist/config/profile.d.ts +7 -0
  68. package/dist/config/profile.js +23 -0
  69. package/dist/config/profile.js.map +1 -1
  70. package/dist/counterparty/a2a-direct/index.d.ts +5 -0
  71. package/dist/counterparty/a2a-direct/index.js +24 -0
  72. package/dist/counterparty/a2a-direct/index.js.map +1 -1
  73. package/dist/counterparty/shopping-cli-hosted/index.js +52 -19
  74. package/dist/counterparty/shopping-cli-hosted/index.js.map +1 -1
  75. package/dist/discovery/catalog-source/kiwi-record.d.ts +3 -0
  76. package/dist/discovery/catalog-source/kiwi-record.js.map +1 -1
  77. package/dist/discovery/catalog-source/kiwi-source.js +46 -36
  78. package/dist/discovery/catalog-source/kiwi-source.js.map +1 -1
  79. package/dist/discovery/catalog-source/register.d.ts +6 -0
  80. package/dist/discovery/catalog-source/register.js +27 -8
  81. package/dist/discovery/catalog-source/register.js.map +1 -1
  82. package/dist/discovery/catalog-source/source.js +39 -35
  83. package/dist/discovery/catalog-source/source.js.map +1 -1
  84. package/dist/discovery/resolve.d.ts +15 -0
  85. package/dist/discovery/resolve.js +59 -35
  86. package/dist/discovery/resolve.js.map +1 -1
  87. package/dist/discovery/ucp/index.d.ts +1 -1
  88. package/dist/discovery/ucp/index.js +1 -1
  89. package/dist/discovery/ucp/index.js.map +1 -1
  90. package/dist/discovery/ucp/resolver.d.ts +5 -0
  91. package/dist/discovery/ucp/resolver.js +65 -38
  92. package/dist/discovery/ucp/resolver.js.map +1 -1
  93. package/dist/discovery/ucp/vendor.d.ts +1 -1
  94. package/dist/discovery/ucp/vendor.js +1 -1
  95. package/dist/handoff/delivery.d.ts +4 -2
  96. package/dist/handoff/delivery.js +6 -1
  97. package/dist/handoff/delivery.js.map +1 -1
  98. package/dist/handoff/errors.d.ts +1 -1
  99. package/dist/handoff/errors.js +1 -0
  100. package/dist/handoff/errors.js.map +1 -1
  101. package/dist/handoff/idempotency.d.ts +10 -1
  102. package/dist/handoff/idempotency.js +13 -3
  103. package/dist/handoff/idempotency.js.map +1 -1
  104. package/dist/handoff/index.d.ts +1 -1
  105. package/dist/handoff/index.js +1 -1
  106. package/dist/handoff/index.js.map +1 -1
  107. package/dist/handoff/ledger.d.ts +9 -0
  108. package/dist/handoff/ledger.js +66 -0
  109. package/dist/handoff/ledger.js.map +1 -1
  110. package/dist/handoff/transaction.d.ts +21 -3
  111. package/dist/handoff/transaction.js +50 -11
  112. package/dist/handoff/transaction.js.map +1 -1
  113. package/dist/handoff/ucp-checkout/channel.js +15 -10
  114. package/dist/handoff/ucp-checkout/channel.js.map +1 -1
  115. package/dist/negotiation/jcs.js +11 -8
  116. package/dist/negotiation/jcs.js.map +1 -1
  117. package/dist/negotiation/lease/store.d.ts +25 -0
  118. package/dist/negotiation/lease/store.js +101 -0
  119. package/dist/negotiation/lease/store.js.map +1 -0
  120. package/dist/negotiation/recovery/recover.js +25 -1
  121. package/dist/negotiation/recovery/recover.js.map +1 -1
  122. package/dist/negotiation/state/phase.js +12 -4
  123. package/dist/negotiation/state/phase.js.map +1 -1
  124. package/dist/operator/runner.d.ts +11 -0
  125. package/dist/operator/runner.js +20 -1
  126. package/dist/operator/runner.js.map +1 -1
  127. package/dist/product-catalog.d.ts +43 -0
  128. package/dist/product-catalog.js +60 -0
  129. package/dist/product-catalog.js.map +1 -0
  130. package/dist/product-cli.d.ts +1 -1
  131. package/dist/product-cli.js +1 -1
  132. package/dist/product-publish.d.ts +15 -17
  133. package/dist/product-publish.js +279 -46
  134. package/dist/product-publish.js.map +1 -1
  135. package/dist/supervisor/stack-config.d.ts +6 -1
  136. package/dist/supervisor/stack-config.js +11 -3
  137. package/dist/supervisor/stack-config.js.map +1 -1
  138. package/dist/trust/identity/message-signature.js +17 -0
  139. package/dist/trust/identity/message-signature.js.map +1 -1
  140. package/dist/weixin/channel.d.ts +89 -0
  141. package/dist/weixin/channel.js +370 -0
  142. package/dist/weixin/channel.js.map +1 -0
  143. package/dist/weixin/cli-weixin.d.ts +29 -0
  144. package/dist/weixin/cli-weixin.js +130 -0
  145. package/dist/weixin/cli-weixin.js.map +1 -0
  146. package/dist/weixin/credentials.d.ts +33 -0
  147. package/dist/weixin/credentials.js +107 -0
  148. package/dist/weixin/credentials.js.map +1 -0
  149. package/dist/weixin/ilink-client.d.ts +44 -0
  150. package/dist/weixin/ilink-client.js +287 -0
  151. package/dist/weixin/ilink-client.js.map +1 -0
  152. package/dist/weixin/login.d.ts +37 -0
  153. package/dist/weixin/login.js +93 -0
  154. package/dist/weixin/login.js.map +1 -0
  155. package/dist/weixin/qr.d.ts +26 -0
  156. package/dist/weixin/qr.js +525 -0
  157. package/dist/weixin/qr.js.map +1 -0
  158. package/dist/weixin/types.d.ts +114 -0
  159. package/dist/weixin/types.js +48 -0
  160. package/dist/weixin/types.js.map +1 -0
  161. package/package.json +12 -2
  162. package/dist/commerce/erp-source.d.ts +0 -51
  163. package/dist/commerce/erp-source.js +0 -184
  164. package/dist/commerce/erp-source.js.map +0 -1
  165. package/dist/commerce/local-db-source.d.ts +0 -50
  166. package/dist/commerce/local-db-source.js +0 -162
  167. package/dist/commerce/local-db-source.js.map +0 -1
package/dist/cli.js CHANGED
@@ -30,21 +30,18 @@
30
30
  import { existsSync, realpathSync } from "node:fs";
31
31
  import path from "node:path";
32
32
  import { fileURLToPath } from "node:url";
33
- import { agentDataDir, ensurePathsForDir } from "./agent/agent-db.js";
34
33
  import { startA2aNode } from "./a2a/node.js";
35
34
  import { runChatTui } from "./agent/chat-tui.js";
36
- import { createFakeChatModels } from "./agent/fake-chat-model.js";
37
- import { isAgentMode } from "./agent/mode.js";
35
+ import { buildChatKernel, buildClient, defaultChatProfile } from "./agent/kernel-builder.js";
38
36
  import { AgentKernel } from "./agent/kernel.js";
39
- import { loadProfile, ProfileError, resolveSecret, RUNTIME_VERSION } from "./config/profile.js";
40
- import { HttpCommerceClient } from "./commerce/http-client.js";
37
+ import { loadProfile, ProfileError } from "./config/profile.js";
41
38
  import { CommerceError } from "./commerce/types.js";
42
39
  import { runDoctor } from "./doctor.js";
43
40
  import { EXIT } from "./exit-codes.js";
44
41
  import { createDeterministicStreamFn } from "./runtime/fake-model.js";
45
42
  import { runForeground } from "./runtime/foreground.js";
46
43
  import { runNegotiationTurn } from "./runtime/negotiation-turn.js";
47
- import { isFakeProvider, realStreamFn, resolveThinkingLevel } from "./runtime/model.js";
44
+ import { isFakeProvider, realStreamFn } from "./runtime/model.js";
48
45
  import { OperatorController } from "./operator/controller.js";
49
46
  import { DeterministicNegotiationRunner } from "./operator/runner.js";
50
47
  import { FileOperatorEventStore, OperatorStoreError } from "./operator/store.js";
@@ -55,9 +52,11 @@ import { runDown, runStatus, runUp, SupervisorError } from "./supervisor/manage.
55
52
  import { parseLogLines, runLogs } from "./supervisor/logs.js";
56
53
  import { StackConfigError } from "./supervisor/stack-config.js";
57
54
  import { cmdProductDoctor, productHelp, PRODUCT_VERSION, DEFAULT_CATALOG_URL } from "./product-cli.js";
55
+ import { cmdWeixin, weixinUsage } from "./weixin/cli-weixin.js";
58
56
  import { merchantInit } from "./product-init.js";
59
57
  import { buyerInit, buyerSearch, buyerTasks } from "./product-buyer.js";
60
58
  import { merchantPublish } from "./product-publish.js";
59
+ import { catalogServe } from "./product-catalog.js";
61
60
  const USAGE = `kiwi ${PRODUCT_VERSION} — commerce negotiation agent runtime
62
61
 
63
62
  Usage:
@@ -81,7 +80,12 @@ Usage:
81
80
  /discover /negotiate /register in-session
82
81
  kiwi chat --profile <file> [--data-dir <dir>]
83
82
  Main conversation with Principal Memory (v0.3.0-A)
83
+ kiwi weixin [--profile <file>] [--allow id,...] [--relogin] [--a2a]
84
+ WeChat remote control (scan QR to log in)
84
85
  kiwi metrics --dir <dir> Handoff/negotiation ledger metrics (JSONL)
86
+ kiwi catalog serve [--db <file>] [--host <host>] [--port <port>]
87
+ Start the standalone kiwi-catalog service
88
+ (foreground; requires pip install kiwi-catalog)
85
89
 
86
90
  Product layer (product-strategy rev1.1 §10/§19):
87
91
  kiwi buyer --help Kiwi Buyer command tree
@@ -100,7 +104,8 @@ contain secrets or private policy values. Secrets come from environment
100
104
  variables named in the profile (commerce.token_env, model.api_key_env);
101
105
  they are never read from or written to the profile file.
102
106
  `;
103
- function parseArgs(argv) {
107
+ /** 导出供测试(审查 P2-L:--a2a flag 解析回归锁定)。 */
108
+ export function parseArgs(argv) {
104
109
  const command = [];
105
110
  let profile;
106
111
  let dir;
@@ -114,6 +119,7 @@ function parseArgs(argv) {
114
119
  let output;
115
120
  let force = false;
116
121
  let noInstall = false;
122
+ let allowEmptyProjectionReconcile = false;
117
123
  let agentId;
118
124
  let ownerId;
119
125
  let autoNegotiate = false;
@@ -124,10 +130,17 @@ function parseArgs(argv) {
124
130
  let dataDir;
125
131
  let catalog;
126
132
  let port;
133
+ let catalogDb;
134
+ let catalogHost;
127
135
  let once = false;
128
136
  let fake = false;
129
137
  let noChat = false;
130
138
  let noA2a = false;
139
+ let a2aExplicit = false;
140
+ let weixinAllow;
141
+ let relogin = false;
142
+ let qrScale = 1;
143
+ let noQr = false;
131
144
  for (let i = 0; i < argv.length; i++) {
132
145
  const arg = argv[i];
133
146
  if (arg === "--profile") {
@@ -163,6 +176,10 @@ function parseArgs(argv) {
163
176
  else if (arg === "--force") {
164
177
  force = true;
165
178
  }
179
+ else if (arg === "--allow-empty-projection") {
180
+ // 审查 P1-B:显式放行"投影为空时 reconcile 全量下架"(默认拒绝)
181
+ allowEmptyProjectionReconcile = true;
182
+ }
166
183
  else if (arg === "--no-install") {
167
184
  noInstall = true;
168
185
  }
@@ -199,6 +216,12 @@ function parseArgs(argv) {
199
216
  const parsed = /^\d+$/.test(raw ?? "") ? Number(raw) : Number.NaN;
200
217
  port = Number.isInteger(parsed) ? parsed : undefined;
201
218
  }
219
+ else if (arg === "--host") {
220
+ catalogHost = argv[++i];
221
+ }
222
+ else if (arg === "--db") {
223
+ catalogDb = argv[++i];
224
+ }
202
225
  else if (arg === "--once") {
203
226
  once = true;
204
227
  }
@@ -211,6 +234,26 @@ function parseArgs(argv) {
211
234
  else if (arg === "--no-a2a") {
212
235
  noA2a = true;
213
236
  }
237
+ else if (arg === "--a2a") {
238
+ // 审查 P2-L:weixin 命令文档承诺"A2A 节点默认关,--a2a 开启"——
239
+ // 此前 parseArgs 无此分支(Unknown argument: --a2a,实测 exit 2),
240
+ // 且默认 noA2a=false 让 A2A 恒开(与文档相反,需 --no-a2a 才能关)。
241
+ // 裸 kiwi 命令的 A2A 默认开不受影响(见 cmdWeixin 的解析)。
242
+ a2aExplicit = true;
243
+ }
244
+ else if (arg === "--allow") {
245
+ weixinAllow = argv[++i];
246
+ }
247
+ else if (arg === "--relogin") {
248
+ relogin = true;
249
+ }
250
+ else if (arg === "--qr-scale") {
251
+ const raw = argv[++i];
252
+ qrScale = raw === "2" ? 2 : 1;
253
+ }
254
+ else if (arg === "--no-qr") {
255
+ noQr = true;
256
+ }
214
257
  else if (arg !== undefined && arg.startsWith("--profile=")) {
215
258
  profile = arg.slice("--profile=".length);
216
259
  }
@@ -239,7 +282,22 @@ function parseArgs(argv) {
239
282
  throw new ProfileError(`Unknown argument: ${arg ?? ""}`);
240
283
  }
241
284
  }
242
- const out = { command, once, fake, noChat, noA2a, force, noInstall, autoNegotiate };
285
+ const out = {
286
+ command,
287
+ once,
288
+ fake,
289
+ noChat,
290
+ noA2a,
291
+ a2aExplicit,
292
+ force,
293
+ noInstall,
294
+ autoNegotiate,
295
+ allowEmptyProjectionReconcile,
296
+ weixinAllow,
297
+ relogin,
298
+ qrScale,
299
+ noQr,
300
+ };
243
301
  if (profile !== undefined)
244
302
  out.profile = profile;
245
303
  if (dir !== undefined)
@@ -254,6 +312,8 @@ function parseArgs(argv) {
254
312
  out.shoppingCliPath = shoppingCliPath;
255
313
  if (shoppingCliMerchant !== undefined)
256
314
  out.shoppingCliMerchant = shoppingCliMerchant;
315
+ if (allowEmptyProjectionReconcile)
316
+ out.allowEmptyProjectionReconcile = true;
257
317
  if (merchantId !== undefined)
258
318
  out.merchantId = merchantId;
259
319
  if (merchantName !== undefined)
@@ -278,6 +338,10 @@ function parseArgs(argv) {
278
338
  out.catalog = catalog;
279
339
  if (port !== undefined)
280
340
  out.port = port;
341
+ if (catalogDb !== undefined)
342
+ out.catalogDb = catalogDb;
343
+ if (catalogHost !== undefined)
344
+ out.catalogHost = catalogHost;
281
345
  return out;
282
346
  }
283
347
  function requireDir(args) {
@@ -292,13 +356,6 @@ function requireProfile(args) {
292
356
  }
293
357
  return loadProfile(args.profile);
294
358
  }
295
- function buildClient(profile) {
296
- const token = resolveSecret(profile.commerce.token_env, "commerce token");
297
- return new HttpCommerceClient({
298
- baseUrl: profile.commerce.base_url,
299
- token,
300
- });
301
- }
302
359
  function printReportJsonl(report) {
303
360
  process.stdout.write(`${JSON.stringify(report)}\n`);
304
361
  }
@@ -473,7 +530,16 @@ async function cmdTui(args) {
473
530
  *
474
531
  * kiwi agent serve --profile <merchant.yaml> [--catalog <url>] # 缺省官方 catalog,本地自托管用 --catalog http://127.0.0.1:8600
475
532
  * [--port 9000] [--data-dir <dir>]
533
+ *
534
+ * 公网暴露:节点仍监听 127.0.0.1,但可用 KIWI_A2A_PUBLIC_URL=<https://domain>
535
+ * 覆盖 Agent Card / UCP / catalog 注册广告的公网地址(配合 Caddy/Nginx 反代)。
476
536
  */
537
+ /** 审查 P1-09:agent serve 的 A2A 状态目录。缺省用稳定路径
538
+ * `<cwd>/.kiwi/agents/<agent_id>`(与 operator 路径共用同一缺省)——重启后
539
+ * Ledger/幂等/终态可恢复;显式 --data-dir 覆盖。绝不回退到临时目录。 */
540
+ export function resolveServeDataDir(dataDir, agentId) {
541
+ return dataDir ?? path.resolve(".kiwi", "agents", agentId);
542
+ }
477
543
  async function cmdAgentServe(args) {
478
544
  const profile = requireProfile(args);
479
545
  if (profile.role !== "merchant") {
@@ -481,13 +547,22 @@ async function cmdAgentServe(args) {
481
547
  return EXIT.CONFIG;
482
548
  }
483
549
  const catalog = args.catalog ?? process.env.KIWI_CATALOG_URL ?? DEFAULT_CATALOG_URL;
550
+ const merchantToken = process.env.KIWI_MERCHANT_TOKEN || "";
551
+ // 审查 P1-09:serve 的初始 A2A 节点必须用稳定 dataDir——此前初始节点缺省走
552
+ // mkdtemp 临时目录且 stop 时删除,重启后 Ledger/幂等/终态全丢:已终态
553
+ // negotiation 可重开、已发 conditional offer 返回 offer_unknown。稳定目录
554
+ // 让重启可恢复状态(resolveServeDataDir 见上)。
555
+ const serveDataDir = resolveServeDataDir(args.dataDir, profile.agent_id);
484
556
  let node = await startA2aNode({
485
557
  profile,
486
558
  catalog,
487
559
  preferredPort: args.port,
560
+ dataDir: serveDataDir,
561
+ ...(merchantToken ? { ownerToken: merchantToken } : {}),
488
562
  ownerTokenSecret: process.env.KIWI_CATALOG_OWNER_TOKEN_SECRET,
489
563
  });
490
- console.log(`[agent serve] merchant ${profile.agent_id} A2A server: ${node.agentCardUrl}`);
564
+ console.log(`[agent serve] merchant ${profile.agent_id} A2A server: ${node.agentCardUrl}` +
565
+ (node.advertisedUrl !== node.url ? ` (local ${node.url})` : ""));
491
566
  console.log(`[agent serve] registered in catalog ${catalog}: ${node.catalogAgentId ?? "?"}`);
492
567
  const a2aNode = {
493
568
  status: () => node === null
@@ -504,6 +579,8 @@ async function cmdAgentServe(args) {
504
579
  profile: p,
505
580
  catalog,
506
581
  preferredPort: args.port,
582
+ dataDir: serveDataDir,
583
+ ...(merchantToken ? { ownerToken: merchantToken } : {}),
507
584
  ownerTokenSecret: process.env.KIWI_CATALOG_OWNER_TOKEN_SECRET,
508
585
  });
509
586
  },
@@ -559,149 +636,12 @@ async function cmdAgentServe(args) {
559
636
  * - KIWI_MODEL_API_KEY_ENV(默认 DEEPSEEK_API_KEY)
560
637
  * - KIWI_MODEL_BASE_URL(可选覆盖)
561
638
  */
562
- function defaultChatProfile() {
563
- const provider = process.env.KIWI_MODEL_PROVIDER ?? "deepseek";
564
- const model = process.env.KIWI_MODEL ?? "deepseek-v4-flash";
565
- const apiKeyEnv = process.env.KIWI_MODEL_API_KEY_ENV ?? "DEEPSEEK_API_KEY";
566
- const baseUrl = process.env.KIWI_MODEL_BASE_URL;
567
- return {
568
- runtime_version: RUNTIME_VERSION,
569
- protocol_version: "shopping.negotiation/0.1",
570
- agent_id: "kiwi-assistant",
571
- role: "buyer",
572
- owner_id: "kiwi-user",
573
- commerce: {
574
- base_url: "http://127.0.0.1:8765",
575
- token_env: "SHOPPING_BUYER_TOKEN",
576
- backend: "local_marketplace",
577
- },
578
- model: {
579
- provider,
580
- model,
581
- api_key_env: apiKeyEnv,
582
- ...(baseUrl !== undefined ? { base_url: baseUrl } : {}),
583
- },
584
- runtime: {
585
- mode: "once",
586
- poll_interval_seconds: 5,
587
- turn_timeout_seconds: 90,
588
- max_model_steps: 4,
589
- max_retries: 2,
590
- },
591
- buyer_policy: {
592
- target_skus: [],
593
- quantity: 1,
594
- max_total_price_private: 1_000_000,
595
- acceptable_eta_latest: "2099-12-31T23:59:59+08:00",
596
- required_after_sales_terms: [],
597
- auto_negotiate: true,
598
- human_review_on: [],
599
- },
600
- };
601
- }
602
639
  /**
603
640
  * Main conversation (v0.3.0-A): build the AgentKernel for a profile.
604
641
  * provider=fake runs the deterministic offline chat fake; real providers
605
642
  * resolve models and auth through pi-ai's built-in provider catalog.
606
643
  * `catalog` 是 agent catalog base URL(buyer 的 negotiate_buyer_task 发现商家用)。
607
644
  */
608
- async function buildChatKernel(profile, dataDir, catalog) {
609
- const paths = ensurePathsForDir(dataDir ?? agentDataDir(profile.agent_id));
610
- let models;
611
- let model;
612
- let connector;
613
- let thinkingLevel;
614
- let merchantClient;
615
- let broker;
616
- let commerceClient;
617
- if (isFakeProvider(profile)) {
618
- ({ models, model } = createFakeChatModels());
619
- if (profile.role === "buyer") {
620
- const { FakeCommerceConnector, fakeConnectorProduct } = await import("./agent/connector/fake-connector.js");
621
- connector = new FakeCommerceConnector([
622
- fakeConnectorProduct(),
623
- fakeConnectorProduct({
624
- sku: "sku-002",
625
- title: "机制陶瓷杯",
626
- price: 59,
627
- stock: 0,
628
- merchant_id: "merchant-002",
629
- }),
630
- ]);
631
- }
632
- else {
633
- // Offline merchant chat: deterministic catalog client + dummy-scope
634
- // credentials so the capability pack is exercisable without a gateway.
635
- const { FakeMerchantClient, fakeMerchantProduct } = await import("./agent/merchant/fake-merchant-client.js");
636
- const { StaticCredentialBroker } = await import("./agent/merchant/credential-broker.js");
637
- merchantClient = new FakeMerchantClient({
638
- products: [fakeMerchantProduct()],
639
- });
640
- broker = new StaticCredentialBroker({
641
- negotiation: "fake-negotiation-token",
642
- catalog: "fake-catalog-token",
643
- inventory: "fake-inventory-token",
644
- });
645
- }
646
- }
647
- else {
648
- const { builtinModels } = await import("@earendil-works/pi-ai/providers/all");
649
- const collection = builtinModels();
650
- const found = collection.getModel(profile.model.provider, profile.model.model);
651
- if (found === undefined) {
652
- process.stderr.write(`no built-in model ${profile.model.provider}/${profile.model.model}; check the profile\n`);
653
- throw new ProfileError(`no built-in model ${profile.model.provider}/${profile.model.model}`);
654
- }
655
- models = collection;
656
- model = found;
657
- thinkingLevel = resolveThinkingLevel(profile);
658
- if (profile.role === "buyer") {
659
- const { ShoppingCliConnector } = await import("./agent/connector/http-connector.js");
660
- connector = new ShoppingCliConnector(profile.commerce.base_url, {
661
- buyerBootstrapToken: process.env.SHOPPING_BUYER_BOOTSTRAP_TOKEN,
662
- });
663
- }
664
- // Real gateway: negotiation client + scoped merchant client + broker.
665
- const { ProfileCredentialBroker } = await import("./agent/merchant/credential-broker.js");
666
- broker = new ProfileCredentialBroker(profile);
667
- try {
668
- commerceClient = buildClient(profile);
669
- }
670
- catch {
671
- // No negotiation token: negotiation tools fail closed at call time.
672
- // Catalog/inventory 只读工具走公开搜索端点,不依赖该 token,仍会挂载。
673
- commerceClient = undefined;
674
- if (profile.role === "merchant") {
675
- process.stderr.write(`[kiwi] 未设置 ${profile.commerce.token_env}:磋商工具停用(fail closed),` +
676
- "目录/库存只读工具仍可用。\n");
677
- }
678
- }
679
- if (profile.role === "merchant") {
680
- const { HttpMerchantClient } = await import("./agent/merchant/merchant-client.js");
681
- merchantClient = new HttpMerchantClient(profile.commerce.base_url, broker);
682
- }
683
- }
684
- // KIWI_MODE env: start the chat already in the given write mode (e.g.
685
- // autopilot for autonomous negotiation) — no manual /mode needed.
686
- const kiwiMode = process.env.KIWI_MODE;
687
- const mode = isAgentMode(kiwiMode) ? kiwiMode : undefined;
688
- if (kiwiMode !== undefined && mode === undefined) {
689
- process.stderr.write(`unknown KIWI_MODE ${kiwiMode}(可选 ${["manual", "supervised", "autopilot"].join("/")})\n`);
690
- }
691
- return AgentKernel.open({
692
- profile,
693
- paths,
694
- models,
695
- model,
696
- ...(mode !== undefined ? { mode } : {}),
697
- ...(connector !== undefined ? { connector } : {}),
698
- ...(commerceClient !== undefined ? { commerceClient } : {}),
699
- ...(merchantClient !== undefined ? { merchantClient } : {}),
700
- ...(broker !== undefined ? { broker } : {}),
701
- ...(catalog !== undefined ? { catalog } : {}),
702
- ...(thinkingLevel !== undefined ? { thinkingLevel } : {}),
703
- });
704
- }
705
645
  /** kiwi 包根目录(dist/cli.js 的上级)——全局 `kiwi` 在任意 cwd 也能解析示例 profile。 */
706
646
  function packageRoot() {
707
647
  const here = path.dirname(fileURLToPath(import.meta.url));
@@ -735,6 +675,7 @@ async function cmdChat(args) {
735
675
  const profile = args.profile !== undefined ? loadProfile(args.profile) : defaultChatProfile();
736
676
  // A2A 节点 + buyer 磋商工具共用一个 catalog:kernel 构建前先解析。
737
677
  const catalog = args.catalog ?? process.env.KIWI_CATALOG_URL ?? DEFAULT_CATALOG_URL;
678
+ const merchantToken = process.env.KIWI_MERCHANT_TOKEN || "";
738
679
  const kernel = await buildChatKernel(profile, args.dataDir, catalog);
739
680
  const kernels = [kernel];
740
681
  const reload = async (file) => {
@@ -758,6 +699,7 @@ async function cmdChat(args) {
758
699
  profile: p,
759
700
  catalog,
760
701
  preferredPort: args.port,
702
+ ...(merchantToken ? { ownerToken: merchantToken } : {}),
761
703
  ownerTokenSecret: process.env.KIWI_CATALOG_OWNER_TOKEN_SECRET,
762
704
  });
763
705
  process.stderr.write(`[a2a] ${node.role}@${node.url}${node.catalogAgentId !== undefined ? ` registered ${node.catalogAgentId}` : ""}\n`);
@@ -969,20 +911,25 @@ async function cmdMerchantPublish(args) {
969
911
  return EXIT.CONFIG;
970
912
  }
971
913
  const ownerTokenSecret = process.env.KIWI_CATALOG_OWNER_TOKEN_SECRET;
972
- if (!ownerTokenSecret) {
973
- process.stderr.write("KIWI_CATALOG_OWNER_TOKEN_SECRET 是必需的(owner token 派生,与 kiwi-catalog 一致)\n");
914
+ const merchantToken = process.env.KIWI_MERCHANT_TOKEN || "";
915
+ if (!merchantToken && !ownerTokenSecret) {
916
+ process.stderr.write("需要 KIWI_MERCHANT_TOKEN(随机 token,推荐)或 KIWI_CATALOG_OWNER_TOKEN_SECRET(legacy HMAC)\n");
974
917
  return EXIT.CONFIG;
975
918
  }
976
919
  const catalog = args.catalog ?? process.env.KIWI_CATALOG_URL ?? DEFAULT_CATALOG_URL;
977
920
  const report = await merchantPublish({
978
921
  profile,
979
922
  catalogBaseUrl: catalog,
980
- ownerTokenSecret,
923
+ ...(merchantToken ? { ownerToken: merchantToken } : {}),
924
+ ...(ownerTokenSecret ? { ownerTokenSecret } : {}),
981
925
  shoppingCliDb,
982
926
  ...(args.shoppingCliPath !== undefined ? { shoppingCliPath: args.shoppingCliPath } : {}),
983
927
  ...(args.shoppingCliMerchant !== undefined
984
928
  ? { shoppingCliMerchant: args.shoppingCliMerchant }
985
929
  : {}),
930
+ ...(args.allowEmptyProjectionReconcile
931
+ ? { allowEmptyProjectionReconcile: true }
932
+ : {}),
986
933
  });
987
934
  printJson(report);
988
935
  return report.ok ? EXIT.OK : EXIT.CONFIG;
@@ -1003,6 +950,10 @@ export async function main(argv = process.argv.slice(2)) {
1003
950
  const hasHelp = argv.includes("--help") || argv.includes("-h");
1004
951
  const firstArg = argv.find((a) => !a.startsWith("-"));
1005
952
  // 产品层命令组帮助优先于全局帮助(`kiwi buyer --help` → buyer 帮助)
953
+ if (hasHelp && firstArg === "weixin") {
954
+ process.stdout.write(weixinUsage());
955
+ return EXIT.OK;
956
+ }
1006
957
  if (hasHelp && (firstArg === "buyer" || firstArg === "merchant" || firstArg === "network")) {
1007
958
  const help = productHelp(firstArg);
1008
959
  if (help !== "") {
@@ -1050,6 +1001,22 @@ export async function main(argv = process.argv.slice(2)) {
1050
1001
  return await cmdTui(args);
1051
1002
  if (cmd === "chat")
1052
1003
  return await cmdChat(args);
1004
+ if (cmd === "weixin") {
1005
+ return await cmdWeixin({
1006
+ profile: args.profile,
1007
+ dataDir: args.dataDir,
1008
+ allow: args.weixinAllow,
1009
+ relogin: args.relogin,
1010
+ // 审查 P2-L:weixin 命令 A2A 节点默认关(headless 控制面收窄攻击面),
1011
+ // 仅 --a2a 显式开启(--no-a2a 仍可覆盖)。裸 kiwi/chat 命令的
1012
+ // A2A 默认开不受影响(各自按 noA2a 判断)。
1013
+ a2a: args.a2aExplicit && !args.noA2a,
1014
+ port: args.port,
1015
+ qrScale: args.qrScale,
1016
+ noQr: args.noQr,
1017
+ catalog: args.catalog,
1018
+ });
1019
+ }
1053
1020
  if (cmd === "init") {
1054
1021
  const result = await runInit({
1055
1022
  dir: requireDir(args),
@@ -1088,6 +1055,24 @@ export async function main(argv = process.argv.slice(2)) {
1088
1055
  printJson(computeHandoffMetrics(byNegotiation));
1089
1056
  return EXIT.OK;
1090
1057
  }
1058
+ if (cmd === "catalog") {
1059
+ // `kiwi catalog serve` —— 前台启动独立 kiwi-catalog 服务(CURRENT-DOCS)。
1060
+ const sub = args.command[1] ?? "";
1061
+ if (sub === "serve") {
1062
+ const result = catalogServe({
1063
+ db: args.catalogDb,
1064
+ host: args.catalogHost,
1065
+ port: args.port,
1066
+ });
1067
+ if (!result.ok) {
1068
+ process.stderr.write(`${result.error ?? "kiwi-catalog 启动失败"}\n`);
1069
+ return EXIT.CONFIG;
1070
+ }
1071
+ return EXIT.OK;
1072
+ }
1073
+ process.stderr.write("usage: kiwi catalog serve [--db <file>] [--host <host>] [--port <port>]\n");
1074
+ return EXIT.CONFIG;
1075
+ }
1091
1076
  if (cmd === "down") {
1092
1077
  const result = await runDown(requireDir(args));
1093
1078
  printJson(result);