@hybridaione/hybridclaw 0.2.2 → 0.2.6

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 (277) hide show
  1. package/.github/workflows/ci.yml +70 -0
  2. package/.husky/pre-commit +1 -0
  3. package/CHANGELOG.md +85 -0
  4. package/CONTRIBUTING.md +33 -0
  5. package/README.md +41 -16
  6. package/SECURITY.md +17 -0
  7. package/biome.json +35 -0
  8. package/config.example.json +71 -8
  9. package/container/package-lock.json +2 -2
  10. package/container/package.json +1 -1
  11. package/container/src/approval-policy.ts +1303 -0
  12. package/container/src/browser-tools.ts +431 -136
  13. package/container/src/extensions.ts +36 -12
  14. package/container/src/hybridai-client.ts +34 -13
  15. package/container/src/index.ts +451 -109
  16. package/container/src/ipc.ts +5 -3
  17. package/container/src/token-usage.ts +20 -10
  18. package/container/src/tools.ts +599 -225
  19. package/container/src/types.ts +32 -2
  20. package/container/src/web-fetch.ts +89 -32
  21. package/dist/agent.d.ts.map +1 -1
  22. package/dist/agent.js +10 -2
  23. package/dist/agent.js.map +1 -1
  24. package/dist/audit-cli.d.ts.map +1 -1
  25. package/dist/audit-cli.js +4 -2
  26. package/dist/audit-cli.js.map +1 -1
  27. package/dist/audit-events.d.ts.map +1 -1
  28. package/dist/audit-events.js +53 -3
  29. package/dist/audit-events.js.map +1 -1
  30. package/dist/audit-trail.d.ts.map +1 -1
  31. package/dist/audit-trail.js +17 -8
  32. package/dist/audit-trail.js.map +1 -1
  33. package/dist/channels/discord/attachments.d.ts.map +1 -1
  34. package/dist/channels/discord/attachments.js +14 -7
  35. package/dist/channels/discord/attachments.js.map +1 -1
  36. package/dist/channels/discord/debounce.d.ts +9 -0
  37. package/dist/channels/discord/debounce.d.ts.map +1 -0
  38. package/dist/channels/discord/debounce.js +20 -0
  39. package/dist/channels/discord/debounce.js.map +1 -0
  40. package/dist/channels/discord/delivery.d.ts +4 -1
  41. package/dist/channels/discord/delivery.d.ts.map +1 -1
  42. package/dist/channels/discord/delivery.js +19 -3
  43. package/dist/channels/discord/delivery.js.map +1 -1
  44. package/dist/channels/discord/human-delay.d.ts +16 -0
  45. package/dist/channels/discord/human-delay.d.ts.map +1 -0
  46. package/dist/channels/discord/human-delay.js +29 -0
  47. package/dist/channels/discord/human-delay.js.map +1 -0
  48. package/dist/channels/discord/inbound.d.ts +4 -0
  49. package/dist/channels/discord/inbound.d.ts.map +1 -1
  50. package/dist/channels/discord/inbound.js +45 -4
  51. package/dist/channels/discord/inbound.js.map +1 -1
  52. package/dist/channels/discord/mentions.d.ts.map +1 -1
  53. package/dist/channels/discord/mentions.js +16 -4
  54. package/dist/channels/discord/mentions.js.map +1 -1
  55. package/dist/channels/discord/presence.d.ts +33 -0
  56. package/dist/channels/discord/presence.d.ts.map +1 -0
  57. package/dist/channels/discord/presence.js +111 -0
  58. package/dist/channels/discord/presence.js.map +1 -0
  59. package/dist/channels/discord/rate-limiter.d.ts +14 -0
  60. package/dist/channels/discord/rate-limiter.d.ts.map +1 -0
  61. package/dist/channels/discord/rate-limiter.js +49 -0
  62. package/dist/channels/discord/rate-limiter.js.map +1 -0
  63. package/dist/channels/discord/reactions.d.ts +38 -0
  64. package/dist/channels/discord/reactions.d.ts.map +1 -0
  65. package/dist/channels/discord/reactions.js +151 -0
  66. package/dist/channels/discord/reactions.js.map +1 -0
  67. package/dist/channels/discord/runtime.d.ts +6 -3
  68. package/dist/channels/discord/runtime.d.ts.map +1 -1
  69. package/dist/channels/discord/runtime.js +621 -125
  70. package/dist/channels/discord/runtime.js.map +1 -1
  71. package/dist/channels/discord/stream.d.ts +4 -1
  72. package/dist/channels/discord/stream.d.ts.map +1 -1
  73. package/dist/channels/discord/stream.js +16 -8
  74. package/dist/channels/discord/stream.js.map +1 -1
  75. package/dist/channels/discord/tool-actions.d.ts.map +1 -1
  76. package/dist/channels/discord/tool-actions.js +24 -12
  77. package/dist/channels/discord/tool-actions.js.map +1 -1
  78. package/dist/channels/discord/typing.d.ts +15 -0
  79. package/dist/channels/discord/typing.d.ts.map +1 -0
  80. package/dist/channels/discord/typing.js +106 -0
  81. package/dist/channels/discord/typing.js.map +1 -0
  82. package/dist/chunk.d.ts.map +1 -1
  83. package/dist/chunk.js +4 -2
  84. package/dist/chunk.js.map +1 -1
  85. package/dist/cli.js +47 -22
  86. package/dist/cli.js.map +1 -1
  87. package/dist/config.d.ts +19 -0
  88. package/dist/config.d.ts.map +1 -1
  89. package/dist/config.js +103 -18
  90. package/dist/config.js.map +1 -1
  91. package/dist/container-runner.d.ts.map +1 -1
  92. package/dist/container-runner.js +58 -26
  93. package/dist/container-runner.js.map +1 -1
  94. package/dist/container-setup.d.ts.map +1 -1
  95. package/dist/container-setup.js +10 -9
  96. package/dist/container-setup.js.map +1 -1
  97. package/dist/conversation.d.ts +2 -2
  98. package/dist/conversation.d.ts.map +1 -1
  99. package/dist/conversation.js +1 -1
  100. package/dist/conversation.js.map +1 -1
  101. package/dist/db.d.ts +118 -2
  102. package/dist/db.d.ts.map +1 -1
  103. package/dist/db.js +1568 -50
  104. package/dist/db.js.map +1 -1
  105. package/dist/delegation-manager.d.ts.map +1 -1
  106. package/dist/delegation-manager.js +3 -2
  107. package/dist/delegation-manager.js.map +1 -1
  108. package/dist/gateway-client.d.ts +2 -2
  109. package/dist/gateway-client.d.ts.map +1 -1
  110. package/dist/gateway-client.js +10 -4
  111. package/dist/gateway-client.js.map +1 -1
  112. package/dist/gateway-service.d.ts +3 -3
  113. package/dist/gateway-service.d.ts.map +1 -1
  114. package/dist/gateway-service.js +563 -73
  115. package/dist/gateway-service.js.map +1 -1
  116. package/dist/gateway-types.d.ts +24 -0
  117. package/dist/gateway-types.d.ts.map +1 -1
  118. package/dist/gateway-types.js.map +1 -1
  119. package/dist/gateway.js +179 -24
  120. package/dist/gateway.js.map +1 -1
  121. package/dist/health.d.ts.map +1 -1
  122. package/dist/health.js +20 -10
  123. package/dist/health.js.map +1 -1
  124. package/dist/heartbeat.d.ts +4 -0
  125. package/dist/heartbeat.d.ts.map +1 -1
  126. package/dist/heartbeat.js +48 -20
  127. package/dist/heartbeat.js.map +1 -1
  128. package/dist/hybridai-bots.d.ts.map +1 -1
  129. package/dist/hybridai-bots.js +4 -2
  130. package/dist/hybridai-bots.js.map +1 -1
  131. package/dist/instruction-approval-audit.d.ts.map +1 -1
  132. package/dist/instruction-approval-audit.js.map +1 -1
  133. package/dist/instruction-integrity.d.ts.map +1 -1
  134. package/dist/instruction-integrity.js +8 -2
  135. package/dist/instruction-integrity.js.map +1 -1
  136. package/dist/ipc.d.ts.map +1 -1
  137. package/dist/ipc.js +6 -1
  138. package/dist/ipc.js.map +1 -1
  139. package/dist/logger.js.map +1 -1
  140. package/dist/memory-consolidation.d.ts +17 -0
  141. package/dist/memory-consolidation.d.ts.map +1 -0
  142. package/dist/memory-consolidation.js +25 -0
  143. package/dist/memory-consolidation.js.map +1 -0
  144. package/dist/memory-service.d.ts +200 -0
  145. package/dist/memory-service.d.ts.map +1 -0
  146. package/dist/memory-service.js +294 -0
  147. package/dist/memory-service.js.map +1 -0
  148. package/dist/mount-security.d.ts.map +1 -1
  149. package/dist/mount-security.js +31 -7
  150. package/dist/mount-security.js.map +1 -1
  151. package/dist/observability-ingest.d.ts.map +1 -1
  152. package/dist/observability-ingest.js +32 -11
  153. package/dist/observability-ingest.js.map +1 -1
  154. package/dist/onboarding.d.ts.map +1 -1
  155. package/dist/onboarding.js +32 -9
  156. package/dist/onboarding.js.map +1 -1
  157. package/dist/proactive-policy.d.ts.map +1 -1
  158. package/dist/proactive-policy.js +2 -1
  159. package/dist/proactive-policy.js.map +1 -1
  160. package/dist/prompt-hooks.d.ts.map +1 -1
  161. package/dist/prompt-hooks.js +9 -7
  162. package/dist/prompt-hooks.js.map +1 -1
  163. package/dist/runtime-config.d.ts +98 -1
  164. package/dist/runtime-config.d.ts.map +1 -1
  165. package/dist/runtime-config.js +477 -23
  166. package/dist/runtime-config.js.map +1 -1
  167. package/dist/scheduled-task-runner.d.ts +1 -0
  168. package/dist/scheduled-task-runner.d.ts.map +1 -1
  169. package/dist/scheduled-task-runner.js +29 -10
  170. package/dist/scheduled-task-runner.js.map +1 -1
  171. package/dist/scheduler.d.ts +43 -4
  172. package/dist/scheduler.d.ts.map +1 -1
  173. package/dist/scheduler.js +530 -56
  174. package/dist/scheduler.js.map +1 -1
  175. package/dist/session-export.d.ts +26 -0
  176. package/dist/session-export.d.ts.map +1 -0
  177. package/dist/session-export.js +149 -0
  178. package/dist/session-export.js.map +1 -0
  179. package/dist/session-maintenance.d.ts.map +1 -1
  180. package/dist/session-maintenance.js +75 -13
  181. package/dist/session-maintenance.js.map +1 -1
  182. package/dist/session-transcripts.d.ts.map +1 -1
  183. package/dist/session-transcripts.js.map +1 -1
  184. package/dist/side-effects.d.ts.map +1 -1
  185. package/dist/side-effects.js +14 -2
  186. package/dist/side-effects.js.map +1 -1
  187. package/dist/skills-guard.d.ts.map +1 -1
  188. package/dist/skills-guard.js +893 -130
  189. package/dist/skills-guard.js.map +1 -1
  190. package/dist/skills.d.ts +5 -0
  191. package/dist/skills.d.ts.map +1 -1
  192. package/dist/skills.js +29 -15
  193. package/dist/skills.js.map +1 -1
  194. package/dist/token-efficiency.d.ts.map +1 -1
  195. package/dist/token-efficiency.js.map +1 -1
  196. package/dist/tui.js +92 -11
  197. package/dist/tui.js.map +1 -1
  198. package/dist/types.d.ts +146 -0
  199. package/dist/types.d.ts.map +1 -1
  200. package/dist/types.js +24 -1
  201. package/dist/types.js.map +1 -1
  202. package/dist/update.d.ts.map +1 -1
  203. package/dist/update.js +42 -14
  204. package/dist/update.js.map +1 -1
  205. package/dist/workspace.d.ts.map +1 -1
  206. package/dist/workspace.js +49 -9
  207. package/dist/workspace.js.map +1 -1
  208. package/docs/chat.html +9 -3
  209. package/docs/index.html +37 -13
  210. package/package.json +8 -2
  211. package/src/agent.ts +16 -3
  212. package/src/audit-cli.ts +44 -16
  213. package/src/audit-events.ts +69 -5
  214. package/src/audit-trail.ts +41 -15
  215. package/src/channels/discord/attachments.ts +81 -27
  216. package/src/channels/discord/debounce.ts +25 -0
  217. package/src/channels/discord/delivery.ts +57 -13
  218. package/src/channels/discord/human-delay.ts +48 -0
  219. package/src/channels/discord/inbound.ts +66 -7
  220. package/src/channels/discord/mentions.ts +42 -18
  221. package/src/channels/discord/presence.ts +148 -0
  222. package/src/channels/discord/rate-limiter.ts +58 -0
  223. package/src/channels/discord/reactions.ts +211 -0
  224. package/src/channels/discord/runtime.ts +1048 -182
  225. package/src/channels/discord/stream.ts +73 -27
  226. package/src/channels/discord/tool-actions.ts +78 -37
  227. package/src/channels/discord/typing.ts +140 -0
  228. package/src/chunk.ts +12 -4
  229. package/src/cli.ts +141 -56
  230. package/src/config.ts +192 -34
  231. package/src/container-runner.ts +132 -42
  232. package/src/container-setup.ts +57 -22
  233. package/src/conversation.ts +9 -7
  234. package/src/db.ts +2217 -84
  235. package/src/delegation-manager.ts +6 -2
  236. package/src/gateway-client.ts +41 -17
  237. package/src/gateway-service.ts +1019 -201
  238. package/src/gateway-types.ts +33 -0
  239. package/src/gateway.ts +321 -48
  240. package/src/health.ts +66 -26
  241. package/src/heartbeat.ts +84 -22
  242. package/src/hybridai-bots.ts +14 -5
  243. package/src/instruction-approval-audit.ts +4 -1
  244. package/src/instruction-integrity.ts +30 -9
  245. package/src/ipc.ts +23 -5
  246. package/src/logger.ts +4 -1
  247. package/src/memory-consolidation.ts +41 -0
  248. package/src/memory-service.ts +606 -0
  249. package/src/mount-security.ts +58 -13
  250. package/src/observability-ingest.ts +134 -35
  251. package/src/onboarding.ts +126 -35
  252. package/src/proactive-policy.ts +3 -1
  253. package/src/prompt-hooks.ts +40 -17
  254. package/src/runtime-config.ts +1114 -99
  255. package/src/scheduled-task-runner.ts +63 -11
  256. package/src/scheduler.ts +683 -60
  257. package/src/session-export.ts +196 -0
  258. package/src/session-maintenance.ts +125 -22
  259. package/src/session-transcripts.ts +12 -3
  260. package/src/side-effects.ts +28 -5
  261. package/src/skills-guard.ts +1067 -219
  262. package/src/skills.ts +163 -65
  263. package/src/token-efficiency.ts +31 -9
  264. package/src/tui.ts +166 -25
  265. package/src/types.ts +195 -2
  266. package/src/update.ts +79 -23
  267. package/src/workspace.ts +63 -11
  268. package/tests/approval-policy.test.ts +224 -0
  269. package/tests/discord.basic.test.ts +82 -2
  270. package/tests/discord.human-presence.test.ts +85 -0
  271. package/tests/gateway-service.media-routing.test.ts +8 -2
  272. package/tests/memory-service.test.ts +1114 -0
  273. package/tests/token-efficiency.basic.test.ts +8 -2
  274. package/vitest.e2e.config.ts +3 -1
  275. package/vitest.integration.config.ts +3 -1
  276. package/vitest.live.config.ts +3 -1
  277. package/vitest.unit.config.ts +9 -0
package/src/onboarding.ts CHANGED
@@ -1,7 +1,7 @@
1
+ import { spawn } from 'child_process';
1
2
  import fs from 'fs';
2
3
  import path from 'path';
3
4
  import readline from 'readline/promises';
4
- import { spawn } from 'child_process';
5
5
 
6
6
  import { loadEnvFile } from './env.js';
7
7
  import {
@@ -64,7 +64,10 @@ function inferThemeFromColorFgBg(): TerminalTheme | null {
64
64
  const raw = process.env.COLORFGBG;
65
65
  if (!raw) return null;
66
66
 
67
- const parts = raw.split(/[;:]/).map((part) => part.trim()).filter(Boolean);
67
+ const parts = raw
68
+ .split(/[;:]/)
69
+ .map((part) => part.trim())
70
+ .filter(Boolean);
68
71
  if (parts.length === 0) return null;
69
72
 
70
73
  const bg = Number.parseInt(parts[parts.length - 1], 10);
@@ -75,12 +78,20 @@ function inferThemeFromColorFgBg(): TerminalTheme | null {
75
78
  }
76
79
 
77
80
  function resolveOnboardingTheme(): TerminalTheme {
78
- const override = (process.env.HYBRIDCLAW_THEME || process.env.HYBRIDCLAW_TUI_THEME || process.env.TUI_THEME || '').trim().toLowerCase();
81
+ const override = (
82
+ process.env.HYBRIDCLAW_THEME ||
83
+ process.env.HYBRIDCLAW_TUI_THEME ||
84
+ process.env.TUI_THEME ||
85
+ ''
86
+ )
87
+ .trim()
88
+ .toLowerCase();
79
89
  if (override === 'light' || override === 'dark') return override;
80
90
  return inferThemeFromColorFgBg() || 'dark';
81
91
  }
82
92
 
83
- const PALETTE = resolveOnboardingTheme() === 'light' ? LIGHT_PALETTE : DARK_PALETTE;
93
+ const PALETTE =
94
+ resolveOnboardingTheme() === 'light' ? LIGHT_PALETTE : DARK_PALETTE;
84
95
  const MUTED = PALETTE.muted;
85
96
  const TEAL = PALETTE.teal;
86
97
  const GOLD = PALETTE.gold;
@@ -135,11 +146,14 @@ function parseErrorMessage(payload: unknown, fallback: string): string {
135
146
  if (typeof payload !== 'object') return fallback;
136
147
 
137
148
  const asRecord = payload as Record<string, unknown>;
138
- if (typeof asRecord.message === 'string' && asRecord.message.trim()) return asRecord.message;
139
- if (typeof asRecord.error === 'string' && asRecord.error.trim()) return asRecord.error;
149
+ if (typeof asRecord.message === 'string' && asRecord.message.trim())
150
+ return asRecord.message;
151
+ if (typeof asRecord.error === 'string' && asRecord.error.trim())
152
+ return asRecord.error;
140
153
  if (asRecord.error && typeof asRecord.error === 'object') {
141
154
  const nested = asRecord.error as Record<string, unknown>;
142
- if (typeof nested.message === 'string' && nested.message.trim()) return nested.message;
155
+ if (typeof nested.message === 'string' && nested.message.trim())
156
+ return nested.message;
143
157
  }
144
158
 
145
159
  return fallback;
@@ -191,7 +205,8 @@ function extractApiKeyFromInput(raw: string): string | null {
191
205
 
192
206
  function getOpenCommand(url: string): { cmd: string; args: string[] } | null {
193
207
  if (process.platform === 'darwin') return { cmd: 'open', args: [url] };
194
- if (process.platform === 'win32') return { cmd: 'cmd', args: ['/c', 'start', '', url] };
208
+ if (process.platform === 'win32')
209
+ return { cmd: 'cmd', args: ['/c', 'start', '', url] };
195
210
  if (process.platform === 'linux') return { cmd: 'xdg-open', args: [url] };
196
211
  return null;
197
212
  }
@@ -215,20 +230,30 @@ async function tryOpenUrl(url: string): Promise<boolean> {
215
230
 
216
231
  function normalizeBots(payload: unknown): HybridAIBot[] {
217
232
  const data = payload as
218
- | { data?: Record<string, unknown>[]; bots?: Record<string, unknown>[]; items?: Record<string, unknown>[] }
233
+ | {
234
+ data?: Record<string, unknown>[];
235
+ bots?: Record<string, unknown>[];
236
+ items?: Record<string, unknown>[];
237
+ }
219
238
  | Record<string, unknown>[];
220
- const raw = Array.isArray(data) ? data : (data?.data || data?.bots || data?.items || []);
239
+ const raw = Array.isArray(data)
240
+ ? data
241
+ : data?.data || data?.bots || data?.items || [];
221
242
 
222
243
  return raw
223
244
  .map((item) => ({
224
245
  id: String(item.id ?? item._id ?? item.chatbot_id ?? item.bot_id ?? ''),
225
246
  name: String(item.bot_name ?? item.name ?? 'Unnamed'),
226
- description: item.description != null ? String(item.description) : undefined,
247
+ description:
248
+ item.description != null ? String(item.description) : undefined,
227
249
  }))
228
250
  .filter((bot) => Boolean(bot.id));
229
251
  }
230
252
 
231
- async function validateApiKey(baseUrl: string, apiKey: string): Promise<ApiKeyValidationResult> {
253
+ async function validateApiKey(
254
+ baseUrl: string,
255
+ apiKey: string,
256
+ ): Promise<ApiKeyValidationResult> {
232
257
  let response: Response;
233
258
  try {
234
259
  response = await fetch(resolveUrl(baseUrl, BOT_LIST_PATH), {
@@ -247,7 +272,10 @@ async function validateApiKey(baseUrl: string, apiKey: string): Promise<ApiKeyVa
247
272
  return {
248
273
  ok: false,
249
274
  bots: [],
250
- error: parseErrorMessage(payload, `Validation failed with HTTP ${response.status}.`),
275
+ error: parseErrorMessage(
276
+ payload,
277
+ `Validation failed with HTTP ${response.status}.`,
278
+ ),
251
279
  };
252
280
  }
253
281
 
@@ -285,7 +313,9 @@ function removeEnvLine(content: string, key: string): string {
285
313
 
286
314
  function saveEnvCredentials(apiKey: string): void {
287
315
  const envPath = path.join(process.cwd(), '.env');
288
- const existing = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf-8') : '';
316
+ const existing = fs.existsSync(envPath)
317
+ ? fs.readFileSync(envPath, 'utf-8')
318
+ : '';
289
319
 
290
320
  let updated = upsertEnvLine(existing, 'HYBRIDAI_API_KEY', apiKey);
291
321
  updated = removeEnvLine(updated, 'HYBRIDAI_CHATBOT_ID');
@@ -345,7 +375,9 @@ async function promptRequired(
345
375
  icon = ICON_PROMPT,
346
376
  ): Promise<string> {
347
377
  while (true) {
348
- const value = (await rl.question(styledPromptWithIcon(question, icon))).trim();
378
+ const value = (
379
+ await rl.question(styledPromptWithIcon(question, icon))
380
+ ).trim();
349
381
  if (value) return value;
350
382
  printWarn('Please enter a value.');
351
383
  }
@@ -366,7 +398,11 @@ async function promptYesNo(
366
398
  icon = ICON_PROMPT,
367
399
  ): Promise<boolean> {
368
400
  const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
369
- const raw = (await rl.question(styledPromptWithIcon(`${question}${suffix}`, icon))).trim().toLowerCase();
401
+ const raw = (
402
+ await rl.question(styledPromptWithIcon(`${question}${suffix}`, icon))
403
+ )
404
+ .trim()
405
+ .toLowerCase();
370
406
  if (!raw) return defaultYes;
371
407
  if (raw === 'y' || raw === 'yes') return true;
372
408
  if (raw === 'n' || raw === 'no') return false;
@@ -390,7 +426,9 @@ async function chooseDefaultBot(
390
426
  console.log(`${TEAL}${ICON_TITLE}${RESET} Available bots:`);
391
427
  for (let i = 0; i < Math.min(10, bots.length); i++) {
392
428
  const bot = bots[i];
393
- console.log(`${TEAL}${i + 1}.${RESET} ${bot.name} ${MUTED}(${bot.id})${RESET}`);
429
+ console.log(
430
+ `${TEAL}${i + 1}.${RESET} ${bot.name} ${MUTED}(${bot.id})${RESET}`,
431
+ );
394
432
  }
395
433
  if (bots.length > 10) {
396
434
  console.log(`${MUTED}...and ${bots.length - 10} more${RESET}`);
@@ -424,17 +462,28 @@ async function ensureSecurityTrustAcceptance(
424
462
  if (isSecurityTrustAccepted(existingConfig) && !force) return false;
425
463
 
426
464
  printHeadline('Security trust model acceptance');
427
- printInfo(`${commandLabel} requires explicit trust model acceptance before runtime starts.`);
465
+ printInfo(
466
+ `${commandLabel} requires explicit trust model acceptance before runtime starts.`,
467
+ );
428
468
  printMeta('Policy version', SECURITY_POLICY_VERSION);
429
469
  printMeta('Current acceptance', formatAcceptanceMeta());
430
470
  printLink(`Policy document: ${TRUST_MODEL_DOC_PATH}`);
431
471
  printInfo('Review TRUST_MODEL.md before continuing.');
432
- printInfo('Acceptance confirms you understand container/tool risks, data handling, and operator responsibilities.');
472
+ printInfo(
473
+ 'Acceptance confirms you understand container/tool risks, data handling, and operator responsibilities.',
474
+ );
433
475
  console.log();
434
476
 
435
- const ready = await promptYesNo(rl, 'Have you reviewed TRUST_MODEL.md and the trust model?', true, ICON_AUTH);
477
+ const ready = await promptYesNo(
478
+ rl,
479
+ 'Have you reviewed TRUST_MODEL.md and the trust model?',
480
+ true,
481
+ ICON_AUTH,
482
+ );
436
483
  if (!ready) {
437
- throw new Error('Security trust model acceptance is required. Review TRUST_MODEL.md and rerun onboarding.');
484
+ throw new Error(
485
+ 'Security trust model acceptance is required. Review TRUST_MODEL.md and rerun onboarding.',
486
+ );
438
487
  }
439
488
 
440
489
  while (true) {
@@ -462,7 +511,9 @@ async function ensureSecurityTrustAcceptance(
462
511
  return true;
463
512
  }
464
513
 
465
- export async function ensureHybridAICredentials(options: OnboardingOptions = {}): Promise<void> {
514
+ export async function ensureHybridAICredentials(
515
+ options: OnboardingOptions = {},
516
+ ): Promise<void> {
466
517
  loadEnvFile();
467
518
  const bootstrappedConfig = ensureRuntimeConfigFile();
468
519
 
@@ -487,12 +538,19 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
487
538
  const bootstrappedEnv = ensureEnvFileFromExample();
488
539
  if (bootstrappedEnv) loadEnvFile();
489
540
 
490
- const baseUrl = normalizeBaseUrl(getRuntimeConfig().hybridai.baseUrl || process.env.HYBRIDAI_BASE_URL || DEFAULT_BASE_URL);
541
+ const baseUrl = normalizeBaseUrl(
542
+ getRuntimeConfig().hybridai.baseUrl ||
543
+ process.env.HYBRIDAI_BASE_URL ||
544
+ DEFAULT_BASE_URL,
545
+ );
491
546
  const registerPageUrl = resolveUrl(baseUrl, DEFAULT_REGISTER_PATH);
492
547
  const loginUrl = resolveUrl(baseUrl, DEFAULT_LOGIN_PATH);
493
548
  const commandLabel = options.commandName || 'hybridclaw';
494
549
 
495
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
550
+ const rl = readline.createInterface({
551
+ input: process.stdin,
552
+ output: process.stdout,
553
+ });
496
554
 
497
555
  try {
498
556
  printHeadline('HybridAI onboarding');
@@ -505,19 +563,28 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
505
563
  await ensureSecurityTrustAcceptance(rl, commandLabel, force);
506
564
 
507
565
  if (existingKey && !force) {
508
- printSuccess('Security trust model already accepted and API key is present. No credential changes needed.');
566
+ printSuccess(
567
+ 'Security trust model already accepted and API key is present. No credential changes needed.',
568
+ );
509
569
  return;
510
570
  }
511
571
 
512
572
  printMeta('HYBRIDAI_BASE_URL', baseUrl);
513
573
  if (!existingKey) {
514
- printInfo(`No HYBRIDAI_API_KEY found. ${commandLabel} needs HybridAI credentials before it can start.`);
574
+ printInfo(
575
+ `No HYBRIDAI_API_KEY found. ${commandLabel} needs HybridAI credentials before it can start.`,
576
+ );
515
577
  } else {
516
578
  printSetup('Reconfiguring HybridAI credentials.');
517
579
  }
518
580
  console.log();
519
581
 
520
- const wantsNewAccount = await promptYesNo(rl, 'Create a new HybridAI account now?', true, ICON_PERSON);
582
+ const wantsNewAccount = await promptYesNo(
583
+ rl,
584
+ 'Create a new HybridAI account now?',
585
+ true,
586
+ ICON_PERSON,
587
+ );
521
588
  let email = '';
522
589
 
523
590
  if (wantsNewAccount) {
@@ -540,10 +607,17 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
540
607
  ICON_PERSON,
541
608
  );
542
609
  if (email) {
543
- const verifyUrl = resolveUrl(baseUrl, `${DEFAULT_VERIFY_PATH}?email=${encodeURIComponent(email)}`);
610
+ const verifyUrl = resolveUrl(
611
+ baseUrl,
612
+ `${DEFAULT_VERIFY_PATH}?email=${encodeURIComponent(email)}`,
613
+ );
544
614
  printLink(`Verify your email here: ${verifyUrl}`);
545
615
  }
546
- await promptOptional(rl, 'When registration/email verification is done, press Enter...', ICON_PERSON);
616
+ await promptOptional(
617
+ rl,
618
+ 'When registration/email verification is done, press Enter...',
619
+ ICON_PERSON,
620
+ );
547
621
  console.log();
548
622
  }
549
623
 
@@ -569,17 +643,31 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
569
643
  if (pasted) {
570
644
  seededApiKey = extractApiKeyFromInput(pasted) || '';
571
645
  if (!seededApiKey) {
572
- printWarn('Could not extract an API key from input; you can paste the raw key next.');
646
+ printWarn(
647
+ 'Could not extract an API key from input; you can paste the raw key next.',
648
+ );
573
649
  }
574
650
  } else {
575
- await promptOptional(rl, 'When login/API key retrieval is done, press Enter...', ICON_AUTH);
651
+ await promptOptional(
652
+ rl,
653
+ 'When login/API key retrieval is done, press Enter...',
654
+ ICON_AUTH,
655
+ );
576
656
  }
577
657
 
578
658
  let apiKey = seededApiKey;
579
- let validation: ApiKeyValidationResult = { ok: false, bots: [], error: 'No validation yet.' };
659
+ let validation: ApiKeyValidationResult = {
660
+ ok: false,
661
+ bots: [],
662
+ error: 'No validation yet.',
663
+ };
580
664
  while (true) {
581
665
  if (!apiKey) {
582
- const entered = await promptRequired(rl, 'HybridAI API key: ', ICON_KEY);
666
+ const entered = await promptRequired(
667
+ rl,
668
+ 'HybridAI API key: ',
669
+ ICON_KEY,
670
+ );
583
671
  apiKey = extractApiKeyFromInput(entered) || entered;
584
672
  }
585
673
 
@@ -602,7 +690,8 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
602
690
  apiKey = '';
603
691
  }
604
692
 
605
- const fallbackChatbotId = getRuntimeConfig().hybridai.defaultChatbotId.trim();
693
+ const fallbackChatbotId =
694
+ getRuntimeConfig().hybridai.defaultChatbotId.trim();
606
695
  const chosenChatbotId = await chooseDefaultBot(
607
696
  rl,
608
697
  validation.ok ? validation.bots : [],
@@ -619,7 +708,9 @@ export async function ensureHybridAICredentials(options: OnboardingOptions = {})
619
708
  if (chosenChatbotId) {
620
709
  printSuccess(`Default bot set to: ${chosenChatbotId}`);
621
710
  } else {
622
- printInfo('No default bot selected. You can set hybridai.defaultChatbotId in config.json later.');
711
+ printInfo(
712
+ 'No default bot selected. You can set hybridai.defaultChatbotId in config.json later.',
713
+ );
623
714
  }
624
715
  console.log();
625
716
  } finally {
@@ -29,7 +29,9 @@ export function isWithinActiveHours(now = new Date()): boolean {
29
29
  const end = Math.max(0, Math.min(23, PROACTIVE_ACTIVE_HOURS_END));
30
30
  if (start === end) return true;
31
31
 
32
- const hour = resolveHourInTimezone(now, PROACTIVE_ACTIVE_HOURS_TIMEZONE) ?? now.getHours();
32
+ const hour =
33
+ resolveHourInTimezone(now, PROACTIVE_ACTIVE_HOURS_TIMEZONE) ??
34
+ now.getHours();
33
35
 
34
36
  if (start < end) {
35
37
  return hour >= start && hour < end;
@@ -1,10 +1,14 @@
1
- import { getRuntimeConfig, isSecurityTrustAccepted, SECURITY_POLICY_VERSION } from './runtime-config.js';
2
- import { buildSkillsPrompt, type Skill } from './skills.js';
3
- import { buildContextPrompt, loadBootstrapFiles } from './workspace.js';
4
- import { APP_VERSION, HYBRIDAI_MODEL } from './config.js';
5
1
  import fs from 'fs';
6
2
  import os from 'os';
7
3
  import path from 'path';
4
+ import { APP_VERSION, HYBRIDAI_MODEL } from './config.js';
5
+ import {
6
+ getRuntimeConfig,
7
+ isSecurityTrustAccepted,
8
+ SECURITY_POLICY_VERSION,
9
+ } from './runtime-config.js';
10
+ import { buildSkillsPrompt, type Skill } from './skills.js';
11
+ import { buildContextPrompt, loadBootstrapFiles } from './workspace.js';
8
12
 
9
13
  export type PromptHookName = 'bootstrap' | 'memory' | 'safety' | 'runtime';
10
14
  export type ExtendedPromptHookName = PromptHookName | 'proactivity';
@@ -39,12 +43,14 @@ interface PromptHook {
39
43
  run: (context: PromptHookContext) => string;
40
44
  }
41
45
 
42
- export function buildSessionSummaryPrompt(summary: string | null | undefined): string {
46
+ export function buildSessionSummaryPrompt(
47
+ summary: string | null | undefined,
48
+ ): string {
43
49
  const trimmed = summary?.trim() || '';
44
50
  if (!trimmed) return '';
45
51
  return [
46
52
  '## Session Summary',
47
- 'Compressed context from earlier turns. Treat this as durable prior context.',
53
+ 'Compressed and recalled context from earlier turns. Treat this as durable prior context.',
48
54
  '',
49
55
  trimmed,
50
56
  ].join('\n');
@@ -106,13 +112,19 @@ function buildSafetyHook(context: PromptHookContext): string {
106
112
  ];
107
113
 
108
114
  if (accepted) {
109
- lines.push(`Trust model acceptance status: accepted (policy ${SECURITY_POLICY_VERSION}).`);
115
+ lines.push(
116
+ `Trust model acceptance status: accepted (policy ${SECURITY_POLICY_VERSION}).`,
117
+ );
110
118
  } else {
111
- lines.push('Trust model acceptance status: missing. Remain conservative and read-only unless user intent is explicit.');
119
+ lines.push(
120
+ 'Trust model acceptance status: missing. Remain conservative and read-only unless user intent is explicit.',
121
+ );
112
122
  }
113
123
 
114
124
  if (context.purpose === 'memory-flush') {
115
- lines.push('This is a pre-compaction memory flush turn. Persist only durable memory worth keeping.');
125
+ lines.push(
126
+ 'This is a pre-compaction memory flush turn. Persist only durable memory worth keeping.',
127
+ );
116
128
  }
117
129
 
118
130
  if (context.extraSafetyText?.trim()) {
@@ -203,7 +215,9 @@ function buildProactivityHook(context: PromptHookContext): string {
203
215
  }
204
216
 
205
217
  if (context.purpose === 'memory-flush') {
206
- lines.push('This is a memory-flush pass. Prioritize preserving durable context over immediate user-facing output.');
218
+ lines.push(
219
+ 'This is a memory-flush pass. Prioritize preserving durable context over immediate user-facing output.',
220
+ );
207
221
  }
208
222
 
209
223
  return lines.join('\n');
@@ -213,9 +227,10 @@ function buildRuntimeHook(context: PromptHookContext): string {
213
227
  const runtimeInfo = context.runtimeInfo || {};
214
228
  const model = runtimeInfo.model?.trim() || HYBRIDAI_MODEL;
215
229
  const defaultModel = runtimeInfo.defaultModel?.trim() || HYBRIDAI_MODEL;
216
- const guildLabel = runtimeInfo.guildId === null
217
- ? 'dm'
218
- : runtimeInfo.guildId?.trim() || 'unknown';
230
+ const guildLabel =
231
+ runtimeInfo.guildId === null
232
+ ? 'dm'
233
+ : runtimeInfo.guildId?.trim() || 'unknown';
219
234
 
220
235
  const lines = [
221
236
  '## Runtime Metadata',
@@ -223,7 +238,9 @@ function buildRuntimeHook(context: PromptHookContext): string {
223
238
  `Date (UTC): ${new Date().toISOString().slice(0, 10)}`,
224
239
  `Model: ${model}`,
225
240
  `Default model: ${defaultModel}`,
226
- runtimeInfo.channelId?.trim() ? `Channel ID: ${runtimeInfo.channelId.trim()}` : '',
241
+ runtimeInfo.channelId?.trim()
242
+ ? `Channel ID: ${runtimeInfo.channelId.trim()}`
243
+ : '',
227
244
  `Guild ID: ${guildLabel}`,
228
245
  `Node: ${process.version}`,
229
246
  `OS: ${process.platform} (${process.arch})`,
@@ -265,15 +282,21 @@ const PROMPT_HOOKS: PromptHook[] = [
265
282
  ];
266
283
 
267
284
  function resolvePromptMode(context: PromptHookContext): PromptMode {
268
- if (context.promptMode === 'minimal' || context.promptMode === 'none') return context.promptMode;
285
+ if (context.promptMode === 'minimal' || context.promptMode === 'none')
286
+ return context.promptMode;
269
287
  return 'full';
270
288
  }
271
289
 
272
- function isHookAllowedForMode(hookName: ExtendedPromptHookName, mode: PromptMode): boolean {
290
+ function isHookAllowedForMode(
291
+ hookName: ExtendedPromptHookName,
292
+ mode: PromptMode,
293
+ ): boolean {
273
294
  if (mode === 'none') return false;
274
295
  if (mode === 'full') return true;
275
296
  // Minimal mode keeps only safety + memory durability context.
276
- return hookName === 'memory' || hookName === 'safety' || hookName === 'runtime';
297
+ return (
298
+ hookName === 'memory' || hookName === 'safety' || hookName === 'runtime'
299
+ );
277
300
  }
278
301
 
279
302
  export function runPromptHooks(context: PromptHookContext): PromptHookOutput[] {