@rozek/nanoclaw 1.2.17

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 (305) hide show
  1. package/.claude/settings.json +1 -0
  2. package/.claude/skills/add-compact/SKILL.md +135 -0
  3. package/.claude/skills/add-discord/SKILL.md +203 -0
  4. package/.claude/skills/add-gmail/SKILL.md +220 -0
  5. package/.claude/skills/add-image-vision/SKILL.md +94 -0
  6. package/.claude/skills/add-ollama-tool/SKILL.md +153 -0
  7. package/.claude/skills/add-parallel/SKILL.md +290 -0
  8. package/.claude/skills/add-pdf-reader/SKILL.md +104 -0
  9. package/.claude/skills/add-reactions/SKILL.md +117 -0
  10. package/.claude/skills/add-slack/SKILL.md +207 -0
  11. package/.claude/skills/add-telegram/SKILL.md +222 -0
  12. package/.claude/skills/add-telegram-swarm/SKILL.md +384 -0
  13. package/.claude/skills/add-voice-transcription/SKILL.md +148 -0
  14. package/.claude/skills/add-whatsapp/SKILL.md +372 -0
  15. package/.claude/skills/convert-to-apple-container/SKILL.md +175 -0
  16. package/.claude/skills/customize/SKILL.md +110 -0
  17. package/.claude/skills/debug/SKILL.md +349 -0
  18. package/.claude/skills/get-qodo-rules/SKILL.md +122 -0
  19. package/.claude/skills/get-qodo-rules/references/output-format.md +41 -0
  20. package/.claude/skills/get-qodo-rules/references/pagination.md +33 -0
  21. package/.claude/skills/get-qodo-rules/references/repository-scope.md +26 -0
  22. package/.claude/skills/qodo-pr-resolver/SKILL.md +326 -0
  23. package/.claude/skills/qodo-pr-resolver/resources/providers.md +329 -0
  24. package/.claude/skills/setup/SKILL.md +218 -0
  25. package/.claude/skills/update-nanoclaw/SKILL.md +235 -0
  26. package/.claude/skills/update-skills/SKILL.md +130 -0
  27. package/.claude/skills/use-local-whisper/SKILL.md +152 -0
  28. package/.claude/skills/x-integration/SKILL.md +417 -0
  29. package/.claude/skills/x-integration/agent.ts +243 -0
  30. package/.claude/skills/x-integration/host.ts +159 -0
  31. package/.claude/skills/x-integration/lib/browser.ts +148 -0
  32. package/.claude/skills/x-integration/lib/config.ts +62 -0
  33. package/.claude/skills/x-integration/scripts/like.ts +56 -0
  34. package/.claude/skills/x-integration/scripts/post.ts +66 -0
  35. package/.claude/skills/x-integration/scripts/quote.ts +80 -0
  36. package/.claude/skills/x-integration/scripts/reply.ts +74 -0
  37. package/.claude/skills/x-integration/scripts/retweet.ts +62 -0
  38. package/.claude/skills/x-integration/scripts/setup.ts +87 -0
  39. package/.env.example +1 -0
  40. package/.github/CODEOWNERS +10 -0
  41. package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  42. package/.github/workflows/bump-version.yml +32 -0
  43. package/.github/workflows/ci.yml +25 -0
  44. package/.github/workflows/merge-forward-skills.yml +160 -0
  45. package/.github/workflows/update-tokens.yml +42 -0
  46. package/.husky/pre-commit +1 -0
  47. package/.mcp.json +3 -0
  48. package/.nvmrc +1 -0
  49. package/.prettierrc +3 -0
  50. package/CHANGELOG.md +8 -0
  51. package/CLAUDE.md +64 -0
  52. package/CONTRIBUTING.md +23 -0
  53. package/CONTRIBUTORS.md +15 -0
  54. package/LICENSE +21 -0
  55. package/NanoClaw_with_Web-Support.md +290 -0
  56. package/README.md +261 -0
  57. package/README_zh.md +200 -0
  58. package/assets/nanoclaw-favicon.png +0 -0
  59. package/assets/nanoclaw-icon.png +0 -0
  60. package/assets/nanoclaw-logo-dark.png +0 -0
  61. package/assets/nanoclaw-logo.png +0 -0
  62. package/assets/nanoclaw-profile.jpeg +0 -0
  63. package/assets/nanoclaw-sales.png +0 -0
  64. package/assets/social-preview.jpg +0 -0
  65. package/config-examples/mount-allowlist.json +25 -0
  66. package/container/Dockerfile +70 -0
  67. package/container/agent-runner/package-lock.json +1524 -0
  68. package/container/agent-runner/package.json +21 -0
  69. package/container/agent-runner/src/index.ts +558 -0
  70. package/container/agent-runner/src/ipc-mcp-stdio.ts +338 -0
  71. package/container/agent-runner/tsconfig.json +15 -0
  72. package/container/build.sh +23 -0
  73. package/container/skills/agent-browser/SKILL.md +159 -0
  74. package/container/skills/capabilities/SKILL.md +100 -0
  75. package/container/skills/status/SKILL.md +104 -0
  76. package/dist/channels/index.d.ts +2 -0
  77. package/dist/channels/index.d.ts.map +1 -0
  78. package/dist/channels/index.js +9 -0
  79. package/dist/channels/index.js.map +1 -0
  80. package/dist/channels/registry.d.ts +13 -0
  81. package/dist/channels/registry.d.ts.map +1 -0
  82. package/dist/channels/registry.js +11 -0
  83. package/dist/channels/registry.js.map +1 -0
  84. package/dist/channels/registry.test.d.ts +2 -0
  85. package/dist/channels/registry.test.d.ts.map +1 -0
  86. package/dist/channels/registry.test.js +32 -0
  87. package/dist/channels/registry.test.js.map +1 -0
  88. package/dist/channels/web.d.ts +2 -0
  89. package/dist/channels/web.d.ts.map +1 -0
  90. package/dist/channels/web.js +1738 -0
  91. package/dist/channels/web.js.map +1 -0
  92. package/dist/cli.d.ts +11 -0
  93. package/dist/cli.d.ts.map +1 -0
  94. package/dist/cli.js +182 -0
  95. package/dist/cli.js.map +1 -0
  96. package/dist/config.d.ts +19 -0
  97. package/dist/config.d.ts.map +1 -0
  98. package/dist/config.js +36 -0
  99. package/dist/config.js.map +1 -0
  100. package/dist/container-runner.d.ts +44 -0
  101. package/dist/container-runner.d.ts.map +1 -0
  102. package/dist/container-runner.js +467 -0
  103. package/dist/container-runner.js.map +1 -0
  104. package/dist/container-runner.test.d.ts +2 -0
  105. package/dist/container-runner.test.d.ts.map +1 -0
  106. package/dist/container-runner.test.js +150 -0
  107. package/dist/container-runner.test.js.map +1 -0
  108. package/dist/container-runtime.d.ts +22 -0
  109. package/dist/container-runtime.d.ts.map +1 -0
  110. package/dist/container-runtime.js +96 -0
  111. package/dist/container-runtime.js.map +1 -0
  112. package/dist/container-runtime.test.d.ts +2 -0
  113. package/dist/container-runtime.test.d.ts.map +1 -0
  114. package/dist/container-runtime.test.js +93 -0
  115. package/dist/container-runtime.test.js.map +1 -0
  116. package/dist/credential-proxy.d.ts +21 -0
  117. package/dist/credential-proxy.d.ts.map +1 -0
  118. package/dist/credential-proxy.js +95 -0
  119. package/dist/credential-proxy.js.map +1 -0
  120. package/dist/credential-proxy.test.d.ts +2 -0
  121. package/dist/credential-proxy.test.d.ts.map +1 -0
  122. package/dist/credential-proxy.test.js +134 -0
  123. package/dist/credential-proxy.test.js.map +1 -0
  124. package/dist/db.d.ts +115 -0
  125. package/dist/db.d.ts.map +1 -0
  126. package/dist/db.js +549 -0
  127. package/dist/db.js.map +1 -0
  128. package/dist/db.test.d.ts +2 -0
  129. package/dist/db.test.d.ts.map +1 -0
  130. package/dist/db.test.js +360 -0
  131. package/dist/db.test.js.map +1 -0
  132. package/dist/env.d.ts +8 -0
  133. package/dist/env.d.ts.map +1 -0
  134. package/dist/env.js +42 -0
  135. package/dist/env.js.map +1 -0
  136. package/dist/formatting.test.d.ts +2 -0
  137. package/dist/formatting.test.d.ts.map +1 -0
  138. package/dist/formatting.test.js +183 -0
  139. package/dist/formatting.test.js.map +1 -0
  140. package/dist/group-folder.d.ts +5 -0
  141. package/dist/group-folder.d.ts.map +1 -0
  142. package/dist/group-folder.js +44 -0
  143. package/dist/group-folder.js.map +1 -0
  144. package/dist/group-folder.test.d.ts +2 -0
  145. package/dist/group-folder.test.d.ts.map +1 -0
  146. package/dist/group-folder.test.js +29 -0
  147. package/dist/group-folder.test.js.map +1 -0
  148. package/dist/group-queue.d.ts +34 -0
  149. package/dist/group-queue.d.ts.map +1 -0
  150. package/dist/group-queue.js +263 -0
  151. package/dist/group-queue.js.map +1 -0
  152. package/dist/group-queue.test.d.ts +2 -0
  153. package/dist/group-queue.test.d.ts.map +1 -0
  154. package/dist/group-queue.test.js +341 -0
  155. package/dist/group-queue.test.js.map +1 -0
  156. package/dist/index.d.ts +12 -0
  157. package/dist/index.d.ts.map +1 -0
  158. package/dist/index.js +518 -0
  159. package/dist/index.js.map +1 -0
  160. package/dist/ipc-auth.test.d.ts +2 -0
  161. package/dist/ipc-auth.test.d.ts.map +1 -0
  162. package/dist/ipc-auth.test.js +434 -0
  163. package/dist/ipc-auth.test.js.map +1 -0
  164. package/dist/ipc.d.ts +32 -0
  165. package/dist/ipc.d.ts.map +1 -0
  166. package/dist/ipc.js +311 -0
  167. package/dist/ipc.js.map +1 -0
  168. package/dist/logger.d.ts +3 -0
  169. package/dist/logger.d.ts.map +1 -0
  170. package/dist/logger.js +14 -0
  171. package/dist/logger.js.map +1 -0
  172. package/dist/mount-security.d.ts +34 -0
  173. package/dist/mount-security.d.ts.map +1 -0
  174. package/dist/mount-security.js +325 -0
  175. package/dist/mount-security.js.map +1 -0
  176. package/dist/remote-control.d.ts +32 -0
  177. package/dist/remote-control.d.ts.map +1 -0
  178. package/dist/remote-control.js +185 -0
  179. package/dist/remote-control.js.map +1 -0
  180. package/dist/remote-control.test.d.ts +2 -0
  181. package/dist/remote-control.test.d.ts.map +1 -0
  182. package/dist/remote-control.test.js +321 -0
  183. package/dist/remote-control.test.js.map +1 -0
  184. package/dist/router.d.ts +8 -0
  185. package/dist/router.d.ts.map +1 -0
  186. package/dist/router.js +37 -0
  187. package/dist/router.js.map +1 -0
  188. package/dist/routing.test.d.ts +2 -0
  189. package/dist/routing.test.d.ts.map +1 -0
  190. package/dist/routing.test.js +81 -0
  191. package/dist/routing.test.js.map +1 -0
  192. package/dist/sender-allowlist.d.ts +14 -0
  193. package/dist/sender-allowlist.d.ts.map +1 -0
  194. package/dist/sender-allowlist.js +79 -0
  195. package/dist/sender-allowlist.js.map +1 -0
  196. package/dist/sender-allowlist.test.d.ts +2 -0
  197. package/dist/sender-allowlist.test.d.ts.map +1 -0
  198. package/dist/sender-allowlist.test.js +186 -0
  199. package/dist/sender-allowlist.test.js.map +1 -0
  200. package/dist/session-commands.d.ts +47 -0
  201. package/dist/session-commands.d.ts.map +1 -0
  202. package/dist/session-commands.js +102 -0
  203. package/dist/session-commands.js.map +1 -0
  204. package/dist/session-commands.test.d.ts +2 -0
  205. package/dist/session-commands.test.d.ts.map +1 -0
  206. package/dist/session-commands.test.js +190 -0
  207. package/dist/session-commands.test.js.map +1 -0
  208. package/dist/task-scheduler.d.ts +22 -0
  209. package/dist/task-scheduler.d.ts.map +1 -0
  210. package/dist/task-scheduler.js +210 -0
  211. package/dist/task-scheduler.js.map +1 -0
  212. package/dist/task-scheduler.test.d.ts +2 -0
  213. package/dist/task-scheduler.test.d.ts.map +1 -0
  214. package/dist/task-scheduler.test.js +107 -0
  215. package/dist/task-scheduler.test.js.map +1 -0
  216. package/dist/timezone.d.ts +6 -0
  217. package/dist/timezone.d.ts.map +1 -0
  218. package/dist/timezone.js +17 -0
  219. package/dist/timezone.js.map +1 -0
  220. package/dist/timezone.test.d.ts +2 -0
  221. package/dist/timezone.test.d.ts.map +1 -0
  222. package/dist/timezone.test.js +23 -0
  223. package/dist/timezone.test.js.map +1 -0
  224. package/dist/types.d.ts +78 -0
  225. package/dist/types.d.ts.map +1 -0
  226. package/dist/types.js +2 -0
  227. package/dist/types.js.map +1 -0
  228. package/docs/APPLE-CONTAINER-NETWORKING.md +90 -0
  229. package/docs/DEBUG_CHECKLIST.md +143 -0
  230. package/docs/REQUIREMENTS.md +196 -0
  231. package/docs/SDK_DEEP_DIVE.md +643 -0
  232. package/docs/SECURITY.md +122 -0
  233. package/docs/SPEC.md +785 -0
  234. package/docs/docker-sandboxes.md +359 -0
  235. package/docs/nanoclaw-architecture-final.md +1063 -0
  236. package/docs/nanorepo-architecture.md +168 -0
  237. package/docs/skills-as-branches.md +662 -0
  238. package/groups/global/CLAUDE.md +58 -0
  239. package/groups/main/CLAUDE.md +246 -0
  240. package/launchd/com.nanoclaw.plist +32 -0
  241. package/package.json +45 -0
  242. package/repo-tokens/README.md +113 -0
  243. package/repo-tokens/action.yml +186 -0
  244. package/repo-tokens/badge.svg +23 -0
  245. package/repo-tokens/examples/green.svg +14 -0
  246. package/repo-tokens/examples/red.svg +14 -0
  247. package/repo-tokens/examples/yellow-green.svg +14 -0
  248. package/repo-tokens/examples/yellow.svg +14 -0
  249. package/scripts/run-migrations.ts +105 -0
  250. package/setup/container.ts +144 -0
  251. package/setup/environment.test.ts +121 -0
  252. package/setup/environment.ts +94 -0
  253. package/setup/groups.ts +229 -0
  254. package/setup/index.ts +58 -0
  255. package/setup/mounts.ts +115 -0
  256. package/setup/platform.test.ts +120 -0
  257. package/setup/platform.ts +132 -0
  258. package/setup/register.test.ts +257 -0
  259. package/setup/register.ts +177 -0
  260. package/setup/service.test.ts +187 -0
  261. package/setup/service.ts +362 -0
  262. package/setup/status.ts +16 -0
  263. package/setup/verify.ts +192 -0
  264. package/setup.sh +161 -0
  265. package/src/channels/index.ts +12 -0
  266. package/src/channels/registry.test.ts +42 -0
  267. package/src/channels/registry.ts +32 -0
  268. package/src/channels/web.ts +1856 -0
  269. package/src/cli.ts +209 -0
  270. package/src/config.ts +73 -0
  271. package/src/container-runner.test.ts +210 -0
  272. package/src/container-runner.ts +707 -0
  273. package/src/container-runtime.test.ts +149 -0
  274. package/src/container-runtime.ts +127 -0
  275. package/src/credential-proxy.test.ts +192 -0
  276. package/src/credential-proxy.ts +125 -0
  277. package/src/db.test.ts +484 -0
  278. package/src/db.ts +803 -0
  279. package/src/env.ts +42 -0
  280. package/src/formatting.test.ts +256 -0
  281. package/src/group-folder.test.ts +43 -0
  282. package/src/group-folder.ts +44 -0
  283. package/src/group-queue.test.ts +484 -0
  284. package/src/group-queue.ts +365 -0
  285. package/src/index.ts +731 -0
  286. package/src/ipc-auth.test.ts +679 -0
  287. package/src/ipc.ts +461 -0
  288. package/src/logger.ts +16 -0
  289. package/src/mount-security.ts +419 -0
  290. package/src/remote-control.test.ts +397 -0
  291. package/src/remote-control.ts +224 -0
  292. package/src/router.ts +52 -0
  293. package/src/routing.test.ts +170 -0
  294. package/src/sender-allowlist.test.ts +216 -0
  295. package/src/sender-allowlist.ts +128 -0
  296. package/src/session-commands.test.ts +247 -0
  297. package/src/session-commands.ts +163 -0
  298. package/src/task-scheduler.test.ts +129 -0
  299. package/src/task-scheduler.ts +295 -0
  300. package/src/timezone.test.ts +29 -0
  301. package/src/timezone.ts +16 -0
  302. package/src/types.ts +107 -0
  303. package/tsconfig.json +20 -0
  304. package/vitest.config.ts +7 -0
  305. package/vitest.skills.config.ts +7 -0
@@ -0,0 +1,467 @@
1
+ /**
2
+ * Container Runner for NanoClaw
3
+ * Spawns agent execution in containers and handles IPC
4
+ */
5
+ import { exec, spawn } from 'child_process';
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { CONTAINER_IMAGE, CONTAINER_MAX_OUTPUT_SIZE, CONTAINER_TIMEOUT, CREDENTIAL_PROXY_PORT, DATA_DIR, GROUPS_DIR, IDLE_TIMEOUT, TIMEZONE, } from './config.js';
9
+ import { resolveGroupFolderPath, resolveGroupIpcPath } from './group-folder.js';
10
+ import { logger } from './logger.js';
11
+ import { CONTAINER_HOST_GATEWAY, CONTAINER_RUNTIME_BIN, hostGatewayArgs, readonlyMountArgs, stopContainer, } from './container-runtime.js';
12
+ import { detectAuthMode } from './credential-proxy.js';
13
+ import { validateAdditionalMounts } from './mount-security.js';
14
+ // Sentinel markers for robust output parsing (must match agent-runner)
15
+ const OUTPUT_START_MARKER = '---NANOCLAW_OUTPUT_START---';
16
+ const OUTPUT_END_MARKER = '---NANOCLAW_OUTPUT_END---';
17
+ function buildVolumeMounts(group, isMain) {
18
+ const mounts = [];
19
+ const projectRoot = process.cwd();
20
+ const groupDir = resolveGroupFolderPath(group.folder);
21
+ if (isMain) {
22
+ // Main gets the project root read-only. Writable paths the agent needs
23
+ // (group folder, IPC, .claude/) are mounted separately below.
24
+ // Read-only prevents the agent from modifying host application code
25
+ // (src/, dist/, package.json, etc.) which would bypass the sandbox
26
+ // entirely on next restart.
27
+ mounts.push({
28
+ hostPath: projectRoot,
29
+ containerPath: '/workspace/project',
30
+ readonly: true,
31
+ });
32
+ // Shadow .env so the agent cannot read secrets from the mounted project root.
33
+ // Credentials are injected by the credential proxy, never exposed to containers.
34
+ const envFile = path.join(projectRoot, '.env');
35
+ if (fs.existsSync(envFile)) {
36
+ mounts.push({
37
+ hostPath: '/dev/null',
38
+ containerPath: '/workspace/project/.env',
39
+ readonly: true,
40
+ });
41
+ }
42
+ // Main also gets its group folder as the working directory
43
+ mounts.push({
44
+ hostPath: groupDir,
45
+ containerPath: '/workspace/group',
46
+ readonly: false,
47
+ });
48
+ }
49
+ else {
50
+ // Other groups only get their own folder
51
+ mounts.push({
52
+ hostPath: groupDir,
53
+ containerPath: '/workspace/group',
54
+ readonly: false,
55
+ });
56
+ // Global memory directory (read-only for non-main)
57
+ // Only directory mounts are supported, not file mounts
58
+ const globalDir = path.join(GROUPS_DIR, 'global');
59
+ if (fs.existsSync(globalDir)) {
60
+ mounts.push({
61
+ hostPath: globalDir,
62
+ containerPath: '/workspace/global',
63
+ readonly: true,
64
+ });
65
+ }
66
+ }
67
+ // Per-group Claude sessions directory (isolated from other groups)
68
+ // Each group gets their own .claude/ to prevent cross-group session access
69
+ const groupSessionsDir = path.join(DATA_DIR, 'sessions', group.folder, '.claude');
70
+ fs.mkdirSync(groupSessionsDir, { recursive: true });
71
+ const settingsFile = path.join(groupSessionsDir, 'settings.json');
72
+ if (!fs.existsSync(settingsFile)) {
73
+ fs.writeFileSync(settingsFile, JSON.stringify({
74
+ env: {
75
+ // Enable agent swarms (subagent orchestration)
76
+ // https://code.claude.com/docs/en/agent-teams#orchestrate-teams-of-claude-code-sessions
77
+ CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
78
+ // Load CLAUDE.md from additional mounted directories
79
+ // https://code.claude.com/docs/en/memory#load-memory-from-additional-directories
80
+ CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD: '1',
81
+ // Enable Claude's memory feature (persists user preferences between sessions)
82
+ // https://code.claude.com/docs/en/memory#manage-auto-memory
83
+ CLAUDE_CODE_DISABLE_AUTO_MEMORY: '0',
84
+ },
85
+ }, null, 2) + '\n');
86
+ }
87
+ // Sync skills from container/skills/ into each group's .claude/skills/
88
+ const skillsSrc = path.join(process.cwd(), 'container', 'skills');
89
+ const skillsDst = path.join(groupSessionsDir, 'skills');
90
+ if (fs.existsSync(skillsSrc)) {
91
+ for (const skillDir of fs.readdirSync(skillsSrc)) {
92
+ const srcDir = path.join(skillsSrc, skillDir);
93
+ if (!fs.statSync(srcDir).isDirectory())
94
+ continue;
95
+ const dstDir = path.join(skillsDst, skillDir);
96
+ fs.cpSync(srcDir, dstDir, { recursive: true });
97
+ }
98
+ }
99
+ mounts.push({
100
+ hostPath: groupSessionsDir,
101
+ containerPath: '/home/node/.claude',
102
+ readonly: false,
103
+ });
104
+ // Per-group IPC namespace: each group gets its own IPC directory
105
+ // This prevents cross-group privilege escalation via IPC
106
+ const groupIpcDir = resolveGroupIpcPath(group.folder);
107
+ fs.mkdirSync(path.join(groupIpcDir, 'messages'), { recursive: true });
108
+ fs.mkdirSync(path.join(groupIpcDir, 'tasks'), { recursive: true });
109
+ fs.mkdirSync(path.join(groupIpcDir, 'input'), { recursive: true });
110
+ mounts.push({
111
+ hostPath: groupIpcDir,
112
+ containerPath: '/workspace/ipc',
113
+ readonly: false,
114
+ });
115
+ // Copy agent-runner source into a per-group writable location so agents
116
+ // can customize it (add tools, change behavior) without affecting other
117
+ // groups. Recompiled on container startup via entrypoint.sh.
118
+ const agentRunnerSrc = path.join(projectRoot, 'container', 'agent-runner', 'src');
119
+ const groupAgentRunnerDir = path.join(DATA_DIR, 'sessions', group.folder, 'agent-runner-src');
120
+ if (!fs.existsSync(groupAgentRunnerDir) && fs.existsSync(agentRunnerSrc)) {
121
+ fs.cpSync(agentRunnerSrc, groupAgentRunnerDir, { recursive: true });
122
+ }
123
+ mounts.push({
124
+ hostPath: groupAgentRunnerDir,
125
+ containerPath: '/app/src',
126
+ readonly: false,
127
+ });
128
+ // Additional mounts validated against external allowlist (tamper-proof from containers)
129
+ if (group.containerConfig?.additionalMounts) {
130
+ const validatedMounts = validateAdditionalMounts(group.containerConfig.additionalMounts, group.name, isMain);
131
+ mounts.push(...validatedMounts);
132
+ }
133
+ return mounts;
134
+ }
135
+ function buildContainerArgs(mounts, containerName) {
136
+ const args = ['run', '-i', '--rm', '--name', containerName];
137
+ // Pass host timezone so container's local time matches the user's
138
+ args.push('-e', `TZ=${TIMEZONE}`);
139
+ // Route API traffic through the credential proxy (containers never see real secrets)
140
+ args.push('-e', `ANTHROPIC_BASE_URL=http://${CONTAINER_HOST_GATEWAY}:${CREDENTIAL_PROXY_PORT}`);
141
+ // Mirror the host's auth method with a placeholder value.
142
+ // API key mode: SDK sends x-api-key, proxy replaces with real key.
143
+ // OAuth mode: SDK exchanges placeholder token for temp API key,
144
+ // proxy injects real OAuth token on that exchange request.
145
+ const authMode = detectAuthMode();
146
+ if (authMode === 'api-key') {
147
+ args.push('-e', 'ANTHROPIC_API_KEY=placeholder');
148
+ }
149
+ else {
150
+ args.push('-e', 'CLAUDE_CODE_OAUTH_TOKEN=placeholder');
151
+ }
152
+ // Runtime-specific args for host gateway resolution
153
+ args.push(...hostGatewayArgs());
154
+ // Run as host user so bind-mounted files are accessible.
155
+ // Skip when running as root (uid 0), as the container's node user (uid 1000),
156
+ // or when getuid is unavailable (native Windows without WSL).
157
+ const hostUid = process.getuid?.();
158
+ const hostGid = process.getgid?.();
159
+ if (hostUid != null && hostUid !== 0 && hostUid !== 1000) {
160
+ args.push('--user', `${hostUid}:${hostGid}`);
161
+ args.push('-e', 'HOME=/home/node');
162
+ }
163
+ for (const mount of mounts) {
164
+ if (mount.readonly) {
165
+ args.push(...readonlyMountArgs(mount.hostPath, mount.containerPath));
166
+ }
167
+ else {
168
+ args.push('-v', `${mount.hostPath}:${mount.containerPath}`);
169
+ }
170
+ }
171
+ args.push(CONTAINER_IMAGE);
172
+ return args;
173
+ }
174
+ export async function runContainerAgent(group, input, onProcess, onOutput) {
175
+ const startTime = Date.now();
176
+ const groupDir = resolveGroupFolderPath(group.folder);
177
+ fs.mkdirSync(groupDir, { recursive: true });
178
+ const mounts = buildVolumeMounts(group, input.isMain);
179
+ const safeName = group.folder.replace(/[^a-zA-Z0-9-]/g, '-');
180
+ const containerName = `nanoclaw-${safeName}-${Date.now()}`;
181
+ const containerArgs = buildContainerArgs(mounts, containerName);
182
+ logger.debug({
183
+ group: group.name,
184
+ containerName,
185
+ mounts: mounts.map((m) => `${m.hostPath} -> ${m.containerPath}${m.readonly ? ' (ro)' : ''}`),
186
+ containerArgs: containerArgs.join(' '),
187
+ }, 'Container mount configuration');
188
+ logger.info({
189
+ group: group.name,
190
+ containerName,
191
+ mountCount: mounts.length,
192
+ isMain: input.isMain,
193
+ }, 'Spawning container agent');
194
+ const logsDir = path.join(groupDir, 'logs');
195
+ fs.mkdirSync(logsDir, { recursive: true });
196
+ return new Promise((resolve) => {
197
+ const container = spawn(CONTAINER_RUNTIME_BIN, containerArgs, {
198
+ stdio: ['pipe', 'pipe', 'pipe'],
199
+ });
200
+ onProcess(container, containerName);
201
+ let stdout = '';
202
+ let stderr = '';
203
+ let stdoutTruncated = false;
204
+ let stderrTruncated = false;
205
+ container.stdin.write(JSON.stringify(input));
206
+ container.stdin.end();
207
+ // Streaming output: parse OUTPUT_START/END marker pairs as they arrive
208
+ let parseBuffer = '';
209
+ let newSessionId;
210
+ let outputChain = Promise.resolve();
211
+ container.stdout.on('data', (data) => {
212
+ const chunk = data.toString();
213
+ // Always accumulate for logging
214
+ if (!stdoutTruncated) {
215
+ const remaining = CONTAINER_MAX_OUTPUT_SIZE - stdout.length;
216
+ if (chunk.length > remaining) {
217
+ stdout += chunk.slice(0, remaining);
218
+ stdoutTruncated = true;
219
+ logger.warn({ group: group.name, size: stdout.length }, 'Container stdout truncated due to size limit');
220
+ }
221
+ else {
222
+ stdout += chunk;
223
+ }
224
+ }
225
+ // Stream-parse for output markers
226
+ if (onOutput) {
227
+ parseBuffer += chunk;
228
+ let startIdx;
229
+ while ((startIdx = parseBuffer.indexOf(OUTPUT_START_MARKER)) !== -1) {
230
+ const endIdx = parseBuffer.indexOf(OUTPUT_END_MARKER, startIdx);
231
+ if (endIdx === -1)
232
+ break; // Incomplete pair, wait for more data
233
+ const jsonStr = parseBuffer
234
+ .slice(startIdx + OUTPUT_START_MARKER.length, endIdx)
235
+ .trim();
236
+ parseBuffer = parseBuffer.slice(endIdx + OUTPUT_END_MARKER.length);
237
+ try {
238
+ const parsed = JSON.parse(jsonStr);
239
+ if (parsed.newSessionId) {
240
+ newSessionId = parsed.newSessionId;
241
+ }
242
+ hadStreamingOutput = true;
243
+ // Activity detected — reset the hard timeout
244
+ resetTimeout();
245
+ // Call onOutput for all markers (including null results)
246
+ // so idle timers start even for "silent" query completions.
247
+ outputChain = outputChain.then(() => onOutput(parsed));
248
+ }
249
+ catch (err) {
250
+ logger.warn({ group: group.name, error: err }, 'Failed to parse streamed output chunk');
251
+ }
252
+ }
253
+ }
254
+ });
255
+ container.stderr.on('data', (data) => {
256
+ const chunk = data.toString();
257
+ const lines = chunk.trim().split('\n');
258
+ for (const line of lines) {
259
+ if (line)
260
+ logger.debug({ container: group.folder }, line);
261
+ }
262
+ // Don't reset timeout on stderr — SDK writes debug logs continuously.
263
+ // Timeout only resets on actual output (OUTPUT_MARKER in stdout).
264
+ if (stderrTruncated)
265
+ return;
266
+ const remaining = CONTAINER_MAX_OUTPUT_SIZE - stderr.length;
267
+ if (chunk.length > remaining) {
268
+ stderr += chunk.slice(0, remaining);
269
+ stderrTruncated = true;
270
+ logger.warn({ group: group.name, size: stderr.length }, 'Container stderr truncated due to size limit');
271
+ }
272
+ else {
273
+ stderr += chunk;
274
+ }
275
+ });
276
+ let timedOut = false;
277
+ let hadStreamingOutput = false;
278
+ const configTimeout = group.containerConfig?.timeout || CONTAINER_TIMEOUT;
279
+ // Grace period: hard timeout must be at least IDLE_TIMEOUT + 30s so the
280
+ // graceful _close sentinel has time to trigger before the hard kill fires.
281
+ const timeoutMs = Math.max(configTimeout, IDLE_TIMEOUT + 30_000);
282
+ const killOnTimeout = () => {
283
+ timedOut = true;
284
+ logger.error({ group: group.name, containerName }, 'Container timeout, stopping gracefully');
285
+ exec(stopContainer(containerName), { timeout: 15000 }, (err) => {
286
+ if (err) {
287
+ logger.warn({ group: group.name, containerName, err }, 'Graceful stop failed, force killing');
288
+ container.kill('SIGKILL');
289
+ }
290
+ });
291
+ };
292
+ let timeout = setTimeout(killOnTimeout, timeoutMs);
293
+ // Reset the timeout whenever there's activity (streaming output)
294
+ const resetTimeout = () => {
295
+ clearTimeout(timeout);
296
+ timeout = setTimeout(killOnTimeout, timeoutMs);
297
+ };
298
+ container.on('close', (code) => {
299
+ clearTimeout(timeout);
300
+ const duration = Date.now() - startTime;
301
+ if (timedOut) {
302
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
303
+ const timeoutLog = path.join(logsDir, `container-${ts}.log`);
304
+ fs.writeFileSync(timeoutLog, [
305
+ `=== Container Run Log (TIMEOUT) ===`,
306
+ `Timestamp: ${new Date().toISOString()}`,
307
+ `Group: ${group.name}`,
308
+ `Container: ${containerName}`,
309
+ `Duration: ${duration}ms`,
310
+ `Exit Code: ${code}`,
311
+ `Had Streaming Output: ${hadStreamingOutput}`,
312
+ ].join('\n'));
313
+ // Timeout after output = idle cleanup, not failure.
314
+ // The agent already sent its response; this is just the
315
+ // container being reaped after the idle period expired.
316
+ if (hadStreamingOutput) {
317
+ logger.info({ group: group.name, containerName, duration, code }, 'Container timed out after output (idle cleanup)');
318
+ outputChain.then(() => {
319
+ resolve({
320
+ status: 'success',
321
+ result: null,
322
+ newSessionId,
323
+ });
324
+ });
325
+ return;
326
+ }
327
+ logger.error({ group: group.name, containerName, duration, code }, 'Container timed out with no output');
328
+ resolve({
329
+ status: 'error',
330
+ result: null,
331
+ error: `Container timed out after ${configTimeout}ms`,
332
+ });
333
+ return;
334
+ }
335
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
336
+ const logFile = path.join(logsDir, `container-${timestamp}.log`);
337
+ const isVerbose = process.env.LOG_LEVEL === 'debug' || process.env.LOG_LEVEL === 'trace';
338
+ const logLines = [
339
+ `=== Container Run Log ===`,
340
+ `Timestamp: ${new Date().toISOString()}`,
341
+ `Group: ${group.name}`,
342
+ `IsMain: ${input.isMain}`,
343
+ `Duration: ${duration}ms`,
344
+ `Exit Code: ${code}`,
345
+ `Stdout Truncated: ${stdoutTruncated}`,
346
+ `Stderr Truncated: ${stderrTruncated}`,
347
+ ``,
348
+ ];
349
+ const isError = code !== 0;
350
+ if (isVerbose || isError) {
351
+ logLines.push(`=== Input ===`, JSON.stringify(input, null, 2), ``, `=== Container Args ===`, containerArgs.join(' '), ``, `=== Mounts ===`, mounts
352
+ .map((m) => `${m.hostPath} -> ${m.containerPath}${m.readonly ? ' (ro)' : ''}`)
353
+ .join('\n'), ``, `=== Stderr${stderrTruncated ? ' (TRUNCATED)' : ''} ===`, stderr, ``, `=== Stdout${stdoutTruncated ? ' (TRUNCATED)' : ''} ===`, stdout);
354
+ }
355
+ else {
356
+ logLines.push(`=== Input Summary ===`, `Prompt length: ${input.prompt.length} chars`, `Session ID: ${input.sessionId || 'new'}`, ``, `=== Mounts ===`, mounts
357
+ .map((m) => `${m.containerPath}${m.readonly ? ' (ro)' : ''}`)
358
+ .join('\n'), ``);
359
+ }
360
+ fs.writeFileSync(logFile, logLines.join('\n'));
361
+ logger.debug({ logFile, verbose: isVerbose }, 'Container log written');
362
+ if (code !== 0) {
363
+ logger.error({
364
+ group: group.name,
365
+ code,
366
+ duration,
367
+ stderr,
368
+ stdout,
369
+ logFile,
370
+ }, 'Container exited with error');
371
+ resolve({
372
+ status: 'error',
373
+ result: null,
374
+ error: `Container exited with code ${code}: ${stderr.slice(-200)}`,
375
+ });
376
+ return;
377
+ }
378
+ // Streaming mode: wait for output chain to settle, return completion marker
379
+ if (onOutput) {
380
+ outputChain.then(() => {
381
+ logger.info({ group: group.name, duration, newSessionId }, 'Container completed (streaming mode)');
382
+ resolve({
383
+ status: 'success',
384
+ result: null,
385
+ newSessionId,
386
+ });
387
+ });
388
+ return;
389
+ }
390
+ // Legacy mode: parse the last output marker pair from accumulated stdout
391
+ try {
392
+ // Extract JSON between sentinel markers for robust parsing
393
+ const startIdx = stdout.indexOf(OUTPUT_START_MARKER);
394
+ const endIdx = stdout.indexOf(OUTPUT_END_MARKER);
395
+ let jsonLine;
396
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
397
+ jsonLine = stdout
398
+ .slice(startIdx + OUTPUT_START_MARKER.length, endIdx)
399
+ .trim();
400
+ }
401
+ else {
402
+ // Fallback: last non-empty line (backwards compatibility)
403
+ const lines = stdout.trim().split('\n');
404
+ jsonLine = lines[lines.length - 1];
405
+ }
406
+ const output = JSON.parse(jsonLine);
407
+ logger.info({
408
+ group: group.name,
409
+ duration,
410
+ status: output.status,
411
+ hasResult: !!output.result,
412
+ }, 'Container completed');
413
+ resolve(output);
414
+ }
415
+ catch (err) {
416
+ logger.error({
417
+ group: group.name,
418
+ stdout,
419
+ stderr,
420
+ error: err,
421
+ }, 'Failed to parse container output');
422
+ resolve({
423
+ status: 'error',
424
+ result: null,
425
+ error: `Failed to parse container output: ${err instanceof Error ? err.message : String(err)}`,
426
+ });
427
+ }
428
+ });
429
+ container.on('error', (err) => {
430
+ clearTimeout(timeout);
431
+ logger.error({ group: group.name, containerName, error: err }, 'Container spawn error');
432
+ resolve({
433
+ status: 'error',
434
+ result: null,
435
+ error: `Container spawn error: ${err.message}`,
436
+ });
437
+ });
438
+ });
439
+ }
440
+ export function writeTasksSnapshot(groupFolder, isMain, tasks) {
441
+ // Write filtered tasks to the group's IPC directory
442
+ const groupIpcDir = resolveGroupIpcPath(groupFolder);
443
+ fs.mkdirSync(groupIpcDir, { recursive: true });
444
+ // Main sees all tasks, others only see their own
445
+ const filteredTasks = isMain
446
+ ? tasks
447
+ : tasks.filter((t) => t.groupFolder === groupFolder);
448
+ const tasksFile = path.join(groupIpcDir, 'current_tasks.json');
449
+ fs.writeFileSync(tasksFile, JSON.stringify(filteredTasks, null, 2));
450
+ }
451
+ /**
452
+ * Write available groups snapshot for the container to read.
453
+ * Only main group can see all available groups (for activation).
454
+ * Non-main groups only see their own registration status.
455
+ */
456
+ export function writeGroupsSnapshot(groupFolder, isMain, groups, registeredJids) {
457
+ const groupIpcDir = resolveGroupIpcPath(groupFolder);
458
+ fs.mkdirSync(groupIpcDir, { recursive: true });
459
+ // Main sees all groups; others see nothing (they can't activate groups)
460
+ const visibleGroups = isMain ? groups : [];
461
+ const groupsFile = path.join(groupIpcDir, 'available_groups.json');
462
+ fs.writeFileSync(groupsFile, JSON.stringify({
463
+ groups: visibleGroups,
464
+ lastSync: new Date().toISOString(),
465
+ }, null, 2));
466
+ }
467
+ //# sourceMappingURL=container-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-runner.js","sourceRoot":"","sources":["../src/container-runner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAgB,IAAI,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EACL,eAAe,EACf,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,QAAQ,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,iBAAiB,EACjB,aAAa,GACd,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAG/D,uEAAuE;AACvE,MAAM,mBAAmB,GAAG,6BAA6B,CAAC;AAC1D,MAAM,iBAAiB,GAAG,2BAA2B,CAAC;AAyBtD,SAAS,iBAAiB,CACxB,KAAsB,EACtB,MAAe;IAEf,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,sBAAsB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAEtD,IAAI,MAAM,EAAE,CAAC;QACX,uEAAuE;QACvE,8DAA8D;QAC9D,oEAAoE;QACpE,mEAAmE;QACnE,4BAA4B;QAC5B,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,WAAW;YACrB,aAAa,EAAE,oBAAoB;YACnC,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;QAEH,8EAA8E;QAC9E,iFAAiF;QACjF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC/C,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,WAAW;gBACrB,aAAa,EAAE,yBAAyB;gBACxC,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC;QACL,CAAC;QAED,2DAA2D;QAC3D,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,QAAQ;YAClB,aAAa,EAAE,kBAAkB;YACjC,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,yCAAyC;QACzC,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,QAAQ;YAClB,aAAa,EAAE,kBAAkB;YACjC,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAC;QAEH,mDAAmD;QACnD,uDAAuD;QACvD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAClD,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,SAAS;gBACnB,aAAa,EAAE,mBAAmB;gBAClC,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,mEAAmE;IACnE,2EAA2E;IAC3E,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAChC,QAAQ,EACR,UAAU,EACV,KAAK,CAAC,MAAM,EACZ,SAAS,CACV,CAAC;IACF,EAAE,CAAC,SAAS,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAClE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,EAAE,CAAC,aAAa,CACd,YAAY,EACZ,IAAI,CAAC,SAAS,CACZ;YACE,GAAG,EAAE;gBACH,+CAA+C;gBAC/C,wFAAwF;gBACxF,oCAAoC,EAAE,GAAG;gBACzC,qDAAqD;gBACrD,iFAAiF;gBACjF,4CAA4C,EAAE,GAAG;gBACjD,8EAA8E;gBAC9E,4DAA4D;gBAC5D,+BAA+B,EAAE,GAAG;aACrC;SACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,CACT,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IACxD,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,KAAK,MAAM,QAAQ,IAAI,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;YACjD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC9C,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE;gBAAE,SAAS;YACjD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC9C,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IACD,MAAM,CAAC,IAAI,CAAC;QACV,QAAQ,EAAE,gBAAgB;QAC1B,aAAa,EAAE,oBAAoB;QACnC,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAC;IAEH,iEAAiE;IACjE,yDAAyD;IACzD,MAAM,WAAW,GAAG,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACtD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnE,MAAM,CAAC,IAAI,CAAC;QACV,QAAQ,EAAE,WAAW;QACrB,aAAa,EAAE,gBAAgB;QAC/B,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAC;IAEH,wEAAwE;IACxE,wEAAwE;IACxE,6DAA6D;IAC7D,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAC9B,WAAW,EACX,WAAW,EACX,cAAc,EACd,KAAK,CACN,CAAC;IACF,MAAM,mBAAmB,GAAG,IAAI,CAAC,IAAI,CACnC,QAAQ,EACR,UAAU,EACV,KAAK,CAAC,MAAM,EACZ,kBAAkB,CACnB,CAAC;IACF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACzE,EAAE,CAAC,MAAM,CAAC,cAAc,EAAE,mBAAmB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,CAAC,IAAI,CAAC;QACV,QAAQ,EAAE,mBAAmB;QAC7B,aAAa,EAAE,UAAU;QACzB,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAC;IAEH,wFAAwF;IACxF,IAAI,KAAK,CAAC,eAAe,EAAE,gBAAgB,EAAE,CAAC;QAC5C,MAAM,eAAe,GAAG,wBAAwB,CAC9C,KAAK,CAAC,eAAe,CAAC,gBAAgB,EACtC,KAAK,CAAC,IAAI,EACV,MAAM,CACP,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,kBAAkB,CACzB,MAAqB,EACrB,aAAqB;IAErB,MAAM,IAAI,GAAa,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IAEtE,kEAAkE;IAClE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,QAAQ,EAAE,CAAC,CAAC;IAElC,qFAAqF;IACrF,IAAI,CAAC,IAAI,CACP,IAAI,EACJ,6BAA6B,sBAAsB,IAAI,qBAAqB,EAAE,CAC/E,CAAC;IAEF,0DAA0D;IAC1D,mEAAmE;IACnE,kEAAkE;IAClE,yEAAyE;IACzE,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;IAClC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,+BAA+B,CAAC,CAAC;IACnD,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,qCAAqC,CAAC,CAAC;IACzD,CAAC;IAED,oDAAoD;IACpD,IAAI,CAAC,IAAI,CAAC,GAAG,eAAe,EAAE,CAAC,CAAC;IAEhC,yDAAyD;IACzD,8EAA8E;IAC9E,8DAA8D;IAC9D,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IACnC,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACzD,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,OAAO,IAAI,OAAO,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;QACvE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAE3B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAsB,EACtB,KAAqB,EACrB,SAA8D,EAC9D,QAAqD;IAErD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,MAAM,QAAQ,GAAG,sBAAsB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACtD,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE5C,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAC7D,MAAM,aAAa,GAAG,YAAY,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IAC3D,MAAM,aAAa,GAAG,kBAAkB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAEhE,MAAM,CAAC,KAAK,CACV;QACE,KAAK,EAAE,KAAK,CAAC,IAAI;QACjB,aAAa;QACb,MAAM,EAAE,MAAM,CAAC,GAAG,CAChB,CAAC,CAAC,EAAE,EAAE,CACJ,GAAG,CAAC,CAAC,QAAQ,OAAO,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACpE;QACD,aAAa,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC;KACvC,EACD,+BAA+B,CAChC,CAAC;IAEF,MAAM,CAAC,IAAI,CACT;QACE,KAAK,EAAE,KAAK,CAAC,IAAI;QACjB,aAAa;QACb,UAAU,EAAE,MAAM,CAAC,MAAM;QACzB,MAAM,EAAE,KAAK,CAAC,MAAM;KACrB,EACD,0BAA0B,CAC3B,CAAC;IAEF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5C,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,SAAS,GAAG,KAAK,CAAC,qBAAqB,EAAE,aAAa,EAAE;YAC5D,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC;QAEH,SAAS,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAEpC,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,eAAe,GAAG,KAAK,CAAC;QAC5B,IAAI,eAAe,GAAG,KAAK,CAAC;QAE5B,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7C,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAEtB,uEAAuE;QACvE,IAAI,WAAW,GAAG,EAAE,CAAC;QACrB,IAAI,YAAgC,CAAC;QACrC,IAAI,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAEpC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACnC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAE9B,gCAAgC;YAChC,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,MAAM,SAAS,GAAG,yBAAyB,GAAG,MAAM,CAAC,MAAM,CAAC;gBAC5D,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;oBAC7B,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;oBACpC,eAAe,GAAG,IAAI,CAAC;oBACvB,MAAM,CAAC,IAAI,CACT,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,EAC1C,8CAA8C,CAC/C,CAAC;gBACJ,CAAC;qBAAM,CAAC;oBACN,MAAM,IAAI,KAAK,CAAC;gBAClB,CAAC;YACH,CAAC;YAED,kCAAkC;YAClC,IAAI,QAAQ,EAAE,CAAC;gBACb,WAAW,IAAI,KAAK,CAAC;gBACrB,IAAI,QAAgB,CAAC;gBACrB,OAAO,CAAC,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;oBACpE,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;oBAChE,IAAI,MAAM,KAAK,CAAC,CAAC;wBAAE,MAAM,CAAC,sCAAsC;oBAEhE,MAAM,OAAO,GAAG,WAAW;yBACxB,KAAK,CAAC,QAAQ,GAAG,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC;yBACpD,IAAI,EAAE,CAAC;oBACV,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;oBAEnE,IAAI,CAAC;wBACH,MAAM,MAAM,GAAoB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;wBACpD,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;4BACxB,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;wBACrC,CAAC;wBACD,kBAAkB,GAAG,IAAI,CAAC;wBAC1B,6CAA6C;wBAC7C,YAAY,EAAE,CAAC;wBACf,yDAAyD;wBACzD,4DAA4D;wBAC5D,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;oBACzD,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,MAAM,CAAC,IAAI,CACT,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EACjC,uCAAuC,CACxC,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACnC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,IAAI;oBAAE,MAAM,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC;YAC5D,CAAC;YACD,sEAAsE;YACtE,kEAAkE;YAClE,IAAI,eAAe;gBAAE,OAAO;YAC5B,MAAM,SAAS,GAAG,yBAAyB,GAAG,MAAM,CAAC,MAAM,CAAC;YAC5D,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;gBACpC,eAAe,GAAG,IAAI,CAAC;gBACvB,MAAM,CAAC,IAAI,CACT,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,EAC1C,8CAA8C,CAC/C,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,KAAK,CAAC;YAClB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,kBAAkB,GAAG,KAAK,CAAC;QAC/B,MAAM,aAAa,GAAG,KAAK,CAAC,eAAe,EAAE,OAAO,IAAI,iBAAiB,CAAC;QAC1E,wEAAwE;QACxE,2EAA2E;QAC3E,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,YAAY,GAAG,MAAM,CAAC,CAAC;QAEjE,MAAM,aAAa,GAAG,GAAG,EAAE;YACzB,QAAQ,GAAG,IAAI,CAAC;YAChB,MAAM,CAAC,KAAK,CACV,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,aAAa,EAAE,EACpC,wCAAwC,CACzC,CAAC;YACF,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE;gBAC7D,IAAI,GAAG,EAAE,CAAC;oBACR,MAAM,CAAC,IAAI,CACT,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,EACzC,qCAAqC,CACtC,CAAC;oBACF,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC5B,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,IAAI,OAAO,GAAG,UAAU,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QAEnD,iEAAiE;QACjE,MAAM,YAAY,GAAG,GAAG,EAAE;YACxB,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,OAAO,GAAG,UAAU,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QACjD,CAAC,CAAC;QAEF,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YAC7B,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAExC,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;gBAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;gBAC7D,EAAE,CAAC,aAAa,CACd,UAAU,EACV;oBACE,qCAAqC;oBACrC,cAAc,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE;oBACxC,UAAU,KAAK,CAAC,IAAI,EAAE;oBACtB,cAAc,aAAa,EAAE;oBAC7B,aAAa,QAAQ,IAAI;oBACzB,cAAc,IAAI,EAAE;oBACpB,yBAAyB,kBAAkB,EAAE;iBAC9C,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;gBAEF,oDAAoD;gBACpD,wDAAwD;gBACxD,wDAAwD;gBACxD,IAAI,kBAAkB,EAAE,CAAC;oBACvB,MAAM,CAAC,IAAI,CACT,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,EACpD,iDAAiD,CAClD,CAAC;oBACF,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE;wBACpB,OAAO,CAAC;4BACN,MAAM,EAAE,SAAS;4BACjB,MAAM,EAAE,IAAI;4BACZ,YAAY;yBACb,CAAC,CAAC;oBACL,CAAC,CAAC,CAAC;oBACH,OAAO;gBACT,CAAC;gBAED,MAAM,CAAC,KAAK,CACV,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,EACpD,oCAAoC,CACrC,CAAC;gBAEF,OAAO,CAAC;oBACN,MAAM,EAAE,OAAO;oBACf,MAAM,EAAE,IAAI;oBACZ,KAAK,EAAE,6BAA6B,aAAa,IAAI;iBACtD,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACjE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,SAAS,MAAM,CAAC,CAAC;YACjE,MAAM,SAAS,GACb,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,OAAO,CAAC;YAEzE,MAAM,QAAQ,GAAG;gBACf,2BAA2B;gBAC3B,cAAc,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE;gBACxC,UAAU,KAAK,CAAC,IAAI,EAAE;gBACtB,WAAW,KAAK,CAAC,MAAM,EAAE;gBACzB,aAAa,QAAQ,IAAI;gBACzB,cAAc,IAAI,EAAE;gBACpB,qBAAqB,eAAe,EAAE;gBACtC,qBAAqB,eAAe,EAAE;gBACtC,EAAE;aACH,CAAC;YAEF,MAAM,OAAO,GAAG,IAAI,KAAK,CAAC,CAAC;YAE3B,IAAI,SAAS,IAAI,OAAO,EAAE,CAAC;gBACzB,QAAQ,CAAC,IAAI,CACX,eAAe,EACf,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAC9B,EAAE,EACF,wBAAwB,EACxB,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EACvB,EAAE,EACF,gBAAgB,EAChB,MAAM;qBACH,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CACJ,GAAG,CAAC,CAAC,QAAQ,OAAO,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACpE;qBACA,IAAI,CAAC,IAAI,CAAC,EACb,EAAE,EACF,aAAa,eAAe,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,MAAM,EACxD,MAAM,EACN,EAAE,EACF,aAAa,eAAe,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,MAAM,EACxD,MAAM,CACP,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,IAAI,CACX,uBAAuB,EACvB,kBAAkB,KAAK,CAAC,MAAM,CAAC,MAAM,QAAQ,EAC7C,eAAe,KAAK,CAAC,SAAS,IAAI,KAAK,EAAE,EACzC,EAAE,EACF,gBAAgB,EAChB,MAAM;qBACH,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;qBAC5D,IAAI,CAAC,IAAI,CAAC,EACb,EAAE,CACH,CAAC;YACJ,CAAC;YAED,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAC/C,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,uBAAuB,CAAC,CAAC;YAEvE,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,MAAM,CAAC,KAAK,CACV;oBACE,KAAK,EAAE,KAAK,CAAC,IAAI;oBACjB,IAAI;oBACJ,QAAQ;oBACR,MAAM;oBACN,MAAM;oBACN,OAAO;iBACR,EACD,6BAA6B,CAC9B,CAAC;gBAEF,OAAO,CAAC;oBACN,MAAM,EAAE,OAAO;oBACf,MAAM,EAAE,IAAI;oBACZ,KAAK,EAAE,8BAA8B,IAAI,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE;iBACnE,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,4EAA4E;YAC5E,IAAI,QAAQ,EAAE,CAAC;gBACb,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE;oBACpB,MAAM,CAAC,IAAI,CACT,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,EAC7C,sCAAsC,CACvC,CAAC;oBACF,OAAO,CAAC;wBACN,MAAM,EAAE,SAAS;wBACjB,MAAM,EAAE,IAAI;wBACZ,YAAY;qBACb,CAAC,CAAC;gBACL,CAAC,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,yEAAyE;YACzE,IAAI,CAAC;gBACH,2DAA2D;gBAC3D,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;gBACrD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;gBAEjD,IAAI,QAAgB,CAAC;gBACrB,IAAI,QAAQ,KAAK,CAAC,CAAC,IAAI,MAAM,KAAK,CAAC,CAAC,IAAI,MAAM,GAAG,QAAQ,EAAE,CAAC;oBAC1D,QAAQ,GAAG,MAAM;yBACd,KAAK,CAAC,QAAQ,GAAG,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC;yBACpD,IAAI,EAAE,CAAC;gBACZ,CAAC;qBAAM,CAAC;oBACN,0DAA0D;oBAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBACxC,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBACrC,CAAC;gBAED,MAAM,MAAM,GAAoB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAErD,MAAM,CAAC,IAAI,CACT;oBACE,KAAK,EAAE,KAAK,CAAC,IAAI;oBACjB,QAAQ;oBACR,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM;iBAC3B,EACD,qBAAqB,CACtB,CAAC;gBAEF,OAAO,CAAC,MAAM,CAAC,CAAC;YAClB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,KAAK,CACV;oBACE,KAAK,EAAE,KAAK,CAAC,IAAI;oBACjB,MAAM;oBACN,MAAM;oBACN,KAAK,EAAE,GAAG;iBACX,EACD,kCAAkC,CACnC,CAAC;gBAEF,OAAO,CAAC;oBACN,MAAM,EAAE,OAAO;oBACf,MAAM,EAAE,IAAI;oBACZ,KAAK,EAAE,qCAAqC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;iBAC/F,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC5B,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,MAAM,CAAC,KAAK,CACV,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,EAChD,uBAAuB,CACxB,CAAC;YACF,OAAO,CAAC;gBACN,MAAM,EAAE,OAAO;gBACf,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,0BAA0B,GAAG,CAAC,OAAO,EAAE;aAC/C,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,WAAmB,EACnB,MAAe,EACf,KAQE;IAEF,oDAAoD;IACpD,MAAM,WAAW,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;IACrD,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/C,iDAAiD;IACjD,MAAM,aAAa,GAAG,MAAM;QAC1B,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,WAAW,CAAC,CAAC;IAEvD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;IAC/D,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACtE,CAAC;AASD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAmB,EACnB,MAAe,EACf,MAAwB,EACxB,cAA2B;IAE3B,MAAM,WAAW,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;IACrD,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/C,wEAAwE;IACxE,MAAM,aAAa,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAE3C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC;IACnE,EAAE,CAAC,aAAa,CACd,UAAU,EACV,IAAI,CAAC,SAAS,CACZ;QACE,MAAM,EAAE,aAAa;QACrB,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACnC,EACD,IAAI,EACJ,CAAC,CACF,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=container-runner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-runner.test.d.ts","sourceRoot":"","sources":["../src/container-runner.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,150 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
+ import { EventEmitter } from 'events';
3
+ import { PassThrough } from 'stream';
4
+ // Sentinel markers must match container-runner.ts
5
+ const OUTPUT_START_MARKER = '---NANOCLAW_OUTPUT_START---';
6
+ const OUTPUT_END_MARKER = '---NANOCLAW_OUTPUT_END---';
7
+ // Mock config
8
+ vi.mock('./config.js', () => ({
9
+ CONTAINER_IMAGE: 'nanoclaw-agent:latest',
10
+ CONTAINER_MAX_OUTPUT_SIZE: 10485760,
11
+ CONTAINER_TIMEOUT: 1800000, // 30min
12
+ CREDENTIAL_PROXY_PORT: 3001,
13
+ DATA_DIR: '/tmp/nanoclaw-test-data',
14
+ GROUPS_DIR: '/tmp/nanoclaw-test-groups',
15
+ IDLE_TIMEOUT: 1800000, // 30min
16
+ TIMEZONE: 'America/Los_Angeles',
17
+ }));
18
+ // Mock logger
19
+ vi.mock('./logger.js', () => ({
20
+ logger: {
21
+ debug: vi.fn(),
22
+ info: vi.fn(),
23
+ warn: vi.fn(),
24
+ error: vi.fn(),
25
+ },
26
+ }));
27
+ // Mock fs
28
+ vi.mock('fs', async () => {
29
+ const actual = await vi.importActual('fs');
30
+ return {
31
+ ...actual,
32
+ default: {
33
+ ...actual,
34
+ existsSync: vi.fn(() => false),
35
+ mkdirSync: vi.fn(),
36
+ writeFileSync: vi.fn(),
37
+ readFileSync: vi.fn(() => ''),
38
+ readdirSync: vi.fn(() => []),
39
+ statSync: vi.fn(() => ({ isDirectory: () => false })),
40
+ copyFileSync: vi.fn(),
41
+ },
42
+ };
43
+ });
44
+ // Mock mount-security
45
+ vi.mock('./mount-security.js', () => ({
46
+ validateAdditionalMounts: vi.fn(() => []),
47
+ }));
48
+ // Create a controllable fake ChildProcess
49
+ function createFakeProcess() {
50
+ const proc = new EventEmitter();
51
+ proc.stdin = new PassThrough();
52
+ proc.stdout = new PassThrough();
53
+ proc.stderr = new PassThrough();
54
+ proc.kill = vi.fn();
55
+ proc.pid = 12345;
56
+ return proc;
57
+ }
58
+ let fakeProc;
59
+ // Mock child_process.spawn
60
+ vi.mock('child_process', async () => {
61
+ const actual = await vi.importActual('child_process');
62
+ return {
63
+ ...actual,
64
+ spawn: vi.fn(() => fakeProc),
65
+ exec: vi.fn((_cmd, _opts, cb) => {
66
+ if (cb)
67
+ cb(null);
68
+ return new EventEmitter();
69
+ }),
70
+ };
71
+ });
72
+ import { runContainerAgent } from './container-runner.js';
73
+ const testGroup = {
74
+ name: 'Test Group',
75
+ folder: 'test-group',
76
+ trigger: '@Andy',
77
+ added_at: new Date().toISOString(),
78
+ };
79
+ const testInput = {
80
+ prompt: 'Hello',
81
+ groupFolder: 'test-group',
82
+ chatJid: 'test@g.us',
83
+ isMain: false,
84
+ };
85
+ function emitOutputMarker(proc, output) {
86
+ const json = JSON.stringify(output);
87
+ proc.stdout.push(`${OUTPUT_START_MARKER}\n${json}\n${OUTPUT_END_MARKER}\n`);
88
+ }
89
+ describe('container-runner timeout behavior', () => {
90
+ beforeEach(() => {
91
+ vi.useFakeTimers();
92
+ fakeProc = createFakeProcess();
93
+ });
94
+ afterEach(() => {
95
+ vi.useRealTimers();
96
+ });
97
+ it('timeout after output resolves as success', async () => {
98
+ const onOutput = vi.fn(async () => { });
99
+ const resultPromise = runContainerAgent(testGroup, testInput, () => { }, onOutput);
100
+ // Emit output with a result
101
+ emitOutputMarker(fakeProc, {
102
+ status: 'success',
103
+ result: 'Here is my response',
104
+ newSessionId: 'session-123',
105
+ });
106
+ // Let output processing settle
107
+ await vi.advanceTimersByTimeAsync(10);
108
+ // Fire the hard timeout (IDLE_TIMEOUT + 30s = 1830000ms)
109
+ await vi.advanceTimersByTimeAsync(1830000);
110
+ // Emit close event (as if container was stopped by the timeout)
111
+ fakeProc.emit('close', 137);
112
+ // Let the promise resolve
113
+ await vi.advanceTimersByTimeAsync(10);
114
+ const result = await resultPromise;
115
+ expect(result.status).toBe('success');
116
+ expect(result.newSessionId).toBe('session-123');
117
+ expect(onOutput).toHaveBeenCalledWith(expect.objectContaining({ result: 'Here is my response' }));
118
+ });
119
+ it('timeout with no output resolves as error', async () => {
120
+ const onOutput = vi.fn(async () => { });
121
+ const resultPromise = runContainerAgent(testGroup, testInput, () => { }, onOutput);
122
+ // No output emitted — fire the hard timeout
123
+ await vi.advanceTimersByTimeAsync(1830000);
124
+ // Emit close event
125
+ fakeProc.emit('close', 137);
126
+ await vi.advanceTimersByTimeAsync(10);
127
+ const result = await resultPromise;
128
+ expect(result.status).toBe('error');
129
+ expect(result.error).toContain('timed out');
130
+ expect(onOutput).not.toHaveBeenCalled();
131
+ });
132
+ it('normal exit after output resolves as success', async () => {
133
+ const onOutput = vi.fn(async () => { });
134
+ const resultPromise = runContainerAgent(testGroup, testInput, () => { }, onOutput);
135
+ // Emit output
136
+ emitOutputMarker(fakeProc, {
137
+ status: 'success',
138
+ result: 'Done',
139
+ newSessionId: 'session-456',
140
+ });
141
+ await vi.advanceTimersByTimeAsync(10);
142
+ // Normal exit (no timeout)
143
+ fakeProc.emit('close', 0);
144
+ await vi.advanceTimersByTimeAsync(10);
145
+ const result = await resultPromise;
146
+ expect(result.status).toBe('success');
147
+ expect(result.newSessionId).toBe('session-456');
148
+ });
149
+ });
150
+ //# sourceMappingURL=container-runner.test.js.map