@cluesmith/codev 3.3.2 → 3.3.4

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 (257) hide show
  1. package/dashboard-dist/assets/{index-D3XaKpZW.js → index-DZf6pNPE.js} +41 -41
  2. package/dashboard-dist/assets/index-DZf6pNPE.js.map +1 -0
  3. package/dashboard-dist/assets/index-DnYLy5C_.css +32 -0
  4. package/dashboard-dist/index.html +2 -2
  5. package/dist/agent-farm/cli.d.ts.map +1 -1
  6. package/dist/agent-farm/cli.js +48 -0
  7. package/dist/agent-farm/cli.js.map +1 -1
  8. package/dist/agent-farm/commands/ide.d.ts +20 -0
  9. package/dist/agent-farm/commands/ide.d.ts.map +1 -0
  10. package/dist/agent-farm/commands/ide.js +138 -0
  11. package/dist/agent-farm/commands/ide.js.map +1 -0
  12. package/dist/agent-farm/commands/inbox.d.ts.map +1 -1
  13. package/dist/agent-farm/commands/inbox.js +79 -10
  14. package/dist/agent-farm/commands/inbox.js.map +1 -1
  15. package/dist/agent-farm/commands/interrupt.d.ts +4 -1
  16. package/dist/agent-farm/commands/interrupt.d.ts.map +1 -1
  17. package/dist/agent-farm/commands/interrupt.js +6 -3
  18. package/dist/agent-farm/commands/interrupt.js.map +1 -1
  19. package/dist/agent-farm/commands/reset.d.ts +4 -1
  20. package/dist/agent-farm/commands/reset.d.ts.map +1 -1
  21. package/dist/agent-farm/commands/reset.js +6 -3
  22. package/dist/agent-farm/commands/reset.js.map +1 -1
  23. package/dist/agent-farm/commands/send.d.ts +28 -0
  24. package/dist/agent-farm/commands/send.d.ts.map +1 -1
  25. package/dist/agent-farm/commands/send.js +79 -6
  26. package/dist/agent-farm/commands/send.js.map +1 -1
  27. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
  28. package/dist/agent-farm/commands/spawn-worktree.js +16 -7
  29. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
  30. package/dist/agent-farm/commands/tower.d.ts +21 -9
  31. package/dist/agent-farm/commands/tower.d.ts.map +1 -1
  32. package/dist/agent-farm/commands/tower.js +90 -30
  33. package/dist/agent-farm/commands/tower.js.map +1 -1
  34. package/dist/agent-farm/db/index.d.ts +3 -1
  35. package/dist/agent-farm/db/index.d.ts.map +1 -1
  36. package/dist/agent-farm/db/index.js +8 -443
  37. package/dist/agent-farm/db/index.js.map +1 -1
  38. package/dist/agent-farm/db/mailbox.d.ts +47 -7
  39. package/dist/agent-farm/db/mailbox.d.ts.map +1 -1
  40. package/dist/agent-farm/db/mailbox.js +56 -12
  41. package/dist/agent-farm/db/mailbox.js.map +1 -1
  42. package/dist/agent-farm/db/migrations.d.ts +57 -0
  43. package/dist/agent-farm/db/migrations.d.ts.map +1 -0
  44. package/dist/agent-farm/db/migrations.js +530 -0
  45. package/dist/agent-farm/db/migrations.js.map +1 -0
  46. package/dist/agent-farm/db/schema.d.ts +1 -1
  47. package/dist/agent-farm/db/schema.d.ts.map +1 -1
  48. package/dist/agent-farm/db/schema.js +1 -0
  49. package/dist/agent-farm/db/schema.js.map +1 -1
  50. package/dist/agent-farm/db/tower-owner.d.ts +168 -0
  51. package/dist/agent-farm/db/tower-owner.d.ts.map +1 -0
  52. package/dist/agent-farm/db/tower-owner.js +369 -0
  53. package/dist/agent-farm/db/tower-owner.js.map +1 -0
  54. package/dist/agent-farm/db/types.d.ts +47 -0
  55. package/dist/agent-farm/db/types.d.ts.map +1 -1
  56. package/dist/agent-farm/db/types.js.map +1 -1
  57. package/dist/agent-farm/lib/ide-record.d.ts +65 -0
  58. package/dist/agent-farm/lib/ide-record.d.ts.map +1 -0
  59. package/dist/agent-farm/lib/ide-record.js +118 -0
  60. package/dist/agent-farm/lib/ide-record.js.map +1 -0
  61. package/dist/agent-farm/lib/tunnel-client.d.ts +22 -0
  62. package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
  63. package/dist/agent-farm/lib/tunnel-client.js +96 -19
  64. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  65. package/dist/agent-farm/servers/cron-delivery.d.ts +7 -1
  66. package/dist/agent-farm/servers/cron-delivery.d.ts.map +1 -1
  67. package/dist/agent-farm/servers/cron-delivery.js +2 -1
  68. package/dist/agent-farm/servers/cron-delivery.js.map +1 -1
  69. package/dist/agent-farm/servers/gate-profiles.d.ts +44 -2
  70. package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -1
  71. package/dist/agent-farm/servers/gate-profiles.js +79 -6
  72. package/dist/agent-farm/servers/gate-profiles.js.map +1 -1
  73. package/dist/agent-farm/servers/ide-forward.d.ts +62 -0
  74. package/dist/agent-farm/servers/ide-forward.d.ts.map +1 -0
  75. package/dist/agent-farm/servers/ide-forward.js +274 -0
  76. package/dist/agent-farm/servers/ide-forward.js.map +1 -0
  77. package/dist/agent-farm/servers/ide-server.d.ts +68 -0
  78. package/dist/agent-farm/servers/ide-server.d.ts.map +1 -0
  79. package/dist/agent-farm/servers/ide-server.js +281 -0
  80. package/dist/agent-farm/servers/ide-server.js.map +1 -0
  81. package/dist/agent-farm/servers/mailbox-delivery.d.ts +296 -12
  82. package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -1
  83. package/dist/agent-farm/servers/mailbox-delivery.js +566 -69
  84. package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -1
  85. package/dist/agent-farm/servers/mailbox-wiring.d.ts +66 -19
  86. package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -1
  87. package/dist/agent-farm/servers/mailbox-wiring.js +204 -37
  88. package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -1
  89. package/dist/agent-farm/servers/message-write.d.ts +138 -11
  90. package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
  91. package/dist/agent-farm/servers/message-write.js +144 -35
  92. package/dist/agent-farm/servers/message-write.js.map +1 -1
  93. package/dist/agent-farm/servers/overview.d.ts +28 -12
  94. package/dist/agent-farm/servers/overview.d.ts.map +1 -1
  95. package/dist/agent-farm/servers/overview.js +130 -79
  96. package/dist/agent-farm/servers/overview.js.map +1 -1
  97. package/dist/agent-farm/servers/render-gate.d.ts +77 -1
  98. package/dist/agent-farm/servers/render-gate.d.ts.map +1 -1
  99. package/dist/agent-farm/servers/render-gate.js +174 -25
  100. package/dist/agent-farm/servers/render-gate.js.map +1 -1
  101. package/dist/agent-farm/servers/session-submit.d.ts +7 -4
  102. package/dist/agent-farm/servers/session-submit.d.ts.map +1 -1
  103. package/dist/agent-farm/servers/session-submit.js +7 -4
  104. package/dist/agent-farm/servers/session-submit.js.map +1 -1
  105. package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
  106. package/dist/agent-farm/servers/tower-cron.js +7 -3
  107. package/dist/agent-farm/servers/tower-cron.js.map +1 -1
  108. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  109. package/dist/agent-farm/servers/tower-instances.js +17 -10
  110. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  111. package/dist/agent-farm/servers/tower-routes.d.ts +32 -0
  112. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  113. package/dist/agent-farm/servers/tower-routes.js +186 -26
  114. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  115. package/dist/agent-farm/servers/tower-server.js +79 -9
  116. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  117. package/dist/agent-farm/servers/tower-terminals.d.ts +21 -0
  118. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  119. package/dist/agent-farm/servers/tower-terminals.js +106 -13
  120. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  121. package/dist/agent-farm/servers/tower-utils.d.ts +35 -0
  122. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  123. package/dist/agent-farm/servers/tower-utils.js +39 -0
  124. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  125. package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
  126. package/dist/agent-farm/servers/tower-websocket.js +26 -2
  127. package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
  128. package/dist/agent-farm/utils/message-format.d.ts +78 -2
  129. package/dist/agent-farm/utils/message-format.d.ts.map +1 -1
  130. package/dist/agent-farm/utils/message-format.js +107 -11
  131. package/dist/agent-farm/utils/message-format.js.map +1 -1
  132. package/dist/agent-farm/utils/port.d.ts +22 -0
  133. package/dist/agent-farm/utils/port.d.ts.map +1 -0
  134. package/dist/agent-farm/utils/port.js +35 -0
  135. package/dist/agent-farm/utils/port.js.map +1 -0
  136. package/dist/agent-farm/utils/server-utils.d.ts +26 -0
  137. package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
  138. package/dist/agent-farm/utils/server-utils.js +36 -0
  139. package/dist/agent-farm/utils/server-utils.js.map +1 -1
  140. package/dist/cli.d.ts.map +1 -1
  141. package/dist/cli.js +3 -1
  142. package/dist/cli.js.map +1 -1
  143. package/dist/commands/consult/index.d.ts.map +1 -1
  144. package/dist/commands/consult/index.js +61 -1
  145. package/dist/commands/consult/index.js.map +1 -1
  146. package/dist/commands/consult/tree-tripwire.d.ts +177 -0
  147. package/dist/commands/consult/tree-tripwire.d.ts.map +1 -0
  148. package/dist/commands/consult/tree-tripwire.js +356 -0
  149. package/dist/commands/consult/tree-tripwire.js.map +1 -0
  150. package/dist/commands/doctor.d.ts +69 -0
  151. package/dist/commands/doctor.d.ts.map +1 -1
  152. package/dist/commands/doctor.js +165 -2
  153. package/dist/commands/doctor.js.map +1 -1
  154. package/dist/commands/generate-image.d.ts +25 -1
  155. package/dist/commands/generate-image.d.ts.map +1 -1
  156. package/dist/commands/generate-image.js +410 -2
  157. package/dist/commands/generate-image.js.map +1 -1
  158. package/dist/commands/porch/prompts.d.ts.map +1 -1
  159. package/dist/commands/porch/prompts.js +18 -2
  160. package/dist/commands/porch/prompts.js.map +1 -1
  161. package/dist/lib/agent-env.d.ts +86 -0
  162. package/dist/lib/agent-env.d.ts.map +1 -0
  163. package/dist/lib/agent-env.js +126 -0
  164. package/dist/lib/agent-env.js.map +1 -0
  165. package/dist/lib/forge-contracts.d.ts +76 -5
  166. package/dist/lib/forge-contracts.d.ts.map +1 -1
  167. package/dist/lib/forge-contracts.js.map +1 -1
  168. package/dist/lib/forge-rate-limit.d.ts +45 -0
  169. package/dist/lib/forge-rate-limit.d.ts.map +1 -0
  170. package/dist/lib/forge-rate-limit.js +89 -0
  171. package/dist/lib/forge-rate-limit.js.map +1 -0
  172. package/dist/lib/forge.d.ts +29 -1
  173. package/dist/lib/forge.d.ts.map +1 -1
  174. package/dist/lib/forge.js +96 -21
  175. package/dist/lib/forge.js.map +1 -1
  176. package/dist/lib/github.d.ts +13 -1
  177. package/dist/lib/github.d.ts.map +1 -1
  178. package/dist/lib/github.js +58 -15
  179. package/dist/lib/github.js.map +1 -1
  180. package/dist/lib/test-env.d.ts +17 -0
  181. package/dist/lib/test-env.d.ts.map +1 -1
  182. package/dist/lib/test-env.js +20 -0
  183. package/dist/lib/test-env.js.map +1 -1
  184. package/dist/terminal/pty-manager.d.ts +10 -1
  185. package/dist/terminal/pty-manager.d.ts.map +1 -1
  186. package/dist/terminal/pty-manager.js +38 -9
  187. package/dist/terminal/pty-manager.js.map +1 -1
  188. package/dist/terminal/pty-session.d.ts +174 -8
  189. package/dist/terminal/pty-session.d.ts.map +1 -1
  190. package/dist/terminal/pty-session.js +299 -25
  191. package/dist/terminal/pty-session.js.map +1 -1
  192. package/dist/terminal/session-manager.d.ts +23 -2
  193. package/dist/terminal/session-manager.d.ts.map +1 -1
  194. package/dist/terminal/session-manager.js +115 -36
  195. package/dist/terminal/session-manager.js.map +1 -1
  196. package/dist/terminal/session-screen.d.ts +34 -5
  197. package/dist/terminal/session-screen.d.ts.map +1 -1
  198. package/dist/terminal/session-screen.js +52 -6
  199. package/dist/terminal/session-screen.js.map +1 -1
  200. package/dist/terminal/shellper-client.d.ts +73 -0
  201. package/dist/terminal/shellper-client.d.ts.map +1 -1
  202. package/dist/terminal/shellper-client.js +151 -0
  203. package/dist/terminal/shellper-client.js.map +1 -1
  204. package/dist/terminal/shellper-main.js +20 -3
  205. package/dist/terminal/shellper-main.js.map +1 -1
  206. package/dist/terminal/shellper-process.d.ts +2 -0
  207. package/dist/terminal/shellper-process.d.ts.map +1 -1
  208. package/dist/terminal/shellper-process.js +17 -1
  209. package/dist/terminal/shellper-process.js.map +1 -1
  210. package/dist/terminal/shellper-protocol.d.ts +28 -0
  211. package/dist/terminal/shellper-protocol.d.ts.map +1 -1
  212. package/dist/terminal/shellper-protocol.js.map +1 -1
  213. package/dist/terminal/terminal-replies.d.ts +102 -0
  214. package/dist/terminal/terminal-replies.d.ts.map +1 -0
  215. package/dist/terminal/terminal-replies.js +181 -0
  216. package/dist/terminal/terminal-replies.js.map +1 -0
  217. package/package.json +4 -4
  218. package/scripts/forge/gitea/_lib.sh +214 -0
  219. package/scripts/forge/gitea/issue-comment.sh +8 -1
  220. package/scripts/forge/gitea/issue-view.sh +95 -4
  221. package/scripts/forge/gitea/pr-create.sh +155 -0
  222. package/scripts/forge/gitea/pr-exists.sh +32 -4
  223. package/scripts/forge/gitea/pr-list.sh +40 -31
  224. package/scripts/forge/gitea/pr-view.sh +65 -4
  225. package/scripts/forge/gitea/recently-merged.sh +91 -21
  226. package/scripts/forge/gitea/user-identity.sh +30 -1
  227. package/scripts/forge/github/issue-view.sh +5 -2
  228. package/scripts/forge/github/pr-create.sh +47 -0
  229. package/scripts/forge/github/pr-list.sh +6 -2
  230. package/scripts/forge/github/pr-search.sh +6 -2
  231. package/scripts/forge/gitlab/issue-search.sh +1 -0
  232. package/scripts/forge/gitlab/issue-view.sh +24 -4
  233. package/scripts/forge/gitlab/pr-create.sh +53 -0
  234. package/scripts/forge/gitlab/pr-search.sh +37 -2
  235. package/scripts/forge/linear/auth-status.sh +1 -0
  236. package/scripts/forge/linear/issue-comment.sh +1 -0
  237. package/scripts/forge/linear/issue-list.sh +1 -0
  238. package/scripts/forge/linear/issue-search.sh +1 -0
  239. package/scripts/forge/linear/issue-view.sh +14 -2
  240. package/scripts/forge/linear/recently-closed.sh +1 -0
  241. package/scripts/forge/linear/user-identity.sh +1 -0
  242. package/skeleton/.claude/skills/generate-image/SKILL.md +39 -1
  243. package/skeleton/.codex/skills/generate-image/SKILL.md +39 -1
  244. package/skeleton/protocols/air/prompts/pr.md +6 -1
  245. package/skeleton/protocols/aspir/prompts/review.md +6 -1
  246. package/skeleton/protocols/bugfix/prompts/pr.md +6 -1
  247. package/skeleton/protocols/bugfix/protocol.md +2 -1
  248. package/skeleton/protocols/maintain/prompts/review.md +6 -1
  249. package/skeleton/protocols/pir/prompts/review.md +10 -7
  250. package/skeleton/protocols/spir/prompts/review.md +6 -1
  251. package/skeleton/resources/commands/agent-farm.md +28 -3
  252. package/skeleton/roles/consultant.md +32 -0
  253. package/skeleton/templates/AGENTS.md +1 -1
  254. package/skeleton/templates/CLAUDE.md +1 -1
  255. package/templates/tower.html +16 -1
  256. package/dashboard-dist/assets/index-BZRTEMtH.css +0 -32
  257. package/dashboard-dist/assets/index-D3XaKpZW.js.map +0 -1
@@ -17,9 +17,30 @@
17
17
  * correctness bug, since agy's screens classify by an entirely different rule.
18
18
  */
19
19
  import type { GateProfile } from './render-gate.js';
20
- /** claude composer profile (marker ❯, dim placeholder — measured, spike g2). */
20
+ /**
21
+ * claude composer profile (marker ❯, dim placeholder — measured, spike g2).
22
+ *
23
+ * `queuesInputMidTurn` measured for Issue #1664 against claude 2.1.266: a message delivered while
24
+ * a turn was streaming landed intact and was submitted as one message at the turn's end in 100 of
25
+ * 100 trials (0 head-loss), and the TUI shows it as `Press up to edit queued messages` in the
26
+ * meantime. Evidence: `codev/evidence/1664-streaming-delivery/`.
27
+ */
21
28
  export declare const CLAUDE_PROFILE: GateProfile;
22
- /** codex composer profile (marker ›, dim placeholder — measured, spike g2). */
29
+ /**
30
+ * codex composer profile (marker ›, dim placeholder — measured, spike g2).
31
+ *
32
+ * `queuesInputMidTurn` measured for Issue #1664 against codex-cli 0.153.4, by the same harness
33
+ * and the same production delivery path as claude's: 20 of 20 mid-turn deliveries landed intact
34
+ * and were submitted, 0 head-loss, every one written at an instant the retired whole-screen
35
+ * settle would have refused. Evidence: `codev/evidence/1664-streaming-delivery/`.
36
+ *
37
+ * One measured quirk worth knowing, though it needs no code: codex's composer SLIDES DOWN the
38
+ * screen as its transcript grows (measured rows 13 → 37 over ~11 s) until it reaches the bottom
39
+ * and pins. Its row span is part of the fingerprint, so during that phase the region legitimately
40
+ * counts as moving and mail holds — a fresh codex session's first delivery waits for the screen
41
+ * to fill. Transient, self-correcting, and not worth special-casing: a long-lived builder's
42
+ * screen is always full.
43
+ */
23
44
  export declare const CODEX_PROFILE: GateProfile;
24
45
  /**
25
46
  * agy composer profile (Spec 1313 Phase 3 — net-new measurement). agy breaks the
@@ -32,6 +53,27 @@ export declare const CODEX_PROFILE: GateProfile;
32
53
  * option is palette-12, counted) — so a blind Enter never confirms filesystem
33
54
  * trust. Region bounds reuse the shared rule-line/status patterns (agy brackets
34
55
  * its composer with `─────` rules, like claude).
56
+ *
57
+ * The two marker ANCHORS (#1474) exist because `> ` alone does not identify agy's
58
+ * composer. Measured on real agy 1.1.13 captures (`fixtures/gate/agy-*.txt`), a live
59
+ * screen routinely carries OTHER `> ` rows, and last-match-wins picked them over the
60
+ * composer:
61
+ * - the slash-menu selection cursor (`> /add-dir …`) — palette-12 like the composer,
62
+ * and rendered BELOW it, so it won the marker scan outright;
63
+ * - the transcript echo of every submitted turn (`> <the message you sent>`), palette-4,
64
+ * one per conversation turn;
65
+ * - the trust dialog's selected option — palette-12, with no composer on screen at all.
66
+ * Hence `markerRequiresCursorRow: true` (only the live input row holds the cursor — menu
67
+ * and dialog rows never do) and `markerFgPalette: 12` (the marker glyph's own color, which
68
+ * separates the composer from the palette-4 transcript echo). Both were measured stable
69
+ * across agy's modes, including the bare-`>` no-hint mode.
70
+ *
71
+ * Direction of failure, deliberately: an agy re-theme, or a cursor parked off the composer
72
+ * (e.g. a single mid-repaint frame), yields NO marker ⇒ `no-composer-marker` ⇒ the message
73
+ * is held and re-checked, never delivered onto a screen we failed to understand. A
74
+ * sustained hold escalates through the mailbox-delivery liveness telemetry
75
+ * (`recordStreak`) — the same drift path {@link REGION_END_PATTERNS} documents — so widen
76
+ * these anchors ONLY from a real capture.
35
77
  */
36
78
  export declare const AGY_PROFILE: GateProfile;
37
79
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"gate-profiles.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/gate-profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA+BpD,gFAAgF;AAChF,eAAO,MAAM,cAAc,EAAE,WAI5B,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,aAAa,EAAE,WAI3B,CAAC;AASF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,EAAE,WAKzB,CAAC;AAQF;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,WAAW,GAAG,IAAI,CAMxE"}
1
+ {"version":3,"file":"gate-profiles.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/gate-profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA+BpD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,EAAE,WAK5B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,EAAE,WAK3B,CAAC;AAkBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,WAAW,EAAE,WAyBzB,CAAC;AAQF;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,WAAW,GAAG,IAAI,CAMxE"}
@@ -45,24 +45,56 @@ const COMPOSER_MARKER = /^[❯›]/;
45
45
  * from a real capture — a too-loose pattern that matches draft content is a false-clean.
46
46
  */
47
47
  const REGION_END_PATTERNS = [/^[─━╌┄]{5,}/, /^\s{2,}(gpt|high:|~\/)/];
48
- /** claude composer profile (marker ❯, dim placeholder — measured, spike g2). */
48
+ /**
49
+ * claude composer profile (marker ❯, dim placeholder — measured, spike g2).
50
+ *
51
+ * `queuesInputMidTurn` measured for Issue #1664 against claude 2.1.266: a message delivered while
52
+ * a turn was streaming landed intact and was submitted as one message at the turn's end in 100 of
53
+ * 100 trials (0 head-loss), and the TUI shows it as `Press up to edit queued messages` in the
54
+ * meantime. Evidence: `codev/evidence/1664-streaming-delivery/`.
55
+ */
49
56
  export const CLAUDE_PROFILE = {
50
57
  app: 'claude',
51
58
  markerPattern: COMPOSER_MARKER,
52
59
  regionEndPatterns: REGION_END_PATTERNS,
60
+ queuesInputMidTurn: true,
53
61
  };
54
- /** codex composer profile (marker ›, dim placeholder — measured, spike g2). */
62
+ /**
63
+ * codex composer profile (marker ›, dim placeholder — measured, spike g2).
64
+ *
65
+ * `queuesInputMidTurn` measured for Issue #1664 against codex-cli 0.153.4, by the same harness
66
+ * and the same production delivery path as claude's: 20 of 20 mid-turn deliveries landed intact
67
+ * and were submitted, 0 head-loss, every one written at an instant the retired whole-screen
68
+ * settle would have refused. Evidence: `codev/evidence/1664-streaming-delivery/`.
69
+ *
70
+ * One measured quirk worth knowing, though it needs no code: codex's composer SLIDES DOWN the
71
+ * screen as its transcript grows (measured rows 13 → 37 over ~11 s) until it reaches the bottom
72
+ * and pins. Its row span is part of the fingerprint, so during that phase the region legitimately
73
+ * counts as moving and mail holds — a fresh codex session's first delivery waits for the screen
74
+ * to fill. Transient, self-correcting, and not worth special-casing: a long-lived builder's
75
+ * screen is always full.
76
+ */
55
77
  export const CODEX_PROFILE = {
56
78
  app: 'codex',
57
79
  markerPattern: COMPOSER_MARKER,
58
80
  regionEndPatterns: REGION_END_PATTERNS,
81
+ queuesInputMidTurn: true,
59
82
  };
60
83
  /**
61
- * agy (Antigravity CLI 1.1.8) composer marker: a `> ` prompt glyph at the input
62
- * row start — a different glyph from claude/codex's `❯`/`›`, so its own pattern.
63
- * (Measured, Spec 1313 Phase 3; the marker cell renders palette-12 bright-blue.)
84
+ * agy (Antigravity CLI) composer marker: a `>` prompt glyph at the input row start — a
85
+ * different glyph from claude/codex's `❯`/`›`, so its own pattern. The trailing separator
86
+ * is `\s|$` rather than a literal space because agy's no-hint mode renders the empty
87
+ * composer as a BARE `>`, which right-trims to `">"` (measured on 1.1.13, #1474) — the
88
+ * old `/^> /` never matched it, so the gate held every message to an agy in that mode
89
+ * forever.
90
+ *
91
+ * This text pattern is deliberately NOT the whole marker test. `> ` is ordinary output —
92
+ * and, measured on real agy screens, it is also agy's own slash-menu selection cursor and
93
+ * the per-turn transcript echo of each submitted message. The composer is identified by
94
+ * this pattern PLUS the two anchors on {@link AGY_PROFILE} below; broaden or narrow the
95
+ * three together, never this line alone.
64
96
  */
65
- const AGY_MARKER = /^> /;
97
+ const AGY_MARKER = /^>(\s|$)/;
66
98
  /**
67
99
  * agy composer profile (Spec 1313 Phase 3 — net-new measurement). agy breaks the
68
100
  * dim-placeholder assumption: its idle mode-hint (`Accept-edits mode: …`) renders
@@ -74,12 +106,53 @@ const AGY_MARKER = /^> /;
74
106
  * option is palette-12, counted) — so a blind Enter never confirms filesystem
75
107
  * trust. Region bounds reuse the shared rule-line/status patterns (agy brackets
76
108
  * its composer with `─────` rules, like claude).
109
+ *
110
+ * The two marker ANCHORS (#1474) exist because `> ` alone does not identify agy's
111
+ * composer. Measured on real agy 1.1.13 captures (`fixtures/gate/agy-*.txt`), a live
112
+ * screen routinely carries OTHER `> ` rows, and last-match-wins picked them over the
113
+ * composer:
114
+ * - the slash-menu selection cursor (`> /add-dir …`) — palette-12 like the composer,
115
+ * and rendered BELOW it, so it won the marker scan outright;
116
+ * - the transcript echo of every submitted turn (`> <the message you sent>`), palette-4,
117
+ * one per conversation turn;
118
+ * - the trust dialog's selected option — palette-12, with no composer on screen at all.
119
+ * Hence `markerRequiresCursorRow: true` (only the live input row holds the cursor — menu
120
+ * and dialog rows never do) and `markerFgPalette: 12` (the marker glyph's own color, which
121
+ * separates the composer from the palette-4 transcript echo). Both were measured stable
122
+ * across agy's modes, including the bare-`>` no-hint mode.
123
+ *
124
+ * Direction of failure, deliberately: an agy re-theme, or a cursor parked off the composer
125
+ * (e.g. a single mid-repaint frame), yields NO marker ⇒ `no-composer-marker` ⇒ the message
126
+ * is held and re-checked, never delivered onto a screen we failed to understand. A
127
+ * sustained hold escalates through the mailbox-delivery liveness telemetry
128
+ * (`recordStreak`) — the same drift path {@link REGION_END_PATTERNS} documents — so widen
129
+ * these anchors ONLY from a real capture.
77
130
  */
78
131
  export const AGY_PROFILE = {
79
132
  app: 'agy',
80
133
  markerPattern: AGY_MARKER,
81
134
  regionEndPatterns: REGION_END_PATTERNS,
82
135
  placeholderFgPalette: 8,
136
+ markerRequiresCursorRow: true,
137
+ markerFgPalette: 12,
138
+ // Issue #1664, set by OWNER RULING (2026-09-09) — **UNMEASURED**. Every other value of this
139
+ // field on this page came from driving the real TUI through the acceptance harness; this one
140
+ // did not. The owner's call is that mid-turn delivery is universal and only human input holds
141
+ // mail, so agy is granted the licence rather than waiting on a measurement.
142
+ //
143
+ // What that trades away, stated plainly so a future reader is not misled by the company this
144
+ // line keeps: if agy DROPS or mishandles input arriving mid-turn, a delivery lands as text in
145
+ // its composer that is never submitted — the body strands on the line, the classifier then
146
+ // reads it as `user-text`, and every later message to that agent queues behind it until a
147
+ // human clears the line. That is a stuck agent with a visible cause, not silent data loss, but
148
+ // it is a real failure mode and it is not ruled out by anything in this repository.
149
+ //
150
+ // Settling it is a harness run, not an argument:
151
+ // node --experimental-strip-types scripts/bugfix-1664-streaming-delivery-harness.mts \
152
+ // --harness agy --scenario streaming --trials 20 --assume-queues-input
153
+ // If that reports 20/20 delivered-intact and submitted, replace this comment with the measured
154
+ // one. If it does not, this line is what needs changing.
155
+ queuesInputMidTurn: true,
83
156
  };
84
157
  /** Registry keyed by the harness name `detectHarnessFromCommand` returns. */
85
158
  const PROFILES_BY_HARNESS = {
@@ -1 +1 @@
1
- {"version":3,"file":"gate-profiles.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/gate-profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAG/D;;;;;GAKG;AACH,MAAM,eAAe,GAAG,OAAO,CAAC;AAEhC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,mBAAmB,GAAG,CAAC,aAAa,EAAE,wBAAwB,CAAC,CAAC;AAEtE,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAgB;IACzC,GAAG,EAAE,QAAQ;IACb,aAAa,EAAE,eAAe;IAC9B,iBAAiB,EAAE,mBAAmB;CACvC,CAAC;AAEF,+EAA+E;AAC/E,MAAM,CAAC,MAAM,aAAa,GAAgB;IACxC,GAAG,EAAE,OAAO;IACZ,aAAa,EAAE,eAAe;IAC9B,iBAAiB,EAAE,mBAAmB;CACvC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,GAAG,KAAK,CAAC;AAEzB;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC,GAAG,EAAE,KAAK;IACV,aAAa,EAAE,UAAU;IACzB,iBAAiB,EAAE,mBAAmB;IACtC,oBAAoB,EAAE,CAAC;CACxB,CAAC;AAEF,6EAA6E;AAC7E,MAAM,mBAAmB,GAAgC;IACvD,MAAM,EAAE,cAAc;IACtB,KAAK,EAAE,aAAa;CACrB,CAAC;AAiBF;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAAC,QAAqB;IAClD,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IACtD,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;QAAE,OAAO,WAAW,CAAC;IAC7E,MAAM,OAAO,GAAG,wBAAwB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3D,IAAI,OAAO,IAAI,OAAO,IAAI,mBAAmB;QAAE,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACnF,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"gate-profiles.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/gate-profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAG/D;;;;;GAKG;AACH,MAAM,eAAe,GAAG,OAAO,CAAC;AAEhC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,mBAAmB,GAAG,CAAC,aAAa,EAAE,wBAAwB,CAAC,CAAC;AAEtE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAgB;IACzC,GAAG,EAAE,QAAQ;IACb,aAAa,EAAE,eAAe;IAC9B,iBAAiB,EAAE,mBAAmB;IACtC,kBAAkB,EAAE,IAAI;CACzB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,aAAa,GAAgB;IACxC,GAAG,EAAE,OAAO;IACZ,aAAa,EAAE,eAAe;IAC9B,iBAAiB,EAAE,mBAAmB;IACtC,kBAAkB,EAAE,IAAI;CACzB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,GAAG,UAAU,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC,GAAG,EAAE,KAAK;IACV,aAAa,EAAE,UAAU;IACzB,iBAAiB,EAAE,mBAAmB;IACtC,oBAAoB,EAAE,CAAC;IACvB,uBAAuB,EAAE,IAAI;IAC7B,eAAe,EAAE,EAAE;IACnB,4FAA4F;IAC5F,6FAA6F;IAC7F,8FAA8F;IAC9F,4EAA4E;IAC5E,EAAE;IACF,6FAA6F;IAC7F,8FAA8F;IAC9F,2FAA2F;IAC3F,0FAA0F;IAC1F,+FAA+F;IAC/F,oFAAoF;IACpF,EAAE;IACF,iDAAiD;IACjD,yFAAyF;IACzF,2EAA2E;IAC3E,+FAA+F;IAC/F,yDAAyD;IACzD,kBAAkB,EAAE,IAAI;CACzB,CAAC;AAEF,6EAA6E;AAC7E,MAAM,mBAAmB,GAAgC;IACvD,MAAM,EAAE,cAAc;IACtB,KAAK,EAAE,aAAa;CACrB,CAAC;AAiBF;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAAC,QAAqB;IAClD,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IACtD,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;QAAE,OAAO,WAAW,CAAC;IAC7E,MAAM,OAAO,GAAG,wBAAwB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3D,IAAI,OAAO,IAAI,OAAO,IAAI,mBAAmB;QAAE,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACnF,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * IDE prefix forward (Issue #1668).
3
+ *
4
+ * Tower forwards a fixed `/ide/` prefix to a single local VS Code server-web
5
+ * process — plain HTTP here and, for the workbench's own remote-protocol
6
+ * sockets, a raw bidirectional WebSocket pipe (Tower never parses those frames;
7
+ * they are VS Code's protocol, not Tower's — see `tower-websocket.ts` for the
8
+ * contrast with the PTY terminal bridge).
9
+ *
10
+ * Authorization is decided upstream by `isForwardAuthorized` (the HTTP path also
11
+ * passes the general `isRequestAllowed` choke point first); this module only
12
+ * proxies, after that decision. Header hygiene and `x-forwarded-*` stamping
13
+ * happen here, strictly after auth (Issue #1668 §D2).
14
+ */
15
+ import http from 'node:http';
16
+ import type net from 'node:net';
17
+ /** The tunnel's public authority, for stamping `x-forwarded-*` on tunnel-borne requests. */
18
+ export interface PublicAuthority {
19
+ host: string;
20
+ proto: string;
21
+ prefix: string;
22
+ }
23
+ /**
24
+ * Merge the IDE connection token into a Cookie header value as `vscode-tkn=…`,
25
+ * preserving every existing cookie and never clobbering an already-present
26
+ * `vscode-tkn` (Issue #1668). Exported for testing.
27
+ */
28
+ export declare function mergeConnectionTokenCookie(existing: string | string[] | undefined, token: string): string;
29
+ /**
30
+ * Derive the tunnel's public authority from the cloud config, matching the
31
+ * `accessUrl` Tower advertises (`tower-tunnel.ts`: `${server_url}/t/${tower_name}/`).
32
+ * Returns null when the tower is not cloud-registered.
33
+ */
34
+ export declare function getPublicAuthority(): PublicAuthority | null;
35
+ /**
36
+ * Build the header set forwarded to the IDE server: drop hop-by-hop and
37
+ * Tower-internal headers, set the loopback `Host`, and — only when tunnel-borne
38
+ * — stamp `x-forwarded-host` / `x-forwarded-proto` / `x-forwarded-prefix` from
39
+ * the public authority so the workbench builds correct `remoteAuthority` and
40
+ * base-path URLs and never sees a stale upstream port. Response caching headers
41
+ * (`Cache-Control` / `ETag`) are a RESPONSE concern and preserved separately.
42
+ *
43
+ * Pure and exported for unit testing.
44
+ */
45
+ export declare function buildForwardHeaders(incoming: http.IncomingHttpHeaders, port: number, publicAuthority: PublicAuthority | null, connectionToken: string | null): Record<string, string | string[]>;
46
+ /** True if `pathname` targets the IDE forward prefix (`/ide` or `/ide/…`). */
47
+ export declare function matchIdePrefix(pathname: string): boolean;
48
+ /**
49
+ * Forward a plain HTTP request to the local IDE server. Assumes authorization
50
+ * has already passed. Path and query are forwarded unchanged (the workbench
51
+ * runs with `--server-base-path /ide` and selects the workspace from `?folder=`).
52
+ * A dead / unreachable server yields 502, never a crash.
53
+ */
54
+ export declare function forwardIdeHttp(req: http.IncomingMessage, res: http.ServerResponse, log: (level: 'INFO' | 'ERROR' | 'WARN', message: string) => void): Promise<void>;
55
+ /**
56
+ * Forward a WebSocket upgrade to the local IDE server as a raw bidirectional
57
+ * byte pipe. Tower authenticates the upgrade (caller) and then gets out of the
58
+ * way — the workbench speaks VS Code's own remote protocol, which Tower must not
59
+ * parse. Mirrors `tunnel-client.ts handleWebSocketConnect`'s pipe.
60
+ */
61
+ export declare function forwardIdeWebSocket(req: http.IncomingMessage, socket: net.Socket, head: Buffer, log: (level: 'INFO' | 'ERROR' | 'WARN', message: string) => void): Promise<void>;
62
+ //# sourceMappingURL=ide-forward.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ide-forward.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/ide-forward.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAuChC,4FAA4F;AAC5F,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMzG;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,IAAI,CAa3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,eAAe,GAAG,IAAI,EACvC,eAAe,EAAE,MAAM,GAAG,IAAI,GAC7B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CA0BnC;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGxD;AAaD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAC/D,OAAO,CAAC,IAAI,CAAC,CAmCf;AAaD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,MAAM,EAAE,GAAG,CAAC,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAC/D,OAAO,CAAC,IAAI,CAAC,CA2Df"}
@@ -0,0 +1,274 @@
1
+ /**
2
+ * IDE prefix forward (Issue #1668).
3
+ *
4
+ * Tower forwards a fixed `/ide/` prefix to a single local VS Code server-web
5
+ * process — plain HTTP here and, for the workbench's own remote-protocol
6
+ * sockets, a raw bidirectional WebSocket pipe (Tower never parses those frames;
7
+ * they are VS Code's protocol, not Tower's — see `tower-websocket.ts` for the
8
+ * contrast with the PTY terminal bridge).
9
+ *
10
+ * Authorization is decided upstream by `isForwardAuthorized` (the HTTP path also
11
+ * passes the general `isRequestAllowed` choke point first); this module only
12
+ * proxies, after that decision. Header hygiene and `x-forwarded-*` stamping
13
+ * happen here, strictly after auth (Issue #1668 §D2).
14
+ */
15
+ import http from 'node:http';
16
+ import { URL } from 'node:url';
17
+ import { readCloudConfig } from '../lib/cloud-config.js';
18
+ import { TUNNEL_PROXY_HEADER } from '../lib/tunnel-client.js';
19
+ import { TOWER_KEY_HEADER, LEGACY_WEB_KEY_HEADER } from '@cluesmith/codev-types';
20
+ import { IDE_DEFAULT_PREFIX, readIdeConnectionToken } from '../lib/ide-record.js';
21
+ import { getRecordedIdePort, ensureIdeServerLive } from './ide-server.js';
22
+ /** Hop-by-hop headers not forwarded between connections (RFC 7230 §6.1). */
23
+ const HOP_BY_HOP = new Set([
24
+ 'connection',
25
+ 'keep-alive',
26
+ 'proxy-authenticate',
27
+ 'proxy-authorization',
28
+ 'te',
29
+ 'trailers',
30
+ 'transfer-encoding',
31
+ 'upgrade',
32
+ ]);
33
+ /**
34
+ * Headers stripped before forwarding to the IDE server:
35
+ * - `x-forwarded-port` / `x-original-host`: a leaked upstream port makes the
36
+ * browser dial the wrong WebSocket port, breaking the live session.
37
+ * - `codev-tower-key` / `codev-web-key`: Tower's auth credential — the IDE
38
+ * server must never see it.
39
+ * - `x-codev-tunnel-proxy`: Tower's tunnel marker — never forwarded, so it
40
+ * cannot leak or be trusted downstream (marker integrity, #1674).
41
+ * - `host`: replaced with the loopback target authority below.
42
+ */
43
+ const STRIP_BEFORE_FORWARD = new Set([
44
+ 'x-forwarded-port',
45
+ 'x-original-host',
46
+ TOWER_KEY_HEADER,
47
+ LEGACY_WEB_KEY_HEADER,
48
+ TUNNEL_PROXY_HEADER,
49
+ 'host',
50
+ ]);
51
+ /** True if this request arrived over the tunnel (stamped by `TunnelClient`, #1588). */
52
+ function isTunnelBorne(headers) {
53
+ return headers[TUNNEL_PROXY_HEADER] === '1';
54
+ }
55
+ /** The connection-token cookie name the VS Code server reads. */
56
+ const IDE_TOKEN_COOKIE = 'vscode-tkn';
57
+ /**
58
+ * Merge the IDE connection token into a Cookie header value as `vscode-tkn=…`,
59
+ * preserving every existing cookie and never clobbering an already-present
60
+ * `vscode-tkn` (Issue #1668). Exported for testing.
61
+ */
62
+ export function mergeConnectionTokenCookie(existing, token) {
63
+ const current = Array.isArray(existing) ? existing.join('; ') : existing ?? '';
64
+ // Never clobber: if a vscode-tkn is already present, leave the header as-is.
65
+ if (new RegExp(`(^|;\\s*)${IDE_TOKEN_COOKIE}=`).test(current))
66
+ return current;
67
+ const pair = `${IDE_TOKEN_COOKIE}=${token}`;
68
+ return current ? `${current}; ${pair}` : pair;
69
+ }
70
+ /**
71
+ * Derive the tunnel's public authority from the cloud config, matching the
72
+ * `accessUrl` Tower advertises (`tower-tunnel.ts`: `${server_url}/t/${tower_name}/`).
73
+ * Returns null when the tower is not cloud-registered.
74
+ */
75
+ export function getPublicAuthority() {
76
+ const cfg = readCloudConfig();
77
+ if (!cfg)
78
+ return null;
79
+ let host;
80
+ let proto;
81
+ try {
82
+ const url = new URL(cfg.server_url);
83
+ host = url.host;
84
+ proto = url.protocol === 'http:' ? 'http' : 'https';
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ return { host, proto, prefix: `/t/${cfg.tower_name}${IDE_DEFAULT_PREFIX.replace(/\/$/, '')}` };
90
+ }
91
+ /**
92
+ * Build the header set forwarded to the IDE server: drop hop-by-hop and
93
+ * Tower-internal headers, set the loopback `Host`, and — only when tunnel-borne
94
+ * — stamp `x-forwarded-host` / `x-forwarded-proto` / `x-forwarded-prefix` from
95
+ * the public authority so the workbench builds correct `remoteAuthority` and
96
+ * base-path URLs and never sees a stale upstream port. Response caching headers
97
+ * (`Cache-Control` / `ETag`) are a RESPONSE concern and preserved separately.
98
+ *
99
+ * Pure and exported for unit testing.
100
+ */
101
+ export function buildForwardHeaders(incoming, port, publicAuthority, connectionToken) {
102
+ const out = {};
103
+ for (const [key, value] of Object.entries(incoming)) {
104
+ if (value === undefined)
105
+ continue;
106
+ const lower = key.toLowerCase();
107
+ if (HOP_BY_HOP.has(lower))
108
+ continue;
109
+ if (STRIP_BEFORE_FORWARD.has(lower))
110
+ continue;
111
+ out[key] = value;
112
+ }
113
+ out['host'] = `127.0.0.1:${port}`;
114
+ if (isTunnelBorne(incoming) && publicAuthority) {
115
+ out['x-forwarded-host'] = publicAuthority.host;
116
+ out['x-forwarded-proto'] = publicAuthority.proto;
117
+ out['x-forwarded-prefix'] = publicAuthority.prefix;
118
+ }
119
+ // Present the IDE server's connection token as a cookie. The server is
120
+ // spawned with `--connection-token-file` and 403s without it, and it cannot
121
+ // ride the browser's cookie jar through the relay (Set-Cookie Path=/ide never
122
+ // matches /t/<tower>/ide/), so Tower injects it here — merging with, never
123
+ // clobbering, any existing Cookie header (Issue #1668).
124
+ if (connectionToken) {
125
+ out['cookie'] = mergeConnectionTokenCookie(incoming.cookie, connectionToken);
126
+ }
127
+ return out;
128
+ }
129
+ /** True if `pathname` targets the IDE forward prefix (`/ide` or `/ide/…`). */
130
+ export function matchIdePrefix(pathname) {
131
+ const bare = IDE_DEFAULT_PREFIX.replace(/\/$/, ''); // "/ide"
132
+ return pathname === bare || pathname.startsWith(IDE_DEFAULT_PREFIX);
133
+ }
134
+ /** Copy response headers, dropping hop-by-hop; preserves `Cache-Control` / `ETag`. */
135
+ function copyResponseHeaders(headers) {
136
+ const out = {};
137
+ for (const [key, value] of Object.entries(headers)) {
138
+ if (value === undefined)
139
+ continue;
140
+ if (HOP_BY_HOP.has(key.toLowerCase()))
141
+ continue;
142
+ out[key] = value;
143
+ }
144
+ return out;
145
+ }
146
+ /**
147
+ * Forward a plain HTTP request to the local IDE server. Assumes authorization
148
+ * has already passed. Path and query are forwarded unchanged (the workbench
149
+ * runs with `--server-base-path /ide` and selects the workspace from `?folder=`).
150
+ * A dead / unreachable server yields 502, never a crash.
151
+ */
152
+ export async function forwardIdeHttp(req, res, log) {
153
+ // Hot path: resolve the target port from the record — no port scan. A dead
154
+ // server fails the connect below, which triggers a respawn (Issue #1668).
155
+ const port = getRecordedIdePort();
156
+ if (port === null) {
157
+ res.writeHead(502, { 'Content-Type': 'application/json' });
158
+ res.end(JSON.stringify({ error: 'IDE server unavailable' }));
159
+ return;
160
+ }
161
+ const headers = buildForwardHeaders(req.headers, port, getPublicAuthority(), readIdeConnectionToken());
162
+ const proxyReq = http.request({ hostname: '127.0.0.1', port, path: req.url || '/', method: req.method, headers }, (proxyRes) => {
163
+ res.writeHead(proxyRes.statusCode ?? 502, copyResponseHeaders(proxyRes.headers));
164
+ proxyRes.pipe(res);
165
+ proxyRes.on('error', () => {
166
+ if (!res.writableEnded)
167
+ res.end();
168
+ });
169
+ });
170
+ proxyReq.on('error', (err) => {
171
+ log('WARN', `IDE HTTP forward error: ${err.message}`);
172
+ onUpstreamConnectError(err, log);
173
+ if (!res.headersSent) {
174
+ res.writeHead(502, { 'Content-Type': 'application/json' });
175
+ res.end(JSON.stringify({ error: 'Bad Gateway: IDE server unavailable' }));
176
+ }
177
+ else if (!res.writableEnded) {
178
+ res.end();
179
+ }
180
+ });
181
+ req.pipe(proxyReq);
182
+ req.on('error', () => proxyReq.destroy());
183
+ }
184
+ /**
185
+ * On a refused/reset upstream connection the recorded server is down. Kick a
186
+ * respawn off the hot path (fire-and-forget) so the next request lands; this
187
+ * request still 502s. The respawn (cold path) is where the `lsof` probe lives.
188
+ */
189
+ function onUpstreamConnectError(err, log) {
190
+ if (err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET') {
191
+ ensureIdeServerLive(log).catch(() => { });
192
+ }
193
+ }
194
+ /**
195
+ * Forward a WebSocket upgrade to the local IDE server as a raw bidirectional
196
+ * byte pipe. Tower authenticates the upgrade (caller) and then gets out of the
197
+ * way — the workbench speaks VS Code's own remote protocol, which Tower must not
198
+ * parse. Mirrors `tunnel-client.ts handleWebSocketConnect`'s pipe.
199
+ */
200
+ export async function forwardIdeWebSocket(req, socket, head, log) {
201
+ const port = getRecordedIdePort();
202
+ if (port === null) {
203
+ socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n');
204
+ socket.destroy();
205
+ return;
206
+ }
207
+ // Header hygiene drops the hop-by-hop `Connection`/`Upgrade` pair, so re-add
208
+ // them for the upstream upgrade (as `tunnel-client.ts` does). The client's
209
+ // `Sec-WebSocket-Key` is preserved by `buildForwardHeaders` and forwarded as-is
210
+ // so the upstream's `Sec-WebSocket-Accept` — relayed back verbatim — validates
211
+ // against the client's own key.
212
+ const headers = buildForwardHeaders(req.headers, port, getPublicAuthority(), readIdeConnectionToken());
213
+ headers['Connection'] = 'Upgrade';
214
+ headers['Upgrade'] = 'websocket';
215
+ const upstream = http.request({
216
+ hostname: '127.0.0.1',
217
+ port,
218
+ path: req.url || '/',
219
+ method: 'GET',
220
+ headers,
221
+ });
222
+ upstream.on('upgrade', (_upstreamRes, upstreamSocket, upstreamHead) => {
223
+ // Replay the handshake response and any buffered bytes, then pipe both ways.
224
+ socket.write(rebuildUpgradeResponse(_upstreamRes));
225
+ if (upstreamHead.length > 0)
226
+ socket.write(upstreamHead);
227
+ if (head.length > 0)
228
+ upstreamSocket.write(head);
229
+ upstreamSocket.pipe(socket);
230
+ socket.pipe(upstreamSocket);
231
+ const closeBoth = () => {
232
+ if (!socket.destroyed)
233
+ socket.destroy();
234
+ if (!upstreamSocket.destroyed)
235
+ upstreamSocket.destroy();
236
+ };
237
+ socket.on('error', closeBoth);
238
+ upstreamSocket.on('error', closeBoth);
239
+ socket.on('close', closeBoth);
240
+ upstreamSocket.on('close', closeBoth);
241
+ });
242
+ upstream.on('response', (upstreamRes) => {
243
+ // Upstream declined the upgrade (e.g. 404) — relay the status and close.
244
+ socket.write(`HTTP/1.1 ${upstreamRes.statusCode ?? 502} ${upstreamRes.statusMessage ?? ''}\r\n\r\n`);
245
+ socket.destroy();
246
+ });
247
+ upstream.on('error', (err) => {
248
+ log('WARN', `IDE WS forward error: ${err.message}`);
249
+ onUpstreamConnectError(err, log);
250
+ if (!socket.destroyed) {
251
+ socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n');
252
+ socket.destroy();
253
+ }
254
+ });
255
+ upstream.end();
256
+ }
257
+ /** Reconstruct the HTTP/1.1 101 switching-protocols response line + headers. */
258
+ function rebuildUpgradeResponse(res) {
259
+ const statusLine = `HTTP/1.1 ${res.statusCode ?? 101} ${res.statusMessage ?? 'Switching Protocols'}`;
260
+ const lines = [statusLine];
261
+ for (const [key, value] of Object.entries(res.headers)) {
262
+ if (value === undefined)
263
+ continue;
264
+ if (Array.isArray(value)) {
265
+ for (const v of value)
266
+ lines.push(`${key}: ${v}`);
267
+ }
268
+ else {
269
+ lines.push(`${key}: ${value}`);
270
+ }
271
+ }
272
+ return lines.join('\r\n') + '\r\n\r\n';
273
+ }
274
+ //# sourceMappingURL=ide-forward.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ide-forward.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/ide-forward.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE1E,4EAA4E;AAC5E,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,YAAY;IACZ,YAAY;IACZ,oBAAoB;IACpB,qBAAqB;IACrB,IAAI;IACJ,UAAU;IACV,mBAAmB;IACnB,SAAS;CACV,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,kBAAkB;IAClB,iBAAiB;IACjB,gBAAgB;IAChB,qBAAqB;IACrB,mBAAmB;IACnB,MAAM;CACP,CAAC,CAAC;AASH,uFAAuF;AACvF,SAAS,aAAa,CAAC,OAAiC;IACtD,OAAO,OAAO,CAAC,mBAAmB,CAAC,KAAK,GAAG,CAAC;AAC9C,CAAC;AAED,iEAAiE;AACjE,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAEtC;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,QAAuC,EAAE,KAAa;IAC/F,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC;IAC/E,6EAA6E;IAC7E,IAAI,IAAI,MAAM,CAAC,YAAY,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAC9E,MAAM,IAAI,GAAG,GAAG,gBAAgB,IAAI,KAAK,EAAE,CAAC;IAC5C,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,IAAY,CAAC;IACjB,IAAI,KAAa,CAAC;IAClB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACpC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QAChB,KAAK,GAAG,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,UAAU,GAAG,kBAAkB,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;AACjG,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAkC,EAClC,IAAY,EACZ,eAAuC,EACvC,eAA8B;IAE9B,MAAM,GAAG,GAAsC,EAAE,CAAC;IAClD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAChC,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,SAAS;QACpC,IAAI,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,SAAS;QAC9C,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACnB,CAAC;IACD,GAAG,CAAC,MAAM,CAAC,GAAG,aAAa,IAAI,EAAE,CAAC;IAElC,IAAI,aAAa,CAAC,QAAQ,CAAC,IAAI,eAAe,EAAE,CAAC;QAC/C,GAAG,CAAC,kBAAkB,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC;QAC/C,GAAG,CAAC,mBAAmB,CAAC,GAAG,eAAe,CAAC,KAAK,CAAC;QACjD,GAAG,CAAC,oBAAoB,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC;IACrD,CAAC;IAED,uEAAuE;IACvE,4EAA4E;IAC5E,8EAA8E;IAC9E,2EAA2E;IAC3E,wDAAwD;IACxD,IAAI,eAAe,EAAE,CAAC;QACpB,GAAG,CAAC,QAAQ,CAAC,GAAG,0BAA0B,CAAC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS;IAC7D,OAAO,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;AACtE,CAAC;AAED,sFAAsF;AACtF,SAAS,mBAAmB,CAAC,OAAiC;IAC5D,MAAM,GAAG,GAA+C,EAAE,CAAC;IAC3D,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YAAE,SAAS;QAChD,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAyB,EACzB,GAAwB,EACxB,GAAgE;IAEhE,2EAA2E;IAC3E,0EAA0E;IAC1E,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;IAClC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,CAAC,CAAC;QAC7D,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,mBAAmB,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,EAAE,sBAAsB,EAAE,CAAC,CAAC;IACvG,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAC3B,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,EAClF,CAAC,QAAQ,EAAE,EAAE;QACX,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,IAAI,GAAG,EAAE,mBAAmB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QACjF,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACxB,IAAI,CAAC,GAAG,CAAC,aAAa;gBAAE,GAAG,CAAC,GAAG,EAAE,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CACF,CAAC;IAEF,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QAC3B,GAAG,CAAC,MAAM,EAAE,2BAA2B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACtD,sBAAsB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACrB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAC,CAAC,CAAC;QAC5E,CAAC;aAAM,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;YAC9B,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,SAAS,sBAAsB,CAAC,GAA0B,EAAE,GAAgE;IAC1H,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC7D,mBAAmB,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,GAAyB,EACzB,MAAkB,EAClB,IAAY,EACZ,GAAgE;IAEhE,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;IAClC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACjD,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,OAAO;IACT,CAAC;IAED,6EAA6E;IAC7E,2EAA2E;IAC3E,gFAAgF;IAChF,+EAA+E;IAC/E,gCAAgC;IAChC,MAAM,OAAO,GAAG,mBAAmB,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,EAAE,sBAAsB,EAAE,CAAC,CAAC;IACvG,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IAClC,OAAO,CAAC,SAAS,CAAC,GAAG,WAAW,CAAC;IACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,QAAQ,EAAE,WAAW;QACrB,IAAI;QACJ,IAAI,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG;QACpB,MAAM,EAAE,KAAK;QACb,OAAO;KACR,CAAC,CAAC;IAEH,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,EAAE;QACpE,6EAA6E;QAC7E,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,YAAY,CAAC,CAAC,CAAC;QACnD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEhD,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5B,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAE5B,MAAM,SAAS,GAAG,GAAG,EAAE;YACrB,IAAI,CAAC,MAAM,CAAC,SAAS;gBAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YACxC,IAAI,CAAC,cAAc,CAAC,SAAS;gBAAE,cAAc,CAAC,OAAO,EAAE,CAAC;QAC1D,CAAC,CAAC;QACF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC9B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QACtC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC9B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,WAAW,EAAE,EAAE;QACtC,yEAAyE;QACzE,MAAM,CAAC,KAAK,CAAC,YAAY,WAAW,CAAC,UAAU,IAAI,GAAG,IAAI,WAAW,CAAC,aAAa,IAAI,EAAE,UAAU,CAAC,CAAC;QACrG,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;QAClD,GAAG,CAAC,MAAM,EAAE,yBAAyB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACpD,sBAAsB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACtB,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;YACjD,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,GAAG,EAAE,CAAC;AACjB,CAAC;AAED,gFAAgF;AAChF,SAAS,sBAAsB,CAAC,GAAyB;IACvD,MAAM,UAAU,GAAG,YAAY,GAAG,CAAC,UAAU,IAAI,GAAG,IAAI,GAAG,CAAC,aAAa,IAAI,qBAAqB,EAAE,CAAC;IACrG,MAAM,KAAK,GAAG,CAAC,UAAU,CAAC,CAAC;IAC3B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,CAAC,IAAI,KAAK;gBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;QACpD,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC;AACzC,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * IDE server lifecycle, Tower-side (Issue #1668).
3
+ *
4
+ * Tower is the spawner: `afx ide start` drives this through the local-only
5
+ * `/api/ide` endpoint, and the same spawn path is reused by boot reconcile and
6
+ * by on-demand respawn when a `/ide/` request arrives and the server is down.
7
+ * Runtime state lives in the `~/.agent-farm/ide-server.json` record (Issue
8
+ * #1668 §D3) — no config file, no `global.db` change. Liveness is discovered by
9
+ * port (`getProcessesOnPort`), the same way Tower finds its own process.
10
+ */
11
+ import http from 'node:http';
12
+ import { type IdeServerRecord } from '../lib/ide-record.js';
13
+ type LogFn = (level: 'INFO' | 'ERROR' | 'WARN', message: string) => void;
14
+ /**
15
+ * The recorded IDE server port, or null if none — the forward's hot-path target
16
+ * resolution. A plain record read, no port scan (Issue #1668): a dead server
17
+ * fails the proxy connect immediately, which is the only liveness check the
18
+ * forward needs.
19
+ */
20
+ export declare function getRecordedIdePort(): number | null;
21
+ export interface SpawnIdeOptions {
22
+ serverPath: string;
23
+ port?: number;
24
+ defaultFolder?: string;
25
+ }
26
+ /**
27
+ * Spawn the IDE server detached (so it survives a Tower restart, like a
28
+ * shellper) and write the runtime record. Resolves to the record once the
29
+ * server is listening, or throws if the binary is missing or never binds.
30
+ */
31
+ export declare function spawnIdeServer(opts: SpawnIdeOptions, log: LogFn): Promise<IdeServerRecord>;
32
+ /**
33
+ * Stop the IDE server on the recorded port: SIGTERM, wait for exit, escalate to
34
+ * SIGKILL for survivors (mirrors `towerStop`, #1668 plan §4), then delete the
35
+ * record. Cold path — `getProcessesOnPort` is fine here.
36
+ */
37
+ export declare function stopIdeServer(log: LogFn): Promise<{
38
+ stopped: boolean;
39
+ port: number | null;
40
+ }>;
41
+ export interface IdeStatus {
42
+ record: IdeServerRecord | null;
43
+ alive: boolean;
44
+ pid: number | null;
45
+ }
46
+ /** Report the IDE server's status from the record plus a live port probe. */
47
+ export declare function ideServerStatus(): IdeStatus;
48
+ /**
49
+ * Ensure the IDE server is live and return its port, or null if it cannot be
50
+ * made live (no record, or a respawn that never bound). A live server is the
51
+ * fast path (no spawn). A recorded-but-dead server is respawned from the
52
+ * recorded `serverPath`; concurrent callers share one in-flight respawn.
53
+ */
54
+ export declare function ensureIdeServerLive(log?: LogFn): Promise<number | null>;
55
+ /**
56
+ * Boot-time reconcile: adopt a live server (no orphan) or respawn a recorded
57
+ * but dead one. Best-effort — never throws into Tower boot.
58
+ */
59
+ export declare function reconcileIdeServer(log: LogFn): Promise<void>;
60
+ /**
61
+ * Local-only `/api/ide` management endpoint driving the lifecycle above:
62
+ * `POST` starts (body `{ serverPath, port?, defaultFolder? }`), `DELETE` stops,
63
+ * `GET` reports status. Reached only post-`isRequestAllowed` (key required) and
64
+ * blocked from the tunnel like `/api/tunnel/*` — it spawns/kills a process.
65
+ */
66
+ export declare function handleIdeApi(req: http.IncomingMessage, res: http.ServerResponse, log: LogFn): Promise<void>;
67
+ export {};
68
+ //# sourceMappingURL=ide-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ide-server.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/ide-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAM7B,OAAO,EAGL,KAAK,eAAe,EAMrB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AA4BzE;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAElD;AAYD,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC,eAAe,CAAC,CA6EhG;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAkClG;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,IAAI,SAAS,CAK3C;AAKD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,GAAE,KAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsBvF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAQlE;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,GAAG,EAAE,KAAK,GACT,OAAO,CAAC,IAAI,CAAC,CA6Cf"}