web-agent-bridge 2.1.0 → 2.3.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.ar.md +8 -0
- package/README.md +8 -0
- package/examples/cross-site-agent.js +91 -0
- package/package.json +1 -1
- package/public/index.html +64 -1
- package/public/llms.txt +1 -0
- package/public/mesh-dashboard.html +401 -0
- package/public/script/wab.min.js +139 -1
- package/public/sovereign.html +1 -1
- package/script/ai-agent-bridge.js +127 -1
- package/sdk/README.md +44 -0
- package/sdk/agent-mesh.js +333 -0
- package/sdk/index.d.ts +93 -0
- package/sdk/index.js +4 -1
- package/sdk/multi-agent.js +318 -0
- package/sdk/package.json +1 -1
- package/server/index.js +5 -0
- package/server/routes/mesh.js +300 -0
- package/server/services/agent-learning.js +422 -0
- package/server/services/agent-mesh.js +346 -0
- package/server/services/agent-symphony.js +681 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Symphony Orchestrator — Autonomous Multi-Agent Collaboration
|
|
3
|
+
*
|
|
4
|
+
* Coordinates specialized agents (Researcher, Negotiator, Analyst, Guardian)
|
|
5
|
+
* to execute complex tasks WITHOUT any external LLM dependency.
|
|
6
|
+
* Each agent has built-in rule engines and heuristics.
|
|
7
|
+
*
|
|
8
|
+
* Symphony Phases:
|
|
9
|
+
* 1. COMPOSE — Assign roles based on task type
|
|
10
|
+
* 2. DISCOVER — Researcher gathers site data via WAB schema
|
|
11
|
+
* 3. ANALYZE — Analyst evaluates options using learned preferences
|
|
12
|
+
* 4. NEGOTIATE — Negotiator pursues best deal terms
|
|
13
|
+
* 5. GUARD — Guardian validates safety & fairness
|
|
14
|
+
* 6. DECIDE — Final consensus assembly from all agent outputs
|
|
15
|
+
*
|
|
16
|
+
* All processing is local — no tokens consumed, no data shared externally.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
const { db } = require('../models/db');
|
|
21
|
+
|
|
22
|
+
// ─── Schema ──────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
db.exec(`
|
|
25
|
+
CREATE TABLE IF NOT EXISTS symphony_compositions (
|
|
26
|
+
id TEXT PRIMARY KEY,
|
|
27
|
+
site_id TEXT NOT NULL,
|
|
28
|
+
task TEXT NOT NULL,
|
|
29
|
+
task_type TEXT NOT NULL,
|
|
30
|
+
status TEXT DEFAULT 'composing',
|
|
31
|
+
phases_completed TEXT DEFAULT '[]',
|
|
32
|
+
current_phase TEXT DEFAULT 'compose',
|
|
33
|
+
final_decision TEXT,
|
|
34
|
+
confidence REAL DEFAULT 0.0,
|
|
35
|
+
duration_ms INTEGER DEFAULT 0,
|
|
36
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
37
|
+
completed_at TEXT
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE TABLE IF NOT EXISTS symphony_roles (
|
|
41
|
+
id TEXT PRIMARY KEY,
|
|
42
|
+
composition_id TEXT NOT NULL,
|
|
43
|
+
role TEXT NOT NULL,
|
|
44
|
+
agent_id TEXT,
|
|
45
|
+
status TEXT DEFAULT 'waiting',
|
|
46
|
+
input TEXT DEFAULT '{}',
|
|
47
|
+
output TEXT DEFAULT '{}',
|
|
48
|
+
reasoning TEXT,
|
|
49
|
+
confidence REAL DEFAULT 0.0,
|
|
50
|
+
started_at TEXT,
|
|
51
|
+
completed_at TEXT,
|
|
52
|
+
FOREIGN KEY (composition_id) REFERENCES symphony_compositions(id) ON DELETE CASCADE
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE TABLE IF NOT EXISTS symphony_consensus (
|
|
56
|
+
id TEXT PRIMARY KEY,
|
|
57
|
+
composition_id TEXT NOT NULL,
|
|
58
|
+
votes TEXT DEFAULT '{}',
|
|
59
|
+
method TEXT DEFAULT 'weighted',
|
|
60
|
+
result TEXT DEFAULT '{}',
|
|
61
|
+
agreement_score REAL DEFAULT 0.0,
|
|
62
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
63
|
+
FOREIGN KEY (composition_id) REFERENCES symphony_compositions(id) ON DELETE CASCADE
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
CREATE TABLE IF NOT EXISTS symphony_templates (
|
|
67
|
+
id TEXT PRIMARY KEY,
|
|
68
|
+
name TEXT UNIQUE NOT NULL,
|
|
69
|
+
task_type TEXT NOT NULL,
|
|
70
|
+
roles TEXT NOT NULL,
|
|
71
|
+
phase_order TEXT NOT NULL,
|
|
72
|
+
description TEXT,
|
|
73
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_symphony_comp_site ON symphony_compositions(site_id);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_symphony_comp_status ON symphony_compositions(status);
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_symphony_roles_comp ON symphony_roles(composition_id);
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_symphony_consensus_comp ON symphony_consensus(composition_id);
|
|
80
|
+
`);
|
|
81
|
+
|
|
82
|
+
// ─── Default Templates ──────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
const TEMPLATES = [
|
|
85
|
+
{
|
|
86
|
+
name: 'purchase_advisor',
|
|
87
|
+
task_type: 'purchase',
|
|
88
|
+
roles: ['researcher', 'analyst', 'negotiator', 'guardian'],
|
|
89
|
+
phase_order: ['discover', 'analyze', 'negotiate', 'guard', 'decide'],
|
|
90
|
+
description: 'End-to-end purchase advisory: discover products, analyze value, negotiate price, verify safety',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'price_hunter',
|
|
94
|
+
task_type: 'price_comparison',
|
|
95
|
+
roles: ['researcher', 'analyst', 'guardian'],
|
|
96
|
+
phase_order: ['discover', 'analyze', 'guard', 'decide'],
|
|
97
|
+
description: 'Cross-site price comparison and best-deal identification',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'deal_negotiator',
|
|
101
|
+
task_type: 'negotiation',
|
|
102
|
+
roles: ['researcher', 'negotiator', 'guardian'],
|
|
103
|
+
phase_order: ['discover', 'negotiate', 'guard', 'decide'],
|
|
104
|
+
description: 'Aggressive deal-seeking with safety verification',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'site_scout',
|
|
108
|
+
task_type: 'exploration',
|
|
109
|
+
roles: ['researcher', 'analyst'],
|
|
110
|
+
phase_order: ['discover', 'analyze', 'decide'],
|
|
111
|
+
description: 'Explore and catalog site capabilities',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'trust_auditor',
|
|
115
|
+
task_type: 'verification',
|
|
116
|
+
roles: ['researcher', 'guardian', 'analyst'],
|
|
117
|
+
phase_order: ['discover', 'guard', 'analyze', 'decide'],
|
|
118
|
+
description: 'Comprehensive trust and safety audit of a site',
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
const _ensureTemplates = db.transaction(() => {
|
|
123
|
+
const insert = db.prepare(`INSERT OR IGNORE INTO symphony_templates (id, name, task_type, roles, phase_order, description) VALUES (?, ?, ?, ?, ?, ?)`);
|
|
124
|
+
for (const t of TEMPLATES) {
|
|
125
|
+
insert.run(crypto.randomUUID(), t.name, t.task_type, JSON.stringify(t.roles), JSON.stringify(t.phase_order), t.description);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
_ensureTemplates();
|
|
129
|
+
|
|
130
|
+
// ─── Prepared Statements ─────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
const stmts = {
|
|
133
|
+
insertComposition: db.prepare(`INSERT INTO symphony_compositions (id, site_id, task, task_type) VALUES (?, ?, ?, ?)`),
|
|
134
|
+
getComposition: db.prepare(`SELECT * FROM symphony_compositions WHERE id = ?`),
|
|
135
|
+
updateComposition: db.prepare(`UPDATE symphony_compositions SET status = ?, current_phase = ?, phases_completed = ?, final_decision = ?, confidence = ?, duration_ms = ?, completed_at = ? WHERE id = ?`),
|
|
136
|
+
getCompositionsBySite: db.prepare(`SELECT * FROM symphony_compositions WHERE site_id = ? ORDER BY created_at DESC LIMIT ?`),
|
|
137
|
+
getActiveCompositions: db.prepare(`SELECT * FROM symphony_compositions WHERE status IN ('composing', 'executing') ORDER BY created_at DESC`),
|
|
138
|
+
|
|
139
|
+
insertRole: db.prepare(`INSERT INTO symphony_roles (id, composition_id, role, agent_id) VALUES (?, ?, ?, ?)`),
|
|
140
|
+
getRoles: db.prepare(`SELECT * FROM symphony_roles WHERE composition_id = ? ORDER BY started_at ASC`),
|
|
141
|
+
getRole: db.prepare(`SELECT * FROM symphony_roles WHERE composition_id = ? AND role = ?`),
|
|
142
|
+
updateRole: db.prepare(`UPDATE symphony_roles SET status = ?, input = ?, output = ?, reasoning = ?, confidence = ?, started_at = COALESCE(started_at, datetime('now')), completed_at = ? WHERE id = ?`),
|
|
143
|
+
|
|
144
|
+
insertConsensus: db.prepare(`INSERT INTO symphony_consensus (id, composition_id, votes, method, result, agreement_score) VALUES (?, ?, ?, ?, ?, ?)`),
|
|
145
|
+
getConsensus: db.prepare(`SELECT * FROM symphony_consensus WHERE composition_id = ?`),
|
|
146
|
+
|
|
147
|
+
getTemplate: db.prepare(`SELECT * FROM symphony_templates WHERE name = ?`),
|
|
148
|
+
getTemplateByType: db.prepare(`SELECT * FROM symphony_templates WHERE task_type = ?`),
|
|
149
|
+
getAllTemplates: db.prepare(`SELECT * FROM symphony_templates ORDER BY name`),
|
|
150
|
+
|
|
151
|
+
getStats: db.prepare(`SELECT
|
|
152
|
+
(SELECT COUNT(*) FROM symphony_compositions WHERE site_id = ?) as total_compositions,
|
|
153
|
+
(SELECT COUNT(*) FROM symphony_compositions WHERE site_id = ? AND status = 'completed') as completed,
|
|
154
|
+
(SELECT AVG(confidence) FROM symphony_compositions WHERE site_id = ? AND status = 'completed') as avg_confidence,
|
|
155
|
+
(SELECT AVG(duration_ms) FROM symphony_compositions WHERE site_id = ? AND status = 'completed') as avg_duration_ms`),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// ─── Role Engines (Rule-Based AI) ────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
const RoleEngines = {
|
|
161
|
+
/**
|
|
162
|
+
* Researcher — Discovers and catalogs site information
|
|
163
|
+
*/
|
|
164
|
+
researcher: {
|
|
165
|
+
execute(input) {
|
|
166
|
+
const { siteData, task } = input;
|
|
167
|
+
const findings = [];
|
|
168
|
+
const capabilities = [];
|
|
169
|
+
|
|
170
|
+
// Analyze schema if available
|
|
171
|
+
if (siteData?.schema) {
|
|
172
|
+
const schema = typeof siteData.schema === 'string' ? JSON.parse(siteData.schema) : siteData.schema;
|
|
173
|
+
if (schema.actions) {
|
|
174
|
+
for (const [name, def] of Object.entries(schema.actions)) {
|
|
175
|
+
capabilities.push({ name, type: 'action', params: Object.keys(def.params || {}) });
|
|
176
|
+
}
|
|
177
|
+
findings.push(`Found ${Object.keys(schema.actions).length} available actions`);
|
|
178
|
+
}
|
|
179
|
+
if (schema.products) {
|
|
180
|
+
findings.push(`Catalog: ${schema.products.length || 'unknown'} products available`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Extract relevant data points from site data
|
|
185
|
+
if (siteData?.products) {
|
|
186
|
+
const products = Array.isArray(siteData.products) ? siteData.products : [];
|
|
187
|
+
const prices = products.filter((p) => p.price).map((p) => ({ name: p.name, price: p.price }));
|
|
188
|
+
if (prices.length > 0) {
|
|
189
|
+
findings.push(`Price range: ${Math.min(...prices.map((p) => p.price))} - ${Math.max(...prices.map((p) => p.price))}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (siteData?.categories) findings.push(`Categories: ${siteData.categories.join(', ')}`);
|
|
194
|
+
if (siteData?.policies) {
|
|
195
|
+
if (siteData.policies.returns) findings.push(`Return policy: ${siteData.policies.returns}`);
|
|
196
|
+
if (siteData.policies.shipping) findings.push(`Shipping: ${siteData.policies.shipping}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
findings,
|
|
201
|
+
capabilities,
|
|
202
|
+
dataQuality: findings.length > 3 ? 'rich' : findings.length > 0 ? 'moderate' : 'sparse',
|
|
203
|
+
confidence: Math.min(0.95, 0.3 + findings.length * 0.1),
|
|
204
|
+
reasoning: `Discovered ${findings.length} data points and ${capabilities.length} capabilities`,
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Analyst — Evaluates options using scoring heuristics
|
|
211
|
+
*/
|
|
212
|
+
analyst: {
|
|
213
|
+
execute(input) {
|
|
214
|
+
const { findings, products, preferences, task } = input;
|
|
215
|
+
const analyses = [];
|
|
216
|
+
|
|
217
|
+
// Score products if available
|
|
218
|
+
if (products && Array.isArray(products)) {
|
|
219
|
+
const scored = products.map((product) => {
|
|
220
|
+
let score = 50; // base score
|
|
221
|
+
const reasons = [];
|
|
222
|
+
|
|
223
|
+
// Price scoring
|
|
224
|
+
if (product.price !== undefined && preferences?.maxPrice) {
|
|
225
|
+
const priceRatio = product.price / preferences.maxPrice;
|
|
226
|
+
if (priceRatio <= 0.5) { score += 25; reasons.push('well under budget'); }
|
|
227
|
+
else if (priceRatio <= 0.8) { score += 15; reasons.push('within budget'); }
|
|
228
|
+
else if (priceRatio <= 1.0) { score += 5; reasons.push('near budget limit'); }
|
|
229
|
+
else { score -= 20; reasons.push('over budget'); }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Rating scoring
|
|
233
|
+
if (product.rating !== undefined) {
|
|
234
|
+
score += (product.rating - 3) * 10;
|
|
235
|
+
if (product.rating >= 4.5) reasons.push('highly rated');
|
|
236
|
+
if (product.rating < 3) reasons.push('poorly rated');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Availability
|
|
240
|
+
if (product.inStock === false) { score -= 30; reasons.push('out of stock'); }
|
|
241
|
+
|
|
242
|
+
// Discount scoring
|
|
243
|
+
if (product.discount) {
|
|
244
|
+
score += Math.min(20, product.discount);
|
|
245
|
+
reasons.push(`${product.discount}% discount`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Preference matching
|
|
249
|
+
if (preferences?.preferredCategory && product.category === preferences.preferredCategory) {
|
|
250
|
+
score += 10;
|
|
251
|
+
reasons.push('matches preferred category');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { ...product, score: Math.max(0, Math.min(100, score)), reasons };
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
scored.sort((a, b) => b.score - a.score);
|
|
258
|
+
analyses.push({
|
|
259
|
+
type: 'product_ranking',
|
|
260
|
+
items: scored.slice(0, 10),
|
|
261
|
+
bestOption: scored[0],
|
|
262
|
+
worstOption: scored[scored.length - 1],
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Value analysis from findings
|
|
267
|
+
const valueInsights = [];
|
|
268
|
+
if (findings) {
|
|
269
|
+
for (const f of findings) {
|
|
270
|
+
if (typeof f === 'string' && f.includes('Price range')) valueInsights.push(f);
|
|
271
|
+
if (typeof f === 'string' && f.includes('discount')) valueInsights.push(f);
|
|
272
|
+
if (typeof f === 'string' && f.includes('Return policy')) valueInsights.push(f);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
analyses,
|
|
278
|
+
valueInsights,
|
|
279
|
+
recommendation: analyses[0]?.items?.[0] || null,
|
|
280
|
+
confidence: analyses.length > 0 ? 0.7 + analyses[0].items.length * 0.02 : 0.3,
|
|
281
|
+
reasoning: `Analyzed ${analyses.length > 0 ? analyses[0].items.length : 0} options with ${valueInsights.length} value insights`,
|
|
282
|
+
};
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Negotiator — Pursues optimal deal terms
|
|
288
|
+
*/
|
|
289
|
+
negotiator: {
|
|
290
|
+
execute(input) {
|
|
291
|
+
const { product, siteCapabilities, preferences, marketData } = input;
|
|
292
|
+
const strategies = [];
|
|
293
|
+
const terms = {};
|
|
294
|
+
|
|
295
|
+
if (!product) {
|
|
296
|
+
return { strategies: [], terms: {}, confidence: 0, reasoning: 'No product to negotiate' };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const price = product.price || 0;
|
|
300
|
+
|
|
301
|
+
// Strategy: Volume discount
|
|
302
|
+
if (siteCapabilities?.some((c) => c.name === 'bulk_order' || c.name === 'quantity_discount')) {
|
|
303
|
+
strategies.push({ type: 'volume', description: 'Request bulk/quantity discount', priority: 2 });
|
|
304
|
+
terms.quantity_discount = true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Strategy: Competitor price matching
|
|
308
|
+
if (marketData?.competitorPrices) {
|
|
309
|
+
const lowest = Math.min(...marketData.competitorPrices);
|
|
310
|
+
if (lowest < price) {
|
|
311
|
+
strategies.push({ type: 'price_match', description: `Competitor offers ${lowest} (${Math.round((1 - lowest / price) * 100)}% less)`, priority: 3 });
|
|
312
|
+
terms.target_price = lowest;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Strategy: Loyalty/repeat customer
|
|
317
|
+
if (preferences?.visitCount > 3) {
|
|
318
|
+
strategies.push({ type: 'loyalty', description: 'Leverage repeat customer status', priority: 1 });
|
|
319
|
+
terms.loyalty = true;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Strategy: Bundle deal
|
|
323
|
+
if (siteCapabilities?.some((c) => c.name === 'bundle' || c.name === 'add_to_cart')) {
|
|
324
|
+
strategies.push({ type: 'bundle', description: 'Explore bundle pricing', priority: 1 });
|
|
325
|
+
terms.bundle = true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Calculate target price
|
|
329
|
+
let targetDiscount = 0;
|
|
330
|
+
if (strategies.length > 0) targetDiscount = Math.min(35, strategies.length * 8 + 5);
|
|
331
|
+
terms.target_price = terms.target_price || price * (1 - targetDiscount / 100);
|
|
332
|
+
terms.max_acceptable = price * 0.95; // worst case: 5% off
|
|
333
|
+
terms.ideal_price = price * (1 - targetDiscount / 100);
|
|
334
|
+
|
|
335
|
+
strategies.sort((a, b) => b.priority - a.priority);
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
strategies,
|
|
339
|
+
terms,
|
|
340
|
+
confidence: Math.min(0.9, 0.3 + strategies.length * 0.15),
|
|
341
|
+
reasoning: `${strategies.length} negotiation strategies identified, target ${targetDiscount}% discount`,
|
|
342
|
+
};
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Guardian — Validates safety, fairness, and trust
|
|
348
|
+
*/
|
|
349
|
+
guardian: {
|
|
350
|
+
execute(input) {
|
|
351
|
+
const { product, siteData, negotiationTerms, reputationData } = input;
|
|
352
|
+
const warnings = [];
|
|
353
|
+
const approvals = [];
|
|
354
|
+
let riskScore = 0;
|
|
355
|
+
|
|
356
|
+
// Price manipulation check
|
|
357
|
+
if (product?.price && product?.originalPrice) {
|
|
358
|
+
const realDiscount = (1 - product.price / product.originalPrice) * 100;
|
|
359
|
+
if (product.discount && Math.abs(product.discount - realDiscount) > 5) {
|
|
360
|
+
warnings.push({ type: 'price_manipulation', severity: 'high', detail: `Claimed discount (${product.discount}%) doesn't match actual (${Math.round(realDiscount)}%)` });
|
|
361
|
+
riskScore += 30;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Reputation check
|
|
366
|
+
if (reputationData) {
|
|
367
|
+
if (reputationData.trustLevel === 'emerging') {
|
|
368
|
+
warnings.push({ type: 'low_trust', severity: 'medium', detail: 'Site has low trust level' });
|
|
369
|
+
riskScore += 15;
|
|
370
|
+
}
|
|
371
|
+
if (reputationData.attestationCount < 3) {
|
|
372
|
+
warnings.push({ type: 'few_attestations', severity: 'low', detail: 'Limited reputation data' });
|
|
373
|
+
riskScore += 10;
|
|
374
|
+
}
|
|
375
|
+
if (reputationData.trustLevel === 'verified' || reputationData.trustLevel === 'exemplary') {
|
|
376
|
+
approvals.push('trusted_site');
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Negotiation terms safety
|
|
381
|
+
if (negotiationTerms?.target_price && product?.price) {
|
|
382
|
+
if (negotiationTerms.target_price < product.price * 0.3) {
|
|
383
|
+
warnings.push({ type: 'unrealistic_price', severity: 'medium', detail: 'Target price seems unrealistically low' });
|
|
384
|
+
riskScore += 10;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Data quality check
|
|
389
|
+
if (siteData) {
|
|
390
|
+
if (!siteData.policies?.returns) {
|
|
391
|
+
warnings.push({ type: 'no_return_policy', severity: 'medium', detail: 'No return policy found' });
|
|
392
|
+
riskScore += 10;
|
|
393
|
+
}
|
|
394
|
+
if (!siteData.policies?.privacy) {
|
|
395
|
+
warnings.push({ type: 'no_privacy_policy', severity: 'low', detail: 'No privacy policy found' });
|
|
396
|
+
riskScore += 5;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const safe = riskScore < 40;
|
|
401
|
+
if (safe) approvals.push('risk_acceptable');
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
safe,
|
|
405
|
+
riskScore: Math.min(100, riskScore),
|
|
406
|
+
warnings,
|
|
407
|
+
approvals,
|
|
408
|
+
confidence: warnings.length === 0 ? 0.9 : Math.max(0.3, 0.9 - warnings.length * 0.1),
|
|
409
|
+
reasoning: `Risk score: ${riskScore}/100, ${warnings.length} warnings, ${approvals.length} approvals`,
|
|
410
|
+
};
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// ─── Core API ────────────────────────────────────────────────────────
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Compose a new symphony — select template and assign roles.
|
|
419
|
+
*/
|
|
420
|
+
function compose(siteId, task, taskType, agentIds = {}) {
|
|
421
|
+
const template = stmts.getTemplateByType.get(taskType) || stmts.getTemplateByType.get('exploration');
|
|
422
|
+
if (!template) throw new Error(`No template for task type: ${taskType}`);
|
|
423
|
+
|
|
424
|
+
const roles = JSON.parse(template.roles);
|
|
425
|
+
const id = crypto.randomUUID();
|
|
426
|
+
stmts.insertComposition.run(id, siteId, task, taskType);
|
|
427
|
+
|
|
428
|
+
// Assign roles
|
|
429
|
+
const roleAssignments = [];
|
|
430
|
+
for (const role of roles) {
|
|
431
|
+
const roleId = crypto.randomUUID();
|
|
432
|
+
const agentId = agentIds[role] || null;
|
|
433
|
+
stmts.insertRole.run(roleId, id, role, agentId);
|
|
434
|
+
roleAssignments.push({ id: roleId, role, agentId });
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
compositionId: id,
|
|
439
|
+
template: template.name,
|
|
440
|
+
roles: roleAssignments,
|
|
441
|
+
phases: JSON.parse(template.phase_order),
|
|
442
|
+
status: 'composing',
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Execute a single phase of the symphony.
|
|
448
|
+
*/
|
|
449
|
+
function executePhase(compositionId, phaseName, phaseInput = {}) {
|
|
450
|
+
const comp = stmts.getComposition.get(compositionId);
|
|
451
|
+
if (!comp) throw new Error('Composition not found');
|
|
452
|
+
|
|
453
|
+
const roles = stmts.getRoles.all(compositionId);
|
|
454
|
+
const phasesCompleted = JSON.parse(comp.phases_completed || '[]');
|
|
455
|
+
|
|
456
|
+
// Determine which role handles this phase
|
|
457
|
+
const phaseRoleMap = {
|
|
458
|
+
discover: 'researcher',
|
|
459
|
+
analyze: 'analyst',
|
|
460
|
+
negotiate: 'negotiator',
|
|
461
|
+
guard: 'guardian',
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const roleName = phaseRoleMap[phaseName];
|
|
465
|
+
if (!roleName) {
|
|
466
|
+
// 'decide' phase — run consensus
|
|
467
|
+
return _runConsensus(compositionId, roles);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const role = roles.find((r) => r.role === roleName);
|
|
471
|
+
if (!role) {
|
|
472
|
+
return { phase: phaseName, skipped: true, reason: `No ${roleName} assigned` };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Gather input from previous phases
|
|
476
|
+
const previousOutputs = {};
|
|
477
|
+
for (const r of roles) {
|
|
478
|
+
if (r.output && r.output !== '{}') {
|
|
479
|
+
previousOutputs[r.role] = JSON.parse(r.output);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const fullInput = { ...previousOutputs, ...phaseInput };
|
|
484
|
+
|
|
485
|
+
// Execute the role engine
|
|
486
|
+
const engine = RoleEngines[roleName];
|
|
487
|
+
if (!engine) throw new Error(`No engine for role: ${roleName}`);
|
|
488
|
+
|
|
489
|
+
const output = engine.execute(fullInput);
|
|
490
|
+
|
|
491
|
+
// Save role result
|
|
492
|
+
stmts.updateRole.run(
|
|
493
|
+
'completed', JSON.stringify(fullInput), JSON.stringify(output),
|
|
494
|
+
output.reasoning || '', output.confidence || 0,
|
|
495
|
+
new Date().toISOString(), role.id
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
// Update composition
|
|
499
|
+
phasesCompleted.push(phaseName);
|
|
500
|
+
stmts.updateComposition.run(
|
|
501
|
+
'executing', phaseName, JSON.stringify(phasesCompleted),
|
|
502
|
+
null, 0, 0, null, compositionId
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
return { phase: phaseName, role: roleName, output, phasesCompleted };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Execute the entire symphony end-to-end.
|
|
510
|
+
*/
|
|
511
|
+
function perform(siteId, task, taskType, inputData = {}, agentIds = {}) {
|
|
512
|
+
const startTime = Date.now();
|
|
513
|
+
const composition = compose(siteId, task, taskType, agentIds);
|
|
514
|
+
|
|
515
|
+
// Get template phase order
|
|
516
|
+
const template = stmts.getTemplateByType.get(taskType);
|
|
517
|
+
const phases = template ? JSON.parse(template.phase_order) : ['discover', 'decide'];
|
|
518
|
+
|
|
519
|
+
const phaseResults = {};
|
|
520
|
+
|
|
521
|
+
// Execute phases sequentially, piping outputs
|
|
522
|
+
for (const phase of phases) {
|
|
523
|
+
const phaseInput = { ...inputData };
|
|
524
|
+
|
|
525
|
+
// Pipe previous phase outputs
|
|
526
|
+
for (const [prevPhase, prevResult] of Object.entries(phaseResults)) {
|
|
527
|
+
if (prevResult.output) {
|
|
528
|
+
Object.assign(phaseInput, prevResult.output);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const result = executePhase(composition.compositionId, phase, phaseInput);
|
|
533
|
+
phaseResults[phase] = result;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const duration = Date.now() - startTime;
|
|
537
|
+
|
|
538
|
+
// Get final decision
|
|
539
|
+
const consensus = stmts.getConsensus.get(composition.compositionId);
|
|
540
|
+
const finalDecision = consensus ? JSON.parse(consensus.result) : phaseResults;
|
|
541
|
+
|
|
542
|
+
// Calculate overall confidence
|
|
543
|
+
const roleOutputs = Object.values(phaseResults).filter((r) => r.output?.confidence);
|
|
544
|
+
const avgConfidence = roleOutputs.length > 0
|
|
545
|
+
? roleOutputs.reduce((s, r) => s + r.output.confidence, 0) / roleOutputs.length
|
|
546
|
+
: 0;
|
|
547
|
+
|
|
548
|
+
// Finalize composition
|
|
549
|
+
stmts.updateComposition.run(
|
|
550
|
+
'completed', 'decide', JSON.stringify(Object.keys(phaseResults)),
|
|
551
|
+
JSON.stringify(finalDecision), avgConfidence, duration,
|
|
552
|
+
new Date().toISOString(), composition.compositionId
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
return {
|
|
556
|
+
compositionId: composition.compositionId,
|
|
557
|
+
template: composition.template,
|
|
558
|
+
phases: phaseResults,
|
|
559
|
+
decision: finalDecision,
|
|
560
|
+
confidence: avgConfidence,
|
|
561
|
+
duration_ms: duration,
|
|
562
|
+
status: 'completed',
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// ─── Consensus Engine ────────────────────────────────────────────────
|
|
567
|
+
|
|
568
|
+
function _runConsensus(compositionId, roles) {
|
|
569
|
+
const completedRoles = roles.filter((r) => r.output && r.output !== '{}');
|
|
570
|
+
const votes = {};
|
|
571
|
+
let totalConfidence = 0;
|
|
572
|
+
|
|
573
|
+
for (const role of completedRoles) {
|
|
574
|
+
const output = JSON.parse(role.output);
|
|
575
|
+
votes[role.role] = {
|
|
576
|
+
confidence: output.confidence || 0,
|
|
577
|
+
recommendation: output.recommendation || output.strategies?.[0] || null,
|
|
578
|
+
reasoning: output.reasoning || '',
|
|
579
|
+
safe: output.safe !== undefined ? output.safe : true,
|
|
580
|
+
riskScore: output.riskScore || 0,
|
|
581
|
+
};
|
|
582
|
+
totalConfidence += output.confidence || 0;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Weighted consensus
|
|
586
|
+
const result = {
|
|
587
|
+
recommendation: null,
|
|
588
|
+
reasoning: [],
|
|
589
|
+
overallConfidence: completedRoles.length > 0 ? totalConfidence / completedRoles.length : 0,
|
|
590
|
+
safe: true,
|
|
591
|
+
participatingRoles: completedRoles.map((r) => r.role),
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// Guardian veto check
|
|
595
|
+
if (votes.guardian && !votes.guardian.safe) {
|
|
596
|
+
result.safe = false;
|
|
597
|
+
result.recommendation = { action: 'abort', reason: 'Guardian flagged safety concerns', riskScore: votes.guardian.riskScore };
|
|
598
|
+
result.reasoning.push(`GUARDIAN VETO: ${votes.guardian.reasoning}`);
|
|
599
|
+
} else {
|
|
600
|
+
// Take analyst recommendation if available, otherwise researcher findings
|
|
601
|
+
if (votes.analyst?.recommendation) {
|
|
602
|
+
result.recommendation = votes.analyst.recommendation;
|
|
603
|
+
result.reasoning.push(`Analyst: ${votes.analyst.reasoning}`);
|
|
604
|
+
}
|
|
605
|
+
if (votes.negotiator) {
|
|
606
|
+
result.negotiation = votes.negotiator;
|
|
607
|
+
result.reasoning.push(`Negotiator: ${votes.negotiator.reasoning}`);
|
|
608
|
+
}
|
|
609
|
+
if (votes.researcher) {
|
|
610
|
+
result.reasoning.push(`Researcher: ${votes.researcher.reasoning}`);
|
|
611
|
+
}
|
|
612
|
+
if (votes.guardian) {
|
|
613
|
+
result.reasoning.push(`Guardian: ${votes.guardian.reasoning}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const consensusId = crypto.randomUUID();
|
|
618
|
+
const agreementScore = _calculateAgreement(votes);
|
|
619
|
+
|
|
620
|
+
stmts.insertConsensus.run(
|
|
621
|
+
consensusId, compositionId, JSON.stringify(votes),
|
|
622
|
+
'weighted', JSON.stringify(result), agreementScore
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
return {
|
|
626
|
+
phase: 'decide',
|
|
627
|
+
consensus: result,
|
|
628
|
+
votes,
|
|
629
|
+
agreementScore,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function _calculateAgreement(votes) {
|
|
634
|
+
const confidences = Object.values(votes).map((v) => v.confidence);
|
|
635
|
+
if (confidences.length < 2) return 1.0;
|
|
636
|
+
|
|
637
|
+
// Agreement = inverse of confidence variance
|
|
638
|
+
const mean = confidences.reduce((s, c) => s + c, 0) / confidences.length;
|
|
639
|
+
const variance = confidences.reduce((s, c) => s + (c - mean) ** 2, 0) / confidences.length;
|
|
640
|
+
return Math.max(0, 1 - variance);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// ─── Query API ───────────────────────────────────────────────────────
|
|
644
|
+
|
|
645
|
+
function getComposition(compositionId) {
|
|
646
|
+
const comp = stmts.getComposition.get(compositionId);
|
|
647
|
+
if (!comp) return null;
|
|
648
|
+
const roles = stmts.getRoles.all(compositionId);
|
|
649
|
+
const consensus = stmts.getConsensus.get(compositionId);
|
|
650
|
+
return {
|
|
651
|
+
...comp,
|
|
652
|
+
phases_completed: JSON.parse(comp.phases_completed || '[]'),
|
|
653
|
+
final_decision: comp.final_decision ? JSON.parse(comp.final_decision) : null,
|
|
654
|
+
roles: roles.map((r) => ({ ...r, input: JSON.parse(r.input || '{}'), output: JSON.parse(r.output || '{}') })),
|
|
655
|
+
consensus: consensus ? { ...consensus, votes: JSON.parse(consensus.votes), result: JSON.parse(consensus.result) } : null,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function getCompositions(siteId, limit = 20) {
|
|
660
|
+
return stmts.getCompositionsBySite.all(siteId, limit).map((c) => ({
|
|
661
|
+
...c,
|
|
662
|
+
phases_completed: JSON.parse(c.phases_completed || '[]'),
|
|
663
|
+
final_decision: c.final_decision ? JSON.parse(c.final_decision) : null,
|
|
664
|
+
}));
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function getTemplates() {
|
|
668
|
+
return stmts.getAllTemplates.all().map((t) => ({
|
|
669
|
+
...t, roles: JSON.parse(t.roles), phase_order: JSON.parse(t.phase_order),
|
|
670
|
+
}));
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function getStats(siteId) {
|
|
674
|
+
return stmts.getStats.get(siteId, siteId, siteId, siteId);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
module.exports = {
|
|
678
|
+
compose, executePhase, perform,
|
|
679
|
+
getComposition, getCompositions, getTemplates, getStats,
|
|
680
|
+
RoleEngines,
|
|
681
|
+
};
|