@sema-agent/server 4.2.0 → 5.0.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 +5 -3
- package/dist/approval-hmac.js +1 -1
- package/dist/approval.d.ts +5 -37
- package/dist/approval.js +12 -112
- package/dist/auth-bridge.d.ts +0 -1
- package/dist/auth-bridge.js +0 -1
- package/dist/auth-keys.d.ts +1 -1
- package/dist/auth-keys.js +1 -1
- package/dist/bench/s1/run-firm.js +1 -1
- package/dist/boot/config-center.d.ts +1 -1
- package/dist/boot/config-center.js +3 -2
- package/dist/boot/execution-env.js +8 -1
- package/dist/boot/reapers.d.ts +1 -2
- package/dist/boot/reapers.js +4 -5
- package/dist/boot/resolve-spec.d.ts +1 -3
- package/dist/boot/resolve-spec.js +21 -35
- package/dist/boot/stores.js +1 -1
- package/dist/brain.js +3 -1
- package/dist/capabilities/center-prompts.d.ts +2 -3
- package/dist/capabilities/center-prompts.js +2 -3
- package/dist/capabilities/sandbox-file-send.d.ts +0 -2
- package/dist/capabilities/sandbox-file-send.js +2 -5
- package/dist/capabilities/scenarios.d.ts +0 -9
- package/dist/capabilities/scenarios.js +1 -20
- package/dist/capabilities/send-user-file-tool.js +3 -3
- package/dist/config-center/apply-effective.js +8 -8
- package/dist/config-center/types.d.ts +10 -3
- package/dist/config-provider.js +6 -11
- package/dist/config-types.d.ts +12 -26
- package/dist/config.js +95 -68
- package/dist/fleet/fleet-bus.js +25 -33
- package/dist/fleet-client.js +1 -1
- package/dist/http/active-run-conflict.d.ts +2 -0
- package/dist/http/active-run-conflict.js +1 -0
- package/dist/http/routes/approvals-assistant.d.ts +2 -1
- package/dist/http/routes/approvals-assistant.js +10 -96
- package/dist/http/routes/capabilities.js +6 -5
- package/dist/http/routes/fleet.js +4 -1
- package/dist/http/routes/images.js +10 -4
- package/dist/http/routes/runs.js +34 -49
- package/dist/http/routes/session-sync.d.ts +1 -1
- package/dist/http/routes/session-sync.js +2 -1
- package/dist/http/routes/tasks.js +7 -7
- package/dist/http/routes/trace-usage.js +1 -1
- package/dist/http/server.d.ts +18 -7
- package/dist/http/server.js +25 -14
- package/dist/http/verify-rounds.d.ts +2 -2
- package/dist/http/wire-types.d.ts +1 -1
- package/dist/images/bake-validate.d.ts +0 -22
- package/dist/images/bake-validate.js +0 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -5
- package/dist/lsp/e2b-bridge.d.ts +36 -3
- package/dist/lsp/e2b-bridge.js +94 -7
- package/dist/lsp/e2b-manager.d.ts +64 -1
- package/dist/lsp/e2b-manager.js +197 -28
- package/dist/lsp/manager.d.ts +71 -4
- package/dist/lsp/manager.js +149 -29
- package/dist/lsp/ws-transport.d.ts +1 -1
- package/dist/lsp/ws-transport.js +4 -1
- package/dist/main.js +5 -9
- package/dist/memory-scope.js +2 -2
- package/dist/observability/metrics.js +6 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +6 -4
- package/dist/orchestration/workflow-completion-inbox.js +6 -4
- package/dist/plugins/breaker-state-sql.d.ts +2 -3
- package/dist/plugins/image-index-sql.d.ts +0 -1
- package/dist/plugins/image-index-sql.js +0 -1
- package/dist/plugins/local-checkpoint-store.d.ts +1 -1
- package/dist/plugins/local-checkpoint-store.js +1 -1
- package/dist/plugins/memory-engine-pg.js +2 -1
- package/dist/plugins/memory-engine-tidb.js +1 -1
- package/dist/plugins/memory-engine-vector-util.d.ts +2 -3
- package/dist/plugins/memory-engine-vector-util.js +2 -3
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-session-policy-store.d.ts +1 -1
- package/dist/plugins/memory-session-policy-store.js +1 -1
- package/dist/plugins/memory-sync-store-pg.js +1 -1
- package/dist/plugins/pg-pool.js +8 -10
- package/dist/plugins/pg-rate-limiter.d.ts +1 -4
- package/dist/plugins/pg-rate-limiter.js +0 -1
- package/dist/plugins/roster-store-sql.d.ts +1 -3
- package/dist/plugins/roster-store-sql.js +3 -5
- package/dist/plugins/store-backend.d.ts +7 -17
- package/dist/plugins/store-backend.js +14 -30
- package/dist/plugins/tidb-pool.js +4 -28
- package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
- package/dist/plugins/workflow-journal-store-sql.js +1 -1
- package/dist/principal-jwt.js +2 -2
- package/dist/prompts-domain-validate.d.ts +4 -3
- package/dist/prompts-domain-validate.js +7 -8
- package/dist/router/route-orchestration.js +3 -3
- package/dist/run-local.js +5 -4
- package/dist/runs.js +8 -6
- package/dist/runtime-caps-resolver.d.ts +3 -2
- package/dist/runtime-caps-resolver.js +4 -4
- package/dist/runtime-governance.d.ts +4 -7
- package/dist/runtime-governance.js +12 -17
- package/dist/sealed-key.d.ts +1 -1
- package/dist/sealed-key.js +1 -1
- package/dist/security.d.ts +0 -5
- package/dist/security.js +8 -12
- package/dist/session-sync.d.ts +0 -2
- package/dist/session-sync.js +10 -17
- package/dist/spec-fields.js +6 -6
- package/dist/task-settings.js +7 -7
- package/dist/tool-approval.d.ts +3 -3
- package/dist/tool-approval.js +10 -10
- package/dist/trace/artifacts.d.ts +11 -0
- package/dist/trace/artifacts.js +8 -19
- package/dist/trace/ledger-sink.js +7 -1
- package/dist/trace/project.d.ts +17 -0
- package/dist/trace/project.js +55 -5
- package/package.json +2 -2
- package/dist/capabilities/oa-tools.d.ts +0 -15
- package/dist/capabilities/oa-tools.js +0 -54
- package/dist/plugins/approval-store-sql.d.ts +0 -116
- package/dist/plugins/approval-store-sql.js +0 -150
- package/dist/plugins/file-workflow-journal-store.d.ts +0 -12
- package/dist/plugins/file-workflow-journal-store.js +0 -12
- package/dist/plugins/pg-approval-store.d.ts +0 -9
- package/dist/plugins/pg-approval-store.js +0 -9
- package/dist/plugins/pg-breaker-state.d.ts +0 -8
- package/dist/plugins/pg-breaker-state.js +0 -8
- package/dist/plugins/pg-checkpoint-store.d.ts +0 -10
- package/dist/plugins/pg-checkpoint-store.js +0 -10
- package/dist/plugins/pg-file-snapshot-store.d.ts +0 -8
- package/dist/plugins/pg-file-snapshot-store.js +0 -8
- package/dist/plugins/pg-image-bake.d.ts +0 -12
- package/dist/plugins/pg-image-bake.js +0 -11
- package/dist/plugins/pg-image-index.d.ts +0 -12
- package/dist/plugins/pg-image-index.js +0 -11
- package/dist/plugins/pg-outcome-ledger.d.ts +0 -12
- package/dist/plugins/pg-outcome-ledger.js +0 -11
- package/dist/plugins/pg-resume-anchor-store.d.ts +0 -7
- package/dist/plugins/pg-resume-anchor-store.js +0 -7
- package/dist/plugins/pg-run-store.d.ts +0 -9
- package/dist/plugins/pg-run-store.js +0 -9
- package/dist/plugins/pg-session-policy-store.d.ts +0 -7
- package/dist/plugins/pg-session-policy-store.js +0 -7
- package/dist/plugins/pg-session-store.d.ts +0 -12
- package/dist/plugins/pg-session-store.js +0 -12
- package/dist/plugins/pg-tool-result-store.d.ts +0 -9
- package/dist/plugins/pg-tool-result-store.js +0 -9
- package/dist/plugins/pg-workflow-journal-store.d.ts +0 -9
- package/dist/plugins/pg-workflow-journal-store.js +0 -9
- package/dist/plugins/pg-workflow-run-store.d.ts +0 -9
- package/dist/plugins/pg-workflow-run-store.js +0 -9
- package/dist/plugins/tidb-approval-store.d.ts +0 -8
- package/dist/plugins/tidb-approval-store.js +0 -8
- package/dist/plugins/tidb-breaker-state.d.ts +0 -7
- package/dist/plugins/tidb-breaker-state.js +0 -7
- package/dist/plugins/tidb-checkpoint-store.d.ts +0 -9
- package/dist/plugins/tidb-checkpoint-store.js +0 -9
- package/dist/plugins/tidb-file-snapshot-store.d.ts +0 -8
- package/dist/plugins/tidb-file-snapshot-store.js +0 -8
- package/dist/plugins/tidb-image-bake.d.ts +0 -12
- package/dist/plugins/tidb-image-bake.js +0 -11
- package/dist/plugins/tidb-image-index.d.ts +0 -12
- package/dist/plugins/tidb-image-index.js +0 -11
- package/dist/plugins/tidb-outcome-ledger.d.ts +0 -12
- package/dist/plugins/tidb-outcome-ledger.js +0 -12
- package/dist/plugins/tidb-resume-anchor-store.d.ts +0 -7
- package/dist/plugins/tidb-resume-anchor-store.js +0 -7
- package/dist/plugins/tidb-run-store.d.ts +0 -10
- package/dist/plugins/tidb-run-store.js +0 -9
- package/dist/plugins/tidb-session-policy-store.d.ts +0 -7
- package/dist/plugins/tidb-session-policy-store.js +0 -7
- package/dist/plugins/tidb-tool-result-store.d.ts +0 -8
- package/dist/plugins/tidb-tool-result-store.js +0 -10
- package/dist/plugins/tidb-workflow-journal-store.d.ts +0 -9
- package/dist/plugins/tidb-workflow-journal-store.js +0 -9
- package/dist/plugins/tidb-workflow-run-store.d.ts +0 -10
- package/dist/plugins/tidb-workflow-run-store.js +0 -10
- package/dist/plugins/workflow-journal-limits.d.ts +0 -12
- package/dist/plugins/workflow-journal-limits.js +0 -12
package/dist/http/server.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { TaskRequestBody } from "./wire-types.js";
|
|
|
5
5
|
import type { ServiceConfig } from "../config-types.js";
|
|
6
6
|
import { type RestartSignal, type SessionMirrorRuling } from "../config-center/facade.js";
|
|
7
7
|
import { type OwnerAwareSessionStore } from "../security.js";
|
|
8
|
-
import type { RunStore,
|
|
8
|
+
import type { RunStore, ImageIndex, ImageBake, CheckpointStoreFull, ResumeAnchorStore, ApprovalExemptionStore, ServiceSessionPolicyStore, ServiceFileSnapshotStore, StoreBackend } from "../plugins/store-backend.js";
|
|
9
9
|
import { type MemorySyncRequest, type MemorySyncResponse } from "../memory-sync.js";
|
|
10
10
|
import type { TaskAttachmentStore } from "../plugins/task-attachment-store.js";
|
|
11
11
|
import type { LeaderEndpoint } from "../leader/endpoint.js";
|
|
@@ -25,8 +25,6 @@ import type { QuotaTracker } from "../observability/cost-quota.js";
|
|
|
25
25
|
import type { ModelUsageTracker, PromptManifestTracker } from "../budget.js";
|
|
26
26
|
import { scopedIdempotencyKey } from "./idempotency.js";
|
|
27
27
|
export { scopedIdempotencyKey };
|
|
28
|
-
import { clampVerifyRounds } from "./verify-rounds.js";
|
|
29
|
-
export { clampVerifyRounds };
|
|
30
28
|
import { streamApprovals, isQuestionAnswer } from "./routes/approvals-assistant.js";
|
|
31
29
|
export { streamApprovals, isQuestionAnswer };
|
|
32
30
|
import { cascadeConfig } from "./run-meta.js";
|
|
@@ -88,8 +86,6 @@ export interface ServiceStoreDeps {
|
|
|
88
86
|
/** E18 resume-at: the eventId→entryId anchor map (tidb/pg/local). Present whenever a backend is — resolveSpec
|
|
89
87
|
* resolves body.resumeAt(eventId)→entryId, the run lifecycle captures per-turn anchors, E21 purges them. */
|
|
90
88
|
resumeAnchorStore?: ResumeAnchorStore;
|
|
91
|
-
/** Human-in-the-loop approval store (F4 poll gate). Enables /v1/approvals when present. */
|
|
92
|
-
approvalStore?: ApprovalStore;
|
|
93
89
|
/** Per-session approval exemption store — enables decide body `remember:"session"` + the
|
|
94
90
|
* exemptions list/revoke surfaces on /v1/approvals. The ask-gate probe is wired separately (main.ts). */
|
|
95
91
|
approvalExemptionStore?: ApprovalExemptionStore;
|
|
@@ -439,7 +435,6 @@ export type FlatServiceDeps = ServiceCoreDeps & Partial<ServiceStoreDeps & Servi
|
|
|
439
435
|
export type ServiceDeps = FlatServiceDeps & ServiceDepGroups;
|
|
440
436
|
/** 分组入参 → 平铺视图(createHttpServer 的第一件事)。一组都没给 ⇒ 原样返回(存量调用零开销、零形变)。 */
|
|
441
437
|
export declare function flattenServiceDeps(deps: ServiceDeps): FlatServiceDeps;
|
|
442
|
-
export type { TaskRequestBody } from "./wire-types.js";
|
|
443
438
|
/** Personalization caps: server-pinned hard limits — a UI may be stricter, never looser. */
|
|
444
439
|
export declare const MAX_USER_SKILLS = 10;
|
|
445
440
|
/** = core `SKILL_CONTENT_MAX_CHARS`(1MB 加载门,core 1.293 起 invoke 全文零截断、超 1MB 整拒;常量在
|
|
@@ -466,6 +461,22 @@ export declare function validateUserSkills(skills: unknown): string | null;
|
|
|
466
461
|
export declare function createHttpServer(rawDeps: ServiceDeps): http.Server & {
|
|
467
462
|
denyExpiredApprovals: (now: number) => Promise<void>;
|
|
468
463
|
};
|
|
469
|
-
/**
|
|
464
|
+
/** SSE: replay durable events after Last-Event-ID, then tail until the run is terminal (or stale). */
|
|
465
|
+
/**
|
|
466
|
+
* The differences a concrete log (task_run | image_bake) feeds the ONE resumable SSE reader (P2.8). Everything
|
|
467
|
+
* the reader does — Last-Event-ID/`?from=` resume, the 416 retention boundary, the per-poll concurrent
|
|
468
|
+
* status+events read, the terminal re-fetch (the terminal event lands in the gap before the status flips), the
|
|
469
|
+
* stale fallback, the 15-min cap, the 15s idle heartbeat — is provider-agnostic and lives in `streamSseLog`.
|
|
470
|
+
* 🔴 The task_run provider MUST keep the existing wire bytes EXACTLY (center's relay + 730+ tests depend on it).
|
|
471
|
+
*/
|
|
472
|
+
/** POST endpoints that trigger BILLABLE work — the fail-closed auth guard must cover ALL of them (council: the
|
|
473
|
+
* guard's inline list had drifted from the handlers and missed `/v1/approvals/:id/decide`, which resumes a run
|
|
474
|
+
* via resumeCheckpoint/store.decide → paid tokens). Keep this in sync when adding a billable POST route. */
|
|
475
|
+
export declare function isBillableSubmitPath(url: string): boolean;
|
|
476
|
+
/** Stable, low-cardinality route label for metrics/logs (ids collapsed to `:id`).
|
|
477
|
+
* [#104] 字面量**先于**模式:此前模式先查,五条精确路由被形状桶吞掉(`/v1/approvals/stream`
|
|
478
|
+
* 落 ":id"、`/v1/images/{bakes,select,register}` 落 ":profile"、`/v1/images/bakes/claim` 落
|
|
479
|
+
* "bakes/:id")——长连接 stream 的 duration 混进 :id 桶正是 fleet/stream 案的同族错桶病。
|
|
480
|
+
* 精确匹配恒比形状匹配更对,序修零反例(billable-route-declaration 门逐样本钉)。 */
|
|
470
481
|
export declare function routeLabel(_method: string, url: string): string;
|
|
471
482
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/http/server.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import { once } from "node:events";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, CheckpointError, HAND_TOOL_EFFECTS,
|
|
4
|
+
import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, CheckpointError, HAND_TOOL_EFFECTS, defaultTaskRegistry, validatePendingSteer, subscribeWorkflow } from "@sema-agent/core"; // canonicalToolName = core single-source (1.162; replaced the transitional service mirror)
|
|
5
5
|
import { decideParkedAgent, findParkedAgentForCheckpoint } from "../parked-decide.js";
|
|
6
6
|
import { matchCatalogModel } from "../model-select.js";
|
|
7
7
|
import {} from "../config-center/facade.js";
|
|
8
|
-
import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isUuidV7,
|
|
8
|
+
import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isUuidV7, isDestructiveSessionWrite, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
|
|
9
|
+
import { verifyDirectDoorProof } from "../principal-jwt.js";
|
|
9
10
|
import {} from "../memory-sync.js";
|
|
10
11
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../task-settings.js";
|
|
11
12
|
import { parseHooksConfig } from "../hooks/hook-runner.js";
|
|
12
13
|
import { validateTaskAgents } from "../spec-fields.js";
|
|
13
14
|
import { isValidCwd, MAX_ADDITIONAL_DIRS } from "../task-cwd.js";
|
|
14
15
|
import { runInBackground, evictIfConflict, stripCheckpointToken, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
|
|
15
|
-
import { looksLikeJwt } from "
|
|
16
|
+
import { looksLikeJwt } from "@sema-agent/registry-core/api/auth-bridge";
|
|
16
17
|
import {} from "../orchestration/workflow-agent-steer.js";
|
|
17
18
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
|
|
18
19
|
import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals } from "../fleet/fleet-bus.js";
|
|
19
20
|
import { defaultSubagentTailBus, projectTailFrame } from "../fleet/subagent-tail-bus.js";
|
|
20
21
|
import {} from "../observability/rate-limit.js";
|
|
21
22
|
import { withPrincipal } from "../observability/principal-context.js";
|
|
22
|
-
import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, workspaceChangedEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
23
|
+
import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
23
24
|
import { redactSecrets } from "../trace/redact.js";
|
|
24
25
|
import { IdempotencyCache, scopedIdempotencyKey } from "./idempotency.js";
|
|
25
26
|
export { scopedIdempotencyKey };
|
|
26
27
|
import { streamSseLog } from "./sse-log.js";
|
|
27
28
|
import { clampVerifyRounds, verifyRoundsFromBody } from "./verify-rounds.js"; // design/97 R9: the shape's neutral leaf (route-ctx.ts type-imports VerifyRoundsSpec from here, not from this file)
|
|
28
|
-
export { clampVerifyRounds }; // deep-path test imports of http/server.js's clampVerifyRounds keep working unchanged
|
|
29
29
|
import { handleCapabilities } from "./routes/capabilities.js";
|
|
30
30
|
import { handleObservability } from "./routes/observability.js";
|
|
31
31
|
import { handleMemoryPolicy } from "./routes/memory-policy.js";
|
|
@@ -59,6 +59,8 @@ export function flattenServiceDeps(deps) {
|
|
|
59
59
|
return deps;
|
|
60
60
|
return { ...stores, ...coordinators, ...seams, ...observability, ...governance, ...deployment, ...knobs, ...flat };
|
|
61
61
|
}
|
|
62
|
+
// design/158 A4: TaskRequestBody lives in ./wire-types.ts (a type-only leaf) — import it from there
|
|
63
|
+
// ([2354] 兼容面全清:此处的兼容 re-export 已删;index.ts 公共面直接从 wire-types 转出)。
|
|
62
64
|
/** Personalization caps: server-pinned hard limits — a UI may be stricter, never looser. */
|
|
63
65
|
export const MAX_USER_SKILLS = 10;
|
|
64
66
|
/** = core `SKILL_CONTENT_MAX_CHARS`(1MB 加载门,core 1.293 起 invoke 全文零截断、超 1MB 整拒;常量在
|
|
@@ -1273,11 +1275,11 @@ export function createHttpServer(rawDeps) {
|
|
|
1273
1275
|
// (core `HAND_TOOL_EFFECTS`, not re-exported from the package root → mirrored here). When this deployment
|
|
1274
1276
|
// has remote exec configured the rebuilt task WILL expose them, so union them in; else gating a hand tool
|
|
1275
1277
|
// (the common code-agent case) would always 422 on resume. Keep in sync with core's HAND_TOOL_EFFECTS.
|
|
1276
|
-
//
|
|
1277
|
-
//
|
|
1278
|
-
//
|
|
1278
|
+
// [2380] RB-476(5.0.0):Q6 折叠预检退役——满足性匹配按 RAW 名。pre-floor 旧名 checkpoint
|
|
1279
|
+
// (`bash`/`run_workflow` 等)在此 422 诚实拒,与 core 自己的 resume.tool_unavailable 同方向
|
|
1280
|
+
// ([2364] 令:耐久面响亮牺牲 + P-7 重开,不静默兜)。现役名恒自映射。
|
|
1279
1281
|
const pendingTool = cp.pendingAction.toolName;
|
|
1280
|
-
const pendingToolCanon = pendingTool
|
|
1282
|
+
const pendingToolCanon = pendingTool;
|
|
1281
1283
|
const availableTools = new Set((spec.tools ?? []).map((t) => t.name));
|
|
1282
1284
|
if (deps.config.remoteExec)
|
|
1283
1285
|
for (const name of HAND_TOOL_NAMES)
|
|
@@ -1763,6 +1765,10 @@ export function createHttpServer(rawDeps) {
|
|
|
1763
1765
|
await flush();
|
|
1764
1766
|
await append("steering_injected", steeringInjectedEventData(ev));
|
|
1765
1767
|
break; // [1550]/[1552]: shared whitelist builder, §E1 redact — resume twin of the sync + ledger-sink legs
|
|
1768
|
+
case "compaction_outcome":
|
|
1769
|
+
await flush();
|
|
1770
|
+
await append("compaction_outcome", compactionOutcomeEventData(ev));
|
|
1771
|
+
break; // [2373]A-2/C-2: durable 双腿补齐(此前回放即消失);共享 builder,差集门看守
|
|
1766
1772
|
case "workspace_changed":
|
|
1767
1773
|
await flush();
|
|
1768
1774
|
await append("workspace_changed", workspaceChangedEventData(ev));
|
|
@@ -2427,7 +2433,9 @@ export function createHttpServer(rawDeps) {
|
|
|
2427
2433
|
/** POST endpoints that trigger BILLABLE work — the fail-closed auth guard must cover ALL of them (council: the
|
|
2428
2434
|
* guard's inline list had drifted from the handlers and missed `/v1/approvals/:id/decide`, which resumes a run
|
|
2429
2435
|
* via resumeCheckpoint/store.decide → paid tokens). Keep this in sync when adding a billable POST route. */
|
|
2430
|
-
|
|
2436
|
+
// [#104/#87-A3] exported for the declaration gate (test/billable-route-declaration.test.ts):每条路由
|
|
2437
|
+
// 标签必须显式申明 billable,与本谓词逐样本对账——「Keep this in sync」从注释请求变成机器断言。
|
|
2438
|
+
export function isBillableSubmitPath(url) {
|
|
2431
2439
|
return (url === "/v1/side-query" || // [1469] one-shot brain call — runs the model, so it rides every billable-submit gate
|
|
2432
2440
|
url === "/v1/tasks" ||
|
|
2433
2441
|
url === "/v1/tasks/stream" ||
|
|
@@ -2468,7 +2476,6 @@ const ROUTE_LABEL_PATTERNS = [
|
|
|
2468
2476
|
[/^\/v1\/images\/[^/]+$/, "/v1/images/:profile"],
|
|
2469
2477
|
[/^\/v1\/approvals\/[^/]+\/decide$/, "/v1/approvals/:id/decide"],
|
|
2470
2478
|
[/^\/v1\/approvals\/[^/]+\/exemptions(\/[^/]+)?$/, "/v1/approvals/:id/exemptions"],
|
|
2471
|
-
[/^\/v1\/approvals\/[^/]+$/, "/v1/approvals/:id"],
|
|
2472
2479
|
[/^\/v1\/sessions\/[^/]+\/(wake|fork|init|notify|events|head|mcp|policy|settings)$/, "/v1/sessions/:id/$verb"],
|
|
2473
2480
|
[/^\/v1\/sessions\/[^/]+\/sync\/(manifest|entries|plan)$/, "/v1/sessions/:id/sync/$verb"],
|
|
2474
2481
|
[/^\/v1\/sessions\/[^/]+\/sync\/import(\/[^/]+\/entries)?$/, "/v1/sessions/:id/sync/import"],
|
|
@@ -2503,13 +2510,17 @@ const ROUTE_LABEL_LITERALS = new Set([
|
|
|
2503
2510
|
"/v1/fleet/stream",
|
|
2504
2511
|
"/v1/memory/export", "/v1/sendfile-links",
|
|
2505
2512
|
]);
|
|
2506
|
-
/** Stable, low-cardinality route label for metrics/logs (ids collapsed to `:id`).
|
|
2513
|
+
/** Stable, low-cardinality route label for metrics/logs (ids collapsed to `:id`).
|
|
2514
|
+
* [#104] 字面量**先于**模式:此前模式先查,五条精确路由被形状桶吞掉(`/v1/approvals/stream`
|
|
2515
|
+
* 落 ":id"、`/v1/images/{bakes,select,register}` 落 ":profile"、`/v1/images/bakes/claim` 落
|
|
2516
|
+
* "bakes/:id")——长连接 stream 的 duration 混进 :id 桶正是 fleet/stream 案的同族错桶病。
|
|
2517
|
+
* 精确匹配恒比形状匹配更对,序修零反例(billable-route-declaration 门逐样本钉)。 */
|
|
2507
2518
|
export function routeLabel(_method, url) {
|
|
2519
|
+
if (ROUTE_LABEL_LITERALS.has(url))
|
|
2520
|
+
return url;
|
|
2508
2521
|
for (const [re, label] of ROUTE_LABEL_PATTERNS)
|
|
2509
2522
|
if (re.test(url))
|
|
2510
2523
|
return label;
|
|
2511
|
-
if (ROUTE_LABEL_LITERALS.has(url))
|
|
2512
|
-
return url;
|
|
2513
2524
|
return "other";
|
|
2514
2525
|
}
|
|
2515
2526
|
/** CORS v2: allowlist semantics. A SINGLE configured origin keeps the v1 posture
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* http/server.ts that each re-derived the same "verify:true → clamp rounds + fold cost ceiling" expression from a
|
|
6
6
|
* request body. A leaf (not server.ts itself) so route-ctx.ts — which must never value-import server.ts (that
|
|
7
7
|
* closes the runtime module-load cycle test/module-cycle-gate.test.ts guards) — can `import type` the shape from
|
|
8
|
-
* one place. server.ts imports {@link clampVerifyRounds} from here
|
|
9
|
-
*
|
|
8
|
+
* one place. server.ts imports {@link clampVerifyRounds} from here; tests import it from HERE too
|
|
9
|
+
* ([2354] 兼容面全清:server.ts 的保深路径 re-export 已删).
|
|
10
10
|
*/
|
|
11
11
|
import type { TaskRequestBody } from "./wire-types.js";
|
|
12
12
|
/** Verification-rounds config threaded onto a task's `verify`/resume paths (see {@link verifyRoundsFromBody}). */
|
|
@@ -258,7 +258,7 @@ export interface TaskRequestBody {
|
|
|
258
258
|
enableFork?: boolean;
|
|
259
259
|
[k: string]: unknown;
|
|
260
260
|
}
|
|
261
|
-
/** design/80 D-1: the operator-echoed decision-action binding carried on POST /v1/approvals/:
|
|
261
|
+
/** design/80 D-1: the operator-echoed decision-action binding carried on POST /v1/approvals/:sessionId/decide([2373]C-4:裸 :id 臂已删).
|
|
262
262
|
* All optional for back-compat — a legacy BFF caller omitting them resumes on the current-pending values
|
|
263
263
|
* exactly as before (the pre-D-1 path), tracked by a deprecation counter.
|
|
264
264
|
* design/158 A9: hoisted VERBATIM out of `createHttpServer`'s closure so the approvals/assistant route domain
|
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bake input WHITELIST + vetted-argv builder (IMAGE-API-DESIGN.md §P2.4 — the anti-RCE core).
|
|
3
|
-
*
|
|
4
|
-
* bands/profile/baseRef/bump compose a Dockerfile + a `FROM` + a `docker build` = arbitrary code on the build
|
|
5
|
-
* host. So EVERY field is validated SERVER-SIDE against a CLOSED SET *before* an argv is built; off-list → an
|
|
6
|
-
* error, never coerced. The runner only ever sees the VETTED argv this module emits (never operator free-text)
|
|
7
|
-
* and only runs `build.sh`, which composes Dockerfiles from the in-repo `bands/` via `gen-dockerfile.sh` — there
|
|
8
|
-
* is no path here to inject a raw Dockerfile or a shell command.
|
|
9
|
-
*
|
|
10
|
-
* Set membership is the gate (NOT regex): `^[a-z0-9-]+$` is belt-only — `python; rm -rf /` ∉ the frozen set →
|
|
11
|
-
* rejected, and the argv is a RAW vetted VECTOR the runner array-spawns (each element in its own argv slot, no
|
|
12
|
-
* shell), never concatenated into a command string.
|
|
13
|
-
*
|
|
14
|
-
* The frozen catalogs below MIRROR the recipe source of truth (`gen-dockerfile.sh` `profile_bands()` + the
|
|
15
|
-
* `bands/` dir). A DRIFT test (test/image-bake-validate.test.ts) asserts they still match — a silently-drifted
|
|
16
|
-
* list would either reject a valid profile or admit a removed band. Keep them in lockstep with the recipes.
|
|
17
|
-
*/
|
|
18
|
-
import { SHA256_DIGEST_RE as DIGEST_RE } from "../digest-form.js";
|
|
19
1
|
/**
|
|
20
2
|
* FROZEN profile catalog — mirrored from `e2b-template/dev-sandbox/gen-dockerfile.sh` `profile_bands()`.
|
|
21
3
|
* (db / browser-nodriver are opt-in overlays, NOT in `code-full` — `code-full-db` adds db; per design §4.)
|
|
@@ -33,10 +15,6 @@ export declare const FROZEN_BUMP_TOOLS: readonly string[];
|
|
|
33
15
|
export declare const VER_RE: RegExp;
|
|
34
16
|
/** Belt-only band shape (set-membership is the real gate); a defense-in-depth reject of structurally weird ids. */
|
|
35
17
|
export declare const BAND_SHAPE_RE: RegExp;
|
|
36
|
-
/** A digest must be an immutable sha256 ref (the allow-list check against the index is the real gate, P2.4c).
|
|
37
|
-
* design/158 R12: the shape itself is collected in `digest-form.ts`'s `SHA256_DIGEST_RE`; `DIGEST_RE` stays a
|
|
38
|
-
* re-exported alias (compat — export surface unchanged). */
|
|
39
|
-
export { DIGEST_RE };
|
|
40
18
|
/** The raw, untrusted submit body fields this module validates (operator free-text). */
|
|
41
19
|
export interface BakeSubmit {
|
|
42
20
|
profile?: unknown;
|
|
@@ -90,10 +90,6 @@ const BUMP_TOOL_SET = new Set(FROZEN_BUMP_TOOLS);
|
|
|
90
90
|
export const VER_RE = /^[A-Za-z0-9._-]{1,40}$/;
|
|
91
91
|
/** Belt-only band shape (set-membership is the real gate); a defense-in-depth reject of structurally weird ids. */
|
|
92
92
|
export const BAND_SHAPE_RE = /^[a-z0-9-]+$/;
|
|
93
|
-
/** A digest must be an immutable sha256 ref (the allow-list check against the index is the real gate, P2.4c).
|
|
94
|
-
* design/158 R12: the shape itself is collected in `digest-form.ts`'s `SHA256_DIGEST_RE`; `DIGEST_RE` stays a
|
|
95
|
-
* re-exported alias (compat — export surface unchanged). */
|
|
96
|
-
export { DIGEST_RE };
|
|
97
93
|
function reject(status, error, conflictBands) {
|
|
98
94
|
return conflictBands ? { ok: false, status, error, conflictBands } : { ok: false, status, error };
|
|
99
95
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,6 @@ export { CachingSessionStore, type CachingOptions } from "./plugins/caching-sess
|
|
|
6
6
|
export type { RunRecord, RunEvent } from "./plugins/store-contracts.js";
|
|
7
7
|
export { runInBackground } from "./runs.js";
|
|
8
8
|
export type { VerifyRoundsSpec } from "./http/verify-rounds.js";
|
|
9
|
-
export { type ApprovalRow, type ApprovalStatus } from "./plugins/approval-store-sql.js";
|
|
10
|
-
export { createOaApprovalPolicy, type OaApprovalOptions } from "./approval.js";
|
|
11
9
|
export { createHardenedVmRunner, runInHardenedVm, type HardenedVmLimits, type HardenedMembrane } from "./orchestration/hardened-vm-runner.js";
|
|
12
10
|
export { createWorkerHardenedVmRunner } from "./orchestration/hardened-vm-worker-runner.js";
|
|
13
11
|
export { buildSessionAudit, type SessionAudit } from "./audit.js";
|
|
@@ -19,6 +17,8 @@ export { nowTool } from "./capabilities/builtin-tools.js";
|
|
|
19
17
|
export { createCouncilTool } from "./capabilities/code-review-council.js";
|
|
20
18
|
export { loadSkills, skillsForScenario, type LoadedSkill } from "./capabilities/skills.js";
|
|
21
19
|
export { buildScenarios, selectScenario, type Scenario, type ScenarioBundle, type ScenarioDeps, type ScenarioRequest, } from "./capabilities/scenarios.js";
|
|
22
|
-
export { createAuthorizer, principalFrom,
|
|
23
|
-
export {
|
|
20
|
+
export { createAuthorizer, principalFrom, HttpError, type AuthContext, type OwnerAwareSessionStore, } from "./security.js";
|
|
21
|
+
export { memoryScopeFor, memoryEngineBackendFor } from "./memory-scope.js";
|
|
22
|
+
export type { TaskRequestBody } from "./http/wire-types.js";
|
|
23
|
+
export { createHttpServer, type ServiceDeps, type RequestAuth, type FlatServiceDeps, type ServiceDepGroups, type ServiceCoreDeps, type ServiceStoreDeps, type ServiceCoordinatorDeps, type ServiceSeamDeps, type ServiceObservabilityDeps, type ServiceGovernanceDeps, type ServiceDeploymentDeps, type ServiceKnobDeps, } from "./http/server.js";
|
|
24
24
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -11,10 +11,6 @@ export { createStoreBackend } from "./plugins/store-backend.js";
|
|
|
11
11
|
// docs/schema/baseline-{mysql,pg}.sql(生成基线,机器门逐字节)。类/函数本体仍是内部实现(深径 import)。
|
|
12
12
|
export { CachingSessionStore } from "./plugins/caching-session-store.js";
|
|
13
13
|
export { runInBackground } from "./runs.js";
|
|
14
|
-
// B5-② 补漏:TiDBApprovalStore 是证据包漏数的第 7 个方言导出,同判据同车摘(全生态零消费);
|
|
15
|
-
// 两个中立行类型改从 A12 后的真源(approval-store-sql.ts)导出。
|
|
16
|
-
export {} from "./plugins/approval-store-sql.js";
|
|
17
|
-
export { createOaApprovalPolicy } from "./approval.js";
|
|
18
14
|
export { createHardenedVmRunner, runInHardenedVm } from "./orchestration/hardened-vm-runner.js";
|
|
19
15
|
export { createWorkerHardenedVmRunner } from "./orchestration/hardened-vm-worker-runner.js";
|
|
20
16
|
export { buildSessionAudit } from "./audit.js";
|
|
@@ -26,6 +22,7 @@ export { nowTool } from "./capabilities/builtin-tools.js";
|
|
|
26
22
|
export { createCouncilTool } from "./capabilities/code-review-council.js";
|
|
27
23
|
export { loadSkills, skillsForScenario } from "./capabilities/skills.js";
|
|
28
24
|
export { buildScenarios, selectScenario, } from "./capabilities/scenarios.js";
|
|
29
|
-
export { createAuthorizer, principalFrom,
|
|
25
|
+
export { createAuthorizer, principalFrom, HttpError, } from "./security.js";
|
|
26
|
+
export { memoryScopeFor, memoryEngineBackendFor } from "./memory-scope.js";
|
|
30
27
|
export { createHttpServer, } from "./http/server.js";
|
|
31
28
|
//# sourceMappingURL=index.js.map
|
package/dist/lsp/e2b-bridge.d.ts
CHANGED
|
@@ -3,16 +3,33 @@
|
|
|
3
3
|
* **auth token** `LSP_TOKEN` (the endpoint is a public `wss://<port>-<id>.e2b.app` URL — the runner connects with
|
|
4
4
|
* `?token=<secret>`; without it, anyone with the URL could read the workspace via LSP, council #4). Frame parsing
|
|
5
5
|
* uses the unit-tested {@link FRAME_DECODER_JS} (council #1/#2/#5).
|
|
6
|
+
*
|
|
7
|
+
* redesign⑤ (#96 §DESIGN-V2 F4): the WS server now rides an explicit `http.createServer` instead of binding its
|
|
8
|
+
* own socket (`new WebSocketServer({host,port})`) — the same port also answers `GET /healthz` (host-side liveness
|
|
9
|
+
* probe, §DESIGN-V2 F2/C6) without a second port or dependency. The EADDRINUSE loud-exit handler MOVES with the
|
|
10
|
+
* bind onto the http server (`server.on('error', …)`, same `lsp-bridge wss error` text + `exit 1` — test/lsp-e2b.test.ts
|
|
11
|
+
* :256-269 pins this exact text/code); `ws` still forwards its underlying server's `'error'` to `wss`, so nothing
|
|
12
|
+
* downstream (per-connection handling) changes.
|
|
6
13
|
*/
|
|
7
14
|
export declare const BRIDGE_SOURCE: string;
|
|
15
|
+
/** Bridge dir inside the sandbox(声明提前:tsserver 定值路径引用它)。 */
|
|
16
|
+
export declare const BRIDGE_DIR = "/home/user/.lsp-bridge";
|
|
17
|
+
/** tls 的 tsserver.js 定值路径(#96 真机门):install 腿保证在场,connect 腿经
|
|
18
|
+
* `initializationOptions.tsserver.path` 告知 tls(v4 起无 CLI 旗可用)。 */
|
|
19
|
+
export declare const TSSERVER_JS_PATH = "/home/user/.lsp-bridge/node_modules/typescript/lib/tsserver.js";
|
|
8
20
|
/** Language server command per languageId. P1 = typescript/javascript; dart/kotlin land with their template servers.
|
|
9
21
|
* `--stdio` only where the server needs it to pick stdio over its other transports (the node-based servers);
|
|
10
|
-
* gopls/jdtls/kotlin-language-server/sourcekit-lsp/`dart language-server` speak LSP over stdio by default.
|
|
22
|
+
* gopls/jdtls/kotlin-language-server/sourcekit-lsp/`dart language-server` speak LSP over stdio by default.
|
|
23
|
+
* #96 真机门发现(E2B 真沙箱逐帧取证,test/lsp-e2b-bridge-live.test.ts):typescript-language-server
|
|
24
|
+
* **不自动发现全局 typescript**(只认 workspace 内 node_modules/typescript 或显式 tsserver 路径),
|
|
25
|
+
* `npm i -g … typescript` 装成之后 initialize 仍答 "Could not find a valid TypeScript installation";
|
|
26
|
+
* 且 v4 起 `--tsserver-path` CLI 旗已移除(未知参数=进程退 1,第二轮真机实测)——路径只能走 LSP
|
|
27
|
+
* `initializationOptions.tsserver.path`(TSSERVER_JS_PATH,connectE2bLspBridge 对 ts/js 传入),
|
|
28
|
+
* typescript 本体由 lspInstallCommand 落进 BRIDGE_DIR 定值路径(烤好模板文件在场则秒过)。 */
|
|
11
29
|
export declare const SERVER_CMD: Readonly<Record<string, string>>;
|
|
12
30
|
/** npm packages a non-baked sandbox needs (on-the-fly install). Languages absent here (go) REQUIRE a baked template. */
|
|
13
31
|
export declare const SERVER_NPM: Readonly<Record<string, string>>;
|
|
14
|
-
/**
|
|
15
|
-
export declare const BRIDGE_DIR = "/home/user/.lsp-bridge";
|
|
32
|
+
/** One bridge process per language → distinct ports.(BRIDGE_DIR 声明已提前至 SERVER_CMD 上方。) */
|
|
16
33
|
export declare function portForLanguage(language: string): number;
|
|
17
34
|
/** FOREGROUND install (run via `env.exec` — must finish before the start). A baked template makes both checks
|
|
18
35
|
* skip instantly; a non-baked sandbox npm-installs the server + ws here (slow once per sandbox). */
|
|
@@ -21,4 +38,20 @@ export declare function lspInstallCommand(language: string): string;
|
|
|
21
38
|
* foreground run: E2B reaps the foreground process group on completion, killing an in-shell `&`/nohup child.
|
|
22
39
|
* So node is the FOREGROUND here and E2B keeps the background command alive. */
|
|
23
40
|
export declare function lspStartCommand(language: string, port: number, token: string): string;
|
|
41
|
+
/** Where the on-the-fly `npm i` output from {@link lspInstallCommand} lands — previously had zero readers
|
|
42
|
+
* (§DESIGN-V2 F7 bonus fix); an install failure now tails this for the `lastError`/event snippet. */
|
|
43
|
+
export declare const LSP_SETUP_LOG = "/tmp/lsp-setup.log";
|
|
44
|
+
/** BRIDGE_DIR-relative log the bridge process itself appends to (fatal errors, child exits) — §DESIGN-V2 C4. */
|
|
45
|
+
export declare function bridgeLogFile(): string;
|
|
46
|
+
/** Per-port pidfile the bridge writes ONLY after a confirmed bind (BRIDGE_SOURCE's `listen` callback) — see its
|
|
47
|
+
* header comment for why a losing (EADDRINUSE) second bridge must never clobber this. */
|
|
48
|
+
export declare function bridgePidFile(port: number): string;
|
|
49
|
+
/** §DESIGN-V2 F3: verify a prior bridge generation ACTUALLY stopped before the host mints a replacement.
|
|
50
|
+
* Exit 0 = confirmed dead (or the pidfile never existed — nothing was ever listening) → safe to start a new
|
|
51
|
+
* generation on this port. Exit 1 = still alive after the bound wait — the caller MUST NOT start a new bridge:
|
|
52
|
+
* an unconfirmed kill risks EADDRINUSE (the new generation's fresh token would then be permanently rejected by
|
|
53
|
+
* the still-listening old process, the token check in BRIDGE_SOURCE) — better to stay `dead` than double-burn
|
|
54
|
+
* the restart budget on a doomed attempt. POSIX `sh` only (no `seq`/bashisms — sandboxes vary in shell).
|
|
55
|
+
* Bound: 20 × 0.1s = 2s. */
|
|
56
|
+
export declare function terminateBridgeCommand(port: number): string;
|
|
24
57
|
//# sourceMappingURL=e2b-bridge.d.ts.map
|
package/dist/lsp/e2b-bridge.js
CHANGED
|
@@ -14,23 +14,60 @@ import { FRAME_DECODER_JS } from "./lsp-frames.js";
|
|
|
14
14
|
* **auth token** `LSP_TOKEN` (the endpoint is a public `wss://<port>-<id>.e2b.app` URL — the runner connects with
|
|
15
15
|
* `?token=<secret>`; without it, anyone with the URL could read the workspace via LSP, council #4). Frame parsing
|
|
16
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.
|
|
17
24
|
*/
|
|
18
25
|
export const BRIDGE_SOURCE = FRAME_DECODER_JS +
|
|
19
26
|
String.raw `
|
|
20
27
|
const { WebSocketServer } = require('ws');
|
|
21
28
|
const { spawn } = require('child_process');
|
|
22
29
|
const { URLSearchParams } = require('url');
|
|
30
|
+
const http = require('http');
|
|
31
|
+
const fs = require('fs');
|
|
23
32
|
const PORT = Number(process.env.LSP_PORT || 8123);
|
|
24
33
|
const CMD = (process.env.LSP_CMD || 'typescript-language-server --stdio').split(' ');
|
|
25
34
|
const TOKEN = process.env.LSP_TOKEN || '';
|
|
26
35
|
const MAX = 50 * 1024 * 1024;
|
|
27
36
|
const HEARTBEAT_MS = Number(process.env.LSP_HEARTBEAT_MS || 30000);
|
|
28
|
-
const
|
|
37
|
+
const START_MS = Date.now();
|
|
38
|
+
// Per-port pidfile (BRIDGE_DIR-relative — lspStartCommand cd's there first): the host reads this to verify a
|
|
39
|
+
// prior generation ACTUALLY stopped before minting a replacement (§DESIGN-V2 F3) — an unconfirmed kill risks a
|
|
40
|
+
// new generation's token being permanently rejected by a still-listening old process (the token check below).
|
|
41
|
+
// Written ONLY after a successful bind (inside the listen callback) — a SECOND bridge racing this same port
|
|
42
|
+
// must NOT clobber the FIRST (still listening) bridge's pidfile with its own (about-to-die) pid.
|
|
43
|
+
const PIDFILE = 'bridge-' + PORT + '.pid';
|
|
44
|
+
const LOGFILE = 'bridge.log';
|
|
45
|
+
function logLine(line) { try { fs.appendFileSync(LOGFILE, line + '\n'); } catch {} }
|
|
46
|
+
// Process-fatal hardening (§DESIGN-V2 C1/C2): an uncaught exception/rejection anywhere in this process previously
|
|
47
|
+
// died silently (no stderr line, no log) — the host's only signal was the WS going dead with no explanation.
|
|
48
|
+
process.on('uncaughtException', (err) => { const m = 'lsp-bridge fatal ' + (err && err.message); console.error(m); logLine(m); process.exit(1); });
|
|
49
|
+
process.on('unhandledRejection', (err) => { const m = 'lsp-bridge fatal ' + (err && err.message); console.error(m); logLine(m); process.exit(1); });
|
|
50
|
+
function tokenOf(url) { return new URLSearchParams(((url || '').split('?')[1]) || '').get('token'); }
|
|
51
|
+
const server = http.createServer((req, res) => {
|
|
52
|
+
const path = ((req && req.url) || '').split('?')[0];
|
|
53
|
+
if (path === '/healthz') {
|
|
54
|
+
// Same-origin auth as the WS gate below: TOKEN unset = open (dev/test), TOKEN set + mismatch = 401.
|
|
55
|
+
if (TOKEN && tokenOf(req.url) !== TOKEN) { res.writeHead(401); res.end(); return; }
|
|
56
|
+
const body = JSON.stringify({ pid: process.pid, uptimeMs: Date.now() - START_MS, connections: wss.clients.size, generationToken: TOKEN.slice(0, 8) });
|
|
57
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
58
|
+
res.end(body);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const body = http.STATUS_CODES[426]; // preserves ws's own default "upgrade required" response for any other path
|
|
62
|
+
res.writeHead(426, { 'Content-Length': Buffer.byteLength(body), 'Content-Type': 'text/plain' });
|
|
63
|
+
res.end(body);
|
|
64
|
+
});
|
|
29
65
|
// A zero-listener 'error' on ANY EventEmitter throws and crashes the process (Node default). Without this, a
|
|
30
66
|
// bind failure (e.g. EADDRINUSE — a second bridge start racing the same port) took down the WHOLE bridge
|
|
31
67
|
// process, killing every language's LSP session in the sandbox, not just the one that failed to start (HRD-LSP-4).
|
|
32
68
|
// Exit loud + explicit instead of an uncaught-exception dump so the failure is at least attributable.
|
|
33
|
-
|
|
69
|
+
server.on('error', (err) => { const m = 'lsp-bridge wss error ' + (err && err.message); console.error(m); logLine(m); process.exit(1); });
|
|
70
|
+
const wss = new WebSocketServer({ server });
|
|
34
71
|
wss.on('connection', (ws, req) => {
|
|
35
72
|
// Same zero-listener 'error' hazard as wss above, but per-connection: registered FIRST, before anything else
|
|
36
73
|
// touches this socket. Close (don't crash) — the heartbeat reaper's own ws.terminate() below is a routine
|
|
@@ -51,7 +88,7 @@ wss.on('connection', (ws, req) => {
|
|
|
51
88
|
// CALLER's process group instead of the (nonexistent) child (HRD-LSP-4; same hazard class as bake-runner's
|
|
52
89
|
// spawner, src/bake-runner/main.ts).
|
|
53
90
|
ws.on('close', () => { if (child.pid !== undefined) child.kill(); });
|
|
54
|
-
child.on('exit', () => { try { ws.close(); } catch {} });
|
|
91
|
+
child.on('exit', (code, signal) => { logLine('lsp-bridge child exit code=' + code + ' signal=' + signal); try { ws.close(); } catch {} });
|
|
55
92
|
});
|
|
56
93
|
// Reap half-open connections: the E2B proxy kills a WS at ~60-75s and the kill can be SILENT on THIS side too —
|
|
57
94
|
// no close frame, so the per-connection language server above would outlive its dead socket (one leaked server
|
|
@@ -63,11 +100,25 @@ setInterval(() => {
|
|
|
63
100
|
try { ws.ping(); } catch {}
|
|
64
101
|
}
|
|
65
102
|
}, HEARTBEAT_MS);
|
|
66
|
-
|
|
103
|
+
server.listen(PORT, '0.0.0.0', () => {
|
|
104
|
+
try { fs.writeFileSync(PIDFILE, String(process.pid)); } catch (e) { logLine('lsp-bridge pidfile write failed ' + (e && e.message)); }
|
|
105
|
+
console.log('lsp-bridge listening', PORT);
|
|
106
|
+
});
|
|
67
107
|
`;
|
|
108
|
+
/** Bridge dir inside the sandbox(声明提前:tsserver 定值路径引用它)。 */
|
|
109
|
+
export const BRIDGE_DIR = "/home/user/.lsp-bridge";
|
|
110
|
+
/** tls 的 tsserver.js 定值路径(#96 真机门):install 腿保证在场,connect 腿经
|
|
111
|
+
* `initializationOptions.tsserver.path` 告知 tls(v4 起无 CLI 旗可用)。 */
|
|
112
|
+
export const TSSERVER_JS_PATH = `${BRIDGE_DIR}/node_modules/typescript/lib/tsserver.js`;
|
|
68
113
|
/** Language server command per languageId. P1 = typescript/javascript; dart/kotlin land with their template servers.
|
|
69
114
|
* `--stdio` only where the server needs it to pick stdio over its other transports (the node-based servers);
|
|
70
|
-
* gopls/jdtls/kotlin-language-server/sourcekit-lsp/`dart language-server` speak LSP over stdio by default.
|
|
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 定值路径(烤好模板文件在场则秒过)。 */
|
|
71
122
|
export const SERVER_CMD = {
|
|
72
123
|
typescript: "typescript-language-server --stdio",
|
|
73
124
|
javascript: "typescript-language-server --stdio",
|
|
@@ -85,8 +136,7 @@ export const SERVER_NPM = {
|
|
|
85
136
|
javascript: "typescript-language-server typescript",
|
|
86
137
|
python: "pyright",
|
|
87
138
|
};
|
|
88
|
-
/**
|
|
89
|
-
export const BRIDGE_DIR = "/home/user/.lsp-bridge";
|
|
139
|
+
/** One bridge process per language → distinct ports.(BRIDGE_DIR 声明已提前至 SERVER_CMD 上方。) */
|
|
90
140
|
export function portForLanguage(language) {
|
|
91
141
|
// stable per-language port; extend the table as templates add servers (dev-mobile: dart/kotlin/java/swift)
|
|
92
142
|
const ports = { typescript: 8123, javascript: 8123, dart: 8124, kotlin: 8125, python: 8126, go: 8127, java: 8128, swift: 8129 };
|
|
@@ -101,6 +151,13 @@ export function lspInstallCommand(language) {
|
|
|
101
151
|
`mkdir -p ${BRIDGE_DIR}`,
|
|
102
152
|
`[ -f ${BRIDGE_DIR}/node_modules/ws/package.json ] || (cd ${BRIDGE_DIR} && echo '{}' > package.json && npm i ws >/tmp/lsp-setup.log 2>&1)`,
|
|
103
153
|
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
|
+
npm?.includes("typescript")
|
|
159
|
+
? `[ -f ${TSSERVER_JS_PATH} ] || (cd ${BRIDGE_DIR} && npm i typescript@5 >>/tmp/lsp-setup.log 2>&1)`
|
|
160
|
+
: "true",
|
|
104
161
|
].join(" && ");
|
|
105
162
|
}
|
|
106
163
|
/** Start the bridge — run as a TRUE background command (`env.startBackground`), NOT `&`-backgrounded inside a
|
|
@@ -109,4 +166,34 @@ export function lspInstallCommand(language) {
|
|
|
109
166
|
export function lspStartCommand(language, port, token) {
|
|
110
167
|
return `cd ${BRIDGE_DIR} && LSP_PORT=${port} LSP_TOKEN='${token}' LSP_CMD='${SERVER_CMD[language]}' node bridge.cjs`;
|
|
111
168
|
}
|
|
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
|
+
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
|
+
export function bridgeLogFile() {
|
|
174
|
+
return `${BRIDGE_DIR}/bridge.log`;
|
|
175
|
+
}
|
|
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
|
+
export function bridgePidFile(port) {
|
|
179
|
+
return `${BRIDGE_DIR}/bridge-${port}.pid`;
|
|
180
|
+
}
|
|
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
|
+
export function terminateBridgeCommand(port) {
|
|
189
|
+
const pidfile = bridgePidFile(port);
|
|
190
|
+
return [
|
|
191
|
+
`[ -f ${pidfile} ] || exit 0`,
|
|
192
|
+
`PID=$(cat ${pidfile})`,
|
|
193
|
+
`kill "$PID" 2>/dev/null`,
|
|
194
|
+
`i=0`,
|
|
195
|
+
`while [ $i -lt 20 ]; do kill -0 "$PID" 2>/dev/null || exit 0; i=$((i+1)); sleep 0.1; done`,
|
|
196
|
+
`exit 1`,
|
|
197
|
+
].join("; ");
|
|
198
|
+
}
|
|
112
199
|
//# sourceMappingURL=e2b-bridge.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { connectWsLspTransport } from "./ws-transport.js";
|
|
2
|
-
import { E2bLspManager, type LspCapableEnv } from "./manager.js";
|
|
2
|
+
import { E2bLspManager, type LspCapableEnv, type LspUnavailableReason } from "./manager.js";
|
|
3
3
|
import type { LspTransport } from "./types.js";
|
|
4
4
|
export interface E2bLspOptions {
|
|
5
5
|
/** Injectable for tests (no E2B/ws). */
|
|
@@ -20,11 +20,74 @@ export interface E2bLspOptions {
|
|
|
20
20
|
scheme?: "ws" | "wss";
|
|
21
21
|
/** Observability hook (open/heal/degrade events) — prod wires the service logger. */
|
|
22
22
|
log?: (event: string, fields: Record<string, unknown>) => void;
|
|
23
|
+
/** §DESIGN-V2 F11: metrics registry (structural — matches the narrow surface remote-env-e2b.ts's `cfg.metrics`
|
|
24
|
+
* already uses). `provider`/`generation`/`reason` labels are filled in by this module, never left freeform. */
|
|
25
|
+
metrics?: {
|
|
26
|
+
inc(name: string, labels?: Record<string, string>): void;
|
|
27
|
+
};
|
|
28
|
+
/** §DESIGN-V2 F11: the deployment lane feeding this manager (boot/execution-env.ts passes it explicitly —
|
|
29
|
+
* "e2b" or "k8s" — rather than this module guessing it back out of `scheme`, which is a WS-protocol detail
|
|
30
|
+
* that happens to correlate with the lane but isn't the same axis). Defaults to inferring from `scheme` only
|
|
31
|
+
* so existing callers that don't pass it (tests) keep working. */
|
|
32
|
+
provider?: string;
|
|
33
|
+
/** §DESIGN-V2 F2/C6: health-probe override for tests (no real sandbox network). Defaults to a real
|
|
34
|
+
* `GET /healthz` fetch (1s timeout) — `scheme` maps to the probe's URL scheme, F11 (`wss→https`, `ws→http`). */
|
|
35
|
+
probe?: (env: LspCapableEnv, port: number, token: string, scheme: "ws" | "wss") => Promise<boolean>;
|
|
23
36
|
}
|
|
24
37
|
/** Start (+ install on a non-baked template) the language server + bridge inside THIS env's sandbox, then connect.
|
|
25
38
|
* Every call starts (or re-starts) the bridge unconditionally — the one production caller is
|
|
26
39
|
* `createE2bLspManager`, which de-dupes per env+port before ever reaching here (see its doc). */
|
|
27
40
|
export declare function openE2bLspTransport(language: string, env: LspCapableEnv, opts?: E2bLspOptions): Promise<LspTransport | undefined>;
|
|
41
|
+
/** §DESIGN-V2 A: `starting` while the write+install+startBackground sequence is in flight; `ready` once at
|
|
42
|
+
* least one WS connect through this generation has succeeded; `dead` once this generation is confirmed (or
|
|
43
|
+
* presumed, per its own failure) unusable — the ONLY way out of `dead` is minting a new generation. */
|
|
44
|
+
export type BridgeState = "starting" | "ready" | "dead";
|
|
45
|
+
/**
|
|
46
|
+
* §DESIGN-V2 A/F3/F6: one bridge RESOURCE per env+port (not per language — `portForLanguage` maps
|
|
47
|
+
* typescript/javascript onto the SAME port/process off the same `SERVER_CMD`, HRD-LSP-4). A crashed/never-
|
|
48
|
+
* booted bridge can be replaced by minting a new `generation` (bounded by `MAX_BRIDGE_GENERATIONS` — a
|
|
49
|
+
* crash-loop must degrade the language, not burn the sandbox indefinitely); the NEW generation is only ever
|
|
50
|
+
* minted after `terminateBridgeCommand` confirms the OLD one actually stopped (F3) — an unconfirmed kill risks
|
|
51
|
+
* EADDRINUSE permanently rejecting the new generation's fresh token (the old process is still the one
|
|
52
|
+
* listening, and its token check has no way to know a replacement was intended).
|
|
53
|
+
*/
|
|
54
|
+
export interface E2bBridgeHandle {
|
|
55
|
+
readonly port: number;
|
|
56
|
+
/** Pinned once per generation — the shared bridge resource's auth secret. A per-LANGUAGE token would be
|
|
57
|
+
* rejected by the one bridge process actually listening whenever two languages share a port (see the
|
|
58
|
+
* module doc's HRD-LSP-4 reference): the second language mints a different token, the bridge only
|
|
59
|
+
* recognizes whichever token started it, auth fails, retries exhaust, and that language is permanently
|
|
60
|
+
* degraded to grep/read_file for the task. */
|
|
61
|
+
readonly token: string;
|
|
62
|
+
/** The `LSP_CMD` this generation's bridge process was launched with — identity documentation (ts/js sharing
|
|
63
|
+
* one value/port/process is legitimate, §DESIGN-V2 "不做"); not read back by this module. */
|
|
64
|
+
readonly serverCmd: string;
|
|
65
|
+
/** 1 at first mint; +1 every restart. Bounds the restart budget (`MAX_BRIDGE_GENERATIONS`). */
|
|
66
|
+
readonly generation: number;
|
|
67
|
+
state: BridgeState;
|
|
68
|
+
/** The most recent human-readable diagnostic (write/install/start/connect failure, or a probe verdict) —
|
|
69
|
+
* feeds `lastError`-style debugging; the MODEL-facing word is `deathReason` (via `unavailableReason`), not
|
|
70
|
+
* this free-text string. */
|
|
71
|
+
lastError?: string;
|
|
72
|
+
/** The SPECIFIC reason THIS generation died (unset while starting/ready). `unavailableReason`-facing
|
|
73
|
+
* reporting overrides this to `bridge-restart-budget-exhausted` once `generation >= MAX_BRIDGE_GENERATIONS`
|
|
74
|
+
* — "further restarts won't help" is a different (and, once true, more useful) word than "what killed the
|
|
75
|
+
* last one." */
|
|
76
|
+
deathReason?: LspUnavailableReason;
|
|
77
|
+
/** True once this generation has completed at least one real WS connect. Gates the fast healthz-probe heal
|
|
78
|
+
* path (§DESIGN-V2 F2/F6): a generation that has never connected yet is still cold-starting, not dead —
|
|
79
|
+
* treating an unreachable healthz as "dead" during that window would misjudge ordinary boot lag as a crash
|
|
80
|
+
* and burn a restart generation for nothing. A generation that HAS connected before, though, really should
|
|
81
|
+
* be reachable again quickly, so it skips the blind 6×1500ms retry loop and asks the bridge directly. */
|
|
82
|
+
everConnected: boolean;
|
|
83
|
+
/** Resolves once the write+install+startBackground sequence has been ATTEMPTED for this generation — `false`
|
|
84
|
+
* (with `state` already flipped to `dead` + `deathReason`/`lastError` set) means it failed before ever
|
|
85
|
+
* reaching a connect attempt. */
|
|
86
|
+
ready: Promise<boolean>;
|
|
87
|
+
}
|
|
88
|
+
/** A crash-loop must degrade the language, not retry the sandbox forever. 3 covers "an occasional OOM kill";
|
|
89
|
+
* a bridge that dies again immediately after 3 fresh starts is a standing failure, not a blip. */
|
|
90
|
+
export declare const MAX_BRIDGE_GENERATIONS = 3;
|
|
28
91
|
/** The single shared `LspServerManager` for the deployment (`RunnerDeps.lspManager`). */
|
|
29
92
|
export declare function createE2bLspManager(opts?: E2bLspOptions): E2bLspManager;
|
|
30
93
|
//# sourceMappingURL=e2b-manager.d.ts.map
|