agentbnb 8.0.1 → 8.2.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.
@@ -1,21 +1,17 @@
1
1
  import {
2
- fetchRemoteCards,
3
- interpolateObject,
4
- requestCapability,
5
- searchCards
6
- } from "./chunk-B2VJTKO5.js";
7
- import "./chunk-HLUEOLSZ.js";
2
+ findPeer
3
+ } from "./chunk-HLUEOLSZ.js";
8
4
  import {
9
5
  getBalance,
10
- getCardsByCapabilityType,
11
- getCardsBySkillCapability
6
+ getFeedbackForProvider,
7
+ holdEscrow,
8
+ releaseEscrow,
9
+ settleEscrow,
10
+ signEscrowReceipt
12
11
  } from "./chunk-7EF3HYVZ.js";
13
- import "./chunk-NWIQJ2CL.js";
14
- import "./chunk-IVOYM3WG.js";
15
- import "./chunk-WVY2W7AA.js";
16
-
17
- // src/conductor/task-decomposer.ts
18
- import { randomUUID } from "crypto";
12
+ import {
13
+ AgentBnBError
14
+ } from "./chunk-WVY2W7AA.js";
19
15
 
20
16
  // src/conductor/decomposition-validator.ts
21
17
  function validateAndNormalizeSubtasks(raw, context) {
@@ -152,6 +148,7 @@ function _validate(raw, context) {
152
148
  }
153
149
 
154
150
  // src/conductor/task-decomposer.ts
151
+ import { randomUUID } from "crypto";
155
152
  var TEMPLATES = {
156
153
  "video-production": {
157
154
  keywords: ["video", "demo", "clip", "animation"],
@@ -259,6 +256,367 @@ function decompose(task, _availableCapabilities) {
259
256
  return [];
260
257
  }
261
258
 
259
+ // src/gateway/client.ts
260
+ import { randomUUID as randomUUID2 } from "crypto";
261
+ async function requestCapability(opts) {
262
+ const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e5, escrowReceipt, identity } = opts;
263
+ const id = randomUUID2();
264
+ const payload = {
265
+ jsonrpc: "2.0",
266
+ id,
267
+ method: "capability.execute",
268
+ params: {
269
+ card_id: cardId,
270
+ ...params,
271
+ ...escrowReceipt ? { escrow_receipt: escrowReceipt } : {}
272
+ }
273
+ };
274
+ const headers = { "Content-Type": "application/json" };
275
+ if (identity) {
276
+ const signature = signEscrowReceipt(payload, identity.privateKey);
277
+ headers["X-Agent-Id"] = identity.agentId;
278
+ headers["X-Agent-Public-Key"] = identity.publicKey;
279
+ headers["X-Agent-Signature"] = signature;
280
+ } else if (token) {
281
+ headers["Authorization"] = `Bearer ${token}`;
282
+ }
283
+ const controller = new AbortController();
284
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
285
+ let response;
286
+ try {
287
+ response = await fetch(`${gatewayUrl}/rpc`, {
288
+ method: "POST",
289
+ headers,
290
+ body: JSON.stringify(payload),
291
+ signal: controller.signal
292
+ });
293
+ } catch (err) {
294
+ clearTimeout(timer);
295
+ const isTimeout = err instanceof Error && err.name === "AbortError";
296
+ throw new AgentBnBError(
297
+ isTimeout ? "Request timed out" : `Network error: ${String(err)}`,
298
+ isTimeout ? "TIMEOUT" : "NETWORK_ERROR"
299
+ );
300
+ } finally {
301
+ clearTimeout(timer);
302
+ }
303
+ const body = await response.json();
304
+ if (body.error) {
305
+ throw new AgentBnBError(body.error.message, `RPC_ERROR_${body.error.code}`);
306
+ }
307
+ return body.result;
308
+ }
309
+ async function requestViaRelay(relay, opts) {
310
+ try {
311
+ return await relay.request({
312
+ targetOwner: opts.targetOwner,
313
+ cardId: opts.cardId,
314
+ skillId: opts.skillId,
315
+ params: opts.params ?? {},
316
+ requester: opts.requester,
317
+ escrowReceipt: opts.escrowReceipt,
318
+ timeoutMs: opts.timeoutMs
319
+ });
320
+ } catch (err) {
321
+ const message = err instanceof Error ? err.message : String(err);
322
+ if (message.includes("timeout")) {
323
+ throw new AgentBnBError(message, "TIMEOUT");
324
+ }
325
+ if (message.includes("offline")) {
326
+ throw new AgentBnBError(message, "AGENT_OFFLINE");
327
+ }
328
+ throw new AgentBnBError(message, "RELAY_ERROR");
329
+ }
330
+ }
331
+
332
+ // src/feedback/reputation.ts
333
+ var QUALITY_SCORES = {
334
+ excellent: 1,
335
+ good: 0.8,
336
+ acceptable: 0.6,
337
+ poor: 0.3,
338
+ failed: 0
339
+ };
340
+ var COST_VALUE_SCORES = {
341
+ great: 1,
342
+ fair: 0.6,
343
+ overpriced: 0.2
344
+ };
345
+ var DECAY_DAYS = 30;
346
+ var WEIGHTS = {
347
+ rating: 0.4,
348
+ quality: 0.3,
349
+ would_reuse: 0.2,
350
+ cost_value: 0.1
351
+ };
352
+ function computeReputation(feedbacks) {
353
+ if (feedbacks.length === 0) return 0.5;
354
+ const now = Date.now();
355
+ let weightedSum = 0;
356
+ let totalWeight = 0;
357
+ for (const fb of feedbacks) {
358
+ const feedbackDate = new Date(fb.timestamp).getTime();
359
+ const ageDays = Math.max(0, (now - feedbackDate) / (1e3 * 60 * 60 * 24));
360
+ const recencyWeight = Math.exp(-ageDays / DECAY_DAYS);
361
+ const ratingScore = (fb.rating - 1) / 4;
362
+ const qualityScore = QUALITY_SCORES[fb.result_quality];
363
+ const reuseScore = fb.would_reuse ? 1 : 0;
364
+ const costScore = COST_VALUE_SCORES[fb.cost_value_ratio];
365
+ const componentScore = WEIGHTS.rating * ratingScore + WEIGHTS.quality * qualityScore + WEIGHTS.would_reuse * reuseScore + WEIGHTS.cost_value * costScore;
366
+ weightedSum += recencyWeight * componentScore;
367
+ totalWeight += recencyWeight;
368
+ }
369
+ if (totalWeight === 0) return 0.5;
370
+ const raw = weightedSum / totalWeight;
371
+ return Math.max(0, Math.min(1, raw));
372
+ }
373
+ function getReputationScore(db, agentId) {
374
+ const feedbacks = getFeedbackForProvider(db, agentId);
375
+ return computeReputation(feedbacks);
376
+ }
377
+
378
+ // src/registry/matcher.ts
379
+ function searchCards(db, query, filters = {}) {
380
+ const words = query.trim().split(/\s+/).map((w) => w.replace(/["*^{}():]/g, "")).filter((w) => w.length > 0);
381
+ if (words.length === 0) return [];
382
+ const ftsQuery = words.map((w) => `"${w}"`).join(" OR ");
383
+ const conditions = [];
384
+ const params = [ftsQuery];
385
+ if (filters.level !== void 0) {
386
+ conditions.push(`json_extract(cc.data, '$.level') = ?`);
387
+ params.push(filters.level);
388
+ }
389
+ if (filters.online !== void 0) {
390
+ conditions.push(`json_extract(cc.data, '$.availability.online') = ?`);
391
+ params.push(filters.online ? 1 : 0);
392
+ }
393
+ const whereClause = conditions.length > 0 ? `AND ${conditions.join(" AND ")}` : "";
394
+ const sql = `
395
+ SELECT cc.data
396
+ FROM capability_cards cc
397
+ JOIN cards_fts ON cc.rowid = cards_fts.rowid
398
+ WHERE cards_fts MATCH ?
399
+ ${whereClause}
400
+ ORDER BY bm25(cards_fts)
401
+ LIMIT 50
402
+ `;
403
+ const stmt = db.prepare(sql);
404
+ const rows = stmt.all(...params);
405
+ const results = rows.map((row) => JSON.parse(row.data));
406
+ let filtered = results;
407
+ if (filters.apis_used && filters.apis_used.length > 0) {
408
+ const requiredApis = filters.apis_used;
409
+ filtered = filtered.filter((card) => {
410
+ const cardApis = card.metadata?.apis_used ?? [];
411
+ return requiredApis.every((api) => cardApis.includes(api));
412
+ });
413
+ }
414
+ if (filters.min_reputation !== void 0 && filters.min_reputation > 0) {
415
+ filtered = applyReputationFilter(db, filtered, filters.min_reputation);
416
+ }
417
+ return filtered;
418
+ }
419
+ function filterCards(db, filters) {
420
+ const conditions = [];
421
+ const params = [];
422
+ if (filters.level !== void 0) {
423
+ conditions.push(`json_extract(data, '$.level') = ?`);
424
+ params.push(filters.level);
425
+ }
426
+ if (filters.online !== void 0) {
427
+ conditions.push(`json_extract(data, '$.availability.online') = ?`);
428
+ params.push(filters.online ? 1 : 0);
429
+ }
430
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
431
+ const sql = `SELECT data FROM capability_cards ${whereClause}`;
432
+ const stmt = db.prepare(sql);
433
+ const rows = stmt.all(...params);
434
+ let cards = rows.map((row) => JSON.parse(row.data));
435
+ if (filters.min_reputation !== void 0 && filters.min_reputation > 0) {
436
+ cards = applyReputationFilter(db, cards, filters.min_reputation);
437
+ }
438
+ return cards;
439
+ }
440
+ function applyReputationFilter(db, cards, minReputation) {
441
+ const owners = [...new Set(cards.map((c) => c.owner))];
442
+ const reputationMap = /* @__PURE__ */ new Map();
443
+ for (const owner of owners) {
444
+ reputationMap.set(owner, getReputationScore(db, owner));
445
+ }
446
+ return cards.filter((card) => {
447
+ const score = reputationMap.get(card.owner) ?? 0.5;
448
+ return score >= minReputation;
449
+ });
450
+ }
451
+ function buildReputationMap(db, owners) {
452
+ const unique = [...new Set(owners)];
453
+ const map = /* @__PURE__ */ new Map();
454
+ for (const owner of unique) {
455
+ map.set(owner, getReputationScore(db, owner));
456
+ }
457
+ return map;
458
+ }
459
+
460
+ // src/autonomy/tiers.ts
461
+ import { randomUUID as randomUUID3 } from "crypto";
462
+ var DEFAULT_AUTONOMY_CONFIG = {
463
+ tier1_max_credits: 0,
464
+ tier2_max_credits: 0
465
+ };
466
+ function getAutonomyTier(creditAmount, config) {
467
+ if (creditAmount < config.tier1_max_credits) return 1;
468
+ if (creditAmount < config.tier2_max_credits) return 2;
469
+ return 3;
470
+ }
471
+ function insertAuditEvent(db, event) {
472
+ const isShareEvent = event.type === "auto_share" || event.type === "auto_share_notify" || event.type === "auto_share_pending";
473
+ const cardId = isShareEvent ? "system" : event.card_id;
474
+ const creditsCharged = isShareEvent ? 0 : event.credits;
475
+ const stmt = db.prepare(`
476
+ INSERT INTO request_log (
477
+ id, card_id, card_name, requester, status, latency_ms, credits_charged,
478
+ created_at, skill_id, action_type, tier_invoked
479
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
480
+ `);
481
+ stmt.run(
482
+ randomUUID3(),
483
+ cardId,
484
+ "autonomy-audit",
485
+ "self",
486
+ "success",
487
+ 0,
488
+ creditsCharged,
489
+ (/* @__PURE__ */ new Date()).toISOString(),
490
+ event.skill_id,
491
+ event.type,
492
+ event.tier_invoked
493
+ );
494
+ }
495
+
496
+ // src/autonomy/pending-requests.ts
497
+ import { randomUUID as randomUUID4 } from "crypto";
498
+ function createPendingRequest(db, opts) {
499
+ const id = randomUUID4();
500
+ const now = (/* @__PURE__ */ new Date()).toISOString();
501
+ const paramsJson = opts.params !== void 0 ? JSON.stringify(opts.params) : null;
502
+ db.prepare(`
503
+ INSERT INTO pending_requests (
504
+ id, skill_query, max_cost_credits, selected_peer, selected_card_id,
505
+ selected_skill_id, credits, status, params, created_at, resolved_at
506
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, NULL)
507
+ `).run(
508
+ id,
509
+ opts.skill_query,
510
+ opts.max_cost_credits,
511
+ opts.selected_peer ?? null,
512
+ opts.selected_card_id ?? null,
513
+ opts.selected_skill_id ?? null,
514
+ opts.credits,
515
+ paramsJson,
516
+ now
517
+ );
518
+ return id;
519
+ }
520
+ function listPendingRequests(db) {
521
+ const rows = db.prepare(`SELECT * FROM pending_requests WHERE status = 'pending' ORDER BY created_at DESC`).all();
522
+ return rows;
523
+ }
524
+ function resolvePendingRequest(db, id, resolution) {
525
+ const now = (/* @__PURE__ */ new Date()).toISOString();
526
+ const result = db.prepare(
527
+ `UPDATE pending_requests SET status = ?, resolved_at = ? WHERE id = ?`
528
+ ).run(resolution, now, id);
529
+ if (result.changes === 0) {
530
+ throw new AgentBnBError(
531
+ `Pending request not found: ${id}`,
532
+ "NOT_FOUND"
533
+ );
534
+ }
535
+ }
536
+
537
+ // src/cli/remote-registry.ts
538
+ var RegistryTimeoutError = class extends AgentBnBError {
539
+ constructor(url) {
540
+ super(
541
+ `Registry at ${url} did not respond within 5s. Showing local results only.`,
542
+ "REGISTRY_TIMEOUT"
543
+ );
544
+ this.name = "RegistryTimeoutError";
545
+ }
546
+ };
547
+ var RegistryConnectionError = class extends AgentBnBError {
548
+ constructor(url) {
549
+ super(
550
+ `Cannot reach ${url}. Is the registry running? Showing local results only.`,
551
+ "REGISTRY_CONNECTION"
552
+ );
553
+ this.name = "RegistryConnectionError";
554
+ }
555
+ };
556
+ var RegistryAuthError = class extends AgentBnBError {
557
+ constructor(url) {
558
+ super(
559
+ `Authentication failed for ${url}. Run \`agentbnb config set token <your-token>\`.`,
560
+ "REGISTRY_AUTH"
561
+ );
562
+ this.name = "RegistryAuthError";
563
+ }
564
+ };
565
+ async function fetchRemoteCards(registryUrl, params, timeoutMs = 5e3) {
566
+ let cardsUrl;
567
+ try {
568
+ cardsUrl = new URL("/cards", registryUrl);
569
+ } catch {
570
+ throw new AgentBnBError(`Invalid registry URL: ${registryUrl}`, "INVALID_REGISTRY_URL");
571
+ }
572
+ const searchParams = new URLSearchParams();
573
+ if (params.q !== void 0) searchParams.set("q", params.q);
574
+ if (params.level !== void 0) searchParams.set("level", String(params.level));
575
+ if (params.online !== void 0) searchParams.set("online", String(params.online));
576
+ if (params.tag !== void 0) searchParams.set("tag", params.tag);
577
+ searchParams.set("limit", "100");
578
+ cardsUrl.search = searchParams.toString();
579
+ const controller = new AbortController();
580
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
581
+ let response;
582
+ try {
583
+ response = await fetch(cardsUrl.toString(), { signal: controller.signal });
584
+ } catch (err) {
585
+ clearTimeout(timer);
586
+ const isTimeout = err instanceof Error && err.name === "AbortError";
587
+ if (isTimeout) {
588
+ throw new RegistryTimeoutError(registryUrl);
589
+ }
590
+ throw new RegistryConnectionError(registryUrl);
591
+ } finally {
592
+ clearTimeout(timer);
593
+ }
594
+ if (response.status === 401 || response.status === 403) {
595
+ throw new RegistryAuthError(registryUrl);
596
+ }
597
+ if (!response.ok) {
598
+ throw new RegistryConnectionError(registryUrl);
599
+ }
600
+ const body = await response.json();
601
+ return body.items;
602
+ }
603
+ function mergeResults(localCards, remoteCards, hasQuery) {
604
+ const taggedLocal = localCards.map((c) => ({ ...c, source: "local" }));
605
+ const taggedRemote = remoteCards.map((c) => ({ ...c, source: "remote" }));
606
+ const localIds = new Set(localCards.map((c) => c.id));
607
+ const dedupedRemote = taggedRemote.filter((c) => !localIds.has(c.id));
608
+ if (!hasQuery) {
609
+ return [...taggedLocal, ...dedupedRemote];
610
+ }
611
+ const result = [];
612
+ const maxLen = Math.max(taggedLocal.length, dedupedRemote.length);
613
+ for (let i = 0; i < maxLen; i++) {
614
+ if (i < taggedLocal.length) result.push(taggedLocal[i]);
615
+ if (i < dedupedRemote.length) result.push(dedupedRemote[i]);
616
+ }
617
+ return result;
618
+ }
619
+
262
620
  // src/autonomy/auto-request.ts
263
621
  function minMaxNormalize(values) {
264
622
  if (values.length === 0) return [];
@@ -301,6 +659,188 @@ function scorePeers(candidates, selfOwner) {
301
659
  scored.sort((a, b) => b.rawScore - a.rawScore);
302
660
  return scored;
303
661
  }
662
+ var AutoRequestor = class {
663
+ owner;
664
+ registryDb;
665
+ creditDb;
666
+ autonomyConfig;
667
+ budgetManager;
668
+ registryUrl;
669
+ /**
670
+ * Creates a new AutoRequestor.
671
+ *
672
+ * @param opts - Configuration for this AutoRequestor instance.
673
+ */
674
+ constructor(opts) {
675
+ this.owner = opts.owner;
676
+ this.registryDb = opts.registryDb;
677
+ this.creditDb = opts.creditDb;
678
+ this.autonomyConfig = opts.autonomyConfig;
679
+ this.budgetManager = opts.budgetManager;
680
+ this.registryUrl = opts.registryUrl;
681
+ }
682
+ /**
683
+ * Executes an autonomous capability request.
684
+ *
685
+ * Performs the full flow:
686
+ * 1. Search for matching capability cards
687
+ * 2. Filter self-owned and over-budget candidates
688
+ * 3. Score candidates using min-max normalized composite scoring
689
+ * 4. Resolve peer gateway config
690
+ * 5. Check autonomy tier (Tier 3 queues to pending_requests)
691
+ * 6. Check budget reserve
692
+ * 7. Hold escrow
693
+ * 8. Execute via peer gateway
694
+ * 9. Settle or release escrow based on outcome
695
+ * 10. Log audit event (for Tier 2 notifications and all failures)
696
+ *
697
+ * @param need - The capability need to fulfill.
698
+ * @returns The result of the auto-request attempt.
699
+ */
700
+ async requestWithAutonomy(need) {
701
+ let cards = searchCards(this.registryDb, need.query, { online: true });
702
+ if (cards.length === 0 && this.registryUrl) {
703
+ try {
704
+ cards = await fetchRemoteCards(this.registryUrl, { q: need.query, online: true });
705
+ } catch {
706
+ insertAuditEvent(this.registryDb, {
707
+ type: "auto_request_failed",
708
+ card_id: "none",
709
+ skill_id: "none",
710
+ tier_invoked: 3,
711
+ credits: 0,
712
+ peer: "none",
713
+ reason: `Remote registry fallback failed for query "${need.query}"`
714
+ });
715
+ cards = [];
716
+ }
717
+ }
718
+ const candidates = [];
719
+ for (const card of cards) {
720
+ const cardAsV2 = card;
721
+ if (Array.isArray(cardAsV2.skills)) {
722
+ for (const skill of cardAsV2.skills) {
723
+ const cost = skill.pricing.credits_per_call;
724
+ if (cost <= need.maxCostCredits) {
725
+ candidates.push({
726
+ card,
727
+ cost,
728
+ skillId: skill.id,
729
+ // Carry skill-level metadata so scorePeers() can prefer it over card-level
730
+ skillMetadata: skill.metadata,
731
+ skillInternal: skill._internal
732
+ });
733
+ }
734
+ }
735
+ } else {
736
+ const cost = card.pricing.credits_per_call;
737
+ if (cost <= need.maxCostCredits) {
738
+ candidates.push({ card, cost, skillId: void 0 });
739
+ }
740
+ }
741
+ }
742
+ const scored = scorePeers(candidates, this.owner);
743
+ if (scored.length === 0) {
744
+ this.logFailure("auto_request_failed", "system", "none", 3, 0, "none", "No eligible peer found");
745
+ return { status: "no_peer", reason: "No eligible peer found" };
746
+ }
747
+ const top = scored[0];
748
+ const peerConfig = findPeer(top.card.owner);
749
+ if (!peerConfig) {
750
+ this.logFailure("auto_request_failed", top.card.id, top.skillId ?? "none", 3, top.cost, top.card.owner, "No gateway config for peer");
751
+ return { status: "no_peer", reason: "No gateway config for peer" };
752
+ }
753
+ const tier = getAutonomyTier(top.cost, this.autonomyConfig);
754
+ if (tier === 3) {
755
+ createPendingRequest(this.registryDb, {
756
+ skill_query: need.query,
757
+ max_cost_credits: need.maxCostCredits,
758
+ credits: top.cost,
759
+ selected_peer: top.card.owner,
760
+ selected_card_id: top.card.id,
761
+ selected_skill_id: top.skillId,
762
+ params: need.params
763
+ });
764
+ insertAuditEvent(this.registryDb, {
765
+ type: "auto_request_pending",
766
+ card_id: top.card.id,
767
+ skill_id: top.skillId ?? top.card.id,
768
+ tier_invoked: 3,
769
+ credits: top.cost,
770
+ peer: top.card.owner
771
+ });
772
+ return {
773
+ status: "tier_blocked",
774
+ reason: "Tier 3: owner approval required",
775
+ peer: top.card.owner
776
+ };
777
+ }
778
+ if (!this.budgetManager.canSpend(top.cost)) {
779
+ this.logFailure("auto_request_failed", top.card.id, top.skillId ?? "none", tier, top.cost, top.card.owner, "Budget reserve would be breached");
780
+ return { status: "budget_blocked", reason: "Insufficient credits \u2014 reserve floor would be breached" };
781
+ }
782
+ const escrowId = holdEscrow(this.creditDb, this.owner, top.cost, top.card.id);
783
+ try {
784
+ const execResult = await requestCapability({
785
+ gatewayUrl: peerConfig.url,
786
+ token: peerConfig.token,
787
+ cardId: top.card.id,
788
+ params: top.skillId ? { skill_id: top.skillId, ...need.params, requester: this.owner } : { ...need.params, requester: this.owner }
789
+ });
790
+ settleEscrow(this.creditDb, escrowId, top.card.owner);
791
+ if (tier === 2) {
792
+ insertAuditEvent(this.registryDb, {
793
+ type: "auto_request_notify",
794
+ card_id: top.card.id,
795
+ skill_id: top.skillId ?? top.card.id,
796
+ tier_invoked: 2,
797
+ credits: top.cost,
798
+ peer: top.card.owner
799
+ });
800
+ } else {
801
+ insertAuditEvent(this.registryDb, {
802
+ type: "auto_request",
803
+ card_id: top.card.id,
804
+ skill_id: top.skillId ?? top.card.id,
805
+ tier_invoked: 1,
806
+ credits: top.cost,
807
+ peer: top.card.owner
808
+ });
809
+ }
810
+ return {
811
+ status: "success",
812
+ result: execResult,
813
+ escrowId,
814
+ peer: top.card.owner,
815
+ creditsSpent: top.cost
816
+ };
817
+ } catch (err) {
818
+ releaseEscrow(this.creditDb, escrowId);
819
+ const reason = err instanceof Error ? err.message : String(err);
820
+ this.logFailure("auto_request_failed", top.card.id, top.skillId ?? "none", tier, top.cost, top.card.owner, `Execution failed: ${reason}`);
821
+ return {
822
+ status: "failed",
823
+ reason: `Execution failed: ${reason}`,
824
+ peer: top.card.owner
825
+ };
826
+ }
827
+ }
828
+ /**
829
+ * Logs a failure audit event to request_log.
830
+ * Used for all non-success paths to satisfy REQ-06.
831
+ */
832
+ logFailure(type, cardId, skillId, tier, credits, peer, reason) {
833
+ insertAuditEvent(this.registryDb, {
834
+ type,
835
+ card_id: cardId,
836
+ skill_id: skillId,
837
+ tier_invoked: tier,
838
+ credits,
839
+ peer,
840
+ reason
841
+ });
842
+ }
843
+ };
304
844
 
305
845
  // src/conductor/capability-matcher.ts
306
846
  var MAX_ALTERNATIVES = 2;
@@ -476,6 +1016,53 @@ var BudgetManager = class {
476
1016
  }
477
1017
  };
478
1018
 
1019
+ // src/utils/interpolation.ts
1020
+ function resolvePath(obj, path) {
1021
+ const segments = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter((s) => s.length > 0);
1022
+ let current = obj;
1023
+ for (const segment of segments) {
1024
+ if (current === null || current === void 0) {
1025
+ return void 0;
1026
+ }
1027
+ if (typeof current !== "object") {
1028
+ return void 0;
1029
+ }
1030
+ current = current[segment];
1031
+ }
1032
+ return current;
1033
+ }
1034
+ function interpolate(template, context) {
1035
+ return template.replace(/\$\{([^}]+)\}/g, (_match, expression) => {
1036
+ const resolved = resolvePath(context, expression.trim());
1037
+ if (resolved === void 0 || resolved === null) {
1038
+ return "";
1039
+ }
1040
+ if (typeof resolved === "object") {
1041
+ return JSON.stringify(resolved);
1042
+ }
1043
+ return String(resolved);
1044
+ });
1045
+ }
1046
+ function interpolateObject(obj, context) {
1047
+ const result = {};
1048
+ for (const [key, value] of Object.entries(obj)) {
1049
+ result[key] = interpolateValue(value, context);
1050
+ }
1051
+ return result;
1052
+ }
1053
+ function interpolateValue(value, context) {
1054
+ if (typeof value === "string") {
1055
+ return interpolate(value, context);
1056
+ }
1057
+ if (Array.isArray(value)) {
1058
+ return value.map((item) => interpolateValue(item, context));
1059
+ }
1060
+ if (value !== null && typeof value === "object") {
1061
+ return interpolateObject(value, context);
1062
+ }
1063
+ return value;
1064
+ }
1065
+
479
1066
  // src/conductor/pipeline-orchestrator.ts
480
1067
  function computeWaves(subtasks) {
481
1068
  const waves = [];
@@ -641,251 +1228,28 @@ async function orchestrate(opts) {
641
1228
  };
642
1229
  }
643
1230
 
644
- // src/conductor/team-formation.ts
645
- import { randomUUID as randomUUID2 } from "crypto";
646
- function selectByStrategy(matches, strategy) {
647
- if (matches.length === 0) return void 0;
648
- if (strategy === "balanced") {
649
- return matches[0];
650
- }
651
- if (strategy === "quality_optimized") {
652
- return matches.reduce((best, m) => m.score > best.score ? m : best, matches[0]);
653
- }
654
- return matches.reduce((best, m) => {
655
- if (m.credits < best.credits) return m;
656
- if (m.credits === best.credits && m.score > best.score) return m;
657
- return best;
658
- }, matches[0]);
659
- }
660
- async function formTeam(opts) {
661
- const { subtasks, strategy, db, conductorOwner, registryUrl } = opts;
662
- const team_id = randomUUID2();
663
- if (subtasks.length === 0) {
664
- return { team_id, strategy, matched: [], unrouted: [] };
665
- }
666
- const matched = [];
667
- const unrouted = [];
668
- for (const subtask of subtasks) {
669
- const skillCards = getCardsBySkillCapability(db, subtask.required_capability).filter((c) => c.owner !== conductorOwner);
670
- if (skillCards.length > 0) {
671
- const candidates = skillCards.map((card) => {
672
- const skills = card.skills ?? [];
673
- const matchingSkill = skills.find(
674
- (s) => s.capability_type === subtask.required_capability || (s.capability_types ?? []).includes(subtask.required_capability)
675
- );
676
- return {
677
- subtask_id: subtask.id,
678
- selected_agent: card.owner,
679
- selected_skill: matchingSkill?.id ?? "",
680
- selected_card_id: card.id,
681
- score: 1,
682
- credits: matchingSkill?.pricing.credits_per_call ?? 0,
683
- alternatives: []
684
- };
685
- });
686
- const selected2 = selectByStrategy(candidates, strategy);
687
- matched.push({
688
- subtask,
689
- capability_type: subtask.required_capability,
690
- agent: selected2.selected_agent,
691
- skill: selected2.selected_skill,
692
- card_id: selected2.selected_card_id,
693
- credits: selected2.credits,
694
- score: selected2.score
695
- });
696
- continue;
697
- }
698
- const matchResults = await matchSubTasks({
699
- db,
700
- subtasks: [subtask],
701
- conductorOwner,
702
- registryUrl
703
- });
704
- const m = matchResults[0];
705
- if (!m || m.selected_agent === "") {
706
- unrouted.push(subtask);
707
- continue;
708
- }
709
- const allCandidates = [
710
- m,
711
- ...m.alternatives.map((alt) => ({
712
- subtask_id: m.subtask_id,
713
- selected_agent: alt.agent,
714
- selected_skill: alt.skill,
715
- score: alt.score,
716
- credits: alt.credits,
717
- alternatives: []
718
- }))
719
- ];
720
- const selected = selectByStrategy(allCandidates, strategy);
721
- matched.push({
722
- subtask,
723
- capability_type: subtask.required_capability,
724
- agent: selected.selected_agent,
725
- skill: selected.selected_skill,
726
- card_id: selected === m ? m.selected_card_id : void 0,
727
- credits: selected.credits,
728
- score: selected.score
729
- });
730
- }
731
- return { team_id, strategy, matched, unrouted };
732
- }
733
-
734
- // src/conductor/conductor-mode.ts
735
- var ConductorMode = class {
736
- db;
737
- creditDb;
738
- conductorOwner;
739
- gatewayToken;
740
- resolveAgentUrl;
741
- maxBudget;
742
- constructor(opts) {
743
- this.db = opts.db;
744
- this.creditDb = opts.creditDb;
745
- this.conductorOwner = opts.conductorOwner;
746
- this.gatewayToken = opts.gatewayToken;
747
- this.resolveAgentUrl = opts.resolveAgentUrl;
748
- this.maxBudget = opts.maxBudget ?? 100;
749
- }
750
- /**
751
- * Execute a conductor skill with the given config and params.
752
- *
753
- * @param config - SkillConfig with type 'conductor' and conductor_skill field.
754
- * @param params - Must include `task` string.
755
- * @returns Execution result without latency_ms (added by SkillExecutor).
756
- */
757
- async execute(config, params, onProgress) {
758
- const conductorSkill = config.conductor_skill;
759
- if (conductorSkill !== "orchestrate" && conductorSkill !== "plan") {
760
- return {
761
- success: false,
762
- error: `Unknown conductor skill: "${conductorSkill}"`
763
- };
764
- }
765
- const task = params.task;
766
- if (typeof task !== "string" || task.length === 0) {
767
- return {
768
- success: false,
769
- error: 'Missing or empty "task" parameter'
770
- };
771
- }
772
- const orchestrationDepth = typeof params.orchestration_depth === "number" ? params.orchestration_depth : 0;
773
- const decompositionDepth = typeof params.decomposition_depth === "number" ? params.decomposition_depth : 0;
774
- if (orchestrationDepth >= 2) {
775
- return {
776
- success: false,
777
- error: "orchestration_depth limit exceeded: max 1 nested orchestration"
778
- };
779
- }
780
- let subtasks = [];
781
- if (decompositionDepth === 0) {
782
- const allDecomposers = getCardsByCapabilityType(this.db, "task_decomposition");
783
- const externalDecomposers = allDecomposers.filter((c) => c.owner !== this.conductorOwner);
784
- if (externalDecomposers.length > 0) {
785
- const provider = externalDecomposers[0];
786
- try {
787
- const providerUrl = this.resolveAgentUrl(provider.owner);
788
- const response = await requestCapability({
789
- gatewayUrl: providerUrl.url,
790
- token: this.gatewayToken,
791
- cardId: provider.id,
792
- params: {
793
- task,
794
- decomposition_depth: decompositionDepth + 1,
795
- orchestration_depth: orchestrationDepth + 1
796
- },
797
- timeoutMs: 3e4
798
- });
799
- if (Array.isArray(response)) {
800
- const validation = validateAndNormalizeSubtasks(response, {
801
- max_credits: this.maxBudget
802
- });
803
- if (validation.errors.length === 0) {
804
- subtasks = validation.valid;
805
- }
806
- }
807
- } catch {
808
- }
809
- }
810
- }
811
- if (subtasks.length === 0) {
812
- subtasks = decompose(task);
813
- }
814
- if (subtasks.length === 0) {
815
- return {
816
- success: false,
817
- error: "No template matches task"
818
- };
819
- }
820
- onProgress?.({ step: 1, total: 5, message: `Decomposed into ${subtasks.length} sub-tasks` });
821
- const matchResults = await matchSubTasks({
822
- db: this.db,
823
- subtasks,
824
- conductorOwner: this.conductorOwner
825
- });
826
- onProgress?.({ step: 2, total: 5, message: `Matched ${matchResults.length} sub-tasks to agents` });
827
- let team;
828
- if (conductorSkill === "orchestrate") {
829
- const strategy = typeof params.formation_strategy === "string" && ["cost_optimized", "quality_optimized", "balanced"].includes(params.formation_strategy) ? params.formation_strategy : "balanced";
830
- team = await formTeam({
831
- subtasks,
832
- strategy,
833
- db: this.db,
834
- conductorOwner: this.conductorOwner
835
- });
836
- onProgress?.({ step: 2, total: 5, message: `Formed team: ${team.matched.length} members, ${team.unrouted.length} unrouted` });
837
- }
838
- const budgetManager = new BudgetManager(this.creditDb, this.conductorOwner);
839
- const budgetController = new BudgetController(budgetManager, this.maxBudget);
840
- const executionBudget = budgetController.calculateBudget(matchResults);
841
- if (!budgetController.canExecute(executionBudget)) {
842
- return {
843
- success: false,
844
- error: `Budget exceeded: estimated ${executionBudget.estimated_total} cr, max ${this.maxBudget} cr`
845
- };
846
- }
847
- onProgress?.({ step: 3, total: 5, message: `Budget approved: ${executionBudget.estimated_total} cr` });
848
- if (conductorSkill === "plan") {
849
- return {
850
- success: true,
851
- result: {
852
- subtasks,
853
- matches: matchResults,
854
- budget: executionBudget,
855
- team
856
- // undefined when no role hints
857
- }
858
- };
859
- }
860
- const matchMap = new Map(
861
- matchResults.map((m) => [m.subtask_id, m])
862
- );
863
- const orchResult = await orchestrate({
864
- subtasks,
865
- matches: matchMap,
866
- gatewayToken: this.gatewayToken,
867
- resolveAgentUrl: this.resolveAgentUrl,
868
- maxBudget: this.maxBudget,
869
- team
870
- });
871
- onProgress?.({ step: 4, total: 5, message: "Pipeline execution complete" });
872
- const resultObj = {};
873
- for (const [key, value] of orchResult.results) {
874
- resultObj[key] = value;
875
- }
876
- return {
877
- success: orchResult.success,
878
- result: {
879
- plan: subtasks,
880
- execution: resultObj,
881
- total_credits: orchResult.total_credits,
882
- latency_ms: orchResult.latency_ms,
883
- errors: orchResult.errors
884
- },
885
- error: orchResult.success ? void 0 : orchResult.errors?.join("; ")
886
- };
887
- }
888
- };
889
1231
  export {
890
- ConductorMode
1232
+ interpolateObject,
1233
+ validateAndNormalizeSubtasks,
1234
+ decompose,
1235
+ computeReputation,
1236
+ searchCards,
1237
+ filterCards,
1238
+ buildReputationMap,
1239
+ requestCapability,
1240
+ requestViaRelay,
1241
+ DEFAULT_AUTONOMY_CONFIG,
1242
+ getAutonomyTier,
1243
+ insertAuditEvent,
1244
+ listPendingRequests,
1245
+ resolvePendingRequest,
1246
+ fetchRemoteCards,
1247
+ mergeResults,
1248
+ AutoRequestor,
1249
+ matchSubTasks,
1250
+ ORCHESTRATION_FEE,
1251
+ BudgetController,
1252
+ DEFAULT_BUDGET_CONFIG,
1253
+ BudgetManager,
1254
+ orchestrate
891
1255
  };