@harrylabsj/kiwi 0.7.22 → 0.8.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.
Files changed (140) hide show
  1. package/README.md +45 -10
  2. package/dist/a2a/node.js +1 -1
  3. package/dist/a2a/node.js.map +1 -1
  4. package/dist/agent/buyer/buyer-tools.js +15 -8
  5. package/dist/agent/buyer/buyer-tools.js.map +1 -1
  6. package/dist/agent/buyer/scheduler.d.ts +1 -0
  7. package/dist/agent/buyer/scheduler.js +5 -1
  8. package/dist/agent/buyer/scheduler.js.map +1 -1
  9. package/dist/agent/buyer/task-store.js +5 -2
  10. package/dist/agent/buyer/task-store.js.map +1 -1
  11. package/dist/agent/chat-tools.js +10 -3
  12. package/dist/agent/chat-tools.js.map +1 -1
  13. package/dist/agent/context/fencing.d.ts +27 -0
  14. package/dist/agent/context/fencing.js +126 -0
  15. package/dist/agent/context/fencing.js.map +1 -0
  16. package/dist/agent/context/grounding.d.ts +18 -0
  17. package/dist/agent/context/grounding.js +22 -0
  18. package/dist/agent/context/grounding.js.map +1 -0
  19. package/dist/agent/host/events.d.ts +31 -0
  20. package/dist/agent/host/events.js +62 -0
  21. package/dist/agent/host/events.js.map +1 -0
  22. package/dist/agent/host/tui-renderer.d.ts +17 -0
  23. package/dist/agent/host/tui-renderer.js +125 -0
  24. package/dist/agent/host/tui-renderer.js.map +1 -0
  25. package/dist/agent/host/web-renderer.d.ts +71 -0
  26. package/dist/agent/host/web-renderer.js +185 -0
  27. package/dist/agent/host/web-renderer.js.map +1 -0
  28. package/dist/agent/kernel-builder.d.ts +3 -1
  29. package/dist/agent/kernel-builder.js +4 -1
  30. package/dist/agent/kernel-builder.js.map +1 -1
  31. package/dist/agent/kernel.d.ts +20 -1
  32. package/dist/agent/kernel.js +251 -14
  33. package/dist/agent/kernel.js.map +1 -1
  34. package/dist/agent/merchant/intelligence/backend.d.ts +40 -0
  35. package/dist/agent/merchant/intelligence/backend.js +2 -0
  36. package/dist/agent/merchant/intelligence/backend.js.map +1 -0
  37. package/dist/agent/merchant/intelligence/default-backend.d.ts +38 -0
  38. package/dist/agent/merchant/intelligence/default-backend.js +454 -0
  39. package/dist/agent/merchant/intelligence/default-backend.js.map +1 -0
  40. package/dist/agent/merchant/intelligence/http-analytics-source.d.ts +55 -0
  41. package/dist/agent/merchant/intelligence/http-analytics-source.js +193 -0
  42. package/dist/agent/merchant/intelligence/http-analytics-source.js.map +1 -0
  43. package/dist/agent/merchant/intelligence/types.d.ts +109 -0
  44. package/dist/agent/merchant/intelligence/types.js +2 -0
  45. package/dist/agent/merchant/intelligence/types.js.map +1 -0
  46. package/dist/agent/merchant/merchant-enrichment.d.ts +10 -0
  47. package/dist/agent/merchant/merchant-enrichment.js +38 -0
  48. package/dist/agent/merchant/merchant-enrichment.js.map +1 -0
  49. package/dist/agent/merchant/merchant-grounding.d.ts +5 -0
  50. package/dist/agent/merchant/merchant-grounding.js +76 -0
  51. package/dist/agent/merchant/merchant-grounding.js.map +1 -0
  52. package/dist/agent/merchant/merchant-presentations.d.ts +8 -0
  53. package/dist/agent/merchant/merchant-presentations.js +184 -0
  54. package/dist/agent/merchant/merchant-presentations.js.map +1 -0
  55. package/dist/agent/merchant/merchant-tools.d.ts +8 -0
  56. package/dist/agent/merchant/merchant-tools.js +208 -9
  57. package/dist/agent/merchant/merchant-tools.js.map +1 -1
  58. package/dist/agent/presentation/registry.d.ts +14 -0
  59. package/dist/agent/presentation/registry.js +24 -0
  60. package/dist/agent/presentation/registry.js.map +1 -0
  61. package/dist/agent/presentation/runner.d.ts +11 -0
  62. package/dist/agent/presentation/runner.js +17 -0
  63. package/dist/agent/presentation/runner.js.map +1 -0
  64. package/dist/agent/presentation/sanitize.d.ts +16 -0
  65. package/dist/agent/presentation/sanitize.js +37 -0
  66. package/dist/agent/presentation/sanitize.js.map +1 -0
  67. package/dist/agent/presentation/types.d.ts +28 -0
  68. package/dist/agent/presentation/types.js +8 -0
  69. package/dist/agent/presentation/types.js.map +1 -0
  70. package/dist/agent/session.d.ts +1 -1
  71. package/dist/agent/session.js +2 -2
  72. package/dist/agent/session.js.map +1 -1
  73. package/dist/agent/skills/registry.d.ts +11 -0
  74. package/dist/agent/skills/registry.js +119 -0
  75. package/dist/agent/skills/registry.js.map +1 -0
  76. package/dist/agent/skills/tools.d.ts +6 -0
  77. package/dist/agent/skills/tools.js +36 -0
  78. package/dist/agent/skills/tools.js.map +1 -0
  79. package/dist/agent/skills/types.d.ts +9 -0
  80. package/dist/agent/skills/types.js +2 -0
  81. package/dist/agent/skills/types.js.map +1 -0
  82. package/dist/agent/supplier/scheduler.d.ts +2 -0
  83. package/dist/agent/supplier/scheduler.js +101 -22
  84. package/dist/agent/supplier/scheduler.js.map +1 -1
  85. package/dist/agent/supplier/store.d.ts +2 -0
  86. package/dist/agent/supplier/store.js +44 -0
  87. package/dist/agent/supplier/store.js.map +1 -1
  88. package/dist/agent/system-prompt.d.ts +5 -1
  89. package/dist/agent/system-prompt.js +20 -3
  90. package/dist/agent/system-prompt.js.map +1 -1
  91. package/dist/cli.js +34 -14
  92. package/dist/cli.js.map +1 -1
  93. package/dist/config/profile.d.ts +21 -0
  94. package/dist/config/profile.js +59 -0
  95. package/dist/config/profile.js.map +1 -1
  96. package/dist/discovery/catalog-source/kiwi-record.d.ts +2 -0
  97. package/dist/discovery/catalog-source/kiwi-record.js.map +1 -1
  98. package/dist/discovery/catalog-source/kiwi-source.js +2 -0
  99. package/dist/discovery/catalog-source/kiwi-source.js.map +1 -1
  100. package/dist/handoff/authorization.js +1 -1
  101. package/dist/http/merchant-server.d.ts +65 -0
  102. package/dist/http/merchant-server.js +346 -0
  103. package/dist/http/merchant-server.js.map +1 -0
  104. package/dist/merchant/stats-store.d.ts +7 -0
  105. package/dist/merchant/stats-store.js +11 -0
  106. package/dist/merchant/stats-store.js.map +1 -1
  107. package/dist/negotiation/action-candidate.js +1 -1
  108. package/dist/operator/types.d.ts +1 -1
  109. package/dist/product-cli.d.ts +2 -2
  110. package/dist/product-cli.js +5 -3
  111. package/dist/product-cli.js.map +1 -1
  112. package/dist/product-supplier.d.ts +73 -0
  113. package/dist/product-supplier.js +224 -5
  114. package/dist/product-supplier.js.map +1 -1
  115. package/dist/runtime/fake-model.d.ts +1 -1
  116. package/dist/trust/identity/jwk.d.ts +11 -0
  117. package/dist/trust/identity/jwk.js +2 -0
  118. package/dist/trust/identity/jwk.js.map +1 -0
  119. package/dist/trust/identity/jws.d.ts +2 -1
  120. package/dist/trust/identity/jws.js.map +1 -1
  121. package/dist/trust/identity/keys.d.ts +2 -1
  122. package/dist/trust/identity/keys.js.map +1 -1
  123. package/dist/trust/identity/message-signature.d.ts +1 -14
  124. package/dist/trust/identity/message-signature.js.map +1 -1
  125. package/dist/weixin/channel.d.ts +3 -0
  126. package/dist/weixin/channel.js +18 -2
  127. package/dist/weixin/channel.js.map +1 -1
  128. package/examples/profiles/merchant.deepseek.yaml +6 -0
  129. package/examples/profiles/merchant.fake.yaml +6 -0
  130. package/examples/profiles/merchant.local.yaml +6 -0
  131. package/package.json +7 -6
  132. package/skills/kiwi-buyer/SKILL.md +66 -0
  133. package/skills/merchant/catalog-operations/SKILL.md +17 -0
  134. package/skills/merchant/change-approval/SKILL.md +16 -0
  135. package/skills/merchant/human-review/SKILL.md +17 -0
  136. package/skills/merchant/inventory-operations/SKILL.md +17 -0
  137. package/skills/merchant/negotiation-review/SKILL.md +18 -0
  138. package/skills/merchant/performance-insights/SKILL.md +19 -0
  139. package/contracts/candidate-agent-dto-1.0.README.md +0 -43
  140. package/contracts/conformance/python-service/README.md +0 -19
@@ -29,6 +29,7 @@
29
29
  import { AgentHarness } from "@earendil-works/pi-agent-core";
30
30
  import { existsSync, readFileSync, rmSync } from "node:fs";
31
31
  import path from "node:path";
32
+ import { fileURLToPath } from "node:url";
32
33
  import { ensureAgentPaths, openAgentDatabase } from "./agent-db.js";
33
34
  import { HandoffEventStore, HandoffIdempotencyStore, foldCandidateLifecycle, deliveryState, recordLaunch, recordOpenEvidence, } from "../handoff/index.js";
34
35
  import { buildBuyerTools } from "./buyer/buyer-tools.js";
@@ -41,6 +42,14 @@ import { buildMemoryTools } from "./chat-tools.js";
41
42
  import { AGENT_MODES, DEFAULT_AGENT_MODE, isAgentMode } from "./mode.js";
42
43
  import { WriteApprovalCandidateStore, executeApprovedCandidate, } from "./merchant/action-candidate.js";
43
44
  import { buildMerchantTools } from "./merchant/merchant-tools.js";
45
+ import { publicCandidatePreview } from "./merchant/merchant-enrichment.js";
46
+ import { DefaultMerchantIntelligenceBackend } from "./merchant/intelligence/default-backend.js";
47
+ import { emitHostEvent, sanitizeHostEventData, SerializedEventSink } from "./host/events.js";
48
+ import { fenceModelPayload } from "./context/fencing.js";
49
+ import { groundingReads } from "./context/grounding.js";
50
+ import { MERCHANT_GROUNDING_RULES, merchantGroundingContext } from "./merchant/merchant-grounding.js";
51
+ import { SkillRegistry } from "./skills/registry.js";
52
+ import { buildSkillTools } from "./skills/tools.js";
44
53
  import { DeterministicNegotiationRunner } from "../operator/runner.js";
45
54
  import { MemoryStore } from "./memory/store.js";
46
55
  import { MemoryError } from "./memory/types.js";
@@ -48,7 +57,7 @@ import { PrivateVault } from "./memory/vault.js";
48
57
  import { openMainSession } from "./session.js";
49
58
  import { registerCatalogAgent } from "../discovery/catalog-source/register.js";
50
59
  import { KiwiCatalogSource } from "../discovery/catalog-source/kiwi-source.js";
51
- import { baseSystemPrompt, renderMemoryBriefing } from "./system-prompt.js";
60
+ import { baseSystemPrompt, combineDynamicBriefing, renderMemoryBriefing } from "./system-prompt.js";
52
61
  export const MAIN_SESSION_ID = "main";
53
62
  /** 审查 P2-I:autopilot 对网关权威门持续拒绝的消息——连续失败上限与冷却窗口。 */
54
63
  export const AUTOPILOT_MAX_SUBMIT_FAILURES = 3;
@@ -97,6 +106,61 @@ function assistantText(message) {
97
106
  .join("\n")
98
107
  .trim();
99
108
  }
109
+ const PRIVATE_TOOL_RESULT_NAMES = new Set(["view_private_thresholds"]);
110
+ export function toolResultSummary(toolName, result) {
111
+ if (PRIVATE_TOOL_RESULT_NAMES.has(toolName))
112
+ return "工具调用已完成。";
113
+ if (result !== null && typeof result === "object") {
114
+ const content = result.content;
115
+ if (Array.isArray(content)) {
116
+ const text = content
117
+ .filter((item) => item !== null && typeof item === "object" &&
118
+ item.type === "text" &&
119
+ typeof item.text === "string")
120
+ .map((item) => item.text)
121
+ .join("\n")
122
+ .trim();
123
+ if (text !== "")
124
+ return text.slice(0, 1_000);
125
+ }
126
+ }
127
+ return "工具调用已完成。";
128
+ }
129
+ /** Bridge the stable AgentHarness stream/tool hooks into the optional host protocol. */
130
+ function attachHarnessHostEvents(harness, emitEvent) {
131
+ return harness.subscribe(async (event) => {
132
+ if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
133
+ if (event.assistantMessageEvent.delta !== "") {
134
+ await emitEvent("text_delta", { text: event.assistantMessageEvent.delta });
135
+ }
136
+ return;
137
+ }
138
+ if (event.type === "tool_execution_start") {
139
+ await emitEvent("tool_call", {
140
+ tool: event.toolName,
141
+ call_id: event.toolCallId,
142
+ input: event.args,
143
+ });
144
+ return;
145
+ }
146
+ if (event.type === "tool_execution_update") {
147
+ await emitEvent("ui_partial", {
148
+ tool: event.toolName,
149
+ call_id: event.toolCallId,
150
+ partial: event.partialResult,
151
+ });
152
+ return;
153
+ }
154
+ if (event.type === "tool_execution_end") {
155
+ await emitEvent("tool_result", {
156
+ tool: event.toolName,
157
+ call_id: event.toolCallId,
158
+ status: event.isError ? "error" : "ok",
159
+ summary: toolResultSummary(event.toolName, event.result),
160
+ });
161
+ }
162
+ });
163
+ }
100
164
  /** The owner's own Restricted memory values (Vault). Owner-only by isolation. */
101
165
  function listRestrictedValues(store) {
102
166
  const out = [];
@@ -212,6 +276,11 @@ export class AgentKernel {
212
276
  commerceClient;
213
277
  merchantClient;
214
278
  broker;
279
+ merchantIntelligence;
280
+ /** Optional host event projection; it never owns business state. */
281
+ emitEvent;
282
+ /** Removes the optional AgentHarness stream bridge during kernel shutdown. */
283
+ unsubscribeHarnessEvents;
215
284
  /** Shared mutable mode ref (tools read it via a getter before the kernel exists). */
216
285
  modeRef = { value: DEFAULT_AGENT_MODE };
217
286
  /** Live execution hooks for pending candidates (v0.3.0-C /approve). */
@@ -266,6 +335,10 @@ export class AgentKernel {
266
335
  this.merchantClient = options.merchantClient;
267
336
  if (options.broker !== undefined)
268
337
  this.broker = options.broker;
338
+ if (options.merchantIntelligence !== undefined)
339
+ this.merchantIntelligence = options.merchantIntelligence;
340
+ this.emitEvent = options.emitEvent;
341
+ this.unsubscribeHarnessEvents = options.unsubscribeHarnessEvents;
269
342
  if (options.modeRef !== undefined)
270
343
  this.modeRef = options.modeRef;
271
344
  this.pendingHooks = options.pendingHooks ?? new Map();
@@ -294,7 +367,7 @@ export class AgentKernel {
294
367
  store.bindPrincipal(principal.principal_id);
295
368
  let session;
296
369
  try {
297
- session = await openMainSession(paths);
370
+ session = await openMainSession(paths, options.eventSessionId ?? MAIN_SESSION_ID);
298
371
  }
299
372
  catch (err) {
300
373
  db.close(); // never leak the SQLite handle on a fail-closed open
@@ -307,14 +380,38 @@ export class AgentKernel {
307
380
  const modeRef = { value: options.mode ?? DEFAULT_AGENT_MODE };
308
381
  const pendingHooks = new Map();
309
382
  const turnId = { current: MAIN_SESSION_ID };
310
- const registerPending = (id, hooks) => {
383
+ const registerPendingHooks = (id, hooks) => {
311
384
  pendingHooks.set(id, hooks);
312
385
  };
386
+ const hostSequence = { value: 0 };
387
+ const hostSink = options.eventSink === undefined ? undefined : new SerializedEventSink(options.eventSink);
388
+ const emitEvent = options.eventSink === undefined
389
+ ? undefined
390
+ : async (type, data) => {
391
+ hostSequence.value += 1;
392
+ await emitHostEvent(hostSink, {
393
+ eventId: `host-${hostSequence.value}`,
394
+ sessionId: options.eventSessionId ?? MAIN_SESSION_ID,
395
+ sequence: hostSequence.value,
396
+ type,
397
+ occurredAt: clock(),
398
+ data: sanitizeHostEventData(type, data),
399
+ });
400
+ };
313
401
  const approvals = new WriteApprovalCandidateStore({
314
402
  db,
315
403
  principalId: principal.principal_id,
316
404
  now: clock,
317
405
  });
406
+ const registerPending = (id, hooks) => {
407
+ registerPendingHooks(id, hooks);
408
+ const candidate = approvals.get(id);
409
+ void emitEvent?.("candidate_update", {
410
+ candidate_id: id,
411
+ status: candidate?.status ?? "pending_approval",
412
+ preview: publicCandidatePreview(candidate),
413
+ });
414
+ };
318
415
  // Execution hooks are deliberately process-local. Invalidate durable
319
416
  // candidates before wiring tools so a restarted kernel never exposes a
320
417
  // /pending item that its /approve path cannot execute.
@@ -324,6 +421,14 @@ export class AgentKernel {
324
421
  let supplierScheduler;
325
422
  let buyerTools = [];
326
423
  let merchantTools = [];
424
+ let merchantSkillTools = [];
425
+ let merchantIntelligence;
426
+ const merchantSkillRegistry = options.profile.role === "merchant" &&
427
+ options.profile.merchant_experience?.enabled === true &&
428
+ options.profile.merchant_experience.skills === true
429
+ ? SkillRegistry.fromDir(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../skills/merchant"), "merchant")
430
+ : new SkillRegistry([]);
431
+ merchantSkillTools = buildSkillTools(merchantSkillRegistry);
327
432
  let handoffRuntime;
328
433
  if (options.profile.role === "buyer" && options.connector !== undefined) {
329
434
  taskStore = new BuyerTaskStore({
@@ -359,6 +464,7 @@ export class AgentKernel {
359
464
  ? { catalogSource: new KiwiCatalogSource({ baseUrl: options.catalog }) }
360
465
  : {}),
361
466
  trustStore: new TrustRecordStore({ dir: path.dirname(paths.db), now: clock }),
467
+ ...(options.allowLoopback === true ? { allowLoopback: true } : {}),
362
468
  });
363
469
  // v0.7.0 KTH:handoff 存储(Ledger 事件 + 执行幂等)落在 agent data dir,
364
470
  // 注入 buyer 工具(handoff_agreement 工具挂载)。
@@ -393,6 +499,20 @@ export class AgentKernel {
393
499
  else if (options.profile.role === "merchant" &&
394
500
  options.merchantClient !== undefined &&
395
501
  options.broker !== undefined) {
502
+ if (options.profile.merchant_experience?.enabled === true &&
503
+ options.profile.merchant_experience.intelligence !== false) {
504
+ merchantIntelligence = new DefaultMerchantIntelligenceBackend({
505
+ merchant_id: principal.owner_id,
506
+ data_dir: path.dirname(paths.db),
507
+ principal_id: principal.principal_id,
508
+ merchant_client: options.merchantClient,
509
+ approvals,
510
+ ...(options.merchantAnalyticsSource !== undefined
511
+ ? { analytics_source: options.merchantAnalyticsSource }
512
+ : {}),
513
+ now: clock,
514
+ });
515
+ }
396
516
  merchantTools = buildMerchantTools({
397
517
  profile: options.profile,
398
518
  merchantClient: options.merchantClient,
@@ -400,17 +520,20 @@ export class AgentKernel {
400
520
  // fail closed,目录/库存只读与审批式写工具照常可用(见 §15.3/§15.4)。
401
521
  ...(options.commerceClient !== undefined ? { commerceClient: options.commerceClient } : {}),
402
522
  broker: options.broker,
523
+ principalId: principal.principal_id,
403
524
  approvals,
404
525
  mode: () => modeRef.value,
405
526
  registerPending,
406
527
  now: clock,
407
528
  privateValues: () => listRestrictedValues(store),
529
+ ...(merchantIntelligence !== undefined ? { intelligence: merchantIntelligence } : {}),
530
+ ...(emitEvent !== undefined ? { emitEvent } : {}),
408
531
  // 商家 A2A 节点 ledger 基础目录(<dataDir>/a2a)——LedgerStore 会再拼
409
532
  // /ledger 到 <dataDir>/a2a/ledger,list_a2a_negotiations 用。
410
533
  a2aLedgerDir: path.join(path.dirname(paths.db), "a2a"),
411
534
  });
412
535
  }
413
- const base = baseSystemPrompt(options.profile, principal);
536
+ const base = baseSystemPrompt(options.profile, principal, merchantSkillRegistry.names.length > 0 ? merchantSkillRegistry : undefined);
414
537
  let briefing;
415
538
  const harness = new AgentHarness({
416
539
  session,
@@ -420,12 +543,19 @@ export class AgentKernel {
420
543
  ...buildMemoryTools(store, { turnId: () => turnId.current }),
421
544
  ...buyerTools,
422
545
  ...merchantTools,
546
+ ...merchantSkillTools,
423
547
  ],
424
548
  systemPrompt: async () => (briefing === undefined ? base : `${base}\n\n${briefing}`),
425
549
  // §18.1: a hung model/provider request must NOT wedge the chat forever —
426
550
  // abort after the profile's turn timeout and surface an error text.
427
551
  streamOptions: {
428
552
  timeoutMs: options.profile.runtime.turn_timeout_seconds * 1000,
553
+ ...(options.profile.merchant_experience?.prompt_cache_retention !== undefined
554
+ ? {
555
+ cacheRetention: options.profile.merchant_experience.prompt_cache_retention,
556
+ sessionId: options.eventSessionId ?? MAIN_SESSION_ID,
557
+ }
558
+ : {}),
429
559
  },
430
560
  ...(options.thinkingLevel !== undefined ? { thinkingLevel: options.thinkingLevel } : {}),
431
561
  });
@@ -435,6 +565,9 @@ export class AgentKernel {
435
565
  if (options.model !== undefined) {
436
566
  await harness.setModel(options.model);
437
567
  }
568
+ const unsubscribeHarnessEvents = emitEvent === undefined
569
+ ? undefined
570
+ : attachHarnessHostEvents(harness, emitEvent);
438
571
  const kernel = new AgentKernel({
439
572
  profile: options.profile,
440
573
  paths,
@@ -455,6 +588,9 @@ export class AgentKernel {
455
588
  ...(options.commerceClient !== undefined ? { commerceClient: options.commerceClient } : {}),
456
589
  ...(options.merchantClient !== undefined ? { merchantClient: options.merchantClient } : {}),
457
590
  ...(options.broker !== undefined ? { broker: options.broker } : {}),
591
+ ...(merchantIntelligence !== undefined ? { merchantIntelligence } : {}),
592
+ emitEvent,
593
+ ...(unsubscribeHarnessEvents !== undefined ? { unsubscribeHarnessEvents } : {}),
458
594
  });
459
595
  kernel.briefingSetter = (value) => {
460
596
  briefing = value;
@@ -605,7 +741,7 @@ export class AgentKernel {
605
741
  /**
606
742
  * Change the runtime mode. Switching INTO autopilot requires an explicit
607
743
  * `confirm` — the same fail-closed rule as the operator control plane
608
- * (docs/operator-tui-v0.2.md §8).
744
+ * (operator control-plane design §8).
609
745
  */
610
746
  setMode(mode, options) {
611
747
  if (!isAgentMode(mode)) {
@@ -658,17 +794,26 @@ export class AgentKernel {
658
794
  if (candidate === undefined) {
659
795
  throw new MemoryError("validation", `未知审批候选 ${candidateId}`);
660
796
  }
797
+ const report = async (result) => {
798
+ const current = this.approvals?.get(candidateId);
799
+ await this.emitEvent?.("candidate_update", {
800
+ candidate_id: candidateId,
801
+ status: current?.status ?? result.candidate.status,
802
+ preview: publicCandidatePreview(current ?? result.candidate),
803
+ });
804
+ return result;
805
+ };
661
806
  // manual 模式语义(评审项 P3-3):manual = advice only(never executes)。
662
807
  // routeWriteCandidate 在 manual 分支仍注册执行钩子(供 /pending 显示与
663
808
  // /revise 重算),但批准路径必须拒绝——此前 /approve 绕过模式直接执行,
664
809
  // 与 operator 平面分叉(controller 对 advice_only 候选明确拒绝批准:
665
810
  // "manual 模式只提供建议,不自动提交")。
666
811
  if (this.getMode() === "manual") {
667
- return {
812
+ return report({
668
813
  kind: "not_approvable",
669
814
  candidate,
670
815
  reason: "manual 模式只提供建议,不自动执行(/approve 拒绝 advice-only 候选)",
671
- };
816
+ });
672
817
  }
673
818
  const hooks = this.pendingHooks.get(candidateId);
674
819
  if (hooks === undefined) {
@@ -676,19 +821,19 @@ export class AgentKernel {
676
821
  // candidate cannot be re-validated against the current marketplace.
677
822
  if (candidate.status === "expired") {
678
823
  this.releasePending(candidateId);
679
- return { kind: "expired", candidate };
824
+ return report({ kind: "expired", candidate });
680
825
  }
681
826
  if (candidate.status !== "pending_approval" && candidate.status !== "approved") {
682
827
  this.releasePending(candidateId);
683
- return {
828
+ return report({
684
829
  kind: "not_approvable",
685
830
  candidate,
686
831
  reason: `候选 ${candidateId} 状态为 ${candidate.status},不可批准。`,
687
- };
832
+ });
688
833
  }
689
834
  const expired = this.approvals.expireCandidate(candidateId);
690
835
  this.releasePending(candidateId);
691
- return { kind: "expired", candidate: expired };
836
+ return report({ kind: "expired", candidate: expired });
692
837
  }
693
838
  this.approvals.markApproved(candidateId);
694
839
  const outcome = await executeApprovedCandidate(this.approvals, candidateId, hooks);
@@ -697,7 +842,7 @@ export class AgentKernel {
697
842
  if (outcome.kind !== "not_approvable") {
698
843
  this.releasePending(candidateId);
699
844
  }
700
- return outcome;
845
+ return report(outcome);
701
846
  }
702
847
  /** Reject a pending/advice-only WriteApprovalCandidate. Never executes. */
703
848
  rejectCandidate(candidateId) {
@@ -711,6 +856,11 @@ export class AgentKernel {
711
856
  }
712
857
  this.approvals.reject(candidateId);
713
858
  this.releasePending(candidateId);
859
+ void this.emitEvent?.("candidate_update", {
860
+ candidate_id: candidateId,
861
+ status: this.approvals.get(candidateId)?.status ?? "rejected",
862
+ preview: publicCandidatePreview(this.approvals.get(candidateId)),
863
+ });
714
864
  return { ok: true };
715
865
  }
716
866
  /**
@@ -728,6 +878,7 @@ export class AgentKernel {
728
878
  this.handoffRuntime?.ledger.sweepExpiredHandoffs(this.clock());
729
879
  const empty = {
730
880
  checked_rules: 0,
881
+ requests_used: 0,
731
882
  notifications: [],
732
883
  tasks_searched: [],
733
884
  tasks_expired: [],
@@ -737,8 +888,9 @@ export class AgentKernel {
737
888
  // M1:供应商关系 pull tick 走同一 kernel 串行链,共享请求预算;
738
889
  // supplier tick 失败不影响 task tick 结果(错误进 supplier.errors)。
739
890
  if (this.supplierScheduler !== undefined) {
891
+ const totalBudget = budget.max_requests ?? 20;
740
892
  result.supplier = await this.supplierScheduler.tick({
741
- ...(budget.max_requests !== undefined ? { max_requests: budget.max_requests } : {}),
893
+ max_requests: Math.max(0, totalBudget - result.requests_used),
742
894
  });
743
895
  }
744
896
  return result;
@@ -921,6 +1073,71 @@ export class AgentKernel {
921
1073
  timestamp: Date.now(),
922
1074
  });
923
1075
  }
1076
+ async runMerchantGroundingRead(read) {
1077
+ await this.emitEvent?.("grounding_started", {
1078
+ rule: read.tool,
1079
+ tool: read.tool,
1080
+ reason: read.reason,
1081
+ });
1082
+ try {
1083
+ let payload;
1084
+ if (read.tool === "get_business_snapshot" && this.merchantIntelligence !== undefined) {
1085
+ payload = await this.merchantIntelligence.getBusinessSnapshot({ merchant_id: this.principal.owner_id });
1086
+ }
1087
+ else if (read.tool === "get_negotiation_digest" && this.merchantIntelligence !== undefined) {
1088
+ payload = await this.merchantIntelligence.getNegotiationDigest({
1089
+ merchant_id: this.principal.owner_id,
1090
+ status: "active",
1091
+ limit: 20,
1092
+ });
1093
+ }
1094
+ else if (read.tool === "get_pending_actions" && this.merchantIntelligence !== undefined) {
1095
+ payload = await this.merchantIntelligence.getPendingActions();
1096
+ }
1097
+ else if (read.tool === "get_catalog_health" && this.merchantIntelligence !== undefined) {
1098
+ payload = await this.merchantIntelligence.getCatalogHealth({ merchant_id: this.principal.owner_id });
1099
+ }
1100
+ else if (read.tool === "get_human_review_queue" && this.merchantClient !== undefined) {
1101
+ payload = await this.merchantClient.getHumanReviewQueue(this.principal.owner_id);
1102
+ }
1103
+ else if (read.tool === "list_catalog_products" && this.merchantClient !== undefined) {
1104
+ payload = await this.merchantClient.listProducts(this.principal.owner_id);
1105
+ }
1106
+ else {
1107
+ throw new Error("grounding dependency is unavailable");
1108
+ }
1109
+ await this.emitEvent?.("grounding_completed", {
1110
+ rule: read.tool,
1111
+ tool: read.tool,
1112
+ status: "ok",
1113
+ });
1114
+ return [
1115
+ `[Grounding · ${read.reason}]`,
1116
+ fenceModelPayload(read.tool === "get_negotiation_digest" ? "a2a_message" : "merchant_api", payload, { maxChars: this.profile.merchant_experience?.max_external_context_chars ?? 12_000 }),
1117
+ ].join("\n");
1118
+ }
1119
+ catch {
1120
+ await this.emitEvent?.("grounding_completed", {
1121
+ rule: read.tool,
1122
+ tool: read.tool,
1123
+ status: "error",
1124
+ retryable: true,
1125
+ });
1126
+ return `[Grounding · ${read.reason}] 当前权威读取不可用,不要把缺失数据当作零值。`;
1127
+ }
1128
+ }
1129
+ /** Run at most two deterministic, read-only merchant grounding reads per turn. */
1130
+ async merchantGrounding(text) {
1131
+ if (this.profile.role !== "merchant" ||
1132
+ this.profile.merchant_experience?.enabled !== true ||
1133
+ this.profile.merchant_experience.grounding === false) {
1134
+ return undefined;
1135
+ }
1136
+ const reads = groundingReads(MERCHANT_GROUNDING_RULES, merchantGroundingContext(this.profile, text), 2);
1137
+ if (reads.length === 0)
1138
+ return undefined;
1139
+ return (await Promise.all(reads.map((read) => this.runMerchantGroundingRead(read)))).join("\n\n");
1140
+ }
924
1141
  /**
925
1142
  * 把一轮 A2A 磋商结果写入记忆(episode namespace,active 无需人工确认):
926
1143
  * 持久上下文,`/why` 可查、跨重启可恢复。
@@ -935,12 +1152,16 @@ export class AgentKernel {
935
1152
  if (text.startsWith("/")) {
936
1153
  return await this.handleSlash(text);
937
1154
  }
1155
+ const startedAt = Date.now();
1156
+ await this.emitEvent?.("message", { role: "user", text });
938
1157
  const memories = this.store.retrieve({
939
1158
  session_id: MAIN_SESSION_ID,
940
1159
  purpose: "clarify",
941
1160
  text,
942
1161
  });
943
- this.briefingSetter(renderMemoryBriefing(memories));
1162
+ const memoryBriefing = renderMemoryBriefing(memories);
1163
+ const groundingBriefing = await this.merchantGrounding(text);
1164
+ this.briefingSetter(combineDynamicBriefing([memoryBriefing, groundingBriefing]));
944
1165
  // Advance the turn id so several remember calls inside this one turn
945
1166
  // dedup into a single evidence piece (§9.3).
946
1167
  this.turnId.current = `${MAIN_SESSION_ID}:${++this.turnSeq}`;
@@ -950,20 +1171,35 @@ export class AgentKernel {
950
1171
  // §18.1: a model failure (throw OR empty response) must leave the
951
1172
  // conversation and TUI intact.
952
1173
  if (reply === "") {
1174
+ await this.emitEvent?.("error", {
1175
+ code: "empty_model_response",
1176
+ message: "model returned an empty response",
1177
+ retryable: true,
1178
+ });
953
1179
  return {
954
1180
  text: "暂时没能给出有效回复——当前目录里可能没有匹配的商品或商家,也可能是这条需求还不够具体。补充品牌、型号或预算范围后再问我一次即可。",
955
1181
  quit: false,
956
1182
  };
957
1183
  }
1184
+ await this.emitEvent?.("message", { role: "assistant", text: reply });
958
1185
  return { text: reply, quit: false };
959
1186
  }
960
1187
  catch (err) {
1188
+ await this.emitEvent?.("error", {
1189
+ code: "model_turn_failed",
1190
+ message: err instanceof Error ? err.message : String(err),
1191
+ retryable: true,
1192
+ });
961
1193
  return {
962
1194
  text: `模型处理失败:${err instanceof Error ? err.message : String(err)}。对话状态未改变,请重试。`,
963
1195
  quit: false,
964
1196
  };
965
1197
  }
966
1198
  finally {
1199
+ await this.emitEvent?.("turn_complete", {
1200
+ elapsed_ms: Date.now() - startedAt,
1201
+ stop_reason: "complete",
1202
+ });
967
1203
  this.briefingSetter(undefined);
968
1204
  }
969
1205
  });
@@ -1293,6 +1529,7 @@ export class AgentKernel {
1293
1529
  catch {
1294
1530
  // best-effort flush; closing the store is what must not be skipped
1295
1531
  }
1532
+ this.unsubscribeHarnessEvents?.();
1296
1533
  this.db.close();
1297
1534
  }
1298
1535
  }