@nookplot/runtime 0.5.142 → 0.5.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/__tests__/apiMarketplace.test.js +189 -2
  2. package/dist/__tests__/apiMarketplace.test.js.map +1 -1
  3. package/dist/__tests__/autonomous.dedup.test.js +11 -0
  4. package/dist/__tests__/autonomous.dedup.test.js.map +1 -1
  5. package/dist/__tests__/autonomous.getAvailableActions.test.js +13 -1
  6. package/dist/__tests__/autonomous.getAvailableActions.test.js.map +1 -1
  7. package/dist/__tests__/autonomous.goalBootstrap.test.d.ts +2 -0
  8. package/dist/__tests__/autonomous.goalBootstrap.test.d.ts.map +1 -0
  9. package/dist/__tests__/autonomous.goalBootstrap.test.js +148 -0
  10. package/dist/__tests__/autonomous.goalBootstrap.test.js.map +1 -0
  11. package/dist/__tests__/autonomous.miningTrack.test.d.ts +2 -0
  12. package/dist/__tests__/autonomous.miningTrack.test.d.ts.map +1 -0
  13. package/dist/__tests__/autonomous.miningTrack.test.js +38 -0
  14. package/dist/__tests__/autonomous.miningTrack.test.js.map +1 -0
  15. package/dist/__tests__/autonomous.payApi.test.d.ts +2 -0
  16. package/dist/__tests__/autonomous.payApi.test.d.ts.map +1 -0
  17. package/dist/__tests__/autonomous.payApi.test.js +73 -0
  18. package/dist/__tests__/autonomous.payApi.test.js.map +1 -0
  19. package/dist/__tests__/autonomous.workspaceOpportunity.test.d.ts +2 -0
  20. package/dist/__tests__/autonomous.workspaceOpportunity.test.d.ts.map +1 -0
  21. package/dist/__tests__/autonomous.workspaceOpportunity.test.js +200 -0
  22. package/dist/__tests__/autonomous.workspaceOpportunity.test.js.map +1 -0
  23. package/dist/__tests__/codegen-drift.test.js +3 -1
  24. package/dist/__tests__/codegen-drift.test.js.map +1 -1
  25. package/dist/__tests__/conversation/modelThresholdsParity.test.js +19 -14
  26. package/dist/__tests__/conversation/modelThresholdsParity.test.js.map +1 -1
  27. package/dist/__tests__/economy.surplusBranch.test.js +64 -0
  28. package/dist/__tests__/economy.surplusBranch.test.js.map +1 -1
  29. package/dist/__tests__/goalLoop.test.d.ts +2 -0
  30. package/dist/__tests__/goalLoop.test.d.ts.map +1 -0
  31. package/dist/__tests__/goalLoop.test.js +358 -0
  32. package/dist/__tests__/goalLoop.test.js.map +1 -0
  33. package/dist/__tests__/helpers/mockRuntime.d.ts.map +1 -1
  34. package/dist/__tests__/helpers/mockRuntime.js +7 -0
  35. package/dist/__tests__/helpers/mockRuntime.js.map +1 -1
  36. package/dist/__tests__/loadProfile.test.d.ts +8 -0
  37. package/dist/__tests__/loadProfile.test.d.ts.map +1 -0
  38. package/dist/__tests__/loadProfile.test.js +134 -0
  39. package/dist/__tests__/loadProfile.test.js.map +1 -0
  40. package/dist/__tests__/mining.test.d.ts +2 -0
  41. package/dist/__tests__/mining.test.d.ts.map +1 -0
  42. package/dist/__tests__/mining.test.js +306 -0
  43. package/dist/__tests__/mining.test.js.map +1 -0
  44. package/dist/__tests__/presetLoader.test.d.ts +2 -0
  45. package/dist/__tests__/presetLoader.test.d.ts.map +1 -0
  46. package/dist/__tests__/presetLoader.test.js +749 -0
  47. package/dist/__tests__/presetLoader.test.js.map +1 -0
  48. package/dist/__tests__/signalActionMap.test.d.ts +17 -0
  49. package/dist/__tests__/signalActionMap.test.d.ts.map +1 -0
  50. package/dist/__tests__/signalActionMap.test.js +165 -0
  51. package/dist/__tests__/signalActionMap.test.js.map +1 -0
  52. package/dist/__tests__/usdcBudget.test.d.ts +2 -0
  53. package/dist/__tests__/usdcBudget.test.d.ts.map +1 -0
  54. package/dist/__tests__/usdcBudget.test.js +128 -0
  55. package/dist/__tests__/usdcBudget.test.js.map +1 -0
  56. package/dist/__tests__/x402.test.d.ts +2 -0
  57. package/dist/__tests__/x402.test.d.ts.map +1 -0
  58. package/dist/__tests__/x402.test.js +117 -0
  59. package/dist/__tests__/x402.test.js.map +1 -0
  60. package/dist/actionCatalog.d.ts.map +1 -1
  61. package/dist/actionCatalog.generated.d.ts +1 -1
  62. package/dist/actionCatalog.generated.d.ts.map +1 -1
  63. package/dist/actionCatalog.generated.js +143 -23
  64. package/dist/actionCatalog.generated.js.map +1 -1
  65. package/dist/actionCatalog.js +0 -10
  66. package/dist/actionCatalog.js.map +1 -1
  67. package/dist/api-marketplace.d.ts +146 -0
  68. package/dist/api-marketplace.d.ts.map +1 -1
  69. package/dist/api-marketplace.js +218 -0
  70. package/dist/api-marketplace.js.map +1 -1
  71. package/dist/autonomous.d.ts +16 -9
  72. package/dist/autonomous.d.ts.map +1 -1
  73. package/dist/autonomous.js +268 -59
  74. package/dist/autonomous.js.map +1 -1
  75. package/dist/contentSafety.d.ts +1 -1
  76. package/dist/contentSafety.d.ts.map +1 -1
  77. package/dist/contentSafety.js +6 -2
  78. package/dist/contentSafety.js.map +1 -1
  79. package/dist/discovery.js +1 -1
  80. package/dist/discovery.js.map +1 -1
  81. package/dist/economy.d.ts +10 -15
  82. package/dist/economy.d.ts.map +1 -1
  83. package/dist/economy.js +16 -29
  84. package/dist/economy.js.map +1 -1
  85. package/dist/goal/goalLoop.d.ts +78 -0
  86. package/dist/goal/goalLoop.d.ts.map +1 -0
  87. package/dist/goal/goalLoop.js +388 -0
  88. package/dist/goal/goalLoop.js.map +1 -0
  89. package/dist/goal/goalPrompts.d.ts +20 -0
  90. package/dist/goal/goalPrompts.d.ts.map +1 -0
  91. package/dist/goal/goalPrompts.js +54 -0
  92. package/dist/goal/goalPrompts.js.map +1 -0
  93. package/dist/goal/types.d.ts +102 -0
  94. package/dist/goal/types.d.ts.map +1 -0
  95. package/dist/goal/types.js +7 -0
  96. package/dist/goal/types.js.map +1 -0
  97. package/dist/identity.d.ts +51 -0
  98. package/dist/identity.d.ts.map +1 -1
  99. package/dist/identity.js +50 -0
  100. package/dist/identity.js.map +1 -1
  101. package/dist/index.d.ts +20 -3
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +16 -1
  104. package/dist/index.js.map +1 -1
  105. package/dist/loadProfile.d.ts +100 -0
  106. package/dist/loadProfile.d.ts.map +1 -0
  107. package/dist/loadProfile.js +221 -0
  108. package/dist/loadProfile.js.map +1 -0
  109. package/dist/presetLoader.d.ts +130 -0
  110. package/dist/presetLoader.d.ts.map +1 -0
  111. package/dist/presetLoader.js +734 -0
  112. package/dist/presetLoader.js.map +1 -0
  113. package/dist/signalActionMap.d.ts.map +1 -1
  114. package/dist/signalActionMap.js +15 -5
  115. package/dist/signalActionMap.js.map +1 -1
  116. package/dist/swarms.d.ts +13 -0
  117. package/dist/swarms.d.ts.map +1 -1
  118. package/dist/swarms.js +4 -0
  119. package/dist/swarms.js.map +1 -1
  120. package/dist/tools.js +1 -1
  121. package/dist/tools.js.map +1 -1
  122. package/dist/types.d.ts +21 -0
  123. package/dist/types.d.ts.map +1 -1
  124. package/dist/usdcBudget.d.ts +90 -0
  125. package/dist/usdcBudget.d.ts.map +1 -0
  126. package/dist/usdcBudget.js +155 -0
  127. package/dist/usdcBudget.js.map +1 -0
  128. package/dist/x402.d.ts +69 -0
  129. package/dist/x402.d.ts.map +1 -0
  130. package/dist/x402.js +139 -0
  131. package/dist/x402.js.map +1 -0
  132. package/package.json +2 -2
  133. package/dist/__tests__/economy.frontierInference.test.d.ts +0 -2
  134. package/dist/__tests__/economy.frontierInference.test.d.ts.map +0 -1
  135. package/dist/__tests__/economy.frontierInference.test.js +0 -61
  136. package/dist/__tests__/economy.frontierInference.test.js.map +0 -1
  137. package/dist/frontierPass.d.ts +0 -30
  138. package/dist/frontierPass.d.ts.map +0 -1
  139. package/dist/frontierPass.js +0 -42
  140. package/dist/frontierPass.js.map +0 -1
@@ -40,11 +40,12 @@
40
40
  * @module autonomous
41
41
  */
42
42
  import { prepareSignRelay } from "./signing.js";
43
- import { runFrontierPass } from "./frontierPass.js";
43
+ import { formatUsdc } from "./usdcBudget.js";
44
44
  import { wrapUntrusted, sanitizeForPrompt, UNTRUSTED_CONTENT_INSTRUCTION } from "./contentSafety.js";
45
45
  import { getAvailableActionsFromMap } from "./signalActionMap.js";
46
46
  import { getCategoryListing, getToolsInCategory } from "./actionCatalog.js";
47
47
  import { WakeUpStack } from "./wakeUpStack.js";
48
+ import { GoalLoop } from "./goal/goalLoop.js";
48
49
  import { hooks as defaultHooks } from "./hooks.js";
49
50
  import { guardrails as defaultGuardrails, GuardrailTripped, InputGuardrailTripped, } from "./guardrails.js";
50
51
  import { buildCorrectivePrompt, checkForDoomLoopFromSignatures, makeSignature, } from "./doomLoop.js";
@@ -62,7 +63,7 @@ const ON_CHAIN_ACTIONS = new Set([
62
63
  "cancel_bounty", "unclaim_bounty",
63
64
  "expire_disputed_bounty", "sweep_treasury_fees", // V8
64
65
  "sweep_creator_refund", // V9 H4 admin recovery
65
- "create_listing", "list_service", "update_service", "create_agreement",
66
+ "create_listing", "list_service", "update_service", "create_agreement", "api_onboard",
66
67
  "deliver_work", "settle_agreement", "dispute_agreement", "cancel_agreement",
67
68
  "expire_dispute", "expire_delivered",
68
69
  "deploy_preview",
@@ -100,7 +101,6 @@ const ON_CHAIN_ACTIONS = new Set([
100
101
  // roadmap §1h Decision 11. Approval-gates the submission before the gateway
101
102
  // pipes the artifact through /submit-solution → submitRlmTrajectory.
102
103
  "submit_rlm",
103
- // API marketplace on-chain actions use existing service/* prepare endpoints
104
104
  // Social (missing)
105
105
  "remove_vote", "revoke_attestation",
106
106
  // Bounty lifecycle (missing)
@@ -124,6 +124,9 @@ const ON_CHAIN_ACTIONS = new Set([
124
124
  "claim_pending_guild_mining_treasury",
125
125
  // Ecosystem partner protocols (raw-tx, agent pays own gas)
126
126
  "ecosystem_stake_tokens", "ecosystem_claim_rewards",
127
+ // Per-call x402 API purchase (client-signed EIP-3009 USDC auth, spends real
128
+ // USDC) — gated for the same reason as ecosystem_*: it moves the agent's money.
129
+ "pay_api",
127
130
  // V11: Multi-payout Open bounties (6 actions — creator + submitter + recovery)
128
131
  "create_open_bounty", "submit_open_bounty", "approve_open_submission",
129
132
  "top_up_open_bounty", "close_open_bounty", "sweep_worker_payout",
@@ -243,6 +246,21 @@ export class AutonomousAgent {
243
246
  if (this.verbose) {
244
247
  console.log("[autonomous] AutonomousAgent started — handling signals + actions");
245
248
  }
249
+ // Pre-load tool categories so the LLM always has web_search +
250
+ // search_knowledge visible without a browse_tools cold-start.
251
+ // Saves ~1 LLM turn per goal-driven agent and simplifies the first
252
+ // step for reactive agents too (CLAUDE.md rule — small code change).
253
+ this.loadedCategories.add("tools");
254
+ this.loadedCategories.add("discovery");
255
+ this.loadedCategories.add("knowledge");
256
+ // Goal bootstrap — run the GoalLoop in background if this agent was
257
+ // forged with initial_goal set (L1 swarm auto-deploy). Failures are
258
+ // non-fatal: the agent continues in normal reactive mode.
259
+ this.maybeBootstrapGoal().catch((err) => {
260
+ if (this.verbose) {
261
+ console.error("[autonomous] Goal bootstrap failed:", err);
262
+ }
263
+ });
246
264
  }
247
265
  /** Stop the autonomous agent. */
248
266
  stop() {
@@ -252,6 +270,157 @@ export class AutonomousAgent {
252
270
  }
253
271
  }
254
272
  // ================================================================
273
+ // Goal bootstrap (L3 — migration 247)
274
+ // ================================================================
275
+ /**
276
+ * Check whether this agent has an initial_goal + pending status, and
277
+ * if so, spin up a GoalLoop in the background. Non-blocking — start()
278
+ * returns immediately so WebSocket signal subscriptions are live even
279
+ * while the goal loop is running its first step.
280
+ */
281
+ async maybeBootstrapGoal() {
282
+ let goalConfig;
283
+ try {
284
+ goalConfig = await this.runtime.identity.getGoal();
285
+ }
286
+ catch (err) {
287
+ if (this.verbose) {
288
+ console.error("[autonomous] getGoal failed — treating as no goal:", err);
289
+ }
290
+ return;
291
+ }
292
+ if (!goalConfig || !goalConfig.initialGoal)
293
+ return;
294
+ if (goalConfig.goalStatus !== "pending") {
295
+ if (this.verbose) {
296
+ console.log(`[autonomous] Skipping goal bootstrap — status is ${goalConfig.goalStatus}, not 'pending'`);
297
+ }
298
+ return;
299
+ }
300
+ if (this.verbose) {
301
+ console.log(`[autonomous] Goal bootstrap: "${goalConfig.initialGoal.slice(0, 80)}..." budget=${goalConfig.goalBudgetNook ?? "unlimited"}`);
302
+ }
303
+ // Transition status atomically before running the loop. If the
304
+ // gateway rejects (network, permissions), bail out — we do not want
305
+ // to run the loop with a mismatched DB state.
306
+ try {
307
+ await this.runtime.identity.updateGoalStatus("in_progress");
308
+ }
309
+ catch (err) {
310
+ if (this.verbose) {
311
+ console.error("[autonomous] Failed to transition goal → in_progress:", err);
312
+ }
313
+ return;
314
+ }
315
+ const budgetNook = goalConfig.goalBudgetNook ? BigInt(goalConfig.goalBudgetNook) : 0n;
316
+ const loopOptions = {
317
+ runtime: this.runtime,
318
+ goal: goalConfig.initialGoal,
319
+ budgetNook,
320
+ parentSwarmId: goalConfig.goalParentSwarmId,
321
+ verbose: this.verbose,
322
+ };
323
+ const loop = new GoalLoop(loopOptions);
324
+ let result;
325
+ try {
326
+ result = await loop.run();
327
+ }
328
+ catch (err) {
329
+ const msg = err instanceof Error ? err.message : String(err);
330
+ if (this.verbose) {
331
+ console.error("[autonomous] GoalLoop threw:", err);
332
+ }
333
+ // Transition to failed so the UI reflects the error.
334
+ await this.runtime.identity.updateGoalStatus("failed").catch(() => { });
335
+ await this.runtime.identity.createPendingTask({
336
+ reason: "unclear_goal",
337
+ description: `Goal loop crashed: ${msg.slice(0, 400)}`,
338
+ parentSwarmId: goalConfig.goalParentSwarmId,
339
+ }).catch(() => { });
340
+ return;
341
+ }
342
+ await this.handleGoalResult(result, goalConfig);
343
+ }
344
+ /**
345
+ * Dispatch on the terminal state of a GoalLoop run:
346
+ * - complete → store artifact in private KG, pause agent
347
+ * - blocked_budget → create pending task (budget_exhausted), pause
348
+ * - blocked_stuck → create pending task (stuck_3x), pause
349
+ * - blocked_capability → create pending task (needs_capability), pause
350
+ */
351
+ async handleGoalResult(result, goalConfig) {
352
+ if (result.outcome === "complete") {
353
+ // Store deliverable in private KG
354
+ let artifactId = null;
355
+ try {
356
+ const storeResult = (await this.runtime.connection.request("POST", "/v1/agents/me/knowledge", {
357
+ contentText: result.artifact.body,
358
+ title: result.artifact.title,
359
+ domain: result.artifact.domain,
360
+ visibility: "private",
361
+ knowledgeType: "fact",
362
+ sourceType: "import",
363
+ metadata: {
364
+ goal: goalConfig.initialGoal,
365
+ parentSwarmId: goalConfig.goalParentSwarmId,
366
+ stepsExecuted: result.stepsExecuted,
367
+ spentNook: result.spentNook.toString(),
368
+ },
369
+ }));
370
+ artifactId = storeResult?.id ?? null;
371
+ }
372
+ catch (err) {
373
+ if (this.verbose) {
374
+ console.error("[autonomous] Failed to store goal artifact:", err);
375
+ }
376
+ }
377
+ try {
378
+ await this.runtime.identity.completeGoal(artifactId ?? "unknown");
379
+ }
380
+ catch (err) {
381
+ if (this.verbose) {
382
+ console.error("[autonomous] completeGoal failed:", err);
383
+ }
384
+ }
385
+ // Q3: agent pauses after completion, does not stay reactive
386
+ this.stop();
387
+ return;
388
+ }
389
+ if (result.outcome === "blocked_budget") {
390
+ // initialGoal is non-null here — maybeBootstrapGoal returned early otherwise.
391
+ const goalText = goalConfig.initialGoal ?? "(unknown)";
392
+ await this.runtime.identity.createPendingTask({
393
+ reason: "budget_exhausted",
394
+ description: `Needs top-off to continue goal: ${goalText.slice(0, 300)}`,
395
+ parentSwarmId: goalConfig.goalParentSwarmId,
396
+ }).catch(() => { });
397
+ await this.runtime.identity.updateGoalStatus("paused_awaiting_topoff").catch(() => { });
398
+ this.stop();
399
+ return;
400
+ }
401
+ if (result.outcome === "blocked_stuck") {
402
+ await this.runtime.identity.createPendingTask({
403
+ reason: "stuck_3x",
404
+ description: result.stuckReason,
405
+ parentSwarmId: goalConfig.goalParentSwarmId,
406
+ }).catch(() => { });
407
+ await this.runtime.identity.updateGoalStatus("blocked_needs_decision").catch(() => { });
408
+ this.stop();
409
+ return;
410
+ }
411
+ if (result.outcome === "blocked_capability") {
412
+ await this.runtime.identity.createPendingTask({
413
+ reason: "needs_capability",
414
+ description: result.capabilityNeeded,
415
+ suggestedPresetId: result.suggestedPreset,
416
+ parentSwarmId: goalConfig.goalParentSwarmId,
417
+ }).catch(() => { });
418
+ await this.runtime.identity.updateGoalStatus("blocked_needs_decision").catch(() => { });
419
+ this.stop();
420
+ return;
421
+ }
422
+ }
423
+ // ================================================================
255
424
  // Signal handling (proactive.signal)
256
425
  // ================================================================
257
426
  /**
@@ -333,6 +502,8 @@ export class AutonomousAgent {
333
502
  return `proj_bounty_done:${data.bountyId ?? ""}`;
334
503
  case "guild_opportunity":
335
504
  return `guild:${data.guildId ?? ""}:${addr}`;
505
+ case "workspace_opportunity":
506
+ return `workspace:${data.workspaceId ?? data.sourceId ?? ""}:${addr}`;
336
507
  case "team_assembly_suggested":
337
508
  return `team_suggest:${data.txHash ?? ""}`;
338
509
  case "team_invitation":
@@ -482,9 +653,7 @@ export class AutonomousAgent {
482
653
  case "community_gap":
483
654
  await this.handleCommunityGap(data);
484
655
  break;
485
- case "directive":
486
- await this.handleDirective(data);
487
- break;
656
+ // DD-7: directive case removed — swarm coordination uses DMs
488
657
  case "files_committed":
489
658
  await this.handleFilesCommitted(data);
490
659
  break;
@@ -597,6 +766,10 @@ export class AutonomousAgent {
597
766
  case "guild_opportunity":
598
767
  await this.handleGuildOpportunity(data);
599
768
  break;
769
+ // ── Open Cognitive Workspaces (P3): discoverable/open workspace to join ──
770
+ case "workspace_opportunity":
771
+ await this.handleWorkspaceOpportunity(data);
772
+ break;
600
773
  // ── Mining signals ──
601
774
  case "mining_opportunity":
602
775
  await this.handleMiningOpportunity(data);
@@ -1123,6 +1296,51 @@ export class AutonomousAgent {
1123
1296
  console.error("[autonomous] Guild opportunity handling failed:", err);
1124
1297
  }
1125
1298
  }
1299
+ async handleWorkspaceOpportunity(data) {
1300
+ const meta = data;
1301
+ const name = meta.name ?? meta.title ?? "Unknown Workspace";
1302
+ const workspaceId = meta.workspaceId ?? meta.sourceId ?? "";
1303
+ const visibility = meta.visibility ?? "discoverable";
1304
+ const description = meta.description ?? data.messagePreview ?? "";
1305
+ const memberCount = meta.memberCount ?? 0;
1306
+ const regionCounts = meta.regionCounts ?? {};
1307
+ const openJoinRole = meta.openJoinRole ?? 0;
1308
+ try {
1309
+ const isOpen = visibility === "open";
1310
+ const joinNote = isOpen
1311
+ ? `You can self-join instantly (you would join as ${openJoinRole === 1 ? "editor" : "viewer"}).`
1312
+ : "You can request to join; the owner approves.";
1313
+ const regionSummary = Object.entries(regionCounts)
1314
+ .map(([r, c]) => `${r}: ${c}`)
1315
+ .join(", ") || "no cognitive state yet";
1316
+ const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
1317
+ "A cognitive workspace opportunity was found on Nookplot.\n" +
1318
+ `Workspace: ${sanitizeForPrompt(name)}\n` +
1319
+ `Description: ${wrapUntrusted(description, "workspace description")}\n` +
1320
+ `Visibility: ${visibility}\n` +
1321
+ `Members: ${memberCount}\n` +
1322
+ `Cognitive state: ${sanitizeForPrompt(regionSummary)}\n` +
1323
+ `ID: ${workspaceId}\n` +
1324
+ `${joinNote}\n\n` +
1325
+ "Should you join this workspace to collaborate on its shared reasoning state? Respond with INTERESTED or SKIP.\n" +
1326
+ "If interested, briefly explain why (under 200 chars).\n\n" +
1327
+ "Format:\nDECISION: INTERESTED or SKIP\nREASON: why you want to join";
1328
+ const response = await this.generateResponse(prompt);
1329
+ const text = response?.trim() ?? "";
1330
+ if (text.toUpperCase().includes("INTERESTED")) {
1331
+ if (this.verbose) {
1332
+ console.log(`[autonomous] ✓ Interested in workspace "${name}" (supervised — join surfaced as an action)`);
1333
+ }
1334
+ // Joining is surfaced as an available action (join_workspace /
1335
+ // request_workspace_join) for the agent's decision loop — not
1336
+ // auto-executed here (adversarial default: no auto-join).
1337
+ }
1338
+ }
1339
+ catch (err) {
1340
+ if (this.verbose)
1341
+ console.error("[autonomous] Workspace opportunity handling failed:", err);
1342
+ }
1343
+ }
1126
1344
  async handleMiningOpportunity(data) {
1127
1345
  const meta = data;
1128
1346
  const opportunityType = meta.opportunityType ?? "unknown";
@@ -2015,40 +2233,7 @@ export class AutonomousAgent {
2015
2233
  console.error("[autonomous] Community gap handling failed:", err);
2016
2234
  }
2017
2235
  }
2018
- async handleDirective(data) {
2019
- const directiveContent = data.messagePreview ?? "";
2020
- const channelId = data.channelId;
2021
- const community = data.community ?? "general";
2022
- try {
2023
- const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
2024
- "You received a directive on Nookplot.\n" +
2025
- `Directive:\n${wrapUntrusted(directiveContent, "directive")}\n\n` +
2026
- "Follow the directive and compose your response.\n" +
2027
- "If it asks you to post, write the post content.\n" +
2028
- "If it asks you to discuss, write a discussion message.\n" +
2029
- "If you can't follow this directive, respond with exactly: [SKIP]\n\n" +
2030
- "Your response (under 500 chars):";
2031
- const response = await this.generateResponse(prompt);
2032
- const content = response?.trim() ?? "";
2033
- if (content && content !== "[SKIP]") {
2034
- if (channelId) {
2035
- await this.runtime.channels.send(channelId, content);
2036
- if (this.verbose)
2037
- console.log(`[autonomous] ✓ Directive response sent to channel ${channelId.slice(0, 12)}`);
2038
- }
2039
- else {
2040
- const title = content.slice(0, 100);
2041
- await this.runtime.memory.publishKnowledge({ title, body: content, community });
2042
- if (this.verbose)
2043
- console.log(`[autonomous] ✓ Directive response posted in ${community}`);
2044
- }
2045
- }
2046
- }
2047
- catch (err) {
2048
- if (this.verbose)
2049
- console.error("[autonomous] Directive handling failed:", err);
2050
- }
2051
- }
2236
+ // DD-7: handleDirective removed — swarm coordination uses DMs exclusively
2052
2237
  // ================================================================
2053
2238
  // Project collaboration signal handlers
2054
2239
  // ================================================================
@@ -2682,16 +2867,6 @@ export class AutonomousAgent {
2682
2867
  // ================================================================
2683
2868
  // Action request handling (proactive.action.request)
2684
2869
  // ================================================================
2685
- /**
2686
- * Execute a `use_frontier_model` action — delegates to the shared
2687
- * {@link runFrontierPass} so the runtime + CLI dispatch paths can't drift on
2688
- * this money path. Consumes an owner-reserved pass, runs a one-shot Surplus
2689
- * completion paid by the agent's OWN x402 key, finalizes (or reverts on
2690
- * failure). Returns the frontier answer, or an `{ error }` tool result.
2691
- */
2692
- async executeFrontierPass(args, agentAddress) {
2693
- return runFrontierPass(this.runtime.connection, this.runtime.economy, args, agentAddress);
2694
- }
2695
2870
  async handleActionRequest(event) {
2696
2871
  if (!this.isRunning)
2697
2872
  return;
@@ -2745,6 +2920,18 @@ export class AutonomousAgent {
2745
2920
  triggers: this.doomLoopTriggers,
2746
2921
  actionType,
2747
2922
  });
2923
+ // Track C.2: also push to gateway as fire-and-forget telemetry so
2924
+ // ops dashboards can answer "which tools most often misbehave?"
2925
+ // and "is this agent stuck right now?" — see
2926
+ // gateway/src/services/doomLoopMetrics.ts. Errors are swallowed so
2927
+ // a backend outage never blocks the runtime's recovery path.
2928
+ void this.runtime.connection
2929
+ .request("POST", "/v1/agents/me/doom-loop-event", {
2930
+ offender: doomOffender,
2931
+ triggers: this.doomLoopTriggers,
2932
+ actionType,
2933
+ })
2934
+ .catch(() => { });
2748
2935
  if (this.doomLoopTriggers >= AUTONOMOUS_DOOM_LOOP_MAX_TRIGGERS) {
2749
2936
  if (this.verbose) {
2750
2937
  console.warn(`[autonomous] ✗ doom loop on '${doomOffender}' (${this.doomLoopTriggers} triggers) — aborting cycle`);
@@ -2850,21 +3037,43 @@ export class AutonomousAgent {
2850
3037
  });
2851
3038
  return;
2852
3039
  }
2853
- // ── Intercept use_frontier_model (client-executed frontier pass) ──
2854
- // Not a gateway tool: consume an owner-reserved pass, run a one-shot
2855
- // Surplus completion paid by the agent's OWN client-signed x402 key, then
2856
- // finalize (or revert on failure). The gateway can't run this
2857
- // non-custodially. See ROADMAP_frontier-passes.md.
2858
- if (actionType === "use_frontier_model") {
2859
- result = await this.executeFrontierPass(args, agentAddress);
3040
+ // ── Intercept pay_api (per-call x402 purchase) ──
3041
+ // Client-side-signed like ecosystem_*: payAndCall signs an EIP-3009 USDC
3042
+ // authorization with the agent's own key (the gateway is non-custodial), so
3043
+ // it CANNOT route through the unified dispatch. The hard USDC spend cap
3044
+ // (budgetUsdc) is enforced HERE, at the point of spend.
3045
+ if (actionType === "pay_api") {
3046
+ const privateKey = this.runtime.connection.privateKey;
3047
+ if (!privateKey)
3048
+ throw new Error("pay_api requires the agent's private key (none configured)");
3049
+ const budget = this.runtime.usdcBudget;
3050
+ const maxForCall = budget.maxForNextCall(); // bigint | undefined; 0n = exhausted
3051
+ if (maxForCall === 0n) {
3052
+ result = { paid: false, blocked: "usdc_budget_exhausted", reason: `USDC daily spend cap exhausted (spent ${formatUsdc(budget.spent)})` };
3053
+ }
3054
+ else {
3055
+ const { ethers } = await import("ethers");
3056
+ const wallet = new ethers.Wallet(privateKey);
3057
+ const payResult = await this.runtime.x402.payAndCall({
3058
+ listingId: args.listingId,
3059
+ path: String(args.path ?? ""),
3060
+ method: args.method,
3061
+ body: args.body,
3062
+ ...(maxForCall !== undefined ? { maxAmountBaseUnits: maxForCall } : {}),
3063
+ }, wallet);
3064
+ if (payResult.paid && payResult.amountPaidBaseUnits)
3065
+ budget.record(BigInt(payResult.amountPaidBaseUnits));
3066
+ result = { ...payResult };
3067
+ }
2860
3068
  result = await guardrails.runOutput(actionType, result);
2861
3069
  hooks.emitFireAndForget("tool_output", { toolName: actionType, args, result });
3070
+ // No txHash — the settlement tx is internal to the facilitator.
2862
3071
  if (actionId)
2863
3072
  await this.runtime.proactive.completeAction(actionId, undefined, result);
2864
3073
  if (this.verbose)
2865
- console.log(`[autonomous] ✓ use_frontier_model`);
3074
+ console.log(`[autonomous] ✓ pay_api listing=${String(args.listingId)} paid=${result.paid}`);
2866
3075
  hooks.emitFireAndForget("action_end", {
2867
- actionType, args, result, durationMs: Date.now() - startTime, actionId,
3076
+ actionType, args, result, durationMs: Date.now() - startTime, actionId, txHash: undefined,
2868
3077
  });
2869
3078
  return;
2870
3079
  }