@tenex-chat/backend 0.9.1

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 (427) hide show
  1. package/README.md +194 -0
  2. package/dist/backend-wrapper.cjs +3 -0
  3. package/dist/src/index.js +331928 -0
  4. package/package.json +103 -0
  5. package/src/agents/AgentRegistry.ts +418 -0
  6. package/src/agents/AgentStorage.ts +1133 -0
  7. package/src/agents/ConfigResolver.ts +229 -0
  8. package/src/agents/agent-installer.ts +236 -0
  9. package/src/agents/agent-loader.ts +241 -0
  10. package/src/agents/constants.ts +82 -0
  11. package/src/agents/errors.ts +48 -0
  12. package/src/agents/execution/AgentExecutor.ts +561 -0
  13. package/src/agents/execution/ExecutionContextFactory.ts +112 -0
  14. package/src/agents/execution/MessageCompiler.ts +597 -0
  15. package/src/agents/execution/MessageSyncer.ts +100 -0
  16. package/src/agents/execution/PostCompletionChecker.ts +278 -0
  17. package/src/agents/execution/ProgressMonitor.ts +50 -0
  18. package/src/agents/execution/RALResolver.ts +177 -0
  19. package/src/agents/execution/SessionManager.ts +181 -0
  20. package/src/agents/execution/StreamCallbacks.ts +312 -0
  21. package/src/agents/execution/StreamExecutionHandler.ts +579 -0
  22. package/src/agents/execution/StreamSetup.ts +313 -0
  23. package/src/agents/execution/ToolEventHandlers.ts +239 -0
  24. package/src/agents/execution/ToolExecutionTracker.ts +498 -0
  25. package/src/agents/execution/ToolResultUtils.ts +97 -0
  26. package/src/agents/execution/ToolSupervisionWrapper.ts +174 -0
  27. package/src/agents/execution/constants.ts +16 -0
  28. package/src/agents/execution/index.ts +3 -0
  29. package/src/agents/execution/types.ts +96 -0
  30. package/src/agents/execution/utils.ts +26 -0
  31. package/src/agents/index.ts +4 -0
  32. package/src/agents/script-installer.ts +266 -0
  33. package/src/agents/supervision/SupervisorLLMService.ts +253 -0
  34. package/src/agents/supervision/SupervisorOrchestrator.ts +471 -0
  35. package/src/agents/supervision/heuristics/ConsecutiveToolsWithoutTodoHeuristic.ts +73 -0
  36. package/src/agents/supervision/heuristics/DelegationClaimHeuristic.ts +80 -0
  37. package/src/agents/supervision/heuristics/HeuristicRegistry.ts +114 -0
  38. package/src/agents/supervision/heuristics/PendingTodosHeuristic.ts +93 -0
  39. package/src/agents/supervision/heuristics/SilentAgentHeuristic.ts +54 -0
  40. package/src/agents/supervision/heuristics/index.ts +5 -0
  41. package/src/agents/supervision/index.ts +28 -0
  42. package/src/agents/supervision/registerHeuristics.ts +110 -0
  43. package/src/agents/supervision/supervisionHealthCheck.ts +123 -0
  44. package/src/agents/supervision/types.ts +171 -0
  45. package/src/agents/tool-names.ts +46 -0
  46. package/src/agents/tool-normalization.ts +184 -0
  47. package/src/agents/types/index.ts +2 -0
  48. package/src/agents/types/runtime.ts +74 -0
  49. package/src/agents/types/storage.ts +145 -0
  50. package/src/commands/agent/import/index.ts +6 -0
  51. package/src/commands/agent/import/openclaw-distiller.ts +57 -0
  52. package/src/commands/agent/import/openclaw-reader.ts +141 -0
  53. package/src/commands/agent/import/openclaw.ts +154 -0
  54. package/src/commands/agent/index.ts +6 -0
  55. package/src/commands/agent.ts +215 -0
  56. package/src/commands/daemon.ts +198 -0
  57. package/src/commands/doctor.ts +134 -0
  58. package/src/commands/setup/embed.ts +228 -0
  59. package/src/commands/setup/global-system-prompt.ts +223 -0
  60. package/src/commands/setup/image.ts +179 -0
  61. package/src/commands/setup/index.ts +16 -0
  62. package/src/commands/setup/interactive.ts +95 -0
  63. package/src/commands/setup/llm.ts +38 -0
  64. package/src/commands/setup/onboarding.ts +294 -0
  65. package/src/commands/setup/providers.ts +27 -0
  66. package/src/constants.ts +34 -0
  67. package/src/conversations/ConversationDiskReader.ts +148 -0
  68. package/src/conversations/ConversationRegistry.ts +728 -0
  69. package/src/conversations/ConversationStore.ts +868 -0
  70. package/src/conversations/MessageBuilder.ts +866 -0
  71. package/src/conversations/executionTime.ts +62 -0
  72. package/src/conversations/formatters/DelegationXmlFormatter.ts +64 -0
  73. package/src/conversations/formatters/ThreadedConversationFormatter.ts +303 -0
  74. package/src/conversations/formatters/index.ts +9 -0
  75. package/src/conversations/formatters/utils/MessageFormatter.ts +46 -0
  76. package/src/conversations/formatters/utils/TimestampFormatter.ts +56 -0
  77. package/src/conversations/formatters/utils/TreeBuilder.ts +131 -0
  78. package/src/conversations/formatters/utils/TreeRenderer.ts +49 -0
  79. package/src/conversations/index.ts +2 -0
  80. package/src/conversations/persistence/ToolMessageStorage.ts +143 -0
  81. package/src/conversations/search/ConversationIndexManager.ts +393 -0
  82. package/src/conversations/search/QueryParser.ts +114 -0
  83. package/src/conversations/search/SearchEngine.ts +175 -0
  84. package/src/conversations/search/SnippetExtractor.ts +345 -0
  85. package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +484 -0
  86. package/src/conversations/search/embeddings/ConversationIndexingJob.ts +320 -0
  87. package/src/conversations/search/embeddings/IndexingStateManager.ts +338 -0
  88. package/src/conversations/search/embeddings/index.ts +18 -0
  89. package/src/conversations/search/index.ts +49 -0
  90. package/src/conversations/search/types.ts +124 -0
  91. package/src/conversations/services/CategoryManager.ts +160 -0
  92. package/src/conversations/services/ConversationResolver.ts +296 -0
  93. package/src/conversations/services/ConversationSummarizer.ts +234 -0
  94. package/src/conversations/services/MetadataDebounceManager.ts +188 -0
  95. package/src/conversations/services/index.ts +2 -0
  96. package/src/conversations/types.ts +148 -0
  97. package/src/conversations/utils/content-utils.ts +69 -0
  98. package/src/conversations/utils/image-placeholder.ts +281 -0
  99. package/src/conversations/utils/image-url-utils.ts +171 -0
  100. package/src/conversations/utils/multimodal-content.ts +90 -0
  101. package/src/conversations/utils/tool-result-truncator.ts +159 -0
  102. package/src/daemon/Daemon.ts +1883 -0
  103. package/src/daemon/ProjectRuntime.ts +657 -0
  104. package/src/daemon/RestartState.ts +152 -0
  105. package/src/daemon/RuntimeLifecycle.ts +268 -0
  106. package/src/daemon/SubscriptionManager.ts +305 -0
  107. package/src/daemon/UnixSocketTransport.ts +318 -0
  108. package/src/daemon/filters/SubscriptionFilterBuilder.ts +119 -0
  109. package/src/daemon/index.ts +9 -0
  110. package/src/daemon/routing/DaemonRouter.ts +491 -0
  111. package/src/daemon/types.ts +150 -0
  112. package/src/daemon/utils/routing-log.ts +76 -0
  113. package/src/daemon/utils/telemetry.ts +173 -0
  114. package/src/event-handler/agentDeletion.ts +383 -0
  115. package/src/event-handler/index.ts +749 -0
  116. package/src/event-handler/newConversation.ts +165 -0
  117. package/src/event-handler/project.ts +166 -0
  118. package/src/event-handler/reply.ts +18 -0
  119. package/src/events/NDKAgentDefinition.ts +292 -0
  120. package/src/events/NDKAgentLesson.ts +106 -0
  121. package/src/events/NDKEventMetadata.ts +34 -0
  122. package/src/events/NDKMCPTool.ts +60 -0
  123. package/src/events/NDKProjectStatus.ts +384 -0
  124. package/src/events/index.ts +4 -0
  125. package/src/index.ts +126 -0
  126. package/src/lib/agent-home.ts +334 -0
  127. package/src/lib/error-formatter.ts +200 -0
  128. package/src/lib/fs/filesystem.ts +128 -0
  129. package/src/lib/fs/index.ts +1 -0
  130. package/src/lib/json-parser.ts +30 -0
  131. package/src/lib/string.ts +15 -0
  132. package/src/lib/time.ts +74 -0
  133. package/src/llm/ChunkHandler.ts +277 -0
  134. package/src/llm/FinishHandler.ts +250 -0
  135. package/src/llm/LLMConfigEditor.ts +154 -0
  136. package/src/llm/LLMServiceFactory.ts +230 -0
  137. package/src/llm/MessageProcessor.ts +90 -0
  138. package/src/llm/RecordingState.ts +37 -0
  139. package/src/llm/StreamPublisher.ts +40 -0
  140. package/src/llm/TracingUtils.ts +77 -0
  141. package/src/llm/chunk-validators.ts +57 -0
  142. package/src/llm/constants.ts +6 -0
  143. package/src/llm/index.ts +12 -0
  144. package/src/llm/meta/MetaModelResolver.ts +352 -0
  145. package/src/llm/meta/index.ts +11 -0
  146. package/src/llm/middleware/flight-recorder.ts +188 -0
  147. package/src/llm/providers/MockProvider.ts +332 -0
  148. package/src/llm/providers/agent/ClaudeCodeProvider.ts +343 -0
  149. package/src/llm/providers/agent/ClaudeCodeToolsAdapter.ts +203 -0
  150. package/src/llm/providers/agent/CodexAppServerProvider.ts +214 -0
  151. package/src/llm/providers/agent/CodexAppServerToolsAdapter.ts +91 -0
  152. package/src/llm/providers/agent/index.ts +10 -0
  153. package/src/llm/providers/base/AgentProvider.ts +107 -0
  154. package/src/llm/providers/base/BaseProvider.ts +114 -0
  155. package/src/llm/providers/base/StandardProvider.ts +38 -0
  156. package/src/llm/providers/base/index.ts +9 -0
  157. package/src/llm/providers/index.ts +106 -0
  158. package/src/llm/providers/key-manager.ts +238 -0
  159. package/src/llm/providers/ollama-models.ts +105 -0
  160. package/src/llm/providers/openrouter-models.ts +102 -0
  161. package/src/llm/providers/provider-ids.ts +18 -0
  162. package/src/llm/providers/registry/ProviderRegistry.ts +414 -0
  163. package/src/llm/providers/registry/index.ts +7 -0
  164. package/src/llm/providers/standard/AnthropicProvider.ts +71 -0
  165. package/src/llm/providers/standard/OllamaProvider.ts +59 -0
  166. package/src/llm/providers/standard/OpenAIProvider.ts +44 -0
  167. package/src/llm/providers/standard/OpenRouterProvider.ts +103 -0
  168. package/src/llm/providers/standard/index.ts +10 -0
  169. package/src/llm/providers/types.ts +194 -0
  170. package/src/llm/providers/usage-metadata.ts +78 -0
  171. package/src/llm/service.ts +713 -0
  172. package/src/llm/types.ts +167 -0
  173. package/src/llm/utils/ConfigurationManager.ts +650 -0
  174. package/src/llm/utils/ConfigurationTester.ts +229 -0
  175. package/src/llm/utils/ModelSelector.ts +212 -0
  176. package/src/llm/utils/ProviderConfigUI.ts +177 -0
  177. package/src/llm/utils/claudeCodePromptCompiler.ts +141 -0
  178. package/src/llm/utils/codex-models.ts +53 -0
  179. package/src/llm/utils/context-window-cache.ts +30 -0
  180. package/src/llm/utils/models-dev-cache.ts +267 -0
  181. package/src/llm/utils/provider-setup.ts +50 -0
  182. package/src/llm/utils/tool-errors.ts +78 -0
  183. package/src/llm/utils/usage.ts +74 -0
  184. package/src/logging/EventRoutingLogger.ts +205 -0
  185. package/src/nostr/AgentEventDecoder.ts +357 -0
  186. package/src/nostr/AgentEventEncoder.ts +677 -0
  187. package/src/nostr/AgentProfilePublisher.ts +657 -0
  188. package/src/nostr/AgentPublisher.ts +437 -0
  189. package/src/nostr/BlossomService.ts +226 -0
  190. package/src/nostr/InterventionPublisher.ts +132 -0
  191. package/src/nostr/TagExtractor.ts +228 -0
  192. package/src/nostr/collectEvents.ts +83 -0
  193. package/src/nostr/constants.ts +38 -0
  194. package/src/nostr/encryption.ts +26 -0
  195. package/src/nostr/index.ts +31 -0
  196. package/src/nostr/keys.ts +17 -0
  197. package/src/nostr/kinds.ts +37 -0
  198. package/src/nostr/ndkClient.ts +72 -0
  199. package/src/nostr/relays.ts +43 -0
  200. package/src/nostr/trace-context.ts +39 -0
  201. package/src/nostr/types.ts +227 -0
  202. package/src/nostr/utils.ts +84 -0
  203. package/src/prompts/core/FragmentRegistry.ts +30 -0
  204. package/src/prompts/core/PromptBuilder.ts +98 -0
  205. package/src/prompts/core/index.ts +3 -0
  206. package/src/prompts/core/types.ts +13 -0
  207. package/src/prompts/fragments/00-global-system-prompt.ts +44 -0
  208. package/src/prompts/fragments/01-agent-identity.ts +69 -0
  209. package/src/prompts/fragments/02-agent-home-directory.ts +114 -0
  210. package/src/prompts/fragments/03-system-reminders-explanation.ts +14 -0
  211. package/src/prompts/fragments/04-relay-configuration.ts +38 -0
  212. package/src/prompts/fragments/05-delegation-chain.ts +45 -0
  213. package/src/prompts/fragments/06-agent-todos.ts +74 -0
  214. package/src/prompts/fragments/06-todo-usage-guidance.ts +34 -0
  215. package/src/prompts/fragments/07-meta-project-context.ts +234 -0
  216. package/src/prompts/fragments/08-active-conversations.ts +382 -0
  217. package/src/prompts/fragments/09-recent-conversations.ts +153 -0
  218. package/src/prompts/fragments/10-referenced-article.ts +21 -0
  219. package/src/prompts/fragments/11-nudges.ts +134 -0
  220. package/src/prompts/fragments/12-skills.ts +127 -0
  221. package/src/prompts/fragments/13-available-nudges.ts +122 -0
  222. package/src/prompts/fragments/15-available-agents.ts +53 -0
  223. package/src/prompts/fragments/16-stay-in-your-lane.ts +41 -0
  224. package/src/prompts/fragments/17-todo-before-delegation.ts +39 -0
  225. package/src/prompts/fragments/20-voice-mode.ts +62 -0
  226. package/src/prompts/fragments/22-scheduled-tasks.ts +175 -0
  227. package/src/prompts/fragments/24-retrieved-lessons.ts +26 -0
  228. package/src/prompts/fragments/25-rag-instructions.ts +333 -0
  229. package/src/prompts/fragments/26-mcp-resources.ts +237 -0
  230. package/src/prompts/fragments/27-memorized-reports.ts +77 -0
  231. package/src/prompts/fragments/28-agent-directed-monitoring.ts +32 -0
  232. package/src/prompts/fragments/29-rag-collections.ts +50 -0
  233. package/src/prompts/fragments/30-worktree-context.ts +98 -0
  234. package/src/prompts/fragments/31-agents-md-guidance.ts +96 -0
  235. package/src/prompts/fragments/32-process-metrics.ts +72 -0
  236. package/src/prompts/fragments/debug-mode.ts +48 -0
  237. package/src/prompts/fragments/delegation-completion.ts +44 -0
  238. package/src/prompts/fragments/index.ts +91 -0
  239. package/src/prompts/index.ts +21 -0
  240. package/src/prompts/utils/systemPromptBuilder.ts +777 -0
  241. package/src/scripts/migrate-prefix-index.ts +157 -0
  242. package/src/services/AgentDefinitionMonitor.ts +701 -0
  243. package/src/services/ConfigService.ts +723 -0
  244. package/src/services/CooldownRegistry.ts +199 -0
  245. package/src/services/LLMOperationsRegistry.ts +424 -0
  246. package/src/services/OwnerAgentListService.ts +354 -0
  247. package/src/services/PubkeyService.ts +308 -0
  248. package/src/services/agents/AgentMetadataStore.ts +72 -0
  249. package/src/services/agents/AgentResolution.ts +59 -0
  250. package/src/services/agents/EscalationService.ts +281 -0
  251. package/src/services/agents/NDKAgentDiscovery.ts +95 -0
  252. package/src/services/agents/index.ts +7 -0
  253. package/src/services/agents-md/AgentsMdService.ts +184 -0
  254. package/src/services/agents-md/SystemReminderInjector.ts +238 -0
  255. package/src/services/agents-md/index.ts +11 -0
  256. package/src/services/apns/APNsClient.ts +203 -0
  257. package/src/services/apns/APNsService.ts +358 -0
  258. package/src/services/apns/index.ts +11 -0
  259. package/src/services/apns/types.ts +80 -0
  260. package/src/services/compression/CompressionService.ts +445 -0
  261. package/src/services/compression/compression-schema.ts +28 -0
  262. package/src/services/compression/compression-types.ts +74 -0
  263. package/src/services/compression/compression-utils.ts +587 -0
  264. package/src/services/config/types.ts +394 -0
  265. package/src/services/dispatch/AgentDispatchService.ts +937 -0
  266. package/src/services/dispatch/AgentRouter.ts +181 -0
  267. package/src/services/dispatch/DelegationCompletionHandler.ts +232 -0
  268. package/src/services/embedding/EmbeddingProvider.ts +188 -0
  269. package/src/services/embedding/index.ts +5 -0
  270. package/src/services/event-context/EventContextService.ts +108 -0
  271. package/src/services/event-context/index.ts +2 -0
  272. package/src/services/heuristics/ContextBuilder.ts +106 -0
  273. package/src/services/heuristics/HeuristicEngine.ts +200 -0
  274. package/src/services/heuristics/formatters.ts +58 -0
  275. package/src/services/heuristics/index.ts +12 -0
  276. package/src/services/heuristics/rules/index.ts +25 -0
  277. package/src/services/heuristics/rules/todoBeforeDelegation.ts +69 -0
  278. package/src/services/heuristics/rules/todoReminderOnToolUse.ts +63 -0
  279. package/src/services/heuristics/types.ts +144 -0
  280. package/src/services/image/ImageGenerationService.ts +389 -0
  281. package/src/services/image/index.ts +12 -0
  282. package/src/services/intervention/InterventionService.ts +1352 -0
  283. package/src/services/intervention/index.ts +7 -0
  284. package/src/services/mcp/MCPManager.ts +683 -0
  285. package/src/services/mcp/McpNotificationDelivery.ts +139 -0
  286. package/src/services/mcp/McpSubscriptionService.ts +653 -0
  287. package/src/services/mcp/mcpInstaller.ts +130 -0
  288. package/src/services/nip46/Nip46SigningLog.ts +81 -0
  289. package/src/services/nip46/Nip46SigningService.ts +467 -0
  290. package/src/services/nip46/index.ts +4 -0
  291. package/src/services/nudge/NudgeService.ts +224 -0
  292. package/src/services/nudge/NudgeWhitelistService.ts +382 -0
  293. package/src/services/nudge/index.ts +5 -0
  294. package/src/services/nudge/types.ts +83 -0
  295. package/src/services/projects/ProjectContext.ts +672 -0
  296. package/src/services/projects/ProjectContextStore.ts +102 -0
  297. package/src/services/projects/index.ts +6 -0
  298. package/src/services/prompt-compiler/index.ts +15 -0
  299. package/src/services/prompt-compiler/prompt-compiler-service.ts +1143 -0
  300. package/src/services/pubkey-gate/PubkeyGateService.ts +93 -0
  301. package/src/services/pubkey-gate/index.ts +1 -0
  302. package/src/services/rag/EmbeddingProviderFactory.ts +292 -0
  303. package/src/services/rag/LanceDBMaintenanceService.ts +211 -0
  304. package/src/services/rag/RAGDatabaseService.ts +173 -0
  305. package/src/services/rag/RAGOperations.ts +682 -0
  306. package/src/services/rag/RAGService.ts +240 -0
  307. package/src/services/rag/RagSubscriptionService.ts +618 -0
  308. package/src/services/rag/rag-utils.ts +174 -0
  309. package/src/services/ral/PendingDelegationsRegistry.ts +168 -0
  310. package/src/services/ral/RALRegistry.ts +2782 -0
  311. package/src/services/ral/index.ts +4 -0
  312. package/src/services/ral/types.ts +292 -0
  313. package/src/services/reports/LocalReportStore.ts +380 -0
  314. package/src/services/reports/ReportEmbeddingService.ts +430 -0
  315. package/src/services/reports/ReportService.ts +440 -0
  316. package/src/services/reports/articleUtils.ts +52 -0
  317. package/src/services/reports/index.ts +7 -0
  318. package/src/services/scheduling/SchedulerService.ts +1057 -0
  319. package/src/services/scheduling/errors.ts +14 -0
  320. package/src/services/scheduling/index.ts +7 -0
  321. package/src/services/scheduling/utils.ts +77 -0
  322. package/src/services/search/SearchProviderRegistry.ts +78 -0
  323. package/src/services/search/UnifiedSearchService.ts +218 -0
  324. package/src/services/search/index.ts +47 -0
  325. package/src/services/search/projectFilter.ts +22 -0
  326. package/src/services/search/providers/ConversationSearchProvider.ts +48 -0
  327. package/src/services/search/providers/LessonSearchProvider.ts +75 -0
  328. package/src/services/search/providers/ReportSearchProvider.ts +49 -0
  329. package/src/services/search/types.ts +144 -0
  330. package/src/services/skill/SkillService.ts +482 -0
  331. package/src/services/skill/index.ts +2 -0
  332. package/src/services/skill/types.ts +70 -0
  333. package/src/services/status/OperationsStatusService.ts +276 -0
  334. package/src/services/status/ProjectStatusService.ts +522 -0
  335. package/src/services/status/index.ts +11 -0
  336. package/src/services/storage/PrefixKVStore.ts +242 -0
  337. package/src/services/storage/index.ts +1 -0
  338. package/src/services/system-reminder/SystemReminderUtils.ts +96 -0
  339. package/src/services/system-reminder/index.ts +7 -0
  340. package/src/services/trust-pubkeys/TrustPubkeyService.ts +325 -0
  341. package/src/services/trust-pubkeys/index.ts +2 -0
  342. package/src/telemetry/ConversationSpanManager.ts +111 -0
  343. package/src/telemetry/EventLoopMonitor.ts +206 -0
  344. package/src/telemetry/LLMSpanRegistry.ts +20 -0
  345. package/src/telemetry/NostrSpanProcessor.ts +89 -0
  346. package/src/telemetry/ToolCallSpanProcessor.ts +66 -0
  347. package/src/telemetry/diagnostics.ts +27 -0
  348. package/src/telemetry/setup.ts +120 -0
  349. package/src/tools/implementations/agents_discover.ts +121 -0
  350. package/src/tools/implementations/agents_hire.ts +127 -0
  351. package/src/tools/implementations/agents_list.ts +96 -0
  352. package/src/tools/implementations/agents_publish.ts +611 -0
  353. package/src/tools/implementations/agents_read.ts +173 -0
  354. package/src/tools/implementations/agents_write.ts +200 -0
  355. package/src/tools/implementations/ask.ts +411 -0
  356. package/src/tools/implementations/change_model.ts +141 -0
  357. package/src/tools/implementations/conversation_get.ts +661 -0
  358. package/src/tools/implementations/conversation_list.ts +377 -0
  359. package/src/tools/implementations/conversation_search.ts +370 -0
  360. package/src/tools/implementations/delegate.ts +327 -0
  361. package/src/tools/implementations/delegate_crossproject.ts +209 -0
  362. package/src/tools/implementations/delegate_followup.ts +300 -0
  363. package/src/tools/implementations/fs_edit.ts +162 -0
  364. package/src/tools/implementations/fs_glob.ts +182 -0
  365. package/src/tools/implementations/fs_grep.ts +513 -0
  366. package/src/tools/implementations/fs_read.ts +332 -0
  367. package/src/tools/implementations/fs_write.ts +113 -0
  368. package/src/tools/implementations/generate_image.ts +259 -0
  369. package/src/tools/implementations/home_fs.ts +515 -0
  370. package/src/tools/implementations/kill.ts +651 -0
  371. package/src/tools/implementations/learn.ts +166 -0
  372. package/src/tools/implementations/lesson-formatter.ts +38 -0
  373. package/src/tools/implementations/lesson_delete.ts +164 -0
  374. package/src/tools/implementations/lesson_get.ts +105 -0
  375. package/src/tools/implementations/lessons_list.ts +153 -0
  376. package/src/tools/implementations/mcp_resource_read.ts +161 -0
  377. package/src/tools/implementations/mcp_subscribe.ts +158 -0
  378. package/src/tools/implementations/mcp_subscription_stop.ts +85 -0
  379. package/src/tools/implementations/nostr_fetch.ts +149 -0
  380. package/src/tools/implementations/nostr_publish_as_user.ts +353 -0
  381. package/src/tools/implementations/project_list.ts +146 -0
  382. package/src/tools/implementations/rag_add_documents.ts +573 -0
  383. package/src/tools/implementations/rag_create_collection.ts +65 -0
  384. package/src/tools/implementations/rag_delete_collection.ts +68 -0
  385. package/src/tools/implementations/rag_list_collections.ts +77 -0
  386. package/src/tools/implementations/rag_query.ts +107 -0
  387. package/src/tools/implementations/rag_subscription_create.ts +105 -0
  388. package/src/tools/implementations/rag_subscription_delete.ts +80 -0
  389. package/src/tools/implementations/rag_subscription_get.ts +123 -0
  390. package/src/tools/implementations/rag_subscription_list.ts +128 -0
  391. package/src/tools/implementations/report_delete.ts +79 -0
  392. package/src/tools/implementations/report_read.ts +160 -0
  393. package/src/tools/implementations/report_write.ts +278 -0
  394. package/src/tools/implementations/reports_list.ts +77 -0
  395. package/src/tools/implementations/schedule_task.ts +104 -0
  396. package/src/tools/implementations/schedule_task_cancel.ts +62 -0
  397. package/src/tools/implementations/schedule_task_once.ts +128 -0
  398. package/src/tools/implementations/schedule_tasks_list.ts +79 -0
  399. package/src/tools/implementations/search.ts +160 -0
  400. package/src/tools/implementations/shell.ts +553 -0
  401. package/src/tools/implementations/todo.ts +260 -0
  402. package/src/tools/implementations/upload_blob.ts +381 -0
  403. package/src/tools/implementations/web_fetch.ts +153 -0
  404. package/src/tools/implementations/web_search.ts +250 -0
  405. package/src/tools/registry.ts +670 -0
  406. package/src/tools/types.ts +177 -0
  407. package/src/tools/utils.ts +256 -0
  408. package/src/types/event-ids.ts +320 -0
  409. package/src/types/index.ts +46 -0
  410. package/src/utils/agentFetcher.ts +107 -0
  411. package/src/utils/cli-error.ts +29 -0
  412. package/src/utils/conversation-id.ts +27 -0
  413. package/src/utils/conversation-utils.ts +1 -0
  414. package/src/utils/delegation-chain.ts +357 -0
  415. package/src/utils/error-handler.ts +42 -0
  416. package/src/utils/git/gitignore.ts +69 -0
  417. package/src/utils/git/index.ts +2 -0
  418. package/src/utils/git/initializeGitRepo.ts +204 -0
  419. package/src/utils/git/worktree.ts +260 -0
  420. package/src/utils/lessonFormatter.ts +70 -0
  421. package/src/utils/lessonTrust.ts +24 -0
  422. package/src/utils/lockfile.ts +123 -0
  423. package/src/utils/logger.ts +149 -0
  424. package/src/utils/nostr-entity-parser.ts +365 -0
  425. package/src/utils/process.ts +49 -0
  426. package/src/wrapper.ts +262 -0
  427. package/tsconfig.json +41 -0
@@ -0,0 +1,184 @@
1
+ /**
2
+ * AgentsMdService - Service for discovering and reading AGENTS.md files
3
+ *
4
+ * AGENTS.md files provide contextual guidelines for AI agents working in specific
5
+ * directories. These files are discovered hierarchically and injected as system
6
+ * reminders after relevant tool results (like fs_read).
7
+ *
8
+ * Key behaviors:
9
+ * - Finds all AGENTS.md files from a path up to the project root
10
+ * - Returns them in order from most specific to most general
11
+ * - Caches file contents to avoid repeated reads
12
+ */
13
+
14
+ import { readFile, stat } from "node:fs/promises";
15
+ import { dirname, join, resolve } from "node:path";
16
+ import { existsSync } from "node:fs";
17
+ import { trace } from "@opentelemetry/api";
18
+
19
+ export interface AgentsMdFile {
20
+ /** Absolute path to the AGENTS.md file */
21
+ path: string;
22
+ /** Directory containing the AGENTS.md file */
23
+ directory: string;
24
+ /** Content of the AGENTS.md file */
25
+ content: string;
26
+ }
27
+
28
+ /**
29
+ * Singleton service for AGENTS.md file discovery and caching
30
+ */
31
+ class AgentsMdServiceImpl {
32
+ /** Cache of AGENTS.md file contents by absolute path */
33
+ private contentCache = new Map<string, string | null>();
34
+
35
+ /** Cache expiry time in milliseconds (5 minutes) */
36
+ private readonly CACHE_TTL = 5 * 60 * 1000;
37
+
38
+ /** Timestamps for cache entries */
39
+ private cacheTimestamps = new Map<string, number>();
40
+
41
+ /**
42
+ * Find all AGENTS.md files from the given path up to the project root.
43
+ *
44
+ * @param targetPath - The file or directory path being accessed
45
+ * @param projectRoot - The project root directory (stops search here)
46
+ * @returns Array of AGENTS.md files, ordered from most specific to most general
47
+ */
48
+ async findAgentsMdFiles(
49
+ targetPath: string,
50
+ projectRoot: string
51
+ ): Promise<AgentsMdFile[]> {
52
+ const absoluteTargetPath = resolve(targetPath);
53
+ const absoluteProjectRoot = resolve(projectRoot);
54
+
55
+ // Start from the directory containing the target
56
+ let currentDir = existsSync(absoluteTargetPath) && !absoluteTargetPath.endsWith("/")
57
+ ? (await this.isDirectory(absoluteTargetPath) ? absoluteTargetPath : dirname(absoluteTargetPath))
58
+ : dirname(absoluteTargetPath);
59
+
60
+ const results: AgentsMdFile[] = [];
61
+ const visited = new Set<string>();
62
+
63
+ // Walk up the directory tree until we reach the project root (inclusive)
64
+ // Note: startsWith handles equality case, so no need for explicit === check
65
+ while (currentDir.startsWith(absoluteProjectRoot)) {
66
+ // Avoid infinite loop
67
+ if (visited.has(currentDir)) break;
68
+ visited.add(currentDir);
69
+
70
+ const agentsMdPath = join(currentDir, "AGENTS.md");
71
+ const content = await this.readAgentsMd(agentsMdPath);
72
+
73
+ if (content !== null) {
74
+ results.push({
75
+ path: agentsMdPath,
76
+ directory: currentDir,
77
+ content,
78
+ });
79
+ }
80
+
81
+ // Stop at project root
82
+ if (currentDir === absoluteProjectRoot) break;
83
+
84
+ // Move up one directory
85
+ const parent = dirname(currentDir);
86
+ if (parent === currentDir) break; // Reached filesystem root
87
+ currentDir = parent;
88
+ }
89
+
90
+ return results;
91
+ }
92
+
93
+ /**
94
+ * Check if the project root has an AGENTS.md file.
95
+ *
96
+ * @param projectRoot - The project root directory
97
+ * @returns true if AGENTS.md exists at the project root
98
+ */
99
+ async hasRootAgentsMd(projectRoot: string): Promise<boolean> {
100
+ const agentsMdPath = join(resolve(projectRoot), "AGENTS.md");
101
+ const content = await this.readAgentsMd(agentsMdPath);
102
+ return content !== null;
103
+ }
104
+
105
+ /**
106
+ * Get the content of the root AGENTS.md file.
107
+ *
108
+ * @param projectRoot - The project root directory
109
+ * @returns The content of the AGENTS.md file, or null if not found
110
+ */
111
+ async getRootAgentsMdContent(projectRoot: string): Promise<string | null> {
112
+ const agentsMdPath = join(resolve(projectRoot), "AGENTS.md");
113
+ return this.readAgentsMd(agentsMdPath);
114
+ }
115
+
116
+ /**
117
+ * Read an AGENTS.md file with caching.
118
+ */
119
+ private async readAgentsMd(absolutePath: string): Promise<string | null> {
120
+ const now = Date.now();
121
+ const cachedTimestamp = this.cacheTimestamps.get(absolutePath);
122
+
123
+ // Check if cache entry exists and is still valid
124
+ if (cachedTimestamp && now - cachedTimestamp < this.CACHE_TTL) {
125
+ const cached = this.contentCache.get(absolutePath);
126
+ if (cached !== undefined) {
127
+ return cached;
128
+ }
129
+ }
130
+
131
+ // Read from disk
132
+ try {
133
+ if (!existsSync(absolutePath)) {
134
+ this.contentCache.set(absolutePath, null);
135
+ this.cacheTimestamps.set(absolutePath, now);
136
+ return null;
137
+ }
138
+
139
+ const content = await readFile(absolutePath, "utf-8");
140
+ this.contentCache.set(absolutePath, content);
141
+ this.cacheTimestamps.set(absolutePath, now);
142
+ return content;
143
+ } catch (error) {
144
+ // Trace error for debugging (file not found after existsSync is unexpected)
145
+ trace.getActiveSpan?.()?.addEvent("agents_md.read_error", {
146
+ "agents_md.path": absolutePath,
147
+ "agents_md.error": error instanceof Error ? error.message : String(error),
148
+ });
149
+ this.contentCache.set(absolutePath, null);
150
+ this.cacheTimestamps.set(absolutePath, now);
151
+ return null;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Check if a path is a directory
157
+ */
158
+ private async isDirectory(path: string): Promise<boolean> {
159
+ try {
160
+ const stats = await stat(path);
161
+ return stats.isDirectory();
162
+ } catch {
163
+ return false;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Clear the cache (useful for testing)
169
+ */
170
+ clearCache(): void {
171
+ this.contentCache.clear();
172
+ this.cacheTimestamps.clear();
173
+ }
174
+
175
+ /**
176
+ * Invalidate a specific path in the cache
177
+ */
178
+ invalidatePath(absolutePath: string): void {
179
+ this.contentCache.delete(absolutePath);
180
+ this.cacheTimestamps.delete(absolutePath);
181
+ }
182
+ }
183
+
184
+ export const agentsMdService = new AgentsMdServiceImpl();
@@ -0,0 +1,238 @@
1
+ /**
2
+ * SystemReminderInjector - Injects AGENTS.md content as system reminders
3
+ *
4
+ * This module handles the injection of AGENTS.md content into tool results,
5
+ * following these rules:
6
+ *
7
+ * 1. When a file/directory is read, find all AGENTS.md files from that path
8
+ * up to the project root
9
+ * 2. Inject system reminders after the tool output
10
+ * 3. Track which AGENTS.md files have already been shown (non-truncated)
11
+ * to avoid duplication
12
+ * 4. Format multiple AGENTS.md files in a single system-reminder block
13
+ * with clear path attribution
14
+ */
15
+
16
+ import { resolve, relative } from "node:path";
17
+ import { agentsMdService, type AgentsMdFile } from "./AgentsMdService";
18
+
19
+ /**
20
+ * Tracker for which AGENTS.md files have been shown in the current context
21
+ */
22
+ export interface AgentsMdVisibilityTracker {
23
+ /**
24
+ * Check if an AGENTS.md file has already been shown (is visible)
25
+ */
26
+ isVisible(agentsMdPath: string): boolean;
27
+
28
+ /**
29
+ * Mark an AGENTS.md file as visible (shown in a non-truncated tool result)
30
+ */
31
+ markVisible(agentsMdPath: string): void;
32
+
33
+ /**
34
+ * Get all visible AGENTS.md paths
35
+ */
36
+ getVisiblePaths(): Set<string>;
37
+ }
38
+
39
+ /**
40
+ * Create a visibility tracker for AGENTS.md files
41
+ */
42
+ export function createAgentsMdVisibilityTracker(): AgentsMdVisibilityTracker {
43
+ const visiblePaths = new Set<string>();
44
+
45
+ return {
46
+ isVisible(agentsMdPath: string): boolean {
47
+ return visiblePaths.has(resolve(agentsMdPath));
48
+ },
49
+
50
+ markVisible(agentsMdPath: string): void {
51
+ visiblePaths.add(resolve(agentsMdPath));
52
+ },
53
+
54
+ getVisiblePaths(): Set<string> {
55
+ return visiblePaths;
56
+ },
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Result from checking for relevant AGENTS.md system reminders
62
+ */
63
+ export interface SystemReminderResult {
64
+ /** Whether any new system reminders need to be injected */
65
+ hasReminders: boolean;
66
+ /** The formatted system reminder content (empty if no reminders) */
67
+ content: string;
68
+ /** The AGENTS.md files that were included */
69
+ includedFiles: AgentsMdFile[];
70
+ }
71
+
72
+ /**
73
+ * Format AGENTS.md files into a system reminder block.
74
+ *
75
+ * The format groups multiple files with clear path attribution:
76
+ * ```
77
+ * <system-reminder>
78
+ * # AGENTS.md from /path/to/directory
79
+ *
80
+ * [content of AGENTS.md]
81
+ *
82
+ * # AGENTS.md from /parent/directory
83
+ *
84
+ * [content of parent AGENTS.md]
85
+ * </system-reminder>
86
+ * ```
87
+ */
88
+ export function formatSystemReminder(
89
+ files: AgentsMdFile[],
90
+ projectRoot: string
91
+ ): string {
92
+ if (files.length === 0) {
93
+ return "";
94
+ }
95
+
96
+ const sections = files.map((file) => {
97
+ const relativePath = relative(projectRoot, file.directory);
98
+ const displayPath = relativePath || "(project root)";
99
+ return `# AGENTS.md from ${displayPath}\n\n${file.content.trim()}`;
100
+ });
101
+
102
+ return `\n<system-reminder>\n${sections.join("\n\n")}\n</system-reminder>`;
103
+ }
104
+
105
+ /**
106
+ * Get system reminders for a file read operation.
107
+ *
108
+ * This function:
109
+ * 1. Finds all AGENTS.md files from the target path up to project root
110
+ * 2. Filters out already-visible files (using the tracker)
111
+ * 3. Marks newly-found files as visible
112
+ * 4. Returns formatted system reminder content
113
+ *
114
+ * @param targetPath - The file/directory path being read
115
+ * @param projectRoot - The project root directory
116
+ * @param tracker - Visibility tracker for deduplication
117
+ * @param isTruncated - Whether the tool result is truncated (don't mark as visible if so)
118
+ * @returns System reminder result with content and metadata
119
+ */
120
+ export async function getSystemRemindersForPath(
121
+ targetPath: string,
122
+ projectRoot: string,
123
+ tracker: AgentsMdVisibilityTracker,
124
+ isTruncated: boolean = false
125
+ ): Promise<SystemReminderResult> {
126
+ const absoluteProjectRoot = resolve(projectRoot);
127
+
128
+ // Find all AGENTS.md files from the target path up to project root
129
+ const allFiles = await agentsMdService.findAgentsMdFiles(targetPath, absoluteProjectRoot);
130
+
131
+ // Filter out already-visible files
132
+ const newFiles = allFiles.filter((file) => !tracker.isVisible(file.path));
133
+
134
+ if (newFiles.length === 0) {
135
+ return {
136
+ hasReminders: false,
137
+ content: "",
138
+ includedFiles: [],
139
+ };
140
+ }
141
+
142
+ // Only mark as visible if the tool result is NOT truncated
143
+ // (truncated results aren't really "visible" to the model)
144
+ if (!isTruncated) {
145
+ for (const file of newFiles) {
146
+ tracker.markVisible(file.path);
147
+ }
148
+ }
149
+
150
+ const content = formatSystemReminder(newFiles, absoluteProjectRoot);
151
+
152
+ return {
153
+ hasReminders: true,
154
+ content,
155
+ includedFiles: newFiles,
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Check if a tool name is one that reads files/directories
161
+ * and should trigger AGENTS.md system reminder injection.
162
+ */
163
+ export function shouldInjectForTool(toolName: string): boolean {
164
+ // Tools that read files/directories
165
+ const fileReadTools = [
166
+ "fs_read",
167
+ "Read", // Claude Code's Read tool
168
+ "mcp__filesystem__read_file",
169
+ "mcp__filesystem__read_directory",
170
+ "mcp__filesystem__list_directory",
171
+ "mcp__filesystem__get_file_info",
172
+ ];
173
+
174
+ // Check for exact match or pattern match
175
+ return fileReadTools.includes(toolName) ||
176
+ toolName.startsWith("mcp__") && toolName.includes("read");
177
+ }
178
+
179
+ /**
180
+ * Extract the path from a tool's input arguments.
181
+ * Different tools use different parameter names for the path.
182
+ */
183
+ export function extractPathFromToolInput(input: unknown): string | null {
184
+ if (!input || typeof input !== "object") {
185
+ return null;
186
+ }
187
+
188
+ const inputObj = input as Record<string, unknown>;
189
+
190
+ // fs_read and similar tools use 'path'
191
+ if ("path" in inputObj && typeof inputObj.path === "string") {
192
+ return inputObj.path;
193
+ }
194
+
195
+ // Some tools use 'file_path'
196
+ if ("file_path" in inputObj && typeof inputObj.file_path === "string") {
197
+ return inputObj.file_path;
198
+ }
199
+
200
+ // Some tools use 'directory'
201
+ if ("directory" in inputObj && typeof inputObj.directory === "string") {
202
+ return inputObj.directory;
203
+ }
204
+
205
+ return null;
206
+ }
207
+
208
+ /**
209
+ * Append system reminder content to a tool result output.
210
+ *
211
+ * @param output - The original tool result output
212
+ * @param reminderContent - The system reminder content to append
213
+ * @returns The modified output with system reminder appended
214
+ */
215
+ export function appendSystemReminderToOutput(
216
+ output: unknown,
217
+ reminderContent: string
218
+ ): unknown {
219
+ if (typeof output === "string") {
220
+ return output + reminderContent;
221
+ }
222
+
223
+ if (output && typeof output === "object" && "value" in output) {
224
+ const existingOutput = output as { type?: string; value: unknown };
225
+ const valueStr = typeof existingOutput.value === "string"
226
+ ? existingOutput.value
227
+ : JSON.stringify(existingOutput.value);
228
+
229
+ return {
230
+ ...existingOutput,
231
+ value: valueStr + reminderContent,
232
+ };
233
+ }
234
+
235
+ // For other formats, try to stringify and append
236
+ const outputStr = typeof output === "string" ? output : JSON.stringify(output);
237
+ return outputStr + reminderContent;
238
+ }
@@ -0,0 +1,11 @@
1
+ export { agentsMdService, type AgentsMdFile } from "./AgentsMdService";
2
+ export {
3
+ createAgentsMdVisibilityTracker,
4
+ formatSystemReminder,
5
+ getSystemRemindersForPath,
6
+ shouldInjectForTool,
7
+ extractPathFromToolInput,
8
+ appendSystemReminderToOutput,
9
+ type AgentsMdVisibilityTracker,
10
+ type SystemReminderResult,
11
+ } from "./SystemReminderInjector";
@@ -0,0 +1,203 @@
1
+ /**
2
+ * HTTP/2 client for Apple Push Notification service (APNs).
3
+ *
4
+ * Handles JWT token generation (ES256) and push delivery.
5
+ * Uses native fetch (Bun supports HTTP/2 via ALPN).
6
+ */
7
+
8
+ import * as fs from "node:fs";
9
+ import * as crypto from "node:crypto";
10
+ import { logger } from "@/utils/logger";
11
+ import type { APNsPayload, APNsSendResult } from "./types";
12
+
13
+ const LOG_PREFIX = "[APNsClient]";
14
+
15
+ /** JWT tokens are valid for up to 1 hour; we refresh at 50 minutes. */
16
+ const JWT_REFRESH_INTERVAL_MS = 50 * 60 * 1000;
17
+
18
+ /** APNs production endpoint */
19
+ const APNS_PRODUCTION_HOST = "https://api.push.apple.com";
20
+
21
+ /** APNs sandbox endpoint */
22
+ const APNS_SANDBOX_HOST = "https://api.sandbox.push.apple.com";
23
+
24
+ export interface APNsClientConfig {
25
+ keyPath: string;
26
+ keyId: string;
27
+ teamId: string;
28
+ bundleId: string;
29
+ production: boolean;
30
+ }
31
+
32
+ /**
33
+ * Base64url encode a buffer (no padding).
34
+ */
35
+ function base64url(buf: Buffer): string {
36
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
37
+ }
38
+
39
+ export class APNsClient {
40
+ private config: APNsClientConfig;
41
+ private signingKey: crypto.KeyObject | null = null;
42
+ private cachedJwt: string | null = null;
43
+ private jwtIssuedAt = 0;
44
+ private fetchFn: typeof fetch;
45
+
46
+ constructor(clientConfig: APNsClientConfig, fetchFn?: typeof fetch) {
47
+ this.config = clientConfig;
48
+ this.fetchFn = fetchFn ?? fetch;
49
+ }
50
+
51
+ /**
52
+ * Load the .p8 private key from disk.
53
+ * Called lazily on first send.
54
+ */
55
+ private loadSigningKey(): void {
56
+ const keyData = fs.readFileSync(this.config.keyPath, "utf-8");
57
+ this.signingKey = crypto.createPrivateKey(keyData);
58
+
59
+ logger.info(`${LOG_PREFIX} Loaded APNs signing key`, {
60
+ keyId: this.config.keyId,
61
+ teamId: this.config.teamId,
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Generate a JWT for APNs authentication (ES256).
67
+ * Caches the token and refreshes before expiry.
68
+ */
69
+ private getJwt(): string {
70
+ const now = Math.floor(Date.now() / 1000);
71
+
72
+ // Return cached JWT if still fresh
73
+ if (this.cachedJwt && (now - this.jwtIssuedAt) * 1000 < JWT_REFRESH_INTERVAL_MS) {
74
+ return this.cachedJwt;
75
+ }
76
+
77
+ if (!this.signingKey) {
78
+ this.loadSigningKey();
79
+ }
80
+
81
+ // JWT Header
82
+ const header = base64url(
83
+ Buffer.from(JSON.stringify({ alg: "ES256", kid: this.config.keyId }))
84
+ );
85
+
86
+ // JWT Payload
87
+ const payload = base64url(
88
+ Buffer.from(JSON.stringify({ iss: this.config.teamId, iat: now }))
89
+ );
90
+
91
+ // Sign
92
+ if (!this.signingKey) {
93
+ throw new Error("Signing key not loaded");
94
+ }
95
+ const signingInput = `${header}.${payload}`;
96
+ const sign = crypto.createSign("SHA256");
97
+ sign.update(signingInput);
98
+ const derSignature = sign.sign(this.signingKey);
99
+
100
+ // Convert DER signature to raw r||s (64 bytes) for ES256
101
+ const rawSignature = derToRaw(derSignature);
102
+ const signature = base64url(rawSignature);
103
+
104
+ this.cachedJwt = `${signingInput}.${signature}`;
105
+ this.jwtIssuedAt = now;
106
+
107
+ logger.debug(`${LOG_PREFIX} Generated new JWT`, { iat: now });
108
+
109
+ return this.cachedJwt;
110
+ }
111
+
112
+ /**
113
+ * Send a push notification to a single device token.
114
+ */
115
+ async send(deviceToken: string, payload: APNsPayload): Promise<APNsSendResult> {
116
+ const host = this.config.production ? APNS_PRODUCTION_HOST : APNS_SANDBOX_HOST;
117
+ const url = `${host}/3/device/${deviceToken}`;
118
+ const body = JSON.stringify(payload);
119
+
120
+ try {
121
+ const jwt = this.getJwt();
122
+ const response = await this.fetchFn(url, {
123
+ method: "POST",
124
+ headers: {
125
+ "authorization": `bearer ${jwt}`,
126
+ "apns-topic": this.config.bundleId,
127
+ "apns-push-type": "alert",
128
+ "apns-priority": "10",
129
+ "content-type": "application/json",
130
+ },
131
+ body,
132
+ });
133
+
134
+ const statusCode = response.status;
135
+
136
+ if (statusCode === 200) {
137
+ return { success: true, statusCode };
138
+ }
139
+
140
+ // Parse error response
141
+ const errorBody = await response.json().catch(() => ({})) as Record<string, unknown>;
142
+ const reason = (errorBody.reason as string) ?? "unknown";
143
+
144
+ logger.warn(`${LOG_PREFIX} APNs rejected push`, {
145
+ statusCode,
146
+ reason,
147
+ deviceToken: deviceToken.substring(0, 8),
148
+ });
149
+
150
+ const result: APNsSendResult = { success: false, statusCode, reason };
151
+
152
+ // 410 Gone means the token is no longer valid
153
+ if (statusCode === 410 && typeof errorBody.timestamp === "number") {
154
+ result.timestampMs = errorBody.timestamp as number;
155
+ }
156
+
157
+ return result;
158
+ } catch (error) {
159
+ logger.error(`${LOG_PREFIX} Failed to send push notification`, {
160
+ error: error instanceof Error ? error.message : String(error),
161
+ deviceToken: deviceToken.substring(0, 8),
162
+ });
163
+
164
+ return { success: false, statusCode: 0, reason: "network_error" };
165
+ }
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Convert a DER-encoded ECDSA signature to raw r||s format (64 bytes for P-256).
171
+ * DER format: 0x30 <total_len> 0x02 <r_len> <r> 0x02 <s_len> <s>
172
+ */
173
+ function derToRaw(der: Buffer): Buffer {
174
+ const raw = Buffer.alloc(64);
175
+
176
+ // Parse DER structure
177
+ let offset = 2; // skip 0x30 and total length
178
+
179
+ // Read r
180
+ offset += 1; // skip 0x02
181
+ const rLen = der[offset] ?? 0;
182
+ offset += 1;
183
+ const rBytes = der.subarray(offset, offset + rLen);
184
+ offset += rLen;
185
+
186
+ // Read s
187
+ offset += 1; // skip 0x02
188
+ const sLen = der[offset] ?? 0;
189
+ offset += 1;
190
+ const sBytes = der.subarray(offset, offset + sLen);
191
+
192
+ // Copy r (right-aligned, strip leading zero if present)
193
+ const rStart = rBytes[0] === 0 ? 1 : 0;
194
+ const rActual = rBytes.subarray(rStart);
195
+ rActual.copy(raw, 32 - rActual.length);
196
+
197
+ // Copy s (right-aligned, strip leading zero if present)
198
+ const sStart = sBytes[0] === 0 ? 1 : 0;
199
+ const sActual = sBytes.subarray(sStart);
200
+ sActual.copy(raw, 64 - sActual.length);
201
+
202
+ return raw;
203
+ }