chorus-codes 0.7.0 → 0.7.2

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 (293) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/cache/.previewinfo +1 -1
  4. package/.next/cache/.rscinfo +1 -1
  5. package/.next/cache/.tsbuildinfo +1 -1
  6. package/.next/fallback-build-manifest.json +3 -3
  7. package/.next/prerender-manifest.json +3 -3
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.html +1 -1
  16. package/.next/server/app/_not-found.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  23. package/.next/server/app/new.html +1 -1
  24. package/.next/server/app/new.rsc +1 -1
  25. package/.next/server/app/new.segments/_full.segment.rsc +1 -1
  26. package/.next/server/app/new.segments/_head.segment.rsc +1 -1
  27. package/.next/server/app/new.segments/_index.segment.rsc +1 -1
  28. package/.next/server/app/new.segments/_tree.segment.rsc +1 -1
  29. package/.next/server/app/new.segments/new/__PAGE__.segment.rsc +1 -1
  30. package/.next/server/app/new.segments/new.segment.rsc +1 -1
  31. package/.next/server/app/onboarding.html +1 -1
  32. package/.next/server/app/onboarding.rsc +1 -1
  33. package/.next/server/app/onboarding.segments/_full.segment.rsc +1 -1
  34. package/.next/server/app/onboarding.segments/_head.segment.rsc +1 -1
  35. package/.next/server/app/onboarding.segments/_index.segment.rsc +1 -1
  36. package/.next/server/app/onboarding.segments/_tree.segment.rsc +1 -1
  37. package/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +1 -1
  38. package/.next/server/app/onboarding.segments/onboarding.segment.rsc +1 -1
  39. package/.next/server/app/personas.html +1 -1
  40. package/.next/server/app/personas.rsc +1 -1
  41. package/.next/server/app/personas.segments/_full.segment.rsc +1 -1
  42. package/.next/server/app/personas.segments/_head.segment.rsc +1 -1
  43. package/.next/server/app/personas.segments/_index.segment.rsc +1 -1
  44. package/.next/server/app/personas.segments/_tree.segment.rsc +1 -1
  45. package/.next/server/app/personas.segments/personas/__PAGE__.segment.rsc +1 -1
  46. package/.next/server/app/personas.segments/personas.segment.rsc +1 -1
  47. package/.next/server/app/settings.html +1 -1
  48. package/.next/server/app/settings.rsc +1 -1
  49. package/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  50. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  51. package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  52. package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  53. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
  54. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  55. package/.next/server/app/templates.html +1 -1
  56. package/.next/server/app/templates.rsc +1 -1
  57. package/.next/server/app/templates.segments/_full.segment.rsc +1 -1
  58. package/.next/server/app/templates.segments/_head.segment.rsc +1 -1
  59. package/.next/server/app/templates.segments/_index.segment.rsc +1 -1
  60. package/.next/server/app/templates.segments/_tree.segment.rsc +1 -1
  61. package/.next/server/app/templates.segments/templates/__PAGE__.segment.rsc +1 -1
  62. package/.next/server/app/templates.segments/templates.segment.rsc +1 -1
  63. package/.next/server/middleware-build-manifest.js +3 -3
  64. package/.next/server/pages/404.html +1 -1
  65. package/.next/server/pages/500.html +1 -1
  66. package/.next/server/server-reference-manifest.js +1 -1
  67. package/.next/server/server-reference-manifest.json +1 -1
  68. package/.next/trace +1 -1
  69. package/.next/trace-build +1 -1
  70. package/README.md +38 -15
  71. package/dist/cli/commands/doctor.js +116 -0
  72. package/dist/cli/commands/doctor.js.map +1 -0
  73. package/dist/cli/commands/init.js +211 -0
  74. package/dist/cli/commands/init.js.map +1 -0
  75. package/dist/cli/commands/start.js +298 -0
  76. package/dist/cli/commands/start.js.map +1 -0
  77. package/dist/cli/commands/status.js +54 -0
  78. package/dist/cli/commands/status.js.map +1 -0
  79. package/dist/cli/commands/stop.js +97 -0
  80. package/dist/cli/commands/stop.js.map +1 -0
  81. package/dist/cli/connect.js +108 -0
  82. package/dist/cli/connect.js.map +1 -0
  83. package/dist/cli/index.js +99 -0
  84. package/dist/cli/index.js.map +1 -0
  85. package/dist/cli/port-utils.js +260 -0
  86. package/dist/cli/port-utils.js.map +1 -0
  87. package/dist/cli/runtime-env.js +60 -0
  88. package/dist/cli/runtime-env.js.map +1 -0
  89. package/dist/cli/shared.js +54 -0
  90. package/dist/cli/shared.js.map +1 -0
  91. package/dist/cli/ui.js +60 -0
  92. package/dist/cli/ui.js.map +1 -0
  93. package/dist/daemon/agents/claude.js +98 -0
  94. package/dist/daemon/agents/claude.js.map +1 -0
  95. package/dist/daemon/agents/codex.js +160 -0
  96. package/dist/daemon/agents/codex.js.map +1 -0
  97. package/dist/daemon/agents/gemini.js +111 -0
  98. package/dist/daemon/agents/gemini.js.map +1 -0
  99. package/dist/daemon/agents/index.js +59 -0
  100. package/dist/daemon/agents/index.js.map +1 -0
  101. package/dist/daemon/agents/kimi.js +206 -0
  102. package/dist/daemon/agents/kimi.js.map +1 -0
  103. package/dist/daemon/agents/opencode.js +228 -0
  104. package/dist/daemon/agents/opencode.js.map +1 -0
  105. package/dist/daemon/agents/openrouter.js +274 -0
  106. package/dist/daemon/agents/openrouter.js.map +1 -0
  107. package/dist/daemon/agents/parsers/claude.js +63 -0
  108. package/dist/daemon/agents/parsers/claude.js.map +1 -0
  109. package/dist/daemon/agents/parsers/codex.js +51 -0
  110. package/dist/daemon/agents/parsers/codex.js.map +1 -0
  111. package/dist/daemon/agents/parsers/gemini.js +144 -0
  112. package/dist/daemon/agents/parsers/gemini.js.map +1 -0
  113. package/dist/daemon/agents/parsers/index.js +31 -0
  114. package/dist/daemon/agents/parsers/index.js.map +1 -0
  115. package/dist/daemon/agents/parsers/kimi.js +8 -0
  116. package/dist/daemon/agents/parsers/kimi.js.map +1 -0
  117. package/dist/daemon/agents/parsers/opencode.js +105 -0
  118. package/dist/daemon/agents/parsers/opencode.js.map +1 -0
  119. package/dist/daemon/agents/parsers/openrouter.js +69 -0
  120. package/dist/daemon/agents/parsers/openrouter.js.map +1 -0
  121. package/dist/daemon/agents/parsers/shared.js +17 -0
  122. package/dist/daemon/agents/parsers/shared.js.map +1 -0
  123. package/dist/daemon/agents/preflight.js +83 -0
  124. package/dist/daemon/agents/preflight.js.map +1 -0
  125. package/dist/daemon/agents/quote.js +45 -0
  126. package/dist/daemon/agents/quote.js.map +1 -0
  127. package/dist/daemon/agents/sandbox-guard.js +69 -0
  128. package/dist/daemon/agents/sandbox-guard.js.map +1 -0
  129. package/dist/daemon/agents/types.js +6 -0
  130. package/dist/daemon/agents/types.js.map +1 -0
  131. package/dist/daemon/api-response.js +65 -0
  132. package/dist/daemon/api-response.js.map +1 -0
  133. package/dist/daemon/error-detector.js +329 -0
  134. package/dist/daemon/error-detector.js.map +1 -0
  135. package/dist/daemon/headless.js +533 -0
  136. package/dist/daemon/headless.js.map +1 -0
  137. package/dist/daemon/index.js +333 -0
  138. package/dist/daemon/index.js.map +1 -0
  139. package/dist/daemon/openrouter.js +192 -0
  140. package/dist/daemon/openrouter.js.map +1 -0
  141. package/dist/daemon/orchestrators/claude.js +163 -0
  142. package/dist/daemon/orchestrators/claude.js.map +1 -0
  143. package/dist/daemon/orchestrators/codex.js +101 -0
  144. package/dist/daemon/orchestrators/codex.js.map +1 -0
  145. package/dist/daemon/orchestrators/cursor-windsurf.js +118 -0
  146. package/dist/daemon/orchestrators/cursor-windsurf.js.map +1 -0
  147. package/dist/daemon/orchestrators/gemini.js +108 -0
  148. package/dist/daemon/orchestrators/gemini.js.map +1 -0
  149. package/dist/daemon/orchestrators/index.js +90 -0
  150. package/dist/daemon/orchestrators/index.js.map +1 -0
  151. package/dist/daemon/orchestrators/kimi.js +108 -0
  152. package/dist/daemon/orchestrators/kimi.js.map +1 -0
  153. package/dist/daemon/orchestrators/opencode.js +152 -0
  154. package/dist/daemon/orchestrators/opencode.js.map +1 -0
  155. package/dist/daemon/orchestrators/shared.js +60 -0
  156. package/dist/daemon/orchestrators/shared.js.map +1 -0
  157. package/dist/daemon/output-watcher.js +131 -0
  158. package/dist/daemon/output-watcher.js.map +1 -0
  159. package/dist/daemon/participant-aborts.js +123 -0
  160. package/dist/daemon/participant-aborts.js.map +1 -0
  161. package/dist/daemon/reaper.js +46 -0
  162. package/dist/daemon/reaper.js.map +1 -0
  163. package/dist/daemon/routes/chats-events.js +62 -0
  164. package/dist/daemon/routes/chats-events.js.map +1 -0
  165. package/dist/daemon/routes/chats-stream.js +241 -0
  166. package/dist/daemon/routes/chats-stream.js.map +1 -0
  167. package/dist/daemon/routes/chats-validation.js +13 -0
  168. package/dist/daemon/routes/chats-validation.js.map +1 -0
  169. package/dist/daemon/routes/chats.js +545 -0
  170. package/dist/daemon/routes/chats.js.map +1 -0
  171. package/dist/daemon/routes/openrouter.js +103 -0
  172. package/dist/daemon/routes/openrouter.js.map +1 -0
  173. package/dist/daemon/routes/settings.js +199 -0
  174. package/dist/daemon/routes/settings.js.map +1 -0
  175. package/dist/daemon/routes/stats.js +155 -0
  176. package/dist/daemon/routes/stats.js.map +1 -0
  177. package/dist/daemon/routes/system.js +208 -0
  178. package/dist/daemon/routes/system.js.map +1 -0
  179. package/dist/daemon/routes/templates-personas.js +254 -0
  180. package/dist/daemon/routes/templates-personas.js.map +1 -0
  181. package/dist/daemon/routes/voices.js +139 -0
  182. package/dist/daemon/routes/voices.js.map +1 -0
  183. package/dist/daemon/runner/doer-driver.js +346 -0
  184. package/dist/daemon/runner/doer-driver.js.map +1 -0
  185. package/dist/daemon/runner/doer.js +336 -0
  186. package/dist/daemon/runner/doer.js.map +1 -0
  187. package/dist/daemon/runner/prior-round.js +140 -0
  188. package/dist/daemon/runner/prior-round.js.map +1 -0
  189. package/dist/daemon/runner/prompt-builder.js +292 -0
  190. package/dist/daemon/runner/prompt-builder.js.map +1 -0
  191. package/dist/daemon/runner/review-only-phase.js +103 -0
  192. package/dist/daemon/runner/review-only-phase.js.map +1 -0
  193. package/dist/daemon/runner/reviewer-driver.js +410 -0
  194. package/dist/daemon/runner/reviewer-driver.js.map +1 -0
  195. package/dist/daemon/runner/reviewer.js +384 -0
  196. package/dist/daemon/runner/reviewer.js.map +1 -0
  197. package/dist/daemon/runner/run-with-fallback.js +56 -0
  198. package/dist/daemon/runner/run-with-fallback.js.map +1 -0
  199. package/dist/daemon/runner/sanitize-name.js +8 -0
  200. package/dist/daemon/runner/sanitize-name.js.map +1 -0
  201. package/dist/daemon/runner/stream-file-writer.js +116 -0
  202. package/dist/daemon/runner/stream-file-writer.js.map +1 -0
  203. package/dist/daemon/runner/swap-sidecar.js +102 -0
  204. package/dist/daemon/runner/swap-sidecar.js.map +1 -0
  205. package/dist/daemon/runner/template-fallback.js +119 -0
  206. package/dist/daemon/runner/template-fallback.js.map +1 -0
  207. package/dist/daemon/runner/types.js +3 -0
  208. package/dist/daemon/runner/types.js.map +1 -0
  209. package/dist/daemon/runner/verdict.js +51 -0
  210. package/dist/daemon/runner/verdict.js.map +1 -0
  211. package/dist/daemon/runner-multiplex.js +364 -0
  212. package/dist/daemon/runner-multiplex.js.map +1 -0
  213. package/dist/daemon/runner.js +427 -0
  214. package/dist/daemon/runner.js.map +1 -0
  215. package/dist/daemon/ship.js +340 -0
  216. package/dist/daemon/ship.js.map +1 -0
  217. package/dist/daemon/template-cache.js +37 -0
  218. package/dist/daemon/template-cache.js.map +1 -0
  219. package/dist/daemon/tmux-types.js +9 -0
  220. package/dist/daemon/tmux-types.js.map +1 -0
  221. package/dist/daemon/tmux.js +341 -0
  222. package/dist/daemon/tmux.js.map +1 -0
  223. package/dist/lib/atomic-write.js +55 -0
  224. package/dist/lib/atomic-write.js.map +1 -0
  225. package/dist/lib/chat-events-bus.js +27 -0
  226. package/dist/lib/chat-events-bus.js.map +1 -0
  227. package/dist/lib/chat-slug.js +105 -0
  228. package/dist/lib/chat-slug.js.map +1 -0
  229. package/dist/lib/cli-detect.js +388 -0
  230. package/dist/lib/cli-detect.js.map +1 -0
  231. package/dist/lib/cli-health.js +156 -0
  232. package/dist/lib/cli-health.js.map +1 -0
  233. package/dist/lib/cli-paths.js +113 -0
  234. package/dist/lib/cli-paths.js.map +1 -0
  235. package/dist/lib/cli-precheck.js +141 -0
  236. package/dist/lib/cli-precheck.js.map +1 -0
  237. package/dist/lib/db/chats.js +244 -0
  238. package/dist/lib/db/chats.js.map +1 -0
  239. package/dist/lib/db/connection.js +254 -0
  240. package/dist/lib/db/connection.js.map +1 -0
  241. package/dist/lib/db/index.js +34 -0
  242. package/dist/lib/db/index.js.map +1 -0
  243. package/dist/lib/db/personas.js +65 -0
  244. package/dist/lib/db/personas.js.map +1 -0
  245. package/dist/lib/db/phase-events.js +172 -0
  246. package/dist/lib/db/phase-events.js.map +1 -0
  247. package/dist/lib/db/secrets.js +53 -0
  248. package/dist/lib/db/secrets.js.map +1 -0
  249. package/dist/lib/db/settings.js +47 -0
  250. package/dist/lib/db/settings.js.map +1 -0
  251. package/dist/lib/db/templates.js +75 -0
  252. package/dist/lib/db/templates.js.map +1 -0
  253. package/dist/lib/db/voices.js +184 -0
  254. package/dist/lib/db/voices.js.map +1 -0
  255. package/dist/lib/lineage-maps.js +200 -0
  256. package/dist/lib/lineage-maps.js.map +1 -0
  257. package/dist/lib/logger.js +186 -0
  258. package/dist/lib/logger.js.map +1 -0
  259. package/dist/lib/personas.js +117 -0
  260. package/dist/lib/personas.js.map +1 -0
  261. package/dist/lib/runtime-path.js +222 -0
  262. package/dist/lib/runtime-path.js.map +1 -0
  263. package/dist/lib/settings/billing.js +58 -0
  264. package/dist/lib/settings/billing.js.map +1 -0
  265. package/dist/lib/settings/permissions.js +81 -0
  266. package/dist/lib/settings/permissions.js.map +1 -0
  267. package/dist/lib/settings/transport.js +113 -0
  268. package/dist/lib/settings/transport.js.map +1 -0
  269. package/dist/lib/telemetry.js +290 -0
  270. package/dist/lib/telemetry.js.map +1 -0
  271. package/dist/lib/template-schema.js +319 -0
  272. package/dist/lib/template-schema.js.map +1 -0
  273. package/dist/lib/template-validation.js +82 -0
  274. package/dist/lib/template-validation.js.map +1 -0
  275. package/dist/lib/voices.js +533 -0
  276. package/dist/lib/voices.js.map +1 -0
  277. package/dist/mcp/client.js +138 -0
  278. package/dist/mcp/client.js.map +1 -0
  279. package/dist/mcp/index.js +178 -0
  280. package/dist/mcp/index.js.map +1 -0
  281. package/dist/mcp/tools.js +355 -0
  282. package/dist/mcp/tools.js.map +1 -0
  283. package/package.json +2 -1
  284. package/.next/dev/static/chunks/05w9_next_dist_shared_lib_0beh7rg._.js +0 -6077
  285. package/.next/dev/static/chunks/05w9_next_dist_shared_lib_0beh7rg._.js.map +0 -69
  286. package/.next/dev/static/chunks/05w9_next_dist_shared_lib_0pjsj.j._.js +0 -6318
  287. package/.next/dev/static/chunks/05w9_next_dist_shared_lib_0pjsj.j._.js.map +0 -71
  288. package/.next/dev/types/cache-life.d.ts +0 -145
  289. package/.next/dev/types/routes.d.ts +0 -84
  290. package/.next/dev/types/validator.ts +0 -178
  291. /package/.next/static/{dJlbRLlhISA0JGtHKVqgQ → 9cD3yIOGe_Aqr17uJHTQS}/_buildManifest.js +0 -0
  292. /package/.next/static/{dJlbRLlhISA0JGtHKVqgQ → 9cD3yIOGe_Aqr17uJHTQS}/_clientMiddlewareManifest.js +0 -0
  293. /package/.next/static/{dJlbRLlhISA0JGtHKVqgQ → 9cD3yIOGe_Aqr17uJHTQS}/_ssgManifest.js +0 -0
@@ -0,0 +1,341 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TmuxManagerImpl = void 0;
4
+ const child_process_1 = require("child_process");
5
+ /**
6
+ * TmuxManagerImpl: Session manager for Chorus CLI integrations.
7
+ *
8
+ * Responsibilities:
9
+ * - Acquire sessions respecting share-session policy (across rounds, rarely across phases)
10
+ * - Spawn fresh tmux sessions via the agent shim's buildLaunchCommand
11
+ * - Provide tmux operations (sendKeys, pasteBuffer, capturePane)
12
+ * - Track sessions in memory + reconcile from `tmux ls` on startup
13
+ * - Reap orphans via reapOnce() called by index.ts every 5 min
14
+ *
15
+ * Hard rules:
16
+ * 1. %q-quote all user/template values at substitution time
17
+ * 2. Never reuse across chats (hard rule)
18
+ * 3. Tag every session with chorus-<chatId>-...
19
+ * 4. Per-session CODEX_HOME for codex (caller/shim handles env-var passing)
20
+ */
21
+ class TmuxManagerImpl {
22
+ /** In-memory registry: "chatId:phaseId:role:agentName" → SessionHandle */
23
+ sessions = new Map();
24
+ constructor() {
25
+ // Reconcile existing chorus sessions on startup
26
+ this.reconcileExisting();
27
+ }
28
+ /**
29
+ * Scan `tmux ls` for existing chorus-* sessions and rebuild the registry.
30
+ * Recovers state after daemon restart.
31
+ */
32
+ reconcileExisting() {
33
+ try {
34
+ const result = (0, child_process_1.spawnSync)('tmux', ['list-sessions', '-F', '#{session_name}'], {
35
+ encoding: 'utf-8',
36
+ });
37
+ if (result.status === 0 && result.stdout) {
38
+ const lines = result.stdout.trim().split('\n').filter((line) => line.length > 0);
39
+ for (const sessionName of lines) {
40
+ if (!sessionName.startsWith('chorus-'))
41
+ continue;
42
+ // Parse session name: chorus-<chatId>-<phaseId>-<role>-<agentName>
43
+ const parts = sessionName.split('-');
44
+ if (parts.length < 6)
45
+ continue; // chorus + chatId + phaseId + role + agentName = 5 parts after split
46
+ // Rebuild a minimal handle; agent lineage/name come from the parts
47
+ const chatId = parts[1];
48
+ const phaseId = parts[2];
49
+ const role = parts[3] || 'doer';
50
+ const agentName = parts.slice(4).join('-'); // Re-join in case agent name has hyphens
51
+ const key = `${chatId}:${phaseId}:${role}:${agentName}`;
52
+ // Avoid overwriting if we already have this session tracked
53
+ if (this.sessions.has(key))
54
+ continue;
55
+ const handle = {
56
+ name: sessionName,
57
+ chatId,
58
+ phaseId,
59
+ role,
60
+ lineage: 'anthropic', // Safe default; actual lineage is unknown from tmux metadata
61
+ agentName,
62
+ spawnedAt: Date.now(),
63
+ lastActivityAt: Date.now(),
64
+ state: 'active',
65
+ };
66
+ this.sessions.set(key, handle);
67
+ }
68
+ }
69
+ }
70
+ catch {
71
+ // Silent fail; tmux may not be available yet
72
+ }
73
+ }
74
+ /**
75
+ * Validate a string against the allowed charset for tmux session names.
76
+ * Tmux allows [a-zA-Z0-9_-].
77
+ */
78
+ validateNameComponent(value, field) {
79
+ if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
80
+ throw new Error(`Invalid ${field}: ${value} contains forbidden characters`);
81
+ }
82
+ return value;
83
+ }
84
+ /**
85
+ * Build the session key used in our in-memory registry.
86
+ */
87
+ makeSessionKey(chatId, phaseId, role, agentName) {
88
+ return `${chatId}:${phaseId}:${role}:${agentName}`;
89
+ }
90
+ /**
91
+ * Build the tmux session name from components.
92
+ * Format: chorus-<chatId>-<phaseId>-<role>-<agentName>
93
+ */
94
+ makeSessionName(chatId, phaseId, role, agentName) {
95
+ return `chorus-${chatId}-${phaseId}-${role}-${agentName}`;
96
+ }
97
+ /**
98
+ * Acquire a session for a phase round, respecting share-session policy.
99
+ *
100
+ * Decision tree:
101
+ * 1. If shareSessionAcrossRounds && session exists for this chat+phase+role+agent → reuse
102
+ * 2. Else if shareSessionAcrossPhases && session exists for this chat+role+agent on ANY phase → reuse + rename
103
+ * 3. Else spawn fresh
104
+ */
105
+ async acquire(opts) {
106
+ const { chatId, phaseId, role, shareSessionAcrossRounds, shareSessionAcrossPhases, agentName, } = opts;
107
+ // Validate input charset
108
+ this.validateNameComponent(chatId, 'chatId');
109
+ this.validateNameComponent(phaseId, 'phaseId');
110
+ this.validateNameComponent(agentName, 'agentName');
111
+ const key = this.makeSessionKey(chatId, phaseId, role, agentName);
112
+ // Rule 1: Across rounds (round N → N+1) within THIS phase
113
+ if (shareSessionAcrossRounds) {
114
+ const existing = this.sessions.get(key);
115
+ if (existing) {
116
+ existing.lastActivityAt = Date.now();
117
+ return existing;
118
+ }
119
+ }
120
+ // Rule 2: Across phases (rare) — reuse from a previous phase
121
+ if (shareSessionAcrossPhases) {
122
+ for (const [registryKey, handle] of this.sessions) {
123
+ // Match on chat+role+agent, different phase
124
+ if (handle.chatId === chatId &&
125
+ handle.role === role &&
126
+ handle.agentName === agentName &&
127
+ handle.phaseId !== phaseId) {
128
+ // Found one on a previous phase — rename and reuse
129
+ const newSessionName = this.makeSessionName(chatId, phaseId, role, agentName);
130
+ try {
131
+ (0, child_process_1.spawnSync)('tmux', ['rename-session', '-t', handle.name, newSessionName]);
132
+ }
133
+ catch {
134
+ // If rename fails, fall through to spawn fresh
135
+ this.sessions.delete(registryKey);
136
+ break;
137
+ }
138
+ // Update the handle
139
+ handle.phaseId = phaseId;
140
+ handle.lastActivityAt = Date.now();
141
+ this.sessions.delete(registryKey);
142
+ this.sessions.set(key, handle);
143
+ return handle;
144
+ }
145
+ }
146
+ }
147
+ // Rule 3: Spawn fresh
148
+ return this.spawnFresh(opts, key);
149
+ }
150
+ /**
151
+ * Spawn a fresh tmux session for the given phase round.
152
+ */
153
+ async spawnFresh(opts, key) {
154
+ const { chatId, phaseId, role, agentName, shim, spawnOpts } = opts;
155
+ const sessionName = this.makeSessionName(chatId, phaseId, role, agentName);
156
+ // Build launch command via the shim
157
+ const launchCommand = shim.buildLaunchCommand(spawnOpts);
158
+ // Spawn tmux session
159
+ try {
160
+ // Use child_process.spawnSync with args array for safety
161
+ const result = (0, child_process_1.spawnSync)('tmux', ['new-session', '-d', '-s', sessionName, launchCommand], {
162
+ encoding: 'utf-8',
163
+ });
164
+ if (result.status !== 0) {
165
+ throw new Error(`Tmux spawn failed: status=${result.status}, stderr=${result.stderr || 'unknown'}`);
166
+ }
167
+ }
168
+ catch (error) {
169
+ throw new Error(`TmuxSpawnError(code=tmux_unavailable): ${error instanceof Error ? error.message : String(error)}`);
170
+ }
171
+ // Cold-start poll: wait up to 8s for the session to be ready
172
+ const startTime = Date.now();
173
+ const timeout = 8000;
174
+ while (Date.now() - startTime < timeout) {
175
+ try {
176
+ const hasResult = (0, child_process_1.spawnSync)('tmux', ['has-session', '-t', sessionName], {
177
+ encoding: 'utf-8',
178
+ });
179
+ if (hasResult.status === 0) {
180
+ // Session is live
181
+ const handle = {
182
+ name: sessionName,
183
+ chatId,
184
+ phaseId,
185
+ role,
186
+ lineage: shim.lineage,
187
+ agentName,
188
+ spawnedAt: Date.now(),
189
+ lastActivityAt: Date.now(),
190
+ state: 'active',
191
+ };
192
+ this.sessions.set(key, handle);
193
+ return handle;
194
+ }
195
+ }
196
+ catch {
197
+ // Still not ready
198
+ }
199
+ // Poll every 200ms
200
+ await new Promise((resolve) => setTimeout(resolve, 200));
201
+ }
202
+ // Cold-start timeout
203
+ throw new Error(`TmuxSpawnError(code=cold_start_timeout): Session ${sessionName} did not become ready within 8s`);
204
+ }
205
+ /**
206
+ * Send raw keys to a session (pre-nudge cleanup: Escape, Ctrl-C, /clear, etc.)
207
+ * Errors swallowed per spec.
208
+ */
209
+ sendKeys(sessionName, keys) {
210
+ try {
211
+ const args = ['send-keys', '-t', sessionName, ...keys];
212
+ (0, child_process_1.spawnSync)('tmux', args, { stdio: 'ignore' });
213
+ }
214
+ catch {
215
+ // Swallow errors — session may be dead or unresponsive
216
+ }
217
+ }
218
+ /**
219
+ * Send a prompt to a CLI's TUI input box.
220
+ *
221
+ * Originally implemented via `tmux load-buffer + paste-buffer`. That works
222
+ * for Claude/Codex/OpenCode/Kimi but Gemini's TUI silently drops paste-
223
+ * buffer events (likely due to its custom terminal-escape handling). The
224
+ * `send-keys -l` approach types each character literally and works
225
+ * universally across all the CLIs we ship — at the cost of slightly more
226
+ * shell argv pressure for very long prompts.
227
+ *
228
+ * Method name kept as `pasteBuffer` for caller compatibility, but the
229
+ * implementation no longer uses tmux's paste mechanism.
230
+ */
231
+ pasteBuffer(sessionName, content) {
232
+ try {
233
+ (0, child_process_1.spawnSync)('tmux', ['send-keys', '-l', '-t', sessionName, content], {
234
+ stdio: 'ignore',
235
+ });
236
+ }
237
+ catch {
238
+ // Silent fail — session may be dead or unresponsive
239
+ }
240
+ }
241
+ /**
242
+ * Capture the current pane content (last 200 lines).
243
+ * Used by the failure detector to match error patterns.
244
+ */
245
+ capturePane(sessionName) {
246
+ try {
247
+ const result = (0, child_process_1.spawnSync)('tmux', ['capture-pane', '-t', sessionName, '-p', '-S', '-200'], {
248
+ encoding: 'utf-8',
249
+ });
250
+ if (result.status === 0) {
251
+ return result.stdout || '';
252
+ }
253
+ return '';
254
+ }
255
+ catch {
256
+ return '';
257
+ }
258
+ }
259
+ /**
260
+ * List all chorus-* sessions currently tracked in memory.
261
+ */
262
+ list() {
263
+ return Array.from(this.sessions.values());
264
+ }
265
+ /**
266
+ * Force-kill a session by name. Idempotent.
267
+ */
268
+ kill(sessionName) {
269
+ try {
270
+ (0, child_process_1.spawnSync)('tmux', ['kill-session', '-t', sessionName], { stdio: 'ignore' });
271
+ }
272
+ catch {
273
+ // Session may not exist or be already dead
274
+ }
275
+ // Remove from registry
276
+ for (const [key, handle] of this.sessions) {
277
+ if (handle.name === sessionName) {
278
+ this.sessions.delete(key);
279
+ break;
280
+ }
281
+ }
282
+ }
283
+ /**
284
+ * Mark a session as terminal (eligible for reaping).
285
+ * Useful when a phase finishes but the tmux session hasn't been killed yet.
286
+ */
287
+ markTerminal(sessionName) {
288
+ for (const handle of this.sessions.values()) {
289
+ if (handle.name === sessionName) {
290
+ handle.state = 'terminal';
291
+ break;
292
+ }
293
+ }
294
+ }
295
+ /**
296
+ * Reaper sweep: identify and kill orphan / idle sessions.
297
+ *
298
+ * Kill criteria:
299
+ * 1. Session's chatId is NOT in activeChats
300
+ * 2. Chat status is terminal (merged, cancelled, errored, timed-out)
301
+ * 3. Session state is awaiting_user AND idle > idleDestroyMinutes
302
+ */
303
+ reapOnce(opts) {
304
+ const { activeChats, idleDestroyMinutes } = opts;
305
+ const killed = [];
306
+ const now = Date.now();
307
+ const idleThresholdMs = idleDestroyMinutes * 60 * 1000;
308
+ for (const handle of this.sessions.values()) {
309
+ let shouldKill = false;
310
+ let killReason = '';
311
+ // Criterion 1: Chat is not in activeChats map
312
+ if (!activeChats.has(handle.chatId)) {
313
+ shouldKill = true;
314
+ killReason = 'chat_not_active';
315
+ }
316
+ // Criterion 2: Chat status is terminal
317
+ const chatStatus = activeChats.get(handle.chatId);
318
+ if (chatStatus && ['merged', 'cancelled', 'errored', 'timed_out'].includes(chatStatus)) {
319
+ shouldKill = true;
320
+ killReason = `chat_${chatStatus}`;
321
+ }
322
+ // Criterion 3: Session state is terminal
323
+ if (handle.state === 'terminal') {
324
+ shouldKill = true;
325
+ killReason = 'session_terminal';
326
+ }
327
+ // Criterion 4: Session is awaiting_user and idle too long
328
+ if (handle.state === 'awaiting_user' && now - handle.lastActivityAt > idleThresholdMs) {
329
+ shouldKill = true;
330
+ killReason = `idle_${idleDestroyMinutes}m`;
331
+ }
332
+ if (shouldKill) {
333
+ this.kill(handle.name);
334
+ killed.push(`${handle.name}(${killReason})`);
335
+ }
336
+ }
337
+ return { killed };
338
+ }
339
+ }
340
+ exports.TmuxManagerImpl = TmuxManagerImpl;
341
+ //# sourceMappingURL=tmux.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tmux.js","sourceRoot":"","sources":["../../src/daemon/tmux.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.atomicWriteJsonSync = atomicWriteJsonSync;
7
+ /**
8
+ * Atomic JSON write — writes to a temp file in the same dir, then renames
9
+ * over the destination. fs.renameSync is atomic on POSIX (single inode swap),
10
+ * so a reader concurrently opening the destination either sees the previous
11
+ * complete file or the new complete file — never a half-written one.
12
+ *
13
+ * Why this matters: the cockpit polls _meta.json sidecars and chat-level
14
+ * meta.json to render run cards. A daemon crash mid-write (or even a slow
15
+ * write under load) could leave a 0-byte or truncated file that JSON.parse
16
+ * rejects, and the cockpit silently swallows the error and the card never
17
+ * renders. Atomic-rename eliminates that window.
18
+ *
19
+ * The temp file uses a `.tmp.<pid>.<rand>` suffix so concurrent writers in
20
+ * the same dir (multiple participants in a phase) don't collide on the
21
+ * temp path. Rename is the synchronisation point; the OS guarantees only
22
+ * one of N concurrent renames "wins" the destination at any instant.
23
+ *
24
+ * Caller note: we deliberately use sync APIs because the existing call
25
+ * sites are sync (writeTransportMeta in kimi.ts, runner setup). An async
26
+ * variant can be added later if a hot path needs it.
27
+ */
28
+ const node_fs_1 = __importDefault(require("node:fs"));
29
+ const node_path_1 = __importDefault(require("node:path"));
30
+ function atomicWriteJsonSync(targetPath, value) {
31
+ const dir = node_path_1.default.dirname(targetPath);
32
+ const base = node_path_1.default.basename(targetPath);
33
+ // pid + 8 hex chars from Date.now() is sufficient; same-process collision
34
+ // is impossible (sequential calls), cross-process needs the pid + nonce.
35
+ const nonce = Math.floor(Math.random() * 0xffffffff).toString(16).padStart(8, '0');
36
+ const tmpPath = node_path_1.default.join(dir, `.${base}.tmp.${process.pid}.${nonce}`);
37
+ // Stringify FIRST so a JSON serialization error doesn't leave an empty
38
+ // tmp file on disk (otherwise the catch-and-cleanup below has more to do).
39
+ const body = JSON.stringify(value, null, 2);
40
+ try {
41
+ node_fs_1.default.writeFileSync(tmpPath, body, 'utf-8');
42
+ node_fs_1.default.renameSync(tmpPath, targetPath);
43
+ }
44
+ catch (err) {
45
+ // Best-effort cleanup of the temp file if rename failed (e.g. EXDEV
46
+ // on a cross-device tmpdir, EACCES on perms). Re-throw so callers can
47
+ // decide whether to swallow (informational sidecars) or escalate.
48
+ try {
49
+ node_fs_1.default.unlinkSync(tmpPath);
50
+ }
51
+ catch { /* tmp may not exist */ }
52
+ throw err;
53
+ }
54
+ }
55
+ //# sourceMappingURL=atomic-write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../../src/lib/atomic-write.ts"],"names":[],"mappings":";;;;;AAwBA,kDAsBC;AA9CD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,sDAAyB;AACzB,0DAA6B;AAE7B,SAAgB,mBAAmB,CAAC,UAAkB,EAAE,KAAc;IACpE,MAAM,GAAG,GAAG,mBAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,mBAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACvC,0EAA0E;IAC1E,yEAAyE;IACzE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnF,MAAM,OAAO,GAAG,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,QAAQ,OAAO,CAAC,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC;IAEvE,uEAAuE;IACvE,2EAA2E;IAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAE5C,IAAI,CAAC;QACH,iBAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACzC,iBAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,oEAAoE;QACpE,sEAAsE;QACtE,kEAAkE;QAClE,IAAI,CAAC;YAAC,iBAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,uBAAuB,CAAC,CAAC;QACjE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * Process-local event bus for chat list mutations.
4
+ *
5
+ * The cockpit sidebar (and any other surface that renders a list of
6
+ * chats) needs to react when a chat is created/updated/deleted from
7
+ * ANY source — REST POST, MCP tool call, runner status change. The
8
+ * daemon polled every 12s before, which is fine for status drift but
9
+ * looks broken when a freshly-fired MCP chat takes 12s to appear.
10
+ *
11
+ * We chose a process-local EventEmitter over a DB-NOTIFY broadcast
12
+ * because chorus is single-daemon today; if we ever shard the daemon,
13
+ * this becomes a libsql LISTEN. Subscribers are SSE handlers in
14
+ * `routes/chats-events.ts`.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.chatEventsBus = void 0;
18
+ const node_events_1 = require("node:events");
19
+ class ChatEventsBus extends node_events_1.EventEmitter {
20
+ emitChange(chatId, kind) {
21
+ const ev = { chatId, kind, ts: Date.now() };
22
+ this.emit('change', ev);
23
+ }
24
+ }
25
+ exports.chatEventsBus = new ChatEventsBus();
26
+ exports.chatEventsBus.setMaxListeners(50);
27
+ //# sourceMappingURL=chat-events-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-events-bus.js","sourceRoot":"","sources":["../../src/lib/chat-events-bus.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAEH,6CAA2C;AAU3C,MAAM,aAAc,SAAQ,0BAAY;IACtC,UAAU,CAAC,MAAc,EAAE,IAAoB;QAC7C,MAAM,EAAE,GAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC7D,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC1B,CAAC;CACF;AAEY,QAAA,aAAa,GAAG,IAAI,aAAa,EAAE,CAAC;AACjD,qBAAa,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC"}
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ /**
3
+ * Chat slug generation.
4
+ *
5
+ * The cockpit URL for a chat is `/runs/<slug>` instead of `/runs/<ULID>`.
6
+ * Bookmarks and shared links should be human-readable: `/runs/review-pr-9`
7
+ * beats `/runs/019DE881853DC50566D8DFCE083F75F0`.
8
+ *
9
+ * Strategy:
10
+ * 1. Slugify the `work` field (truncated, ASCII-folded, lowercase,
11
+ * hyphen-separated).
12
+ * 2. Fall back to the template_id when work is empty/non-Latin/all
13
+ * whitespace.
14
+ * 3. On collision, append `-2`, `-3`, ... until unique.
15
+ *
16
+ * Pure module: takes an `existsFn(slug)` callback so the DB layer (or a
17
+ * test) decides what "exists" means. No I/O here, fully unit-testable.
18
+ *
19
+ * Slug grammar (regex): `[a-z0-9]+(-[a-z0-9]+)*` — same shape GitHub
20
+ * uses for issue/repo URLs. Leading/trailing/duplicate hyphens are
21
+ * collapsed. Max length 60 characters; collision suffix always fits in
22
+ * the trailing 8 chars even after truncation.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.slugifyText = slugifyText;
26
+ exports.generateChatSlug = generateChatSlug;
27
+ exports.looksLikeSlug = looksLikeSlug;
28
+ const MAX_BASE_LEN = 60;
29
+ /** Generic stand-in when slugify produces empty string (all-emoji input, etc.). */
30
+ const FALLBACK_BASE = 'chat';
31
+ /** Defensive ceiling on the dedup loop — way above realistic collision counts. */
32
+ const MAX_DEDUP_ATTEMPTS = 10_000;
33
+ /**
34
+ * Convert arbitrary text to a URL-safe slug component. Pure, deterministic.
35
+ * - Folds Unicode → ASCII via NFKD (so "café" → "cafe").
36
+ * - Replaces every non-[a-z0-9] run with a single hyphen.
37
+ * - Strips leading/trailing hyphens.
38
+ * - Truncates to MAX_BASE_LEN.
39
+ * - Returns "" when input has no extractable slug characters; callers
40
+ * are expected to substitute a fallback.
41
+ */
42
+ function slugifyText(input) {
43
+ if (!input)
44
+ return '';
45
+ // NFKD splits accented chars into base + combining mark, then strip
46
+ // the marks. Falls back to the original if the runtime doesn't
47
+ // implement Intl normalisation.
48
+ let s;
49
+ try {
50
+ s = input.normalize('NFKD').replace(/[̀-ͯ]/g, '');
51
+ }
52
+ catch {
53
+ s = input;
54
+ }
55
+ s = s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
56
+ if (s.length === 0)
57
+ return '';
58
+ if (s.length > MAX_BASE_LEN) {
59
+ s = s.slice(0, MAX_BASE_LEN).replace(/-+$/g, '');
60
+ // Edge: truncating mid-token can leave us with a stray trailing
61
+ // hyphen even after the regex trim above (when the truncated tail
62
+ // ends with `-something`). The replace above handles it.
63
+ }
64
+ return s;
65
+ }
66
+ /**
67
+ * Build a slug for a chat with the given `work` text and `template_id`,
68
+ * unique among `existsFn(slug) === true` rows. Pure function — caller
69
+ * supplies the existence check.
70
+ *
71
+ * Collision suffix uses simple `-2`, `-3`, … rather than ULID prefixes
72
+ * because the URL stays short and predictable. Real-world collision rate
73
+ * is near zero (every chat starts with a different brief), so the loop
74
+ * almost always exits on the first iteration.
75
+ */
76
+ async function generateChatSlug(args) {
77
+ let base = slugifyText(args.work);
78
+ if (!base)
79
+ base = slugifyText(args.templateId);
80
+ if (!base)
81
+ base = FALLBACK_BASE;
82
+ // Try the base first, then -2, -3, … until we find a free one.
83
+ for (let i = 0; i < MAX_DEDUP_ATTEMPTS; i++) {
84
+ const candidate = i === 0 ? base : `${base}-${i + 1}`;
85
+ if (!(await args.existsFn(candidate)))
86
+ return candidate;
87
+ }
88
+ // Vanishingly unlikely — every realistic workload exits in <5 iterations.
89
+ // Fall through to a timestamp-suffixed slug rather than throw.
90
+ return `${base}-${Date.now()}`;
91
+ }
92
+ /**
93
+ * Lightweight check: does this string look like a slug rather than a
94
+ * ULID? Used by routes that accept either.
95
+ *
96
+ * ULIDs are 26 uppercase Crockford-base32 chars; the codebase emits
97
+ * 32-char uppercase hex, but either way they're [0-9A-Z]+ with no
98
+ * hyphens. Slugs are lowercase + hyphens by construction. Test for the
99
+ * presence of a lowercase letter or hyphen — both are absent in ULIDs
100
+ * but always present in our slugs.
101
+ */
102
+ function looksLikeSlug(s) {
103
+ return /[a-z]/.test(s) || s.includes('-');
104
+ }
105
+ //# sourceMappingURL=chat-slug.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-slug.js","sourceRoot":"","sources":["../../src/lib/chat-slug.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;AAiBH,kCAoBC;AAYD,4CAkBC;AAYD,sCAEC;AA/ED,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,mFAAmF;AACnF,MAAM,aAAa,GAAG,MAAM,CAAC;AAC7B,kFAAkF;AAClF,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC;;;;;;;;GAQG;AACH,SAAgB,WAAW,CAAC,KAAa;IACvC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,oEAAoE;IACpE,+DAA+D;IAC/D,gCAAgC;IAChC,IAAI,CAAS,CAAC;IACd,IAAI,CAAC;QACH,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,CAAC,GAAG,KAAK,CAAC;IACZ,CAAC;IACD,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACxE,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9B,IAAI,CAAC,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;QAC5B,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjD,gEAAgE;QAChE,kEAAkE;QAClE,yDAAyD;IAC3D,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;;GASG;AACI,KAAK,UAAU,gBAAgB,CAAC,IAKtC;IACC,IAAI,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI;QAAE,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC/C,IAAI,CAAC,IAAI;QAAE,IAAI,GAAG,aAAa,CAAC;IAEhC,+DAA+D;IAC/D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,kBAAkB,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACtD,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YAAE,OAAO,SAAS,CAAC;IAC1D,CAAC;IACD,0EAA0E;IAC1E,+DAA+D;IAC/D,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;AACjC,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,aAAa,CAAC,CAAS;IACrC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC5C,CAAC"}