@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.
- package/README.md +45 -10
- package/dist/a2a/node.js +1 -1
- package/dist/a2a/node.js.map +1 -1
- package/dist/agent/buyer/buyer-tools.js +15 -8
- 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 +251 -14
- package/dist/agent/kernel.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 +454 -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-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 +184 -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 +208 -9
- package/dist/agent/merchant/merchant-tools.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/cli.js +34 -14
- package/dist/cli.js.map +1 -1
- package/dist/config/profile.d.ts +21 -0
- package/dist/config/profile.js +59 -0
- package/dist/config/profile.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/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/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/negotiation/action-candidate.js +1 -1
- package/dist/operator/types.d.ts +1 -1
- package/dist/product-cli.d.ts +2 -2
- package/dist/product-cli.js +5 -3
- package/dist/product-cli.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 +7 -6
- package/skills/kiwi-buyer/SKILL.md +66 -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/agent/kernel.js
CHANGED
|
@@ -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
|
|
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
|
-
* (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|