@hybridaione/hybridclaw 0.1.5

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 (196) hide show
  1. package/.env.example +14 -0
  2. package/.github/workflows/pages.yml +41 -0
  3. package/AGENTS.md +24 -0
  4. package/CHANGELOG.md +113 -0
  5. package/LICENSE +21 -0
  6. package/README.md +226 -0
  7. package/SECURITY.md +67 -0
  8. package/config.example.json +62 -0
  9. package/container/Dockerfile +23 -0
  10. package/container/package-lock.json +246 -0
  11. package/container/package.json +17 -0
  12. package/container/src/hybridai-client.ts +38 -0
  13. package/container/src/index.ts +198 -0
  14. package/container/src/ipc.ts +37 -0
  15. package/container/src/tools.ts +1008 -0
  16. package/container/src/types.ts +74 -0
  17. package/container/src/web-fetch.ts +389 -0
  18. package/container/tsconfig.json +16 -0
  19. package/dist/agent.d.ts +3 -0
  20. package/dist/agent.d.ts.map +1 -0
  21. package/dist/agent.js +21 -0
  22. package/dist/agent.js.map +1 -0
  23. package/dist/cli.d.ts +3 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +32 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/config.d.ts +38 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +107 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/container-runner.d.ts +11 -0
  32. package/dist/container-runner.d.ts.map +1 -0
  33. package/dist/container-runner.js +246 -0
  34. package/dist/container-runner.js.map +1 -0
  35. package/dist/conversation.d.ts +18 -0
  36. package/dist/conversation.d.ts.map +1 -0
  37. package/dist/conversation.js +29 -0
  38. package/dist/conversation.js.map +1 -0
  39. package/dist/db.d.ts +29 -0
  40. package/dist/db.d.ts.map +1 -0
  41. package/dist/db.js +205 -0
  42. package/dist/db.js.map +1 -0
  43. package/dist/discord.d.ts +17 -0
  44. package/dist/discord.d.ts.map +1 -0
  45. package/dist/discord.js +115 -0
  46. package/dist/discord.js.map +1 -0
  47. package/dist/env.d.ts +6 -0
  48. package/dist/env.d.ts.map +1 -0
  49. package/dist/env.js +36 -0
  50. package/dist/env.js.map +1 -0
  51. package/dist/gateway-client.d.ts +8 -0
  52. package/dist/gateway-client.d.ts.map +1 -0
  53. package/dist/gateway-client.js +57 -0
  54. package/dist/gateway-client.js.map +1 -0
  55. package/dist/gateway-service.d.ts +23 -0
  56. package/dist/gateway-service.d.ts.map +1 -0
  57. package/dist/gateway-service.js +360 -0
  58. package/dist/gateway-service.js.map +1 -0
  59. package/dist/gateway-types.d.ts +40 -0
  60. package/dist/gateway-types.d.ts.map +1 -0
  61. package/dist/gateway-types.js +6 -0
  62. package/dist/gateway-types.js.map +1 -0
  63. package/dist/gateway.d.ts +2 -0
  64. package/dist/gateway.d.ts.map +1 -0
  65. package/dist/gateway.js +138 -0
  66. package/dist/gateway.js.map +1 -0
  67. package/dist/hatch.d.ts +7 -0
  68. package/dist/hatch.d.ts.map +1 -0
  69. package/dist/hatch.js +99 -0
  70. package/dist/hatch.js.map +1 -0
  71. package/dist/health.d.ts +2 -0
  72. package/dist/health.d.ts.map +1 -0
  73. package/dist/health.js +169 -0
  74. package/dist/health.js.map +1 -0
  75. package/dist/heartbeat.d.ts +3 -0
  76. package/dist/heartbeat.d.ts.map +1 -0
  77. package/dist/heartbeat.js +103 -0
  78. package/dist/heartbeat.js.map +1 -0
  79. package/dist/hybridai-bots.d.ts +5 -0
  80. package/dist/hybridai-bots.d.ts.map +1 -0
  81. package/dist/hybridai-bots.js +34 -0
  82. package/dist/hybridai-bots.js.map +1 -0
  83. package/dist/index.d.ts +2 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +60 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/ipc.d.ts +33 -0
  88. package/dist/ipc.d.ts.map +1 -0
  89. package/dist/ipc.js +142 -0
  90. package/dist/ipc.js.map +1 -0
  91. package/dist/logger.d.ts +3 -0
  92. package/dist/logger.d.ts.map +1 -0
  93. package/dist/logger.js +21 -0
  94. package/dist/logger.js.map +1 -0
  95. package/dist/mount-security.d.ts +28 -0
  96. package/dist/mount-security.d.ts.map +1 -0
  97. package/dist/mount-security.js +187 -0
  98. package/dist/mount-security.js.map +1 -0
  99. package/dist/onboarding.d.ts +7 -0
  100. package/dist/onboarding.d.ts.map +1 -0
  101. package/dist/onboarding.js +445 -0
  102. package/dist/onboarding.js.map +1 -0
  103. package/dist/prompt-hooks.d.ts +17 -0
  104. package/dist/prompt-hooks.d.ts.map +1 -0
  105. package/dist/prompt-hooks.js +83 -0
  106. package/dist/prompt-hooks.js.map +1 -0
  107. package/dist/runtime-config.d.ts +78 -0
  108. package/dist/runtime-config.d.ts.map +1 -0
  109. package/dist/runtime-config.js +471 -0
  110. package/dist/runtime-config.js.map +1 -0
  111. package/dist/scheduled-task-runner.d.ts +11 -0
  112. package/dist/scheduled-task-runner.d.ts.map +1 -0
  113. package/dist/scheduled-task-runner.js +16 -0
  114. package/dist/scheduled-task-runner.js.map +1 -0
  115. package/dist/scheduler.d.ts +11 -0
  116. package/dist/scheduler.d.ts.map +1 -0
  117. package/dist/scheduler.js +165 -0
  118. package/dist/scheduler.js.map +1 -0
  119. package/dist/session-maintenance.d.ts +9 -0
  120. package/dist/session-maintenance.d.ts.map +1 -0
  121. package/dist/session-maintenance.js +168 -0
  122. package/dist/session-maintenance.js.map +1 -0
  123. package/dist/session-transcripts.d.ts +11 -0
  124. package/dist/session-transcripts.d.ts.map +1 -0
  125. package/dist/session-transcripts.js +32 -0
  126. package/dist/session-transcripts.js.map +1 -0
  127. package/dist/side-effects.d.ts +3 -0
  128. package/dist/side-effects.d.ts.map +1 -0
  129. package/dist/side-effects.js +30 -0
  130. package/dist/side-effects.js.map +1 -0
  131. package/dist/skills.d.ts +32 -0
  132. package/dist/skills.d.ts.map +1 -0
  133. package/dist/skills.js +376 -0
  134. package/dist/skills.js.map +1 -0
  135. package/dist/tui.d.ts +2 -0
  136. package/dist/tui.d.ts.map +1 -0
  137. package/dist/tui.js +305 -0
  138. package/dist/tui.js.map +1 -0
  139. package/dist/types.d.ts +132 -0
  140. package/dist/types.d.ts.map +1 -0
  141. package/dist/types.js +3 -0
  142. package/dist/types.js.map +1 -0
  143. package/dist/workspace.d.ts +25 -0
  144. package/dist/workspace.d.ts.map +1 -0
  145. package/dist/workspace.js +154 -0
  146. package/dist/workspace.js.map +1 -0
  147. package/docs/chat.html +929 -0
  148. package/docs/hai_logo_free.png +0 -0
  149. package/docs/hero.png +0 -0
  150. package/docs/index.html +1213 -0
  151. package/package.json +34 -0
  152. package/skills/current-time/SKILL.md +26 -0
  153. package/skills/iss-position/SKILL.md +46 -0
  154. package/skills/iss-position/agents/openai.yaml +3 -0
  155. package/skills/iss-position/scripts/get_iss_position.py +107 -0
  156. package/skills/repo-orientation/SKILL.md +74 -0
  157. package/src/agent.ts +35 -0
  158. package/src/cli.ts +35 -0
  159. package/src/config.ts +137 -0
  160. package/src/container-runner.ts +305 -0
  161. package/src/conversation.ts +49 -0
  162. package/src/db.ts +290 -0
  163. package/src/discord.ts +156 -0
  164. package/src/env.ts +36 -0
  165. package/src/gateway-client.ts +73 -0
  166. package/src/gateway-service.ts +456 -0
  167. package/src/gateway-types.ts +47 -0
  168. package/src/gateway.ts +199 -0
  169. package/src/health.ts +189 -0
  170. package/src/heartbeat.ts +121 -0
  171. package/src/hybridai-bots.ts +48 -0
  172. package/src/ipc.ts +163 -0
  173. package/src/logger.ts +26 -0
  174. package/src/mount-security.ts +216 -0
  175. package/src/onboarding.ts +569 -0
  176. package/src/prompt-hooks.ts +113 -0
  177. package/src/runtime-config.ts +588 -0
  178. package/src/scheduled-task-runner.ts +26 -0
  179. package/src/scheduler.ts +196 -0
  180. package/src/session-maintenance.ts +263 -0
  181. package/src/session-transcripts.ts +45 -0
  182. package/src/side-effects.ts +41 -0
  183. package/src/skills.ts +429 -0
  184. package/src/tui.ts +327 -0
  185. package/src/types.ts +135 -0
  186. package/src/workspace.ts +171 -0
  187. package/templates/AGENTS.md +104 -0
  188. package/templates/BOOT.md +3 -0
  189. package/templates/BOOTSTRAP.md +54 -0
  190. package/templates/HEARTBEAT.md +5 -0
  191. package/templates/IDENTITY.md +18 -0
  192. package/templates/MEMORY.md +19 -0
  193. package/templates/SOUL.md +36 -0
  194. package/templates/TOOLS.md +22 -0
  195. package/templates/USER.md +17 -0
  196. package/tsconfig.json +18 -0
@@ -0,0 +1,246 @@
1
+ {
2
+ "name": "hybridclaw-agent",
3
+ "version": "0.1.5",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "hybridclaw-agent",
9
+ "version": "0.1.5",
10
+ "dependencies": {
11
+ "@mozilla/readability": "^0.6.0",
12
+ "linkedom": "^0.18.12"
13
+ },
14
+ "devDependencies": {
15
+ "@types/node": "^22.10.0",
16
+ "typescript": "^5.7.0"
17
+ }
18
+ },
19
+ "node_modules/@mozilla/readability": {
20
+ "version": "0.6.0",
21
+ "resolved": "https://registry.npmjs.org/@mozilla/readability/-/readability-0.6.0.tgz",
22
+ "integrity": "sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==",
23
+ "license": "Apache-2.0",
24
+ "engines": {
25
+ "node": ">=14.0.0"
26
+ }
27
+ },
28
+ "node_modules/@types/node": {
29
+ "version": "22.19.11",
30
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz",
31
+ "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==",
32
+ "dev": true,
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "undici-types": "~6.21.0"
36
+ }
37
+ },
38
+ "node_modules/boolbase": {
39
+ "version": "1.0.0",
40
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
41
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
42
+ "license": "ISC"
43
+ },
44
+ "node_modules/css-select": {
45
+ "version": "5.2.2",
46
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
47
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
48
+ "license": "BSD-2-Clause",
49
+ "dependencies": {
50
+ "boolbase": "^1.0.0",
51
+ "css-what": "^6.1.0",
52
+ "domhandler": "^5.0.2",
53
+ "domutils": "^3.0.1",
54
+ "nth-check": "^2.0.1"
55
+ },
56
+ "funding": {
57
+ "url": "https://github.com/sponsors/fb55"
58
+ }
59
+ },
60
+ "node_modules/css-what": {
61
+ "version": "6.2.2",
62
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
63
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
64
+ "license": "BSD-2-Clause",
65
+ "engines": {
66
+ "node": ">= 6"
67
+ },
68
+ "funding": {
69
+ "url": "https://github.com/sponsors/fb55"
70
+ }
71
+ },
72
+ "node_modules/cssom": {
73
+ "version": "0.5.0",
74
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
75
+ "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
76
+ "license": "MIT"
77
+ },
78
+ "node_modules/dom-serializer": {
79
+ "version": "2.0.0",
80
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
81
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
82
+ "license": "MIT",
83
+ "dependencies": {
84
+ "domelementtype": "^2.3.0",
85
+ "domhandler": "^5.0.2",
86
+ "entities": "^4.2.0"
87
+ },
88
+ "funding": {
89
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
90
+ }
91
+ },
92
+ "node_modules/domelementtype": {
93
+ "version": "2.3.0",
94
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
95
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
96
+ "funding": [
97
+ {
98
+ "type": "github",
99
+ "url": "https://github.com/sponsors/fb55"
100
+ }
101
+ ],
102
+ "license": "BSD-2-Clause"
103
+ },
104
+ "node_modules/domhandler": {
105
+ "version": "5.0.3",
106
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
107
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
108
+ "license": "BSD-2-Clause",
109
+ "dependencies": {
110
+ "domelementtype": "^2.3.0"
111
+ },
112
+ "engines": {
113
+ "node": ">= 4"
114
+ },
115
+ "funding": {
116
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
117
+ }
118
+ },
119
+ "node_modules/domutils": {
120
+ "version": "3.2.2",
121
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
122
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
123
+ "license": "BSD-2-Clause",
124
+ "dependencies": {
125
+ "dom-serializer": "^2.0.0",
126
+ "domelementtype": "^2.3.0",
127
+ "domhandler": "^5.0.3"
128
+ },
129
+ "funding": {
130
+ "url": "https://github.com/fb55/domutils?sponsor=1"
131
+ }
132
+ },
133
+ "node_modules/entities": {
134
+ "version": "4.5.0",
135
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
136
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
137
+ "license": "BSD-2-Clause",
138
+ "engines": {
139
+ "node": ">=0.12"
140
+ },
141
+ "funding": {
142
+ "url": "https://github.com/fb55/entities?sponsor=1"
143
+ }
144
+ },
145
+ "node_modules/html-escaper": {
146
+ "version": "3.0.3",
147
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
148
+ "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==",
149
+ "license": "MIT"
150
+ },
151
+ "node_modules/htmlparser2": {
152
+ "version": "10.1.0",
153
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
154
+ "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
155
+ "funding": [
156
+ "https://github.com/fb55/htmlparser2?sponsor=1",
157
+ {
158
+ "type": "github",
159
+ "url": "https://github.com/sponsors/fb55"
160
+ }
161
+ ],
162
+ "license": "MIT",
163
+ "dependencies": {
164
+ "domelementtype": "^2.3.0",
165
+ "domhandler": "^5.0.3",
166
+ "domutils": "^3.2.2",
167
+ "entities": "^7.0.1"
168
+ }
169
+ },
170
+ "node_modules/htmlparser2/node_modules/entities": {
171
+ "version": "7.0.1",
172
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
173
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
174
+ "license": "BSD-2-Clause",
175
+ "engines": {
176
+ "node": ">=0.12"
177
+ },
178
+ "funding": {
179
+ "url": "https://github.com/fb55/entities?sponsor=1"
180
+ }
181
+ },
182
+ "node_modules/linkedom": {
183
+ "version": "0.18.12",
184
+ "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.12.tgz",
185
+ "integrity": "sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==",
186
+ "license": "ISC",
187
+ "dependencies": {
188
+ "css-select": "^5.1.0",
189
+ "cssom": "^0.5.0",
190
+ "html-escaper": "^3.0.3",
191
+ "htmlparser2": "^10.0.0",
192
+ "uhyphen": "^0.2.0"
193
+ },
194
+ "engines": {
195
+ "node": ">=16"
196
+ },
197
+ "peerDependencies": {
198
+ "canvas": ">= 2"
199
+ },
200
+ "peerDependenciesMeta": {
201
+ "canvas": {
202
+ "optional": true
203
+ }
204
+ }
205
+ },
206
+ "node_modules/nth-check": {
207
+ "version": "2.1.1",
208
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
209
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
210
+ "license": "BSD-2-Clause",
211
+ "dependencies": {
212
+ "boolbase": "^1.0.0"
213
+ },
214
+ "funding": {
215
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
216
+ }
217
+ },
218
+ "node_modules/typescript": {
219
+ "version": "5.9.3",
220
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
221
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
222
+ "dev": true,
223
+ "license": "Apache-2.0",
224
+ "bin": {
225
+ "tsc": "bin/tsc",
226
+ "tsserver": "bin/tsserver"
227
+ },
228
+ "engines": {
229
+ "node": ">=14.17"
230
+ }
231
+ },
232
+ "node_modules/uhyphen": {
233
+ "version": "0.2.0",
234
+ "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz",
235
+ "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==",
236
+ "license": "ISC"
237
+ },
238
+ "node_modules/undici-types": {
239
+ "version": "6.21.0",
240
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
241
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
242
+ "dev": true,
243
+ "license": "MIT"
244
+ }
245
+ }
246
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "hybridclaw-agent",
3
+ "version": "0.1.5",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc",
7
+ "lint": "tsc --noEmit --noUnusedLocals --noUnusedParameters"
8
+ },
9
+ "dependencies": {
10
+ "@mozilla/readability": "^0.6.0",
11
+ "linkedom": "^0.18.12"
12
+ },
13
+ "devDependencies": {
14
+ "@types/node": "^22.10.0",
15
+ "typescript": "^5.7.0"
16
+ }
17
+ }
@@ -0,0 +1,38 @@
1
+ import type { ChatCompletionResponse, ChatMessage, ToolDefinition } from './types.js';
2
+
3
+ export async function callHybridAI(
4
+ baseUrl: string,
5
+ apiKey: string,
6
+ model: string,
7
+ chatbotId: string,
8
+ enableRag: boolean,
9
+ messages: ChatMessage[],
10
+ tools: ToolDefinition[],
11
+ ): Promise<ChatCompletionResponse> {
12
+ const url = `${baseUrl}/v1/chat/completions`;
13
+
14
+ const body: Record<string, unknown> = {
15
+ model,
16
+ chatbot_id: chatbotId,
17
+ messages,
18
+ tools,
19
+ tool_choice: 'auto',
20
+ enable_rag: enableRag,
21
+ };
22
+
23
+ const response = await fetch(url, {
24
+ method: 'POST',
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ Authorization: `Bearer ${apiKey}`,
28
+ },
29
+ body: JSON.stringify(body),
30
+ });
31
+
32
+ if (!response.ok) {
33
+ const text = await response.text();
34
+ throw new Error(`HybridAI API error ${response.status}: ${text}`);
35
+ }
36
+
37
+ return (await response.json()) as ChatCompletionResponse;
38
+ }
@@ -0,0 +1,198 @@
1
+ import { callHybridAI } from './hybridai-client.js';
2
+ import { waitForInput, writeOutput } from './ipc.js';
3
+ import { executeTool, getPendingSideEffects, resetSideEffects, setScheduledTasks, setSessionContext, TOOL_DEFINITIONS } from './tools.js';
4
+ import type { ChatMessage, ContainerInput, ContainerOutput, ToolDefinition, ToolExecution } from './types.js';
5
+
6
+ const MAX_ITERATIONS = 20;
7
+ const IDLE_TIMEOUT_MS = parseInt(process.env.CONTAINER_IDLE_TIMEOUT || '300000', 10); // 5 min
8
+
9
+ /** API key received once via stdin, held in memory for the container lifetime. */
10
+ let storedApiKey = '';
11
+
12
+ /**
13
+ * Read a single line from stdin (the initial request JSON containing secrets).
14
+ * Resolves on the first newline — does not consume the entire stream, so docker -i
15
+ * keeps the container alive after the host stops writing.
16
+ */
17
+ function readStdinLine(): Promise<string> {
18
+ return new Promise((resolve, reject) => {
19
+ let buffer = '';
20
+ const onData = (chunk: Buffer) => {
21
+ buffer += chunk.toString('utf-8');
22
+ const nl = buffer.indexOf('\n');
23
+ if (nl !== -1) {
24
+ process.stdin.removeListener('data', onData);
25
+ process.stdin.removeListener('error', onError);
26
+ process.stdin.pause();
27
+ resolve(buffer.slice(0, nl));
28
+ }
29
+ };
30
+ const onError = (err: Error) => {
31
+ process.stdin.removeListener('data', onData);
32
+ reject(err);
33
+ };
34
+ process.stdin.on('data', onData);
35
+ process.stdin.on('error', onError);
36
+ process.stdin.resume();
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Process a single request: call API, run tool loop, write output.
42
+ */
43
+ async function processRequest(
44
+ messages: ChatMessage[],
45
+ apiKey: string,
46
+ baseUrl: string,
47
+ model: string,
48
+ chatbotId: string,
49
+ enableRag: boolean,
50
+ tools: ToolDefinition[],
51
+ ): Promise<ContainerOutput> {
52
+ const history: ChatMessage[] = [...messages];
53
+ const toolsUsed: string[] = [];
54
+ const toolExecutions: ToolExecution[] = [];
55
+ let iterations = 0;
56
+
57
+ while (iterations < MAX_ITERATIONS) {
58
+ iterations++;
59
+
60
+ let response;
61
+ try {
62
+ response = await callHybridAI(baseUrl, apiKey, model, chatbotId, enableRag, history, tools);
63
+ } catch (err) {
64
+ return { status: 'error', result: null, toolsUsed, toolExecutions, error: `API error: ${err instanceof Error ? err.message : String(err)}` };
65
+ }
66
+
67
+ const choice = response.choices[0];
68
+ if (!choice) {
69
+ return { status: 'error', result: null, toolsUsed, toolExecutions, error: 'No response from API' };
70
+ }
71
+
72
+ const assistantMessage: ChatMessage = {
73
+ role: 'assistant',
74
+ content: choice.message.content,
75
+ };
76
+
77
+ if (choice.message.tool_calls && choice.message.tool_calls.length > 0) {
78
+ assistantMessage.tool_calls = choice.message.tool_calls;
79
+ }
80
+
81
+ history.push(assistantMessage);
82
+
83
+ const toolCalls = choice.message.tool_calls || [];
84
+ if (toolCalls.length === 0) {
85
+ return { status: 'success', result: choice.message.content, toolsUsed: [...new Set(toolsUsed)], toolExecutions };
86
+ }
87
+
88
+ for (const call of toolCalls) {
89
+ const toolName = call.function.name;
90
+ toolsUsed.push(toolName);
91
+ console.error(`[tool] ${toolName}: ${call.function.arguments.slice(0, 100)}`);
92
+ const toolStart = Date.now();
93
+ const result = await executeTool(toolName, call.function.arguments);
94
+ const toolDuration = Date.now() - toolStart;
95
+ console.error(`[tool] ${toolName} result (${toolDuration}ms): ${result.slice(0, 100)}`);
96
+ toolExecutions.push({
97
+ name: toolName,
98
+ arguments: call.function.arguments,
99
+ result,
100
+ durationMs: toolDuration,
101
+ });
102
+ history.push({ role: 'tool', content: result, tool_call_id: call.id });
103
+
104
+ // Bail on fatal filesystem/system errors — retrying won't help
105
+ if (/EROFS|EPERM|EACCES|read-only file system/i.test(result)) {
106
+ return { status: 'error', result: null, toolsUsed, toolExecutions, error: result };
107
+ }
108
+ }
109
+ }
110
+
111
+ const lastAssistant = history.filter((m) => m.role === 'assistant').pop();
112
+ return { status: 'success', result: lastAssistant?.content || 'Max tool iterations reached.', toolsUsed: [...new Set(toolsUsed)], toolExecutions };
113
+ }
114
+
115
+ /**
116
+ * Main loop: read first request from stdin (with secrets), then poll IPC for follow-ups.
117
+ */
118
+ function resolveTools(input: ContainerInput): ToolDefinition[] {
119
+ const tools = input.allowedTools
120
+ ? TOOL_DEFINITIONS.filter((t) => input.allowedTools!.includes(t.function.name))
121
+ : [...TOOL_DEFINITIONS];
122
+ // Sort alphabetically for deterministic system-prompt ordering (KV cache stability)
123
+ tools.sort((a, b) => a.function.name.localeCompare(b.function.name));
124
+ return tools;
125
+ }
126
+
127
+ async function main(): Promise<void> {
128
+ console.error(`[hybridclaw-agent] started, idle timeout ${IDLE_TIMEOUT_MS}ms`);
129
+
130
+ // First request arrives via stdin (contains apiKey — never written to disk)
131
+ const stdinData = await readStdinLine();
132
+ const firstInput: ContainerInput = JSON.parse(stdinData);
133
+ storedApiKey = firstInput.apiKey;
134
+
135
+ console.error(`[hybridclaw-agent] processing first request (${firstInput.messages.length} messages)`);
136
+
137
+ resetSideEffects();
138
+ setScheduledTasks(firstInput.scheduledTasks);
139
+ setSessionContext(firstInput.sessionId);
140
+
141
+ const firstOutput = await processRequest(
142
+ firstInput.messages,
143
+ storedApiKey,
144
+ firstInput.baseUrl,
145
+ firstInput.model,
146
+ firstInput.chatbotId,
147
+ firstInput.enableRag,
148
+ resolveTools(firstInput),
149
+ );
150
+
151
+ firstOutput.sideEffects = getPendingSideEffects();
152
+ writeOutput(firstOutput);
153
+ console.error(`[hybridclaw-agent] first request complete: ${firstOutput.status}`);
154
+
155
+ // Subsequent requests come via IPC file polling
156
+ while (true) {
157
+ const input = await waitForInput(IDLE_TIMEOUT_MS);
158
+
159
+ if (!input) {
160
+ console.error('[hybridclaw-agent] idle timeout, exiting');
161
+ process.exit(0);
162
+ }
163
+
164
+ // Use stored apiKey — IPC file no longer contains it
165
+ const apiKey = input.apiKey || storedApiKey;
166
+
167
+ console.error(`[hybridclaw-agent] processing request (${input.messages.length} messages)`);
168
+
169
+ resetSideEffects();
170
+ setScheduledTasks(input.scheduledTasks);
171
+ setSessionContext(input.sessionId);
172
+
173
+ const output = await processRequest(
174
+ input.messages,
175
+ apiKey,
176
+ input.baseUrl,
177
+ input.model,
178
+ input.chatbotId,
179
+ input.enableRag,
180
+ resolveTools(input),
181
+ );
182
+
183
+ output.sideEffects = getPendingSideEffects();
184
+ writeOutput(output);
185
+ console.error(`[hybridclaw-agent] request complete: ${output.status}`);
186
+ }
187
+ }
188
+
189
+ main().catch((err) => {
190
+ console.error('Container agent fatal error:', err);
191
+ writeOutput({
192
+ status: 'error',
193
+ result: null,
194
+ toolsUsed: [],
195
+ error: `Unhandled error: ${err instanceof Error ? err.message : String(err)}`,
196
+ });
197
+ process.exit(1);
198
+ });
@@ -0,0 +1,37 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ import type { ContainerInput, ContainerOutput } from './types.js';
5
+
6
+ const IPC_DIR = '/ipc';
7
+ const INPUT_PATH = path.join(IPC_DIR, 'input.json');
8
+ const OUTPUT_PATH = path.join(IPC_DIR, 'output.json');
9
+
10
+ /**
11
+ * Poll for input.json. Returns null if idle timeout expires.
12
+ */
13
+ export async function waitForInput(idleTimeoutMs: number): Promise<ContainerInput | null> {
14
+ const pollInterval = 200;
15
+ const deadline = Date.now() + idleTimeoutMs;
16
+
17
+ while (Date.now() < deadline) {
18
+ if (fs.existsSync(INPUT_PATH)) {
19
+ try {
20
+ const raw = fs.readFileSync(INPUT_PATH, 'utf-8');
21
+ const input = JSON.parse(raw) as ContainerInput;
22
+ // Remove input file to signal we've consumed it
23
+ fs.unlinkSync(INPUT_PATH);
24
+ return input;
25
+ } catch {
26
+ // Partially written, retry
27
+ }
28
+ }
29
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
30
+ }
31
+
32
+ return null; // Idle timeout
33
+ }
34
+
35
+ export function writeOutput(output: ContainerOutput): void {
36
+ fs.writeFileSync(OUTPUT_PATH, JSON.stringify(output, null, 2));
37
+ }