@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.
- package/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
package/dist/lsp/e2b-bridge.js
CHANGED
|
@@ -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",
|
|
126
|
-
go: "gopls",
|
|
127
|
-
|
|
128
|
-
|
|
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 [
|
package/dist/lsp/e2b-manager.js
CHANGED
|
@@ -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;
|
|
68
|
+
return undefined;
|
|
115
69
|
const port = portForLanguage(language);
|
|
116
|
-
const token = opts?.token ?? randomUUID();
|
|
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" };
|
|
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
|
-
|
|
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),
|
|
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);
|
|
152
|
+
perPort.set(port, newHandle);
|
|
241
153
|
handle = newHandle;
|
|
242
154
|
}
|
|
243
155
|
const started = await handle.ready;
|
package/dist/lsp/lsp-frames.js
CHANGED
|
@@ -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)
|
package/dist/lsp/manager.js
CHANGED
|
@@ -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;
|
|
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);
|
|
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);
|
|
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);
|
|
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
|
}
|