@nookplot/runtime 0.5.131 → 0.5.132
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/dist/__tests__/apiMarketplace.test.d.ts +2 -0
- package/dist/__tests__/apiMarketplace.test.d.ts.map +1 -0
- package/dist/__tests__/apiMarketplace.test.js +102 -0
- package/dist/__tests__/apiMarketplace.test.js.map +1 -0
- package/dist/__tests__/autonomous.actionDispatch.test.d.ts +2 -0
- package/dist/__tests__/autonomous.actionDispatch.test.d.ts.map +1 -0
- package/dist/__tests__/autonomous.actionDispatch.test.js +287 -0
- package/dist/__tests__/autonomous.actionDispatch.test.js.map +1 -0
- package/dist/__tests__/autonomous.dedup.test.d.ts +2 -0
- package/dist/__tests__/autonomous.dedup.test.d.ts.map +1 -0
- package/dist/__tests__/autonomous.dedup.test.js +125 -0
- package/dist/__tests__/autonomous.dedup.test.js.map +1 -0
- package/dist/__tests__/autonomous.doomLoop.test.d.ts +2 -0
- package/dist/__tests__/autonomous.doomLoop.test.d.ts.map +1 -0
- package/dist/__tests__/autonomous.doomLoop.test.js +126 -0
- package/dist/__tests__/autonomous.doomLoop.test.js.map +1 -0
- package/dist/__tests__/autonomous.getAvailableActions.test.d.ts +2 -0
- package/dist/__tests__/autonomous.getAvailableActions.test.d.ts.map +1 -0
- package/dist/__tests__/autonomous.getAvailableActions.test.js +233 -0
- package/dist/__tests__/autonomous.getAvailableActions.test.js.map +1 -0
- package/dist/__tests__/autonomous.guardrails.test.d.ts +2 -0
- package/dist/__tests__/autonomous.guardrails.test.d.ts.map +1 -0
- package/dist/__tests__/autonomous.guardrails.test.js +215 -0
- package/dist/__tests__/autonomous.guardrails.test.js.map +1 -0
- package/dist/__tests__/autonomous.hooks.test.d.ts +2 -0
- package/dist/__tests__/autonomous.hooks.test.d.ts.map +1 -0
- package/dist/__tests__/autonomous.hooks.test.js +107 -0
- package/dist/__tests__/autonomous.hooks.test.js.map +1 -0
- package/dist/__tests__/autonomous.latentSpace.test.d.ts +2 -0
- package/dist/__tests__/autonomous.latentSpace.test.d.ts.map +1 -0
- package/dist/__tests__/autonomous.latentSpace.test.js +224 -0
- package/dist/__tests__/autonomous.latentSpace.test.js.map +1 -0
- package/dist/__tests__/autonomous.lifecycle.test.d.ts +2 -0
- package/dist/__tests__/autonomous.lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/autonomous.lifecycle.test.js +147 -0
- package/dist/__tests__/autonomous.lifecycle.test.js.map +1 -0
- package/dist/__tests__/autonomous.loadedSkillRefs.test.d.ts +2 -0
- package/dist/__tests__/autonomous.loadedSkillRefs.test.d.ts.map +1 -0
- package/dist/__tests__/autonomous.loadedSkillRefs.test.js +150 -0
- package/dist/__tests__/autonomous.loadedSkillRefs.test.js.map +1 -0
- package/dist/__tests__/chatEngine.episodicHook.test.d.ts +2 -0
- package/dist/__tests__/chatEngine.episodicHook.test.d.ts.map +1 -0
- package/dist/__tests__/chatEngine.episodicHook.test.js +160 -0
- package/dist/__tests__/chatEngine.episodicHook.test.js.map +1 -0
- package/dist/__tests__/chatEngine.test.d.ts +2 -0
- package/dist/__tests__/chatEngine.test.d.ts.map +1 -0
- package/dist/__tests__/chatEngine.test.js +482 -0
- package/dist/__tests__/chatEngine.test.js.map +1 -0
- package/dist/__tests__/codegen-drift.test.d.ts +23 -0
- package/dist/__tests__/codegen-drift.test.d.ts.map +1 -0
- package/dist/__tests__/codegen-drift.test.js +185 -0
- package/dist/__tests__/codegen-drift.test.js.map +1 -0
- package/dist/__tests__/contentSafety.test.d.ts +2 -0
- package/dist/__tests__/contentSafety.test.d.ts.map +1 -0
- package/dist/__tests__/contentSafety.test.js +90 -0
- package/dist/__tests__/contentSafety.test.js.map +1 -0
- package/dist/__tests__/conversation/compactionMemory.test.d.ts +2 -0
- package/dist/__tests__/conversation/compactionMemory.test.d.ts.map +1 -0
- package/dist/__tests__/conversation/compactionMemory.test.js +447 -0
- package/dist/__tests__/conversation/compactionMemory.test.js.map +1 -0
- package/dist/__tests__/conversation/modelThresholdsParity.test.d.ts +2 -0
- package/dist/__tests__/conversation/modelThresholdsParity.test.d.ts.map +1 -0
- package/dist/__tests__/conversation/modelThresholdsParity.test.js +79 -0
- package/dist/__tests__/conversation/modelThresholdsParity.test.js.map +1 -0
- package/dist/__tests__/doomLoop.test.d.ts +6 -0
- package/dist/__tests__/doomLoop.test.d.ts.map +1 -0
- package/dist/__tests__/doomLoop.test.js +144 -0
- package/dist/__tests__/doomLoop.test.js.map +1 -0
- package/dist/__tests__/guardrails.test.d.ts +2 -0
- package/dist/__tests__/guardrails.test.d.ts.map +1 -0
- package/dist/__tests__/guardrails.test.js +236 -0
- package/dist/__tests__/guardrails.test.js.map +1 -0
- package/dist/__tests__/helpers/mockRuntime.d.ts +11 -0
- package/dist/__tests__/helpers/mockRuntime.d.ts.map +1 -0
- package/dist/__tests__/helpers/mockRuntime.js +146 -0
- package/dist/__tests__/helpers/mockRuntime.js.map +1 -0
- package/dist/__tests__/hooks.test.d.ts +9 -0
- package/dist/__tests__/hooks.test.d.ts.map +1 -0
- package/dist/__tests__/hooks.test.js +192 -0
- package/dist/__tests__/hooks.test.js.map +1 -0
- package/dist/__tests__/manifestActivationHook.test.d.ts +2 -0
- package/dist/__tests__/manifestActivationHook.test.d.ts.map +1 -0
- package/dist/__tests__/manifestActivationHook.test.js +312 -0
- package/dist/__tests__/manifestActivationHook.test.js.map +1 -0
- package/dist/__tests__/memory.test.d.ts +2 -0
- package/dist/__tests__/memory.test.d.ts.map +1 -0
- package/dist/__tests__/memory.test.js +192 -0
- package/dist/__tests__/memory.test.js.map +1 -0
- package/dist/__tests__/onChainActions.parity.test.d.ts +12 -0
- package/dist/__tests__/onChainActions.parity.test.d.ts.map +1 -0
- package/dist/__tests__/onChainActions.parity.test.js +104 -0
- package/dist/__tests__/onChainActions.parity.test.js.map +1 -0
- package/dist/__tests__/querySegmentation.test.d.ts +2 -0
- package/dist/__tests__/querySegmentation.test.d.ts.map +1 -0
- package/dist/__tests__/querySegmentation.test.js +187 -0
- package/dist/__tests__/querySegmentation.test.js.map +1 -0
- package/dist/__tests__/sandbox.test.d.ts +13 -0
- package/dist/__tests__/sandbox.test.d.ts.map +1 -0
- package/dist/__tests__/sandbox.test.js +413 -0
- package/dist/__tests__/sandbox.test.js.map +1 -0
- package/dist/__tests__/signing.test.d.ts +2 -0
- package/dist/__tests__/signing.test.d.ts.map +1 -0
- package/dist/__tests__/signing.test.js +260 -0
- package/dist/__tests__/signing.test.js.map +1 -0
- package/dist/__tests__/wakeUpStack.test.d.ts +2 -0
- package/dist/__tests__/wakeUpStack.test.d.ts.map +1 -0
- package/dist/__tests__/wakeUpStack.test.js +239 -0
- package/dist/__tests__/wakeUpStack.test.js.map +1 -0
- package/dist/actionCatalog.d.ts +57 -0
- package/dist/actionCatalog.d.ts.map +1 -0
- package/dist/actionCatalog.generated.d.ts +4 -0
- package/dist/actionCatalog.generated.d.ts.map +1 -0
- package/dist/actionCatalog.generated.js +2194 -0
- package/dist/actionCatalog.generated.js.map +1 -0
- package/dist/actionCatalog.js +214 -0
- package/dist/actionCatalog.js.map +1 -0
- package/dist/api-marketplace.d.ts +111 -0
- package/dist/api-marketplace.d.ts.map +1 -0
- package/dist/api-marketplace.js +154 -0
- package/dist/api-marketplace.js.map +1 -0
- package/dist/artifactEmbeddings.d.ts +69 -0
- package/dist/artifactEmbeddings.d.ts.map +1 -0
- package/dist/artifactEmbeddings.js +52 -0
- package/dist/artifactEmbeddings.js.map +1 -0
- package/dist/autonomous.d.ts +271 -0
- package/dist/autonomous.d.ts.map +1 -0
- package/dist/autonomous.js +3517 -0
- package/dist/autonomous.js.map +1 -0
- package/dist/bounties.d.ts +112 -0
- package/dist/bounties.d.ts.map +1 -0
- package/dist/bounties.js +140 -0
- package/dist/bounties.js.map +1 -0
- package/dist/bundles.d.ts +174 -0
- package/dist/bundles.d.ts.map +1 -0
- package/dist/bundles.js +208 -0
- package/dist/bundles.js.map +1 -0
- package/dist/channels.d.ts +131 -0
- package/dist/channels.d.ts.map +1 -0
- package/dist/channels.js +227 -0
- package/dist/channels.js.map +1 -0
- package/dist/chat/chatEngine.d.ts +138 -0
- package/dist/chat/chatEngine.d.ts.map +1 -0
- package/dist/chat/chatEngine.js +613 -0
- package/dist/chat/chatEngine.js.map +1 -0
- package/dist/chat/index.d.ts +30 -0
- package/dist/chat/index.d.ts.map +1 -0
- package/dist/chat/index.js +29 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/terminal.d.ts +19 -0
- package/dist/chat/terminal.d.ts.map +1 -0
- package/dist/chat/terminal.js +17 -0
- package/dist/chat/terminal.js.map +1 -0
- package/dist/chat/terminals/discordTerminal.d.ts +22 -0
- package/dist/chat/terminals/discordTerminal.d.ts.map +1 -0
- package/dist/chat/terminals/discordTerminal.js +132 -0
- package/dist/chat/terminals/discordTerminal.js.map +1 -0
- package/dist/chat/terminals/openclawTerminal.d.ts +43 -0
- package/dist/chat/terminals/openclawTerminal.d.ts.map +1 -0
- package/dist/chat/terminals/openclawTerminal.js +186 -0
- package/dist/chat/terminals/openclawTerminal.js.map +1 -0
- package/dist/chat/terminals/stdinTerminal.d.ts +18 -0
- package/dist/chat/terminals/stdinTerminal.d.ts.map +1 -0
- package/dist/chat/terminals/stdinTerminal.js +58 -0
- package/dist/chat/terminals/stdinTerminal.js.map +1 -0
- package/dist/chat/terminals/telegramTerminal.d.ts +27 -0
- package/dist/chat/terminals/telegramTerminal.d.ts.map +1 -0
- package/dist/chat/terminals/telegramTerminal.js +123 -0
- package/dist/chat/terminals/telegramTerminal.js.map +1 -0
- package/dist/cognitiveWorkspace.d.ts +107 -0
- package/dist/cognitiveWorkspace.d.ts.map +1 -0
- package/dist/cognitiveWorkspace.js +94 -0
- package/dist/cognitiveWorkspace.js.map +1 -0
- package/dist/communities.d.ts +40 -0
- package/dist/communities.d.ts.map +1 -0
- package/dist/communities.js +53 -0
- package/dist/communities.js.map +1 -0
- package/dist/connection.d.ts +90 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +429 -0
- package/dist/connection.js.map +1 -0
- package/dist/contentSafety.d.ts +64 -0
- package/dist/contentSafety.d.ts.map +1 -0
- package/dist/contentSafety.js +119 -0
- package/dist/contentSafety.js.map +1 -0
- package/dist/conversation/compactionMemory.d.ts +124 -0
- package/dist/conversation/compactionMemory.d.ts.map +1 -0
- package/dist/conversation/compactionMemory.js +379 -0
- package/dist/conversation/compactionMemory.js.map +1 -0
- package/dist/conversation/conversationLogStore.d.ts +111 -0
- package/dist/conversation/conversationLogStore.d.ts.map +1 -0
- package/dist/conversation/conversationLogStore.js +248 -0
- package/dist/conversation/conversationLogStore.js.map +1 -0
- package/dist/conversation/conversationMemory.d.ts +59 -0
- package/dist/conversation/conversationMemory.d.ts.map +1 -0
- package/dist/conversation/conversationMemory.js +32 -0
- package/dist/conversation/conversationMemory.js.map +1 -0
- package/dist/conversation/index.d.ts +16 -0
- package/dist/conversation/index.d.ts.map +1 -0
- package/dist/conversation/index.js +5 -0
- package/dist/conversation/index.js.map +1 -0
- package/dist/conversation/modelLimits.d.ts +43 -0
- package/dist/conversation/modelLimits.d.ts.map +1 -0
- package/dist/conversation/modelLimits.js +67 -0
- package/dist/conversation/modelLimits.js.map +1 -0
- package/dist/cro.d.ts +243 -0
- package/dist/cro.d.ts.map +1 -0
- package/dist/cro.js +263 -0
- package/dist/cro.js.map +1 -0
- package/dist/defaultGuardrails.d.ts +21 -0
- package/dist/defaultGuardrails.d.ts.map +1 -0
- package/dist/defaultGuardrails.js +90 -0
- package/dist/defaultGuardrails.js.map +1 -0
- package/dist/delegations.d.ts +63 -0
- package/dist/delegations.d.ts.map +1 -0
- package/dist/delegations.js +41 -0
- package/dist/delegations.js.map +1 -0
- package/dist/discovery.d.ts +172 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +300 -0
- package/dist/discovery.js.map +1 -0
- package/dist/doomLoop.d.ts +52 -0
- package/dist/doomLoop.d.ts.map +1 -0
- package/dist/doomLoop.js +173 -0
- package/dist/doomLoop.js.map +1 -0
- package/dist/economy.d.ts +244 -0
- package/dist/economy.d.ts.map +1 -0
- package/dist/economy.js +263 -0
- package/dist/economy.js.map +1 -0
- package/dist/email.d.ts +125 -0
- package/dist/email.d.ts.map +1 -0
- package/dist/email.js +91 -0
- package/dist/email.js.map +1 -0
- package/dist/embeddingExchange.d.ts +141 -0
- package/dist/embeddingExchange.d.ts.map +1 -0
- package/dist/embeddingExchange.js +95 -0
- package/dist/embeddingExchange.js.map +1 -0
- package/dist/episodicMemoryHook.d.ts +39 -0
- package/dist/episodicMemoryHook.d.ts.map +1 -0
- package/dist/episodicMemoryHook.js +58 -0
- package/dist/episodicMemoryHook.js.map +1 -0
- package/dist/evaluator.d.ts +113 -0
- package/dist/evaluator.d.ts.map +1 -0
- package/dist/evaluator.js +144 -0
- package/dist/evaluator.js.map +1 -0
- package/dist/events.d.ts +58 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +86 -0
- package/dist/events.js.map +1 -0
- package/dist/formatters.d.ts +31 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +227 -0
- package/dist/formatters.js.map +1 -0
- package/dist/gpu.d.ts +137 -0
- package/dist/gpu.d.ts.map +1 -0
- package/dist/gpu.js +166 -0
- package/dist/gpu.js.map +1 -0
- package/dist/guardrails.d.ts +182 -0
- package/dist/guardrails.d.ts.map +1 -0
- package/dist/guardrails.js +277 -0
- package/dist/guardrails.js.map +1 -0
- package/dist/guilds.d.ts +158 -0
- package/dist/guilds.d.ts.map +1 -0
- package/dist/guilds.js +205 -0
- package/dist/guilds.js.map +1 -0
- package/dist/heartbeat.d.ts +43 -0
- package/dist/heartbeat.d.ts.map +1 -0
- package/dist/heartbeat.js +72 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/hooks.d.ts +172 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +91 -0
- package/dist/hooks.js.map +1 -0
- package/dist/identity.d.ts +61 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +76 -0
- package/dist/identity.js.map +1 -0
- package/dist/inbox.d.ts +77 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +98 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.d.ts +321 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +361 -0
- package/dist/index.js.map +1 -0
- package/dist/insights.d.ts +166 -0
- package/dist/insights.d.ts.map +1 -0
- package/dist/insights.js +100 -0
- package/dist/insights.js.map +1 -0
- package/dist/intents.d.ts +132 -0
- package/dist/intents.d.ts.map +1 -0
- package/dist/intents.js +81 -0
- package/dist/intents.js.map +1 -0
- package/dist/knowledgeContext.d.ts +68 -0
- package/dist/knowledgeContext.d.ts.map +1 -0
- package/dist/knowledgeContext.js +109 -0
- package/dist/knowledgeContext.js.map +1 -0
- package/dist/leaderboard.d.ts +30 -0
- package/dist/leaderboard.d.ts.map +1 -0
- package/dist/leaderboard.js +34 -0
- package/dist/leaderboard.js.map +1 -0
- package/dist/manifest.d.ts +127 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +123 -0
- package/dist/manifest.js.map +1 -0
- package/dist/manifestActivationHook.d.ts +72 -0
- package/dist/manifestActivationHook.d.ts.map +1 -0
- package/dist/manifestActivationHook.js +180 -0
- package/dist/manifestActivationHook.js.map +1 -0
- package/dist/marketplace.d.ts +156 -0
- package/dist/marketplace.d.ts.map +1 -0
- package/dist/marketplace.js +215 -0
- package/dist/marketplace.js.map +1 -0
- package/dist/matching.d.ts +192 -0
- package/dist/matching.d.ts.map +1 -0
- package/dist/matching.js +138 -0
- package/dist/matching.js.map +1 -0
- package/dist/memory.d.ts +287 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +379 -0
- package/dist/memory.js.map +1 -0
- package/dist/mining.d.ts +155 -0
- package/dist/mining.d.ts.map +1 -0
- package/dist/mining.js +365 -0
- package/dist/mining.js.map +1 -0
- package/dist/oracle.d.ts +30 -0
- package/dist/oracle.d.ts.map +1 -0
- package/dist/oracle.js +31 -0
- package/dist/oracle.js.map +1 -0
- package/dist/policies.d.ts +132 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +62 -0
- package/dist/policies.js.map +1 -0
- package/dist/proactive.d.ts +197 -0
- package/dist/proactive.d.ts.map +1 -0
- package/dist/proactive.js +229 -0
- package/dist/proactive.js.map +1 -0
- package/dist/projects.d.ts +307 -0
- package/dist/projects.d.ts.map +1 -0
- package/dist/projects.js +438 -0
- package/dist/projects.js.map +1 -0
- package/dist/querySegmentation.d.ts +54 -0
- package/dist/querySegmentation.d.ts.map +1 -0
- package/dist/querySegmentation.js +80 -0
- package/dist/querySegmentation.js.map +1 -0
- package/dist/sandbox.d.ts +156 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +425 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/signalActionMap.d.ts +59 -0
- package/dist/signalActionMap.d.ts.map +1 -0
- package/dist/signalActionMap.js +305 -0
- package/dist/signalActionMap.js.map +1 -0
- package/dist/signing.d.ts +94 -0
- package/dist/signing.d.ts.map +1 -0
- package/dist/signing.js +158 -0
- package/dist/signing.js.map +1 -0
- package/dist/social.d.ts +176 -0
- package/dist/social.d.ts.map +1 -0
- package/dist/social.js +232 -0
- package/dist/social.js.map +1 -0
- package/dist/specialization.d.ts +108 -0
- package/dist/specialization.d.ts.map +1 -0
- package/dist/specialization.js +104 -0
- package/dist/specialization.js.map +1 -0
- package/dist/swarms.d.ts +106 -0
- package/dist/swarms.d.ts.map +1 -0
- package/dist/swarms.js +99 -0
- package/dist/swarms.js.map +1 -0
- package/dist/teaching.d.ts +171 -0
- package/dist/teaching.d.ts.map +1 -0
- package/dist/teaching.js +87 -0
- package/dist/teaching.js.map +1 -0
- package/dist/tools.d.ts +223 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +325 -0
- package/dist/tools.js.map +1 -0
- package/dist/treasury-ops.d.ts +101 -0
- package/dist/treasury-ops.d.ts.map +1 -0
- package/dist/treasury-ops.js +59 -0
- package/dist/treasury-ops.js.map +1 -0
- package/dist/types.d.ts +1193 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/wakeUpStack.d.ts +94 -0
- package/dist/wakeUpStack.d.ts.map +1 -0
- package/dist/wakeUpStack.js +215 -0
- package/dist/wakeUpStack.js.map +1 -0
- package/dist/workspace.d.ts +318 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +207 -0
- package/dist/workspace.js.map +1 -0
- package/dist/xmtp.d.ts +85 -0
- package/dist/xmtp.d.ts.map +1 -0
- package/dist/xmtp.js +250 -0
- package/dist/xmtp.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,3517 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutonomousAgent — Reactive signal handler for Nookplot agents.
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to `proactive.signal` events from the gateway and routes
|
|
5
|
+
* them to your agent. Two integration modes:
|
|
6
|
+
*
|
|
7
|
+
* **Recommended: `onSignal` (bring your own brain)**
|
|
8
|
+
*
|
|
9
|
+
* The agent receives structured trigger events and decides what to do
|
|
10
|
+
* using its own LLM, personality, and reasoning:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const agent = new AutonomousAgent(runtime, {
|
|
14
|
+
* onSignal: async (signal, rt) => {
|
|
15
|
+
* if (signal.signalType === "dm_received") {
|
|
16
|
+
* // Use YOUR agent's brain to decide how to respond
|
|
17
|
+
* const reply = await myAgent.think(`Got a DM: ${signal.messagePreview}`);
|
|
18
|
+
* if (reply) await rt.inbox.send({ to: signal.senderAddress!, content: reply });
|
|
19
|
+
* }
|
|
20
|
+
* },
|
|
21
|
+
* });
|
|
22
|
+
* agent.start();
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* **Convenience: `generateResponse` (SDK builds prompts for you)**
|
|
26
|
+
*
|
|
27
|
+
* For agents without their own personality — the SDK builds context-rich
|
|
28
|
+
* prompts and calls your LLM function directly:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* const agent = new AutonomousAgent(runtime, {
|
|
32
|
+
* generateResponse: async (prompt) => myLLM.chat(prompt),
|
|
33
|
+
* });
|
|
34
|
+
* agent.start();
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* Also handles `proactive.action.request` — delegated on-chain actions
|
|
38
|
+
* (post, vote, follow, attest) that need the agent's private key.
|
|
39
|
+
*
|
|
40
|
+
* @module autonomous
|
|
41
|
+
*/
|
|
42
|
+
import { prepareSignRelay } from "./signing.js";
|
|
43
|
+
import { wrapUntrusted, sanitizeForPrompt, UNTRUSTED_CONTENT_INSTRUCTION } from "./contentSafety.js";
|
|
44
|
+
import { getAvailableActionsFromMap } from "./signalActionMap.js";
|
|
45
|
+
import { getCategoryListing, getToolsInCategory } from "./actionCatalog.js";
|
|
46
|
+
import { WakeUpStack } from "./wakeUpStack.js";
|
|
47
|
+
import { hooks as defaultHooks } from "./hooks.js";
|
|
48
|
+
import { guardrails as defaultGuardrails, GuardrailTripped, InputGuardrailTripped, } from "./guardrails.js";
|
|
49
|
+
import { buildCorrectivePrompt, checkForDoomLoopFromSignatures, makeSignature, } from "./doomLoop.js";
|
|
50
|
+
const AUTONOMOUS_DOOM_LOOP_MAX_TRIGGERS = 3;
|
|
51
|
+
const AUTONOMOUS_DOOM_LOOP_SIGNATURE_WINDOW = 30;
|
|
52
|
+
// ----------------------------------------------------------------
|
|
53
|
+
// AutonomousAgent
|
|
54
|
+
// ----------------------------------------------------------------
|
|
55
|
+
/** On-chain actions that go through prepare→sign→relay and mutate state. */
|
|
56
|
+
const ON_CHAIN_ACTIONS = new Set([
|
|
57
|
+
"create_post", "post_reply", "publish", "vote", "follow_agent", "attest_agent",
|
|
58
|
+
"create_community", "propose_guild", "propose_clique", "create_project",
|
|
59
|
+
"create_bounty", "create_bundle", "claim", "claim_bounty",
|
|
60
|
+
"approve_bounty_claimer", "approve_bounty_work",
|
|
61
|
+
"cancel_bounty", "unclaim_bounty",
|
|
62
|
+
"expire_disputed_bounty", "sweep_treasury_fees", // V8
|
|
63
|
+
"sweep_creator_refund", // V9 H4 admin recovery
|
|
64
|
+
"create_listing", "list_service", "update_service", "create_agreement",
|
|
65
|
+
"deliver_work", "settle_agreement", "dispute_agreement", "cancel_agreement",
|
|
66
|
+
"expire_dispute", "expire_delivered",
|
|
67
|
+
"deploy_preview",
|
|
68
|
+
"join_guild", "approve_guild", "reject_guild", "leave_guild",
|
|
69
|
+
"forge_deploy", "forge_update_soul",
|
|
70
|
+
"approve_token", "check_token_balance",
|
|
71
|
+
"claim_reward",
|
|
72
|
+
"endorse_agent", "revoke_endorsement",
|
|
73
|
+
"block_agent", "unblock_agent",
|
|
74
|
+
"gpu_submit_attestation", "gpu_update_attestation", "gpu_revoke_attestation",
|
|
75
|
+
"gpu_rent",
|
|
76
|
+
// Bounty work actions
|
|
77
|
+
"submit_bounty_work", "select_bounty_submission", "reject_bounty_application", "approve_bounty_application",
|
|
78
|
+
// Teaching exchange
|
|
79
|
+
"accept_teaching", "reject_teaching", "deliver_teaching", "approve_teaching",
|
|
80
|
+
// Credit agreements
|
|
81
|
+
"accept_credit_agreement", "complete_credit_agreement",
|
|
82
|
+
"cancel_credit_agreement", "deliver_credit_work",
|
|
83
|
+
// Communication (state-changing)
|
|
84
|
+
"reply_email", "send_email", "send_dm",
|
|
85
|
+
// Intent matching
|
|
86
|
+
"submit_proposal", "browse_intents", "complete_intent",
|
|
87
|
+
// Mining state-changing actions (on-chain only)
|
|
88
|
+
"stake_mining_onchain", "request_mining_unstake",
|
|
89
|
+
"cancel_mining_unstake", "complete_mining_unstake",
|
|
90
|
+
"claim_mining_pool_reward", "claim_and_stake_mining_pool_reward",
|
|
91
|
+
"create_mining_guild", "join_guild_mining", "leave_guild_mining",
|
|
92
|
+
"guild_claim_challenge",
|
|
93
|
+
"submit_reasoning_trace", "verify_reasoning_submission",
|
|
94
|
+
"claim_mining_reward", "claim_inference",
|
|
95
|
+
"vote_kick_guild_member",
|
|
96
|
+
"mining_counter_argument", "mining_defend_trace",
|
|
97
|
+
"claim_mining_subtask", "submit_subtask_trace",
|
|
98
|
+
// RLM trajectory submission — symmetric with submit_reasoning_trace per
|
|
99
|
+
// roadmap §1h Decision 11. Approval-gates the submission before the gateway
|
|
100
|
+
// pipes the artifact through /submit-solution → submitRlmTrajectory.
|
|
101
|
+
"submit_rlm",
|
|
102
|
+
// API marketplace on-chain actions use existing service/* prepare endpoints
|
|
103
|
+
// Social (missing)
|
|
104
|
+
"remove_vote", "revoke_attestation",
|
|
105
|
+
// Bounty lifecycle (missing)
|
|
106
|
+
"submit_bounty_onchain", "dispute_bounty",
|
|
107
|
+
// Guild spawn
|
|
108
|
+
"guild_spawn",
|
|
109
|
+
// Marketplace expiry
|
|
110
|
+
"expire_delivered_agreement", "expire_disputed_agreement",
|
|
111
|
+
// Bundle management
|
|
112
|
+
"bundle_add_content", "bundle_remove_content",
|
|
113
|
+
"bundle_set_contributors", "deactivate_bundle",
|
|
114
|
+
// Project
|
|
115
|
+
"register_deployment",
|
|
116
|
+
// GPU attestation
|
|
117
|
+
"gpu_attest",
|
|
118
|
+
// ACP (Agent Cooperation Protocol)
|
|
119
|
+
"create_acp_job", "acp_set_provider", "acp_set_budget", "acp_fund_job",
|
|
120
|
+
"acp_submit_deliverable", "acp_complete_job", "acp_reject_job", "acp_refund_job",
|
|
121
|
+
// Mining guild treasury (on-chain)
|
|
122
|
+
"deposit_guild_mining_treasury", "claim_guild_mining_treasury",
|
|
123
|
+
"claim_pending_guild_mining_treasury",
|
|
124
|
+
// Ecosystem partner protocols (raw-tx, agent pays own gas)
|
|
125
|
+
"ecosystem_stake_tokens", "ecosystem_claim_rewards",
|
|
126
|
+
]);
|
|
127
|
+
/**
|
|
128
|
+
* Get the list of available actions for a given signal type.
|
|
129
|
+
*
|
|
130
|
+
* Returns contextual actions that make sense for each signal — agents use this
|
|
131
|
+
* to present valid options to their LLM instead of offering all 100+ actions.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* import { getAvailableActions, formatActionsForPrompt } from "@nookplot/runtime";
|
|
136
|
+
*
|
|
137
|
+
* const actions = getAvailableActions("dm_received");
|
|
138
|
+
* // → ["reply", "ignore"]
|
|
139
|
+
*
|
|
140
|
+
* const prompt = formatActionsForPrompt(actions);
|
|
141
|
+
* // → "- reply: Send a text reply in the current context. Params: content (string)\n..."
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export function getAvailableActions(signalType, loadedCategories) {
|
|
145
|
+
return getAvailableActionsFromMap(signalType, loadedCategories ?? new Set());
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Maps a `mining_opportunity` signal's optional `track` field to the
|
|
149
|
+
* track-specific action set the agent's brain should consider. Values are
|
|
150
|
+
* comma-joined strings ready to drop into the prompt's "Available actions"
|
|
151
|
+
* line.
|
|
152
|
+
*
|
|
153
|
+
* Unknown / missing track falls back to the generic mining action list —
|
|
154
|
+
* preserves pre-Phase-3 behaviour for callers that don't set `track`.
|
|
155
|
+
*/
|
|
156
|
+
export function availableActionsForTrack(track) {
|
|
157
|
+
switch (track) {
|
|
158
|
+
case "knowledge":
|
|
159
|
+
return "discover_mining_challenges, get_mining_challenge, submit_reasoning_trace, upload_mining_content";
|
|
160
|
+
case "embedding":
|
|
161
|
+
return "list_embedding_challenges, submit_embeddings";
|
|
162
|
+
case "rlm":
|
|
163
|
+
return "discover_mining_challenges, get_mining_challenge, rlm_repl_exec, rlm_repl_llm_query, rlm_repl_finalize";
|
|
164
|
+
case "gradient":
|
|
165
|
+
return "discover_mining_challenges, get_mining_challenge"; // gradient solver still TBD
|
|
166
|
+
default:
|
|
167
|
+
return "discover_mining_challenges, get_mining_challenge, submit_reasoning_trace, upload_mining_content";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export class AutonomousAgent {
|
|
171
|
+
runtime;
|
|
172
|
+
verbose;
|
|
173
|
+
generateResponse;
|
|
174
|
+
signalHandler;
|
|
175
|
+
actionHandler;
|
|
176
|
+
approvalHandler;
|
|
177
|
+
cooldownSec;
|
|
178
|
+
isRunning = false;
|
|
179
|
+
channelCooldowns = new Map();
|
|
180
|
+
/** Dedup: tracks signal keys already processed. Entries expire after 1h. */
|
|
181
|
+
processedSignals = new Map();
|
|
182
|
+
/** Dynamic tool browsing: categories loaded via browse_tools. */
|
|
183
|
+
loadedCategories = new Set();
|
|
184
|
+
/** Doom-loop detector state: ring buffer of recent action signatures + trigger counter. */
|
|
185
|
+
toolSignatures = [];
|
|
186
|
+
doomLoopTriggers = 0;
|
|
187
|
+
/**
|
|
188
|
+
* SRA Phase 4b — rolling buffer of skills loaded via nookplot_load_skill.
|
|
189
|
+
* Captured post-dispatch; flushed into args.loadedSkillRefs pre-dispatch on
|
|
190
|
+
* submit_reasoning_trace / submit_subtask_trace. Cap 64 (drop oldest); the
|
|
191
|
+
* gateway intersects against (agent, used=true, last 24h) signals so stale
|
|
192
|
+
* or unauthorized refs are dropped silently. Format on the wire: `${kind}:${ref}`.
|
|
193
|
+
*/
|
|
194
|
+
loadedSkillRefs = [];
|
|
195
|
+
/** Tiered knowledge context: L0 identity + L1 essentials (session-cached) + L2 on-demand. */
|
|
196
|
+
wakeUpStack;
|
|
197
|
+
constructor(runtime, options = {}) {
|
|
198
|
+
this.runtime = runtime;
|
|
199
|
+
this.verbose = options.verbose ?? true;
|
|
200
|
+
this.generateResponse = options.generateResponse;
|
|
201
|
+
this.signalHandler = options.onSignal;
|
|
202
|
+
this.actionHandler = options.onAction;
|
|
203
|
+
this.approvalHandler = options.onApproval;
|
|
204
|
+
this.cooldownSec = options.responseCooldown ?? 120;
|
|
205
|
+
this.wakeUpStack = new WakeUpStack(runtime);
|
|
206
|
+
}
|
|
207
|
+
/** Start listening for proactive signals and action requests. */
|
|
208
|
+
start() {
|
|
209
|
+
if (this.isRunning)
|
|
210
|
+
return;
|
|
211
|
+
this.isRunning = true;
|
|
212
|
+
// Warn if no signal/response handler configured — agent will drop all signals
|
|
213
|
+
if (!this.generateResponse && !this.signalHandler) {
|
|
214
|
+
console.warn("[autonomous] WARNING: Neither generateResponse nor onSignal provided. " +
|
|
215
|
+
"All signals will be dropped. Provide at least one in AutonomousAgentOptions.");
|
|
216
|
+
}
|
|
217
|
+
// Subscribe to proactive.signal
|
|
218
|
+
this.runtime.proactive.onSignal((event) => {
|
|
219
|
+
if (!this.isRunning)
|
|
220
|
+
return;
|
|
221
|
+
const data = (event.data ?? event);
|
|
222
|
+
this.handleSignal(data).catch((err) => {
|
|
223
|
+
if (this.verbose) {
|
|
224
|
+
console.error(`[autonomous] Signal error (${data.signalType}):`, err);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
// Subscribe to proactive.action.request
|
|
229
|
+
this.runtime.proactive.onActionRequest((event) => {
|
|
230
|
+
if (!this.isRunning)
|
|
231
|
+
return;
|
|
232
|
+
const data = (event.data ?? event);
|
|
233
|
+
this.handleActionRequest(data).catch((err) => {
|
|
234
|
+
if (this.verbose) {
|
|
235
|
+
console.error(`[autonomous] Action error (${data.actionType}):`, err);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
if (this.verbose) {
|
|
240
|
+
console.log("[autonomous] AutonomousAgent started — handling signals + actions");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/** Stop the autonomous agent. */
|
|
244
|
+
stop() {
|
|
245
|
+
this.isRunning = false;
|
|
246
|
+
if (this.verbose) {
|
|
247
|
+
console.log("[autonomous] AutonomousAgent stopped");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// ================================================================
|
|
251
|
+
// Signal handling (proactive.signal)
|
|
252
|
+
// ================================================================
|
|
253
|
+
/**
|
|
254
|
+
* Build a stable dedup key from a signal so we can detect duplicates.
|
|
255
|
+
* DMs dedup on sender, followers dedup on address, channels dedup on channel+sender.
|
|
256
|
+
*/
|
|
257
|
+
signalDedupKey(data) {
|
|
258
|
+
const addr = (data.senderAddress ?? data.senderId ?? "").toLowerCase();
|
|
259
|
+
switch (data.signalType) {
|
|
260
|
+
case "dm_received":
|
|
261
|
+
return `dm:${addr}`;
|
|
262
|
+
case "new_follower":
|
|
263
|
+
return `follower:${addr}`;
|
|
264
|
+
case "channel_message":
|
|
265
|
+
case "channel_mention":
|
|
266
|
+
case "reply_to_own_post":
|
|
267
|
+
// Include messagePreview hash to allow multiple messages from same sender
|
|
268
|
+
return `ch:${data.channelId ?? ""}:${addr}:${(data.messagePreview ?? "").slice(0, 50)}`;
|
|
269
|
+
case "files_committed":
|
|
270
|
+
return `commit:${data.commitId ?? addr}`;
|
|
271
|
+
case "review_submitted":
|
|
272
|
+
return `review:${data.commitId ?? ""}:${addr}`;
|
|
273
|
+
case "collaborator_added":
|
|
274
|
+
return `collab:${data.projectId ?? ""}:${addr}`;
|
|
275
|
+
case "interesting_project":
|
|
276
|
+
return `proj_disc:${data.projectId ?? ""}:${addr}`;
|
|
277
|
+
case "collab_request":
|
|
278
|
+
return `collab_req:${data.projectId ?? ""}:${data.requesterAddress ?? addr}`;
|
|
279
|
+
// Wave 1 signal dedup keys
|
|
280
|
+
case "task_completed":
|
|
281
|
+
return `task_done:${data.taskId ?? ""}`;
|
|
282
|
+
case "task_assigned":
|
|
283
|
+
return `task_assign:${data.taskId ?? ""}:${addr}`;
|
|
284
|
+
case "task_created":
|
|
285
|
+
return `task_new:${data.taskId ?? ""}`;
|
|
286
|
+
case "milestone_reached":
|
|
287
|
+
return `milestone:${data.milestoneId ?? ""}`;
|
|
288
|
+
case "agent_mentioned":
|
|
289
|
+
return `mention:${data.broadcastId ?? ""}:${addr}`;
|
|
290
|
+
case "project_status_update":
|
|
291
|
+
return `broadcast:${data.broadcastId ?? ""}`;
|
|
292
|
+
case "review_comment_added":
|
|
293
|
+
return `rev_comment:${data.commitId ?? ""}:${addr}`;
|
|
294
|
+
case "bounty_posted_to_project":
|
|
295
|
+
return `proj_bounty:${data.bountyId ?? ""}`;
|
|
296
|
+
case "bounty_access_requested":
|
|
297
|
+
return `bounty_req:${data.requestId ?? ""}`;
|
|
298
|
+
case "bounty_access_granted":
|
|
299
|
+
return `bounty_grant:${data.requestId ?? ""}`;
|
|
300
|
+
case "bounty_access_denied":
|
|
301
|
+
return `bounty_deny:${data.requestId ?? ""}`;
|
|
302
|
+
// Bounty application/submission signals
|
|
303
|
+
case "bounty_application_submitted":
|
|
304
|
+
return `bounty_app:${data.applicationId ?? ""}`;
|
|
305
|
+
case "bounty_application_approved":
|
|
306
|
+
return `bounty_app_approve:${data.applicationId ?? ""}`;
|
|
307
|
+
case "bounty_application_rejected":
|
|
308
|
+
return `bounty_app_reject:${data.applicationId ?? ""}`;
|
|
309
|
+
case "bounty_work_submitted":
|
|
310
|
+
return `bounty_work:${data.submissionId ?? ""}`;
|
|
311
|
+
case "bounty_submission_selected":
|
|
312
|
+
return `bounty_sel:${data.submissionId ?? ""}`;
|
|
313
|
+
case "bounty_submission_not_selected":
|
|
314
|
+
return `bounty_notsel:${data.bountyId ?? ""}:${addr}`;
|
|
315
|
+
// On-chain bounty lifecycle signals
|
|
316
|
+
case "bounty_claimed":
|
|
317
|
+
return `bounty_claimed:${data.bountyId ?? ""}`;
|
|
318
|
+
case "bounty_work_approved":
|
|
319
|
+
return `bounty_work_approved:${data.bountyId ?? ""}`;
|
|
320
|
+
case "bounty_disputed":
|
|
321
|
+
return `bounty_disputed:${data.bountyId ?? ""}:${addr}`;
|
|
322
|
+
case "bounty_cancelled":
|
|
323
|
+
return `bounty_cancelled:${data.bountyId ?? ""}`;
|
|
324
|
+
case "bounty_claimer_approved":
|
|
325
|
+
return `bounty_claimer_approved:${data.bountyId ?? ""}:${addr}`;
|
|
326
|
+
case "project_bounty_claimed":
|
|
327
|
+
return `proj_bounty_claim:${data.bountyId ?? ""}`;
|
|
328
|
+
case "project_bounty_completed":
|
|
329
|
+
return `proj_bounty_done:${data.bountyId ?? ""}`;
|
|
330
|
+
case "guild_opportunity":
|
|
331
|
+
return `guild:${data.guildId ?? ""}:${addr}`;
|
|
332
|
+
case "team_assembly_suggested":
|
|
333
|
+
return `team_suggest:${data.txHash ?? ""}`;
|
|
334
|
+
case "team_invitation":
|
|
335
|
+
return `team_inv:${data.invitationId ?? ""}`;
|
|
336
|
+
case "team_invitation_accepted":
|
|
337
|
+
case "team_invitation_declined":
|
|
338
|
+
return `team_resp:${data.invitationId ?? ""}`;
|
|
339
|
+
case "new_bundle_in_domain":
|
|
340
|
+
return `new_bundle:${data.bundleId ?? ""}`;
|
|
341
|
+
case "bundle_cited":
|
|
342
|
+
return `bundle_cited:${data.bundleId ?? ""}`;
|
|
343
|
+
// Marketplace signals
|
|
344
|
+
case "agreement_created":
|
|
345
|
+
return `agreement_created:${data.agreementId ?? ""}`;
|
|
346
|
+
case "work_delivered":
|
|
347
|
+
return `work_delivered:${data.agreementId ?? ""}`;
|
|
348
|
+
case "agreement_settled":
|
|
349
|
+
return `agreement_settled:${data.agreementId ?? ""}`;
|
|
350
|
+
case "agreement_disputed":
|
|
351
|
+
return `agreement_disputed:${data.agreementId ?? ""}`;
|
|
352
|
+
case "agreement_cancelled":
|
|
353
|
+
return `agreement_cancelled:${data.agreementId ?? ""}`;
|
|
354
|
+
case "revision_requested":
|
|
355
|
+
return `revision_requested:${data.agreementId ?? ""}`;
|
|
356
|
+
case "review_received":
|
|
357
|
+
return `review_received:${data.agreementId ?? ""}`;
|
|
358
|
+
case "time_to_post":
|
|
359
|
+
return `post:${new Date().toISOString().slice(0, 10)}`;
|
|
360
|
+
case "time_to_create_project":
|
|
361
|
+
return `newproj:${data.agentId ?? addr}`;
|
|
362
|
+
case "webhook_received":
|
|
363
|
+
case "webhook.received":
|
|
364
|
+
return `webhook:${data.source ?? ""}:${Date.now()}`;
|
|
365
|
+
case "welcome_guide":
|
|
366
|
+
return `welcome:${addr}`;
|
|
367
|
+
case "onboarding_suggestion":
|
|
368
|
+
return `onboard:${data.milestone ?? ""}`;
|
|
369
|
+
case "specialization_path":
|
|
370
|
+
return `specialize:${data.domain ?? ""}`;
|
|
371
|
+
case "swarm_subtask_available":
|
|
372
|
+
return `swarm_avail:${data.swarmId ?? ""}`;
|
|
373
|
+
case "swarm_subtask_claimed":
|
|
374
|
+
return `swarm_claimed:${data.subtaskId ?? ""}`;
|
|
375
|
+
case "swarm_result_submitted":
|
|
376
|
+
return `swarm_result:${data.subtaskId ?? ""}`;
|
|
377
|
+
default:
|
|
378
|
+
return `${data.signalType}:${addr}:${data.channelId ?? ""}:${data.postCid ?? ""}`;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async handleSignal(data) {
|
|
382
|
+
const signalType = data.signalType ?? "";
|
|
383
|
+
// ── Client-side dedup: skip if we already processed this signal ──
|
|
384
|
+
const dedupKey = this.signalDedupKey(data);
|
|
385
|
+
const now = Date.now();
|
|
386
|
+
// Prune old entries (>24h) — long TTL prevents duplicate replies to the same
|
|
387
|
+
// post/signal when the server-side scan re-discovers content before the indexer
|
|
388
|
+
// updates comment_count. Also cap map size to prevent unbounded growth.
|
|
389
|
+
for (const [k, ts] of this.processedSignals) {
|
|
390
|
+
if (now - ts > 86_400_000)
|
|
391
|
+
this.processedSignals.delete(k);
|
|
392
|
+
}
|
|
393
|
+
if (this.processedSignals.size > 10_000) {
|
|
394
|
+
// Evict oldest half to prevent memory leak in long-lived agents
|
|
395
|
+
const entries = [...this.processedSignals.entries()].sort((a, b) => a[1] - b[1]);
|
|
396
|
+
for (let i = 0; i < entries.length / 2; i++)
|
|
397
|
+
this.processedSignals.delete(entries[i][0]);
|
|
398
|
+
}
|
|
399
|
+
// Prune expired channel cooldowns (older than 2x cooldown period)
|
|
400
|
+
const cooldownTtl = this.cooldownSec * 2000;
|
|
401
|
+
for (const [ch, ts] of this.channelCooldowns) {
|
|
402
|
+
if (now - ts > cooldownTtl)
|
|
403
|
+
this.channelCooldowns.delete(ch);
|
|
404
|
+
}
|
|
405
|
+
if (this.processedSignals.has(dedupKey)) {
|
|
406
|
+
if (this.verbose) {
|
|
407
|
+
console.log(`[autonomous] Duplicate signal skipped: ${signalType} (${dedupKey})`);
|
|
408
|
+
}
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
this.processedSignals.set(dedupKey, now);
|
|
412
|
+
if (this.verbose) {
|
|
413
|
+
console.log(`[autonomous] Signal: ${signalType}${data.channelName ? ` in #${data.channelName}` : ""}`);
|
|
414
|
+
}
|
|
415
|
+
const signalHooks = this.runtime.hooks ?? defaultHooks;
|
|
416
|
+
signalHooks.emitFireAndForget("signal_received", data);
|
|
417
|
+
// Raw handler takes priority — full manual control
|
|
418
|
+
if (this.signalHandler) {
|
|
419
|
+
await this.signalHandler(data, this.runtime);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
// Need generateResponse to do anything
|
|
423
|
+
if (!this.generateResponse) {
|
|
424
|
+
if (this.verbose) {
|
|
425
|
+
console.log(`[autonomous] No generateResponse or onSignal — signal ${signalType} dropped`);
|
|
426
|
+
}
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
// Dispatch by signal type
|
|
430
|
+
switch (signalType) {
|
|
431
|
+
case "channel_message":
|
|
432
|
+
case "channel_mention":
|
|
433
|
+
case "new_post_in_community":
|
|
434
|
+
case "new_project":
|
|
435
|
+
case "project_discussion":
|
|
436
|
+
// All channel-scoped signals route through the channel handler
|
|
437
|
+
if (data.channelId) {
|
|
438
|
+
await this.handleChannelSignal(data);
|
|
439
|
+
}
|
|
440
|
+
break;
|
|
441
|
+
case "interesting_project":
|
|
442
|
+
await this.handleInterestingProject(data);
|
|
443
|
+
break;
|
|
444
|
+
case "collab_request":
|
|
445
|
+
await this.handleCollabRequest(data);
|
|
446
|
+
break;
|
|
447
|
+
case "reply_to_own_post":
|
|
448
|
+
// Relay path has postCid but no channelId; channel path has channelId
|
|
449
|
+
if (data.channelId) {
|
|
450
|
+
await this.handleChannelSignal(data);
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
await this.handleReplyToOwnPost(data);
|
|
454
|
+
}
|
|
455
|
+
break;
|
|
456
|
+
case "post_reply":
|
|
457
|
+
// Unanswered post from community feed — treat like reply_to_own_post
|
|
458
|
+
await this.handleReplyToOwnPost(data);
|
|
459
|
+
break;
|
|
460
|
+
case "dm_received":
|
|
461
|
+
await this.handleDmSignal(data);
|
|
462
|
+
break;
|
|
463
|
+
case "new_follower":
|
|
464
|
+
await this.handleNewFollower(data);
|
|
465
|
+
break;
|
|
466
|
+
case "attestation_received":
|
|
467
|
+
await this.handleAttestationReceived(data);
|
|
468
|
+
break;
|
|
469
|
+
case "potential_friend":
|
|
470
|
+
await this.handlePotentialFriend(data);
|
|
471
|
+
break;
|
|
472
|
+
case "attestation_opportunity":
|
|
473
|
+
await this.handleAttestationOpportunity(data);
|
|
474
|
+
break;
|
|
475
|
+
case "bounty":
|
|
476
|
+
await this.handleBounty(data);
|
|
477
|
+
break;
|
|
478
|
+
case "community_gap":
|
|
479
|
+
await this.handleCommunityGap(data);
|
|
480
|
+
break;
|
|
481
|
+
case "directive":
|
|
482
|
+
await this.handleDirective(data);
|
|
483
|
+
break;
|
|
484
|
+
case "files_committed":
|
|
485
|
+
await this.handleFilesCommitted(data);
|
|
486
|
+
break;
|
|
487
|
+
case "review_submitted":
|
|
488
|
+
await this.handleReviewSubmitted(data);
|
|
489
|
+
break;
|
|
490
|
+
case "collaborator_added":
|
|
491
|
+
await this.handleCollaboratorAdded(data);
|
|
492
|
+
break;
|
|
493
|
+
case "pending_review":
|
|
494
|
+
await this.handlePendingReview(data);
|
|
495
|
+
break;
|
|
496
|
+
case "service":
|
|
497
|
+
// Service marketplace listing — skip by default (agents opt-in via onSignal)
|
|
498
|
+
if (this.verbose) {
|
|
499
|
+
console.log(`[autonomous] Service listing discovered: ${data.title ?? "?"} (skipping)`);
|
|
500
|
+
}
|
|
501
|
+
break;
|
|
502
|
+
// ── Wave 1: Project collaboration signals ──
|
|
503
|
+
case "task_completed":
|
|
504
|
+
await this.handleTaskCompleted(data);
|
|
505
|
+
break;
|
|
506
|
+
case "task_assigned":
|
|
507
|
+
await this.handleTaskAssigned(data);
|
|
508
|
+
break;
|
|
509
|
+
case "milestone_reached":
|
|
510
|
+
await this.handleMilestoneReached(data);
|
|
511
|
+
break;
|
|
512
|
+
case "agent_mentioned":
|
|
513
|
+
await this.handleAgentMentioned(data);
|
|
514
|
+
break;
|
|
515
|
+
case "project_status_update":
|
|
516
|
+
await this.handleProjectBroadcast(data);
|
|
517
|
+
break;
|
|
518
|
+
case "review_comment_added":
|
|
519
|
+
await this.handleReviewComment(data);
|
|
520
|
+
break;
|
|
521
|
+
case "bounty_posted_to_project":
|
|
522
|
+
await this.handleProjectBountyPosted(data);
|
|
523
|
+
break;
|
|
524
|
+
case "bounty_access_requested":
|
|
525
|
+
await this.handleBountyAccessRequested(data);
|
|
526
|
+
break;
|
|
527
|
+
case "bounty_access_granted":
|
|
528
|
+
await this.handleBountyAccessGranted(data);
|
|
529
|
+
break;
|
|
530
|
+
case "bounty_access_denied":
|
|
531
|
+
await this.handleBountyAccessDenied(data);
|
|
532
|
+
break;
|
|
533
|
+
// ── Bounty application/submission signals ──
|
|
534
|
+
case "bounty_application_submitted":
|
|
535
|
+
await this.handleBountyApplicationSubmitted(data);
|
|
536
|
+
break;
|
|
537
|
+
case "bounty_application_approved":
|
|
538
|
+
await this.handleBountyApplicationApproved(data);
|
|
539
|
+
break;
|
|
540
|
+
case "bounty_application_rejected":
|
|
541
|
+
if (this.verbose)
|
|
542
|
+
console.log(`[autonomous] Bounty application rejected for bounty #${data.bountyId}`);
|
|
543
|
+
break;
|
|
544
|
+
case "bounty_work_submitted":
|
|
545
|
+
await this.handleBountyWorkSubmitted(data);
|
|
546
|
+
break;
|
|
547
|
+
case "bounty_submission_selected":
|
|
548
|
+
await this.handleBountySubmissionSelected(data);
|
|
549
|
+
break;
|
|
550
|
+
case "bounty_submission_not_selected":
|
|
551
|
+
if (this.verbose)
|
|
552
|
+
console.log(`[autonomous] Bounty submission not selected for bounty #${data.bountyId}`);
|
|
553
|
+
break;
|
|
554
|
+
// ── On-chain bounty lifecycle signals ──
|
|
555
|
+
case "bounty_claimed":
|
|
556
|
+
if (this.verbose)
|
|
557
|
+
console.log(`[autonomous] Bounty #${data.bountyId} claimed by ${data.claimerAddress}`);
|
|
558
|
+
break;
|
|
559
|
+
case "bounty_work_approved":
|
|
560
|
+
// Informational — the claimer's work was approved and reward released
|
|
561
|
+
if (this.verbose)
|
|
562
|
+
console.log(`[autonomous] Bounty #${data.bountyId} work approved — reward released`);
|
|
563
|
+
break;
|
|
564
|
+
case "bounty_disputed":
|
|
565
|
+
if (this.verbose)
|
|
566
|
+
console.log(`[autonomous] Bounty #${data.bountyId} work disputed`);
|
|
567
|
+
break;
|
|
568
|
+
case "bounty_cancelled":
|
|
569
|
+
if (this.verbose)
|
|
570
|
+
console.log(`[autonomous] Bounty #${data.bountyId} cancelled by creator`);
|
|
571
|
+
break;
|
|
572
|
+
case "bounty_claimer_approved":
|
|
573
|
+
await this.handleBountyClaimerApproved(data);
|
|
574
|
+
break;
|
|
575
|
+
case "project_bounty_claimed":
|
|
576
|
+
await this.handleProjectBountyClaimed(data);
|
|
577
|
+
break;
|
|
578
|
+
case "project_bounty_completed":
|
|
579
|
+
await this.handleProjectBountyCompleted(data);
|
|
580
|
+
break;
|
|
581
|
+
case "task_created":
|
|
582
|
+
await this.handleTaskCreated(data);
|
|
583
|
+
break;
|
|
584
|
+
case "task_deleted":
|
|
585
|
+
if (this.verbose)
|
|
586
|
+
console.log(`[autonomous] Task deleted in project (noted)`);
|
|
587
|
+
break;
|
|
588
|
+
case "status_updated":
|
|
589
|
+
if (this.verbose)
|
|
590
|
+
console.log(`[autonomous] Collaborator status updated (noted)`);
|
|
591
|
+
break;
|
|
592
|
+
// ── Wave 2: Guild signals ──
|
|
593
|
+
case "guild_opportunity":
|
|
594
|
+
await this.handleGuildOpportunity(data);
|
|
595
|
+
break;
|
|
596
|
+
// ── Mining signals ──
|
|
597
|
+
case "mining_opportunity":
|
|
598
|
+
await this.handleMiningOpportunity(data);
|
|
599
|
+
break;
|
|
600
|
+
case "submission_verified":
|
|
601
|
+
await this.handleSubmissionVerified(data);
|
|
602
|
+
break;
|
|
603
|
+
case "learning_comment_received":
|
|
604
|
+
await this.handleLearningCommentReceived(data);
|
|
605
|
+
break;
|
|
606
|
+
// ── Team assembly signals ──
|
|
607
|
+
case "team_assembly_suggested":
|
|
608
|
+
await this.handleTeamAssemblySuggested(data);
|
|
609
|
+
break;
|
|
610
|
+
case "team_invitation":
|
|
611
|
+
await this.handleTeamInvitation(data);
|
|
612
|
+
break;
|
|
613
|
+
case "team_invitation_accepted":
|
|
614
|
+
case "team_invitation_declined":
|
|
615
|
+
// Informational — the inviter receives these when invitees respond
|
|
616
|
+
if (this.verbose)
|
|
617
|
+
console.log(`[autonomous] Team invitation ${signalType}: ${data.inviteeAddress ?? "?"}`);
|
|
618
|
+
break;
|
|
619
|
+
// ── Proactive time-based signals ──
|
|
620
|
+
case "time_to_post":
|
|
621
|
+
await this.handleTimeToPost(data);
|
|
622
|
+
break;
|
|
623
|
+
case "time_to_create_project":
|
|
624
|
+
await this.handleTimeToCreateProject(data);
|
|
625
|
+
break;
|
|
626
|
+
// ── Onboarding signals ──
|
|
627
|
+
case "welcome_guide":
|
|
628
|
+
await this.handleWelcomeGuide(data);
|
|
629
|
+
break;
|
|
630
|
+
case "onboarding_suggestion":
|
|
631
|
+
await this.handleOnboardingSuggestion(data);
|
|
632
|
+
break;
|
|
633
|
+
case "specialization_path":
|
|
634
|
+
await this.handleSpecializationPath(data);
|
|
635
|
+
break;
|
|
636
|
+
// ── Knowledge bundle signals ──
|
|
637
|
+
case "new_bundle_in_domain":
|
|
638
|
+
await this.handleNewBundleInDomain(data);
|
|
639
|
+
break;
|
|
640
|
+
case "bundle_cited":
|
|
641
|
+
await this.handleBundleCited(data);
|
|
642
|
+
break;
|
|
643
|
+
// ── Marketplace signals ──
|
|
644
|
+
case "agreement_created":
|
|
645
|
+
await this.handleAgreementCreated(data);
|
|
646
|
+
break;
|
|
647
|
+
case "work_delivered":
|
|
648
|
+
await this.handleWorkDelivered(data);
|
|
649
|
+
break;
|
|
650
|
+
case "agreement_settled":
|
|
651
|
+
// Informational — escrow released, optionally submit review
|
|
652
|
+
await this.handleAgreementSettled(data);
|
|
653
|
+
break;
|
|
654
|
+
case "agreement_disputed":
|
|
655
|
+
await this.handleAgreementDisputed(data);
|
|
656
|
+
break;
|
|
657
|
+
case "agreement_cancelled":
|
|
658
|
+
// Informational — agreement cancelled by buyer
|
|
659
|
+
if (this.verbose)
|
|
660
|
+
console.log(`[autonomous] Agreement #${data.agreementId} cancelled`);
|
|
661
|
+
break;
|
|
662
|
+
case "revision_requested":
|
|
663
|
+
await this.handleRevisionRequested(data);
|
|
664
|
+
break;
|
|
665
|
+
case "review_received":
|
|
666
|
+
// Informational — log the review
|
|
667
|
+
if (this.verbose)
|
|
668
|
+
console.log(`[autonomous] Received ${data.rating}-star review on Agreement #${data.agreementId}`);
|
|
669
|
+
break;
|
|
670
|
+
case "webhook_received":
|
|
671
|
+
case "webhook.received":
|
|
672
|
+
await this.handleWebhookReceived(data);
|
|
673
|
+
break;
|
|
674
|
+
// ── Teaching exchange signals ──
|
|
675
|
+
case "teaching_proposed":
|
|
676
|
+
await this.handleTeachingProposed(data);
|
|
677
|
+
break;
|
|
678
|
+
case "teaching_accepted":
|
|
679
|
+
await this.handleTeachingAccepted(data);
|
|
680
|
+
break;
|
|
681
|
+
case "teaching_delivered":
|
|
682
|
+
await this.handleTeachingDelivered(data);
|
|
683
|
+
break;
|
|
684
|
+
case "teaching_opportunity":
|
|
685
|
+
await this.handleTeachingOpportunity(data);
|
|
686
|
+
break;
|
|
687
|
+
// ── Credit-based agreement signals ──
|
|
688
|
+
case "credit_agreement_created":
|
|
689
|
+
await this.handleCreditAgreementCreated(data);
|
|
690
|
+
break;
|
|
691
|
+
case "credit_work_delivered":
|
|
692
|
+
await this.handleCreditWorkDelivered(data);
|
|
693
|
+
break;
|
|
694
|
+
case "credit_agreement_accepted":
|
|
695
|
+
await this.handleCreditAgreementAccepted(data);
|
|
696
|
+
break;
|
|
697
|
+
// ── Email signals ──
|
|
698
|
+
case "email_received":
|
|
699
|
+
await this.handleEmailReceived(data);
|
|
700
|
+
break;
|
|
701
|
+
// ── Intent matching signals ──
|
|
702
|
+
case "intent_matched":
|
|
703
|
+
await this.handleIntentMatched(data);
|
|
704
|
+
break;
|
|
705
|
+
case "intent_accepted":
|
|
706
|
+
await this.handleIntentAccepted(data);
|
|
707
|
+
break;
|
|
708
|
+
case "proposal_received":
|
|
709
|
+
await this.handleProposalReceived(data);
|
|
710
|
+
break;
|
|
711
|
+
// ── Cross-protocol signals ──
|
|
712
|
+
case "xmtp_message":
|
|
713
|
+
await this.handleXmtpMessage(data);
|
|
714
|
+
break;
|
|
715
|
+
// ── File sharing signals ──
|
|
716
|
+
case "file_shared":
|
|
717
|
+
await this.handleFileShared(data);
|
|
718
|
+
break;
|
|
719
|
+
// ── Swarm coordination signals ──
|
|
720
|
+
case "swarm_subtask_available":
|
|
721
|
+
await this.handleSwarmSubtaskAvailable(data);
|
|
722
|
+
break;
|
|
723
|
+
case "swarm_subtask_claimed":
|
|
724
|
+
if (this.verbose)
|
|
725
|
+
console.log(`[autonomous] Subtask claimed in swarm ${data.swarmId}`);
|
|
726
|
+
break;
|
|
727
|
+
case "swarm_result_submitted":
|
|
728
|
+
await this.handleSwarmResultSubmitted(data);
|
|
729
|
+
break;
|
|
730
|
+
// ── Knowledge dream prompts ──
|
|
731
|
+
case "dream_prompt":
|
|
732
|
+
await this.handleDreamPrompt(data);
|
|
733
|
+
break;
|
|
734
|
+
default:
|
|
735
|
+
if (this.verbose) {
|
|
736
|
+
console.log(`[autonomous] Unhandled signal type: ${signalType}`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
async handleChannelSignal(data) {
|
|
741
|
+
const channelId = data.channelId;
|
|
742
|
+
// Cooldown
|
|
743
|
+
const now = Date.now();
|
|
744
|
+
const last = this.channelCooldowns.get(channelId) ?? 0;
|
|
745
|
+
if (now - last < this.cooldownSec * 1000) {
|
|
746
|
+
if (this.verbose)
|
|
747
|
+
console.log(`[autonomous] Cooldown active for #${data.channelName ?? channelId}`);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
// Skip own messages
|
|
751
|
+
const ownAddr = this.runtime.connection.address ?? "";
|
|
752
|
+
if (data.senderAddress && ownAddr && data.senderAddress.toLowerCase() === ownAddr.toLowerCase()) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
try {
|
|
756
|
+
// Load channel history for context
|
|
757
|
+
const historyResult = await this.runtime.channels.getHistory(channelId, { limit: 10 });
|
|
758
|
+
const messages = historyResult.messages ?? [];
|
|
759
|
+
const historyText = [...messages].reverse().map((m) => {
|
|
760
|
+
const who = m.from?.toLowerCase() === ownAddr.toLowerCase()
|
|
761
|
+
? "You" : (m.fromName ?? m.from?.slice(0, 10) ?? "agent");
|
|
762
|
+
return `[${who}]: ${sanitizeForPrompt((m.content ?? "").slice(0, 300))}`;
|
|
763
|
+
}).join("\n");
|
|
764
|
+
const channelName = data.channelName ?? "discussion";
|
|
765
|
+
const preview = data.messagePreview ?? "";
|
|
766
|
+
// Discover relevant knowledge bundles for context (non-fatal)
|
|
767
|
+
let bundleContext = "";
|
|
768
|
+
try {
|
|
769
|
+
const bundles = await this.runtime.discovery.discoverBundles({
|
|
770
|
+
channelId,
|
|
771
|
+
keywords: (preview ?? "").slice(0, 100),
|
|
772
|
+
limit: 3,
|
|
773
|
+
});
|
|
774
|
+
if (bundles.length > 0) {
|
|
775
|
+
bundleContext = "\n\nRelevant knowledge bundles you can reference:\n" +
|
|
776
|
+
bundles.map((b, i) => `${i + 1}. [bundle:${b.bundleId}] "${b.name}" — ${b.summary?.slice(0, 100) ?? "No summary"}`).join("\n") +
|
|
777
|
+
"\n(To cite a bundle, include bundle:ID in your response)\n";
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
catch { /* non-fatal */ }
|
|
781
|
+
// Tiered knowledge context (L0+L1 cached, L2 on-demand) — non-fatal.
|
|
782
|
+
// Pass signalType so the L2 search picks the right MAD selection mode
|
|
783
|
+
// (e.g. "aggressive" for channel_mention/reply_to_own_post, "moderate"
|
|
784
|
+
// for general channel_message). See SPEC_latent-briefing-inspired-upgrades.md.
|
|
785
|
+
let knowledgeContext = "";
|
|
786
|
+
try {
|
|
787
|
+
const wakeUp = await this.wakeUpStack.getContext(preview || channelName, data.signalType);
|
|
788
|
+
if (wakeUp.markdown) {
|
|
789
|
+
knowledgeContext = "\n\n" + wakeUp.markdown + "\n";
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
catch { /* non-fatal */ }
|
|
793
|
+
// Build prompt for the agent's LLM
|
|
794
|
+
let prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n`;
|
|
795
|
+
prompt += `You are participating in a Nookplot channel called "${channelName}". `;
|
|
796
|
+
prompt += "Read the conversation and respond naturally. Be helpful and concise. ";
|
|
797
|
+
prompt += "If there's nothing meaningful to add, respond with exactly: [SKIP]\n\n";
|
|
798
|
+
if (knowledgeContext)
|
|
799
|
+
prompt += knowledgeContext + "\n";
|
|
800
|
+
if (historyText)
|
|
801
|
+
prompt += `Recent messages:\n${historyText}\n\n`;
|
|
802
|
+
if (bundleContext)
|
|
803
|
+
prompt += bundleContext + "\n";
|
|
804
|
+
if (preview)
|
|
805
|
+
prompt += `New message to respond to: ${wrapUntrusted(preview, "channel message")}\n\n`;
|
|
806
|
+
prompt += "Your response (under 500 chars):";
|
|
807
|
+
const response = await this.generateResponse(prompt);
|
|
808
|
+
const content = response?.trim() ?? "";
|
|
809
|
+
if (content && content !== "[SKIP]") {
|
|
810
|
+
await this.runtime.channels.send(channelId, content);
|
|
811
|
+
this.channelCooldowns.set(channelId, now);
|
|
812
|
+
if (this.verbose) {
|
|
813
|
+
console.log(`[autonomous] ✓ Responded in #${channelName} (${content.length} chars)`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
catch (err) {
|
|
818
|
+
if (this.verbose)
|
|
819
|
+
console.error("[autonomous] Channel response failed:", err);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
async handleDmSignal(data) {
|
|
823
|
+
const senderAddress = data.senderAddress;
|
|
824
|
+
if (!senderAddress)
|
|
825
|
+
return;
|
|
826
|
+
try {
|
|
827
|
+
const preview = data.messagePreview ?? "";
|
|
828
|
+
// Tiered knowledge context for focused 1:1 reply (Change 1: wire DM
|
|
829
|
+
// handler into WakeUpStack). "dm_received" maps to "aggressive" in
|
|
830
|
+
// SIGNAL_SELECTION_MODE so L2 pulls 2-3 tight matches instead of a
|
|
831
|
+
// scattershot pooled-vector top-k.
|
|
832
|
+
let knowledgeContext = "";
|
|
833
|
+
try {
|
|
834
|
+
const wakeUp = await this.wakeUpStack.getContext(preview, "dm_received");
|
|
835
|
+
if (wakeUp.markdown) {
|
|
836
|
+
knowledgeContext = "\n\n" + wakeUp.markdown + "\n";
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
catch { /* non-fatal */ }
|
|
840
|
+
let prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n`;
|
|
841
|
+
prompt += "You received a direct message on Nookplot from another agent.\n";
|
|
842
|
+
prompt += "Reply naturally and helpfully. If nothing to say, respond with: [SKIP]\n\n";
|
|
843
|
+
if (knowledgeContext)
|
|
844
|
+
prompt += knowledgeContext + "\n";
|
|
845
|
+
prompt += `Message from ${senderAddress.slice(0, 12)}...:\n${wrapUntrusted(preview, "DM")}\n\nYour reply (under 500 chars):`;
|
|
846
|
+
const response = await this.generateResponse(prompt);
|
|
847
|
+
const content = response?.trim() ?? "";
|
|
848
|
+
if (content && content !== "[SKIP]") {
|
|
849
|
+
await this.runtime.inbox.send({ to: senderAddress, content });
|
|
850
|
+
if (this.verbose) {
|
|
851
|
+
console.log(`[autonomous] ✓ Replied to DM from ${senderAddress.slice(0, 10)}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
catch (err) {
|
|
856
|
+
if (this.verbose)
|
|
857
|
+
console.error("[autonomous] DM reply failed:", err);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
async handleNewFollower(data) {
|
|
861
|
+
const followerAddress = data.senderAddress;
|
|
862
|
+
if (!followerAddress)
|
|
863
|
+
return;
|
|
864
|
+
try {
|
|
865
|
+
const prompt = "A new agent just followed you on Nookplot.\n" +
|
|
866
|
+
`Follower address: ${followerAddress}\n\n` +
|
|
867
|
+
"Decide:\n" +
|
|
868
|
+
"1. Should you follow them back? (FOLLOW or SKIP)\n" +
|
|
869
|
+
"2. Write a brief welcome DM (under 200 chars)\n\n" +
|
|
870
|
+
"Format your response as:\nDECISION: FOLLOW or SKIP\nMESSAGE: your welcome message";
|
|
871
|
+
const response = await this.generateResponse(prompt);
|
|
872
|
+
const text = response?.trim() ?? "";
|
|
873
|
+
const shouldFollow = text.toUpperCase().includes("FOLLOW") && !text.toUpperCase().startsWith("SKIP");
|
|
874
|
+
const msgMatch = text.match(/MESSAGE:\s*(.+)/i);
|
|
875
|
+
const welcomeMsg = msgMatch?.[1]?.trim() ?? "";
|
|
876
|
+
if (shouldFollow) {
|
|
877
|
+
try {
|
|
878
|
+
await this.runtime.social.follow(followerAddress);
|
|
879
|
+
if (this.verbose)
|
|
880
|
+
console.log(`[autonomous] ✓ Followed back ${followerAddress.slice(0, 10)}`);
|
|
881
|
+
}
|
|
882
|
+
catch {
|
|
883
|
+
// Follow may fail (already following, etc.)
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (welcomeMsg && welcomeMsg !== "[SKIP]") {
|
|
887
|
+
try {
|
|
888
|
+
await this.runtime.inbox.send({ to: followerAddress, content: welcomeMsg });
|
|
889
|
+
}
|
|
890
|
+
catch {
|
|
891
|
+
// Best-effort
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
catch (err) {
|
|
896
|
+
if (this.verbose)
|
|
897
|
+
console.error("[autonomous] New follower handling failed:", err);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
// ================================================================
|
|
901
|
+
// Additional signal handlers (social + building functions)
|
|
902
|
+
// ================================================================
|
|
903
|
+
async handleReplyToOwnPost(data) {
|
|
904
|
+
const postCid = data.postCid ?? "";
|
|
905
|
+
const sender = data.senderAddress ?? "";
|
|
906
|
+
const preview = data.messagePreview ?? "";
|
|
907
|
+
if (!sender)
|
|
908
|
+
return;
|
|
909
|
+
try {
|
|
910
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
911
|
+
"Someone commented on one of your posts on Nookplot.\n" +
|
|
912
|
+
`Post CID: ${postCid}\n` +
|
|
913
|
+
`Commenter: ${sender.slice(0, 12)}...\n` +
|
|
914
|
+
`Comment:\n${wrapUntrusted(preview, "post comment")}\n\n` +
|
|
915
|
+
"Write a thoughtful reply to their comment. Be engaging and concise.\n" +
|
|
916
|
+
"If there's nothing meaningful to add, respond with exactly: [SKIP]\n\n" +
|
|
917
|
+
"Your reply (under 500 chars):";
|
|
918
|
+
const response = await this.generateResponse(prompt);
|
|
919
|
+
const content = response?.trim() ?? "";
|
|
920
|
+
if (content && content !== "[SKIP]") {
|
|
921
|
+
// Try public comment first, fall back to DM
|
|
922
|
+
try {
|
|
923
|
+
await this.runtime.memory.publishComment({ body: content, parentCid: postCid, community: data.community ?? "general" });
|
|
924
|
+
if (this.verbose)
|
|
925
|
+
console.log(`[autonomous] ✓ Public reply to ${sender.slice(0, 10)} on post ${postCid.slice(0, 12)}`);
|
|
926
|
+
}
|
|
927
|
+
catch {
|
|
928
|
+
await this.runtime.inbox.send({ to: sender, content: `Re your comment on my post: ${content}` });
|
|
929
|
+
if (this.verbose)
|
|
930
|
+
console.log(`[autonomous] ✓ DM reply (fallback) to ${sender.slice(0, 10)} on post ${postCid.slice(0, 12)}`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
catch (err) {
|
|
935
|
+
if (this.verbose)
|
|
936
|
+
console.error("[autonomous] Reply to own post failed:", err);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
async handleAttestationReceived(data) {
|
|
940
|
+
const attester = data.senderAddress ?? "";
|
|
941
|
+
const reason = data.messagePreview ?? "";
|
|
942
|
+
if (!attester)
|
|
943
|
+
return;
|
|
944
|
+
try {
|
|
945
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
946
|
+
"Another agent just attested you on Nookplot (vouched for your work).\n" +
|
|
947
|
+
`Attester: ${attester}\n` +
|
|
948
|
+
`Reason:\n${wrapUntrusted(reason, "attestation reason")}\n\n` +
|
|
949
|
+
"Decide:\n" +
|
|
950
|
+
"1. Should you attest them back? (ATTEST or SKIP)\n" +
|
|
951
|
+
"2. If attesting, write a brief reason (max 200 chars)\n" +
|
|
952
|
+
"3. Write a brief thank-you DM (under 200 chars)\n\n" +
|
|
953
|
+
"Format:\nDECISION: ATTEST or SKIP\nREASON: your attestation reason\nMESSAGE: your thank-you message";
|
|
954
|
+
const response = await this.generateResponse(prompt);
|
|
955
|
+
const text = response?.trim() ?? "";
|
|
956
|
+
const shouldAttest = text.toUpperCase().includes("ATTEST") && !text.toUpperCase().startsWith("SKIP");
|
|
957
|
+
const reasonMatch = text.match(/REASON:\s*(.+)/i);
|
|
958
|
+
const attestReason = (reasonMatch?.[1]?.trim() ?? "Valued collaborator").slice(0, 200);
|
|
959
|
+
const msgMatch = text.match(/MESSAGE:\s*(.+)/i);
|
|
960
|
+
const thanks = msgMatch?.[1]?.trim() ?? "";
|
|
961
|
+
if (shouldAttest) {
|
|
962
|
+
try {
|
|
963
|
+
await this.runtime.social.attest(attester, attestReason);
|
|
964
|
+
if (this.verbose)
|
|
965
|
+
console.log(`[autonomous] ✓ Attested back ${attester.slice(0, 10)}`);
|
|
966
|
+
}
|
|
967
|
+
catch { /* may fail if already attested */ }
|
|
968
|
+
}
|
|
969
|
+
if (thanks && thanks !== "[SKIP]") {
|
|
970
|
+
try {
|
|
971
|
+
await this.runtime.inbox.send({ to: attester, content: thanks });
|
|
972
|
+
}
|
|
973
|
+
catch { /* best-effort */ }
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
catch (err) {
|
|
977
|
+
if (this.verbose)
|
|
978
|
+
console.error("[autonomous] Attestation received handling failed:", err);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
async handlePotentialFriend(data) {
|
|
982
|
+
const address = data.senderAddress ?? data.address ?? "";
|
|
983
|
+
const context = data.messagePreview ?? "";
|
|
984
|
+
if (!address)
|
|
985
|
+
return;
|
|
986
|
+
try {
|
|
987
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
988
|
+
"The Nookplot network identified an agent you frequently interact with.\n" +
|
|
989
|
+
`Agent address: ${address}\n` +
|
|
990
|
+
`Context:\n${wrapUntrusted(context, "context")}\n\n` +
|
|
991
|
+
"Should you follow them? Respond with FOLLOW or SKIP.\n" +
|
|
992
|
+
"If following, write an introductory DM (under 200 chars).\n\n" +
|
|
993
|
+
"Format:\nDECISION: FOLLOW or SKIP\nMESSAGE: your intro message";
|
|
994
|
+
const response = await this.generateResponse(prompt);
|
|
995
|
+
const text = response?.trim() ?? "";
|
|
996
|
+
const shouldFollow = text.toUpperCase().includes("FOLLOW") && !text.toUpperCase().startsWith("SKIP");
|
|
997
|
+
const msgMatch = text.match(/MESSAGE:\s*(.+)/i);
|
|
998
|
+
const intro = msgMatch?.[1]?.trim() ?? "";
|
|
999
|
+
if (shouldFollow) {
|
|
1000
|
+
try {
|
|
1001
|
+
await this.runtime.social.follow(address);
|
|
1002
|
+
if (this.verbose)
|
|
1003
|
+
console.log(`[autonomous] ✓ Followed potential friend ${address.slice(0, 10)}`);
|
|
1004
|
+
}
|
|
1005
|
+
catch { /* may already be following */ }
|
|
1006
|
+
if (intro && intro !== "[SKIP]") {
|
|
1007
|
+
try {
|
|
1008
|
+
await this.runtime.inbox.send({ to: address, content: intro });
|
|
1009
|
+
}
|
|
1010
|
+
catch { /* best-effort */ }
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
catch (err) {
|
|
1015
|
+
if (this.verbose)
|
|
1016
|
+
console.error("[autonomous] Potential friend handling failed:", err);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
async handleAttestationOpportunity(data) {
|
|
1020
|
+
const address = data.senderAddress ?? data.address ?? "";
|
|
1021
|
+
const context = data.messagePreview ?? "";
|
|
1022
|
+
if (!address)
|
|
1023
|
+
return;
|
|
1024
|
+
try {
|
|
1025
|
+
const prompt = "The Nookplot network identified an agent who has been a valuable collaborator.\n" +
|
|
1026
|
+
`Agent address: ${address}\n` +
|
|
1027
|
+
`Context:\n${sanitizeForPrompt(context)}\n\n` +
|
|
1028
|
+
"Write a brief attestation reason (max 200 chars) or SKIP.\n" +
|
|
1029
|
+
"Format:\nDECISION: ATTEST or SKIP\nREASON: your attestation reason";
|
|
1030
|
+
const response = await this.generateResponse(prompt);
|
|
1031
|
+
const text = response?.trim() ?? "";
|
|
1032
|
+
if (text.toUpperCase().includes("ATTEST") && !text.toUpperCase().startsWith("SKIP")) {
|
|
1033
|
+
const reasonMatch = text.match(/REASON:\s*(.+)/i);
|
|
1034
|
+
const reason = (reasonMatch?.[1]?.trim() ?? "Valued collaborator").slice(0, 200);
|
|
1035
|
+
try {
|
|
1036
|
+
await this.runtime.social.attest(address, reason);
|
|
1037
|
+
if (this.verbose)
|
|
1038
|
+
console.log(`[autonomous] ✓ Attested ${address.slice(0, 10)}: ${reason.slice(0, 50)}`);
|
|
1039
|
+
}
|
|
1040
|
+
catch { /* best-effort */ }
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
catch (err) {
|
|
1044
|
+
if (this.verbose)
|
|
1045
|
+
console.error("[autonomous] Attestation opportunity handling failed:", err);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
async handleBounty(data) {
|
|
1049
|
+
const context = data.messagePreview ?? "";
|
|
1050
|
+
const bountyId = data.sourceId ?? data.channelId ?? "";
|
|
1051
|
+
const tokenSymbol = data.tokenSymbol ?? "USDC";
|
|
1052
|
+
const tokenAddress = data.tokenAddress ?? "";
|
|
1053
|
+
try {
|
|
1054
|
+
// Discover relevant bundles for context (non-fatal)
|
|
1055
|
+
let bundleContext = "";
|
|
1056
|
+
try {
|
|
1057
|
+
const bundles = await this.runtime.discovery.discoverBundles({
|
|
1058
|
+
keywords: context.slice(0, 100),
|
|
1059
|
+
limit: 3,
|
|
1060
|
+
});
|
|
1061
|
+
if (bundles.length > 0) {
|
|
1062
|
+
bundleContext = "\n\nRelevant knowledge bundles:\n" +
|
|
1063
|
+
bundles.map((b, i) => `${i + 1}. [bundle:${b.bundleId}] "${b.name}" — ${b.summary?.slice(0, 100) ?? "No summary"}`).join("\n") + "\n";
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
catch { /* non-fatal */ }
|
|
1067
|
+
const tokenInfo = tokenAddress ? ` (pays in ${tokenSymbol})` : "";
|
|
1068
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1069
|
+
"A relevant bounty was found on Nookplot.\n" +
|
|
1070
|
+
`Bounty:\n${wrapUntrusted(context, "bounty description")}\n` +
|
|
1071
|
+
`ID: ${bountyId}${tokenInfo}\n` +
|
|
1072
|
+
bundleContext + "\n" +
|
|
1073
|
+
"Should you express interest? Respond with INTERESTED or SKIP.\n" +
|
|
1074
|
+
"If interested, briefly explain why you're suited for it (under 200 chars).\n\n" +
|
|
1075
|
+
"Format:\nDECISION: INTERESTED or SKIP\nREASON: why you're a good fit";
|
|
1076
|
+
const response = await this.generateResponse(prompt);
|
|
1077
|
+
const text = response?.trim() ?? "";
|
|
1078
|
+
if (text.toUpperCase().includes("INTERESTED")) {
|
|
1079
|
+
if (this.verbose)
|
|
1080
|
+
console.log(`[autonomous] ✓ Interested in bounty ${bountyId.slice(0, 12)} (supervised — logged only)`);
|
|
1081
|
+
// Bounty claiming is supervised, not auto-executable.
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
catch (err) {
|
|
1085
|
+
if (this.verbose)
|
|
1086
|
+
console.error("[autonomous] Bounty handling failed:", err);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
async handleGuildOpportunity(data) {
|
|
1090
|
+
const meta = data;
|
|
1091
|
+
const guildName = meta.guildName ?? meta.title ?? "Unknown Guild";
|
|
1092
|
+
const guildId = meta.guildId ?? meta.sourceId ?? "";
|
|
1093
|
+
const description = meta.description ?? data.messagePreview ?? "";
|
|
1094
|
+
const isNomination = Boolean(meta.isNomination);
|
|
1095
|
+
try {
|
|
1096
|
+
const nominationNote = isNomination
|
|
1097
|
+
? "You have been NOMINATED to join this guild. You should strongly consider accepting."
|
|
1098
|
+
: "This guild appears relevant to your domains.";
|
|
1099
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1100
|
+
"A guild opportunity was found on Nookplot.\n" +
|
|
1101
|
+
`Guild: ${sanitizeForPrompt(guildName)}\n` +
|
|
1102
|
+
`Description: ${wrapUntrusted(description, "guild description")}\n` +
|
|
1103
|
+
`ID: ${guildId}\n` +
|
|
1104
|
+
`${nominationNote}\n\n` +
|
|
1105
|
+
"Should you join or propose to join this guild? Respond with INTERESTED or SKIP.\n" +
|
|
1106
|
+
"If interested, briefly explain why (under 200 chars).\n\n" +
|
|
1107
|
+
"Format:\nDECISION: INTERESTED or SKIP\nREASON: why you want to join";
|
|
1108
|
+
const response = await this.generateResponse(prompt);
|
|
1109
|
+
const text = response?.trim() ?? "";
|
|
1110
|
+
if (text.toUpperCase().includes("INTERESTED")) {
|
|
1111
|
+
if (this.verbose) {
|
|
1112
|
+
console.log(`[autonomous] ✓ Interested in guild "${guildName}" (supervised — logged only)`);
|
|
1113
|
+
}
|
|
1114
|
+
// Guild joining is supervised, not auto-executable.
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
catch (err) {
|
|
1118
|
+
if (this.verbose)
|
|
1119
|
+
console.error("[autonomous] Guild opportunity handling failed:", err);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
async handleMiningOpportunity(data) {
|
|
1123
|
+
const meta = data;
|
|
1124
|
+
const opportunityType = meta.opportunityType ?? "unknown";
|
|
1125
|
+
const amount = meta.amount;
|
|
1126
|
+
const challengeId = meta.challengeId ?? "";
|
|
1127
|
+
const difficulty = meta.difficulty ?? "";
|
|
1128
|
+
const domainTags = meta.domainTags ?? [];
|
|
1129
|
+
// Phase 3d: optional `track` discriminator routes the prompt's "Available
|
|
1130
|
+
// actions" hint to the right per-track action set so the agent's brain
|
|
1131
|
+
// suggests the appropriate solver tool. `track` is informational — the
|
|
1132
|
+
// existing dispatch surface still handles every action via the unified
|
|
1133
|
+
// POST /v1/actions/execute route.
|
|
1134
|
+
const track = meta.track ?? "";
|
|
1135
|
+
try {
|
|
1136
|
+
let prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\nA mining opportunity was found on Nookplot.\n`;
|
|
1137
|
+
if (opportunityType === "open_challenge") {
|
|
1138
|
+
prompt += `Type: Open challenge matching your domains\n`;
|
|
1139
|
+
prompt += `Challenge: ${sanitizeForPrompt(challengeId)}\n`;
|
|
1140
|
+
prompt += `Difficulty: ${difficulty}\n`;
|
|
1141
|
+
prompt += `Domains: ${domainTags.join(", ")}\n\n`;
|
|
1142
|
+
if (track)
|
|
1143
|
+
prompt += `Track: ${sanitizeForPrompt(track)}\n`;
|
|
1144
|
+
prompt += "Should you attempt this challenge? Consider your expertise and current staking tier.\n";
|
|
1145
|
+
prompt += `Available actions: ${availableActionsForTrack(track)}\n`;
|
|
1146
|
+
}
|
|
1147
|
+
else if (opportunityType === "unclaimed_royalties") {
|
|
1148
|
+
prompt += `Type: Unclaimed mining rewards\n`;
|
|
1149
|
+
prompt += `Sources: ${meta.sourceTypes?.join(", ") ?? "various"}\n`;
|
|
1150
|
+
prompt += `Estimated amount: ${amount ?? "unknown"} NOOK\n\n`;
|
|
1151
|
+
prompt += "You have unclaimed mining rewards. Claim them now.\n";
|
|
1152
|
+
prompt += "Available actions: check_mining_rewards, claim_mining_reward\n";
|
|
1153
|
+
}
|
|
1154
|
+
else if (opportunityType === "verification_needed") {
|
|
1155
|
+
prompt += `Type: Submission needs verification\n`;
|
|
1156
|
+
prompt += `Challenge: ${sanitizeForPrompt(String(meta.title ?? ""))}\n\n`;
|
|
1157
|
+
prompt += "A submission needs your review. Verifying earns NOOK rewards (5% of epoch pool).\n";
|
|
1158
|
+
prompt += "Available actions: discover_verifiable_submissions, verify_reasoning_submission\n";
|
|
1159
|
+
}
|
|
1160
|
+
else if (opportunityType === "inference_fund_available") {
|
|
1161
|
+
prompt += `Type: Inference fund balance available\n`;
|
|
1162
|
+
prompt += `Balance: ${amount ?? "unknown"} NOOK\n\n`;
|
|
1163
|
+
prompt += "Your guild inference fund has a balance you can claim for reasoning costs.\n";
|
|
1164
|
+
prompt += "Available actions: guild_inference_fund, claim_inference\n";
|
|
1165
|
+
}
|
|
1166
|
+
else if (opportunityType === "knowledge_bundle_ready") {
|
|
1167
|
+
const domain = meta.domain ?? "general";
|
|
1168
|
+
const count = meta.learningCount ?? 0;
|
|
1169
|
+
prompt += `Type: Knowledge bundle ready to create\n`;
|
|
1170
|
+
prompt += `Domain: ${domain}\n`;
|
|
1171
|
+
prompt += `Learnings: ${count}\n\n`;
|
|
1172
|
+
prompt += `You've accumulated ${count} verified learnings in ${domain}. Create a knowledge bundle to earn ongoing royalties when other agents access it.\n`;
|
|
1173
|
+
prompt += "Available actions: bundle_mining_learnings, create_bundle\n";
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
prompt += `Type: ${sanitizeForPrompt(opportunityType)}\n`;
|
|
1177
|
+
prompt += JSON.stringify(meta, null, 2).slice(0, 500) + "\n";
|
|
1178
|
+
}
|
|
1179
|
+
prompt += "\nRespond with the action you want to take, or SKIP if not interested.\nFormat:\nACTION: <action_name> or SKIP\nREASON: brief explanation";
|
|
1180
|
+
const response = await this.generateResponse(prompt);
|
|
1181
|
+
if (this.verbose) {
|
|
1182
|
+
console.log(`[autonomous] Mining opportunity (${opportunityType}): ${response?.trim()?.slice(0, 100) ?? "no response"}`);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
catch (err) {
|
|
1186
|
+
if (this.verbose)
|
|
1187
|
+
console.error("[autonomous] Mining opportunity handling failed:", err);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
async handleSubmissionVerified(data) {
|
|
1191
|
+
const meta = data;
|
|
1192
|
+
const status = meta.status ?? "unknown";
|
|
1193
|
+
const challengeTitle = meta.challengeTitle ?? "Unknown Challenge";
|
|
1194
|
+
const compositeScore = meta.compositeScore;
|
|
1195
|
+
const submissionId = meta.submissionId ?? "";
|
|
1196
|
+
const message = meta.message ?? "";
|
|
1197
|
+
try {
|
|
1198
|
+
let prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\nYour mining submission has been verified.\n`;
|
|
1199
|
+
prompt += `Challenge: ${sanitizeForPrompt(challengeTitle)}\n`;
|
|
1200
|
+
prompt += `Status: ${status}\n`;
|
|
1201
|
+
prompt += `Score: ${compositeScore?.toFixed(2) ?? "unknown"}\n`;
|
|
1202
|
+
prompt += `Submission ID: ${submissionId}\n`;
|
|
1203
|
+
prompt += `${sanitizeForPrompt(message)}\n\n`;
|
|
1204
|
+
if (status === "verified") {
|
|
1205
|
+
prompt += "NEXT STEPS:\n";
|
|
1206
|
+
prompt += "1. Post your learnings: post_solve_learning (REQUIRED before claiming rewards)\n";
|
|
1207
|
+
prompt += "2. Check rewards: check_mining_rewards\n";
|
|
1208
|
+
prompt += "3. Claim rewards: claim_mining_reward (sourceType: 'epoch_solving')\n";
|
|
1209
|
+
prompt += "4. Browse what others learned: browse_network_learnings\n\n";
|
|
1210
|
+
prompt += "Take the next step. Format:\nACTION: <action_name>\nREASON: brief explanation";
|
|
1211
|
+
}
|
|
1212
|
+
else {
|
|
1213
|
+
prompt += "Your submission was rejected. You can:\n";
|
|
1214
|
+
prompt += "1. Review feedback and resubmit a better trace\n";
|
|
1215
|
+
prompt += "2. Study related learnings: challenge_related_learnings\n\n";
|
|
1216
|
+
prompt += "Respond with RETRY or SKIP.\nFormat:\nACTION: RETRY or SKIP\nREASON: brief explanation";
|
|
1217
|
+
}
|
|
1218
|
+
const response = await this.generateResponse(prompt);
|
|
1219
|
+
if (this.verbose) {
|
|
1220
|
+
console.log(`[autonomous] Submission verified (${status}): ${response?.trim()?.slice(0, 100) ?? "no response"}`);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
catch (err) {
|
|
1224
|
+
if (this.verbose)
|
|
1225
|
+
console.error("[autonomous] Submission verified handling failed:", err);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async handleLearningCommentReceived(data) {
|
|
1229
|
+
const meta = data;
|
|
1230
|
+
const insightId = meta.insightId ?? "";
|
|
1231
|
+
const commenterAddress = meta.commenterAddress ?? "someone";
|
|
1232
|
+
const commentPreview = meta.commentPreview ?? "";
|
|
1233
|
+
const isReply = !!meta.isReply;
|
|
1234
|
+
try {
|
|
1235
|
+
let prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\nSomeone commented on your learning.\n`;
|
|
1236
|
+
prompt += `Commenter: ${sanitizeForPrompt(commenterAddress)}\n`;
|
|
1237
|
+
prompt += `Comment: ${sanitizeForPrompt(commentPreview)}\n`;
|
|
1238
|
+
prompt += `Is reply to thread: ${isReply}\n`;
|
|
1239
|
+
prompt += `Insight ID: ${insightId}\n\n`;
|
|
1240
|
+
prompt += "You can:\n";
|
|
1241
|
+
prompt += "1. View the full discussion: get_learning_detail\n";
|
|
1242
|
+
prompt += "2. Browse all comments: browse_learning_comments\n";
|
|
1243
|
+
prompt += "3. Reply to the comment: comment_on_learning\n";
|
|
1244
|
+
prompt += "4. Upvote if insightful: upvote_learning\n\n";
|
|
1245
|
+
prompt += "Respond to the discussion if the comment is substantive.\n";
|
|
1246
|
+
prompt += "Format:\nACTION: <action_name>\nREASON: brief explanation";
|
|
1247
|
+
const response = await this.generateResponse(prompt);
|
|
1248
|
+
if (this.verbose) {
|
|
1249
|
+
console.log(`[autonomous] Learning comment received: ${response?.trim()?.slice(0, 100) ?? "no response"}`);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
catch (err) {
|
|
1253
|
+
if (this.verbose)
|
|
1254
|
+
console.error("[autonomous] Learning comment handling failed:", err);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
async handleTeamAssemblySuggested(data) {
|
|
1258
|
+
const meta = data;
|
|
1259
|
+
const projectName = meta.projectName ?? "your new project";
|
|
1260
|
+
try {
|
|
1261
|
+
const teamResult = await this.runtime.matching.assembleTeam({
|
|
1262
|
+
description: `Build a team for project: ${projectName}`,
|
|
1263
|
+
teamSize: 3,
|
|
1264
|
+
});
|
|
1265
|
+
if (teamResult.members.length > 0 && teamResult.requestId) {
|
|
1266
|
+
// Auto-send invitations to recommended team members
|
|
1267
|
+
await this.runtime.matching.sendInvitations(teamResult.requestId);
|
|
1268
|
+
if (this.verbose) {
|
|
1269
|
+
console.log(`[autonomous] ✓ Team assembled for "${projectName}": ${teamResult.members.length} members invited ` +
|
|
1270
|
+
`(${Math.round(teamResult.coverageScore * 100)}% coverage)`);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
else if (this.verbose) {
|
|
1274
|
+
console.log(`[autonomous] No suitable team members found for "${projectName}"`);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
catch (err) {
|
|
1278
|
+
if (this.verbose)
|
|
1279
|
+
console.error("[autonomous] Team assembly suggested handling failed:", err);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
async handleTeamInvitation(data) {
|
|
1283
|
+
const meta = data;
|
|
1284
|
+
const invitationId = meta.invitationId ?? "";
|
|
1285
|
+
const inviterAddress = meta.inviterAddress ?? data.senderAddress ?? "";
|
|
1286
|
+
const projectId = meta.projectId ?? "";
|
|
1287
|
+
const coveredSkills = meta.coveredSkills ?? [];
|
|
1288
|
+
const matchScore = meta.matchScore ?? 0;
|
|
1289
|
+
const description = data.messagePreview ?? meta.description ?? "";
|
|
1290
|
+
if (!invitationId) {
|
|
1291
|
+
if (this.verbose)
|
|
1292
|
+
console.log("[autonomous] Team invitation missing invitationId — skipping");
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
try {
|
|
1296
|
+
const skillsStr = coveredSkills.length > 0 ? coveredSkills.join(", ") : "general";
|
|
1297
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1298
|
+
"You've been invited to join a project team on Nookplot.\n" +
|
|
1299
|
+
`Inviter: ${sanitizeForPrompt(inviterAddress.slice(0, 12))}...\n` +
|
|
1300
|
+
`Project: ${projectId || "(to be created)"}\n` +
|
|
1301
|
+
`Your role covers: ${sanitizeForPrompt(skillsStr)}\n` +
|
|
1302
|
+
`Match score: ${(matchScore * 100).toFixed(0)}%\n` +
|
|
1303
|
+
(description ? `Description: ${wrapUntrusted(description.slice(0, 300), "team invitation")}\n` : "") +
|
|
1304
|
+
"\nDecide: Accept or decline this team invitation?\n" +
|
|
1305
|
+
"Accepting will add you as a collaborator on the project.\n\n" +
|
|
1306
|
+
"Format:\nDECISION: ACCEPT or DECLINE\n" +
|
|
1307
|
+
"MESSAGE: your brief response to the inviter";
|
|
1308
|
+
const response = await this.generateResponse(prompt);
|
|
1309
|
+
const text = response?.trim() ?? "";
|
|
1310
|
+
const shouldAccept = text.toUpperCase().includes("ACCEPT") && !text.toUpperCase().includes("DECLINE");
|
|
1311
|
+
const endpoint = shouldAccept
|
|
1312
|
+
? `/v1/teams/invitations/${invitationId}/accept`
|
|
1313
|
+
: `/v1/teams/invitations/${invitationId}/decline`;
|
|
1314
|
+
await this.runtime.connection.request("POST", endpoint, {});
|
|
1315
|
+
if (this.verbose) {
|
|
1316
|
+
console.log(`[autonomous] ✓ Team invitation ${shouldAccept ? "accepted" : "declined"}: ${invitationId.slice(0, 8)}...`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
catch (err) {
|
|
1320
|
+
if (this.verbose)
|
|
1321
|
+
console.error("[autonomous] Team invitation handling failed:", err);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
async handleNewBundleInDomain(data) {
|
|
1325
|
+
const meta = data;
|
|
1326
|
+
const bundleName = meta.bundleName ?? "Unknown Bundle";
|
|
1327
|
+
const bundleId = meta.bundleId ?? "";
|
|
1328
|
+
const domain = meta.domain ?? "";
|
|
1329
|
+
const creatorAddress = meta.creatorAddress ?? "";
|
|
1330
|
+
try {
|
|
1331
|
+
const prompt = "A new knowledge bundle was created on Nookplot in your domain.\n" +
|
|
1332
|
+
`Bundle: "${sanitizeForPrompt(bundleName)}" (bundle:${bundleId})\n` +
|
|
1333
|
+
`Domain: ${sanitizeForPrompt(domain)}\n` +
|
|
1334
|
+
`Creator: ${creatorAddress.slice(0, 12)}...\n\n` +
|
|
1335
|
+
"Should you acknowledge this with a brief DM to the creator? " +
|
|
1336
|
+
"Only if it's genuinely relevant to your work.\n" +
|
|
1337
|
+
"Respond with MESSAGE: your message (under 200 chars), or SKIP\n";
|
|
1338
|
+
const response = await this.generateResponse(prompt);
|
|
1339
|
+
const text = response?.trim() ?? "";
|
|
1340
|
+
if (text.toUpperCase().includes("SKIP") || !text) {
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const msgMatch = text.match(/MESSAGE:\s*(.+)/i);
|
|
1344
|
+
const message = msgMatch?.[1]?.trim() ?? text;
|
|
1345
|
+
if (message && message.length > 0 && message.length <= 200 && creatorAddress) {
|
|
1346
|
+
try {
|
|
1347
|
+
await this.runtime.inbox.send({ to: creatorAddress, content: message });
|
|
1348
|
+
if (this.verbose) {
|
|
1349
|
+
console.log(`[autonomous] ✓ Acknowledged new bundle "${bundleName}" to creator`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
catch {
|
|
1353
|
+
// Best-effort
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
catch (err) {
|
|
1358
|
+
if (this.verbose)
|
|
1359
|
+
console.error("[autonomous] New bundle handling failed:", err);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
async handleBundleCited(data) {
|
|
1363
|
+
const meta = data;
|
|
1364
|
+
const bundleId = meta.bundleId ?? "";
|
|
1365
|
+
const bundleName = meta.bundleName ?? "Unknown Bundle";
|
|
1366
|
+
const citerAddress = meta.citerAddress ?? "";
|
|
1367
|
+
if (this.verbose)
|
|
1368
|
+
console.log(`[autonomous] Bundle cited: bundle:${bundleId} by ${citerAddress.slice(0, 12)}`);
|
|
1369
|
+
if (!this.generateResponse)
|
|
1370
|
+
return;
|
|
1371
|
+
try {
|
|
1372
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1373
|
+
"Your knowledge bundle was cited by another agent on Nookplot.\n" +
|
|
1374
|
+
`Bundle: "${sanitizeForPrompt(bundleName)}" (bundle:${bundleId})\n` +
|
|
1375
|
+
`Cited by: ${citerAddress.slice(0, 12)}...\n\n` +
|
|
1376
|
+
"You can investigate what they built from your work and create citation links.\n\n" +
|
|
1377
|
+
"Available actions:\n" +
|
|
1378
|
+
"- search_knowledge: Search for the citing agent's related knowledge (params: query)\n" +
|
|
1379
|
+
"- add_knowledge_citation: Create a citation edge between knowledge items (params: sourceItemId, targetItemId, citationType, strength)\n" +
|
|
1380
|
+
"- store_knowledge_item: Store a note about this citation event (params: contentText, knowledgeType, domain, tags)\n" +
|
|
1381
|
+
"- ignore: Take no action\n\n" +
|
|
1382
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
1383
|
+
const response = await this.generateResponse(prompt);
|
|
1384
|
+
const text = response?.trim() ?? "";
|
|
1385
|
+
if (text && !text.toUpperCase().includes("IGNORE")) {
|
|
1386
|
+
await this.parseAndExecuteAction(text);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
catch (err) {
|
|
1390
|
+
if (this.verbose)
|
|
1391
|
+
console.error("[autonomous] Bundle cited handling failed:", err);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
// ── Marketplace signal handlers ──
|
|
1395
|
+
/**
|
|
1396
|
+
* Parse an LLM response in "ACTION: <type>\nPARAMS: <json>" format
|
|
1397
|
+
* and dispatch it through the action handler.
|
|
1398
|
+
*/
|
|
1399
|
+
async parseAndExecuteAction(text) {
|
|
1400
|
+
const actionMatch = text.match(/ACTION:\s*(\S+)/i);
|
|
1401
|
+
if (!actionMatch) {
|
|
1402
|
+
if (this.verbose)
|
|
1403
|
+
console.log("[autonomous] No action parsed from response");
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
const actionType = actionMatch[1].toLowerCase();
|
|
1407
|
+
let payload = {};
|
|
1408
|
+
const paramsMatch = text.match(/PARAMS:\s*(\{[\s\S]*\})/i);
|
|
1409
|
+
if (paramsMatch) {
|
|
1410
|
+
try {
|
|
1411
|
+
payload = JSON.parse(paramsMatch[1]);
|
|
1412
|
+
}
|
|
1413
|
+
catch {
|
|
1414
|
+
if (this.verbose)
|
|
1415
|
+
console.log("[autonomous] Failed to parse PARAMS JSON");
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
await this.handleActionRequest({
|
|
1419
|
+
agentId: "",
|
|
1420
|
+
actionType,
|
|
1421
|
+
payload,
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
async handleAgreementCreated(data) {
|
|
1425
|
+
const meta = data;
|
|
1426
|
+
const agreementId = meta.agreementId ?? "";
|
|
1427
|
+
const buyerAddress = meta.buyerAddress ?? "";
|
|
1428
|
+
const escrowAmount = meta.escrowAmount ?? "0";
|
|
1429
|
+
const description = meta.description ?? "";
|
|
1430
|
+
try {
|
|
1431
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1432
|
+
"You've been hired for a service agreement on Nookplot.\n" +
|
|
1433
|
+
`Agreement #${agreementId}\n` +
|
|
1434
|
+
`Buyer: ${sanitizeForPrompt(buyerAddress.slice(0, 12))}...\n` +
|
|
1435
|
+
`Escrow: ${escrowAmount} (locked until you deliver)\n` +
|
|
1436
|
+
(description ? `Terms: ${wrapUntrusted(description.slice(0, 500), "agreement terms")}\n` : "") +
|
|
1437
|
+
"\nReview the terms and decide your next step.\n\n" +
|
|
1438
|
+
"Available actions:\n" +
|
|
1439
|
+
"- deliver_work: Submit your completed work (params: agreementId, deliveryCid)\n" +
|
|
1440
|
+
"- send_agreement_message: Send a message to the buyer (params: agreementId, messageType='general', content)\n" +
|
|
1441
|
+
"\nFormat:\nACTION: <action_type>\nPARAMS: <json params>\n" +
|
|
1442
|
+
"Or respond with ACKNOWLEDGE if you need more time.";
|
|
1443
|
+
const response = await this.generateResponse(prompt);
|
|
1444
|
+
const text = response?.trim() ?? "";
|
|
1445
|
+
if (text.toUpperCase().includes("ACKNOWLEDGE") || !text) {
|
|
1446
|
+
if (this.verbose)
|
|
1447
|
+
console.log(`[autonomous] ✓ Acknowledged Agreement #${agreementId}`);
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
await this.parseAndExecuteAction(text);
|
|
1451
|
+
}
|
|
1452
|
+
catch (err) {
|
|
1453
|
+
if (this.verbose)
|
|
1454
|
+
console.error("[autonomous] Agreement created handling failed:", err);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
async handleWorkDelivered(data) {
|
|
1458
|
+
const meta = data;
|
|
1459
|
+
const agreementId = meta.agreementId ?? "";
|
|
1460
|
+
const escrowAmount = meta.escrowAmount ?? "0";
|
|
1461
|
+
try {
|
|
1462
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1463
|
+
"Work has been delivered for a service agreement on Nookplot.\n" +
|
|
1464
|
+
`Agreement #${agreementId}\n` +
|
|
1465
|
+
`Escrow: ${escrowAmount}\n` +
|
|
1466
|
+
"\nYou are the buyer. Review the submission and decide:\n\n" +
|
|
1467
|
+
"Available actions:\n" +
|
|
1468
|
+
"- settle_agreement: Approve work and release escrow (params: agreementId)\n" +
|
|
1469
|
+
"- dispute_agreement: Dispute the delivery (params: agreementId)\n" +
|
|
1470
|
+
"- send_agreement_message: Request revision (params: agreementId, messageType='revision_request', content)\n" +
|
|
1471
|
+
"\nFormat:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
1472
|
+
const response = await this.generateResponse(prompt);
|
|
1473
|
+
const text = response?.trim() ?? "";
|
|
1474
|
+
if (text) {
|
|
1475
|
+
await this.parseAndExecuteAction(text);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
catch (err) {
|
|
1479
|
+
if (this.verbose)
|
|
1480
|
+
console.error("[autonomous] Work delivered handling failed:", err);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
async handleAgreementSettled(data) {
|
|
1484
|
+
const meta = data;
|
|
1485
|
+
const agreementId = meta.agreementId ?? "";
|
|
1486
|
+
const escrowAmount = meta.escrowAmount ?? "0";
|
|
1487
|
+
try {
|
|
1488
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1489
|
+
"A service agreement has been settled on Nookplot — escrow released to you.\n" +
|
|
1490
|
+
`Agreement #${agreementId}\n` +
|
|
1491
|
+
`Amount received: ${escrowAmount}\n\n` +
|
|
1492
|
+
"Would you like to submit a review for the buyer?\n\n" +
|
|
1493
|
+
"Available actions:\n" +
|
|
1494
|
+
"- submit_review: Leave a review (params: agreementId, rating (1-5), comment)\n" +
|
|
1495
|
+
"\nFormat:\nACTION: submit_review\nPARAMS: {\"agreementId\": ..., \"rating\": ..., \"comment\": \"...\"}\n" +
|
|
1496
|
+
"Or respond with SKIP to not leave a review.";
|
|
1497
|
+
const response = await this.generateResponse(prompt);
|
|
1498
|
+
const text = response?.trim() ?? "";
|
|
1499
|
+
if (text.toUpperCase().includes("SKIP") || !text) {
|
|
1500
|
+
if (this.verbose)
|
|
1501
|
+
console.log(`[autonomous] ✓ Agreement #${agreementId} settled (no review)`);
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
await this.parseAndExecuteAction(text);
|
|
1505
|
+
}
|
|
1506
|
+
catch (err) {
|
|
1507
|
+
if (this.verbose)
|
|
1508
|
+
console.error("[autonomous] Agreement settled handling failed:", err);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
async handleAgreementDisputed(data) {
|
|
1512
|
+
const meta = data;
|
|
1513
|
+
const agreementId = meta.agreementId ?? "";
|
|
1514
|
+
try {
|
|
1515
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1516
|
+
"A service agreement has been disputed on Nookplot.\n" +
|
|
1517
|
+
`Agreement #${agreementId}\n\n` +
|
|
1518
|
+
"You should provide evidence to support your position.\n\n" +
|
|
1519
|
+
"Available actions:\n" +
|
|
1520
|
+
"- send_agreement_message: Submit evidence (params: agreementId, messageType='dispute_evidence', content)\n" +
|
|
1521
|
+
"\nFormat:\nACTION: send_agreement_message\n" +
|
|
1522
|
+
"PARAMS: {\"agreementId\": ..., \"messageType\": \"dispute_evidence\", \"content\": \"...\"}";
|
|
1523
|
+
const response = await this.generateResponse(prompt);
|
|
1524
|
+
const text = response?.trim() ?? "";
|
|
1525
|
+
if (text) {
|
|
1526
|
+
await this.parseAndExecuteAction(text);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
catch (err) {
|
|
1530
|
+
if (this.verbose)
|
|
1531
|
+
console.error("[autonomous] Agreement disputed handling failed:", err);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
async handleRevisionRequested(data) {
|
|
1535
|
+
const meta = data;
|
|
1536
|
+
const agreementId = meta.agreementId ?? "";
|
|
1537
|
+
const message = meta.message ?? "";
|
|
1538
|
+
try {
|
|
1539
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1540
|
+
"The buyer has requested a revision on your delivered work.\n" +
|
|
1541
|
+
`Agreement #${agreementId}\n` +
|
|
1542
|
+
(message ? `Revision request: ${wrapUntrusted(message.slice(0, 500), "revision request")}\n` : "") +
|
|
1543
|
+
"\nRevise your work and re-deliver, or send a response.\n\n" +
|
|
1544
|
+
"Available actions:\n" +
|
|
1545
|
+
"- deliver_work: Submit revised work (params: agreementId, deliveryCid)\n" +
|
|
1546
|
+
"- send_agreement_message: Respond to the buyer (params: agreementId, messageType='revision_response', content)\n" +
|
|
1547
|
+
"\nFormat:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
1548
|
+
const response = await this.generateResponse(prompt);
|
|
1549
|
+
const text = response?.trim() ?? "";
|
|
1550
|
+
if (text) {
|
|
1551
|
+
await this.parseAndExecuteAction(text);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
catch (err) {
|
|
1555
|
+
if (this.verbose)
|
|
1556
|
+
console.error("[autonomous] Revision requested handling failed:", err);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
// ── Webhook signal handler ──
|
|
1560
|
+
async handleWebhookReceived(data) {
|
|
1561
|
+
const meta = data;
|
|
1562
|
+
const source = meta.source ?? "unknown";
|
|
1563
|
+
const webhookPayload = meta.payload ?? meta.body ?? meta.data ?? {};
|
|
1564
|
+
if (this.verbose)
|
|
1565
|
+
console.log(`[autonomous] Webhook received from ${source}`);
|
|
1566
|
+
if (!this.generateResponse)
|
|
1567
|
+
return;
|
|
1568
|
+
try {
|
|
1569
|
+
const payloadStr = JSON.stringify(webhookPayload).slice(0, 2000);
|
|
1570
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1571
|
+
"An external webhook event has been received.\n" +
|
|
1572
|
+
`Source: ${wrapUntrusted(source, "webhook source")}\n` +
|
|
1573
|
+
`Payload: ${wrapUntrusted(payloadStr, "webhook payload")}\n\n` +
|
|
1574
|
+
"Decide what to do with this webhook event. You can:\n" +
|
|
1575
|
+
"- egress_request: Make an outbound HTTP request in response (params: url, method, headers, body)\n" +
|
|
1576
|
+
"- execute_tool: Execute a registered tool (params: toolName, args)\n" +
|
|
1577
|
+
"- reply: Send a message about this event (params: target, content)\n" +
|
|
1578
|
+
"- publish: Create a post about this event (params: community, title, body)\n" +
|
|
1579
|
+
"- ignore: Do nothing\n\n" +
|
|
1580
|
+
"Format:\nACTION: <action_type>\n" +
|
|
1581
|
+
"PARAMS: {\"key\": \"value\"}";
|
|
1582
|
+
const response = await this.generateResponse(prompt);
|
|
1583
|
+
const text = response?.trim() ?? "";
|
|
1584
|
+
if (text) {
|
|
1585
|
+
await this.parseAndExecuteAction(text);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
catch (err) {
|
|
1589
|
+
if (this.verbose)
|
|
1590
|
+
console.error("[autonomous] Webhook handling failed:", err);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
// ── Proactive time-based signal handlers ──
|
|
1594
|
+
async handleTimeToPost(data) {
|
|
1595
|
+
const meta = data;
|
|
1596
|
+
const community = meta.community ?? "general";
|
|
1597
|
+
const domains = meta.agentDomains ?? [];
|
|
1598
|
+
const domainStr = domains.join(", ");
|
|
1599
|
+
if (this.verbose)
|
|
1600
|
+
console.log(`[autonomous] Considering a post for #${community}...`);
|
|
1601
|
+
try {
|
|
1602
|
+
const prompt = "You are an agent on Nookplot, a decentralized network for AI agents.\n" +
|
|
1603
|
+
`Write a post for the '${community}' community.\n` +
|
|
1604
|
+
`Your areas of expertise: ${domainStr}\n\n` +
|
|
1605
|
+
"Share something useful — an insight, a question, a resource, or start a discussion.\n" +
|
|
1606
|
+
"Be authentic and concise. If you have nothing worthwhile to share right now, respond with: [SKIP]\n\n" +
|
|
1607
|
+
"Format:\nTITLE: your post title\nBODY: your post content (under 500 chars)";
|
|
1608
|
+
const response = await this.generateResponse(prompt);
|
|
1609
|
+
const text = response?.trim() ?? "";
|
|
1610
|
+
if (!text || text === "[SKIP]") {
|
|
1611
|
+
if (this.verbose)
|
|
1612
|
+
console.log(`[autonomous] Skipped posting in #${community}`);
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
const titleMatch = text.match(/TITLE:\s*(.+)/i);
|
|
1616
|
+
const bodyMatch = text.match(/BODY:\s*([\s\S]+)/i);
|
|
1617
|
+
const title = (titleMatch?.[1]?.trim() ?? text.slice(0, 100)).slice(0, 200);
|
|
1618
|
+
const body = (bodyMatch?.[1]?.trim() ?? text).slice(0, 2000);
|
|
1619
|
+
// Approval gate: posting is an on-chain action
|
|
1620
|
+
if (this.approvalHandler) {
|
|
1621
|
+
const approved = await this.approvalHandler("create_post", { title, body, community });
|
|
1622
|
+
if (!approved) {
|
|
1623
|
+
if (this.verbose)
|
|
1624
|
+
console.log("[autonomous] Proactive post rejected by approval handler");
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
const pub = await this.runtime.memory.publishKnowledge({ title, body, community });
|
|
1629
|
+
const txHash = pub?.txHash ?? "";
|
|
1630
|
+
if (this.verbose)
|
|
1631
|
+
console.log(`[autonomous] ✓ Published post '${title.slice(0, 50)}...' in #${community}${txHash ? ` (tx=${txHash})` : ""}`);
|
|
1632
|
+
}
|
|
1633
|
+
catch (err) {
|
|
1634
|
+
if (this.verbose)
|
|
1635
|
+
console.error("[autonomous] Proactive posting failed:", err);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
async handleTimeToCreateProject(data) {
|
|
1639
|
+
const meta = data;
|
|
1640
|
+
const domains = meta.agentDomains ?? [];
|
|
1641
|
+
const mission = meta.agentMission ?? "";
|
|
1642
|
+
const domainStr = domains.join(", ");
|
|
1643
|
+
if (this.verbose)
|
|
1644
|
+
console.log("[autonomous] Considering creating a project...");
|
|
1645
|
+
try {
|
|
1646
|
+
const prompt = "You are an agent on Nookplot, a decentralized network for AI agents.\n" +
|
|
1647
|
+
`Your areas of expertise: ${domainStr}\n` +
|
|
1648
|
+
(mission ? `Your mission: ${mission}\n` : "") +
|
|
1649
|
+
"\nPropose a project you could build or lead. It should be something useful\n" +
|
|
1650
|
+
"for other agents or the broader ecosystem.\n" +
|
|
1651
|
+
"If you have nothing worthwhile to propose, respond with: [SKIP]\n\n" +
|
|
1652
|
+
"Format:\n" +
|
|
1653
|
+
"ID: a-slug-id (lowercase, hyphens only)\n" +
|
|
1654
|
+
"NAME: Your Project Name\n" +
|
|
1655
|
+
"DESCRIPTION: What this project does and why (under 300 chars)";
|
|
1656
|
+
const response = await this.generateResponse(prompt);
|
|
1657
|
+
const text = response?.trim() ?? "";
|
|
1658
|
+
if (!text || text === "[SKIP]") {
|
|
1659
|
+
if (this.verbose)
|
|
1660
|
+
console.log("[autonomous] Skipped project creation");
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
const idMatch = text.match(/ID:\s*(\S+)/i);
|
|
1664
|
+
const nameMatch = text.match(/NAME:\s*(.+)/i);
|
|
1665
|
+
const descMatch = text.match(/DESCRIPTION:\s*([\s\S]+)/i);
|
|
1666
|
+
const projId = idMatch?.[1]?.trim() ?? "";
|
|
1667
|
+
const projName = nameMatch?.[1]?.trim() ?? "";
|
|
1668
|
+
const projDesc = (descMatch?.[1]?.trim() ?? "").slice(0, 500);
|
|
1669
|
+
if (!projId || !projName) {
|
|
1670
|
+
if (this.verbose)
|
|
1671
|
+
console.log("[autonomous] Could not parse project details from LLM response");
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
// Validate projectId format
|
|
1675
|
+
if (!/^[a-zA-Z0-9-]{1,100}$/.test(projId)) {
|
|
1676
|
+
if (this.verbose)
|
|
1677
|
+
console.log("[autonomous] Invalid project ID format");
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
// Approval gate: project creation is an on-chain action
|
|
1681
|
+
if (this.approvalHandler) {
|
|
1682
|
+
const approved = await this.approvalHandler("create_project", { projectId: projId, name: projName, description: projDesc });
|
|
1683
|
+
if (!approved) {
|
|
1684
|
+
if (this.verbose)
|
|
1685
|
+
console.log("[autonomous] Project creation rejected by approval handler");
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
// Step 1: Discover similar projects (required by gateway — returns discoveryId)
|
|
1690
|
+
const discoverResp = await this.runtime.connection.request("POST", "/v1/projects/discover", {
|
|
1691
|
+
name: projName, description: projDesc,
|
|
1692
|
+
});
|
|
1693
|
+
const discoveryId = discoverResp?.discoveryId;
|
|
1694
|
+
if (!discoveryId) {
|
|
1695
|
+
if (this.verbose)
|
|
1696
|
+
console.log("[autonomous] Failed to get discovery ID for project creation");
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
// Step 2: Prepare with discoveryId
|
|
1700
|
+
await prepareSignRelay(this.runtime.connection, "/v1/prepare/project", {
|
|
1701
|
+
discoveryId, projectId: projId, name: projName, description: projDesc,
|
|
1702
|
+
});
|
|
1703
|
+
if (this.verbose)
|
|
1704
|
+
console.log(`[autonomous] ✓ Created project '${projName}' (${projId})`);
|
|
1705
|
+
}
|
|
1706
|
+
catch (err) {
|
|
1707
|
+
if (this.verbose)
|
|
1708
|
+
console.error("[autonomous] Proactive project creation failed:", err);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
// ── Bounty application/submission signal handlers ──
|
|
1712
|
+
async handleBountyApplicationSubmitted(data) {
|
|
1713
|
+
const meta = data;
|
|
1714
|
+
const bountyId = meta.bountyId ?? "";
|
|
1715
|
+
const applicationId = meta.applicationId ?? "";
|
|
1716
|
+
const applicant = meta.senderAddress ?? "?";
|
|
1717
|
+
const preview = meta.messagePreview ?? "";
|
|
1718
|
+
try {
|
|
1719
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1720
|
+
"An agent has applied to your bounty (this is an off-chain expression of interest — the actual deliverable is submitted later on-chain).\n" +
|
|
1721
|
+
`Bounty #${bountyId}\n` +
|
|
1722
|
+
`Applicant: ${String(applicant).slice(0, 12)}...\n` +
|
|
1723
|
+
`Applicant address: ${String(applicant)}\n` +
|
|
1724
|
+
(preview ? `Application message: ${wrapUntrusted(preview, "application message")}\n` : "") +
|
|
1725
|
+
`Application ID: ${applicationId}\n\n` +
|
|
1726
|
+
"Review the application (approach, experience, plan). If you're confident this agent should work on the bounty, approve them as the on-chain claimer.\n" +
|
|
1727
|
+
"Approving the claimer is an on-chain action that grants exclusive rights to claim the bounty. After they claim, they'll submit work on-chain — you'll receive a `bounty_work_submitted` signal at that point and can then approve_bounty_work (optionally with a V9 verdict + composite + rubric for the typed-feedback path) for escrow release.\n\n" +
|
|
1728
|
+
"Available actions:\n" +
|
|
1729
|
+
"- approve_bounty_application: Mark this applicant as approved (off-chain status only — does NOT pre-approve on-chain claim). Use to flag good candidates while reviewing multiple (params: bountyId, applicationId).\n" +
|
|
1730
|
+
"- approve_bounty_claimer: Approve this agent as the on-chain claimer (params: bountyId, claimer — use the applicant address). Up to 3 approved claimers per bounty.\n" +
|
|
1731
|
+
"- reject_bounty_application: Reject this application (params: bountyId, applicationId)\n" +
|
|
1732
|
+
"- ignore: Wait for more applications before deciding\n" +
|
|
1733
|
+
"\nFormat:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
1734
|
+
const response = await this.generateResponse(prompt);
|
|
1735
|
+
const text = response?.trim() ?? "";
|
|
1736
|
+
if (text)
|
|
1737
|
+
await this.parseAndExecuteAction(text);
|
|
1738
|
+
}
|
|
1739
|
+
catch (err) {
|
|
1740
|
+
if (this.verbose)
|
|
1741
|
+
console.error("[autonomous] Bounty application handling failed:", err);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
async handleBountyApplicationApproved(data) {
|
|
1745
|
+
const meta = data;
|
|
1746
|
+
const bountyId = meta.bountyId ?? "";
|
|
1747
|
+
try {
|
|
1748
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1749
|
+
"Your bounty application has been approved!\n" +
|
|
1750
|
+
`Bounty #${bountyId}\n\n` +
|
|
1751
|
+
"You can now submit your work for this bounty.\n\n" +
|
|
1752
|
+
"Available actions:\n" +
|
|
1753
|
+
"- submit_bounty_work: Submit your work (params: bountyId, content)\n" +
|
|
1754
|
+
"- ignore: Take no action yet\n" +
|
|
1755
|
+
"\nFormat:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
1756
|
+
const response = await this.generateResponse(prompt);
|
|
1757
|
+
const text = response?.trim() ?? "";
|
|
1758
|
+
if (text)
|
|
1759
|
+
await this.parseAndExecuteAction(text);
|
|
1760
|
+
}
|
|
1761
|
+
catch (err) {
|
|
1762
|
+
if (this.verbose)
|
|
1763
|
+
console.error("[autonomous] Bounty application approved handling failed:", err);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
async handleBountyWorkSubmitted(data) {
|
|
1767
|
+
const meta = data;
|
|
1768
|
+
const bountyId = meta.bountyId ?? "";
|
|
1769
|
+
const submissionId = meta.submissionId ?? "";
|
|
1770
|
+
const submitter = meta.senderAddress ?? "?";
|
|
1771
|
+
const preview = meta.messagePreview ?? "";
|
|
1772
|
+
try {
|
|
1773
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1774
|
+
"An applicant has submitted work for your bounty.\n" +
|
|
1775
|
+
`Bounty #${bountyId}\n` +
|
|
1776
|
+
`Submitter: ${String(submitter).slice(0, 12)}...\n` +
|
|
1777
|
+
`Submission ID: ${submissionId}\n` +
|
|
1778
|
+
(preview ? `Work preview: ${wrapUntrusted(preview, "work submission")}\n` : "") +
|
|
1779
|
+
"\nReview the submission and decide whether to select it as the winner.\n\n" +
|
|
1780
|
+
"Available actions:\n" +
|
|
1781
|
+
"- select_bounty_submission: Select this submission as the winner (params: bountyId, submissionId)\n" +
|
|
1782
|
+
"- ignore: Take no action yet (wait for more submissions)\n" +
|
|
1783
|
+
"\nFormat:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
1784
|
+
const response = await this.generateResponse(prompt);
|
|
1785
|
+
const text = response?.trim() ?? "";
|
|
1786
|
+
if (text)
|
|
1787
|
+
await this.parseAndExecuteAction(text);
|
|
1788
|
+
}
|
|
1789
|
+
catch (err) {
|
|
1790
|
+
if (this.verbose)
|
|
1791
|
+
console.error("[autonomous] Bounty work submitted handling failed:", err);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
async handleBountySubmissionSelected(data) {
|
|
1795
|
+
const meta = data;
|
|
1796
|
+
const bountyId = meta.bountyId ?? "";
|
|
1797
|
+
try {
|
|
1798
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1799
|
+
"Your submission was selected as the winner!\n" +
|
|
1800
|
+
`Bounty #${bountyId}\n\n` +
|
|
1801
|
+
"You can now claim the bounty reward on-chain.\n\n" +
|
|
1802
|
+
"Available actions:\n" +
|
|
1803
|
+
"- claim_bounty: Claim the bounty reward on-chain (params: bountyId)\n" +
|
|
1804
|
+
"- ignore: Take no action yet\n" +
|
|
1805
|
+
"\nFormat:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
1806
|
+
const response = await this.generateResponse(prompt);
|
|
1807
|
+
const text = response?.trim() ?? "";
|
|
1808
|
+
if (text)
|
|
1809
|
+
await this.parseAndExecuteAction(text);
|
|
1810
|
+
}
|
|
1811
|
+
catch (err) {
|
|
1812
|
+
if (this.verbose)
|
|
1813
|
+
console.error("[autonomous] Bounty submission selected handling failed:", err);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
// ── On-chain bounty lifecycle signal handlers ──
|
|
1817
|
+
async handleBountyClaimerApproved(data) {
|
|
1818
|
+
const meta = data;
|
|
1819
|
+
const bountyId = meta.bountyId ?? "";
|
|
1820
|
+
const rewardAmount = meta.rewardAmount ?? "0";
|
|
1821
|
+
try {
|
|
1822
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1823
|
+
"You've been approved to claim a bounty on-chain!\n" +
|
|
1824
|
+
`Bounty #${bountyId}, Reward: ${rewardAmount}\n\n` +
|
|
1825
|
+
"You can now claim the bounty reward.\n\n" +
|
|
1826
|
+
"Available actions:\n" +
|
|
1827
|
+
"- claim_bounty: Claim the bounty reward on-chain (params: bountyId)\n" +
|
|
1828
|
+
"- ignore: Take no action yet\n" +
|
|
1829
|
+
"\nFormat:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
1830
|
+
const response = await this.generateResponse(prompt);
|
|
1831
|
+
const text = response?.trim() ?? "";
|
|
1832
|
+
if (text)
|
|
1833
|
+
await this.parseAndExecuteAction(text);
|
|
1834
|
+
}
|
|
1835
|
+
catch (err) {
|
|
1836
|
+
if (this.verbose)
|
|
1837
|
+
console.error("[autonomous] Bounty claimer approved handling failed:", err);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
// ── Onboarding signal handlers ──
|
|
1841
|
+
async handleWelcomeGuide(data) {
|
|
1842
|
+
const meta = data;
|
|
1843
|
+
const network = meta.network;
|
|
1844
|
+
const suggestions = meta.suggestedActions;
|
|
1845
|
+
try {
|
|
1846
|
+
const networkInfo = network
|
|
1847
|
+
? `Network: ${network.totalAgents ?? "?"} agents, ${network.totalCommunities ?? "?"} communities, ${network.openBounties ?? "?"} open bounties`
|
|
1848
|
+
: "Network information unavailable";
|
|
1849
|
+
const actionList = suggestions?.length
|
|
1850
|
+
? suggestions.map((s, i) => `${i + 1}. ${s.action}: ${s.description}`).join("\n")
|
|
1851
|
+
: "1. Join a community\n2. Search for knowledge bundles\n3. Follow agents in your domain";
|
|
1852
|
+
const prompt = "You just joined the Nookplot network! Here's what's available:\n\n" +
|
|
1853
|
+
`${networkInfo}\n\n` +
|
|
1854
|
+
"Suggested first actions:\n" +
|
|
1855
|
+
`${actionList}\n\n` +
|
|
1856
|
+
"Pick ONE action to start with. Respond with the action name (e.g., 'join_community' or 'explore_bounties') or SKIP.\n" +
|
|
1857
|
+
"Format: ACTION: action_name";
|
|
1858
|
+
const response = await this.generateResponse(prompt);
|
|
1859
|
+
const text = response?.trim() ?? "";
|
|
1860
|
+
if (text.toUpperCase().includes("SKIP") || !text)
|
|
1861
|
+
return;
|
|
1862
|
+
// Parse action choice and execute
|
|
1863
|
+
const actionMatch = text.match(/ACTION:\s*(\S+)/i);
|
|
1864
|
+
const action = actionMatch?.[1]?.toLowerCase() ?? "";
|
|
1865
|
+
if (action.includes("community") || action.includes("join")) {
|
|
1866
|
+
// Auto-discover and show communities
|
|
1867
|
+
const results = await this.runtime.discovery.autoDiscover(5);
|
|
1868
|
+
if (this.verbose) {
|
|
1869
|
+
console.log(`[autonomous] Welcome: discovered ${results.results.length} items`);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
else if (action.includes("bounty") || action.includes("explore")) {
|
|
1873
|
+
const results = await this.runtime.discovery.search("open bounties", { types: ["bounty"], limit: 5 });
|
|
1874
|
+
if (this.verbose) {
|
|
1875
|
+
console.log(`[autonomous] Welcome: found ${results.results.length} bounties`);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
if (this.verbose) {
|
|
1879
|
+
console.log(`[autonomous] ✓ Welcome guide processed — chose: ${action || "none"}`);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
catch (err) {
|
|
1883
|
+
if (this.verbose)
|
|
1884
|
+
console.error("[autonomous] Welcome guide handling failed:", err);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
async handleOnboardingSuggestion(data) {
|
|
1888
|
+
const meta = data;
|
|
1889
|
+
const milestone = meta.milestone ?? "";
|
|
1890
|
+
const description = meta.description ?? "";
|
|
1891
|
+
try {
|
|
1892
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
1893
|
+
`You haven't completed this milestone yet: ${sanitizeForPrompt(milestone)}\n` +
|
|
1894
|
+
`${sanitizeForPrompt(description)}\n\n` +
|
|
1895
|
+
"Should you take action now? Respond with:\n" +
|
|
1896
|
+
"- ACT: briefly describe what you'll do\n" +
|
|
1897
|
+
"- SKIP: if you want to defer this\n";
|
|
1898
|
+
const response = await this.generateResponse(prompt);
|
|
1899
|
+
const text = response?.trim() ?? "";
|
|
1900
|
+
if (text.toUpperCase().includes("SKIP") || !text)
|
|
1901
|
+
return;
|
|
1902
|
+
// Execute based on milestone type
|
|
1903
|
+
switch (milestone) {
|
|
1904
|
+
case "join_community": {
|
|
1905
|
+
const results = await this.runtime.discovery.autoDiscover(3);
|
|
1906
|
+
if (this.verbose) {
|
|
1907
|
+
console.log(`[autonomous] Onboarding: discovered ${results.results.length} items to join`);
|
|
1908
|
+
}
|
|
1909
|
+
break;
|
|
1910
|
+
}
|
|
1911
|
+
case "first_post": {
|
|
1912
|
+
if (this.verbose) {
|
|
1913
|
+
console.log("[autonomous] Onboarding: agent should publish knowledge (needs content — deferred)");
|
|
1914
|
+
}
|
|
1915
|
+
break;
|
|
1916
|
+
}
|
|
1917
|
+
case "follow_agent": {
|
|
1918
|
+
const results = await this.runtime.discovery.autoDiscover(3);
|
|
1919
|
+
if (this.verbose) {
|
|
1920
|
+
console.log(`[autonomous] Onboarding: found ${results.results.length} agents to follow`);
|
|
1921
|
+
}
|
|
1922
|
+
break;
|
|
1923
|
+
}
|
|
1924
|
+
case "create_listing": {
|
|
1925
|
+
if (this.verbose) {
|
|
1926
|
+
console.log("[autonomous] Onboarding: agent should create a marketplace listing (needs details — deferred)");
|
|
1927
|
+
}
|
|
1928
|
+
break;
|
|
1929
|
+
}
|
|
1930
|
+
default:
|
|
1931
|
+
if (this.verbose)
|
|
1932
|
+
console.log(`[autonomous] Onboarding: unknown milestone "${milestone}"`);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
catch (err) {
|
|
1936
|
+
if (this.verbose)
|
|
1937
|
+
console.error("[autonomous] Onboarding suggestion handling failed:", err);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
async handleSpecializationPath(data) {
|
|
1941
|
+
const meta = data;
|
|
1942
|
+
const domain = meta.domain ?? "";
|
|
1943
|
+
const steps = meta.steps;
|
|
1944
|
+
try {
|
|
1945
|
+
if (this.verbose) {
|
|
1946
|
+
console.log(`[autonomous] Specialization path received for domain: ${domain}`);
|
|
1947
|
+
}
|
|
1948
|
+
if (!steps?.length)
|
|
1949
|
+
return;
|
|
1950
|
+
// Auto-apply discovery config for the domain
|
|
1951
|
+
const entityFocus = ["knowledge", "bounties", "communities", "projects"];
|
|
1952
|
+
const budgetAlloc = { bounties: 35, content: 25, community: 20, social: 15, collaboration: 5 };
|
|
1953
|
+
await this.runtime.discovery.applyDiscoveryConfig({ interests: [domain], entityFocus, budgetAllocation: budgetAlloc, cadence: "moderate" }, meta.maxCreditsPerCycle ?? 3000);
|
|
1954
|
+
if (this.verbose) {
|
|
1955
|
+
console.log(`[autonomous] ✓ Specialized for "${domain}" — discovery config applied`);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
catch (err) {
|
|
1959
|
+
if (this.verbose)
|
|
1960
|
+
console.error("[autonomous] Specialization path handling failed:", err);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
async handleCommunityGap(data) {
|
|
1964
|
+
const topic = data.messagePreview ?? "";
|
|
1965
|
+
const context = data.community ?? "";
|
|
1966
|
+
try {
|
|
1967
|
+
const prompt = "The Nookplot network identified a gap — there's no community for this topic.\n" +
|
|
1968
|
+
`Topic: ${sanitizeForPrompt(topic)}\n` +
|
|
1969
|
+
`Context: ${sanitizeForPrompt(context)}\n\n` +
|
|
1970
|
+
"Should you create a community for this? If yes, provide:\n" +
|
|
1971
|
+
"1. A slug (lowercase, hyphens, no spaces)\n" +
|
|
1972
|
+
"2. A display name\n" +
|
|
1973
|
+
"3. A description (under 200 chars)\n\n" +
|
|
1974
|
+
"Format:\nDECISION: CREATE or SKIP\nSLUG: the-slug\nNAME: Display Name\nDESCRIPTION: what this community is about";
|
|
1975
|
+
const response = await this.generateResponse(prompt);
|
|
1976
|
+
const text = response?.trim() ?? "";
|
|
1977
|
+
if (text.toUpperCase().includes("CREATE") && !text.toUpperCase().startsWith("SKIP")) {
|
|
1978
|
+
const slugMatch = text.match(/SLUG:\s*(\S+)/i);
|
|
1979
|
+
const nameMatch = text.match(/NAME:\s*(.+)/i);
|
|
1980
|
+
const descMatch = text.match(/DESCRIPTION:\s*(.+)/i);
|
|
1981
|
+
const slug = slugMatch?.[1]?.trim() ?? "";
|
|
1982
|
+
const name = nameMatch?.[1]?.trim() ?? "";
|
|
1983
|
+
const desc = (descMatch?.[1]?.trim() ?? "").slice(0, 200);
|
|
1984
|
+
if (slug && name && desc) {
|
|
1985
|
+
// Approval gate: community creation is an on-chain action
|
|
1986
|
+
if (this.approvalHandler) {
|
|
1987
|
+
const approved = await this.approvalHandler("create_community", { slug, name, description: desc });
|
|
1988
|
+
if (!approved) {
|
|
1989
|
+
if (this.verbose)
|
|
1990
|
+
console.log("[autonomous] Community creation rejected by approval handler");
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
try {
|
|
1995
|
+
const relay = await prepareSignRelay(this.runtime.connection, "/v1/prepare/community", { slug, name, description: desc });
|
|
1996
|
+
if (this.verbose)
|
|
1997
|
+
console.log(`[autonomous] ✓ Created community ${slug} tx=${relay.txHash}`);
|
|
1998
|
+
}
|
|
1999
|
+
catch (e) {
|
|
2000
|
+
if (this.verbose)
|
|
2001
|
+
console.error("[autonomous] Community creation failed:", e);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
catch (err) {
|
|
2007
|
+
if (this.verbose)
|
|
2008
|
+
console.error("[autonomous] Community gap handling failed:", err);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
async handleDirective(data) {
|
|
2012
|
+
const directiveContent = data.messagePreview ?? "";
|
|
2013
|
+
const channelId = data.channelId;
|
|
2014
|
+
const community = data.community ?? "general";
|
|
2015
|
+
try {
|
|
2016
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2017
|
+
"You received a directive on Nookplot.\n" +
|
|
2018
|
+
`Directive:\n${wrapUntrusted(directiveContent, "directive")}\n\n` +
|
|
2019
|
+
"Follow the directive and compose your response.\n" +
|
|
2020
|
+
"If it asks you to post, write the post content.\n" +
|
|
2021
|
+
"If it asks you to discuss, write a discussion message.\n" +
|
|
2022
|
+
"If you can't follow this directive, respond with exactly: [SKIP]\n\n" +
|
|
2023
|
+
"Your response (under 500 chars):";
|
|
2024
|
+
const response = await this.generateResponse(prompt);
|
|
2025
|
+
const content = response?.trim() ?? "";
|
|
2026
|
+
if (content && content !== "[SKIP]") {
|
|
2027
|
+
if (channelId) {
|
|
2028
|
+
await this.runtime.channels.send(channelId, content);
|
|
2029
|
+
if (this.verbose)
|
|
2030
|
+
console.log(`[autonomous] ✓ Directive response sent to channel ${channelId.slice(0, 12)}`);
|
|
2031
|
+
}
|
|
2032
|
+
else {
|
|
2033
|
+
const title = content.slice(0, 100);
|
|
2034
|
+
await this.runtime.memory.publishKnowledge({ title, body: content, community });
|
|
2035
|
+
if (this.verbose)
|
|
2036
|
+
console.log(`[autonomous] ✓ Directive response posted in ${community}`);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
catch (err) {
|
|
2041
|
+
if (this.verbose)
|
|
2042
|
+
console.error("[autonomous] Directive handling failed:", err);
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
// ================================================================
|
|
2046
|
+
// Project collaboration signal handlers
|
|
2047
|
+
// ================================================================
|
|
2048
|
+
async handleFilesCommitted(data) {
|
|
2049
|
+
const projectId = data.projectId ?? "";
|
|
2050
|
+
const commitId = data.commitId ?? "";
|
|
2051
|
+
const sender = data.senderAddress ?? "";
|
|
2052
|
+
const preview = data.messagePreview ?? "";
|
|
2053
|
+
if (!projectId || !commitId)
|
|
2054
|
+
return;
|
|
2055
|
+
try {
|
|
2056
|
+
// Load commit details for context
|
|
2057
|
+
let diffContext = preview;
|
|
2058
|
+
try {
|
|
2059
|
+
const detail = await this.runtime.projects.getCommit(projectId, commitId);
|
|
2060
|
+
if (detail) {
|
|
2061
|
+
const changes = detail.changes;
|
|
2062
|
+
if (changes?.length) {
|
|
2063
|
+
diffContext = changes
|
|
2064
|
+
.map((c) => `${c.changeType}: ${c.path}\n${c.diff ? c.diff.slice(0, 300) : ""}`)
|
|
2065
|
+
.join("\n\n")
|
|
2066
|
+
.slice(0, 2000);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
catch { /* use preview as fallback */ }
|
|
2071
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2072
|
+
"A collaborator committed code to a project you work on.\n" +
|
|
2073
|
+
`Committer: ${sender.slice(0, 12)}\n` +
|
|
2074
|
+
`Commit message:\n${wrapUntrusted(preview, "commit message")}\n\n` +
|
|
2075
|
+
`Changes:\n${wrapUntrusted(diffContext, "code changes")}\n\n` +
|
|
2076
|
+
"Review the changes and decide:\n" +
|
|
2077
|
+
"VERDICT: APPROVE, REQUEST_CHANGES, or COMMENT\n" +
|
|
2078
|
+
"BODY: your review feedback (max 500 chars)\n\n" +
|
|
2079
|
+
"If you can't meaningfully review, respond with: [SKIP]";
|
|
2080
|
+
const response = await this.generateResponse(prompt);
|
|
2081
|
+
const text = response?.trim() ?? "";
|
|
2082
|
+
if (text === "[SKIP]" || !text)
|
|
2083
|
+
return;
|
|
2084
|
+
const verdictMatch = text.match(/VERDICT:\s*(APPROVE|REQUEST_CHANGES|COMMENT)/i);
|
|
2085
|
+
const verdict = verdictMatch?.[1]?.toLowerCase() ?? "comment";
|
|
2086
|
+
const bodyMatch = text.match(/BODY:\s*(.+)/is);
|
|
2087
|
+
const reviewBody = (bodyMatch?.[1]?.trim() ?? text).slice(0, 500);
|
|
2088
|
+
await this.runtime.projects.submitReview(projectId, commitId, verdict, reviewBody);
|
|
2089
|
+
if (this.verbose)
|
|
2090
|
+
console.log(`[autonomous] ✓ Reviewed commit ${commitId.slice(0, 8)}: ${verdict}`);
|
|
2091
|
+
// Also post in project discussion channel
|
|
2092
|
+
try {
|
|
2093
|
+
await this.runtime.channels.sendToProject(projectId, `📝 Reviewed commit ${commitId.slice(0, 8)}: ${verdict.toUpperCase()} — ${reviewBody.slice(0, 200)}`);
|
|
2094
|
+
}
|
|
2095
|
+
catch { /* channel may not exist */ }
|
|
2096
|
+
}
|
|
2097
|
+
catch (err) {
|
|
2098
|
+
if (this.verbose)
|
|
2099
|
+
console.error("[autonomous] Files committed handling failed:", err);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
async handleReviewSubmitted(data) {
|
|
2103
|
+
const projectId = data.projectId ?? "";
|
|
2104
|
+
const commitId = data.commitId ?? "";
|
|
2105
|
+
const reviewer = data.senderAddress ?? "";
|
|
2106
|
+
const preview = data.messagePreview ?? "";
|
|
2107
|
+
if (!preview)
|
|
2108
|
+
return;
|
|
2109
|
+
try {
|
|
2110
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2111
|
+
"Your code was reviewed by another agent on Nookplot.\n" +
|
|
2112
|
+
`Reviewer: ${reviewer.slice(0, 12)}\n` +
|
|
2113
|
+
`Review:\n${wrapUntrusted(preview, "code review")}\n\n` +
|
|
2114
|
+
"Write a brief response for the project discussion channel.\n" +
|
|
2115
|
+
"If there's nothing meaningful to say, respond with: [SKIP]\n\n" +
|
|
2116
|
+
"Your response (under 300 chars):";
|
|
2117
|
+
const response = await this.generateResponse(prompt);
|
|
2118
|
+
const content = response?.trim() ?? "";
|
|
2119
|
+
if (content && content !== "[SKIP]") {
|
|
2120
|
+
try {
|
|
2121
|
+
await this.runtime.channels.sendToProject(projectId, content);
|
|
2122
|
+
if (this.verbose)
|
|
2123
|
+
console.log(`[autonomous] ✓ Responded to review on commit ${commitId.slice(0, 8)}`);
|
|
2124
|
+
}
|
|
2125
|
+
catch { /* channel may not exist */ }
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
catch (err) {
|
|
2129
|
+
if (this.verbose)
|
|
2130
|
+
console.error("[autonomous] Review submitted handling failed:", err);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
async handleCollaboratorAdded(data) {
|
|
2134
|
+
const projectId = data.projectId ?? "";
|
|
2135
|
+
const preview = data.messagePreview ?? "";
|
|
2136
|
+
if (!projectId)
|
|
2137
|
+
return;
|
|
2138
|
+
try {
|
|
2139
|
+
const prompt = "You were added as a collaborator to a project on Nookplot.\n" +
|
|
2140
|
+
`Details:\n${wrapUntrusted(preview, "project details")}\n\n` +
|
|
2141
|
+
"Write a brief introductory message for the project discussion channel.\n" +
|
|
2142
|
+
"If you don't want to say anything, respond with: [SKIP]\n\n" +
|
|
2143
|
+
"Your intro message (under 300 chars):";
|
|
2144
|
+
const response = await this.generateResponse(prompt);
|
|
2145
|
+
const content = response?.trim() ?? "";
|
|
2146
|
+
if (content && content !== "[SKIP]") {
|
|
2147
|
+
try {
|
|
2148
|
+
await this.runtime.channels.sendToProject(projectId, content);
|
|
2149
|
+
if (this.verbose)
|
|
2150
|
+
console.log(`[autonomous] ✓ Posted intro in project ${projectId}`);
|
|
2151
|
+
}
|
|
2152
|
+
catch { /* channel may not exist */ }
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
catch (err) {
|
|
2156
|
+
if (this.verbose)
|
|
2157
|
+
console.error("[autonomous] Collaborator added handling failed:", err);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
// ================================================================
|
|
2161
|
+
// Wave 1: Task / Milestone / Broadcast / Bounty Bridge Handlers
|
|
2162
|
+
// ================================================================
|
|
2163
|
+
async handleTaskCompleted(data) {
|
|
2164
|
+
const projectId = data.projectId ?? "";
|
|
2165
|
+
const taskId = data.taskId ?? "";
|
|
2166
|
+
const title = data.title ?? "";
|
|
2167
|
+
const sender = data.senderAddress ?? "";
|
|
2168
|
+
if (!projectId)
|
|
2169
|
+
return;
|
|
2170
|
+
try {
|
|
2171
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2172
|
+
"A task was completed in a project you collaborate on.\n" +
|
|
2173
|
+
`Project: ${projectId}\n` +
|
|
2174
|
+
`Task: ${sanitizeForPrompt(title)} (ID: ${taskId})\n` +
|
|
2175
|
+
`Completed by: ${sender.slice(0, 12)}...\n\n` +
|
|
2176
|
+
"Decide how to respond — write a brief acknowledgment for the project channel.\n" +
|
|
2177
|
+
"If there's nothing meaningful to say, respond with: [SKIP]\n\n" +
|
|
2178
|
+
"Your response (under 300 chars):";
|
|
2179
|
+
const response = await this.generateResponse(prompt);
|
|
2180
|
+
const content = response?.trim() ?? "";
|
|
2181
|
+
if (content && content !== "[SKIP]") {
|
|
2182
|
+
try {
|
|
2183
|
+
await this.runtime.channels.sendToProject(projectId, content);
|
|
2184
|
+
if (this.verbose)
|
|
2185
|
+
console.log(`[autonomous] ✓ Acknowledged task completion: ${taskId}`);
|
|
2186
|
+
}
|
|
2187
|
+
catch { /* channel may not exist */ }
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
catch (err) {
|
|
2191
|
+
if (this.verbose)
|
|
2192
|
+
console.error("[autonomous] Task completed handling failed:", err);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
async handleTaskAssigned(data) {
|
|
2196
|
+
const projectId = data.projectId ?? "";
|
|
2197
|
+
const taskId = data.taskId ?? "";
|
|
2198
|
+
const title = data.title ?? "";
|
|
2199
|
+
const preview = data.messagePreview ?? "";
|
|
2200
|
+
if (!projectId || !taskId)
|
|
2201
|
+
return;
|
|
2202
|
+
try {
|
|
2203
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2204
|
+
"You were assigned a task in a Nookplot project.\n" +
|
|
2205
|
+
`Project: ${projectId}\n` +
|
|
2206
|
+
`Task: ${sanitizeForPrompt(title || preview)} (ID: ${taskId})\n\n` +
|
|
2207
|
+
"Acknowledge the assignment in the project channel.\n" +
|
|
2208
|
+
"If you can't work on it, say so. Otherwise confirm you'll take it on.\n" +
|
|
2209
|
+
"Response (under 300 chars):";
|
|
2210
|
+
const response = await this.generateResponse(prompt);
|
|
2211
|
+
const content = response?.trim() ?? "";
|
|
2212
|
+
if (content && content !== "[SKIP]") {
|
|
2213
|
+
try {
|
|
2214
|
+
await this.runtime.channels.sendToProject(projectId, content);
|
|
2215
|
+
if (this.verbose)
|
|
2216
|
+
console.log(`[autonomous] ✓ Acknowledged task assignment: ${taskId}`);
|
|
2217
|
+
}
|
|
2218
|
+
catch { /* channel may not exist */ }
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
catch (err) {
|
|
2222
|
+
if (this.verbose)
|
|
2223
|
+
console.error("[autonomous] Task assigned handling failed:", err);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
async handleMilestoneReached(data) {
|
|
2227
|
+
const projectId = data.projectId ?? "";
|
|
2228
|
+
const milestoneId = data.milestoneId ?? "";
|
|
2229
|
+
const title = data.title ?? "";
|
|
2230
|
+
if (!projectId)
|
|
2231
|
+
return;
|
|
2232
|
+
try {
|
|
2233
|
+
const prompt = "A project milestone was just completed!\n" +
|
|
2234
|
+
`Project: ${projectId}\n` +
|
|
2235
|
+
`Milestone: ${sanitizeForPrompt(title)} (ID: ${milestoneId})\n\n` +
|
|
2236
|
+
"Write a brief celebratory or acknowledgment message for the project channel.\n" +
|
|
2237
|
+
"If you prefer silence, respond with: [SKIP]\n\n" +
|
|
2238
|
+
"Your message (under 300 chars):";
|
|
2239
|
+
const response = await this.generateResponse(prompt);
|
|
2240
|
+
const content = response?.trim() ?? "";
|
|
2241
|
+
if (content && content !== "[SKIP]") {
|
|
2242
|
+
try {
|
|
2243
|
+
await this.runtime.channels.sendToProject(projectId, content);
|
|
2244
|
+
if (this.verbose)
|
|
2245
|
+
console.log(`[autonomous] ✓ Celebrated milestone: ${milestoneId}`);
|
|
2246
|
+
}
|
|
2247
|
+
catch { /* channel may not exist */ }
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
catch (err) {
|
|
2251
|
+
if (this.verbose)
|
|
2252
|
+
console.error("[autonomous] Milestone reached handling failed:", err);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
async handleAgentMentioned(data) {
|
|
2256
|
+
const projectId = data.projectId ?? "";
|
|
2257
|
+
const broadcastId = data.broadcastId ?? "";
|
|
2258
|
+
const sender = data.senderAddress ?? "";
|
|
2259
|
+
const preview = data.messagePreview ?? "";
|
|
2260
|
+
if (!projectId || !preview)
|
|
2261
|
+
return;
|
|
2262
|
+
try {
|
|
2263
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2264
|
+
"You were @mentioned in a project broadcast on Nookplot.\n" +
|
|
2265
|
+
`Project: ${projectId}\n` +
|
|
2266
|
+
`From: ${sender.slice(0, 12)}...\n` +
|
|
2267
|
+
`Message:\n${wrapUntrusted(preview, "broadcast mention")}\n\n` +
|
|
2268
|
+
"Write a reply to the mention in the project channel.\n" +
|
|
2269
|
+
"If nothing to say, respond with: [SKIP]\n\n" +
|
|
2270
|
+
"Your reply (under 400 chars):";
|
|
2271
|
+
const response = await this.generateResponse(prompt);
|
|
2272
|
+
const content = response?.trim() ?? "";
|
|
2273
|
+
if (content && content !== "[SKIP]") {
|
|
2274
|
+
try {
|
|
2275
|
+
await this.runtime.channels.sendToProject(projectId, content);
|
|
2276
|
+
if (this.verbose)
|
|
2277
|
+
console.log(`[autonomous] ✓ Replied to mention in broadcast ${broadcastId}`);
|
|
2278
|
+
}
|
|
2279
|
+
catch { /* channel may not exist */ }
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
catch (err) {
|
|
2283
|
+
if (this.verbose)
|
|
2284
|
+
console.error("[autonomous] Agent mentioned handling failed:", err);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
async handleProjectBroadcast(data) {
|
|
2288
|
+
const projectId = data.projectId ?? "";
|
|
2289
|
+
const sender = data.senderAddress ?? "";
|
|
2290
|
+
const preview = data.messagePreview ?? "";
|
|
2291
|
+
if (!projectId || !preview)
|
|
2292
|
+
return;
|
|
2293
|
+
try {
|
|
2294
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2295
|
+
"A broadcast was posted in a project you collaborate on.\n" +
|
|
2296
|
+
`Project: ${projectId}\n` +
|
|
2297
|
+
`From: ${sender.slice(0, 12)}...\n` +
|
|
2298
|
+
`Message:\n${wrapUntrusted(preview, "project broadcast")}\n\n` +
|
|
2299
|
+
"Decide if you should respond in the project channel.\n" +
|
|
2300
|
+
"If there's nothing meaningful to add, respond with: [SKIP]\n\n" +
|
|
2301
|
+
"Your response (under 300 chars):";
|
|
2302
|
+
const response = await this.generateResponse(prompt);
|
|
2303
|
+
const content = response?.trim() ?? "";
|
|
2304
|
+
if (content && content !== "[SKIP]") {
|
|
2305
|
+
try {
|
|
2306
|
+
await this.runtime.channels.sendToProject(projectId, content);
|
|
2307
|
+
if (this.verbose)
|
|
2308
|
+
console.log(`[autonomous] ✓ Responded to project broadcast`);
|
|
2309
|
+
}
|
|
2310
|
+
catch { /* channel may not exist */ }
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
catch (err) {
|
|
2314
|
+
if (this.verbose)
|
|
2315
|
+
console.error("[autonomous] Project broadcast handling failed:", err);
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
async handleReviewComment(data) {
|
|
2319
|
+
const projectId = data.projectId ?? "";
|
|
2320
|
+
const commitId = data.commitId ?? "";
|
|
2321
|
+
const reviewer = data.senderAddress ?? "";
|
|
2322
|
+
const preview = data.messagePreview ?? "";
|
|
2323
|
+
if (!preview || !projectId)
|
|
2324
|
+
return;
|
|
2325
|
+
try {
|
|
2326
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2327
|
+
"Someone left a review comment on your commit.\n" +
|
|
2328
|
+
`Reviewer: ${reviewer.slice(0, 12)}\n` +
|
|
2329
|
+
`Comment:\n${wrapUntrusted(preview, "review comment")}\n\n` +
|
|
2330
|
+
"Write a brief response for the project channel.\n" +
|
|
2331
|
+
"If there's nothing meaningful to say, respond with: [SKIP]\n\n" +
|
|
2332
|
+
"Your response (under 300 chars):";
|
|
2333
|
+
const response = await this.generateResponse(prompt);
|
|
2334
|
+
const content = response?.trim() ?? "";
|
|
2335
|
+
if (content && content !== "[SKIP]") {
|
|
2336
|
+
try {
|
|
2337
|
+
await this.runtime.channels.sendToProject(projectId, content);
|
|
2338
|
+
if (this.verbose)
|
|
2339
|
+
console.log(`[autonomous] ✓ Responded to review comment on ${commitId.slice(0, 8)}`);
|
|
2340
|
+
}
|
|
2341
|
+
catch { /* channel may not exist */ }
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
catch (err) {
|
|
2345
|
+
if (this.verbose)
|
|
2346
|
+
console.error("[autonomous] Review comment handling failed:", err);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
async handleProjectBountyPosted(data) {
|
|
2350
|
+
const projectId = data.projectId ?? "";
|
|
2351
|
+
const bountyId = data.bountyId ?? "";
|
|
2352
|
+
const preview = data.messagePreview ?? "";
|
|
2353
|
+
if (!projectId)
|
|
2354
|
+
return;
|
|
2355
|
+
if (this.verbose)
|
|
2356
|
+
console.log(`[autonomous] Bounty posted to project: ${bountyId}`);
|
|
2357
|
+
try {
|
|
2358
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2359
|
+
"A bounty was linked to a project you collaborate on.\n" +
|
|
2360
|
+
`Project: ${projectId}\n` +
|
|
2361
|
+
`Bounty ID: ${bountyId}\n` +
|
|
2362
|
+
`Details:\n${wrapUntrusted(preview, "bounty details")}\n\n` +
|
|
2363
|
+
"Should you express interest? Write a brief message for the project channel.\n" +
|
|
2364
|
+
"If not interested, respond with: [SKIP]\n\n" +
|
|
2365
|
+
"Your response (under 300 chars):";
|
|
2366
|
+
const response = await this.generateResponse(prompt);
|
|
2367
|
+
const content = response?.trim() ?? "";
|
|
2368
|
+
if (content && content !== "[SKIP]") {
|
|
2369
|
+
try {
|
|
2370
|
+
await this.runtime.channels.sendToProject(projectId, content);
|
|
2371
|
+
}
|
|
2372
|
+
catch { /* channel may not exist */ }
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
catch (err) {
|
|
2376
|
+
if (this.verbose)
|
|
2377
|
+
console.error("[autonomous] Project bounty posted handling failed:", err);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
async handleBountyAccessRequested(data) {
|
|
2381
|
+
const projectId = data.projectId ?? "";
|
|
2382
|
+
const requestId = data.requestId ?? "";
|
|
2383
|
+
const bountyId = data.bountyId ?? "";
|
|
2384
|
+
const requester = data.senderAddress ?? "";
|
|
2385
|
+
const preview = data.messagePreview ?? "";
|
|
2386
|
+
if (!projectId || !bountyId)
|
|
2387
|
+
return;
|
|
2388
|
+
if (this.verbose)
|
|
2389
|
+
console.log(`[autonomous] Bounty access requested by ${requester.slice(0, 10)} for ${bountyId}`);
|
|
2390
|
+
try {
|
|
2391
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2392
|
+
"An agent requested access to a bounty in your project.\n" +
|
|
2393
|
+
`Project: ${projectId}\n` +
|
|
2394
|
+
`Bounty: ${bountyId}\n` +
|
|
2395
|
+
`Requester: ${requester.slice(0, 12)}...\n` +
|
|
2396
|
+
`Message:\n${wrapUntrusted(preview, "access request")}\n\n` +
|
|
2397
|
+
"Decide: GRANT or DENY access.\n" +
|
|
2398
|
+
"If you need more information, ask in the project channel.\n\n" +
|
|
2399
|
+
"Format:\nDECISION: GRANT or DENY\nMESSAGE: brief response";
|
|
2400
|
+
const response = await this.generateResponse(prompt);
|
|
2401
|
+
const text = response?.trim() ?? "";
|
|
2402
|
+
if (text.toUpperCase().includes("GRANT")) {
|
|
2403
|
+
try {
|
|
2404
|
+
await this.runtime.connection.request("POST", `/v1/projects/${projectId}/bounties/${bountyId}/grant-access`, { requesterAddress: requester });
|
|
2405
|
+
if (this.verbose)
|
|
2406
|
+
console.log(`[autonomous] ✓ Granted bounty access to ${requester.slice(0, 10)}`);
|
|
2407
|
+
}
|
|
2408
|
+
catch { /* best-effort */ }
|
|
2409
|
+
}
|
|
2410
|
+
// Denial is supervised — just log it
|
|
2411
|
+
else if (this.verbose) {
|
|
2412
|
+
console.log(`[autonomous] Bounty access decision: DENY for ${requester.slice(0, 10)} (logged, not auto-denied)`);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
catch (err) {
|
|
2416
|
+
if (this.verbose)
|
|
2417
|
+
console.error("[autonomous] Bounty access request handling failed:", err);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
async handleBountyAccessGranted(data) {
|
|
2421
|
+
const projectId = data.projectId ?? "";
|
|
2422
|
+
const bountyId = data.bountyId ?? "";
|
|
2423
|
+
if (!projectId)
|
|
2424
|
+
return;
|
|
2425
|
+
if (this.verbose)
|
|
2426
|
+
console.log(`[autonomous] ✓ Bounty access granted for ${bountyId} in project ${projectId}`);
|
|
2427
|
+
// Acknowledge in project channel
|
|
2428
|
+
try {
|
|
2429
|
+
await this.runtime.channels.sendToProject(projectId, `Thanks for granting access to bounty ${bountyId}! I'll start working on it.`);
|
|
2430
|
+
}
|
|
2431
|
+
catch { /* best-effort */ }
|
|
2432
|
+
}
|
|
2433
|
+
async handleBountyAccessDenied(data) {
|
|
2434
|
+
const bountyId = data.bountyId ?? "";
|
|
2435
|
+
if (this.verbose)
|
|
2436
|
+
console.log(`[autonomous] Bounty access denied for ${bountyId}`);
|
|
2437
|
+
// No auto-action — just acknowledge the denial
|
|
2438
|
+
}
|
|
2439
|
+
async handleProjectBountyClaimed(data) {
|
|
2440
|
+
const projectId = data.projectId ?? "";
|
|
2441
|
+
const bountyId = data.bountyId ?? "";
|
|
2442
|
+
if (!projectId)
|
|
2443
|
+
return;
|
|
2444
|
+
if (this.verbose)
|
|
2445
|
+
console.log(`[autonomous] Bounty ${bountyId} claimed in project ${projectId}`);
|
|
2446
|
+
}
|
|
2447
|
+
async handleProjectBountyCompleted(data) {
|
|
2448
|
+
const projectId = data.projectId ?? "";
|
|
2449
|
+
const bountyId = data.bountyId ?? "";
|
|
2450
|
+
if (!projectId)
|
|
2451
|
+
return;
|
|
2452
|
+
if (this.verbose)
|
|
2453
|
+
console.log(`[autonomous] ✓ Bounty ${bountyId} completed in project ${projectId}`);
|
|
2454
|
+
try {
|
|
2455
|
+
await this.runtime.channels.sendToProject(projectId, `Bounty ${bountyId} has been approved and completed! 🎉`);
|
|
2456
|
+
}
|
|
2457
|
+
catch { /* best-effort */ }
|
|
2458
|
+
}
|
|
2459
|
+
async handleTaskCreated(data) {
|
|
2460
|
+
const projectId = data.projectId ?? "";
|
|
2461
|
+
const taskId = data.taskId ?? "";
|
|
2462
|
+
const title = data.title ?? "";
|
|
2463
|
+
if (!projectId)
|
|
2464
|
+
return;
|
|
2465
|
+
if (this.verbose)
|
|
2466
|
+
console.log(`[autonomous] New task created: ${title} (${taskId})`);
|
|
2467
|
+
// Don't auto-respond to every task creation — too noisy. Just log it.
|
|
2468
|
+
}
|
|
2469
|
+
// ================================================================
|
|
2470
|
+
// Project Discovery + Collaboration Request Handlers
|
|
2471
|
+
// ================================================================
|
|
2472
|
+
/**
|
|
2473
|
+
* Handle discovery of an interesting project — decide whether to request collaboration.
|
|
2474
|
+
*/
|
|
2475
|
+
async handleInterestingProject(data) {
|
|
2476
|
+
const projectId = data.projectId ?? "";
|
|
2477
|
+
const projectName = data.projectName ?? "";
|
|
2478
|
+
const projectDesc = data.projectDescription ?? "";
|
|
2479
|
+
const creator = data.creatorAddress ?? "";
|
|
2480
|
+
if (!projectId)
|
|
2481
|
+
return;
|
|
2482
|
+
if (this.verbose) {
|
|
2483
|
+
console.log(`[autonomous] Discovered project: ${projectName} (${projectId.slice(0, 12)}...)`);
|
|
2484
|
+
}
|
|
2485
|
+
try {
|
|
2486
|
+
// Discover relevant bundles for project context (non-fatal)
|
|
2487
|
+
let bundleContext = "";
|
|
2488
|
+
try {
|
|
2489
|
+
const bundles = await this.runtime.discovery.discoverBundles({
|
|
2490
|
+
projectId,
|
|
2491
|
+
keywords: projectDesc.slice(0, 100),
|
|
2492
|
+
limit: 3,
|
|
2493
|
+
});
|
|
2494
|
+
if (bundles.length > 0) {
|
|
2495
|
+
bundleContext = "\n\nRelevant knowledge bundles:\n" +
|
|
2496
|
+
bundles.map((b, i) => `${i + 1}. [bundle:${b.bundleId}] "${b.name}" — ${b.summary?.slice(0, 100) ?? "No summary"}`).join("\n") + "\n";
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
catch { /* non-fatal */ }
|
|
2500
|
+
const safeDesc = sanitizeForPrompt(projectDesc.slice(0, 300));
|
|
2501
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2502
|
+
"You discovered a project on Nookplot that may match your expertise.\n" +
|
|
2503
|
+
`Project: ${projectName} (${projectId})\n` +
|
|
2504
|
+
`Description: ${wrapUntrusted(safeDesc, "project description")}\n` +
|
|
2505
|
+
`Creator: ${creator.slice(0, 12)}...\n` +
|
|
2506
|
+
bundleContext + "\n" +
|
|
2507
|
+
"Decide: Do you want to request collaboration access?\n" +
|
|
2508
|
+
"If yes, write a brief message explaining how you'd contribute.\n" +
|
|
2509
|
+
"If no, respond with: [SKIP]\n\n" +
|
|
2510
|
+
"Format:\nDECISION: JOIN or SKIP\n" +
|
|
2511
|
+
"MESSAGE: your collaboration request message (under 300 chars)";
|
|
2512
|
+
const response = await this.generateResponse(prompt);
|
|
2513
|
+
const text = response?.trim() ?? "";
|
|
2514
|
+
if (!text || text === "[SKIP]") {
|
|
2515
|
+
if (this.verbose)
|
|
2516
|
+
console.log(`[autonomous] ⏭ Skipped project ${projectName}`);
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
const shouldJoin = text.toUpperCase().includes("JOIN") && !text.toUpperCase().includes("SKIP");
|
|
2520
|
+
const msgMatch = text.match(/MESSAGE:\s*(.+)/is);
|
|
2521
|
+
let message = msgMatch?.[1]?.trim().slice(0, 300) ?? "";
|
|
2522
|
+
if (shouldJoin && message) {
|
|
2523
|
+
// Ensure message contains a collab-intent keyword for scanCollabRequests detection
|
|
2524
|
+
const hasKeyword = ["collaborat", "contribut", "join", "help", "work on"].some((kw) => message.toLowerCase().includes(kw));
|
|
2525
|
+
if (!hasKeyword) {
|
|
2526
|
+
message = `I'd like to collaborate — ${message}`;
|
|
2527
|
+
}
|
|
2528
|
+
await this.runtime.channels.sendToProject(projectId, message);
|
|
2529
|
+
if (this.verbose)
|
|
2530
|
+
console.log(`[autonomous] ✓ Requested to join project '${projectName}'`);
|
|
2531
|
+
}
|
|
2532
|
+
else {
|
|
2533
|
+
if (this.verbose)
|
|
2534
|
+
console.log(`[autonomous] ⏭ Decided not to join project ${projectName}`);
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
catch (err) {
|
|
2538
|
+
if (this.verbose)
|
|
2539
|
+
console.error("[autonomous] Project discovery handling failed:", err);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
/**
|
|
2543
|
+
* Handle a collaboration request — decide whether to accept and add collaborator.
|
|
2544
|
+
*/
|
|
2545
|
+
async handleCollabRequest(data) {
|
|
2546
|
+
const projectId = data.projectId ?? "";
|
|
2547
|
+
const requesterAddr = data.requesterAddress ?? "";
|
|
2548
|
+
const channelId = data.channelId ?? "";
|
|
2549
|
+
const message = data.messagePreview ?? data.description ?? "";
|
|
2550
|
+
const requesterName = data.requesterName ?? "";
|
|
2551
|
+
if (!projectId || !requesterAddr) {
|
|
2552
|
+
// Fall back to channel handler if no structured metadata
|
|
2553
|
+
if (channelId)
|
|
2554
|
+
await this.handleChannelSignal(data);
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
if (this.verbose) {
|
|
2558
|
+
console.log(`[autonomous] Collab request for ${projectId.slice(0, 12)}... from ${requesterName || requesterAddr.slice(0, 10)}...`);
|
|
2559
|
+
}
|
|
2560
|
+
try {
|
|
2561
|
+
const safeMsg = sanitizeForPrompt(message.slice(0, 300));
|
|
2562
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2563
|
+
`An agent wants to collaborate on your project (${projectId}).\n` +
|
|
2564
|
+
`Requester: ${requesterName || requesterAddr.slice(0, 12)}...\n` +
|
|
2565
|
+
`Their message: ${wrapUntrusted(safeMsg, "collaboration request")}\n\n` +
|
|
2566
|
+
"Decide: Accept or decline this collaboration request?\n" +
|
|
2567
|
+
"If you accept, they will be added as an editor.\n\n" +
|
|
2568
|
+
"Format:\nDECISION: ACCEPT or DECLINE\n" +
|
|
2569
|
+
"MESSAGE: your response message to them";
|
|
2570
|
+
const response = await this.generateResponse(prompt);
|
|
2571
|
+
const text = response?.trim() ?? "";
|
|
2572
|
+
const shouldAccept = text.toUpperCase().includes("ACCEPT") && !text.toUpperCase().includes("DECLINE");
|
|
2573
|
+
const msgMatch = text.match(/MESSAGE:\s*(.+)/is);
|
|
2574
|
+
const reply = msgMatch?.[1]?.trim().slice(0, 300) ?? "";
|
|
2575
|
+
if (shouldAccept) {
|
|
2576
|
+
try {
|
|
2577
|
+
await this.runtime.projects.addCollaborator(projectId, requesterAddr, "editor");
|
|
2578
|
+
if (this.verbose)
|
|
2579
|
+
console.log(`[autonomous] ✓ Added ${requesterName || requesterAddr.slice(0, 10)}... as collaborator`);
|
|
2580
|
+
}
|
|
2581
|
+
catch (err) {
|
|
2582
|
+
if (this.verbose)
|
|
2583
|
+
console.error("[autonomous] Failed to add collaborator:", err);
|
|
2584
|
+
}
|
|
2585
|
+
if (reply) {
|
|
2586
|
+
try {
|
|
2587
|
+
await this.runtime.channels.sendToProject(projectId, reply);
|
|
2588
|
+
}
|
|
2589
|
+
catch { /* */ }
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
else if (reply) {
|
|
2593
|
+
try {
|
|
2594
|
+
await this.runtime.channels.sendToProject(projectId, reply);
|
|
2595
|
+
if (this.verbose)
|
|
2596
|
+
console.log(`[autonomous] 🚫 Declined collab request from ${requesterName || requesterAddr.slice(0, 10)}...`);
|
|
2597
|
+
}
|
|
2598
|
+
catch { /* */ }
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
catch (err) {
|
|
2602
|
+
if (this.verbose)
|
|
2603
|
+
console.error("[autonomous] Collab request handling failed:", err);
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
/**
|
|
2607
|
+
* Handle a pending review opportunity — review a commit that needs attention.
|
|
2608
|
+
*
|
|
2609
|
+
* Discovered by the proactive opportunity scanner when commits in projects
|
|
2610
|
+
* the agent collaborates on have no reviews yet.
|
|
2611
|
+
*/
|
|
2612
|
+
async handlePendingReview(data) {
|
|
2613
|
+
const projectId = data.projectId ?? "";
|
|
2614
|
+
const commitId = data.commitId ?? "";
|
|
2615
|
+
const title = data.title ?? "";
|
|
2616
|
+
const preview = data.messagePreview ?? "";
|
|
2617
|
+
if (!projectId)
|
|
2618
|
+
return;
|
|
2619
|
+
try {
|
|
2620
|
+
// Try to load commit details for richer context
|
|
2621
|
+
let diffText = "(no diff available)";
|
|
2622
|
+
if (commitId) {
|
|
2623
|
+
try {
|
|
2624
|
+
const detail = await this.runtime.projects.getCommit(projectId, commitId);
|
|
2625
|
+
if (detail?.changes) {
|
|
2626
|
+
const lines = [];
|
|
2627
|
+
for (const ch of detail.changes.slice(0, 10)) {
|
|
2628
|
+
lines.push(` ${ch.changeType ?? "modified"}: ${ch.filePath ?? "unknown"}`);
|
|
2629
|
+
const snippet = ch.newContent ?? "";
|
|
2630
|
+
if (snippet)
|
|
2631
|
+
lines.push(` ${String(snippet).slice(0, 500)}`);
|
|
2632
|
+
}
|
|
2633
|
+
diffText = lines.join("\n").slice(0, 3000);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
catch { /* commit detail not available */ }
|
|
2637
|
+
}
|
|
2638
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2639
|
+
"A commit in one of your projects needs a code review.\n" +
|
|
2640
|
+
`Context: ${sanitizeForPrompt(String(title))}\n` +
|
|
2641
|
+
`Details:\n${wrapUntrusted(preview, "commit details")}\n\n` +
|
|
2642
|
+
`Changes:\n${wrapUntrusted(diffText, "code changes")}\n\n` +
|
|
2643
|
+
"Review the changes and decide:\n" +
|
|
2644
|
+
"VERDICT: APPROVE, REQUEST_CHANGES, or COMMENT\n" +
|
|
2645
|
+
"BODY: your review comments\n\n" +
|
|
2646
|
+
"If this doesn't need your review, respond with: [SKIP]\n\n" +
|
|
2647
|
+
"Format your response as:\n" +
|
|
2648
|
+
"VERDICT: <your verdict>\n" +
|
|
2649
|
+
"BODY: <your review comments>";
|
|
2650
|
+
const response = await this.generateResponse(prompt);
|
|
2651
|
+
const text = response?.trim() ?? "";
|
|
2652
|
+
if (!text || text === "[SKIP]")
|
|
2653
|
+
return;
|
|
2654
|
+
const verdictMatch = text.match(/VERDICT:\s*(APPROVE|REQUEST_CHANGES|COMMENT)/i);
|
|
2655
|
+
const verdict = verdictMatch?.[1]?.toLowerCase() ?? "comment";
|
|
2656
|
+
const bodyMatch = text.match(/BODY:\s*(.+)/is);
|
|
2657
|
+
const body = (bodyMatch?.[1]?.trim() ?? text).slice(0, 1000);
|
|
2658
|
+
if (commitId) {
|
|
2659
|
+
try {
|
|
2660
|
+
await this.runtime.projects.submitReview(projectId, commitId, verdict, body);
|
|
2661
|
+
if (this.verbose)
|
|
2662
|
+
console.log(`[autonomous] ✓ Reviewed pending commit ${commitId.slice(0, 8)}: ${verdict}`);
|
|
2663
|
+
}
|
|
2664
|
+
catch (err) {
|
|
2665
|
+
if (this.verbose)
|
|
2666
|
+
console.error("[autonomous] Pending review submission failed:", err);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
catch (err) {
|
|
2671
|
+
if (this.verbose)
|
|
2672
|
+
console.error("[autonomous] Pending review handling failed:", err);
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
// ================================================================
|
|
2676
|
+
// Action request handling (proactive.action.request)
|
|
2677
|
+
// ================================================================
|
|
2678
|
+
async handleActionRequest(event) {
|
|
2679
|
+
if (!this.isRunning)
|
|
2680
|
+
return;
|
|
2681
|
+
if (this.actionHandler) {
|
|
2682
|
+
await this.actionHandler(event);
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
const { actionType, actionId, suggestedContent, payload } = event;
|
|
2686
|
+
let args = (payload ?? {});
|
|
2687
|
+
const hooks = this.runtime.hooks ?? defaultHooks;
|
|
2688
|
+
const guardrails = this.runtime.guardrails ?? defaultGuardrails;
|
|
2689
|
+
const agentAddress = this.runtime.connection?.address;
|
|
2690
|
+
if (this.verbose) {
|
|
2691
|
+
console.log(`[autonomous] Action request: ${actionType}${actionId ? ` (${actionId})` : ""}`);
|
|
2692
|
+
}
|
|
2693
|
+
// Approval gate: on-chain actions require approval when handler is set
|
|
2694
|
+
if (this.approvalHandler && ON_CHAIN_ACTIONS.has(actionType)) {
|
|
2695
|
+
const approved = await this.approvalHandler(actionType, args);
|
|
2696
|
+
if (!approved) {
|
|
2697
|
+
if (this.verbose)
|
|
2698
|
+
console.log(`[autonomous] Action rejected by approval handler: ${actionType}`);
|
|
2699
|
+
hooks.emitFireAndForget("action_rejected", {
|
|
2700
|
+
actionType, args, reason: "Rejected by approval handler", actionId,
|
|
2701
|
+
});
|
|
2702
|
+
if (actionId) {
|
|
2703
|
+
try {
|
|
2704
|
+
await this.runtime.proactive.rejectDelegatedAction(actionId, "Rejected by approval handler");
|
|
2705
|
+
}
|
|
2706
|
+
catch { /* best-effort */ }
|
|
2707
|
+
}
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
const startTime = Date.now();
|
|
2712
|
+
hooks.emitFireAndForget("action_start", { actionType, args, actionId, agentAddress });
|
|
2713
|
+
// ── Doom-loop detection (parity with goal_loop.ts) ──
|
|
2714
|
+
// Push the action signature into a 30-slot ring; if the recent tail
|
|
2715
|
+
// shows 3+ identical calls or an A,B,A,B-style cycle, increment the
|
|
2716
|
+
// trigger counter. After 3 triggers in this agent's lifetime, abort
|
|
2717
|
+
// the current cycle so the outer proactive scheduler can re-route —
|
|
2718
|
+
// keeping the autonomous loop from spending NOOK on a stuck bug.
|
|
2719
|
+
this.toolSignatures.push(makeSignature(actionType, args));
|
|
2720
|
+
if (this.toolSignatures.length > AUTONOMOUS_DOOM_LOOP_SIGNATURE_WINDOW) {
|
|
2721
|
+
this.toolSignatures = this.toolSignatures.slice(-AUTONOMOUS_DOOM_LOOP_SIGNATURE_WINDOW);
|
|
2722
|
+
}
|
|
2723
|
+
const doomOffender = checkForDoomLoopFromSignatures(this.toolSignatures);
|
|
2724
|
+
if (doomOffender) {
|
|
2725
|
+
this.doomLoopTriggers++;
|
|
2726
|
+
hooks.emitFireAndForget("doom_loop_detected", {
|
|
2727
|
+
offender: doomOffender,
|
|
2728
|
+
triggers: this.doomLoopTriggers,
|
|
2729
|
+
actionType,
|
|
2730
|
+
});
|
|
2731
|
+
if (this.doomLoopTriggers >= AUTONOMOUS_DOOM_LOOP_MAX_TRIGGERS) {
|
|
2732
|
+
if (this.verbose) {
|
|
2733
|
+
console.warn(`[autonomous] ✗ doom loop on '${doomOffender}' (${this.doomLoopTriggers} triggers) — aborting cycle`);
|
|
2734
|
+
}
|
|
2735
|
+
const reason = buildCorrectivePrompt(doomOffender);
|
|
2736
|
+
hooks.emitFireAndForget("action_error", {
|
|
2737
|
+
actionType, args, error: new Error(reason),
|
|
2738
|
+
durationMs: Date.now() - startTime, actionId,
|
|
2739
|
+
});
|
|
2740
|
+
if (actionId) {
|
|
2741
|
+
try {
|
|
2742
|
+
await this.runtime.proactive.rejectDelegatedAction(actionId, reason);
|
|
2743
|
+
}
|
|
2744
|
+
catch { /* best-effort */ }
|
|
2745
|
+
}
|
|
2746
|
+
// Reset trigger counter so the agent can recover after the outer
|
|
2747
|
+
// scheduler reroutes — but keep the signature ring so a doom loop
|
|
2748
|
+
// resurfacing on the next action still trips the detector.
|
|
2749
|
+
this.doomLoopTriggers = 0;
|
|
2750
|
+
return;
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
// ── Input guardrails (Phase 3) ──
|
|
2754
|
+
// Run BEFORE the action body so a tripped guardrail prevents dispatch.
|
|
2755
|
+
// `tool_input` fires regardless so observability sees the call attempt;
|
|
2756
|
+
// a tripped guardrail then routes to `action_error`, NOT `action_end`.
|
|
2757
|
+
hooks.emitFireAndForget("tool_input", { toolName: actionType, args });
|
|
2758
|
+
try {
|
|
2759
|
+
args = await guardrails.runInput(actionType, args);
|
|
2760
|
+
}
|
|
2761
|
+
catch (err) {
|
|
2762
|
+
const guardrailErr = err instanceof GuardrailTripped
|
|
2763
|
+
? err
|
|
2764
|
+
: new InputGuardrailTripped(`Input guardrail for "${actionType}" failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
2765
|
+
if (this.verbose)
|
|
2766
|
+
console.error(`[autonomous] ✗ guardrail blocked ${actionType}: ${guardrailErr.message}`);
|
|
2767
|
+
hooks.emitFireAndForget("action_error", {
|
|
2768
|
+
actionType, args, error: guardrailErr,
|
|
2769
|
+
durationMs: Date.now() - startTime, actionId,
|
|
2770
|
+
});
|
|
2771
|
+
if (actionId) {
|
|
2772
|
+
try {
|
|
2773
|
+
await this.runtime.proactive.rejectDelegatedAction(actionId, guardrailErr.message);
|
|
2774
|
+
}
|
|
2775
|
+
catch { /* best-effort */ }
|
|
2776
|
+
}
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
try {
|
|
2780
|
+
let txHash;
|
|
2781
|
+
let result;
|
|
2782
|
+
// ── Intercept browse_tools (client-side, no gateway call needed) ──
|
|
2783
|
+
if (actionType === "browse_tools") {
|
|
2784
|
+
// Read from the (possibly guardrail-mutated) args, not raw payload.
|
|
2785
|
+
const category = args?.category;
|
|
2786
|
+
if (!category) {
|
|
2787
|
+
const listing = getCategoryListing();
|
|
2788
|
+
if (this.verbose) {
|
|
2789
|
+
console.log(`[autonomous] browse_tools — ${listing.length} categories: ${listing.map((c) => `${c.name} (${c.count})`).join(", ")}`);
|
|
2790
|
+
}
|
|
2791
|
+
let browseResult = { categories: listing };
|
|
2792
|
+
browseResult = await guardrails.runOutput(actionType, browseResult);
|
|
2793
|
+
hooks.emitFireAndForget("tool_output", { toolName: actionType, args, result: browseResult });
|
|
2794
|
+
hooks.emitFireAndForget("action_end", {
|
|
2795
|
+
actionType, args, result: browseResult, durationMs: Date.now() - startTime, actionId,
|
|
2796
|
+
});
|
|
2797
|
+
return;
|
|
2798
|
+
}
|
|
2799
|
+
this.loadedCategories.add(category);
|
|
2800
|
+
const tools = getToolsInCategory(category);
|
|
2801
|
+
if (this.verbose) {
|
|
2802
|
+
console.log(`[autonomous] browse_tools — loaded ${tools.length} tools from "${category}"`);
|
|
2803
|
+
}
|
|
2804
|
+
let browseResult = { category, count: tools.length };
|
|
2805
|
+
browseResult = await guardrails.runOutput(actionType, browseResult);
|
|
2806
|
+
hooks.emitFireAndForget("tool_output", { toolName: actionType, args, result: browseResult });
|
|
2807
|
+
hooks.emitFireAndForget("action_end", {
|
|
2808
|
+
actionType, args, result: browseResult, durationMs: Date.now() - startTime, actionId,
|
|
2809
|
+
});
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
// ── Intercept ecosystem raw-tx actions ──
|
|
2813
|
+
// External partner contracts (BOTCOIN, etc.) don't trust Nookplot's
|
|
2814
|
+
// EIP-2771 forwarder. The gateway returns { txRequests, meta } instead
|
|
2815
|
+
// of a ForwardRequest, and the agent signs+submits with own wallet.
|
|
2816
|
+
if (actionType === "ecosystem_stake_tokens" || actionType === "ecosystem_claim_rewards") {
|
|
2817
|
+
const { prepareSignSend } = await import("./signing.js");
|
|
2818
|
+
const preparePath = actionType === "ecosystem_stake_tokens"
|
|
2819
|
+
? "/v1/prepare/ecosystem/stake-tokens"
|
|
2820
|
+
: "/v1/prepare/ecosystem/claim-rewards";
|
|
2821
|
+
const sendResult = await prepareSignSend(this.runtime.connection, preparePath, args);
|
|
2822
|
+
const hashes = sendResult.txHashes ?? [];
|
|
2823
|
+
txHash = hashes.length > 0 ? hashes[hashes.length - 1] : undefined;
|
|
2824
|
+
result = { txHashes: hashes, meta: sendResult.meta };
|
|
2825
|
+
result = await guardrails.runOutput(actionType, result);
|
|
2826
|
+
hooks.emitFireAndForget("tool_output", { toolName: actionType, args, result });
|
|
2827
|
+
if (actionId)
|
|
2828
|
+
await this.runtime.proactive.completeAction(actionId, txHash, result);
|
|
2829
|
+
if (this.verbose)
|
|
2830
|
+
console.log(`[autonomous] ✓ ${actionType}${txHash ? ` tx=${txHash}` : ""}`);
|
|
2831
|
+
hooks.emitFireAndForget("action_end", {
|
|
2832
|
+
actionType, args, result, durationMs: Date.now() - startTime, actionId, txHash,
|
|
2833
|
+
});
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
// ── Unified dispatch via POST /v1/actions/execute ──
|
|
2837
|
+
// The gateway's ToolDispatcher routes the call to the correct internal
|
|
2838
|
+
// endpoint, replacing the 2000+ line switch statement that was here before.
|
|
2839
|
+
// NOTE: build the dispatch body from the GUARDRAIL-MUTATED `args`, not
|
|
2840
|
+
// the original `payload` — otherwise input-guardrail mutations
|
|
2841
|
+
// (lowercase normalization, content sanitization, etc.) silently get
|
|
2842
|
+
// dropped on the way to the gateway.
|
|
2843
|
+
const toolName = `nookplot_${actionType}`;
|
|
2844
|
+
const dispatchPayload = {
|
|
2845
|
+
...args,
|
|
2846
|
+
...(suggestedContent ? { suggestedContent } : {}),
|
|
2847
|
+
};
|
|
2848
|
+
// SRA Phase 4b — flush loaded-skill buffer into submit traces. If the
|
|
2849
|
+
// caller already set loadedSkillRefs explicitly, respect it; otherwise
|
|
2850
|
+
// populate from the buffer. Drop entries older than 24h client-side
|
|
2851
|
+
// (server enforces the same window via intersection, but this keeps the
|
|
2852
|
+
// payload small).
|
|
2853
|
+
if ((actionType === "submit_reasoning_trace" || actionType === "submit_subtask_trace") &&
|
|
2854
|
+
dispatchPayload.loadedSkillRefs == null &&
|
|
2855
|
+
this.loadedSkillRefs.length > 0) {
|
|
2856
|
+
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
2857
|
+
this.loadedSkillRefs = this.loadedSkillRefs.filter(e => e.loadedAt >= cutoff);
|
|
2858
|
+
if (this.loadedSkillRefs.length > 0) {
|
|
2859
|
+
dispatchPayload.loadedSkillRefs = this.loadedSkillRefs.map(e => `${e.kind}:${e.ref}`);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
const dispatchResult = await this.runtime.connection.request("POST", "/v1/actions/execute", { toolName, payload: dispatchPayload });
|
|
2863
|
+
switch (dispatchResult.status) {
|
|
2864
|
+
case "completed": {
|
|
2865
|
+
result = (dispatchResult.result ?? {});
|
|
2866
|
+
// SRA Phase 4b — capture canonical_ref from load_skill responses so
|
|
2867
|
+
// the next submit_reasoning_trace can declare them. The gateway's
|
|
2868
|
+
// recordLoad() already flips used=true on its skill_utility_signals
|
|
2869
|
+
// row; we just need to remember the ref for the trace-linkage step.
|
|
2870
|
+
if (actionType === "load_skill" && typeof result.canonical_ref === "string") {
|
|
2871
|
+
const kind = args.kind;
|
|
2872
|
+
if (typeof kind === "string") {
|
|
2873
|
+
this.loadedSkillRefs.push({ kind, ref: result.canonical_ref, loadedAt: Date.now() });
|
|
2874
|
+
if (this.loadedSkillRefs.length > 64) {
|
|
2875
|
+
this.loadedSkillRefs = this.loadedSkillRefs.slice(-64);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
break;
|
|
2880
|
+
}
|
|
2881
|
+
case "sign_required": {
|
|
2882
|
+
// On-chain action: gateway returned unsigned ForwardRequest — sign locally + relay
|
|
2883
|
+
if (!dispatchResult.forwardRequest || !dispatchResult.domain || !dispatchResult.types) {
|
|
2884
|
+
throw new Error(`sign_required response missing forwardRequest/domain/types for ${actionType}`);
|
|
2885
|
+
}
|
|
2886
|
+
const privateKey = this.runtime.connection.privateKey;
|
|
2887
|
+
if (!privateKey) {
|
|
2888
|
+
throw new Error("Private key not configured — cannot sign on-chain transactions.");
|
|
2889
|
+
}
|
|
2890
|
+
const { signForwardRequest } = await import("./signing.js");
|
|
2891
|
+
const signature = await signForwardRequest(privateKey, dispatchResult.domain, dispatchResult.types, dispatchResult.forwardRequest);
|
|
2892
|
+
const relayResult = await this.runtime.connection.request("POST", "/v1/relay", { ...dispatchResult.forwardRequest, signature });
|
|
2893
|
+
txHash = relayResult.txHash;
|
|
2894
|
+
result = { txHash };
|
|
2895
|
+
break;
|
|
2896
|
+
}
|
|
2897
|
+
case "client_side_required": {
|
|
2898
|
+
// Tool requires local execution (e.g. approve_token needs private key)
|
|
2899
|
+
if (dispatchResult.action === "nookplot_approve_token") {
|
|
2900
|
+
// Handle ERC-20 token approval locally
|
|
2901
|
+
const privateKey = this.runtime.connection.privateKey;
|
|
2902
|
+
if (!privateKey)
|
|
2903
|
+
throw new Error("Private key required for token approval");
|
|
2904
|
+
const { ethers } = await import("ethers");
|
|
2905
|
+
const provider = new ethers.JsonRpcProvider(this.runtime.connection.config?.rpcUrl ?? "https://mainnet.base.org");
|
|
2906
|
+
const signer = new ethers.Wallet(privateKey, provider);
|
|
2907
|
+
const params = dispatchResult.params ?? {};
|
|
2908
|
+
const tokenAddress = params.tokenAddress;
|
|
2909
|
+
const spenderAddress = params.spenderAddress;
|
|
2910
|
+
const amount = params.amount;
|
|
2911
|
+
const erc20 = new ethers.Contract(tokenAddress, [
|
|
2912
|
+
"function approve(address spender, uint256 amount) returns (bool)",
|
|
2913
|
+
], signer);
|
|
2914
|
+
const amountBN = amount === "max" ? ethers.MaxUint256 : ethers.parseUnits(amount, 18);
|
|
2915
|
+
const tx = await erc20.approve(spenderAddress, amountBN);
|
|
2916
|
+
await tx.wait();
|
|
2917
|
+
txHash = tx.hash;
|
|
2918
|
+
result = { txHash, approved: true };
|
|
2919
|
+
}
|
|
2920
|
+
else {
|
|
2921
|
+
throw new Error(`Client-side action not supported in runtime: ${dispatchResult.action}`);
|
|
2922
|
+
}
|
|
2923
|
+
break;
|
|
2924
|
+
}
|
|
2925
|
+
case "error": {
|
|
2926
|
+
throw new Error(dispatchResult.error ?? `Action failed: ${actionType}`);
|
|
2927
|
+
}
|
|
2928
|
+
default: {
|
|
2929
|
+
// Legacy fallback for older gateway versions that don't support ToolDispatcher
|
|
2930
|
+
if (this.verbose)
|
|
2931
|
+
console.warn(`[autonomous] Unexpected dispatch status: ${dispatchResult.status}`);
|
|
2932
|
+
result = dispatchResult;
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
// ── END unified dispatch ──
|
|
2936
|
+
// ── Output guardrails (Phase 3) ──
|
|
2937
|
+
// Run AFTER the body, BEFORE we mark the action complete on the
|
|
2938
|
+
// gateway. A tripped output guardrail throws -> caught by the outer
|
|
2939
|
+
// catch -> action_error fires (NOT action_end).
|
|
2940
|
+
result = await guardrails.runOutput(actionType, result ?? {});
|
|
2941
|
+
hooks.emitFireAndForget("tool_output", { toolName: actionType, args, result });
|
|
2942
|
+
if (actionId)
|
|
2943
|
+
await this.runtime.proactive.completeAction(actionId, txHash, result);
|
|
2944
|
+
if (this.verbose)
|
|
2945
|
+
console.log(`[autonomous] ✓ ${actionType}${txHash ? ` tx=${txHash}` : ""}`);
|
|
2946
|
+
hooks.emitFireAndForget("action_end", {
|
|
2947
|
+
actionType, args, result, durationMs: Date.now() - startTime, actionId, txHash,
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
catch (error) {
|
|
2951
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2952
|
+
if (this.verbose)
|
|
2953
|
+
console.error(`[autonomous] ✗ ${actionType}: ${msg}`);
|
|
2954
|
+
hooks.emitFireAndForget("action_error", {
|
|
2955
|
+
actionType, args, error: error instanceof Error ? error : new Error(msg),
|
|
2956
|
+
durationMs: Date.now() - startTime, actionId,
|
|
2957
|
+
});
|
|
2958
|
+
if (actionId) {
|
|
2959
|
+
try {
|
|
2960
|
+
await this.runtime.proactive.rejectDelegatedAction(actionId, msg);
|
|
2961
|
+
}
|
|
2962
|
+
catch { /* best-effort */ }
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
// ── Teaching exchange signal handlers ──
|
|
2967
|
+
async handleTeachingProposed(data) {
|
|
2968
|
+
const meta = data;
|
|
2969
|
+
const exchangeId = meta.exchangeId ?? "";
|
|
2970
|
+
const teacherAddress = meta.teacherAddress ?? "";
|
|
2971
|
+
const skill = meta.skill ?? "unknown";
|
|
2972
|
+
const description = meta.description ?? "";
|
|
2973
|
+
if (this.verbose)
|
|
2974
|
+
console.log(`[autonomous] Teaching proposed: ${skill} (exchange ${exchangeId})`);
|
|
2975
|
+
if (!this.generateResponse)
|
|
2976
|
+
return;
|
|
2977
|
+
try {
|
|
2978
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
2979
|
+
"An agent has proposed a teaching exchange on Nookplot.\n" +
|
|
2980
|
+
`Exchange ID: ${exchangeId}\n` +
|
|
2981
|
+
`Teacher: ${sanitizeForPrompt(String(teacherAddress).slice(0, 12))}...\n` +
|
|
2982
|
+
`Skill: ${wrapUntrusted(skill.slice(0, 100), "skill name")}\n` +
|
|
2983
|
+
(description ? `Description: ${wrapUntrusted(description.slice(0, 500), "teaching description")}\n` : "") +
|
|
2984
|
+
"\nDecide whether to accept or reject this teaching proposal.\n\n" +
|
|
2985
|
+
"Available actions:\n" +
|
|
2986
|
+
"- accept_teaching: Accept the teaching proposal (params: exchangeId)\n" +
|
|
2987
|
+
"- reject_teaching: Reject the teaching proposal (params: exchangeId)\n" +
|
|
2988
|
+
"- reply: Send a message to the teacher (params: to, content)\n" +
|
|
2989
|
+
"- ignore: Take no action\n\n" +
|
|
2990
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>\n" +
|
|
2991
|
+
"Or respond with ACKNOWLEDGE to consider later.";
|
|
2992
|
+
const response = await this.generateResponse(prompt);
|
|
2993
|
+
const text = response?.trim() ?? "";
|
|
2994
|
+
if (text && !text.toUpperCase().includes("ACKNOWLEDGE")) {
|
|
2995
|
+
await this.parseAndExecuteAction(text);
|
|
2996
|
+
}
|
|
2997
|
+
else if (this.verbose) {
|
|
2998
|
+
console.log(`[autonomous] ✓ Acknowledged teaching proposal ${exchangeId}`);
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
catch (err) {
|
|
3002
|
+
if (this.verbose)
|
|
3003
|
+
console.error("[autonomous] Teaching proposed handling failed:", err);
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
async handleTeachingAccepted(data) {
|
|
3007
|
+
const meta = data;
|
|
3008
|
+
const exchangeId = meta.exchangeId ?? "";
|
|
3009
|
+
const learnerAddress = meta.learnerAddress ?? "";
|
|
3010
|
+
const skill = meta.skill ?? "unknown";
|
|
3011
|
+
if (this.verbose)
|
|
3012
|
+
console.log(`[autonomous] Teaching accepted: ${skill} (exchange ${exchangeId})`);
|
|
3013
|
+
if (!this.generateResponse)
|
|
3014
|
+
return;
|
|
3015
|
+
try {
|
|
3016
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3017
|
+
"Your teaching proposal has been accepted on Nookplot.\n" +
|
|
3018
|
+
`Exchange ID: ${exchangeId}\n` +
|
|
3019
|
+
`Learner: ${sanitizeForPrompt(String(learnerAddress).slice(0, 12))}...\n` +
|
|
3020
|
+
`Skill: ${wrapUntrusted(skill.slice(0, 100), "skill name")}\n\n` +
|
|
3021
|
+
"You should now prepare and deliver the teaching content.\n\n" +
|
|
3022
|
+
"Available actions:\n" +
|
|
3023
|
+
"- deliver_teaching: Submit teaching content (params: exchangeId, content)\n" +
|
|
3024
|
+
"- reply: Send a message to the learner (params: to, content)\n" +
|
|
3025
|
+
"- ignore: Take no action yet\n\n" +
|
|
3026
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3027
|
+
const response = await this.generateResponse(prompt);
|
|
3028
|
+
const text = response?.trim() ?? "";
|
|
3029
|
+
if (text)
|
|
3030
|
+
await this.parseAndExecuteAction(text);
|
|
3031
|
+
}
|
|
3032
|
+
catch (err) {
|
|
3033
|
+
if (this.verbose)
|
|
3034
|
+
console.error("[autonomous] Teaching accepted handling failed:", err);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
async handleTeachingDelivered(data) {
|
|
3038
|
+
const meta = data;
|
|
3039
|
+
const exchangeId = meta.exchangeId ?? "";
|
|
3040
|
+
const teacherAddress = meta.teacherAddress ?? "";
|
|
3041
|
+
const content = meta.content ?? "";
|
|
3042
|
+
if (this.verbose)
|
|
3043
|
+
console.log(`[autonomous] Teaching delivered (exchange ${exchangeId})`);
|
|
3044
|
+
if (!this.generateResponse)
|
|
3045
|
+
return;
|
|
3046
|
+
try {
|
|
3047
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3048
|
+
"Teaching content has been delivered for an exchange on Nookplot.\n" +
|
|
3049
|
+
`Exchange ID: ${exchangeId}\n` +
|
|
3050
|
+
`Teacher: ${sanitizeForPrompt(String(teacherAddress).slice(0, 12))}...\n` +
|
|
3051
|
+
(content ? `Content preview: ${wrapUntrusted(content.slice(0, 500), "teaching content")}\n` : "") +
|
|
3052
|
+
"\nReview the teaching content and decide whether to approve or request changes.\n\n" +
|
|
3053
|
+
"Available actions:\n" +
|
|
3054
|
+
"- approve_teaching: Accept the delivered content (params: exchangeId)\n" +
|
|
3055
|
+
"- reject_teaching: Request changes (params: exchangeId)\n" +
|
|
3056
|
+
"- reply: Send feedback to the teacher (params: to, content)\n" +
|
|
3057
|
+
"- ignore: Take no action yet\n\n" +
|
|
3058
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3059
|
+
const response = await this.generateResponse(prompt);
|
|
3060
|
+
const text = response?.trim() ?? "";
|
|
3061
|
+
if (text)
|
|
3062
|
+
await this.parseAndExecuteAction(text);
|
|
3063
|
+
}
|
|
3064
|
+
catch (err) {
|
|
3065
|
+
if (this.verbose)
|
|
3066
|
+
console.error("[autonomous] Teaching delivered handling failed:", err);
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
async handleTeachingOpportunity(data) {
|
|
3070
|
+
const meta = data;
|
|
3071
|
+
const skill = meta.skill ?? "unknown";
|
|
3072
|
+
const learnerAddress = meta.learnerAddress ?? "";
|
|
3073
|
+
const description = meta.description ?? "";
|
|
3074
|
+
if (this.verbose)
|
|
3075
|
+
console.log(`[autonomous] Teaching opportunity: ${skill}`);
|
|
3076
|
+
if (!this.generateResponse)
|
|
3077
|
+
return;
|
|
3078
|
+
try {
|
|
3079
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3080
|
+
"A teaching opportunity has been discovered on Nookplot.\n" +
|
|
3081
|
+
`Skill requested: ${wrapUntrusted(skill.slice(0, 100), "skill name")}\n` +
|
|
3082
|
+
(learnerAddress ? `Learner: ${sanitizeForPrompt(learnerAddress.slice(0, 12))}...\n` : "") +
|
|
3083
|
+
(description ? `Details: ${wrapUntrusted(description.slice(0, 500), "opportunity details")}\n` : "") +
|
|
3084
|
+
"\nDecide whether to offer your teaching services.\n\n" +
|
|
3085
|
+
"Available actions:\n" +
|
|
3086
|
+
"- propose_teaching: Offer to teach this skill (params: skill, description, targetAgent)\n" +
|
|
3087
|
+
"- search_teachers: Search for other teachers (params: skill)\n" +
|
|
3088
|
+
"- reply: Send a message to the learner (params: to, content)\n" +
|
|
3089
|
+
"- ignore: Skip this opportunity\n\n" +
|
|
3090
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3091
|
+
const response = await this.generateResponse(prompt);
|
|
3092
|
+
const text = response?.trim() ?? "";
|
|
3093
|
+
if (text)
|
|
3094
|
+
await this.parseAndExecuteAction(text);
|
|
3095
|
+
}
|
|
3096
|
+
catch (err) {
|
|
3097
|
+
if (this.verbose)
|
|
3098
|
+
console.error("[autonomous] Teaching opportunity handling failed:", err);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
// ── Credit-based agreement signal handlers ──
|
|
3102
|
+
async handleCreditAgreementCreated(data) {
|
|
3103
|
+
const meta = data;
|
|
3104
|
+
const agreementId = meta.agreementId ?? "";
|
|
3105
|
+
const buyerAddress = meta.buyerAddress ?? "";
|
|
3106
|
+
const amount = meta.amount ?? meta.credits ?? "0";
|
|
3107
|
+
const description = meta.description ?? "";
|
|
3108
|
+
if (this.verbose)
|
|
3109
|
+
console.log(`[autonomous] Credit agreement created: #${agreementId}`);
|
|
3110
|
+
if (!this.generateResponse)
|
|
3111
|
+
return;
|
|
3112
|
+
try {
|
|
3113
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3114
|
+
"A credit-based service agreement has been created for you on Nookplot.\n" +
|
|
3115
|
+
`Agreement #${agreementId}\n` +
|
|
3116
|
+
`Buyer: ${sanitizeForPrompt(String(buyerAddress).slice(0, 12))}...\n` +
|
|
3117
|
+
`Credits: ${amount}\n` +
|
|
3118
|
+
(description ? `Terms: ${wrapUntrusted(description.slice(0, 500), "agreement terms")}\n` : "") +
|
|
3119
|
+
"\nReview and decide whether to accept this agreement.\n\n" +
|
|
3120
|
+
"Available actions:\n" +
|
|
3121
|
+
"- accept_credit_agreement: Accept the agreement (params: agreementId)\n" +
|
|
3122
|
+
"- cancel_credit_agreement: Decline (params: agreementId)\n" +
|
|
3123
|
+
"- reply: Send a message to the buyer (params: to, content)\n" +
|
|
3124
|
+
"- ignore: Take no action yet\n\n" +
|
|
3125
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>\n" +
|
|
3126
|
+
"Or respond with ACKNOWLEDGE to consider later.";
|
|
3127
|
+
const response = await this.generateResponse(prompt);
|
|
3128
|
+
const text = response?.trim() ?? "";
|
|
3129
|
+
if (text && !text.toUpperCase().includes("ACKNOWLEDGE")) {
|
|
3130
|
+
await this.parseAndExecuteAction(text);
|
|
3131
|
+
}
|
|
3132
|
+
else if (this.verbose) {
|
|
3133
|
+
console.log(`[autonomous] ✓ Acknowledged credit agreement #${agreementId}`);
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
catch (err) {
|
|
3137
|
+
if (this.verbose)
|
|
3138
|
+
console.error("[autonomous] Credit agreement created handling failed:", err);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
async handleCreditWorkDelivered(data) {
|
|
3142
|
+
const meta = data;
|
|
3143
|
+
const agreementId = meta.agreementId ?? "";
|
|
3144
|
+
const providerAddress = meta.providerAddress ?? "";
|
|
3145
|
+
const deliveryNotes = meta.deliveryNotes ?? "";
|
|
3146
|
+
if (this.verbose)
|
|
3147
|
+
console.log(`[autonomous] Credit work delivered: agreement #${agreementId}`);
|
|
3148
|
+
if (!this.generateResponse)
|
|
3149
|
+
return;
|
|
3150
|
+
try {
|
|
3151
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3152
|
+
"Work has been delivered on a credit-based agreement on Nookplot.\n" +
|
|
3153
|
+
`Agreement #${agreementId}\n` +
|
|
3154
|
+
`Provider: ${sanitizeForPrompt(String(providerAddress).slice(0, 12))}...\n` +
|
|
3155
|
+
(deliveryNotes ? `Delivery notes: ${wrapUntrusted(deliveryNotes.slice(0, 500), "delivery notes")}\n` : "") +
|
|
3156
|
+
"\nYou are the buyer. Review the submission and decide.\n\n" +
|
|
3157
|
+
"Available actions:\n" +
|
|
3158
|
+
"- complete_credit_agreement: Accept work and release credits (params: agreementId, rating, review)\n" +
|
|
3159
|
+
"- cancel_credit_agreement: Reject and refund (params: agreementId)\n" +
|
|
3160
|
+
"- reply: Send feedback (params: to, content)\n" +
|
|
3161
|
+
"- ignore: Take no action yet\n\n" +
|
|
3162
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3163
|
+
const response = await this.generateResponse(prompt);
|
|
3164
|
+
const text = response?.trim() ?? "";
|
|
3165
|
+
if (text)
|
|
3166
|
+
await this.parseAndExecuteAction(text);
|
|
3167
|
+
}
|
|
3168
|
+
catch (err) {
|
|
3169
|
+
if (this.verbose)
|
|
3170
|
+
console.error("[autonomous] Credit work delivered handling failed:", err);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
async handleCreditAgreementAccepted(data) {
|
|
3174
|
+
const meta = data;
|
|
3175
|
+
const agreementId = meta.agreementId ?? "";
|
|
3176
|
+
const buyerAddress = meta.buyerAddress ?? "";
|
|
3177
|
+
const amount = meta.amount ?? meta.credits ?? "0";
|
|
3178
|
+
if (this.verbose)
|
|
3179
|
+
console.log(`[autonomous] Credit agreement accepted: #${agreementId}`);
|
|
3180
|
+
if (!this.generateResponse)
|
|
3181
|
+
return;
|
|
3182
|
+
try {
|
|
3183
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3184
|
+
"Your credit-based agreement has been accepted on Nookplot.\n" +
|
|
3185
|
+
`Agreement #${agreementId}\n` +
|
|
3186
|
+
`Buyer: ${sanitizeForPrompt(String(buyerAddress).slice(0, 12))}...\n` +
|
|
3187
|
+
`Credits: ${amount}\n\n` +
|
|
3188
|
+
"You should now start working and deliver when ready.\n\n" +
|
|
3189
|
+
"Available actions:\n" +
|
|
3190
|
+
"- deliver_credit_work: Submit your work (params: agreementId, deliveryNotes)\n" +
|
|
3191
|
+
"- cancel_credit_agreement: Cancel the agreement (params: agreementId)\n" +
|
|
3192
|
+
"- reply: Send a message to the buyer (params: to, content)\n" +
|
|
3193
|
+
"- ignore: Take no action yet\n\n" +
|
|
3194
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3195
|
+
const response = await this.generateResponse(prompt);
|
|
3196
|
+
const text = response?.trim() ?? "";
|
|
3197
|
+
if (text)
|
|
3198
|
+
await this.parseAndExecuteAction(text);
|
|
3199
|
+
}
|
|
3200
|
+
catch (err) {
|
|
3201
|
+
if (this.verbose)
|
|
3202
|
+
console.error("[autonomous] Credit agreement accepted handling failed:", err);
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
// ── Email signal handler ──
|
|
3206
|
+
async handleEmailReceived(data) {
|
|
3207
|
+
const meta = data;
|
|
3208
|
+
const from = meta.from ?? "unknown";
|
|
3209
|
+
const subject = meta.subject ?? "";
|
|
3210
|
+
const body = meta.body ?? meta.preview ?? "";
|
|
3211
|
+
if (this.verbose)
|
|
3212
|
+
console.log(`[autonomous] Email received from ${from}`);
|
|
3213
|
+
if (!this.generateResponse)
|
|
3214
|
+
return;
|
|
3215
|
+
try {
|
|
3216
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3217
|
+
"You have received an email.\n" +
|
|
3218
|
+
`From: ${wrapUntrusted(from.slice(0, 100), "sender")}\n` +
|
|
3219
|
+
(subject ? `Subject: ${wrapUntrusted(subject.slice(0, 200), "subject")}\n` : "") +
|
|
3220
|
+
(body ? `Body: ${wrapUntrusted(body.slice(0, 1000), "email body")}\n` : "") +
|
|
3221
|
+
"\nDecide how to respond to this email.\n\n" +
|
|
3222
|
+
"Available actions:\n" +
|
|
3223
|
+
"- reply_email: Reply to the email (params: to, subject, body)\n" +
|
|
3224
|
+
"- send_email: Send a new email (params: to, subject, body)\n" +
|
|
3225
|
+
"- send_dm: Message another agent about this (params: to, content)\n" +
|
|
3226
|
+
"- ignore: Take no action\n\n" +
|
|
3227
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3228
|
+
const response = await this.generateResponse(prompt);
|
|
3229
|
+
const text = response?.trim() ?? "";
|
|
3230
|
+
if (text)
|
|
3231
|
+
await this.parseAndExecuteAction(text);
|
|
3232
|
+
}
|
|
3233
|
+
catch (err) {
|
|
3234
|
+
if (this.verbose)
|
|
3235
|
+
console.error("[autonomous] Email received handling failed:", err);
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
// ── Intent matching signal handlers ──
|
|
3239
|
+
async handleIntentMatched(data) {
|
|
3240
|
+
const meta = data;
|
|
3241
|
+
const intentId = meta.intentId ?? "";
|
|
3242
|
+
const title = meta.title ?? "";
|
|
3243
|
+
const description = meta.description ?? "";
|
|
3244
|
+
const budget = meta.budgetAmount ?? meta.budget ?? "";
|
|
3245
|
+
const skills = meta.requiredSkills ?? [];
|
|
3246
|
+
if (this.verbose)
|
|
3247
|
+
console.log(`[autonomous] Intent matched: ${title || intentId}`);
|
|
3248
|
+
if (!this.generateResponse)
|
|
3249
|
+
return;
|
|
3250
|
+
try {
|
|
3251
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3252
|
+
"An intent has been matched to your skills on Nookplot.\n" +
|
|
3253
|
+
`Intent ID: ${intentId}\n` +
|
|
3254
|
+
(title ? `Title: ${wrapUntrusted(title.slice(0, 200), "intent title")}\n` : "") +
|
|
3255
|
+
(description ? `Description: ${wrapUntrusted(description.slice(0, 500), "intent description")}\n` : "") +
|
|
3256
|
+
(budget ? `Budget: ${budget} credits\n` : "") +
|
|
3257
|
+
(skills.length ? `Required skills: ${skills.join(", ")}\n` : "") +
|
|
3258
|
+
"\nDecide whether to submit a proposal for this work.\n\n" +
|
|
3259
|
+
"Available actions:\n" +
|
|
3260
|
+
"- submit_proposal: Submit a proposal (params: intentId, content, estimatedCredits)\n" +
|
|
3261
|
+
"- browse_intents: Browse more intents (params: query)\n" +
|
|
3262
|
+
"- reply: Send a message (params: to, content)\n" +
|
|
3263
|
+
"- ignore: Skip this opportunity\n\n" +
|
|
3264
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3265
|
+
const response = await this.generateResponse(prompt);
|
|
3266
|
+
const text = response?.trim() ?? "";
|
|
3267
|
+
if (text)
|
|
3268
|
+
await this.parseAndExecuteAction(text);
|
|
3269
|
+
}
|
|
3270
|
+
catch (err) {
|
|
3271
|
+
if (this.verbose)
|
|
3272
|
+
console.error("[autonomous] Intent matched handling failed:", err);
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
async handleIntentAccepted(data) {
|
|
3276
|
+
const meta = data;
|
|
3277
|
+
const intentId = meta.intentId ?? "";
|
|
3278
|
+
const title = meta.title ?? "";
|
|
3279
|
+
if (this.verbose)
|
|
3280
|
+
console.log(`[autonomous] Intent accepted: ${title || intentId}`);
|
|
3281
|
+
if (!this.generateResponse)
|
|
3282
|
+
return;
|
|
3283
|
+
try {
|
|
3284
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3285
|
+
"Your proposal has been accepted for an intent on Nookplot.\n" +
|
|
3286
|
+
`Intent ID: ${intentId}\n` +
|
|
3287
|
+
(title ? `Title: ${wrapUntrusted(title.slice(0, 200), "intent title")}\n` : "") +
|
|
3288
|
+
"\nYou should now complete the work as described in your proposal.\n\n" +
|
|
3289
|
+
"Available actions:\n" +
|
|
3290
|
+
"- complete_intent: Mark the intent as completed (params: intentId)\n" +
|
|
3291
|
+
"- reply: Send a message to the intent creator (params: to, content)\n" +
|
|
3292
|
+
"- ignore: Take no action yet\n\n" +
|
|
3293
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3294
|
+
const response = await this.generateResponse(prompt);
|
|
3295
|
+
const text = response?.trim() ?? "";
|
|
3296
|
+
if (text)
|
|
3297
|
+
await this.parseAndExecuteAction(text);
|
|
3298
|
+
}
|
|
3299
|
+
catch (err) {
|
|
3300
|
+
if (this.verbose)
|
|
3301
|
+
console.error("[autonomous] Intent accepted handling failed:", err);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
async handleProposalReceived(data) {
|
|
3305
|
+
const meta = data;
|
|
3306
|
+
const intentId = meta.intentId ?? "";
|
|
3307
|
+
const proposalId = meta.proposalId ?? "";
|
|
3308
|
+
const proposerAddress = meta.proposerAddress ?? "";
|
|
3309
|
+
const content = meta.content ?? "";
|
|
3310
|
+
const estimatedCredits = meta.estimatedCredits ?? "";
|
|
3311
|
+
if (this.verbose)
|
|
3312
|
+
console.log(`[autonomous] Proposal received on intent ${intentId}`);
|
|
3313
|
+
if (!this.generateResponse)
|
|
3314
|
+
return;
|
|
3315
|
+
try {
|
|
3316
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3317
|
+
"A proposal has been submitted for your intent on Nookplot.\n" +
|
|
3318
|
+
`Intent ID: ${intentId}\n` +
|
|
3319
|
+
`Proposal ID: ${proposalId}\n` +
|
|
3320
|
+
`Proposer: ${sanitizeForPrompt(String(proposerAddress).slice(0, 12))}...\n` +
|
|
3321
|
+
(estimatedCredits ? `Estimated cost: ${estimatedCredits} credits\n` : "") +
|
|
3322
|
+
(content ? `Proposal: ${wrapUntrusted(String(content).slice(0, 500), "proposal content")}\n` : "") +
|
|
3323
|
+
"\nReview the proposal and decide whether to accept or reject it.\n\n" +
|
|
3324
|
+
"Available actions:\n" +
|
|
3325
|
+
"- accept_proposal: Accept this proposal (params: intentId, proposalId)\n" +
|
|
3326
|
+
"- reject_proposal: Reject this proposal (params: intentId, proposalId, reason)\n" +
|
|
3327
|
+
"- reply: Send a message to the proposer (params: to, content)\n" +
|
|
3328
|
+
"- ignore: Take no action yet\n\n" +
|
|
3329
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3330
|
+
const response = await this.generateResponse(prompt);
|
|
3331
|
+
const text = response?.trim() ?? "";
|
|
3332
|
+
if (text)
|
|
3333
|
+
await this.parseAndExecuteAction(text);
|
|
3334
|
+
}
|
|
3335
|
+
catch (err) {
|
|
3336
|
+
if (this.verbose)
|
|
3337
|
+
console.error("[autonomous] Proposal received handling failed:", err);
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
// ── XMTP message signal handler ──
|
|
3341
|
+
async handleXmtpMessage(data) {
|
|
3342
|
+
const meta = data;
|
|
3343
|
+
const senderAddress = meta.senderAddress ?? "";
|
|
3344
|
+
const content = meta.content ?? meta.messagePreview ?? "";
|
|
3345
|
+
if (this.verbose)
|
|
3346
|
+
console.log(`[autonomous] XMTP message from ${senderAddress.slice(0, 10)}...`);
|
|
3347
|
+
if (!this.generateResponse)
|
|
3348
|
+
return;
|
|
3349
|
+
try {
|
|
3350
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3351
|
+
"You have received a cross-protocol XMTP message.\n" +
|
|
3352
|
+
`From: ${sanitizeForPrompt(senderAddress.slice(0, 12))}...\n` +
|
|
3353
|
+
(content ? `Message: ${wrapUntrusted(content.slice(0, 1000), "xmtp message")}\n` : "") +
|
|
3354
|
+
"\nDecide how to respond.\n\n" +
|
|
3355
|
+
"Available actions:\n" +
|
|
3356
|
+
"- reply: Send a reply via XMTP (params: to, content)\n" +
|
|
3357
|
+
"- ignore: Take no action\n\n" +
|
|
3358
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3359
|
+
const response = await this.generateResponse(prompt);
|
|
3360
|
+
const text = response?.trim() ?? "";
|
|
3361
|
+
if (text)
|
|
3362
|
+
await this.parseAndExecuteAction(text);
|
|
3363
|
+
}
|
|
3364
|
+
catch (err) {
|
|
3365
|
+
if (this.verbose)
|
|
3366
|
+
console.error("[autonomous] XMTP message handling failed:", err);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
// ── File sharing signal handler ──
|
|
3370
|
+
async handleFileShared(data) {
|
|
3371
|
+
const meta = data;
|
|
3372
|
+
const projectId = meta.projectId ?? "";
|
|
3373
|
+
const filePath = meta.filePath ?? meta.path ?? "";
|
|
3374
|
+
const sharedBy = meta.sharedBy ?? meta.senderAddress ?? "";
|
|
3375
|
+
if (this.verbose)
|
|
3376
|
+
console.log(`[autonomous] File shared: ${filePath} in project ${projectId}`);
|
|
3377
|
+
if (!this.generateResponse)
|
|
3378
|
+
return;
|
|
3379
|
+
try {
|
|
3380
|
+
const prompt = `${UNTRUSTED_CONTENT_INSTRUCTION}\n\n` +
|
|
3381
|
+
"A file has been shared in a project on Nookplot.\n" +
|
|
3382
|
+
(projectId ? `Project: ${projectId}\n` : "") +
|
|
3383
|
+
(filePath ? `File: ${wrapUntrusted(filePath.slice(0, 200), "file path")}\n` : "") +
|
|
3384
|
+
(sharedBy ? `Shared by: ${sanitizeForPrompt(sharedBy.slice(0, 12))}...\n` : "") +
|
|
3385
|
+
"\nDecide how to respond.\n\n" +
|
|
3386
|
+
"Available actions:\n" +
|
|
3387
|
+
"- reply: Acknowledge or comment on the file (params: to, content)\n" +
|
|
3388
|
+
"- ignore: Take no action\n\n" +
|
|
3389
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3390
|
+
const response = await this.generateResponse(prompt);
|
|
3391
|
+
const text = response?.trim() ?? "";
|
|
3392
|
+
if (text)
|
|
3393
|
+
await this.parseAndExecuteAction(text);
|
|
3394
|
+
}
|
|
3395
|
+
catch (err) {
|
|
3396
|
+
if (this.verbose)
|
|
3397
|
+
console.error("[autonomous] File shared handling failed:", err);
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
// ── Swarm coordination signal handlers ──────────────────────────────────
|
|
3401
|
+
async handleSwarmSubtaskAvailable(data) {
|
|
3402
|
+
const meta = data;
|
|
3403
|
+
const swarmId = meta.swarmId ?? "";
|
|
3404
|
+
const swarmTitle = meta.swarmTitle ?? "Unknown swarm";
|
|
3405
|
+
const skillTags = meta.skillTags ?? [];
|
|
3406
|
+
if (this.verbose) {
|
|
3407
|
+
console.log(`[autonomous] Swarm subtask available: "${swarmTitle}" (skills: ${skillTags.join(", ")})`);
|
|
3408
|
+
}
|
|
3409
|
+
if (!this.generateResponse)
|
|
3410
|
+
return;
|
|
3411
|
+
try {
|
|
3412
|
+
const actions = getAvailableActionsFromMap("swarm_subtask_available", this.loadedCategories);
|
|
3413
|
+
const prompt = `A swarm "${sanitizeForPrompt(swarmTitle.slice(0, 100))}" has open subtasks matching your skills.\n` +
|
|
3414
|
+
`Swarm ID: ${swarmId}\n` +
|
|
3415
|
+
`Matching skills: ${skillTags.slice(0, 10).join(", ")}\n\n` +
|
|
3416
|
+
"Should you participate? You can browse the available subtasks and claim one.\n\n" +
|
|
3417
|
+
`Available actions:\n${actions.map(a => `- ${a}`).join("\n")}\n\n` +
|
|
3418
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3419
|
+
const response = await this.generateResponse(prompt);
|
|
3420
|
+
const text = response?.trim() ?? "";
|
|
3421
|
+
if (text)
|
|
3422
|
+
await this.parseAndExecuteAction(text);
|
|
3423
|
+
}
|
|
3424
|
+
catch (err) {
|
|
3425
|
+
if (this.verbose)
|
|
3426
|
+
console.error("[autonomous] Swarm subtask available handling failed:", err);
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
async handleSwarmResultSubmitted(data) {
|
|
3430
|
+
const meta = data;
|
|
3431
|
+
const swarmId = meta.swarmId ?? "";
|
|
3432
|
+
const subtaskTitle = meta.subtaskTitle ?? "Unknown subtask";
|
|
3433
|
+
const submittedBy = meta.submittedBy ?? "";
|
|
3434
|
+
if (this.verbose) {
|
|
3435
|
+
console.log(`[autonomous] Swarm result submitted for "${subtaskTitle}" by ${submittedBy}`);
|
|
3436
|
+
}
|
|
3437
|
+
if (!this.generateResponse)
|
|
3438
|
+
return;
|
|
3439
|
+
try {
|
|
3440
|
+
const actions = getAvailableActionsFromMap("swarm_result_submitted", this.loadedCategories);
|
|
3441
|
+
const prompt = `A result was submitted for subtask "${sanitizeForPrompt(subtaskTitle.slice(0, 100))}" in your swarm.\n` +
|
|
3442
|
+
`Swarm ID: ${swarmId}\n` +
|
|
3443
|
+
`Submitted by: ${sanitizeForPrompt((submittedBy).slice(0, 12))}...\n\n` +
|
|
3444
|
+
"Review the result and decide whether to accept, reject, or aggregate.\n\n" +
|
|
3445
|
+
`Available actions:\n${actions.map(a => `- ${a}`).join("\n")}\n\n` +
|
|
3446
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3447
|
+
const response = await this.generateResponse(prompt);
|
|
3448
|
+
const text = response?.trim() ?? "";
|
|
3449
|
+
if (text)
|
|
3450
|
+
await this.parseAndExecuteAction(text);
|
|
3451
|
+
}
|
|
3452
|
+
catch (err) {
|
|
3453
|
+
if (this.verbose)
|
|
3454
|
+
console.error("[autonomous] Swarm result submitted handling failed:", err);
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
/**
|
|
3458
|
+
* Handle dream_prompt — idle-time knowledge exploration.
|
|
3459
|
+
* The gateway's KnowledgeDreamer detects gaps (unlinked domains, shallow areas, stale items)
|
|
3460
|
+
* and sends a prompt. The agent searches its own knowledge, thinks about connections,
|
|
3461
|
+
* and stores new insights.
|
|
3462
|
+
*/
|
|
3463
|
+
async handleDreamPrompt(data) {
|
|
3464
|
+
const meta = data;
|
|
3465
|
+
const dreamType = meta.dreamType ?? "gap_bridge";
|
|
3466
|
+
const promptText = meta.prompt ?? meta.message ?? "";
|
|
3467
|
+
const domains = meta.domains ?? [];
|
|
3468
|
+
if (this.verbose) {
|
|
3469
|
+
console.log(`[autonomous] Dream prompt (${dreamType}): ${promptText.slice(0, 80)}...`);
|
|
3470
|
+
}
|
|
3471
|
+
if (!this.generateResponse || !promptText)
|
|
3472
|
+
return;
|
|
3473
|
+
try {
|
|
3474
|
+
const domainContext = domains.length > 0
|
|
3475
|
+
? `Domains involved: ${domains.join(", ")}\n`
|
|
3476
|
+
: "";
|
|
3477
|
+
// Step 1: Search knowledge for the mentioned domains
|
|
3478
|
+
const searchPrompt = `KNOWLEDGE DREAM — Step 1: Search\n\n` +
|
|
3479
|
+
`${sanitizeForPrompt(promptText.slice(0, 500))}\n\n` +
|
|
3480
|
+
`${domainContext}` +
|
|
3481
|
+
`Search your knowledge for the domains mentioned above to find relevant items.\n\n` +
|
|
3482
|
+
"Format:\nACTION: search_knowledge\nPARAMS: <json params>";
|
|
3483
|
+
const searchResponse = await this.generateResponse(searchPrompt);
|
|
3484
|
+
const searchText = searchResponse?.trim() ?? "";
|
|
3485
|
+
let searchResult = null;
|
|
3486
|
+
if (searchText) {
|
|
3487
|
+
searchResult = await this.parseAndExecuteAction(searchText);
|
|
3488
|
+
}
|
|
3489
|
+
// Step 2: Based on search results, store an insight
|
|
3490
|
+
const actions = getAvailableActionsFromMap("dream_prompt", this.loadedCategories);
|
|
3491
|
+
const searchContext = searchResult
|
|
3492
|
+
? `\nSearch results (summarized): ${JSON.stringify(searchResult).slice(0, 1500)}\n`
|
|
3493
|
+
: "\nNo search results found — consider creating foundational knowledge for these domains.\n";
|
|
3494
|
+
const storePrompt = `KNOWLEDGE DREAM — Step 2: Synthesize & Store\n\n` +
|
|
3495
|
+
`Original prompt: ${sanitizeForPrompt(promptText.slice(0, 300))}\n` +
|
|
3496
|
+
`${domainContext}` +
|
|
3497
|
+
`${searchContext}\n` +
|
|
3498
|
+
`Based on your search results and the dream prompt, create a new insight that:\n` +
|
|
3499
|
+
`- Connects knowledge across the mentioned domains\n` +
|
|
3500
|
+
`- Identifies patterns, gaps, or actionable takeaways\n` +
|
|
3501
|
+
`- Uses rich markdown formatting (headers, bullets, code blocks)\n\n` +
|
|
3502
|
+
`Store it via store_knowledge_item with sourceType "dream".\n` +
|
|
3503
|
+
`If nothing meaningful to add, use "ignore".\n\n` +
|
|
3504
|
+
`Available actions:\n${actions.map(a => `- ${a}`).join("\n")}\n\n` +
|
|
3505
|
+
"Format:\nACTION: <action_type>\nPARAMS: <json params>";
|
|
3506
|
+
const storeResponse = await this.generateResponse(storePrompt);
|
|
3507
|
+
const storeText = storeResponse?.trim() ?? "";
|
|
3508
|
+
if (storeText)
|
|
3509
|
+
await this.parseAndExecuteAction(storeText);
|
|
3510
|
+
}
|
|
3511
|
+
catch (err) {
|
|
3512
|
+
if (this.verbose)
|
|
3513
|
+
console.error("[autonomous] Dream prompt handling failed:", err);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
//# sourceMappingURL=autonomous.js.map
|