@sema-agent/server 7.43.0 → 7.44.0

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 (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -1,27 +1,4 @@
1
- /**
2
- * The WS↔stdio bridge that runs INSIDE the E2B sandbox + the setup it needs. A language server speaks LSP-JSON-RPC
3
- * over stdio with **Content-Length** framing; our `WsLspTransport` speaks one raw JSON-RPC message per WS frame.
4
- * This bridge (started per language server, exposed via `sandbox.getHost(port)`) translates between the two — it's
5
- * the piece that lets core's stdio-only LSP design work over E2B's HTTP/WS proxy (no stdin on `execStream`).
6
- *
7
- * **Proven end-to-end** (scripts/lsp-live-test.ts): `goToDefinition` resolved to the real definition through
8
- * `wss://…e2b.app`. For production the servers + bridge should be BAKED into an E2B template (the heavy-install
9
- * pitfall); `lspInstallAndStart` is the on-the-fly path for P1 / un-baked templates.
10
- */
11
1
  import { FRAME_DECODER_JS } from "./lsp-frames.js";
12
- /**
13
- * Bridges WS ⇄ a language server's stdio. Server command from `LSP_CMD`, port `LSP_PORT`, and a per-session
14
- * **auth token** `LSP_TOKEN` (the endpoint is a public `wss://<port>-<id>.e2b.app` URL — the runner connects with
15
- * `?token=<secret>`; without it, anyone with the URL could read the workspace via LSP, council #4). Frame parsing
16
- * uses the unit-tested {@link FRAME_DECODER_JS} (council #1/#2/#5).
17
- *
18
- * redesign⑤ (#96 §DESIGN-V2 F4): the WS server now rides an explicit `http.createServer` instead of binding its
19
- * own socket (`new WebSocketServer({host,port})`) — the same port also answers `GET /healthz` (host-side liveness
20
- * probe, §DESIGN-V2 F2/C6) without a second port or dependency. The EADDRINUSE loud-exit handler MOVES with the
21
- * bind onto the http server (`server.on('error', …)`, same `lsp-bridge wss error` text + `exit 1` — test/lsp-e2b.test.ts
22
- * :256-269 pins this exact text/code); `ws` still forwards its underlying server's `'error'` to `wss`, so nothing
23
- * downstream (per-connection handling) changes.
24
- */
25
2
  export const BRIDGE_SOURCE = FRAME_DECODER_JS +
26
3
  String.raw `
27
4
  const { WebSocketServer } = require('ws');
@@ -105,45 +82,27 @@ server.listen(PORT, '0.0.0.0', () => {
105
82
  console.log('lsp-bridge listening', PORT);
106
83
  });
107
84
  `;
108
- /** Bridge dir inside the sandbox(声明提前:tsserver 定值路径引用它)。 */
109
85
  export const BRIDGE_DIR = "/home/user/.lsp-bridge";
110
- /** tls 的 tsserver.js 定值路径(#96 真机门):install 腿保证在场,connect 腿经
111
- * `initializationOptions.tsserver.path` 告知 tls(v4 起无 CLI 旗可用)。 */
112
86
  export const TSSERVER_JS_PATH = `${BRIDGE_DIR}/node_modules/typescript/lib/tsserver.js`;
113
- /** Language server command per languageId. P1 = typescript/javascript; dart/kotlin land with their template servers.
114
- * `--stdio` only where the server needs it to pick stdio over its other transports (the node-based servers);
115
- * gopls/jdtls/kotlin-language-server/sourcekit-lsp/`dart language-server` speak LSP over stdio by default.
116
- * #96 真机门发现(E2B 真沙箱逐帧取证,test/lsp-e2b-bridge-live.test.ts):typescript-language-server
117
- * **不自动发现全局 typescript**(只认 workspace 内 node_modules/typescript 或显式 tsserver 路径),
118
- * `npm i -g … typescript` 装成之后 initialize 仍答 "Could not find a valid TypeScript installation";
119
- * 且 v4 起 `--tsserver-path` CLI 旗已移除(未知参数=进程退 1,第二轮真机实测)——路径只能走 LSP
120
- * `initializationOptions.tsserver.path`(TSSERVER_JS_PATH,connectE2bLspBridge 对 ts/js 传入),
121
- * typescript 本体由 lspInstallCommand 落进 BRIDGE_DIR 定值路径(烤好模板文件在场则秒过)。 */
122
87
  export const SERVER_CMD = {
123
88
  typescript: "typescript-language-server --stdio",
124
89
  javascript: "typescript-language-server --stdio",
125
- python: "pyright-langserver --stdio", // dev-base bakes pyright
126
- go: "gopls", // gopls serves LSP over stdio by default; baked in dev-base (no on-the-fly path — needs the go toolchain)
127
- // dev-mobile baked servers (all stdio; no on-the-fly path — they need their SDKs):
128
- dart: "dart language-server", // built into the Dart SDK (comes with Flutter)
129
- kotlin: "kotlin-language-server", // fwcd candidate; a future bake-off may switch this to JetBrains kotlin-lsp
90
+ python: "pyright-langserver --stdio",
91
+ go: "gopls",
92
+ dart: "dart language-server",
93
+ kotlin: "kotlin-language-server",
130
94
  java: "jdtls",
131
95
  swift: "sourcekit-lsp",
132
96
  };
133
- /** npm packages a non-baked sandbox needs (on-the-fly install). Languages absent here (go) REQUIRE a baked template. */
134
97
  export const SERVER_NPM = {
135
98
  typescript: "typescript-language-server typescript",
136
99
  javascript: "typescript-language-server typescript",
137
100
  python: "pyright",
138
101
  };
139
- /** One bridge process per language → distinct ports.(BRIDGE_DIR 声明已提前至 SERVER_CMD 上方。) */
140
102
  export function portForLanguage(language) {
141
- // stable per-language port; extend the table as templates add servers (dev-mobile: dart/kotlin/java/swift)
142
103
  const ports = { typescript: 8123, javascript: 8123, dart: 8124, kotlin: 8125, python: 8126, go: 8127, java: 8128, swift: 8129 };
143
104
  return ports[language] ?? 8123;
144
105
  }
145
- /** FOREGROUND install (run via `env.exec` — must finish before the start). A baked template makes both checks
146
- * skip instantly; a non-baked sandbox npm-installs the server + ws here (slow once per sandbox). */
147
106
  export function lspInstallCommand(language) {
148
107
  const cmd = SERVER_CMD[language];
149
108
  const npm = SERVER_NPM[language];
@@ -151,40 +110,21 @@ export function lspInstallCommand(language) {
151
110
  `mkdir -p ${BRIDGE_DIR}`,
152
111
  `[ -f ${BRIDGE_DIR}/node_modules/ws/package.json ] || (cd ${BRIDGE_DIR} && echo '{}' > package.json && npm i ws >/tmp/lsp-setup.log 2>&1)`,
153
112
  npm ? `command -v ${cmd.split(" ")[0]} >/dev/null || npm i -g ${npm} >>/tmp/lsp-setup.log 2>&1` : "true",
154
- // #96 真机门:tls 只认 workspace typescript 或显式 tsserver 路径(全局装了也不认)——把 typescript
155
- // 落到 BRIDGE_DIR 定值路径,与 TSSERVER_JS_PATH(initializationOptions 腿)同源。烤好模板文件在场则秒过。
156
- // 🔴 钉 @5:裸 `typescript` 现装到 7.x(Go 原生港,lib/ 无 tsserver.js,真机实测)——tls 绑死
157
- // TS5 的 JS tsserver。TS7 自带原生 LSP 是未来的换代方向(届时连 tls 一起退役),不是本车。
158
113
  npm?.includes("typescript")
159
114
  ? `[ -f ${TSSERVER_JS_PATH} ] || (cd ${BRIDGE_DIR} && npm i typescript@5 >>/tmp/lsp-setup.log 2>&1)`
160
115
  : "true",
161
116
  ].join(" && ");
162
117
  }
163
- /** Start the bridge — run as a TRUE background command (`env.startBackground`), NOT `&`-backgrounded inside a
164
- * foreground run: E2B reaps the foreground process group on completion, killing an in-shell `&`/nohup child.
165
- * So node is the FOREGROUND here and E2B keeps the background command alive. */
166
118
  export function lspStartCommand(language, port, token) {
167
119
  return `cd ${BRIDGE_DIR} && LSP_PORT=${port} LSP_TOKEN='${token}' LSP_CMD='${SERVER_CMD[language]}' node bridge.cjs`;
168
120
  }
169
- /** Where the on-the-fly `npm i` output from {@link lspInstallCommand} lands — previously had zero readers
170
- * (§DESIGN-V2 F7 bonus fix); an install failure now tails this for the `lastError`/event snippet. */
171
121
  export const LSP_SETUP_LOG = "/tmp/lsp-setup.log";
172
- /** BRIDGE_DIR-relative log the bridge process itself appends to (fatal errors, child exits) — §DESIGN-V2 C4. */
173
122
  export function bridgeLogFile() {
174
123
  return `${BRIDGE_DIR}/bridge.log`;
175
124
  }
176
- /** Per-port pidfile the bridge writes ONLY after a confirmed bind (BRIDGE_SOURCE's `listen` callback) — see its
177
- * header comment for why a losing (EADDRINUSE) second bridge must never clobber this. */
178
125
  export function bridgePidFile(port) {
179
126
  return `${BRIDGE_DIR}/bridge-${port}.pid`;
180
127
  }
181
- /** §DESIGN-V2 F3: verify a prior bridge generation ACTUALLY stopped before the host mints a replacement.
182
- * Exit 0 = confirmed dead (or the pidfile never existed — nothing was ever listening) → safe to start a new
183
- * generation on this port. Exit 1 = still alive after the bound wait — the caller MUST NOT start a new bridge:
184
- * an unconfirmed kill risks EADDRINUSE (the new generation's fresh token would then be permanently rejected by
185
- * the still-listening old process, the token check in BRIDGE_SOURCE) — better to stay `dead` than double-burn
186
- * the restart budget on a doomed attempt. POSIX `sh` only (no `seq`/bashisms — sandboxes vary in shell).
187
- * Bound: 20 × 0.1s = 2s. */
188
128
  export function terminateBridgeCommand(port) {
189
129
  const pidfile = bridgePidFile(port);
190
130
  return [
@@ -1,20 +1,3 @@
1
- /**
2
- * E2B LSP wiring — `createE2bLspManager()` builds the ONE stateless manager (core 1.86.2 seam: core passes
3
- * the task's env into `sessionFor` at the tool mount point, so there is NO sessionId→env registry and no per-task
4
- * manager construction; the old registry/monkey-patch wiring is gone).
5
- *
6
- * `openE2bLspTransport` does the per-sandbox start: write bridge.cjs from BRIDGE_SOURCE (single source of truth —
7
- * works on baked + un-baked templates) → foreground install (baked template skips instantly) → start the bridge
8
- * as a TRUE background command (a shell `&` child gets reaped when E2B completes the foreground command) → WS
9
- * connect via `wss://getHost(port)?token=…` (per-session auth — the URL is public). It is a single-shot legacy
10
- * primitive (still used standalone by the k8s wiring test) — NOT part of the bridge resource model below; it
11
- * carries no generation/dead tracking of its own.
12
- *
13
- * redesign⑤ (#96 §DESIGN-V2 A/F3/F6): `createE2bLspManager`'s `transportFactory` owns the actual bridge
14
- * RESOURCE model — one `E2bBridgeHandle` per env+port, surviving across heals, with a bounded restart budget
15
- * (`MAX_BRIDGE_GENERATIONS`) so a bridge that crashes (or a first boot that never completes) degrades that
16
- * language gracefully instead of burning sandbox round-trips forever.
17
- */
18
1
  import { randomUUID } from "node:crypto";
19
2
  import { connectWsLspTransport } from "./ws-transport.js";
20
3
  import { E2bLspManager } from "./manager.js";
@@ -22,11 +5,6 @@ import { BRIDGE_DIR, BRIDGE_SOURCE, TSSERVER_JS_PATH, LSP_SETUP_LOG, bridgeLogFi
22
5
  function describeErr(e) {
23
6
  return e instanceof Error ? e.message : String(e);
24
7
  }
25
- /** Best-effort `tail -n <lines> <path>`, capped — used for both the install-failure snippet (§DESIGN-V2 F7,
26
- * `LSP_SETUP_LOG` — previously had zero readers) and the connect-exhaustion postmortem (§DESIGN-V2 C5/F9,
27
- * `bridge.log`). Returns `undefined` ONLY when the exec itself failed (never observed the log) — an empty
28
- * log is still a meaningful (if boring) tail, distinct from "couldn't look." Caps to the LAST `capBytes` —
29
- * a giant `Content-Length` framing dump truncated from the FRONT still keeps the most recent (relevant) lines. */
30
8
  async function tailLog(env, path, lines = 40, capBytes = 4096) {
31
9
  const r = await env.exec(`tail -n ${lines} ${path} 2>/dev/null`).catch(() => undefined);
32
10
  if (!r?.ok)
@@ -34,15 +12,9 @@ async function tailLog(env, path, lines = 40, capBytes = 4096) {
34
12
  const out = r.value?.stdout ?? "";
35
13
  return out.length > capBytes ? out.slice(-capBytes) : out;
36
14
  }
37
- /** §DESIGN-V2 F11: the probe's URL scheme is the WS scheme's HTTP-family counterpart (`wss`'s TLS proxy →
38
- * `https`; the k8s pod network's plain `ws` → plain `http` — same "TLS or not" the WS connect itself uses). */
39
15
  function httpSchemeFor(scheme) {
40
16
  return scheme === "wss" ? "https" : "http";
41
17
  }
42
- /** Real `GET /healthz?token=…` probe (§DESIGN-V2 F2/C6) — 1s bound, `fetch` (no new dependency). Any failure
43
- * (no host, network error, non-2xx, timeout) reads as "not confirmed alive"; the caller's decision (skip the
44
- * blind retry loop and go straight to the restart arm) only fires once `everConnected` is true (F6) — a cold
45
- * first boot never reaches this probe. */
46
18
  async function defaultProbeHealthz(env, port, token, scheme) {
47
19
  const host = await env.getHost(port);
48
20
  if (!host)
@@ -55,15 +27,6 @@ async function defaultProbeHealthz(env, port, token, scheme) {
55
27
  return false;
56
28
  }
57
29
  }
58
- /** Write bridge.cjs + (foreground) install the language server, then start the bridge as a TRUE background
59
- * command. A single-shot primitive: it does NOT check whether a bridge is already listening on `port` — a
60
- * caller that can have two languages share one port (`createE2bLspManager`, since `portForLanguage` maps
61
- * typescript AND javascript onto 8123 off the same `SERVER_CMD`) MUST call this at most ONCE per env+port
62
- * itself. A second `node bridge.cjs` on an already-bound port surfaces as an EADDRINUSE `wss` 'error'
63
- * (e2b-bridge.ts D6 hardening keeps that from crashing the sandbox, but the second attempt still fails and
64
- * wastes a sandbox round-trip for nothing — HRD-LSP-4). None of the three steps' failures are swallowed
65
- * (§DESIGN-V2 F6) — a silently-lost write means `node bridge.cjs` fails at runtime with no signal until
66
- * every connect attempt exhausts; reporting it here is strictly faster AND gives a precise reason. */
67
30
  async function startE2bBridge(language, env, port, token) {
68
31
  const wrote = await env.writeFile(`${BRIDGE_DIR}/bridge.cjs`, BRIDGE_SOURCE).catch((e) => ({ ok: false, error: e }));
69
32
  if (!wrote.ok) {
@@ -83,17 +46,11 @@ async function startE2bBridge(language, env, port, token) {
83
46
  }
84
47
  return { ok: true };
85
48
  }
86
- /** Connect (with retry) to an already-started bridge. Each call opens its OWN WS connection — the bridge spawns
87
- * a FRESH child language-server process per connection (e2b-bridge.ts `wss.on('connection', …)`), so two
88
- * languages sharing one bridge port each still get an isolated language-server instance. */
89
49
  async function connectE2bLspBridge(env, port, token, opts, language) {
90
50
  const connect = opts?.connect ?? connectWsLspTransport;
91
51
  const retry = opts?.retry ?? { attempts: 6, delayMs: 1500 };
92
52
  const root = env.workspaceHandle().mountPath;
93
- // #96 真机门:tls(v4 无 CLI 旗)只能经 initializationOptions 拿 tsserver 路径;install 腿已把
94
- // typescript 落到 TSSERVER_JS_PATH 定值路径。非 tls 语言不发键(LSP 规范下多余键也会被忽略,但缺席=零风险)。
95
53
  const initOptions = language === "typescript" || language === "javascript" ? { tsserver: { path: TSSERVER_JS_PATH } } : undefined;
96
- // the bridge starts as a background command → retry the WS connect (with the auth token) while it comes up
97
54
  const scheme = opts?.scheme ?? "wss";
98
55
  for (let attempt = 0; attempt < retry.attempts; attempt++) {
99
56
  const host = await env.getHost(port);
@@ -106,52 +63,29 @@ async function connectE2bLspBridge(env, port, token, opts, language) {
106
63
  }
107
64
  return undefined;
108
65
  }
109
- /** Start (+ install on a non-baked template) the language server + bridge inside THIS env's sandbox, then connect.
110
- * Every call starts (or re-starts) the bridge unconditionally — the one production caller is
111
- * `createE2bLspManager`, which de-dupes per env+port before ever reaching here (see its doc). */
112
66
  export async function openE2bLspTransport(language, env, opts) {
113
67
  if (!SERVER_CMD[language])
114
- return undefined; // no server for this language → graceful degrade
68
+ return undefined;
115
69
  const port = portForLanguage(language);
116
- const token = opts?.token ?? randomUUID(); // per-session bridge auth — the getHost URL is public (council #4)
70
+ const token = opts?.token ?? randomUUID();
117
71
  const started = await startE2bBridge(language, env, port, token);
118
72
  if (!started.ok)
119
73
  return undefined;
120
74
  return connectE2bLspBridge(env, port, token, opts, language);
121
75
  }
122
- /** A crash-loop must degrade the language, not retry the sandbox forever. 3 covers "an occasional OOM kill";
123
- * a bridge that dies again immediately after 3 fresh starts is a standing failure, not a blip. */
124
76
  export const MAX_BRIDGE_GENERATIONS = 3;
125
- /** The single shared `LspServerManager` for the deployment (`RunnerDeps.lspManager`). */
126
77
  export function createE2bLspManager(opts) {
127
- // One bridge PROCESS + token per env+PORT — not per env+language. `portForLanguage` maps typescript and
128
- // javascript onto the SAME port (they share `SERVER_CMD`): keying by language minted two auth tokens for the
129
- // one listening bridge (HRD-LSP-4) and re-invoked `startBackground` on an already-bound port on every open.
130
- // WeakMap → a destroyed task's bridge bookkeeping is collectible with its env.
131
78
  const bridges = new WeakMap();
132
79
  const scheme = opts?.scheme ?? "wss";
133
80
  const provider = opts?.provider ?? (scheme === "ws" ? "k8s" : "e2b");
134
81
  const log = opts?.log ?? (() => { });
135
82
  const metrics = opts?.metrics;
136
83
  const probe = opts?.probe ?? defaultProbeHealthz;
137
- /** The model-facing word for this handle's CURRENT death — `deathReason` unless the restart budget is
138
- * exhausted, in which case "further restarts won't help" supersedes whatever specifically killed the last
139
- * generation. */
140
84
  function reasonFor(handle) {
141
85
  if (handle.generation >= MAX_BRIDGE_GENERATIONS)
142
86
  return "bridge-restart-budget-exhausted";
143
87
  return handle.deathReason ?? "bridge-connect-exhausted";
144
88
  }
145
- /** The ONE place a handle transitions into `dead` — fires the `lsp_bridge_dead_total{reason}` metric +
146
- * `lsp_bridge_dead` event exactly once per transition (§DESIGN-V2 E), using the ALREADY-budget-aware
147
- * reported reason (not the raw `deathReason`) so the metric bucket for a generation that just hit the cap
148
- * reads as "budget exhausted," not as whatever technical failure happened to be the final straw.
149
- * review finding ③ (independent re-review): idempotent — two concurrent callers CAN both conclude the
150
- * SAME still-"ready" handle is dead (e.g. two languages sharing a port each running their own §DESIGN-V2
151
- * F2 healthz probe before either has written `state="dead"`); without this guard the metric/event fired
152
- * once per CALLER instead of once per TRANSITION. A handle already `dead` keeps its ORIGINAL
153
- * deathReason/lastError (the first caller to observe the death is definitionally the one whose diagnostic
154
- * is accurate — a second, redundant observation shouldn't overwrite it). */
155
89
  function markDead(handle, reason, detail) {
156
90
  if (handle.state === "dead")
157
91
  return reasonFor(handle);
@@ -163,9 +97,6 @@ export function createE2bLspManager(opts) {
163
97
  log("lsp_bridge_dead", { port: handle.port, generation: handle.generation, reason: reported, detail });
164
98
  return reported;
165
99
  }
166
- /** §DESIGN-V2 C5/F9: best-effort postmortem — tail the bridge's OWN log after a connect exhausts (or the
167
- * probe declares it dead) and before giving up on this generation. Silent on failure (getting the tail is
168
- * itself best-effort; it must never become a SECOND failure mode). */
169
100
  async function postmortem(env, port) {
170
101
  const tail = await tailLog(env, bridgeLogFile());
171
102
  if (tail !== undefined)
@@ -177,9 +108,7 @@ export function createE2bLspManager(opts) {
177
108
  hasServerCommand: (language) => Boolean(SERVER_CMD[language]),
178
109
  transportFactory: async (language, env) => {
179
110
  if (!SERVER_CMD[language])
180
- return { ok: false, reason: "no-server-command" }; // mirrors openE2bLspTransport's
181
- // own guard; MUST run before touching `bridges` — `portForLanguage` falls back to port 8123 for an
182
- // unmapped language, which would otherwise silently borrow typescript's live bridge.
111
+ return { ok: false, reason: "no-server-command" };
183
112
  const port = portForLanguage(language);
184
113
  let perPort = bridges.get(env);
185
114
  if (!perPort) {
@@ -190,19 +119,7 @@ export function createE2bLspManager(opts) {
190
119
  if (!handle || handle.state === "dead") {
191
120
  if (handle && handle.generation >= MAX_BRIDGE_GENERATIONS)
192
121
  return { ok: false, reason: reasonFor(handle) };
193
- // review finding ① (independent re-review): the termination-verification (§DESIGN-V2 F3) used to sit
194
- // in an `await` HERE, between reading `perPort` above and writing it below — that broke the
195
- // run-to-completion dedupe guarantee two languages sharing a port rely on (portForLanguage maps
196
- // typescript/javascript onto the SAME port/SERVER_CMD): both could see the same dead handle, both
197
- // race past the await, and both mint a COMPETING replacement generation (each calling
198
- // startBackground) instead of joining one shared restart. The mint itself — capturing `generation`+
199
- // `token` and registering `newHandle` into `perPort` — MUST stay synchronous relative to the
200
- // `perPort.get(port)` read above (true again now: "no await above" below). The termination check
201
- // moves INSIDE the `ready` chain instead: mint synchronously, verify asynchronously. A SECOND
202
- // language arriving while this is in flight now sees `state==="starting"` (not "dead"), skips this
203
- // whole branch, and joins the SAME `ready` — exactly the pre-existing dedupe every other caller of
204
- // `bridges`/`perPort` already relies on.
205
- const priorHandle = handle; // the dying generation (if any) — captured for the async verification below
122
+ const priorHandle = handle;
206
123
  const generation = handle ? handle.generation + 1 : 1;
207
124
  const token = opts?.token ?? randomUUID();
208
125
  const newHandle = {
@@ -212,15 +129,10 @@ export function createE2bLspManager(opts) {
212
129
  generation,
213
130
  state: "starting",
214
131
  everConnected: false,
215
- ready: Promise.resolve(false), // placeholder — overwritten synchronously below, before any await
132
+ ready: Promise.resolve(false),
216
133
  };
217
134
  newHandle.ready = (async () => {
218
135
  if (priorHandle) {
219
- // §DESIGN-V2 F3: verify the OLD generation actually stopped before this one starts — an
220
- // unconfirmed kill risks EADDRINUSE permanently rejecting THIS generation's fresh token (the old
221
- // process is still the one listening). This generation is already minted+registered by the time
222
- // this runs (the dedupe above), so an unconfirmed termination marks IT dead directly rather than
223
- // leaving the prior (already-replaced) handle in the map.
224
136
  const released = await env.exec(terminateBridgeCommand(port)).catch(() => undefined);
225
137
  if (!released?.ok || released.value?.exitCode !== 0) {
226
138
  markDead(newHandle, "bridge-start-failed", "previous generation could not be confirmed stopped — held back from starting to avoid a doomed EADDRINUSE race");
@@ -237,7 +149,7 @@ export function createE2bLspManager(opts) {
237
149
  log("lsp_bridge_start", { port, generation });
238
150
  return true;
239
151
  })();
240
- perPort.set(port, newHandle); // synchronous — no await above (run-to-completion dedupe guard RESTORED)
152
+ perPort.set(port, newHandle);
241
153
  handle = newHandle;
242
154
  }
243
155
  const started = await handle.ready;
@@ -1,15 +1,3 @@
1
- /**
2
- * The LSP stdio Content-Length frame decoder, as JS SOURCE — embedded verbatim into `BRIDGE_SOURCE` (which runs
3
- * inside the E2B sandbox, can't import TS) AND unit-tested here (test/lsp-frames.test.ts evals this same string).
4
- * So the in-sandbox parser is actually exercised (council #5: the untestable raw-string parser was the risk).
5
- *
6
- * Guards: a frame claiming a non-finite / negative / over-cap length is skipped instead of looping or waiting
7
- * forever (council #1 defense); the accumulator is dropped if it exceeds the cap with no complete frame (council
8
- * #2 — a server that never sends `\r\n\r\n`, or a huge Content-Length, can't grow the buffer without bound).
9
- * The runaway cap carries a `HEADER_SLACK` (core 1.196 #6 twin): a VALID frame whose PAYLOAD passes the
10
- * `len <= maxBytes` check but whose `Content-Length: <n>\r\n\r\n` header pushes header+payload just over `maxBytes`
11
- * must NOT be wiped mid-arrival (that silently dropped an in-window legal reply) — so cap at `maxBytes + 128`.
12
- */
13
1
  export const FRAME_DECODER_JS = String.raw `
14
2
  function makeFrameDecoder(maxBytes) {
15
3
  var HEADER_SLACK = 128; // room for "Content-Length: <n>\r\n\r\n" ON TOP of a max-size payload (core 1.196 #6 twin)
@@ -1,18 +1,5 @@
1
- /**
2
- * `E2bLspManager` — our `LspServerManager` (core mounts the `lsp` tool over it). STATELESS since core 1.86.2:
3
- * core passes the task's own ExecutionEnv into `sessionFor(filePath, signal, env)` (prepare-task
4
- * has the per-task env in scope), so ONE manager serves all tasks; no sessionId→env registry, no per-task
5
- * construction. Sessions are cached per env+language (a language server is heavy — keep it warm per sandbox)
6
- * in a WeakMap, so a destroyed task's entries become collectible (its bridge dies with the sandbox).
7
- *
8
- * Any miss (no env / unknown language / env without the E2B surface / failed open) → `undefined`, which makes
9
- * core's tool degrade gracefully to grep/read_file. redesign⑤ (#96 §DESIGN-V2 B/F5/F10): the miss now also comes
10
- * with a reason (`unavailableReason`, the core 1.86.2+ seam's optional method) — a fixed, model-facing word list
11
- * (§DESIGN-V2 F7: "reason 词表=模型可见面", short + stable — changing a word IS a behavior change).
12
- */
13
1
  import path from "node:path";
14
2
  import { TransportLspSession, LspDiagnosticsRegistry, settleOnAbort } from "@sema-agent/core";
15
- /** Duck-type check: is this ExecutionEnv an E2B adapter with the LSP surface? */
16
3
  export function isLspCapable(env) {
17
4
  const e = env;
18
5
  return typeof e.getHost === "function" && typeof e.startBackground === "function" && typeof e.workspaceHandle === "function";
@@ -35,32 +22,14 @@ const DEFAULT_EXT_TO_LANG = {
35
22
  ".swift": "swift",
36
23
  ".rs": "rust",
37
24
  };
38
- /**
39
- * §DESIGN-V2 F1/F8/F9 —— 本文件曾**手抄**一份 core 的 `settleOnAbort`(注释自陈 core「un-exported」,并写下
40
- * 「一旦 core 导出即 find-and-delete」的计划)。A-033.1(#256)执行了那句话:core 现已从包根导出它
41
- * (`index.d.ts:27`,来自 `core/lsp.js`),手抄件遂删除,本文件的两处调用直接消费 core 的实现。
42
- *
43
- * 它的语义(读点在 `open` 的两处调用):给**这一个**调用方一个自己的 settle —— 调用方的 `signal` 让它拿到
44
- * `onAbort()` 的返回,而**共享作业本身不受打扰**(别的调用方可能还等着它)。铁律仍在:绝不能把 signal 穿进
45
- * 作业里(F9:共享的 open/heal 链不接受 per-caller signal,见 `open` 的文档)。
46
- */
47
25
  export class E2bLspManager {
48
- /** Per-sandbox session cache: env (weak) → language → the (self-contained, see `startJob`) shared job. */
49
26
  perEnv = new WeakMap();
50
- /** §DESIGN-V2 B/F10: env (weak) → language → the reason the MOST RECENT open/heal attempt failed. Cleared
51
- * on a successful open (F10) so a stale reason never outlives the failure that produced it. */
52
27
  lastFailure = new WeakMap();
53
28
  factory;
54
29
  extToLang;
55
30
  log;
56
31
  hasServerCommand;
57
32
  metrics;
58
- /** design/121 (core 1.220, board [S] ①): the workspace diagnostics registry — WORKSPACE-scoped (survives
59
- * heal/evict; diagnostics belong to files, not servers), drained by the Runner at turn boundaries. Every
60
- * session feeds it from `textDocument/publishDiagnostics` (the WS transport now dispatches notifications).
61
- * ⚠️ ONE registry across sandboxes on this manager instance (it is per-deployment) — same posture as
62
- * NodeLspManager's workspace scoping; a task only sees diagnostics for files IT touched (delivered-set +
63
- * fileEdited keying is per-uri, and sandbox workspace paths are per-task-unique). */
64
33
  diagnostics = new LspDiagnosticsRegistry();
65
34
  constructor(opts) {
66
35
  this.factory = opts.transportFactory;
@@ -69,13 +38,6 @@ export class E2bLspManager {
69
38
  this.hasServerCommand = opts.hasServerCommand ?? (() => true);
70
39
  this.metrics = opts.metrics;
71
40
  }
72
- /**
73
- * §DESIGN-V2 B: the core 1.86.2+ seam's optional method — WHY the most recent `sessionFor` miss for this
74
- * file happened. Three reasons are STATIC (re-derived here fresh, every call, from `filePath`+`env` alone —
75
- * never stored, since they don't need an open attempt to be knowable); the rest are DYNAMIC, read back from
76
- * `lastFailure`. `undefined` = this exact (env, language) was never attempted (NOT "available" — a
77
- * best-effort postmortem surface, per the seam's own contract).
78
- */
79
41
  unavailableReason(filePath, env) {
80
42
  if (!env || !isLspCapable(env))
81
43
  return "env-not-lsp-capable";
@@ -88,7 +50,7 @@ export class E2bLspManager {
88
50
  }
89
51
  async sessionFor(filePath, signal, env) {
90
52
  if (!env || !isLspCapable(env))
91
- return undefined; // no env passed (core <1.86.2) or not the E2B adapter → degrade
53
+ return undefined;
92
54
  const language = this.extToLang[path.extname(filePath).toLowerCase()];
93
55
  if (!language)
94
56
  return undefined;
@@ -98,67 +60,27 @@ export class E2bLspManager {
98
60
  this.perEnv.set(env, sessions);
99
61
  }
100
62
  let pending = sessions.get(language) ?? this.startJob(env, sessions, language);
101
- // §DESIGN-V2 F9: the shared job never sees a per-caller `signal` — it is a CONTRACT, not an accident, that
102
- // the chain below has nothing to wire one into. `settleOnAbort` only bounds how long THIS caller waits for
103
- // the (unaffected, still-running-for-everyone-else) shared job — Q16. Wiring a per-caller signal into the
104
- // shared chain would let one caller's cancellation abort a session other callers are still waiting on.
105
63
  let session = await settleOnAbort(pending, signal, () => undefined);
106
64
  if (session?.closed) {
107
- // Transport died between tool calls (the E2B proxy idle-kills silent WS connections — live-drill
108
- // finding 2026-06-10). Heal: evict + reopen ONCE — the bridge spawns a fresh server per connection.
109
- // Carry the dead session's opened files over: a fresh server with no project config only "sees" its
110
- // open files, so without the replay cross-file ops silently degrade to single-file (live-repro).
111
- // Guard against clobbering a concurrent heal: a racer that lost this race adopts whichever job IS
112
- // current in `sessions` (the winner's). `startJob` itself does the `sessions.set` synchronously (no
113
- // `await` between creating the job and registering it) — the run-to-completion dedupe guard `startJob`
114
- // documents.
115
65
  const carry = session.openedFiles();
116
66
  pending = sessions.get(language) === pending ? this.startJob(env, sessions, language, { carry }) : sessions.get(language);
117
67
  session = await settleOnAbort(pending, signal, () => undefined);
118
68
  }
119
69
  return session;
120
70
  }
121
- /**
122
- * §DESIGN-V2 F1 (aligned with core's `NodeLspManager.open`, node-lsp-manager.js :123-153 — same self-
123
- * contained-job shape, not the same code: that manager also tracks a TTL/LRU cache and per-job
124
- * `SharedAbortScope`, which this one has no equivalent of per F9). The returned job is SELF-completing:
125
- *
126
- * - self-caches: registers itself into `sessions` synchronously BEFORE returning (so a concurrent caller
127
- * in the SAME microtask sees it — no dedupe race);
128
- * - self-cleans: on failure, removes ITSELF from `sessions` (never a newer entry a racer already installed
129
- * — the `sessions.get(language) === job` check) so the next request retries instead of caching a miss;
130
- * - self-warms: a heal job (`heal` set) `warmOpen`s the carried files as part of settling, before anyone
131
- * can observe it as "done" — no caller-side extra step, no window where a racer sees a warm-less session;
132
- * - self-closes an orphan: if a CONCURRENT heal already replaced this job in `sessions` before it settled
133
- * (this job "lost" the race), the just-opened transport is closed instead of leaked with no owner.
134
- *
135
- * Callers must NEVER wrap `settleOnAbort` around a naive `await` of a SHARED promise and then branch the
136
- * logging/cache-eviction on THAT wrapped value — a caller whose OWN signal aborted would then look
137
- * indistinguishable from the job itself failing, misfiring `lsp_session_*_failed` and evicting a still-live
138
- * in-flight entry out from under a concurrent caller (§DESIGN-V2 F1). `sessionFor` only ever wraps the
139
- * RETURN boundary; every cache/log decision above happens INSIDE the job, driven by the job's own outcome.
140
- */
141
71
  startJob(env, sessions, language, heal) {
142
72
  const job = this.open(language, env)
143
73
  .then(async (session) => {
144
74
  if (!session) {
145
75
  if (sessions.get(language) === job)
146
- sessions.delete(language); // don't cache a miss — retry next request
76
+ sessions.delete(language);
147
77
  this.log(heal ? "lsp_session_heal_failed" : "lsp_session_open_failed", { language, ...(heal ? { carried: heal.carry.length } : {}) });
148
78
  this.metrics?.inc(heal ? "lsp_session_heal_total" : "lsp_session_open_total", heal ? { outcome: "failed" } : { language, outcome: "failed" });
149
79
  return undefined;
150
80
  }
151
- // review finding ② (independent re-review): `warmOpen` (unlike `syncOne`'s OWN internal `readText`
152
- // guard) can still reject — e.g. `TransportLspSession.syncOne`'s unwrapped `this.transport.notify(...)`
153
- // call (core lsp-session.js) — and this is the SHARED job every caller for this env+language awaits;
154
- // an uncaught rejection here would otherwise propagate through `job` itself. Aligned with core's own
155
- // precedent for this exact call (node-lsp-manager.js:137): the carry-over is best-effort — a failed
156
- // resync degrades to "not carried," not an aborted heal.
157
81
  if (heal)
158
82
  await session.warmOpen(heal.carry).catch(() => ({ opened: 0, failed: heal.carry.length }));
159
83
  if (sessions.get(language) !== job) {
160
- // Orphaned: a concurrent heal replaced us in `sessions` before we settled — the winner's session is
161
- // what everyone will use; don't leak this one.
162
84
  await session.close().catch(() => undefined);
163
85
  return undefined;
164
86
  }
@@ -167,10 +89,6 @@ export class E2bLspManager {
167
89
  return session;
168
90
  })
169
91
  .catch((e) => {
170
- // Defense in depth (review finding ②): ANY other unexpected throw in the callback above (not just
171
- // warmOpen) must not leave a PERMANENTLY REJECTED promise cached in `sessions` — the self-clean-on-
172
- // failure logic above only runs on the resolve-undefined path, never on a rejection, so without this
173
- // every subsequent `sessionFor` for this env+language would throw instead of degrading.
174
92
  if (sessions.get(language) === job)
175
93
  sessions.delete(language);
176
94
  this.log(heal ? "lsp_session_heal_failed" : "lsp_session_open_failed", {
@@ -181,14 +99,10 @@ export class E2bLspManager {
181
99
  this.metrics?.inc(heal ? "lsp_session_heal_total" : "lsp_session_open_total", heal ? { outcome: "failed" } : { language, outcome: "failed" });
182
100
  return undefined;
183
101
  });
184
- sessions.set(language, job); // synchronous — no `await` above (run-to-completion dedupe guard)
102
+ sessions.set(language, job);
185
103
  return job;
186
104
  }
187
105
  async open(language, env) {
188
- // "session-open-failed" is the catch-all: the factory threw instead of returning its normal discriminated
189
- // result — everything ELSE (install/start/connect/budget failures) is reported through that result, never
190
- // through a rejection, so reaching this branch itself is already the diagnostic (the factory's own
191
- // implementation is where a specific reason would have been attached).
192
106
  const result = await this.factory(language, env).catch(() => ({ ok: false, reason: "session-open-failed" }));
193
107
  let byLang = this.lastFailure.get(env);
194
108
  if (!result.ok) {
@@ -199,7 +113,7 @@ export class E2bLspManager {
199
113
  byLang.set(language, result.reason);
200
114
  return undefined;
201
115
  }
202
- byLang?.delete(language); // F10: a successful open clears any stale reason from a PRIOR failure
116
+ byLang?.delete(language);
203
117
  const transport = result.transport;
204
118
  const readText = async (fp) => {
205
119
  const r = await env.readTextFile(fp);
@@ -207,12 +121,6 @@ export class E2bLspManager {
207
121
  throw new Error("lsp: read failed");
208
122
  return r.value;
209
123
  };
210
- // core 1.190+ `TransportLspSession` (byte-equivalent to the former local RemoteLspSession — service originally
211
- // ported it there; core deduplicated it back, single-source). `undefined` warmup ⇒ core's default {windowMs:10_000,
212
- // retryMs:1_000}, identical to the old 4-arg call (equivalence-verified). 6th arg (design/121): the
213
- // workspace diagnostics registry — the session subscribes publishDiagnostics via transport.onNotification
214
- // (version-staleness guarded core-side); absent onNotification would be a silent no-op, but WsLspTransport
215
- // now dispatches.
216
124
  return new TransportLspSession(transport, language, readText, this.log, undefined, this.diagnostics);
217
125
  }
218
126
  }