@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/config.ts CHANGED
@@ -1,7 +1,8 @@
1
- import fs from 'fs';
2
- import os from 'os';
3
- import path from 'path';
4
- import { randomBytes } from 'crypto';
1
+ import { randomBytes } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
5
6
 
6
7
  import { loadEnvFile } from './env.js';
7
8
  import {
@@ -27,13 +28,9 @@ function required(name: string): string {
27
28
  return val;
28
29
  }
29
30
 
30
- function resolveAppVersion(): string {
31
- const envVersion = process.env.npm_package_version;
32
- if (envVersion) return envVersion;
33
-
34
- const packagePath = path.join(process.cwd(), 'package.json');
31
+ function readVersionFromPackageJson(packageJsonPath: string): string | null {
35
32
  try {
36
- const raw = fs.readFileSync(packagePath, 'utf-8');
33
+ const raw = fs.readFileSync(packageJsonPath, 'utf-8');
37
34
  const parsed = JSON.parse(raw) as { version?: unknown };
38
35
  if (typeof parsed.version === 'string' && parsed.version.trim()) {
39
36
  return parsed.version.trim();
@@ -41,6 +38,31 @@ function resolveAppVersion(): string {
41
38
  } catch {
42
39
  // fall through
43
40
  }
41
+ return null;
42
+ }
43
+
44
+ function resolveAppVersion(): string {
45
+ const envVersion = process.env.npm_package_version;
46
+ if (envVersion?.trim()) return envVersion.trim();
47
+
48
+ const modulePath = fileURLToPath(import.meta.url);
49
+ const moduleVersion = readVersionFromPackageJson(
50
+ path.join(path.dirname(modulePath), '..', 'package.json'),
51
+ );
52
+ if (moduleVersion) return moduleVersion;
53
+
54
+ const entryPath = process.argv[1];
55
+ if (entryPath) {
56
+ const entryVersion = readVersionFromPackageJson(
57
+ path.join(path.dirname(path.resolve(entryPath)), '..', 'package.json'),
58
+ );
59
+ if (entryVersion) return entryVersion;
60
+ }
61
+
62
+ const cwdVersion = readVersionFromPackageJson(
63
+ path.join(process.cwd(), 'package.json'),
64
+ );
65
+ if (cwdVersion) return cwdVersion;
44
66
 
45
67
  return '0.0.0';
46
68
  }
@@ -62,6 +84,52 @@ export let DISCORD_PRESENCE_INTENT = false;
62
84
  export let DISCORD_RESPOND_TO_ALL_MESSAGES = false;
63
85
  export let DISCORD_COMMANDS_ONLY = false;
64
86
  export let DISCORD_COMMAND_USER_ID = '';
87
+ export let DISCORD_GROUP_POLICY: RuntimeConfig['discord']['groupPolicy'] =
88
+ 'open';
89
+ export let DISCORD_FREE_RESPONSE_CHANNELS: string[] = [];
90
+ export let DISCORD_HUMAN_DELAY: RuntimeConfig['discord']['humanDelay'] = {
91
+ mode: 'natural',
92
+ minMs: 800,
93
+ maxMs: 2_500,
94
+ };
95
+ export let DISCORD_TYPING_MODE: RuntimeConfig['discord']['typingMode'] =
96
+ 'thinking';
97
+ export let DISCORD_SELF_PRESENCE: RuntimeConfig['discord']['presence'] = {
98
+ enabled: true,
99
+ intervalMs: 30_000,
100
+ healthyText: 'Watching the channels',
101
+ degradedText: 'Thinking slowly...',
102
+ exhaustedText: 'Taking a break',
103
+ activityType: 'watching',
104
+ };
105
+ export let DISCORD_LIFECYCLE_REACTIONS: RuntimeConfig['discord']['lifecycleReactions'] =
106
+ {
107
+ enabled: true,
108
+ removeOnComplete: true,
109
+ phases: {
110
+ queued: '⏳',
111
+ thinking: '🤔',
112
+ toolUse: '⚙️',
113
+ streaming: '✍️',
114
+ done: '✅',
115
+ error: '❌',
116
+ },
117
+ };
118
+ export let DISCORD_ACK_REACTION = '👀';
119
+ export let DISCORD_ACK_REACTION_SCOPE: RuntimeConfig['discord']['ackReactionScope'] =
120
+ 'group-mentions';
121
+ export let DISCORD_REMOVE_ACK_AFTER_REPLY = true;
122
+ export let DISCORD_DEBOUNCE_MS = 2_500;
123
+ export let DISCORD_RATE_LIMIT_PER_USER = 0;
124
+ export let DISCORD_RATE_LIMIT_EXEMPT_ROLES: string[] = [];
125
+ export let DISCORD_SUPPRESS_PATTERNS: string[] = [
126
+ '/stop',
127
+ '/pause',
128
+ 'brb',
129
+ 'afk',
130
+ ];
131
+ export let DISCORD_MAX_CONCURRENT_PER_CHANNEL = 2;
132
+ export let DISCORD_GUILDS: RuntimeConfig['discord']['guilds'] = {};
65
133
 
66
134
  export let HYBRIDAI_BASE_URL = 'https://hybridai.one';
67
135
  export let HYBRIDAI_MODEL = 'gpt-5-nano';
@@ -75,7 +143,10 @@ export let CONTAINER_CPUS = '1';
75
143
  export let CONTAINER_TIMEOUT = 300_000;
76
144
 
77
145
  export const MOUNT_ALLOWLIST_PATH = path.join(
78
- os.homedir(), '.config', 'hybridclaw', 'mount-allowlist.json',
146
+ os.homedir(),
147
+ '.config',
148
+ 'hybridclaw',
149
+ 'mount-allowlist.json',
79
150
  );
80
151
  export let ADDITIONAL_MOUNTS = '';
81
152
 
@@ -85,6 +156,8 @@ export let MAX_CONCURRENT_CONTAINERS = 5;
85
156
  export let HEARTBEAT_ENABLED = true;
86
157
  export let HEARTBEAT_INTERVAL = 1_800_000;
87
158
  export let HEARTBEAT_CHANNEL = '';
159
+ export let MEMORY_DECAY_RATE = 0.1;
160
+ export let MEMORY_CONSOLIDATION_INTERVAL_HOURS = 24;
88
161
 
89
162
  export let HEALTH_HOST = '127.0.0.1';
90
163
  export let HEALTH_PORT = 9090;
@@ -97,7 +170,8 @@ export let DATA_DIR = path.dirname(DB_PATH);
97
170
 
98
171
  export let OBSERVABILITY_ENABLED = true;
99
172
  export let OBSERVABILITY_BASE_URL = 'https://hybridai.one';
100
- export let OBSERVABILITY_INGEST_PATH = '/api/v1/agent-observability/events:batch';
173
+ export let OBSERVABILITY_INGEST_PATH =
174
+ '/api/v1/agent-observability/events:batch';
101
175
  export let OBSERVABILITY_STATUS_PATH = '/api/v1/agent-observability/status';
102
176
  export let OBSERVABILITY_BOT_ID = '';
103
177
  export let OBSERVABILITY_AGENT_ID = 'agent_main';
@@ -107,7 +181,9 @@ export let OBSERVABILITY_FLUSH_INTERVAL_MS = 10_000;
107
181
  export let OBSERVABILITY_BATCH_MAX_EVENTS = 500;
108
182
 
109
183
  export let SESSION_COMPACTION_ENABLED = true;
110
- export let SESSION_COMPACTION_THRESHOLD = 120;
184
+ export let SESSION_COMPACTION_TOKEN_BUDGET = 100_000;
185
+ export let SESSION_COMPACTION_BUDGET_RATIO = 0.7;
186
+ export let SESSION_COMPACTION_THRESHOLD = 200;
111
187
  export let SESSION_COMPACTION_KEEP_RECENT = 40;
112
188
  export let SESSION_COMPACTION_SUMMARY_MAX_CHARS = 8_000;
113
189
  export let PRE_COMPACTION_MEMORY_FLUSH_ENABLED = true;
@@ -138,6 +214,32 @@ function applyRuntimeConfig(config: RuntimeConfig): void {
138
214
  DISCORD_RESPOND_TO_ALL_MESSAGES = config.discord.respondToAllMessages;
139
215
  DISCORD_COMMANDS_ONLY = config.discord.commandsOnly;
140
216
  DISCORD_COMMAND_USER_ID = config.discord.commandUserId;
217
+ DISCORD_GROUP_POLICY = config.discord.groupPolicy;
218
+ DISCORD_FREE_RESPONSE_CHANNELS = [...config.discord.freeResponseChannels];
219
+ DISCORD_HUMAN_DELAY = JSON.parse(
220
+ JSON.stringify(config.discord.humanDelay),
221
+ ) as RuntimeConfig['discord']['humanDelay'];
222
+ DISCORD_TYPING_MODE = config.discord.typingMode;
223
+ DISCORD_SELF_PRESENCE = JSON.parse(
224
+ JSON.stringify(config.discord.presence),
225
+ ) as RuntimeConfig['discord']['presence'];
226
+ DISCORD_LIFECYCLE_REACTIONS = JSON.parse(
227
+ JSON.stringify(config.discord.lifecycleReactions),
228
+ ) as RuntimeConfig['discord']['lifecycleReactions'];
229
+ DISCORD_ACK_REACTION = config.discord.ackReaction;
230
+ DISCORD_ACK_REACTION_SCOPE = config.discord.ackReactionScope;
231
+ DISCORD_REMOVE_ACK_AFTER_REPLY = config.discord.removeAckAfterReply;
232
+ DISCORD_DEBOUNCE_MS = Math.max(0, config.discord.debounceMs);
233
+ DISCORD_RATE_LIMIT_PER_USER = Math.max(0, config.discord.rateLimitPerUser);
234
+ DISCORD_RATE_LIMIT_EXEMPT_ROLES = [...config.discord.rateLimitExemptRoles];
235
+ DISCORD_SUPPRESS_PATTERNS = [...config.discord.suppressPatterns];
236
+ DISCORD_MAX_CONCURRENT_PER_CHANNEL = Math.max(
237
+ 1,
238
+ config.discord.maxConcurrentPerChannel,
239
+ );
240
+ DISCORD_GUILDS = JSON.parse(
241
+ JSON.stringify(config.discord.guilds),
242
+ ) as RuntimeConfig['discord']['guilds'];
141
243
 
142
244
  HYBRIDAI_BASE_URL = config.hybridai.baseUrl;
143
245
  HYBRIDAI_MODEL = config.hybridai.defaultModel;
@@ -156,16 +258,19 @@ function applyRuntimeConfig(config: RuntimeConfig): void {
156
258
  HEARTBEAT_ENABLED = config.heartbeat.enabled;
157
259
  HEARTBEAT_INTERVAL = config.heartbeat.intervalMs;
158
260
  HEARTBEAT_CHANNEL = config.heartbeat.channel;
261
+ MEMORY_DECAY_RATE = config.memory.decayRate;
262
+ MEMORY_CONSOLIDATION_INTERVAL_HOURS =
263
+ config.memory.consolidationIntervalHours;
159
264
 
160
265
  HEALTH_HOST = config.ops.healthHost;
161
266
  HEALTH_PORT = config.ops.healthPort;
162
267
  WEB_API_TOKEN = process.env.WEB_API_TOKEN || config.ops.webApiToken;
163
268
  GATEWAY_BASE_URL = config.ops.gatewayBaseUrl;
164
269
  GATEWAY_API_TOKEN =
165
- process.env.GATEWAY_API_TOKEN
166
- || config.ops.gatewayApiToken
167
- || WEB_API_TOKEN
168
- || INTERNAL_GATEWAY_API_TOKEN;
270
+ process.env.GATEWAY_API_TOKEN ||
271
+ config.ops.gatewayApiToken ||
272
+ WEB_API_TOKEN ||
273
+ INTERNAL_GATEWAY_API_TOKEN;
169
274
  DB_PATH = config.ops.dbPath;
170
275
  DATA_DIR = path.dirname(DB_PATH);
171
276
 
@@ -177,41 +282,94 @@ function applyRuntimeConfig(config: RuntimeConfig): void {
177
282
  OBSERVABILITY_AGENT_ID = config.observability.agentId;
178
283
  OBSERVABILITY_LABEL = config.observability.label;
179
284
  OBSERVABILITY_ENVIRONMENT = config.observability.environment;
180
- OBSERVABILITY_FLUSH_INTERVAL_MS = Math.max(1_000, config.observability.flushIntervalMs);
181
- OBSERVABILITY_BATCH_MAX_EVENTS = Math.max(1, Math.min(1_000, config.observability.batchMaxEvents));
285
+ OBSERVABILITY_FLUSH_INTERVAL_MS = Math.max(
286
+ 1_000,
287
+ config.observability.flushIntervalMs,
288
+ );
289
+ OBSERVABILITY_BATCH_MAX_EVENTS = Math.max(
290
+ 1,
291
+ Math.min(1_000, config.observability.batchMaxEvents),
292
+ );
182
293
 
183
294
  SESSION_COMPACTION_ENABLED = config.sessionCompaction.enabled;
184
- SESSION_COMPACTION_THRESHOLD = Math.max(20, config.sessionCompaction.threshold);
295
+ SESSION_COMPACTION_TOKEN_BUDGET = Math.max(
296
+ 1_000,
297
+ config.sessionCompaction.tokenBudget,
298
+ );
299
+ SESSION_COMPACTION_BUDGET_RATIO = Math.max(
300
+ 0.05,
301
+ Math.min(1, config.sessionCompaction.budgetRatio),
302
+ );
303
+ SESSION_COMPACTION_THRESHOLD = Math.max(
304
+ 20,
305
+ config.sessionCompaction.threshold,
306
+ );
185
307
  SESSION_COMPACTION_KEEP_RECENT = Math.max(
186
308
  1,
187
- Math.min(config.sessionCompaction.keepRecent, SESSION_COMPACTION_THRESHOLD - 1),
309
+ Math.min(
310
+ config.sessionCompaction.keepRecent,
311
+ SESSION_COMPACTION_THRESHOLD - 1,
312
+ ),
313
+ );
314
+ SESSION_COMPACTION_SUMMARY_MAX_CHARS = Math.max(
315
+ 1_000,
316
+ config.sessionCompaction.summaryMaxChars,
317
+ );
318
+ PRE_COMPACTION_MEMORY_FLUSH_ENABLED =
319
+ config.sessionCompaction.preCompactionMemoryFlush.enabled;
320
+ PRE_COMPACTION_MEMORY_FLUSH_MAX_MESSAGES = Math.max(
321
+ 8,
322
+ config.sessionCompaction.preCompactionMemoryFlush.maxMessages,
323
+ );
324
+ PRE_COMPACTION_MEMORY_FLUSH_MAX_CHARS = Math.max(
325
+ 4_000,
326
+ config.sessionCompaction.preCompactionMemoryFlush.maxChars,
188
327
  );
189
- SESSION_COMPACTION_SUMMARY_MAX_CHARS = Math.max(1_000, config.sessionCompaction.summaryMaxChars);
190
- PRE_COMPACTION_MEMORY_FLUSH_ENABLED = config.sessionCompaction.preCompactionMemoryFlush.enabled;
191
- PRE_COMPACTION_MEMORY_FLUSH_MAX_MESSAGES = Math.max(8, config.sessionCompaction.preCompactionMemoryFlush.maxMessages);
192
- PRE_COMPACTION_MEMORY_FLUSH_MAX_CHARS = Math.max(4_000, config.sessionCompaction.preCompactionMemoryFlush.maxChars);
193
328
 
194
329
  PROACTIVE_ACTIVE_HOURS_ENABLED = config.proactive.activeHours.enabled;
195
330
  PROACTIVE_ACTIVE_HOURS_TIMEZONE = config.proactive.activeHours.timezone;
196
- PROACTIVE_ACTIVE_HOURS_START = Math.max(0, Math.min(23, config.proactive.activeHours.startHour));
197
- PROACTIVE_ACTIVE_HOURS_END = Math.max(0, Math.min(23, config.proactive.activeHours.endHour));
198
- PROACTIVE_QUEUE_OUTSIDE_HOURS = config.proactive.activeHours.queueOutsideHours;
331
+ PROACTIVE_ACTIVE_HOURS_START = Math.max(
332
+ 0,
333
+ Math.min(23, config.proactive.activeHours.startHour),
334
+ );
335
+ PROACTIVE_ACTIVE_HOURS_END = Math.max(
336
+ 0,
337
+ Math.min(23, config.proactive.activeHours.endHour),
338
+ );
339
+ PROACTIVE_QUEUE_OUTSIDE_HOURS =
340
+ config.proactive.activeHours.queueOutsideHours;
199
341
 
200
342
  PROACTIVE_DELEGATION_ENABLED = config.proactive.delegation.enabled;
201
- PROACTIVE_DELEGATION_MAX_CONCURRENT = Math.max(1, config.proactive.delegation.maxConcurrent);
202
- PROACTIVE_DELEGATION_MAX_DEPTH = Math.max(1, config.proactive.delegation.maxDepth);
203
- PROACTIVE_DELEGATION_MAX_PER_TURN = Math.max(1, config.proactive.delegation.maxPerTurn);
343
+ PROACTIVE_DELEGATION_MAX_CONCURRENT = Math.max(
344
+ 1,
345
+ config.proactive.delegation.maxConcurrent,
346
+ );
347
+ PROACTIVE_DELEGATION_MAX_DEPTH = Math.max(
348
+ 1,
349
+ config.proactive.delegation.maxDepth,
350
+ );
351
+ PROACTIVE_DELEGATION_MAX_PER_TURN = Math.max(
352
+ 1,
353
+ config.proactive.delegation.maxPerTurn,
354
+ );
204
355
 
205
356
  PROACTIVE_AUTO_RETRY_ENABLED = config.proactive.autoRetry.enabled;
206
- PROACTIVE_AUTO_RETRY_MAX_ATTEMPTS = Math.max(1, config.proactive.autoRetry.maxAttempts);
207
- PROACTIVE_AUTO_RETRY_BASE_DELAY_MS = Math.max(100, config.proactive.autoRetry.baseDelayMs);
357
+ PROACTIVE_AUTO_RETRY_MAX_ATTEMPTS = Math.max(
358
+ 1,
359
+ config.proactive.autoRetry.maxAttempts,
360
+ );
361
+ PROACTIVE_AUTO_RETRY_BASE_DELAY_MS = Math.max(
362
+ 100,
363
+ config.proactive.autoRetry.baseDelayMs,
364
+ );
208
365
  PROACTIVE_AUTO_RETRY_MAX_DELAY_MS = Math.max(
209
366
  PROACTIVE_AUTO_RETRY_BASE_DELAY_MS,
210
367
  config.proactive.autoRetry.maxDelayMs,
211
368
  );
212
369
 
213
370
  const rawRalphMax = Math.trunc(config.proactive.ralph.maxIterations);
214
- PROACTIVE_RALPH_MAX_ITERATIONS = rawRalphMax === -1 ? -1 : Math.max(0, rawRalphMax);
371
+ PROACTIVE_RALPH_MAX_ITERATIONS =
372
+ rawRalphMax === -1 ? -1 : Math.max(0, rawRalphMax);
215
373
  }
216
374
 
217
375
  applyRuntimeConfig(getRuntimeConfig());
@@ -2,7 +2,7 @@
2
2
  * Container Runner — manages a pool of persistent containers.
3
3
  * Containers stay alive between requests and exit after an idle timeout.
4
4
  */
5
- import { ChildProcess, spawn } from 'child_process';
5
+ import { type ChildProcess, spawn } from 'child_process';
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
8
 
@@ -15,6 +15,7 @@ import {
15
15
  DATA_DIR,
16
16
  GATEWAY_API_TOKEN,
17
17
  GATEWAY_BASE_URL,
18
+ getHybridAIApiKey,
18
19
  HYBRIDAI_BASE_URL,
19
20
  HYBRIDAI_MODEL,
20
21
  MAX_CONCURRENT_CONTAINERS,
@@ -23,9 +24,15 @@ import {
23
24
  PROACTIVE_AUTO_RETRY_MAX_ATTEMPTS,
24
25
  PROACTIVE_AUTO_RETRY_MAX_DELAY_MS,
25
26
  PROACTIVE_RALPH_MAX_ITERATIONS,
26
- getHybridAIApiKey,
27
27
  } from './config.js';
28
- import { cleanupIpc, ensureAgentDirs, ensureSessionDirs, getSessionPaths, readOutput, writeInput } from './ipc.js';
28
+ import {
29
+ cleanupIpc,
30
+ ensureAgentDirs,
31
+ ensureSessionDirs,
32
+ getSessionPaths,
33
+ readOutput,
34
+ writeInput,
35
+ } from './ipc.js';
29
36
  import { logger } from './logger.js';
30
37
  import { validateAdditionalMounts } from './mount-security.js';
31
38
  import type {
@@ -52,7 +59,8 @@ interface PoolEntry {
52
59
  }
53
60
 
54
61
  const pool = new Map<string, PoolEntry>();
55
- const TOOL_RESULT_RE = /^\[tool\]\s+([a-zA-Z0-9_.-]+)\s+result\s+\((\d+)ms\):\s*(.*)$/;
62
+ const TOOL_RESULT_RE =
63
+ /^\[tool\]\s+([a-zA-Z0-9_.-]+)\s+result\s+\((\d+)ms\):\s*(.*)$/;
56
64
  const TOOL_START_RE = /^\[tool\]\s+([a-zA-Z0-9_.-]+):\s*(.*)$/;
57
65
  const STREAM_DELTA_RE = /^\[stream\]\s+([A-Za-z0-9+/=]+)$/;
58
66
  const CONTAINER_WORKSPACE_ROOT = '/workspace';
@@ -73,7 +81,10 @@ function emitTextDelta(entry: PoolEntry, line: string): void {
73
81
  if (!delta) return;
74
82
  callback(delta);
75
83
  } catch (err) {
76
- logger.debug({ sessionId: entry.sessionId, err }, 'Text delta callback failed');
84
+ logger.debug(
85
+ { sessionId: entry.sessionId, err },
86
+ 'Text delta callback failed',
87
+ );
77
88
  }
78
89
  }
79
90
 
@@ -92,7 +103,10 @@ function emitToolProgress(entry: PoolEntry, line: string): void {
92
103
  preview: resultMatch[3],
93
104
  });
94
105
  } catch (err) {
95
- logger.debug({ sessionId: entry.sessionId, err }, 'Tool progress callback failed');
106
+ logger.debug(
107
+ { sessionId: entry.sessionId, err },
108
+ 'Tool progress callback failed',
109
+ );
96
110
  }
97
111
  return;
98
112
  }
@@ -107,7 +121,10 @@ function emitToolProgress(entry: PoolEntry, line: string): void {
107
121
  preview: startMatch[2],
108
122
  });
109
123
  } catch (err) {
110
- logger.debug({ sessionId: entry.sessionId, err }, 'Tool progress callback failed');
124
+ logger.debug(
125
+ { sessionId: entry.sessionId, err },
126
+ 'Tool progress callback failed',
127
+ );
111
128
  }
112
129
  }
113
130
  }
@@ -119,7 +136,10 @@ export function getActiveContainerCount(): number {
119
136
  export function stopSessionContainer(sessionId: string): boolean {
120
137
  const entry = pool.get(sessionId);
121
138
  if (!entry) return false;
122
- logger.info({ sessionId, containerName: entry.containerName }, 'Stopping session container');
139
+ logger.info(
140
+ { sessionId, containerName: entry.containerName },
141
+ 'Stopping session container',
142
+ );
123
143
  stopContainer(entry.containerName);
124
144
  pool.delete(sessionId);
125
145
  return true;
@@ -132,7 +152,10 @@ function stopContainer(containerName: string): void {
132
152
  });
133
153
  }
134
154
 
135
- function resolveArtifactHostPath(rawPath: string, workspacePath: string): string | null {
155
+ function resolveArtifactHostPath(
156
+ rawPath: string,
157
+ workspacePath: string,
158
+ ): string | null {
136
159
  const input = String(rawPath || '').trim();
137
160
  if (!input) return null;
138
161
  const normalized = input.replace(/\\/g, '/');
@@ -140,12 +163,20 @@ function resolveArtifactHostPath(rawPath: string, workspacePath: string): string
140
163
 
141
164
  if (path.posix.isAbsolute(normalized)) {
142
165
  const cleanAbs = path.posix.normalize(normalized);
143
- if (cleanAbs !== CONTAINER_WORKSPACE_ROOT && !cleanAbs.startsWith(`${CONTAINER_WORKSPACE_ROOT}/`)) {
166
+ if (
167
+ cleanAbs !== CONTAINER_WORKSPACE_ROOT &&
168
+ !cleanAbs.startsWith(`${CONTAINER_WORKSPACE_ROOT}/`)
169
+ ) {
144
170
  return null;
145
171
  }
146
- const rel = cleanAbs.slice(CONTAINER_WORKSPACE_ROOT.length).replace(/^\/+/, '');
172
+ const rel = cleanAbs
173
+ .slice(CONTAINER_WORKSPACE_ROOT.length)
174
+ .replace(/^\/+/, '');
147
175
  const resolved = path.resolve(workspaceRoot, rel);
148
- if (resolved === workspaceRoot || resolved.startsWith(`${workspaceRoot}${path.sep}`)) {
176
+ if (
177
+ resolved === workspaceRoot ||
178
+ resolved.startsWith(`${workspaceRoot}${path.sep}`)
179
+ ) {
149
180
  return resolved;
150
181
  }
151
182
  return null;
@@ -154,21 +185,32 @@ function resolveArtifactHostPath(rawPath: string, workspacePath: string): string
154
185
  const cleanRel = path.posix.normalize(normalized);
155
186
  if (cleanRel === '..' || cleanRel.startsWith('../')) return null;
156
187
  const resolved = path.resolve(workspaceRoot, cleanRel);
157
- if (resolved === workspaceRoot || resolved.startsWith(`${workspaceRoot}${path.sep}`)) {
188
+ if (
189
+ resolved === workspaceRoot ||
190
+ resolved.startsWith(`${workspaceRoot}${path.sep}`)
191
+ ) {
158
192
  return resolved;
159
193
  }
160
194
  return null;
161
195
  }
162
196
 
163
- function remapOutputArtifacts(output: ContainerOutput, workspacePath: string): void {
197
+ function remapOutputArtifacts(
198
+ output: ContainerOutput,
199
+ workspacePath: string,
200
+ ): void {
164
201
  if (!Array.isArray(output.artifacts) || output.artifacts.length === 0) return;
165
202
  const mapped: ArtifactMetadata[] = [];
166
203
  for (const artifact of output.artifacts) {
167
204
  const raw = artifact as Partial<ArtifactMetadata>;
168
- const hostPath = resolveArtifactHostPath(String(raw.path || ''), workspacePath);
205
+ const hostPath = resolveArtifactHostPath(
206
+ String(raw.path || ''),
207
+ workspacePath,
208
+ );
169
209
  if (!hostPath) continue;
170
- const filename = String(raw.filename || '').trim() || path.basename(hostPath);
171
- const mimeType = String(raw.mimeType || '').trim() || 'application/octet-stream';
210
+ const filename =
211
+ String(raw.filename || '').trim() || path.basename(hostPath);
212
+ const mimeType =
213
+ String(raw.mimeType || '').trim() || 'application/octet-stream';
172
214
  mapped.push({ path: hostPath, filename, mimeType });
173
215
  }
174
216
  if (mapped.length === 0) {
@@ -179,7 +221,10 @@ function remapOutputArtifacts(output: ContainerOutput, workspacePath: string): v
179
221
  }
180
222
 
181
223
  function remapHostBaseUrlForContainer(baseUrl: string): string {
182
- return baseUrl.replace(/\/\/(localhost|127\.0\.0\.1)([:\/])/, '//host.docker.internal$2');
224
+ return baseUrl.replace(
225
+ /\/\/(localhost|127\.0\.0\.1)([:/])/,
226
+ '//host.docker.internal$2',
227
+ );
183
228
  }
184
229
 
185
230
  /**
@@ -187,8 +232,15 @@ function remapHostBaseUrlForContainer(baseUrl: string): string {
187
232
  */
188
233
  function getOrSpawnContainer(sessionId: string, agentId: string): PoolEntry {
189
234
  const existing = pool.get(sessionId);
190
- if (existing && !existing.process.killed && existing.process.exitCode === null) {
191
- logger.debug({ sessionId, containerName: existing.containerName }, 'Reusing container');
235
+ if (
236
+ existing &&
237
+ !existing.process.killed &&
238
+ existing.process.exitCode === null
239
+ ) {
240
+ logger.debug(
241
+ { sessionId, containerName: existing.containerName },
242
+ 'Reusing container',
243
+ );
192
244
  return existing;
193
245
  }
194
246
 
@@ -208,23 +260,38 @@ function getOrSpawnContainer(sessionId: string, agentId: string): PoolEntry {
208
260
  'run',
209
261
  '--rm',
210
262
  '-i',
211
- '--name', containerName,
212
- '--memory', CONTAINER_MEMORY,
263
+ '--name',
264
+ containerName,
265
+ '--memory',
266
+ CONTAINER_MEMORY,
213
267
  `--cpus=${CONTAINER_CPUS}`,
214
268
  '--read-only',
215
- '--tmpfs', '/tmp',
216
- '-v', `${workspacePath}:/workspace:rw`,
217
- '-v', `${ipcPath}:/ipc:rw`,
218
- '-v', `${mediaCacheHostPath}:${CONTAINER_DISCORD_MEDIA_CACHE_ROOT}:ro`,
219
- '-e', `HYBRIDAI_BASE_URL=${HYBRIDAI_BASE_URL}`,
220
- '-e', `HYBRIDAI_MODEL=${HYBRIDAI_MODEL}`,
221
- '-e', `CONTAINER_IDLE_TIMEOUT=${IDLE_TIMEOUT_MS}`,
222
- '-e', `HYBRIDCLAW_RETRY_ENABLED=${PROACTIVE_AUTO_RETRY_ENABLED ? 'true' : 'false'}`,
223
- '-e', `HYBRIDCLAW_RETRY_MAX_ATTEMPTS=${PROACTIVE_AUTO_RETRY_MAX_ATTEMPTS}`,
224
- '-e', `HYBRIDCLAW_RETRY_BASE_DELAY_MS=${PROACTIVE_AUTO_RETRY_BASE_DELAY_MS}`,
225
- '-e', `HYBRIDCLAW_RETRY_MAX_DELAY_MS=${PROACTIVE_AUTO_RETRY_MAX_DELAY_MS}`,
226
- '-e', `HYBRIDCLAW_RALPH_MAX_ITERATIONS=${PROACTIVE_RALPH_MAX_ITERATIONS}`,
227
- '-e', 'PLAYWRIGHT_BROWSERS_PATH=/ms-playwright',
269
+ '--tmpfs',
270
+ '/tmp',
271
+ '-v',
272
+ `${workspacePath}:/workspace:rw`,
273
+ '-v',
274
+ `${ipcPath}:/ipc:rw`,
275
+ '-v',
276
+ `${mediaCacheHostPath}:${CONTAINER_DISCORD_MEDIA_CACHE_ROOT}:ro`,
277
+ '-e',
278
+ `HYBRIDAI_BASE_URL=${HYBRIDAI_BASE_URL}`,
279
+ '-e',
280
+ `HYBRIDAI_MODEL=${HYBRIDAI_MODEL}`,
281
+ '-e',
282
+ `CONTAINER_IDLE_TIMEOUT=${IDLE_TIMEOUT_MS}`,
283
+ '-e',
284
+ `HYBRIDCLAW_RETRY_ENABLED=${PROACTIVE_AUTO_RETRY_ENABLED ? 'true' : 'false'}`,
285
+ '-e',
286
+ `HYBRIDCLAW_RETRY_MAX_ATTEMPTS=${PROACTIVE_AUTO_RETRY_MAX_ATTEMPTS}`,
287
+ '-e',
288
+ `HYBRIDCLAW_RETRY_BASE_DELAY_MS=${PROACTIVE_AUTO_RETRY_BASE_DELAY_MS}`,
289
+ '-e',
290
+ `HYBRIDCLAW_RETRY_MAX_DELAY_MS=${PROACTIVE_AUTO_RETRY_MAX_DELAY_MS}`,
291
+ '-e',
292
+ `HYBRIDCLAW_RALPH_MAX_ITERATIONS=${PROACTIVE_RALPH_MAX_ITERATIONS}`,
293
+ '-e',
294
+ 'PLAYWRIGHT_BROWSERS_PATH=/ms-playwright',
228
295
  ];
229
296
 
230
297
  // Run as host user so bind-mount file ownership matches
@@ -241,10 +308,16 @@ function getOrSpawnContainer(sessionId: string, agentId: string): PoolEntry {
241
308
  const requested = JSON.parse(ADDITIONAL_MOUNTS) as AdditionalMount[];
242
309
  const validated = validateAdditionalMounts(requested);
243
310
  for (const m of validated) {
244
- args.push('-v', `${m.hostPath}:${m.containerPath}:${m.readonly ? 'ro' : 'rw'}`);
311
+ args.push(
312
+ '-v',
313
+ `${m.hostPath}:${m.containerPath}:${m.readonly ? 'ro' : 'rw'}`,
314
+ );
245
315
  }
246
316
  } catch (err) {
247
- logger.warn({ error: err instanceof Error ? err.message : String(err) }, 'Failed to parse ADDITIONAL_MOUNTS');
317
+ logger.warn(
318
+ { error: err instanceof Error ? err.message : String(err) },
319
+ 'Failed to parse ADDITIONAL_MOUNTS',
320
+ );
248
321
  }
249
322
  }
250
323
 
@@ -334,7 +407,10 @@ export async function runContainer(
334
407
  cleanupIpc(sessionId);
335
408
  ensureSessionDirs(sessionId);
336
409
 
337
- const isNewContainer = !pool.has(sessionId) || pool.get(sessionId)!.process.killed || pool.get(sessionId)!.process.exitCode !== null;
410
+ const isNewContainer =
411
+ !pool.has(sessionId) ||
412
+ pool.get(sessionId)!.process.killed ||
413
+ pool.get(sessionId)!.process.exitCode !== null;
338
414
 
339
415
  let entry: PoolEntry;
340
416
  try {
@@ -377,7 +453,10 @@ export async function runContainer(
377
453
  entry.onTextDelta = onTextDelta;
378
454
  entry.onToolProgress = onToolProgress;
379
455
  const onAbort = () => {
380
- logger.info({ sessionId, containerName: entry.containerName }, 'Interrupt requested, stopping container');
456
+ logger.info(
457
+ { sessionId, containerName: entry.containerName },
458
+ 'Interrupt requested, stopping container',
459
+ );
381
460
  stopContainer(entry.containerName);
382
461
  };
383
462
  if (abortSignal) {
@@ -397,12 +476,20 @@ export async function runContainer(
397
476
  }
398
477
 
399
478
  // Wait for the container to produce output
400
- const output = await readOutput(sessionId, CONTAINER_TIMEOUT, { signal: abortSignal });
479
+ const output = await readOutput(sessionId, CONTAINER_TIMEOUT, {
480
+ signal: abortSignal,
481
+ });
401
482
  remapOutputArtifacts(output, workspacePath);
402
483
  const duration = Date.now() - startTime;
403
484
 
404
485
  logger.info(
405
- { sessionId, containerName: entry.containerName, duration, status: output.status, toolsUsed: output.toolsUsed },
486
+ {
487
+ sessionId,
488
+ containerName: entry.containerName,
489
+ duration,
490
+ status: output.status,
491
+ toolsUsed: output.toolsUsed,
492
+ },
406
493
  'Request completed',
407
494
  );
408
495
 
@@ -423,7 +510,10 @@ export async function runContainer(
423
510
  */
424
511
  export function stopAllContainers(): void {
425
512
  for (const [sessionId, entry] of pool) {
426
- logger.info({ sessionId, containerName: entry.containerName }, 'Stopping container (shutdown)');
513
+ logger.info(
514
+ { sessionId, containerName: entry.containerName },
515
+ 'Stopping container (shutdown)',
516
+ );
427
517
  stopContainer(entry.containerName);
428
518
  }
429
519
  pool.clear();