@holoscript/framework 6.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/ALL-test-results.json +1 -0
  2. package/CHANGELOG.md +8 -0
  3. package/LICENSE +21 -0
  4. package/ROADMAP.md +175 -0
  5. package/dist/AgentManifest-CB4xM-Ma.d.cts +704 -0
  6. package/dist/AgentManifest-CB4xM-Ma.d.ts +704 -0
  7. package/dist/BehaviorTree-BrBFECv5.d.cts +103 -0
  8. package/dist/BehaviorTree-BrBFECv5.d.ts +103 -0
  9. package/dist/InvisibleWallet-BB6tFvRA.d.cts +1732 -0
  10. package/dist/InvisibleWallet-rtRrBOA8.d.ts +1732 -0
  11. package/dist/OrchestratorAgent-BvWgf9uw.d.cts +798 -0
  12. package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +798 -0
  13. package/dist/agents/index.cjs +4790 -0
  14. package/dist/agents/index.d.cts +1788 -0
  15. package/dist/agents/index.d.ts +1788 -0
  16. package/dist/agents/index.js +4695 -0
  17. package/dist/ai/index.cjs +5347 -0
  18. package/dist/ai/index.d.cts +1753 -0
  19. package/dist/ai/index.d.ts +1753 -0
  20. package/dist/ai/index.js +5244 -0
  21. package/dist/behavior.cjs +449 -0
  22. package/dist/behavior.d.cts +130 -0
  23. package/dist/behavior.d.ts +130 -0
  24. package/dist/behavior.js +407 -0
  25. package/dist/economy/index.cjs +3659 -0
  26. package/dist/economy/index.d.cts +747 -0
  27. package/dist/economy/index.d.ts +747 -0
  28. package/dist/economy/index.js +3617 -0
  29. package/dist/implementations-D9T3un9D.d.cts +236 -0
  30. package/dist/implementations-D9T3un9D.d.ts +236 -0
  31. package/dist/index.cjs +24550 -0
  32. package/dist/index.d.cts +1729 -0
  33. package/dist/index.d.ts +1729 -0
  34. package/dist/index.js +24277 -0
  35. package/dist/learning/index.cjs +219 -0
  36. package/dist/learning/index.d.cts +104 -0
  37. package/dist/learning/index.d.ts +104 -0
  38. package/dist/learning/index.js +189 -0
  39. package/dist/negotiation/index.cjs +970 -0
  40. package/dist/negotiation/index.d.cts +610 -0
  41. package/dist/negotiation/index.d.ts +610 -0
  42. package/dist/negotiation/index.js +931 -0
  43. package/dist/skills/index.cjs +1118 -0
  44. package/dist/skills/index.d.cts +289 -0
  45. package/dist/skills/index.d.ts +289 -0
  46. package/dist/skills/index.js +1079 -0
  47. package/dist/swarm/index.cjs +5268 -0
  48. package/dist/swarm/index.d.cts +2433 -0
  49. package/dist/swarm/index.d.ts +2433 -0
  50. package/dist/swarm/index.js +5221 -0
  51. package/dist/training/index.cjs +2745 -0
  52. package/dist/training/index.d.cts +1734 -0
  53. package/dist/training/index.d.ts +1734 -0
  54. package/dist/training/index.js +2687 -0
  55. package/extract-failures.js +10 -0
  56. package/package.json +82 -0
  57. package/src/__tests__/bounty-marketplace.test.ts +374 -0
  58. package/src/__tests__/delegation.test.ts +144 -0
  59. package/src/__tests__/distributed-claimer.test.ts +147 -0
  60. package/src/__tests__/done-log-audit.test.ts +342 -0
  61. package/src/__tests__/framework.test.ts +865 -0
  62. package/src/__tests__/goal-synthesizer.test.ts +236 -0
  63. package/src/__tests__/presence.test.ts +223 -0
  64. package/src/__tests__/protocol-agent.test.ts +254 -0
  65. package/src/__tests__/revenue-splitter.test.ts +114 -0
  66. package/src/__tests__/scenario-driven-todo.test.ts +197 -0
  67. package/src/__tests__/self-improve.test.ts +349 -0
  68. package/src/__tests__/service-lifecycle.test.ts +237 -0
  69. package/src/__tests__/skill-router.test.ts +121 -0
  70. package/src/agents/AgentManifest.ts +493 -0
  71. package/src/agents/AgentRegistry.ts +475 -0
  72. package/src/agents/AgentTypes.ts +585 -0
  73. package/src/agents/AgentWalletRegistry.ts +83 -0
  74. package/src/agents/AuthenticatedCRDT.ts +388 -0
  75. package/src/agents/CapabilityMatcher.ts +453 -0
  76. package/src/agents/CrossRealityHandoff.ts +305 -0
  77. package/src/agents/CulturalMemory.ts +454 -0
  78. package/src/agents/FederatedRegistryAdapter.ts +429 -0
  79. package/src/agents/NormEngine.ts +450 -0
  80. package/src/agents/OrchestratorAgent.ts +414 -0
  81. package/src/agents/SkillWorkflowEngine.ts +472 -0
  82. package/src/agents/TaskDelegationService.ts +551 -0
  83. package/src/agents/__tests__/AgentManifest.prod.test.ts +134 -0
  84. package/src/agents/__tests__/AgentManifest.test.ts +182 -0
  85. package/src/agents/__tests__/AgentModule.test.ts +864 -0
  86. package/src/agents/__tests__/AgentRegistry.prod.test.ts +125 -0
  87. package/src/agents/__tests__/AgentRegistry.test.ts +148 -0
  88. package/src/agents/__tests__/AgentTypes.test.ts +534 -0
  89. package/src/agents/__tests__/AgentWalletRegistry.test.ts +152 -0
  90. package/src/agents/__tests__/AuthenticatedCRDT.test.ts +558 -0
  91. package/src/agents/__tests__/CapabilityMatcher.prod.test.ts +117 -0
  92. package/src/agents/__tests__/CapabilityMatcher.test.ts +178 -0
  93. package/src/agents/__tests__/CrossRealityHandoff.test.ts +402 -0
  94. package/src/agents/__tests__/CulturalMemory.test.ts +200 -0
  95. package/src/agents/__tests__/FederatedRegistryAdapter.test.ts +409 -0
  96. package/src/agents/__tests__/NormEngine.test.ts +276 -0
  97. package/src/agents/__tests__/OrchestratorAgent.test.ts +182 -0
  98. package/src/agents/__tests__/SkillWorkflowEngine.test.ts +357 -0
  99. package/src/agents/__tests__/TaskDelegationService.test.ts +446 -0
  100. package/src/agents/index.ts +107 -0
  101. package/src/agents/spatial-comms/Layer1RealTime.ts +621 -0
  102. package/src/agents/spatial-comms/Layer2A2A.ts +661 -0
  103. package/src/agents/spatial-comms/Layer3MCP.ts +651 -0
  104. package/src/agents/spatial-comms/ProtocolTypes.ts +543 -0
  105. package/src/agents/spatial-comms/SpatialCommClient.ts +483 -0
  106. package/src/agents/spatial-comms/__tests__/performance-benchmark.test.ts +465 -0
  107. package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +409 -0
  108. package/src/agents/spatial-comms/index.ts +66 -0
  109. package/src/ai/AIAdapter.ts +313 -0
  110. package/src/ai/AICopilot.ts +331 -0
  111. package/src/ai/AIOutputValidator.ts +203 -0
  112. package/src/ai/BTNodes.ts +239 -0
  113. package/src/ai/BehaviorSelector.ts +135 -0
  114. package/src/ai/BehaviorTree.ts +153 -0
  115. package/src/ai/Blackboard.ts +165 -0
  116. package/src/ai/GenerationAnalytics.ts +461 -0
  117. package/src/ai/GenerationCache.ts +265 -0
  118. package/src/ai/GoalPlanner.ts +165 -0
  119. package/src/ai/HoloScriptGenerator.ts +580 -0
  120. package/src/ai/InfluenceMap.ts +180 -0
  121. package/src/ai/NavMesh.ts +168 -0
  122. package/src/ai/PerceptionSystem.ts +178 -0
  123. package/src/ai/PromptTemplates.ts +453 -0
  124. package/src/ai/SemanticSearchService.ts +80 -0
  125. package/src/ai/StateMachine.ts +196 -0
  126. package/src/ai/SteeringBehavior.ts +150 -0
  127. package/src/ai/SteeringBehaviors.ts +244 -0
  128. package/src/ai/TrainingDataGenerator.ts +1082 -0
  129. package/src/ai/UtilityAI.ts +145 -0
  130. package/src/ai/__tests__/AIAdapter.prod.test.ts +259 -0
  131. package/src/ai/__tests__/AIAdapter.test.ts +109 -0
  132. package/src/ai/__tests__/AICopilot.prod.test.ts +341 -0
  133. package/src/ai/__tests__/AICopilot.test.ts +178 -0
  134. package/src/ai/__tests__/AIOutputValidator.prod.test.ts +226 -0
  135. package/src/ai/__tests__/AIOutputValidator.test.ts +138 -0
  136. package/src/ai/__tests__/BTNodes.prod.test.ts +391 -0
  137. package/src/ai/__tests__/BTNodes.test.ts +263 -0
  138. package/src/ai/__tests__/BehaviorSelector.prod.test.ts +129 -0
  139. package/src/ai/__tests__/BehaviorSelector.test.ts +132 -0
  140. package/src/ai/__tests__/BehaviorTree.prod.test.ts +266 -0
  141. package/src/ai/__tests__/BehaviorTree.test.ts +216 -0
  142. package/src/ai/__tests__/Blackboard.prod.test.ts +339 -0
  143. package/src/ai/__tests__/Blackboard.test.ts +183 -0
  144. package/src/ai/__tests__/GenerationAnalytics.prod.test.ts +141 -0
  145. package/src/ai/__tests__/GenerationAnalytics.test.ts +165 -0
  146. package/src/ai/__tests__/GenerationCache.prod.test.ts +144 -0
  147. package/src/ai/__tests__/GenerationCache.test.ts +171 -0
  148. package/src/ai/__tests__/GoalPlanner.prod.test.ts +189 -0
  149. package/src/ai/__tests__/GoalPlanner.test.ts +137 -0
  150. package/src/ai/__tests__/GoalPlannerDepth.prod.test.ts +217 -0
  151. package/src/ai/__tests__/HoloScriptGenerator.test.ts +125 -0
  152. package/src/ai/__tests__/InfluenceMap.prod.test.ts +146 -0
  153. package/src/ai/__tests__/InfluenceMap.test.ts +149 -0
  154. package/src/ai/__tests__/NavMesh.prod.test.ts +141 -0
  155. package/src/ai/__tests__/NavMesh.test.ts +159 -0
  156. package/src/ai/__tests__/PerceptionSystem.prod.test.ts +135 -0
  157. package/src/ai/__tests__/PerceptionSystem.test.ts +250 -0
  158. package/src/ai/__tests__/PromptTemplates.prod.test.ts +313 -0
  159. package/src/ai/__tests__/PromptTemplates.test.ts +146 -0
  160. package/src/ai/__tests__/SemanticSearch.test.ts +37 -0
  161. package/src/ai/__tests__/StateMachine.prod.test.ts +162 -0
  162. package/src/ai/__tests__/StateMachine.test.ts +163 -0
  163. package/src/ai/__tests__/SteeringBehavior.prod.test.ts +251 -0
  164. package/src/ai/__tests__/SteeringBehavior.test.ts +135 -0
  165. package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +133 -0
  166. package/src/ai/__tests__/SteeringBehaviors.test.ts +151 -0
  167. package/src/ai/__tests__/TrainingDataGenerator.prod.test.ts +286 -0
  168. package/src/ai/__tests__/TrainingDataGenerator.test.ts +286 -0
  169. package/src/ai/__tests__/UtilityAI.prod.test.ts +207 -0
  170. package/src/ai/__tests__/UtilityAI.test.ts +155 -0
  171. package/src/ai/__tests__/adapters.prod.test.ts +263 -0
  172. package/src/ai/__tests__/adapters.test.ts +320 -0
  173. package/src/ai/adapters.ts +1585 -0
  174. package/src/ai/index.ts +130 -0
  175. package/src/behavior/BehaviorPresets.ts +140 -0
  176. package/src/behavior/BehaviorTree.ts +236 -0
  177. package/src/behavior/StateMachine.ts +176 -0
  178. package/src/behavior/StateTrait.ts +67 -0
  179. package/src/behavior/index.ts +8 -0
  180. package/src/behavior.ts +8 -0
  181. package/src/board/audit.ts +284 -0
  182. package/src/board/board-ops.ts +336 -0
  183. package/src/board/board-types.ts +302 -0
  184. package/src/board/index.ts +69 -0
  185. package/src/define-agent.ts +46 -0
  186. package/src/define-team.ts +33 -0
  187. package/src/delegation.ts +265 -0
  188. package/src/distributed-claimer.ts +228 -0
  189. package/src/economy/AgentBudgetEnforcer.ts +464 -0
  190. package/src/economy/BountyManager.ts +185 -0
  191. package/src/economy/CreatorRevenueAggregator.ts +460 -0
  192. package/src/economy/InvisibleWallet.ts +82 -0
  193. package/src/economy/KnowledgeMarketplace.ts +193 -0
  194. package/src/economy/PaymentWebhookService.ts +512 -0
  195. package/src/economy/RevenueSplitter.ts +156 -0
  196. package/src/economy/SubscriptionManager.ts +546 -0
  197. package/src/economy/UnifiedBudgetOptimizer.ts +635 -0
  198. package/src/economy/UsageMeter.ts +440 -0
  199. package/src/economy/_core-stubs.ts +219 -0
  200. package/src/economy/index.ts +100 -0
  201. package/src/economy/x402-facilitator.ts +1978 -0
  202. package/src/index.ts +348 -0
  203. package/src/knowledge/__tests__/knowledge-consolidator.test.ts +444 -0
  204. package/src/knowledge/__tests__/knowledge-store-vector.test.ts +291 -0
  205. package/src/knowledge/brain.ts +167 -0
  206. package/src/knowledge/consolidation.ts +581 -0
  207. package/src/knowledge/knowledge-consolidator.ts +510 -0
  208. package/src/knowledge/knowledge-store.ts +616 -0
  209. package/src/learning/MemoryConsolidator.ts +102 -0
  210. package/src/learning/MemoryScorer.ts +69 -0
  211. package/src/learning/ProceduralCompiler.ts +45 -0
  212. package/src/learning/SemanticClusterer.ts +66 -0
  213. package/src/learning/index.ts +8 -0
  214. package/src/llm/llm-adapter.ts +159 -0
  215. package/src/mesh/index.ts +309 -0
  216. package/src/negotiation/NegotiationProtocol.ts +694 -0
  217. package/src/negotiation/NegotiationTypes.ts +473 -0
  218. package/src/negotiation/VotingMechanisms.ts +691 -0
  219. package/src/negotiation/index.ts +49 -0
  220. package/src/protocol/goal-synthesizer.ts +317 -0
  221. package/src/protocol/implementations.ts +474 -0
  222. package/src/protocol/micro-phase-decomposer.ts +299 -0
  223. package/src/protocol/micro-step-decomposer.test.ts +306 -0
  224. package/src/protocol-agent.test.ts +353 -0
  225. package/src/protocol-agent.ts +670 -0
  226. package/src/self-improve/absorb-scanner.ts +252 -0
  227. package/src/self-improve/evolution-engine.ts +149 -0
  228. package/src/self-improve/framework-absorber.ts +214 -0
  229. package/src/self-improve/index.ts +50 -0
  230. package/src/self-improve/prompt-optimizer.ts +212 -0
  231. package/src/self-improve/test-generator.ts +175 -0
  232. package/src/skill-router.ts +186 -0
  233. package/src/skills/index.ts +5 -0
  234. package/src/skills/skill-md-bridge.ts +1699 -0
  235. package/src/swarm/ACOEngine.ts +261 -0
  236. package/src/swarm/CollectiveIntelligence.ts +383 -0
  237. package/src/swarm/ContributionSynthesizer.ts +481 -0
  238. package/src/swarm/LeaderElection.ts +393 -0
  239. package/src/swarm/PSOEngine.ts +206 -0
  240. package/src/swarm/QuorumPolicy.ts +173 -0
  241. package/src/swarm/SwarmCoordinator.ts +335 -0
  242. package/src/swarm/SwarmManager.ts +442 -0
  243. package/src/swarm/SwarmMembership.ts +456 -0
  244. package/src/swarm/VotingRound.ts +255 -0
  245. package/src/swarm/__tests__/ACOEngine.prod.test.ts +164 -0
  246. package/src/swarm/__tests__/ACOEngine.test.ts +117 -0
  247. package/src/swarm/__tests__/CollectiveIntelligence.prod.test.ts +296 -0
  248. package/src/swarm/__tests__/CollectiveIntelligence.test.ts +457 -0
  249. package/src/swarm/__tests__/ContributionSynthesizer.prod.test.ts +269 -0
  250. package/src/swarm/__tests__/ContributionSynthesizer.test.ts +254 -0
  251. package/src/swarm/__tests__/LeaderElection.prod.test.ts +196 -0
  252. package/src/swarm/__tests__/LeaderElection.test.ts +151 -0
  253. package/src/swarm/__tests__/PSOEngine.prod.test.ts +162 -0
  254. package/src/swarm/__tests__/PSOEngine.test.ts +106 -0
  255. package/src/swarm/__tests__/QuorumPolicy.prod.test.ts +216 -0
  256. package/src/swarm/__tests__/QuorumPolicy.test.ts +177 -0
  257. package/src/swarm/__tests__/SwarmCoordinator.prod.test.ts +186 -0
  258. package/src/swarm/__tests__/SwarmCoordinator.test.ts +167 -0
  259. package/src/swarm/__tests__/SwarmManager.prod.test.ts +308 -0
  260. package/src/swarm/__tests__/SwarmManager.test.ts +373 -0
  261. package/src/swarm/__tests__/SwarmMembership.prod.test.ts +273 -0
  262. package/src/swarm/__tests__/SwarmMembership.test.ts +264 -0
  263. package/src/swarm/__tests__/VotingRound.prod.test.ts +233 -0
  264. package/src/swarm/__tests__/VotingRound.test.ts +174 -0
  265. package/src/swarm/analytics/SwarmInspector.ts +476 -0
  266. package/src/swarm/analytics/SwarmMetrics.ts +449 -0
  267. package/src/swarm/analytics/__tests__/SwarmInspector.prod.test.ts +366 -0
  268. package/src/swarm/analytics/__tests__/SwarmInspector.test.ts +454 -0
  269. package/src/swarm/analytics/__tests__/SwarmMetrics.prod.test.ts +254 -0
  270. package/src/swarm/analytics/__tests__/SwarmMetrics.test.ts +370 -0
  271. package/src/swarm/analytics/index.ts +7 -0
  272. package/src/swarm/index.ts +69 -0
  273. package/src/swarm/messaging/BroadcastChannel.ts +509 -0
  274. package/src/swarm/messaging/GossipProtocol.ts +565 -0
  275. package/src/swarm/messaging/SwarmEventBus.ts +443 -0
  276. package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +331 -0
  277. package/src/swarm/messaging/__tests__/BroadcastChannel.test.ts +333 -0
  278. package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +356 -0
  279. package/src/swarm/messaging/__tests__/GossipProtocol.test.ts +437 -0
  280. package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +191 -0
  281. package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +247 -0
  282. package/src/swarm/messaging/index.ts +8 -0
  283. package/src/swarm/spatial/FlockingBehavior.ts +462 -0
  284. package/src/swarm/spatial/FormationController.ts +500 -0
  285. package/src/swarm/spatial/Vector3.ts +170 -0
  286. package/src/swarm/spatial/ZoneClaiming.ts +509 -0
  287. package/src/swarm/spatial/__tests__/FlockingBehavior.prod.test.ts +239 -0
  288. package/src/swarm/spatial/__tests__/FlockingBehavior.test.ts +298 -0
  289. package/src/swarm/spatial/__tests__/FormationController.prod.test.ts +240 -0
  290. package/src/swarm/spatial/__tests__/FormationController.test.ts +297 -0
  291. package/src/swarm/spatial/__tests__/Vector3.prod.test.ts +283 -0
  292. package/src/swarm/spatial/__tests__/Vector3.test.ts +224 -0
  293. package/src/swarm/spatial/__tests__/ZoneClaiming.prod.test.ts +246 -0
  294. package/src/swarm/spatial/__tests__/ZoneClaiming.test.ts +374 -0
  295. package/src/swarm/spatial/index.ts +28 -0
  296. package/src/team.ts +1245 -0
  297. package/src/training/LRScheduler.ts +377 -0
  298. package/src/training/QualityScoringPipeline.ts +139 -0
  299. package/src/training/SoftDedup.ts +461 -0
  300. package/src/training/SparsityMonitor.ts +685 -0
  301. package/src/training/SparsityMonitorTypes.ts +209 -0
  302. package/src/training/SpatialTrainingDataGenerator.ts +1526 -0
  303. package/src/training/SpatialTrainingDataTypes.ts +216 -0
  304. package/src/training/TrainingPipelineConfig.ts +215 -0
  305. package/src/training/constants.ts +94 -0
  306. package/src/training/index.ts +138 -0
  307. package/src/training/schema.ts +147 -0
  308. package/src/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
  309. package/src/training/scripts/generate-spatial-dataset.ts +521 -0
  310. package/src/training/training/data/novel-use-cases.jsonl +153 -0
  311. package/src/training/training/data/spatial-reasoning-10k.jsonl +9354 -0
  312. package/src/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
  313. package/src/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
  314. package/src/training/trainingmonkey/index.ts +26 -0
  315. package/src/training/trait-mappings.ts +157 -0
  316. package/src/types/core-stubs.d.ts +113 -0
  317. package/src/types.ts +304 -0
  318. package/test-output.txt +0 -0
  319. package/test-result.json +1 -0
  320. package/tsc-errors.txt +4 -0
  321. package/tsc_output.txt +0 -0
  322. package/tsconfig.json +14 -0
  323. package/tsup-learning-esm.config.ts +12 -0
  324. package/tsup.config.ts +21 -0
  325. package/typescript-errors-2.txt +0 -0
  326. package/typescript-errors.txt +22 -0
  327. package/vitest-log-utf8.txt +268 -0
  328. package/vitest-log.txt +0 -0
  329. package/vitest.config.ts +8 -0
package/src/team.ts ADDED
@@ -0,0 +1,1245 @@
1
+ /**
2
+ * Team — The central orchestrator.
3
+ *
4
+ * A persistent, knowledge-compounding workspace where agents claim tasks,
5
+ * execute work, publish knowledge, and get smarter over time.
6
+ *
7
+ * Local-first: works entirely in-process. Optional remote board/knowledge.
8
+ */
9
+
10
+ import type {
11
+ TeamConfig,
12
+ AgentConfig,
13
+ TaskDef,
14
+ CycleResult,
15
+ AgentCycleResult,
16
+ AgentRuntime,
17
+ KnowledgeInsight,
18
+ ConsensusMode,
19
+ ProposalResult,
20
+ ReputationTier,
21
+ Suggestion,
22
+ SuggestionVoteEntry,
23
+ SuggestionCreateResult,
24
+ SuggestionVoteResult,
25
+ SuggestionListResult,
26
+ SuggestionStatus,
27
+ TeamMode,
28
+ SetModeResult,
29
+ DeriveResult,
30
+ PresenceResult,
31
+ HeartbeatResult,
32
+ AgentPresence,
33
+ AgentPresenceStatus,
34
+ PresenceConfig,
35
+ SlotRole,
36
+ } from './types';
37
+ import { KnowledgeStore } from './knowledge/knowledge-store';
38
+ import { DoneLogAuditor } from './board/audit';
39
+ import type { FullAuditResult } from './board/audit';
40
+ import type { DoneLogEntry } from './board/board-types';
41
+ import { callLLM } from './llm/llm-adapter';
42
+ import type { LLMMessage } from './llm/llm-adapter';
43
+ import { runProtocolCycle } from './protocol-agent';
44
+ import { GoalSynthesizer } from './protocol/goal-synthesizer';
45
+ import type { GoalContext, SynthesizedGoal } from './protocol/goal-synthesizer';
46
+ import type { Goal } from './protocol/implementations';
47
+ import { SmartMicroPhaseDecomposer, createLLMAdapter } from './protocol/micro-phase-decomposer';
48
+ import type { DecompositionResult, TaskDescription } from './protocol/micro-phase-decomposer';
49
+ import { parseDeriveContent, ROOM_PRESETS } from './board';
50
+ import { MeshDiscovery, SignalService, GossipProtocol } from './mesh';
51
+ import type { PeerMetadata, GossipPacket } from './mesh';
52
+ import { BountyManager } from './economy/BountyManager';
53
+ import type { Bounty, BountyReward, ClaimResult as BountyClaimResult, CompletionProof, PayoutResult } from './economy/BountyManager';
54
+
55
+ // ── Mode Claim Filters (FW-0.3) ──
56
+ // Each mode defines which SlotRoles can actively claim tasks.
57
+ // Agents whose claimFilter.roles don't overlap with the active set are deprioritized.
58
+
59
+ const MODE_CLAIM_ROLES: Record<TeamMode, SlotRole[]> = {
60
+ audit: ['researcher', 'reviewer', 'tester'],
61
+ build: ['coder', 'tester'],
62
+ research: ['researcher'],
63
+ review: ['reviewer', 'researcher'],
64
+ };
65
+
66
+ // ── Internal Proposal (not exported — use ProposalResult from types) ──
67
+
68
+ interface InternalProposal<T = unknown> {
69
+ id: string;
70
+ key: string;
71
+ value: T;
72
+ proposedBy: string;
73
+ votes: Map<string, boolean>;
74
+ status: 'pending' | 'accepted' | 'rejected';
75
+ createdAt: number;
76
+ resolvedAt?: number;
77
+ }
78
+
79
+ // ── Reputation ──
80
+
81
+ const REPUTATION_THRESHOLDS: Record<ReputationTier, number> = {
82
+ newcomer: 0,
83
+ contributor: 5,
84
+ expert: 30,
85
+ authority: 100,
86
+ };
87
+
88
+ function computeTier(score: number): ReputationTier {
89
+ if (score >= REPUTATION_THRESHOLDS.authority) return 'authority';
90
+ if (score >= REPUTATION_THRESHOLDS.expert) return 'expert';
91
+ if (score >= REPUTATION_THRESHOLDS.contributor) return 'contributor';
92
+ return 'newcomer';
93
+ }
94
+
95
+ /** When fewer than this many open tasks exist, agents synthesize goals instead of skipping. */
96
+ const OPEN_TASK_THRESHOLD = 3;
97
+
98
+ /** Default presence timeouts (FW-0.3). */
99
+ const DEFAULT_IDLE_TIMEOUT_MS = 60_000; // 60 seconds
100
+ const DEFAULT_OFFLINE_TIMEOUT_MS = 300_000; // 5 minutes
101
+
102
+ /** Internal record for tracking an agent's presence state. */
103
+ interface PresenceRecord {
104
+ firstSeen: number;
105
+ lastSeen: number;
106
+ currentTask?: string;
107
+ }
108
+
109
+ export class Team {
110
+ readonly name: string;
111
+ readonly knowledge: KnowledgeStore;
112
+
113
+ private config: TeamConfig;
114
+ private agentConfigs: AgentConfig[];
115
+ private runtimes: Map<string, AgentRuntime> = new Map();
116
+ private board: TaskDef[] = [];
117
+ private doneLog: Array<{ taskId: string; title: string; completedBy: string; timestamp: string }> = [];
118
+ private cycle = 0;
119
+ private consensusMode: ConsensusMode;
120
+ private proposals: Map<string, InternalProposal> = new Map();
121
+ private localSuggestions: Suggestion[] = [];
122
+ private currentMode: TeamMode = 'build';
123
+ private modeObjective: string = ROOM_PRESETS['build'].objective;
124
+ private modeRules: string[] = ROOM_PRESETS['build'].rules;
125
+ private goalSynthesizer: GoalSynthesizer;
126
+ private decomposer: SmartMicroPhaseDecomposer | null = null;
127
+
128
+ // ── Local presence tracking (FW-0.3) ──
129
+ private presenceRecords: Map<string, PresenceRecord> = new Map();
130
+ private idleTimeoutMs: number;
131
+ private offlineTimeoutMs: number;
132
+
133
+ // ── Mesh integration (FW-0.4) ──
134
+ readonly mesh: MeshDiscovery;
135
+ readonly signals: SignalService;
136
+ readonly gossip: GossipProtocol;
137
+
138
+ // ── Bounty system (FW-0.6) ──
139
+ readonly bounties: BountyManager;
140
+
141
+ constructor(config: TeamConfig) {
142
+ this.config = config;
143
+ this.name = config.name;
144
+ this.agentConfigs = config.agents;
145
+ this.consensusMode = config.consensus ?? 'simple_majority';
146
+ this.idleTimeoutMs = config.presence?.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
147
+ this.offlineTimeoutMs = config.presence?.offlineTimeoutMs ?? DEFAULT_OFFLINE_TIMEOUT_MS;
148
+
149
+ this.knowledge = new KnowledgeStore(
150
+ config.knowledge ?? { persist: false }
151
+ );
152
+
153
+ // GoalSynthesizer gets knowledge store so it can derive context-aware goals (FW-0.2)
154
+ // LLM config comes from first agent — all agents on a team typically share a provider.
155
+ const firstAgent = this.agentConfigs[0];
156
+ this.goalSynthesizer = new GoalSynthesizer({
157
+ knowledge: this.knowledge,
158
+ llm: firstAgent?.model,
159
+ });
160
+
161
+ // SmartMicroPhaseDecomposer — LLM-powered task decomposition (FW-0.2)
162
+ if (firstAgent?.model) {
163
+ this.decomposer = new SmartMicroPhaseDecomposer(createLLMAdapter(firstAgent.model));
164
+ }
165
+
166
+ // Mesh integration (FW-0.4) — peer discovery, signaling, gossip
167
+ this.mesh = new MeshDiscovery(config.name);
168
+ this.signals = new SignalService(config.name);
169
+ this.gossip = new GossipProtocol();
170
+
171
+ // Bounty system (FW-0.6)
172
+ this.bounties = new BountyManager();
173
+
174
+ // Initialize agent runtimes
175
+ for (const agent of this.agentConfigs) {
176
+ this.runtimes.set(agent.name, {
177
+ name: agent.name,
178
+ role: agent.role,
179
+ config: agent,
180
+ tasksCompleted: 0,
181
+ knowledgePublished: 0,
182
+ reputationScore: 0,
183
+ reputationTier: 'newcomer',
184
+ });
185
+ }
186
+ }
187
+
188
+ /** Whether this team is connected to a remote HoloMesh board. */
189
+ get isRemote(): boolean {
190
+ return !!(this.config.boardUrl && this.config.boardApiKey);
191
+ }
192
+
193
+ /** POST/GET/PATCH to remote board API. Returns null if local-only. */
194
+ private async boardFetch(
195
+ path: string,
196
+ method: 'GET' | 'POST' | 'PATCH',
197
+ body?: Record<string, unknown>
198
+ ): Promise<Record<string, unknown> | null> {
199
+ if (!this.config.boardUrl || !this.config.boardApiKey) return null;
200
+ try {
201
+ const res = await fetch(`${this.config.boardUrl}${path}`, {
202
+ method,
203
+ headers: {
204
+ 'Content-Type': 'application/json',
205
+ Authorization: `Bearer ${this.config.boardApiKey}`,
206
+ },
207
+ body: body ? JSON.stringify(body) : undefined,
208
+ signal: AbortSignal.timeout(15_000),
209
+ });
210
+ return (await res.json()) as Record<string, unknown>;
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+
216
+ // ── Board Management ──
217
+
218
+ /** Add tasks to the board. Deduplicates by normalized title. */
219
+ async addTasks(tasks: Array<Omit<TaskDef, 'id' | 'status' | 'createdAt'>>): Promise<TaskDef[]> {
220
+ if (this.isRemote) {
221
+ const res = await this.boardFetch(`/api/holomesh/team/${encodeURIComponent(this.name)}/board`, 'POST', { tasks });
222
+ if (res && res.error) throw new Error(String(res.error));
223
+ return (res?.tasks || res?.added || []) as TaskDef[];
224
+ }
225
+
226
+ const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().slice(0, 60);
227
+ const existing = new Set([
228
+ ...this.board.map(t => normalize(t.title)),
229
+ ...this.doneLog.map(d => normalize(d.title)),
230
+ ]);
231
+
232
+ const added: TaskDef[] = [];
233
+ for (const t of tasks) {
234
+ const norm = normalize(t.title);
235
+ if (existing.has(norm)) continue;
236
+ existing.add(norm);
237
+
238
+ const task: TaskDef = {
239
+ id: `task_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
240
+ title: t.title,
241
+ description: t.description,
242
+ priority: t.priority,
243
+ role: t.role,
244
+ source: t.source,
245
+ status: 'open',
246
+ createdAt: new Date().toISOString(),
247
+ };
248
+ this.board.push(task);
249
+ added.push(task);
250
+ }
251
+ return added;
252
+ }
253
+
254
+ /** Get all open tasks, sorted by priority. */
255
+ get openTasks(): TaskDef[] {
256
+ return this.board
257
+ .filter(t => t.status === 'open')
258
+ .sort((a, b) => a.priority - b.priority);
259
+ }
260
+
261
+ /** Get all board tasks. */
262
+ get allTasks(): TaskDef[] {
263
+ return [...this.board];
264
+ }
265
+
266
+ /** Get completed task count. */
267
+ get completedCount(): number {
268
+ return this.doneLog.length;
269
+ }
270
+
271
+ // ── Work Cycle ──
272
+
273
+ /**
274
+ * Run one work cycle: each agent checks the board, claims a matching task,
275
+ * executes it via LLM, publishes knowledge, and compounds insights.
276
+ */
277
+ async runCycle(): Promise<CycleResult> {
278
+ const start = Date.now();
279
+ this.cycle++;
280
+
281
+ // Remote mode: fetch board, claim via API, execute locally, mark done via API
282
+ if (this.isRemote) {
283
+ return this.runRemoteCycle(start);
284
+ }
285
+
286
+ const results: AgentCycleResult[] = [];
287
+ const allInsights: KnowledgeInsight[] = [];
288
+ const claimed = new Set<string>();
289
+
290
+ for (const [, runtime] of this.runtimes) {
291
+ const agent = runtime.config;
292
+
293
+ // Find a matching task
294
+ let task = this.findClaimableTask(runtime, claimed);
295
+ let synthesized = false;
296
+
297
+ // When no task found and board is sparse, synthesize a goal autonomously (FW-0.2)
298
+ if (!task && this.openTasks.length < OPEN_TASK_THRESHOLD) {
299
+ const domain = agent.knowledgeDomains?.[0] ?? 'general';
300
+ const goalContext: GoalContext = {
301
+ domain,
302
+ teamName: this.name,
303
+ agentName: runtime.name,
304
+ capabilities: agent.capabilities,
305
+ recentCompletedTasks: this.doneLog.slice(-10).map(d => d.title),
306
+ };
307
+ try {
308
+ const goals = await this.goalSynthesizer.synthesizeMultiple(goalContext, 1);
309
+ if (goals.length > 0) {
310
+ task = this.goalToTask(goals[0], agent.role);
311
+ synthesized = true;
312
+ }
313
+ } catch {
314
+ // Fallback to synchronous heuristic
315
+ const goal = this.goalSynthesizer.synthesize(domain, 'autonomous-boredom');
316
+ task = this.goalToTask(goal, agent.role);
317
+ synthesized = true;
318
+ }
319
+ }
320
+
321
+ if (!task) {
322
+ results.push({
323
+ agentName: runtime.name,
324
+ taskId: null,
325
+ taskTitle: null,
326
+ action: 'skipped',
327
+ summary: 'No matching open tasks',
328
+ knowledge: [],
329
+ });
330
+ continue;
331
+ }
332
+
333
+ claimed.add(task.id);
334
+ task.status = 'claimed';
335
+ task.claimedBy = runtime.name;
336
+
337
+ // Auto-heartbeat: record agent as alive with current task (FW-0.3)
338
+ this.localHeartbeat(runtime.name, task.title);
339
+
340
+ // Execute via LLM
341
+ try {
342
+ const { summary, insights } = await this.executeTask(runtime, task);
343
+
344
+ // Mark done
345
+ task.status = 'done';
346
+ task.completedAt = new Date().toISOString();
347
+ this.board = this.board.filter(t => t.id !== task.id);
348
+ this.doneLog.push({
349
+ taskId: task.id,
350
+ title: task.title,
351
+ completedBy: runtime.name,
352
+ timestamp: task.completedAt,
353
+ });
354
+
355
+ // Publish knowledge
356
+ for (const insight of insights) {
357
+ this.knowledge.publish(insight, runtime.name);
358
+ runtime.knowledgePublished++;
359
+ }
360
+
361
+ // Update reputation
362
+ runtime.tasksCompleted++;
363
+ runtime.reputationScore += 1 + insights.length * 0.5;
364
+ runtime.reputationTier = computeTier(runtime.reputationScore);
365
+
366
+ allInsights.push(...insights);
367
+ results.push({
368
+ agentName: runtime.name,
369
+ taskId: task.id,
370
+ taskTitle: task.title,
371
+ action: synthesized ? 'synthesized' : 'completed',
372
+ summary,
373
+ knowledge: insights,
374
+ });
375
+ } catch (err) {
376
+ task.status = 'open';
377
+ task.claimedBy = undefined;
378
+ results.push({
379
+ agentName: runtime.name,
380
+ taskId: task.id,
381
+ taskTitle: task.title,
382
+ action: 'error',
383
+ summary: err instanceof Error ? err.message : String(err),
384
+ knowledge: [],
385
+ });
386
+ }
387
+ }
388
+
389
+ // Compound knowledge
390
+ const compounded = this.knowledge.compound(allInsights);
391
+
392
+ return {
393
+ teamName: this.name,
394
+ cycle: this.cycle,
395
+ agentResults: results,
396
+ knowledgeProduced: allInsights,
397
+ compoundedInsights: compounded,
398
+ durationMs: Date.now() - start,
399
+ };
400
+ }
401
+
402
+ /** Remote work cycle: board ops via HoloMesh API, execution local via LLM. */
403
+ private async runRemoteCycle(start: number): Promise<CycleResult> {
404
+ const teamId = encodeURIComponent(this.name);
405
+ const results: AgentCycleResult[] = [];
406
+ const allInsights: KnowledgeInsight[] = [];
407
+
408
+ // Fetch board state from remote
409
+ const boardData = await this.boardFetch(`/api/holomesh/team/${teamId}/board`, 'GET');
410
+ const openTasks = ((boardData?.board as Record<string, unknown>)?.open as TaskDef[] || [])
411
+ .sort((a, b) => a.priority - b.priority);
412
+
413
+ const claimed = new Set<string>();
414
+
415
+ for (const [, runtime] of this.runtimes) {
416
+ const agent = runtime.config;
417
+
418
+ // Find matching task from remote board
419
+ let task = this.findClaimableTask(runtime, claimed, openTasks);
420
+ let synthesized = false;
421
+
422
+ // When no task found and remote board is sparse, synthesize a goal autonomously (FW-0.2)
423
+ if (!task && openTasks.length < OPEN_TASK_THRESHOLD) {
424
+ const domain = agent.knowledgeDomains?.[0] ?? 'general';
425
+ let goal: Goal;
426
+ try {
427
+ const goals = await this.goalSynthesizer.synthesizeMultiple({
428
+ domain,
429
+ teamName: this.name,
430
+ agentName: runtime.name,
431
+ capabilities: agent.capabilities,
432
+ recentCompletedTasks: this.doneLog.slice(-10).map(d => d.title),
433
+ }, 1);
434
+ goal = goals[0] ?? this.goalSynthesizer.synthesize(domain, 'autonomous-boredom');
435
+ } catch {
436
+ goal = this.goalSynthesizer.synthesize(domain, 'autonomous-boredom');
437
+ }
438
+ const addRes = await this.boardFetch(`/api/holomesh/team/${teamId}/board`, 'POST', {
439
+ tasks: [{
440
+ title: goal.description,
441
+ description: `Autonomously synthesized goal [${goal.category}] — priority ${goal.priority}`,
442
+ priority: goal.priority === 'high' ? 2 : goal.priority === 'medium' ? 4 : 6,
443
+ role: (agent.role === 'architect' || agent.role === 'researcher') ? 'researcher' : 'coder',
444
+ source: `synthesizer:${goal.source}`,
445
+ }],
446
+ });
447
+ const addedTasks = (addRes?.tasks || addRes?.added || []) as TaskDef[];
448
+ task = addedTasks[0];
449
+ synthesized = true;
450
+ }
451
+
452
+ if (!task) {
453
+ results.push({ agentName: runtime.name, taskId: null, taskTitle: null, action: 'skipped', summary: 'No matching open tasks', knowledge: [] });
454
+ continue;
455
+ }
456
+
457
+ claimed.add(task.id);
458
+
459
+ // Auto-heartbeat: record agent as alive with current task (FW-0.3)
460
+ this.localHeartbeat(runtime.name, task.title);
461
+
462
+ // Claim via API
463
+ const claimRes = await this.boardFetch(`/api/holomesh/team/${teamId}/board/${encodeURIComponent(task.id)}`, 'PATCH', { action: 'claim' });
464
+ if (claimRes?.error) {
465
+ results.push({ agentName: runtime.name, taskId: task.id, taskTitle: task.title, action: 'error', summary: `Claim failed: ${claimRes.error}`, knowledge: [] });
466
+ continue;
467
+ }
468
+
469
+ // Proactively claim any associated bounties
470
+ const taskBounties = this.bounties.byTask(task.id);
471
+ for (const b of taskBounties) {
472
+ if (b.status === 'open') {
473
+ this.claimBountyForAgent(b.id, runtime.name);
474
+ }
475
+ }
476
+
477
+ // Proactively buy relevant knowledge if cheap enough (e.g. < 5 credits)
478
+ const activeListings = this.knowledge.marketplace.activeListings();
479
+ for (const listing of activeListings) {
480
+ if (listing.price <= 5 && listing.currency === 'credits' && task.title.toLowerCase().includes(listing.preview.domain?.toLowerCase() || '')) {
481
+ this.knowledge.marketplace.buyKnowledge(listing.id, runtime.name);
482
+ }
483
+ }
484
+
485
+ // Execute locally via LLM
486
+ try {
487
+ const { summary, insights } = await this.executeTask(runtime, task);
488
+
489
+ // Mark done via API
490
+ await this.boardFetch(`/api/holomesh/team/${teamId}/board/${encodeURIComponent(task.id)}`, 'PATCH', { action: 'done', summary });
491
+
492
+ // Complete the bounty with proof
493
+ for (const b of taskBounties) {
494
+ if (b.status === 'claimed' && b.claimedBy === runtime.name) {
495
+ this.completeBountyWithProof(b.id, { summary });
496
+ }
497
+ }
498
+
499
+ // Publish knowledge locally + compound
500
+ for (const insight of insights) {
501
+ this.knowledge.publish(insight, runtime.name);
502
+ runtime.knowledgePublished++;
503
+ }
504
+
505
+ runtime.tasksCompleted++;
506
+ runtime.reputationScore += 1 + insights.length * 0.5;
507
+ runtime.reputationTier = computeTier(runtime.reputationScore);
508
+
509
+ allInsights.push(...insights);
510
+ results.push({ agentName: runtime.name, taskId: task.id, taskTitle: task.title, action: synthesized ? 'synthesized' : 'completed', summary, knowledge: insights });
511
+ } catch (err) {
512
+ // Reopen task on failure
513
+ await this.boardFetch(`/api/holomesh/team/${teamId}/board/${encodeURIComponent(task.id)}`, 'PATCH', { action: 'reopen' });
514
+ results.push({ agentName: runtime.name, taskId: task.id, taskTitle: task.title, action: 'error', summary: err instanceof Error ? err.message : String(err), knowledge: [] });
515
+ }
516
+ }
517
+
518
+ const compounded = this.knowledge.compound(allInsights);
519
+
520
+ // Sync knowledge to remote if configured
521
+ if (this.knowledge['config'].remoteUrl) {
522
+ await this.knowledge.syncToRemote();
523
+ }
524
+
525
+ return { teamName: this.name, cycle: this.cycle, agentResults: results, knowledgeProduced: allInsights, compoundedInsights: compounded, durationMs: Date.now() - start };
526
+ }
527
+
528
+ // ── Consensus ──
529
+
530
+ /** Propose a decision for the team to vote on. */
531
+ async propose<T>(key: string, value: T): Promise<ProposalResult<T>> {
532
+ const proposal: InternalProposal<T> = {
533
+ id: `prop_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
534
+ key,
535
+ value,
536
+ proposedBy: 'team',
537
+ votes: new Map(),
538
+ status: 'pending',
539
+ createdAt: Date.now(),
540
+ };
541
+
542
+ // Each agent votes based on their LLM
543
+ for (const [, runtime] of this.runtimes) {
544
+ try {
545
+ const vote = await this.getAgentVote(runtime, key, value);
546
+ proposal.votes.set(runtime.name, vote);
547
+ } catch {
548
+ // Abstain on error
549
+ }
550
+ }
551
+
552
+ const votesFor = Array.from(proposal.votes.values()).filter(v => v).length;
553
+ const votesAgainst = proposal.votes.size - votesFor;
554
+ const total = proposal.votes.size;
555
+
556
+ let accepted: boolean;
557
+ switch (this.consensusMode) {
558
+ case 'unanimous':
559
+ accepted = votesFor === total && total > 0;
560
+ break;
561
+ case 'owner_decides':
562
+ accepted = true; // owner always decides yes (simplified)
563
+ break;
564
+ case 'simple_majority':
565
+ default:
566
+ accepted = votesFor > total / 2;
567
+ break;
568
+ }
569
+
570
+ proposal.status = accepted ? 'accepted' : 'rejected';
571
+ proposal.resolvedAt = Date.now();
572
+ this.proposals.set(proposal.id, proposal);
573
+
574
+ return {
575
+ proposalId: proposal.id,
576
+ accepted,
577
+ votesFor,
578
+ votesAgainst,
579
+ votesTotal: total,
580
+ value,
581
+ };
582
+ }
583
+
584
+ // ── Reputation ──
585
+
586
+ /** Get the reputation leaderboard. */
587
+ leaderboard(): AgentRuntime[] {
588
+ return Array.from(this.runtimes.values())
589
+ .sort((a, b) => b.reputationScore - a.reputationScore);
590
+ }
591
+
592
+ /** Get a specific agent's runtime. */
593
+ getAgent(name: string): AgentRuntime | undefined {
594
+ return this.runtimes.get(name);
595
+ }
596
+
597
+ // ── Audit (FW-0.3) ──
598
+
599
+ /**
600
+ * Audit the done log: check for missing fields, duplicates, non-monotonic
601
+ * timestamps, and return aggregate statistics.
602
+ *
603
+ * Works in both local and remote modes. In local mode, the internal doneLog
604
+ * is converted to DoneLogEntry format. In remote mode, fetches the board
605
+ * and extracts the done log from the response.
606
+ */
607
+ async audit(): Promise<FullAuditResult> {
608
+ let entries: DoneLogEntry[];
609
+
610
+ if (this.isRemote) {
611
+ const boardData = await this.listBoard();
612
+ const board = boardData.board as Record<string, unknown> | undefined;
613
+ const rawDone = (board?.done_log ?? board?.doneLog ?? []) as Array<Record<string, unknown>>;
614
+ entries = rawDone.map((d) => ({
615
+ taskId: String(d.taskId ?? d.task_id ?? ''),
616
+ title: String(d.title ?? ''),
617
+ completedBy: String(d.completedBy ?? d.completed_by ?? ''),
618
+ commitHash: d.commitHash as string | undefined ?? d.commit_hash as string | undefined,
619
+ timestamp: String(d.timestamp ?? d.completedAt ?? ''),
620
+ summary: String(d.summary ?? ''),
621
+ }));
622
+ } else {
623
+ entries = this.doneLog.map((d) => ({
624
+ taskId: d.taskId,
625
+ title: d.title,
626
+ completedBy: d.completedBy,
627
+ timestamp: d.timestamp,
628
+ summary: '',
629
+ }));
630
+ }
631
+
632
+ const auditor = new DoneLogAuditor(entries);
633
+ return auditor.fullAudit();
634
+ }
635
+
636
+ // ── Suggestions (FW-0.3 — local-first with optional remote) ──
637
+
638
+ /**
639
+ * Create a suggestion for the team.
640
+ * Works locally (in-memory) and remotely (delegates to HoloMesh API).
641
+ */
642
+ async suggest(
643
+ title: string,
644
+ opts?: {
645
+ description?: string;
646
+ category?: string;
647
+ evidence?: string;
648
+ proposedBy?: string;
649
+ autoPromoteThreshold?: number;
650
+ autoDismissThreshold?: number;
651
+ }
652
+ ): Promise<SuggestionCreateResult> {
653
+ if (this.isRemote) {
654
+ const teamId = encodeURIComponent(this.name);
655
+ const res = await this.boardFetch(`/api/holomesh/team/${teamId}/suggestions`, 'POST', {
656
+ title,
657
+ description: opts?.description,
658
+ category: opts?.category,
659
+ evidence: opts?.evidence,
660
+ });
661
+ if (!res) throw new Error('Failed to create suggestion — no response from board');
662
+ if (res.error) throw new Error(String(res.error));
663
+ return res as unknown as SuggestionCreateResult;
664
+ }
665
+
666
+ const trimmedTitle = title.trim().slice(0, 200);
667
+ if (!trimmedTitle) throw new Error('title is required');
668
+
669
+ const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
670
+ const existingNorm = new Set(
671
+ this.localSuggestions.filter(s => s.status === 'open').map(s => normalize(s.title))
672
+ );
673
+ if (existingNorm.has(normalize(trimmedTitle))) {
674
+ throw new Error('A similar open suggestion already exists');
675
+ }
676
+
677
+ const suggestion: Suggestion = {
678
+ id: `sug_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
679
+ title: trimmedTitle,
680
+ description: opts?.description?.slice(0, 2000),
681
+ category: opts?.category,
682
+ evidence: opts?.evidence?.slice(0, 1000),
683
+ proposedBy: opts?.proposedBy ?? 'anonymous',
684
+ status: 'open',
685
+ votes: [],
686
+ score: 0,
687
+ createdAt: new Date().toISOString(),
688
+ autoPromoteThreshold: opts?.autoPromoteThreshold,
689
+ autoDismissThreshold: opts?.autoDismissThreshold,
690
+ };
691
+ this.localSuggestions.push(suggestion);
692
+ return { suggestion };
693
+ }
694
+
695
+ /**
696
+ * Vote on an existing suggestion.
697
+ * Local mode: vote(id, agentName, 'up'|'down', reason?).
698
+ * Remote mode: vote(id, 1|-1, reason?) for backward compatibility.
699
+ */
700
+ async vote(suggestionId: string, agentNameOrValue: string | 1 | -1, voteOrReason?: 'up' | 'down' | string, reason?: string): Promise<SuggestionVoteResult> {
701
+ if (this.isRemote) {
702
+ const value = typeof agentNameOrValue === 'number' ? agentNameOrValue : (voteOrReason === 'down' ? -1 : 1);
703
+ const remoteReason = typeof agentNameOrValue === 'number' ? (voteOrReason as string | undefined) : reason;
704
+ const teamId = encodeURIComponent(this.name);
705
+ const res = await this.boardFetch(
706
+ `/api/holomesh/team/${teamId}/suggestions/${encodeURIComponent(suggestionId)}`,
707
+ 'PATCH',
708
+ { action: 'vote', value, reason: remoteReason }
709
+ );
710
+ if (!res) throw new Error('Failed to vote — no response from board');
711
+ if (res.error) throw new Error(String(res.error));
712
+ return res as unknown as SuggestionVoteResult;
713
+ }
714
+
715
+ const agentName = String(agentNameOrValue);
716
+ const voteDir: 'up' | 'down' = (voteOrReason === 'up' || voteOrReason === 'down') ? voteOrReason : 'up';
717
+
718
+ const suggestion = this.localSuggestions.find(s => s.id === suggestionId);
719
+ if (!suggestion) throw new Error('Suggestion not found');
720
+ if (suggestion.status !== 'open') throw new Error(`Suggestion is ${suggestion.status}, voting closed`);
721
+
722
+ suggestion.votes = suggestion.votes.filter(v => v.agent !== agentName);
723
+ suggestion.votes.push({ agent: agentName, vote: voteDir, reason, votedAt: new Date().toISOString() });
724
+
725
+ const upvotes = suggestion.votes.filter(v => v.vote === 'up').length;
726
+ const downvotes = suggestion.votes.filter(v => v.vote === 'down').length;
727
+ suggestion.score = upvotes - downvotes;
728
+
729
+ let promotedTaskId: string | undefined;
730
+
731
+ const promoteThreshold = suggestion.autoPromoteThreshold ?? Math.ceil(this.agentConfigs.length / 2);
732
+ if (upvotes >= promoteThreshold && suggestion.status === 'open') {
733
+ suggestion.status = 'promoted';
734
+ suggestion.resolvedAt = new Date().toISOString();
735
+ const promoted = await this.addTasks([{
736
+ title: suggestion.title,
737
+ description: `${suggestion.description ?? ''}\n\n[Auto-promoted from suggestion by ${suggestion.proposedBy} with ${suggestion.score} net votes]`.trim(),
738
+ priority: suggestion.category === 'architecture' ? 2 : suggestion.category === 'testing' ? 3 : 4,
739
+ source: `suggestion:${suggestion.id}`,
740
+ }]);
741
+ if (promoted.length > 0) {
742
+ promotedTaskId = promoted[0].id;
743
+ suggestion.promotedTaskId = promotedTaskId;
744
+ }
745
+ }
746
+
747
+ const dismissThreshold = suggestion.autoDismissThreshold ?? Math.ceil(this.agentConfigs.length / 2);
748
+ if (downvotes >= dismissThreshold && suggestion.status === 'open') {
749
+ suggestion.status = 'dismissed';
750
+ suggestion.resolvedAt = new Date().toISOString();
751
+ }
752
+
753
+ return { suggestion, promotedTaskId };
754
+ }
755
+
756
+ /**
757
+ * List suggestions, optionally filtered by status.
758
+ * Works locally (in-memory) and remotely (delegates to HoloMesh API).
759
+ */
760
+ async suggestions(status?: SuggestionStatus): Promise<SuggestionListResult> {
761
+ if (this.isRemote) {
762
+ const teamId = encodeURIComponent(this.name);
763
+ const query = status ? `?status=${encodeURIComponent(status)}` : '';
764
+ const res = await this.boardFetch(`/api/holomesh/team/${teamId}/suggestions${query}`, 'GET');
765
+ if (!res) throw new Error('Failed to list suggestions — no response from board');
766
+ if (res.error) throw new Error(String(res.error));
767
+ return res as unknown as SuggestionListResult;
768
+ }
769
+
770
+ const filtered = status
771
+ ? this.localSuggestions.filter(s => s.status === status)
772
+ : [...this.localSuggestions];
773
+ return { suggestions: filtered };
774
+ }
775
+
776
+ /** Promote a suggestion to a board task manually. Local-only. */
777
+ async promoteSuggestion(suggestionId: string, promoterName?: string): Promise<SuggestionVoteResult> {
778
+ if (this.isRemote) throw new Error('promoteSuggestion() is not supported in remote mode — use the board API');
779
+
780
+ const suggestion = this.localSuggestions.find(s => s.id === suggestionId);
781
+ if (!suggestion) throw new Error('Suggestion not found');
782
+ if (suggestion.status !== 'open') throw new Error(`Suggestion is already ${suggestion.status}`);
783
+
784
+ suggestion.status = 'promoted';
785
+ suggestion.resolvedAt = new Date().toISOString();
786
+
787
+ const promoted = await this.addTasks([{
788
+ title: suggestion.title,
789
+ description: `${suggestion.description ?? ''}\n\n[Promoted by ${promoterName ?? 'team'} from suggestion by ${suggestion.proposedBy}]`.trim(),
790
+ priority: suggestion.category === 'architecture' ? 2 : suggestion.category === 'testing' ? 3 : 4,
791
+ source: `suggestion:${suggestion.id}`,
792
+ }]);
793
+
794
+ const promotedTaskId = promoted.length > 0 ? promoted[0].id : undefined;
795
+ suggestion.promotedTaskId = promotedTaskId;
796
+ return { suggestion, promotedTaskId };
797
+ }
798
+
799
+ /** Dismiss a suggestion. Local-only. */
800
+ dismissSuggestion(suggestionId: string): SuggestionVoteResult {
801
+ if (this.isRemote) throw new Error('dismissSuggestion() is not supported in remote mode — use the board API');
802
+
803
+ const suggestion = this.localSuggestions.find(s => s.id === suggestionId);
804
+ if (!suggestion) throw new Error('Suggestion not found');
805
+ if (suggestion.status !== 'open') throw new Error(`Suggestion is already ${suggestion.status}`);
806
+
807
+ suggestion.status = 'dismissed';
808
+ suggestion.resolvedAt = new Date().toISOString();
809
+ return { suggestion };
810
+ }
811
+
812
+ // ── Mode ──
813
+
814
+ /** Get the current operating mode. */
815
+ get mode(): TeamMode {
816
+ return this.currentMode;
817
+ }
818
+
819
+ /** Get the current mode's objective string. */
820
+ get objective(): string {
821
+ return this.modeObjective;
822
+ }
823
+
824
+ /** Get the current mode's rules. */
825
+ get rules(): string[] {
826
+ return [...this.modeRules];
827
+ }
828
+
829
+ /** Switch the team's operating mode. Local-first with optional remote sync. */
830
+ async setMode(mode: TeamMode): Promise<SetModeResult> {
831
+ const preset = ROOM_PRESETS[mode];
832
+ if (!preset) throw new Error(`Unknown mode: ${mode}. Valid: ${Object.keys(ROOM_PRESETS).join(', ')}`);
833
+
834
+ const previousMode = this.currentMode;
835
+ this.currentMode = mode;
836
+ this.modeObjective = preset.objective;
837
+ this.modeRules = preset.rules;
838
+
839
+ // Sync to remote if connected
840
+ if (this.isRemote) {
841
+ const teamId = encodeURIComponent(this.name);
842
+ const res = await this.boardFetch(`/api/holomesh/team/${teamId}/mode`, 'POST', { mode });
843
+ if (res?.error) throw new Error(String(res.error));
844
+ }
845
+
846
+ return { mode, previousMode };
847
+ }
848
+
849
+ // ── Derive (local-first with optional remote sync) ──
850
+
851
+ /**
852
+ * Derive tasks from a source document (audit, roadmap, grep output, etc.).
853
+ * Parses markdown checkboxes, section headers, and TODO/FIXME patterns.
854
+ * Deduplicates against existing board and done log.
855
+ * Works locally and remotely.
856
+ */
857
+ async derive(source: string, content: string): Promise<DeriveResult> {
858
+ if (this.isRemote) {
859
+ const teamId = encodeURIComponent(this.name);
860
+ const res = await this.boardFetch(`/api/holomesh/team/${teamId}/board/derive`, 'POST', { source, content });
861
+ if (!res) throw new Error('Failed to derive tasks — no response from board');
862
+ if (res.error) throw new Error(String(res.error));
863
+ return res as unknown as DeriveResult;
864
+ }
865
+
866
+ // Parse content into task candidates using the framework's derive parser
867
+ const candidates = parseDeriveContent(content, source);
868
+ if (candidates.length === 0) return { tasks: [] };
869
+
870
+ // Add to board with dedup
871
+ const added = await this.addTasks(candidates);
872
+ return { tasks: added };
873
+ }
874
+
875
+ // ── Delegation (FW-0.6) ──
876
+
877
+ /**
878
+ * Delegate a task from this team's board to another team's board.
879
+ * Modifies both boards. Requires connected remote board.
880
+ */
881
+ async delegate(otherTeamId: string, taskId: string): Promise<boolean> {
882
+ if (!this.isRemote) throw new Error('Delegation requires a remote board to communicate with other teams');
883
+ const teamId = encodeURIComponent(this.name);
884
+ const res = await this.boardFetch(`/api/holomesh/team/${teamId}/board/${encodeURIComponent(taskId)}`, 'PATCH', {
885
+ action: 'delegate',
886
+ targetTeamId: otherTeamId
887
+ });
888
+
889
+ if (res?.error) throw new Error(String(res.error));
890
+ return true;
891
+ }
892
+
893
+ // ── Presence (FW-0.3 — local-first with optional remote) ──
894
+
895
+ /**
896
+ * Record that an agent is alive. Updates lastSeen timestamp.
897
+ * Works locally on all teams. Optionally pass the task the agent is working on.
898
+ */
899
+ localHeartbeat(agentName: string, currentTask?: string): void {
900
+ const now = Date.now();
901
+ const existing = this.presenceRecords.get(agentName);
902
+ if (existing) {
903
+ existing.lastSeen = now;
904
+ existing.currentTask = currentTask;
905
+ } else {
906
+ this.presenceRecords.set(agentName, {
907
+ firstSeen: now,
908
+ lastSeen: now,
909
+ currentTask,
910
+ });
911
+ }
912
+ }
913
+
914
+ /**
915
+ * Return the presence status of all known agents.
916
+ * Agents not heartbeating within idleTimeoutMs → 'idle'.
917
+ * Agents not heartbeating within offlineTimeoutMs → 'offline'.
918
+ */
919
+ localPresence(): AgentPresence[] {
920
+ const now = Date.now();
921
+ const result: AgentPresence[] = [];
922
+
923
+ for (const [name, record] of this.presenceRecords) {
924
+ const elapsed = now - record.lastSeen;
925
+ let status: AgentPresenceStatus;
926
+ if (elapsed >= this.offlineTimeoutMs) {
927
+ status = 'offline';
928
+ } else if (elapsed >= this.idleTimeoutMs) {
929
+ status = 'idle';
930
+ } else {
931
+ status = 'online';
932
+ }
933
+
934
+ result.push({
935
+ name,
936
+ status,
937
+ lastSeen: record.lastSeen,
938
+ currentTask: record.currentTask,
939
+ uptime: now - record.firstSeen,
940
+ });
941
+ }
942
+
943
+ return result;
944
+ }
945
+
946
+ /** Get presence/slot info for the team. Requires remote board. */
947
+ async presence(): Promise<PresenceResult> {
948
+ if (!this.isRemote) throw new Error('presence() requires a remote board (boardUrl + boardApiKey)');
949
+ const teamId = encodeURIComponent(this.name);
950
+ const res = await this.boardFetch(`/api/holomesh/team/${teamId}/slots`, 'GET');
951
+ if (!res) throw new Error('Failed to get presence — no response from board');
952
+ if (res.error) throw new Error(String(res.error));
953
+ return res as unknown as PresenceResult;
954
+ }
955
+
956
+ /** Send a heartbeat to the team's presence system. Requires remote board. */
957
+ async heartbeat(ideType?: string): Promise<HeartbeatResult> {
958
+ if (!this.isRemote) throw new Error('heartbeat() requires a remote board (boardUrl + boardApiKey)');
959
+ const teamId = encodeURIComponent(this.name);
960
+ const res = await this.boardFetch(`/api/holomesh/team/${teamId}/presence`, 'POST', {
961
+ ide_type: ideType ?? 'unknown',
962
+ status: 'active',
963
+ });
964
+ if (!res) throw new Error('Failed to send heartbeat — no response from board');
965
+ if (res.error) throw new Error(String(res.error));
966
+ return res as unknown as HeartbeatResult;
967
+ }
968
+
969
+ // ── Mesh Integration (FW-0.4) ──
970
+
971
+ /**
972
+ * Get discovered peers with their metadata.
973
+ * Prunes stale peers (>15s since last seen) before returning.
974
+ */
975
+ peers(): PeerMetadata[] {
976
+ this.mesh.pruneStalePeers();
977
+ return this.mesh.getPeers();
978
+ }
979
+
980
+ /**
981
+ * Register a peer node for multi-team coordination.
982
+ */
983
+ registerPeer(peer: PeerMetadata): void {
984
+ this.mesh.registerPeer(peer);
985
+ }
986
+
987
+ /**
988
+ * Broadcast this team's capabilities as a mesh signal.
989
+ * Other teams can discover this signal via `signals.discoverSignals('agent-host')`.
990
+ */
991
+ broadcastCapabilities(url?: string): void {
992
+ const capabilities = new Set<string>();
993
+ for (const agent of this.agentConfigs) {
994
+ for (const cap of agent.capabilities) capabilities.add(cap);
995
+ }
996
+ this.signals.broadcastSignal({
997
+ type: 'agent-host',
998
+ url: url ?? `local://${this.name}`,
999
+ capabilities: [...capabilities],
1000
+ });
1001
+ }
1002
+
1003
+ /**
1004
+ * Share a knowledge insight via gossip protocol.
1005
+ * Returns the gossip packet for cross-team anti-entropy sync.
1006
+ */
1007
+ shareKnowledge(payload: unknown): GossipPacket {
1008
+ return this.gossip.shareWisdom(this.name, payload);
1009
+ }
1010
+
1011
+ /**
1012
+ * Sync knowledge from a peer's gossip pool.
1013
+ * Returns the number of new entries absorbed.
1014
+ */
1015
+ syncFromPeer(peerPool: Map<string, GossipPacket>): number {
1016
+ return this.gossip.antiEntropySync(peerPool);
1017
+ }
1018
+
1019
+ // ── Scout ──
1020
+
1021
+ /** On-demand scout: parse TODO/FIXME content into tasks. */
1022
+ async scoutFromTodos(grepOutput: string): Promise<TaskDef[]> {
1023
+ if (this.isRemote) {
1024
+ const res = await this.boardFetch(`/api/holomesh/team/${encodeURIComponent(this.name)}/board/scout`, 'POST', { todo_content: grepOutput });
1025
+ if (res && res.error) throw new Error(String(res.error));
1026
+ return (res?.tasks || res?.added || []) as TaskDef[];
1027
+ }
1028
+
1029
+ const tasks: Array<Omit<TaskDef, 'id' | 'status' | 'createdAt'>> = [];
1030
+
1031
+ for (const line of grepOutput.split('\n')) {
1032
+ const match = line.trim().match(/^(.+?):(\d+):\s*(?:\/\/\s*)?(TODO|FIXME|HACK|XXX)\s*:?\s*(.+)$/i);
1033
+ if (!match) continue;
1034
+
1035
+ const [, file, lineNo, kind, detail] = match;
1036
+ const upper = `${kind} ${detail}`.toUpperCase();
1037
+ const priority = /SECURITY|VULN|AUTH|CRITICAL/.test(upper) ? 1
1038
+ : /FIXME|BUG|BROKEN|ERROR/.test(upper) ? 2
1039
+ : /TODO|HACK|REFACTOR/.test(upper) ? 3 : 4;
1040
+
1041
+ tasks.push({
1042
+ title: `${kind.toUpperCase()}: ${detail.trim().slice(0, 180)}`,
1043
+ description: `${file}:${lineNo}`,
1044
+ priority,
1045
+ role: 'coder',
1046
+ source: 'scout:todo-scan',
1047
+ });
1048
+ }
1049
+
1050
+ return await this.addTasks(tasks);
1051
+ }
1052
+
1053
+ // ── Task Decomposition (FW-0.2) ──
1054
+
1055
+ /**
1056
+ * Decompose a complex task into parallel micro-phases and distribute
1057
+ * the sub-phases as individual board tasks. Each sub-phase becomes a
1058
+ * separate task that agents can claim independently.
1059
+ *
1060
+ * Returns the decomposition result including the wave-based execution plan.
1061
+ * If no decomposer is available (no LLM configured), falls back to adding
1062
+ * the task as-is.
1063
+ */
1064
+ async decomposeAndDistribute(task: TaskDef): Promise<DecompositionResult | null> {
1065
+ if (!this.decomposer) return null;
1066
+
1067
+ const taskDesc: TaskDescription = {
1068
+ id: task.id,
1069
+ title: task.title,
1070
+ description: task.description,
1071
+ requiredCapabilities: task.role ? [task.role] : ['coding'],
1072
+ };
1073
+
1074
+ const result = await this.decomposer.decompose(taskDesc);
1075
+
1076
+ if (!result.wasDecomposed) return result;
1077
+
1078
+ // Convert micro-phases into board tasks, preserving wave ordering via priority
1079
+ const subTasks: Array<Omit<TaskDef, 'id' | 'status' | 'createdAt'>> = [];
1080
+ for (let waveIdx = 0; waveIdx < result.plan.waves.length; waveIdx++) {
1081
+ for (const phase of result.plan.waves[waveIdx]) {
1082
+ subTasks.push({
1083
+ title: `[${task.id}/${phase.id}] ${phase.description}`,
1084
+ description: `Sub-phase of "${task.title}" (wave ${waveIdx + 1}/${result.plan.waves.length}). Dependencies: ${phase.dependencies.join(', ') || 'none'}`,
1085
+ priority: Math.max(1, task.priority - 1 + waveIdx), // Earlier waves get higher priority
1086
+ role: (phase.requiredCapabilities[0] as TaskDef['role']) ?? task.role,
1087
+ source: `decomposer:${task.id}`,
1088
+ });
1089
+ }
1090
+ }
1091
+
1092
+ // Remove the original complex task and add sub-tasks
1093
+ this.board = this.board.filter(t => t.id !== task.id);
1094
+ await this.addTasks(subTasks);
1095
+
1096
+ return result;
1097
+ }
1098
+
1099
+ /** Get the decomposer instance for direct use (e.g., manual decomposition). */
1100
+ getDecomposer(): SmartMicroPhaseDecomposer | null {
1101
+ return this.decomposer;
1102
+ }
1103
+
1104
+ // ── Remote Proxy ──
1105
+
1106
+ async listBoard(): Promise<Record<string, unknown>> {
1107
+ if (!this.isRemote) throw new Error('listBoard() requires a remote board');
1108
+ const res = await this.boardFetch(`/api/holomesh/team/${encodeURIComponent(this.name)}/board`, 'GET');
1109
+ if (!res) throw new Error('Failed to list board');
1110
+ return res as Record<string, unknown>;
1111
+ }
1112
+
1113
+ async claimTask(taskId: string): Promise<Record<string, unknown>> {
1114
+ if (!this.isRemote) throw new Error('claimTask() requires a remote board');
1115
+ const res = await this.boardFetch(`/api/holomesh/team/${encodeURIComponent(this.name)}/board/${encodeURIComponent(taskId)}`, 'PATCH', { action: 'claim' });
1116
+ if (!res) throw new Error('Failed to claim task');
1117
+ return res as Record<string, unknown>;
1118
+ }
1119
+
1120
+ async completeTask(taskId: string, commit?: string, summary?: string): Promise<Record<string, unknown>> {
1121
+ if (!this.isRemote) throw new Error('completeTask() requires a remote board');
1122
+ const body: Record<string, unknown> = { action: 'done' };
1123
+ if (commit) body.commit = commit;
1124
+ if (summary) body.summary = summary;
1125
+ const res = await this.boardFetch(`/api/holomesh/team/${encodeURIComponent(this.name)}/board/${encodeURIComponent(taskId)}`, 'PATCH', body);
1126
+ if (!res) throw new Error('Failed to complete task');
1127
+ return res as Record<string, unknown>;
1128
+ }
1129
+
1130
+ async assignSlots(roles: string[]): Promise<Record<string, unknown>> {
1131
+ if (!this.isRemote) throw new Error('assignSlots() requires a remote board');
1132
+ const res = await this.boardFetch(`/api/holomesh/team/${encodeURIComponent(this.name)}/roles`, 'PATCH', { roles });
1133
+ if (!res) throw new Error('Failed to assign slots');
1134
+ return res as Record<string, unknown>;
1135
+ }
1136
+
1137
+ // ── Bounty System (FW-0.6) ──
1138
+
1139
+ /** Create a bounty for a board task. */
1140
+ createBounty(taskId: string, reward: BountyReward, createdBy: string, deadline?: number): Bounty {
1141
+ // Verify the task exists on the board
1142
+ const task = this.board.find(t => t.id === taskId);
1143
+ if (!task) throw new Error(`Task ${taskId} not found on board`);
1144
+ return this.bounties.createBounty(taskId, reward, createdBy, deadline);
1145
+ }
1146
+
1147
+ /** Claim a bounty for an agent. */
1148
+ claimBountyForAgent(bountyId: string, agentId: string): BountyClaimResult {
1149
+ // Verify agent is on the team
1150
+ if (!this.runtimes.has(agentId)) throw new Error(`Agent ${agentId} not on team`);
1151
+ return this.bounties.claimBounty(bountyId, agentId);
1152
+ }
1153
+
1154
+ /** Complete a bounty with proof. */
1155
+ completeBountyWithProof(bountyId: string, proof: CompletionProof): PayoutResult {
1156
+ return this.bounties.completeBounty(bountyId, proof);
1157
+ }
1158
+
1159
+ /** List bounties, optionally filtered by status. */
1160
+ listBounties(status?: Bounty['status']): Bounty[] {
1161
+ return this.bounties.list(status);
1162
+ }
1163
+
1164
+ // ── Internal ──
1165
+
1166
+ /** Convert a synthesized Goal into a TaskDef and add it to the board. */
1167
+ private goalToTask(goal: Goal, agentRole: string): TaskDef {
1168
+ const task: TaskDef = {
1169
+ id: `task_synth_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
1170
+ title: goal.description,
1171
+ description: `Autonomously synthesized goal [${goal.category}] — priority ${goal.priority}`,
1172
+ priority: goal.priority === 'high' ? 2 : goal.priority === 'medium' ? 4 : 6,
1173
+ role: (agentRole === 'architect' || agentRole === 'researcher') ? 'researcher' as const : 'coder' as const,
1174
+ source: `synthesizer:${goal.source}`,
1175
+ status: 'open',
1176
+ createdAt: goal.generatedAt,
1177
+ };
1178
+ this.board.push(task);
1179
+ return task;
1180
+ }
1181
+
1182
+ private findClaimableTask(runtime: AgentRuntime, alreadyClaimed: Set<string>, pool?: TaskDef[]): TaskDef | undefined {
1183
+ const tasks = pool ?? this.openTasks;
1184
+ const agent = runtime.config;
1185
+
1186
+ const validTasks = tasks.filter(task => {
1187
+ if (alreadyClaimed.has(task.id)) return false;
1188
+ if (task.priority > agent.claimFilter.maxPriority) return false;
1189
+ if (task.role) return agent.claimFilter.roles.includes(task.role);
1190
+ return true;
1191
+ });
1192
+
1193
+ if (validTasks.length === 0) return undefined;
1194
+
1195
+ // Score and pick highest
1196
+ return validTasks.map(task => {
1197
+ let score = (10 - task.priority) * 10; // Prioritize higher priority
1198
+ score += runtime.reputationScore;
1199
+
1200
+ const fullText = `${task.title} ${task.description}`.toLowerCase();
1201
+ for (const cap of agent.capabilities || []) {
1202
+ if (fullText.includes(cap.toLowerCase())) {
1203
+ score += 15; // Capability match bonus
1204
+ }
1205
+ }
1206
+
1207
+ if (task.role && agent.role === task.role) {
1208
+ score += 20; // Exact role match bonus
1209
+ }
1210
+
1211
+ return { task, score };
1212
+ }).sort((a, b) => b.score - a.score)[0]?.task;
1213
+ }
1214
+
1215
+ private async executeTask(
1216
+ runtime: AgentRuntime,
1217
+ task: TaskDef
1218
+ ): Promise<{ summary: string; insights: KnowledgeInsight[] }> {
1219
+ const relevantKnowledge = this.knowledge.search(task.title, 3);
1220
+ const knowledgeContext = relevantKnowledge.length > 0
1221
+ ? relevantKnowledge.map(k => `[${k.type}] ${k.content}`).join('\n')
1222
+ : '';
1223
+
1224
+ // Run the full 7-phase protocol cycle
1225
+ const result = await runProtocolCycle(runtime.config, task, knowledgeContext);
1226
+ return { summary: result.summary, insights: result.insights };
1227
+ }
1228
+
1229
+
1230
+ private async getAgentVote<T>(runtime: AgentRuntime, key: string, value: T): Promise<boolean> {
1231
+ const messages: LLMMessage[] = [
1232
+ {
1233
+ role: 'system',
1234
+ content: `You are ${runtime.name}, a ${runtime.role}. Vote YES or NO on team proposals.`,
1235
+ },
1236
+ {
1237
+ role: 'user',
1238
+ content: `Proposal "${key}": ${JSON.stringify(value)}\n\nRespond with only YES or NO.`,
1239
+ },
1240
+ ];
1241
+
1242
+ const response = await callLLM(runtime.config.model, messages, { maxTokens: 10 });
1243
+ return response.content.trim().toUpperCase().startsWith('YES');
1244
+ }
1245
+ }