@harrylabsj/kiwi 0.7.21 → 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.d.ts +7 -0
- package/dist/agent/buyer/buyer-tools.js +53 -0
- package/dist/agent/buyer/buyer-tools.js.map +1 -1
- package/dist/agent/buyer/scheduler.d.ts +4 -0
- package/dist/agent/buyer/scheduler.js +5 -1
- package/dist/agent/buyer/scheduler.js.map +1 -1
- package/dist/agent/buyer/task-store.d.ts +5 -0
- package/dist/agent/buyer/task-store.js +13 -0
- 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 +22 -1
- package/dist/agent/kernel.js +294 -22
- package/dist/agent/kernel.js.map +1 -1
- package/dist/agent/memory/schema.d.ts +1 -1
- package/dist/agent/memory/schema.js +67 -1
- package/dist/agent/memory/schema.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 +86 -0
- package/dist/agent/supplier/scheduler.js +618 -0
- package/dist/agent/supplier/scheduler.js.map +1 -0
- package/dist/agent/supplier/store.d.ts +173 -0
- package/dist/agent/supplier/store.js +387 -0
- package/dist/agent/supplier/store.js.map +1 -0
- 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.d.ts +5 -0
- package/dist/cli.js +177 -12
- 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 +14 -2
- package/dist/product-cli.js.map +1 -1
- package/dist/product-supplier.d.ts +148 -0
- package/dist/product-supplier.js +452 -0
- package/dist/product-supplier.js.map +1 -0
- 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,15 +29,27 @@
|
|
|
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";
|
|
35
36
|
import { TaskScheduler } from "./buyer/scheduler.js";
|
|
36
37
|
import { BuyerTaskStore } from "./buyer/task-store.js";
|
|
38
|
+
import { SupplierScheduler } from "./supplier/scheduler.js";
|
|
39
|
+
import { SupplierRelationshipStore } from "./supplier/store.js";
|
|
40
|
+
import { TrustRecordStore } from "../trust/records/index.js";
|
|
37
41
|
import { buildMemoryTools } from "./chat-tools.js";
|
|
38
42
|
import { AGENT_MODES, DEFAULT_AGENT_MODE, isAgentMode } from "./mode.js";
|
|
39
43
|
import { WriteApprovalCandidateStore, executeApprovedCandidate, } from "./merchant/action-candidate.js";
|
|
40
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";
|
|
41
53
|
import { DeterministicNegotiationRunner } from "../operator/runner.js";
|
|
42
54
|
import { MemoryStore } from "./memory/store.js";
|
|
43
55
|
import { MemoryError } from "./memory/types.js";
|
|
@@ -45,7 +57,7 @@ import { PrivateVault } from "./memory/vault.js";
|
|
|
45
57
|
import { openMainSession } from "./session.js";
|
|
46
58
|
import { registerCatalogAgent } from "../discovery/catalog-source/register.js";
|
|
47
59
|
import { KiwiCatalogSource } from "../discovery/catalog-source/kiwi-source.js";
|
|
48
|
-
import { baseSystemPrompt, renderMemoryBriefing } from "./system-prompt.js";
|
|
60
|
+
import { baseSystemPrompt, combineDynamicBriefing, renderMemoryBriefing } from "./system-prompt.js";
|
|
49
61
|
export const MAIN_SESSION_ID = "main";
|
|
50
62
|
/** 审查 P2-I:autopilot 对网关权威门持续拒绝的消息——连续失败上限与冷却窗口。 */
|
|
51
63
|
export const AUTOPILOT_MAX_SUBMIT_FAILURES = 3;
|
|
@@ -94,6 +106,61 @@ function assistantText(message) {
|
|
|
94
106
|
.join("\n")
|
|
95
107
|
.trim();
|
|
96
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
|
+
}
|
|
97
164
|
/** The owner's own Restricted memory values (Vault). Owner-only by isolation. */
|
|
98
165
|
function listRestrictedValues(store) {
|
|
99
166
|
const out = [];
|
|
@@ -201,12 +268,19 @@ export class AgentKernel {
|
|
|
201
268
|
closed = false;
|
|
202
269
|
taskStore;
|
|
203
270
|
scheduler;
|
|
271
|
+
/** M1:Buyer-owned supplier pull scheduler(buyer 角色构造;与 TaskScheduler 同一 tick 链)。 */
|
|
272
|
+
supplierScheduler;
|
|
204
273
|
approvals;
|
|
205
274
|
/** v0.7.0 KTH:handoff 存储(open() 构造,buyer 角色注入)。 */
|
|
206
275
|
handoffRuntime;
|
|
207
276
|
commerceClient;
|
|
208
277
|
merchantClient;
|
|
209
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;
|
|
210
284
|
/** Shared mutable mode ref (tools read it via a getter before the kernel exists). */
|
|
211
285
|
modeRef = { value: DEFAULT_AGENT_MODE };
|
|
212
286
|
/** Live execution hooks for pending candidates (v0.3.0-C /approve). */
|
|
@@ -249,6 +323,8 @@ export class AgentKernel {
|
|
|
249
323
|
this.taskStore = options.taskStore;
|
|
250
324
|
if (options.scheduler !== undefined)
|
|
251
325
|
this.scheduler = options.scheduler;
|
|
326
|
+
if (options.supplierScheduler !== undefined)
|
|
327
|
+
this.supplierScheduler = options.supplierScheduler;
|
|
252
328
|
if (options.approvals !== undefined)
|
|
253
329
|
this.approvals = options.approvals;
|
|
254
330
|
if (options.handoffRuntime !== undefined)
|
|
@@ -259,6 +335,10 @@ export class AgentKernel {
|
|
|
259
335
|
this.merchantClient = options.merchantClient;
|
|
260
336
|
if (options.broker !== undefined)
|
|
261
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;
|
|
262
342
|
if (options.modeRef !== undefined)
|
|
263
343
|
this.modeRef = options.modeRef;
|
|
264
344
|
this.pendingHooks = options.pendingHooks ?? new Map();
|
|
@@ -287,7 +367,7 @@ export class AgentKernel {
|
|
|
287
367
|
store.bindPrincipal(principal.principal_id);
|
|
288
368
|
let session;
|
|
289
369
|
try {
|
|
290
|
-
session = await openMainSession(paths);
|
|
370
|
+
session = await openMainSession(paths, options.eventSessionId ?? MAIN_SESSION_ID);
|
|
291
371
|
}
|
|
292
372
|
catch (err) {
|
|
293
373
|
db.close(); // never leak the SQLite handle on a fail-closed open
|
|
@@ -300,22 +380,55 @@ export class AgentKernel {
|
|
|
300
380
|
const modeRef = { value: options.mode ?? DEFAULT_AGENT_MODE };
|
|
301
381
|
const pendingHooks = new Map();
|
|
302
382
|
const turnId = { current: MAIN_SESSION_ID };
|
|
303
|
-
const
|
|
383
|
+
const registerPendingHooks = (id, hooks) => {
|
|
304
384
|
pendingHooks.set(id, hooks);
|
|
305
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
|
+
};
|
|
306
401
|
const approvals = new WriteApprovalCandidateStore({
|
|
307
402
|
db,
|
|
308
403
|
principalId: principal.principal_id,
|
|
309
404
|
now: clock,
|
|
310
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
|
+
};
|
|
311
415
|
// Execution hooks are deliberately process-local. Invalidate durable
|
|
312
416
|
// candidates before wiring tools so a restarted kernel never exposes a
|
|
313
417
|
// /pending item that its /approve path cannot execute.
|
|
314
418
|
approvals.expireForRecovery();
|
|
315
419
|
let taskStore;
|
|
316
420
|
let scheduler;
|
|
421
|
+
let supplierScheduler;
|
|
317
422
|
let buyerTools = [];
|
|
318
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);
|
|
319
432
|
let handoffRuntime;
|
|
320
433
|
if (options.profile.role === "buyer" && options.connector !== undefined) {
|
|
321
434
|
taskStore = new BuyerTaskStore({
|
|
@@ -333,6 +446,26 @@ export class AgentKernel {
|
|
|
333
446
|
? { catalogSource: new KiwiCatalogSource({ baseUrl: options.catalog }) }
|
|
334
447
|
: {}),
|
|
335
448
|
});
|
|
449
|
+
// M1:Buyer-owned supplier pull scheduler(pull-relationship §7.1/§8)。
|
|
450
|
+
// 与 TaskScheduler 同一 DB、同一 tick 串行链;catalogSource 不可得时仍可
|
|
451
|
+
// 做 Agent Card / UCP Profile 直拉(容错:可选依赖缺省不阻塞构造)。
|
|
452
|
+
// trustStore 落 agent data dir(trust/ 子目录,首次观察时创建)。
|
|
453
|
+
// 同一 store 也注入 buyer 工具:RFQ 成功后的 supplier_save_suggested
|
|
454
|
+
// 本地建议要查已有关系(§13 M0)。
|
|
455
|
+
const supplierStore = new SupplierRelationshipStore({
|
|
456
|
+
db,
|
|
457
|
+
principalId: principal.principal_id,
|
|
458
|
+
now: clock,
|
|
459
|
+
});
|
|
460
|
+
supplierScheduler = new SupplierScheduler({
|
|
461
|
+
store: supplierStore,
|
|
462
|
+
now: clock,
|
|
463
|
+
...(options.catalog !== undefined
|
|
464
|
+
? { catalogSource: new KiwiCatalogSource({ baseUrl: options.catalog }) }
|
|
465
|
+
: {}),
|
|
466
|
+
trustStore: new TrustRecordStore({ dir: path.dirname(paths.db), now: clock }),
|
|
467
|
+
...(options.allowLoopback === true ? { allowLoopback: true } : {}),
|
|
468
|
+
});
|
|
336
469
|
// v0.7.0 KTH:handoff 存储(Ledger 事件 + 执行幂等)落在 agent data dir,
|
|
337
470
|
// 注入 buyer 工具(handoff_agreement 工具挂载)。
|
|
338
471
|
const handoffDir = path.dirname(paths.db);
|
|
@@ -360,11 +493,26 @@ export class AgentKernel {
|
|
|
360
493
|
now: clock,
|
|
361
494
|
recordNegotiation: (input) => rememberNegotiation(store, input),
|
|
362
495
|
handoff: { ledger: handoffLedger, idempotency: handoffIdempotency },
|
|
496
|
+
supplierStore,
|
|
363
497
|
});
|
|
364
498
|
}
|
|
365
499
|
else if (options.profile.role === "merchant" &&
|
|
366
500
|
options.merchantClient !== undefined &&
|
|
367
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
|
+
}
|
|
368
516
|
merchantTools = buildMerchantTools({
|
|
369
517
|
profile: options.profile,
|
|
370
518
|
merchantClient: options.merchantClient,
|
|
@@ -372,17 +520,20 @@ export class AgentKernel {
|
|
|
372
520
|
// fail closed,目录/库存只读与审批式写工具照常可用(见 §15.3/§15.4)。
|
|
373
521
|
...(options.commerceClient !== undefined ? { commerceClient: options.commerceClient } : {}),
|
|
374
522
|
broker: options.broker,
|
|
523
|
+
principalId: principal.principal_id,
|
|
375
524
|
approvals,
|
|
376
525
|
mode: () => modeRef.value,
|
|
377
526
|
registerPending,
|
|
378
527
|
now: clock,
|
|
379
528
|
privateValues: () => listRestrictedValues(store),
|
|
529
|
+
...(merchantIntelligence !== undefined ? { intelligence: merchantIntelligence } : {}),
|
|
530
|
+
...(emitEvent !== undefined ? { emitEvent } : {}),
|
|
380
531
|
// 商家 A2A 节点 ledger 基础目录(<dataDir>/a2a)——LedgerStore 会再拼
|
|
381
532
|
// /ledger 到 <dataDir>/a2a/ledger,list_a2a_negotiations 用。
|
|
382
533
|
a2aLedgerDir: path.join(path.dirname(paths.db), "a2a"),
|
|
383
534
|
});
|
|
384
535
|
}
|
|
385
|
-
const base = baseSystemPrompt(options.profile, principal);
|
|
536
|
+
const base = baseSystemPrompt(options.profile, principal, merchantSkillRegistry.names.length > 0 ? merchantSkillRegistry : undefined);
|
|
386
537
|
let briefing;
|
|
387
538
|
const harness = new AgentHarness({
|
|
388
539
|
session,
|
|
@@ -392,12 +543,19 @@ export class AgentKernel {
|
|
|
392
543
|
...buildMemoryTools(store, { turnId: () => turnId.current }),
|
|
393
544
|
...buyerTools,
|
|
394
545
|
...merchantTools,
|
|
546
|
+
...merchantSkillTools,
|
|
395
547
|
],
|
|
396
548
|
systemPrompt: async () => (briefing === undefined ? base : `${base}\n\n${briefing}`),
|
|
397
549
|
// §18.1: a hung model/provider request must NOT wedge the chat forever —
|
|
398
550
|
// abort after the profile's turn timeout and surface an error text.
|
|
399
551
|
streamOptions: {
|
|
400
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
|
+
: {}),
|
|
401
559
|
},
|
|
402
560
|
...(options.thinkingLevel !== undefined ? { thinkingLevel: options.thinkingLevel } : {}),
|
|
403
561
|
});
|
|
@@ -407,6 +565,9 @@ export class AgentKernel {
|
|
|
407
565
|
if (options.model !== undefined) {
|
|
408
566
|
await harness.setModel(options.model);
|
|
409
567
|
}
|
|
568
|
+
const unsubscribeHarnessEvents = emitEvent === undefined
|
|
569
|
+
? undefined
|
|
570
|
+
: attachHarnessHostEvents(harness, emitEvent);
|
|
410
571
|
const kernel = new AgentKernel({
|
|
411
572
|
profile: options.profile,
|
|
412
573
|
paths,
|
|
@@ -423,9 +584,13 @@ export class AgentKernel {
|
|
|
423
584
|
now: clock,
|
|
424
585
|
...(taskStore !== undefined ? { taskStore } : {}),
|
|
425
586
|
...(scheduler !== undefined ? { scheduler } : {}),
|
|
587
|
+
...(supplierScheduler !== undefined ? { supplierScheduler } : {}),
|
|
426
588
|
...(options.commerceClient !== undefined ? { commerceClient: options.commerceClient } : {}),
|
|
427
589
|
...(options.merchantClient !== undefined ? { merchantClient: options.merchantClient } : {}),
|
|
428
590
|
...(options.broker !== undefined ? { broker: options.broker } : {}),
|
|
591
|
+
...(merchantIntelligence !== undefined ? { merchantIntelligence } : {}),
|
|
592
|
+
emitEvent,
|
|
593
|
+
...(unsubscribeHarnessEvents !== undefined ? { unsubscribeHarnessEvents } : {}),
|
|
429
594
|
});
|
|
430
595
|
kernel.briefingSetter = (value) => {
|
|
431
596
|
briefing = value;
|
|
@@ -576,7 +741,7 @@ export class AgentKernel {
|
|
|
576
741
|
/**
|
|
577
742
|
* Change the runtime mode. Switching INTO autopilot requires an explicit
|
|
578
743
|
* `confirm` — the same fail-closed rule as the operator control plane
|
|
579
|
-
* (
|
|
744
|
+
* (operator control-plane design §8).
|
|
580
745
|
*/
|
|
581
746
|
setMode(mode, options) {
|
|
582
747
|
if (!isAgentMode(mode)) {
|
|
@@ -629,17 +794,26 @@ export class AgentKernel {
|
|
|
629
794
|
if (candidate === undefined) {
|
|
630
795
|
throw new MemoryError("validation", `未知审批候选 ${candidateId}`);
|
|
631
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
|
+
};
|
|
632
806
|
// manual 模式语义(评审项 P3-3):manual = advice only(never executes)。
|
|
633
807
|
// routeWriteCandidate 在 manual 分支仍注册执行钩子(供 /pending 显示与
|
|
634
808
|
// /revise 重算),但批准路径必须拒绝——此前 /approve 绕过模式直接执行,
|
|
635
809
|
// 与 operator 平面分叉(controller 对 advice_only 候选明确拒绝批准:
|
|
636
810
|
// "manual 模式只提供建议,不自动提交")。
|
|
637
811
|
if (this.getMode() === "manual") {
|
|
638
|
-
return {
|
|
812
|
+
return report({
|
|
639
813
|
kind: "not_approvable",
|
|
640
814
|
candidate,
|
|
641
815
|
reason: "manual 模式只提供建议,不自动执行(/approve 拒绝 advice-only 候选)",
|
|
642
|
-
};
|
|
816
|
+
});
|
|
643
817
|
}
|
|
644
818
|
const hooks = this.pendingHooks.get(candidateId);
|
|
645
819
|
if (hooks === undefined) {
|
|
@@ -647,19 +821,19 @@ export class AgentKernel {
|
|
|
647
821
|
// candidate cannot be re-validated against the current marketplace.
|
|
648
822
|
if (candidate.status === "expired") {
|
|
649
823
|
this.releasePending(candidateId);
|
|
650
|
-
return { kind: "expired", candidate };
|
|
824
|
+
return report({ kind: "expired", candidate });
|
|
651
825
|
}
|
|
652
826
|
if (candidate.status !== "pending_approval" && candidate.status !== "approved") {
|
|
653
827
|
this.releasePending(candidateId);
|
|
654
|
-
return {
|
|
828
|
+
return report({
|
|
655
829
|
kind: "not_approvable",
|
|
656
830
|
candidate,
|
|
657
831
|
reason: `候选 ${candidateId} 状态为 ${candidate.status},不可批准。`,
|
|
658
|
-
};
|
|
832
|
+
});
|
|
659
833
|
}
|
|
660
834
|
const expired = this.approvals.expireCandidate(candidateId);
|
|
661
835
|
this.releasePending(candidateId);
|
|
662
|
-
return { kind: "expired", candidate: expired };
|
|
836
|
+
return report({ kind: "expired", candidate: expired });
|
|
663
837
|
}
|
|
664
838
|
this.approvals.markApproved(candidateId);
|
|
665
839
|
const outcome = await executeApprovedCandidate(this.approvals, candidateId, hooks);
|
|
@@ -668,7 +842,7 @@ export class AgentKernel {
|
|
|
668
842
|
if (outcome.kind !== "not_approvable") {
|
|
669
843
|
this.releasePending(candidateId);
|
|
670
844
|
}
|
|
671
|
-
return outcome;
|
|
845
|
+
return report(outcome);
|
|
672
846
|
}
|
|
673
847
|
/** Reject a pending/advice-only WriteApprovalCandidate. Never executes. */
|
|
674
848
|
rejectCandidate(candidateId) {
|
|
@@ -682,6 +856,11 @@ export class AgentKernel {
|
|
|
682
856
|
}
|
|
683
857
|
this.approvals.reject(candidateId);
|
|
684
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
|
+
});
|
|
685
864
|
return { ok: true };
|
|
686
865
|
}
|
|
687
866
|
/**
|
|
@@ -697,16 +876,24 @@ export class AgentKernel {
|
|
|
697
876
|
// 审查 P3:交付域同样清扫——DELIVERED/LAUNCHED 打开时限过期落
|
|
698
877
|
// handoff_expired(此前该事件全仓无生产者,"过期未打开"不可观测)。
|
|
699
878
|
this.handoffRuntime?.ledger.sweepExpiredHandoffs(this.clock());
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
879
|
+
const empty = {
|
|
880
|
+
checked_rules: 0,
|
|
881
|
+
requests_used: 0,
|
|
882
|
+
notifications: [],
|
|
883
|
+
tasks_searched: [],
|
|
884
|
+
tasks_expired: [],
|
|
885
|
+
errors: [],
|
|
886
|
+
};
|
|
887
|
+
const result = this.scheduler === undefined ? empty : await this.scheduler.tick(budget);
|
|
888
|
+
// M1:供应商关系 pull tick 走同一 kernel 串行链,共享请求预算;
|
|
889
|
+
// supplier tick 失败不影响 task tick 结果(错误进 supplier.errors)。
|
|
890
|
+
if (this.supplierScheduler !== undefined) {
|
|
891
|
+
const totalBudget = budget.max_requests ?? 20;
|
|
892
|
+
result.supplier = await this.supplierScheduler.tick({
|
|
893
|
+
max_requests: Math.max(0, totalBudget - result.requests_used),
|
|
894
|
+
});
|
|
708
895
|
}
|
|
709
|
-
return
|
|
896
|
+
return result;
|
|
710
897
|
});
|
|
711
898
|
}
|
|
712
899
|
/** 惰性过期清扫入口(/handoff 渲染前调用;幂等,可重复调)。 */
|
|
@@ -886,6 +1073,71 @@ export class AgentKernel {
|
|
|
886
1073
|
timestamp: Date.now(),
|
|
887
1074
|
});
|
|
888
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
|
+
}
|
|
889
1141
|
/**
|
|
890
1142
|
* 把一轮 A2A 磋商结果写入记忆(episode namespace,active 无需人工确认):
|
|
891
1143
|
* 持久上下文,`/why` 可查、跨重启可恢复。
|
|
@@ -900,12 +1152,16 @@ export class AgentKernel {
|
|
|
900
1152
|
if (text.startsWith("/")) {
|
|
901
1153
|
return await this.handleSlash(text);
|
|
902
1154
|
}
|
|
1155
|
+
const startedAt = Date.now();
|
|
1156
|
+
await this.emitEvent?.("message", { role: "user", text });
|
|
903
1157
|
const memories = this.store.retrieve({
|
|
904
1158
|
session_id: MAIN_SESSION_ID,
|
|
905
1159
|
purpose: "clarify",
|
|
906
1160
|
text,
|
|
907
1161
|
});
|
|
908
|
-
|
|
1162
|
+
const memoryBriefing = renderMemoryBriefing(memories);
|
|
1163
|
+
const groundingBriefing = await this.merchantGrounding(text);
|
|
1164
|
+
this.briefingSetter(combineDynamicBriefing([memoryBriefing, groundingBriefing]));
|
|
909
1165
|
// Advance the turn id so several remember calls inside this one turn
|
|
910
1166
|
// dedup into a single evidence piece (§9.3).
|
|
911
1167
|
this.turnId.current = `${MAIN_SESSION_ID}:${++this.turnSeq}`;
|
|
@@ -915,20 +1171,35 @@ export class AgentKernel {
|
|
|
915
1171
|
// §18.1: a model failure (throw OR empty response) must leave the
|
|
916
1172
|
// conversation and TUI intact.
|
|
917
1173
|
if (reply === "") {
|
|
1174
|
+
await this.emitEvent?.("error", {
|
|
1175
|
+
code: "empty_model_response",
|
|
1176
|
+
message: "model returned an empty response",
|
|
1177
|
+
retryable: true,
|
|
1178
|
+
});
|
|
918
1179
|
return {
|
|
919
1180
|
text: "暂时没能给出有效回复——当前目录里可能没有匹配的商品或商家,也可能是这条需求还不够具体。补充品牌、型号或预算范围后再问我一次即可。",
|
|
920
1181
|
quit: false,
|
|
921
1182
|
};
|
|
922
1183
|
}
|
|
1184
|
+
await this.emitEvent?.("message", { role: "assistant", text: reply });
|
|
923
1185
|
return { text: reply, quit: false };
|
|
924
1186
|
}
|
|
925
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
|
+
});
|
|
926
1193
|
return {
|
|
927
1194
|
text: `模型处理失败:${err instanceof Error ? err.message : String(err)}。对话状态未改变,请重试。`,
|
|
928
1195
|
quit: false,
|
|
929
1196
|
};
|
|
930
1197
|
}
|
|
931
1198
|
finally {
|
|
1199
|
+
await this.emitEvent?.("turn_complete", {
|
|
1200
|
+
elapsed_ms: Date.now() - startedAt,
|
|
1201
|
+
stop_reason: "complete",
|
|
1202
|
+
});
|
|
932
1203
|
this.briefingSetter(undefined);
|
|
933
1204
|
}
|
|
934
1205
|
});
|
|
@@ -1258,6 +1529,7 @@ export class AgentKernel {
|
|
|
1258
1529
|
catch {
|
|
1259
1530
|
// best-effort flush; closing the store is what must not be skipped
|
|
1260
1531
|
}
|
|
1532
|
+
this.unsubscribeHarnessEvents?.();
|
|
1261
1533
|
this.db.close();
|
|
1262
1534
|
}
|
|
1263
1535
|
}
|