@sema-agent/server 7.140.0 → 7.142.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/MIGRATION.md +505 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +142 -25
- package/dist/approval-ask-audit-store.d.ts +54 -38
- package/dist/approval-ask-audit-store.js +58 -39
- package/dist/approval-batch-census.d.ts +27 -0
- package/dist/approval-batch-census.js +27 -0
- package/dist/approval-card.d.ts +39 -24
- package/dist/approval-card.js +6 -3
- package/dist/approval-crash-convergence.d.ts +17 -7
- package/dist/approval-crash-convergence.js +94 -22
- package/dist/approval-deny-reasons.d.ts +34 -30
- package/dist/approval-deny-reasons.js +21 -6
- package/dist/approval-policy-settlement.d.ts +1 -1
- package/dist/approval-reconciler.d.ts +33 -1
- package/dist/approval-reconciler.js +43 -40
- package/dist/approval.d.ts +15 -0
- package/dist/approval.js +10 -0
- package/dist/audit.d.ts +4 -2
- package/dist/audit.js +2 -2
- package/dist/auth-bridge.d.ts +9 -6
- package/dist/auth-bridge.js +9 -3
- package/dist/boot/adoption.js +2 -1
- package/dist/boot/budget-tracing.d.ts +16 -0
- package/dist/boot/budget-tracing.js +10 -6
- package/dist/boot/device-lane.d.ts +11 -0
- package/dist/boot/device-lane.js +16 -0
- package/dist/boot/engine-lease.d.ts +24 -3
- package/dist/boot/engine-lease.js +9 -2
- package/dist/boot/execution-env.d.ts +24 -4
- package/dist/boot/execution-env.js +34 -27
- package/dist/boot/execution-lane.js +2 -1
- package/dist/boot/exit-boundary.js +5 -4
- package/dist/boot/memory-boundary.d.ts +2 -2
- package/dist/boot/memory-consolidation.d.ts +1 -1
- package/dist/boot/memory-consolidation.js +3 -2
- package/dist/boot/parked-revive-gate.js +2 -1
- package/dist/boot/permission-rules-audit.d.ts +0 -28
- package/dist/boot/permission-rules-audit.js +2 -1
- package/dist/boot/read-deny-anchor-audit.js +2 -1
- package/dist/boot/reapers.d.ts +2 -2
- package/dist/boot/reapers.js +4 -3
- package/dist/boot/resolve-spec.js +18 -6
- package/dist/boot/retention-lane.js +5 -3
- package/dist/boot/runner-deps.js +16 -13
- package/dist/boot/runtime-caps.js +3 -2
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +8 -6
- package/dist/boot/shutdown.js +8 -2
- package/dist/boot/stage-04-stores.js +3 -2
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.js +11 -7
- package/dist/boot/stage-06-runners.d.ts +1 -3
- package/dist/boot/stage-06-runners.js +3 -2
- package/dist/boot/stage-07-capability-layer.d.ts +1 -2
- package/dist/boot/stage-07-capability-layer.js +10 -11
- package/dist/boot/stage-08-reapers.d.ts +1 -2
- package/dist/boot/stage-08-reapers.js +3 -2
- package/dist/boot/stage-09-leader.d.ts +1 -2
- package/dist/boot/stage-10-http-server.d.ts +1 -2
- package/dist/boot/stage-10-http-server.js +6 -4
- package/dist/boot/stores.js +13 -6
- package/dist/boot/web-admin.js +18 -17
- package/dist/boot/webfetch-summarize-lane.js +2 -1
- package/dist/boot/workflow-orchestration.js +3 -2
- package/dist/capabilities/builtin-tools.d.ts +2 -1
- package/dist/capabilities/builtin-tools.js +2 -4
- package/dist/capabilities/composite-members.d.ts +2 -1
- package/dist/capabilities/composite-members.js +2 -1
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +33 -0
- package/dist/capability-facts.js +32 -0
- package/dist/card-void.d.ts +38 -0
- package/dist/card-void.js +23 -0
- package/dist/config-catalog.js +12 -8
- package/dist/config-provider.d.ts +13 -13
- package/dist/config-provider.js +19 -33
- package/dist/config-types.d.ts +39 -10
- package/dist/config.d.ts +6 -0
- package/dist/config.js +72 -49
- package/dist/delegated-session-owner.d.ts +21 -19
- package/dist/delegated-session-owner.js +1 -1
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +3 -0
- package/dist/execution-config-root.d.ts +22 -0
- package/dist/execution-config-root.js +35 -0
- package/dist/execution-lane-caps.d.ts +50 -2
- package/dist/execution-lane-caps.js +90 -13
- package/dist/execution-placement.d.ts +3 -0
- package/dist/execution-placement.js +7 -0
- package/dist/fleet/drain-entry.d.ts +3 -0
- package/dist/fleet/fleet-bus.d.ts +6 -3
- package/dist/hooks/hook-runner.d.ts +6 -0
- package/dist/hooks/hook-runner.js +30 -1
- package/dist/hosted-posture.d.ts +8 -0
- package/dist/hosted-posture.js +4 -0
- package/dist/http/admission.js +3 -3
- package/dist/http/caller-trust.d.ts +3 -2
- package/dist/http/caller-trust.js +2 -1
- package/dist/http/dispatch.js +8 -2
- package/dist/http/execution-context.d.ts +2 -2
- package/dist/http/idempotency.d.ts +1 -1
- package/dist/http/live-frame.d.ts +24 -0
- package/dist/http/live-frame.js +16 -0
- package/dist/http/principal-gate.d.ts +3 -1
- package/dist/http/principal-gate.js +17 -8
- package/dist/http/resume-legs.js +42 -29
- package/dist/http/route-table.js +4 -0
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/attachments.js +18 -0
- package/dist/http/routes/capabilities.js +10 -6
- package/dist/http/routes/diagnostics.d.ts +0 -19
- package/dist/http/routes/diagnostics.js +16 -2
- package/dist/http/routes/memory-bundle.d.ts +2 -2
- package/dist/http/routes/memory-bundle.js +1 -1
- package/dist/http/routes/memory-compliance.js +1 -1
- package/dist/http/routes/memory-policy.js +27 -7
- package/dist/http/routes/run-permission-mode.d.ts +75 -0
- package/dist/http/routes/run-permission-mode.js +170 -0
- package/dist/http/routes/runs.js +3 -3
- package/dist/http/routes/session-memory-sync.d.ts +63 -0
- package/dist/http/routes/session-memory-sync.js +157 -0
- package/dist/http/routes/session-sync.d.ts +3 -6
- package/dist/http/routes/session-sync.js +7 -2
- package/dist/http/routes/sessions-list.d.ts +2 -1
- package/dist/http/routes/sessions.js +5 -4
- package/dist/http/routes/tasks.js +70 -71
- package/dist/http/run-read-face.d.ts +7 -0
- package/dist/http/run-read-face.js +10 -4
- package/dist/http/send.d.ts +6 -1
- package/dist/http/send.js +10 -3
- package/dist/http/server.d.ts +20 -7
- package/dist/http/server.js +2 -4
- package/dist/http/sse-log.d.ts +3 -8
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +3 -1
- package/dist/http/submit-replay.d.ts +4 -3
- package/dist/http/submit-replay.js +2 -2
- package/dist/http/sync-submit-body.d.ts +21 -4
- package/dist/http/sync-submit-body.js +13 -0
- package/dist/identity-source.d.ts +21 -2
- package/dist/identity-source.js +6 -0
- package/dist/instruction-source-notice.d.ts +7 -0
- package/dist/instruction-source-notice.js +6 -6
- package/dist/mcp-assertion-header.d.ts +4 -0
- package/dist/mcp-assertion-header.js +5 -0
- package/dist/memory-incident-sink.d.ts +19 -6
- package/dist/memory-incident-sink.js +8 -4
- package/dist/memory-posture.d.ts +1 -1
- package/dist/memory-scope.d.ts +4 -4
- package/dist/memory-scope.js +1 -1
- package/dist/memory-sync-client.d.ts +35 -23
- package/dist/memory-sync-client.js +122 -52
- package/dist/memory-sync-identity.d.ts +24 -0
- package/dist/memory-sync-identity.js +57 -0
- package/dist/memory-sync.d.ts +7 -1
- package/dist/memory-sync.js +6 -3
- package/dist/observability/fail-open.d.ts +57 -12
- package/dist/observability/fail-open.js +50 -9
- package/dist/observability/metrics.js +0 -1
- package/dist/observability/run-terminal.d.ts +93 -31
- package/dist/observability/run-terminal.js +175 -171
- package/dist/observability/secret-env-scrub.d.ts +20 -1
- package/dist/observability/secret-env-scrub.js +14 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
- package/dist/orchestration/workflow-completion-inbox.js +13 -5
- package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
- package/dist/orchestration/workflow-notify-journal.js +2 -1
- package/dist/permission-mode-admission.d.ts +68 -0
- package/dist/permission-mode-admission.js +23 -0
- package/dist/permission-rule-list.d.ts +55 -6
- package/dist/permission-rule-list.js +52 -5
- package/dist/plugins/approval-ask-store-file.d.ts +14 -4
- package/dist/plugins/approval-ask-store-file.js +48 -7
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
- package/dist/plugins/approval-ask-store-memory.js +9 -8
- package/dist/plugins/approval-ask-store-sql.d.ts +42 -10
- package/dist/plugins/approval-ask-store-sql.js +49 -19
- package/dist/plugins/caching-session-store.d.ts +6 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
- package/dist/plugins/checkpoint-store-sql.js +74 -3
- package/dist/plugins/device-store-sql.d.ts +5 -2
- package/dist/plugins/device-store-sql.js +4 -1
- package/dist/plugins/execution-placement-sql.d.ts +11 -1
- package/dist/plugins/execution-placement-sql.js +12 -1
- package/dist/plugins/file-run-store.d.ts +7 -2
- package/dist/plugins/file-run-store.js +12 -2
- package/dist/plugins/fork-routing-session-store.d.ts +10 -32
- package/dist/plugins/fork-routing-session-store.js +2 -61
- package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
- package/dist/plugins/k8s-exec-protocol.js +6 -1
- package/dist/plugins/local-checkpoint-store.d.ts +10 -1
- package/dist/plugins/local-checkpoint-store.js +22 -0
- package/dist/plugins/local-session-store.d.ts +260 -39
- package/dist/plugins/local-session-store.js +582 -57
- package/dist/plugins/local-session-window.d.ts +17 -3
- package/dist/plugins/local-session-window.js +7 -13
- package/dist/plugins/local-task-attachment-store.d.ts +12 -2
- package/dist/plugins/local-task-attachment-store.js +67 -43
- package/dist/plugins/memory-key-guards.d.ts +3 -3
- package/dist/plugins/memory-run-store.d.ts +7 -2
- package/dist/plugins/memory-run-store.js +12 -2
- package/dist/plugins/one-time-migrations.d.ts +68 -2
- package/dist/plugins/one-time-migrations.js +120 -4
- package/dist/plugins/pg-pool.js +3 -2
- package/dist/plugins/pg-session-storage.d.ts +2 -0
- package/dist/plugins/pg-session-storage.js +3 -2
- package/dist/plugins/remote-env-e2b.d.ts +47 -7
- package/dist/plugins/remote-env-e2b.js +242 -46
- package/dist/plugins/remote-env-k8s.d.ts +51 -6
- package/dist/plugins/remote-env-k8s.js +234 -44
- package/dist/plugins/remote-env-local-docker.d.ts +40 -14
- package/dist/plugins/remote-env-local-docker.js +127 -33
- package/dist/plugins/run-store-sql.d.ts +11 -3
- package/dist/plugins/run-store-sql.js +13 -4
- package/dist/plugins/sandbox-read-floor.d.ts +273 -0
- package/dist/plugins/sandbox-read-floor.js +608 -0
- package/dist/plugins/session-ownership.d.ts +2 -1
- package/dist/plugins/session-placement.d.ts +17 -4
- package/dist/plugins/session-placement.js +2 -1
- package/dist/plugins/session-store.d.ts +18 -20
- package/dist/plugins/session-store.js +4 -12
- package/dist/plugins/store-backend.d.ts +41 -7
- package/dist/plugins/store-backend.js +22 -4
- package/dist/plugins/store-contracts.d.ts +35 -3
- package/dist/plugins/store-contracts.js +3 -0
- package/dist/plugins/task-attachment-store.d.ts +112 -14
- package/dist/plugins/task-attachment-store.js +221 -28
- package/dist/plugins/tidb-pool.js +2 -1
- package/dist/plugins/tidb-session-store.d.ts +4 -1
- package/dist/plugins/tidb-session-store.js +3 -2
- package/dist/plugins/workspace-fill.d.ts +60 -0
- package/dist/plugins/workspace-fill.js +106 -0
- package/dist/project-bundle.d.ts +38 -0
- package/dist/project-bundle.js +609 -0
- package/dist/run-cancel-context.d.ts +34 -0
- package/dist/run-cancel-context.js +7 -0
- package/dist/run-local.js +2 -8
- package/dist/runs.d.ts +39 -12
- package/dist/runs.js +69 -55
- package/dist/sandbox-egress-policy.d.ts +4 -1
- package/dist/sandbox-egress-policy.js +3 -1
- package/dist/sandbox-git-plane.d.ts +31 -12
- package/dist/sandbox-git-plane.js +24 -4
- package/dist/security.d.ts +11 -2
- package/dist/security.js +8 -0
- package/dist/server-secret-env.d.ts +2 -1
- package/dist/server-secret-env.js +2 -4
- package/dist/session-error-wire.d.ts +46 -4
- package/dist/session-error-wire.js +23 -0
- package/dist/session-incarnation.d.ts +9 -3
- package/dist/session-sync-kernel.d.ts +42 -1
- package/dist/session-sync-kernel.js +17 -0
- package/dist/session-sync.js +16 -4
- package/dist/sql-engine-coordinates.d.ts +49 -0
- package/dist/sql-engine-coordinates.js +27 -0
- package/dist/standing-answer-grant.d.ts +31 -2
- package/dist/standing-answer-grant.js +14 -2
- package/dist/store-posture.d.ts +1 -1
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-mcp.d.ts +1 -1
- package/dist/task-settings.d.ts +62 -27
- package/dist/task-settings.js +105 -29
- package/dist/terminal.d.ts +97 -20
- package/dist/terminal.js +107 -18
- package/dist/tool-approval.d.ts +4 -3
- package/dist/tool-approval.js +12 -14
- package/dist/trace/core-keyset-guard.d.ts +196 -2
- package/dist/trace/engine-notice-wire.d.ts +8 -2
- package/dist/trace/engine-notice-wire.js +4 -2
- package/dist/trace/exit-seat.d.ts +9 -1
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/ledger-sink.d.ts +8 -4
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/model-segment.d.ts +2 -2
- package/dist/trace/project.d.ts +18 -1
- package/dist/trace/project.js +16 -0
- package/dist/trace/redact.js +323 -90
- package/dist/trace/self-minted-wire.d.ts +419 -4
- package/dist/trace/self-minted-wire.js +145 -0
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/verifier-pause-card.d.ts +8 -0
- package/dist/verifier-pause-card.js +19 -0
- package/dist/web-admin-client/apply-effective.d.ts +5 -7
- package/dist/web-admin-client/apply-effective.js +22 -2
- package/dist/web-admin-client/candidate-gate.d.ts +1 -1
- package/dist/web-admin-client/effective-keys.js +2 -0
- package/dist/web-admin-client/read-warnings.d.ts +2 -2
- package/dist/web-admin-client/types.d.ts +4 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
- package/package.json +2 -2
- package/wire-changes/server@7.141.0.json +367 -0
- package/wire-changes/server@7.142.0.json +639 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { type LaneHomeConfig } from "../lane-home-probe.js";
|
|
3
|
-
import { FileError, ExecutionError, RemoteExecutionError, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory } from "@sema-agent/core";
|
|
3
|
+
import { FileError, ExecutionError, RemoteExecutionError, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, type ExecutionEnvCapabilities } from "@sema-agent/core";
|
|
4
|
+
import { type SandboxReadFloorKnob } from "../execution-lane-caps.js";
|
|
5
|
+
import { type SandboxReadFloor } from "./sandbox-read-floor.js";
|
|
6
|
+
import type { WorkspaceFill, WorkspaceFillOf } from "./workspace-fill.js";
|
|
4
7
|
/** Options accepted by `ExecutionEnv.exec` (core does not re-export the type; derive it from the seam). */
|
|
5
8
|
type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
6
9
|
/** Construction config for {@link RemoteLocalDockerExecutionEnv}. */
|
|
@@ -55,14 +58,22 @@ export interface LocalDockerEnvConfig extends LaneHomeConfig {
|
|
|
55
58
|
/** Stable id mixed into the container NAME (e.g. the sessionId). A random suffix is always appended so two
|
|
56
59
|
* envs sharing an id never collide. Default = a random id. */
|
|
57
60
|
id?: string;
|
|
61
|
+
/** `SANDBOX_READ_FLOOR`(keep | off;缺席 ⇒ keep):本容器守不守读拒表的字节地板 = 读地板模块服务本车道 ∧ 本键不是 off(`sandbox-read-floor.ts`)。 */
|
|
62
|
+
sandboxReadFloor?: SandboxReadFloorKnob;
|
|
63
|
+
/** 恢复点的填充座(`workspace-fill.ts`;工厂按 run 装配):容器新建之后、交给本环境之前,经本适配器自己的 `docker exec` 往工作区里放内容。缺席 ⇒ 不放。 */
|
|
64
|
+
workspaceFill?: WorkspaceFill;
|
|
58
65
|
}
|
|
59
66
|
/** Inject `spawn` for tests (a fake docker CLI). Defaults to node's child_process spawn. */
|
|
60
67
|
export interface LocalDockerEnvDeps {
|
|
61
68
|
spawn?: typeof spawn;
|
|
69
|
+
/** 这只环境手里的读地板模块(缺席 ⇒ `createSandboxReadFloor` 按旋钮选;钉注入守地板的模块)。能力块的 `keepsReadFloor` 读它的状态。 */
|
|
70
|
+
readFloor?: SandboxReadFloor;
|
|
62
71
|
}
|
|
63
72
|
export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv {
|
|
64
|
-
/** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)
|
|
65
|
-
readonly capabilities:
|
|
73
|
+
/** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。读地板旋钮是它的输入,{@link floor} 由同一只判据、同一个旋钮值选出。 */
|
|
74
|
+
readonly capabilities: ExecutionEnvCapabilities;
|
|
75
|
+
/** 读拒表字节地板模块:容器里每一次 `docker exec` 起动都先经它的 `wrap`({@link startArgv},唯一接缝)。 */
|
|
76
|
+
private readonly floor;
|
|
66
77
|
/** Working directory inside the container; relative paths resolve against it (ExecutionEnv contract). */
|
|
67
78
|
cwd: string;
|
|
68
79
|
/** core `ExecutionEnv.homeDir`([ref]):本容器里执行用户的 home —— 声明在场 ⇒ 声明(开环境那一拍与 `$HOME` 对证过),否则开容器那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
|
|
@@ -84,12 +95,15 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
84
95
|
private destroyed;
|
|
85
96
|
/** In-flight connect, memoized so concurrent lazy first-use starts exactly one container (no double-run). */
|
|
86
97
|
private connecting?;
|
|
87
|
-
/**
|
|
88
|
-
|
|
98
|
+
/** 这只环境的拒 —— 记一次、此后每次连接原样回它(同一镜像同一答案:不再起容器、不再问)。两个写者:家目录(环境答了而与声明不一致)
|
|
99
|
+
* 与恢复点的填充(换一只容器也是同一个答的拒:种子不是项目包、摘要不符、镜像没有 `tar`、镜像预置撞名 —— `FillOutcome.standing`)。 */
|
|
100
|
+
private connectRefusal?;
|
|
89
101
|
/** 家目录的座(声明在场 ⇒ 开环境那一拍对证 · 否则开容器那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。与其余读得出家目录的车道同一只判官、同一句拒。 */
|
|
90
102
|
private readonly homeSeat;
|
|
91
103
|
constructor(config: LocalDockerEnvConfig, deps?: LocalDockerEnvDeps);
|
|
92
104
|
private resolve;
|
|
105
|
+
/** 收据句柄。还没连上(懒环境:续跑腿的引擎在恢复相**之前**先取它比车道)⇒ 未连接形 `{ sandboxId: "(unconnected)", provider, mountPath }`(与 k8s / e2b 车道同形),
|
|
106
|
+
* 不抛 —— 抛了,续跑腿在恢复相就失败。 */
|
|
93
107
|
workspaceHandle(): WorkspaceHandle;
|
|
94
108
|
connect(config?: RemoteConnectConfig): Promise<{
|
|
95
109
|
ok: true;
|
|
@@ -99,6 +113,10 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
99
113
|
error: RemoteExecutionError;
|
|
100
114
|
}>;
|
|
101
115
|
private doConnect;
|
|
116
|
+
/** 恢复点的填充:有座 ⇒ 经 {@link fillChannel} 放(容器恒是新起的 ⇒ `minted`;本车道不可挂起);没有 ⇒ 什么都不放。 */
|
|
117
|
+
private fillWorkspace;
|
|
118
|
+
/** 恢复点手里的通道:`docker exec` 经唯一起动接缝(起动形 `adapter`,与引擎命令同一执行用户;连接进行中,不经懒连接的文件面);写 = 容器内 `cat >` 从 stdin 收整份字节。 */
|
|
119
|
+
private fillChannel;
|
|
102
120
|
/**
|
|
103
121
|
* 家目录:座要问({@link LaneHomeSeat.asks}:声明在场 ⇒ 对证;没声明 ⇒ 实测)⇒ 在本容器里问一次 `$HOME`(与命令同一条 `docker exec` 通道、同一份 `-e` env),读数交座。
|
|
104
122
|
* 声明在场:环境答了而不一致 / 不是绝对路径 ⇒ 回拒并记在环境上(同一镜像同一答案:此后每次连接原样回它,不再起容器、不再问);没答上(exec 失败 / 超时 / 非零退出)⇒ 这一次同样不开,
|
|
@@ -159,7 +177,7 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
159
177
|
}): Promise<Result<string[], FileError>>;
|
|
160
178
|
/** 暂存写的两只面(规则与提交脚本的唯一属主 = `remote-shell.ts` `writeFileStaged` / `appendFileStaged`;父目录与私有暂存目录由编排的准备那一步建):
|
|
161
179
|
* 上传 = 容器内 `cat` 从 stdin 收整份字节落到私有目录里的暂存件({@link pipeIn});提交 = 容器内一次 shell 往返。两者同一身份 ——
|
|
162
|
-
* 同一只 argv 构造({@link
|
|
180
|
+
* 同一只 argv 构造({@link startArgv}),都是镜像的用户。不用 `docker cp`:它由守护进程以 root 落盘,镜像用户 ≠ root 时暂存件属 root、
|
|
163
181
|
* 提交方拿不走也清不掉,「目录不收新名字」也探不出来(root 哪里都写得进)。 */
|
|
164
182
|
private stagedLane;
|
|
165
183
|
/** 整旧或整新(暂存 → 一次 `mv -f`)。修前 `docker cp` 直落目标(以守护进程的 root 身份):传输中途断 / 取消时目标剩什么取决于守护进程的解包实现,本适配器不依赖它。 */
|
|
@@ -205,17 +223,23 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
205
223
|
/**
|
|
206
224
|
* 交给宿主 `docker` CLI 的 env —— 宿主子进程 env 的唯一组合(`server-secret-env.ts` `hostSubprocessEnv`),继承词 `"all"`:本服务自有凭据(起服声明的名与值)
|
|
207
225
|
* 任何词都不交,CLI 与它拉起的凭据助手 / CLI 插件要读的变量(`DOCKER_*` / `AWS_*` …)照交 —— 形状剥会剥 `AWS_SECRET_ACCESS_KEY` 一类,ECR / GCR 凭据助手因此断。
|
|
208
|
-
* 命令本身跑在容器里,容器的 env 只由 `
|
|
226
|
+
* 命令本身跑在容器里,容器的 env 只由 `startArgv` 的 `-e K=V` 显式给,本 env 到不了容器。每次起进程现算一次(与 host shell 同)。
|
|
209
227
|
*/
|
|
210
228
|
private cliEnv;
|
|
211
229
|
/** Prepend `-H <dockerHost>` to a docker argv when configured (per-invocation, not the process env). */
|
|
212
230
|
private withHostFlag;
|
|
213
|
-
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
231
|
+
/**
|
|
232
|
+
* **唯一起动接缝**:容器里的每一次 `docker exec` 起动(引擎的命令、文件面助手、探针、地板的引导命令)都从这里拿 argv —— 先问地板模块
|
|
233
|
+
* `wrap(起动形)`,再按答案编码(身份旗标 + 监督者命令行头;不守地板 ⇒ 与改前逐字同)。本适配器对地板零判断;模块拒 ⇒ `spawn_error`,命令从未起。
|
|
234
|
+
* argv elements are SEPARATE (no host-side shell join) — the command runs in the CONTAINER's shell (`-c`). `stdin` ⇒ `-i` (the payload of
|
|
235
|
+
* {@link pipeIn}); nothing else differs, so a piped upload runs as the same user as every other exec. cwd (`-w`) and per-command env (`-e K=V`;
|
|
236
|
+
* adapter base env under, per-command wins) — except `containerDefaults`: the command asks about the container's OWN defaults (the shell probe),
|
|
237
|
+
* so it carries neither (the bare argv is then `exec <name> <shell> -c <cmd>`, byte-identical to the pre-floor probe).
|
|
238
|
+
*/
|
|
239
|
+
private startArgv;
|
|
240
|
+
/** 地板模块的三件原语(引导命令 / 放置 / 中继)。引导与放置经 {@link startArgv}(起动形 `bootstrap`:只在附着中合法);中继以模块给的身份起一只
|
|
241
|
+
* 长连的 `docker exec -i`(stdin / stdout 作双工),随本环境 `destroy` 由模块关。 */
|
|
242
|
+
private floorTransport;
|
|
219
243
|
/** Spawn the docker binary with argv; collect stdout/stderr (+ raw bytes when binary), bound by timeout/abort. */
|
|
220
244
|
private docker;
|
|
221
245
|
/** Lazy connect for an exec path → ExecutionError on failure (fail closed, never throws). */
|
|
@@ -244,6 +268,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
244
268
|
* owns the lifetime and calls `destroy()` (= `docker rm -f`) on task end. Folds the per-task `ctx.taskId`/
|
|
245
269
|
* `ctx.sessionId` into the container name so two concurrent tasks never share a container.
|
|
246
270
|
*/
|
|
247
|
-
export declare function localDockerExecutionEnvFactory(config: LocalDockerEnvConfig,
|
|
271
|
+
export declare function localDockerExecutionEnvFactory(config: Omit<LocalDockerEnvConfig, "workspaceFill"> & {
|
|
272
|
+
readonly workspaceFillOf?: WorkspaceFillOf;
|
|
273
|
+
}, deps?: LocalDockerEnvDeps): ExecutionEnvFactory;
|
|
248
274
|
export {};
|
|
249
275
|
//# sourceMappingURL=remote-env-local-docker.d.ts.map
|
|
@@ -9,6 +9,7 @@ import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
|
|
|
9
9
|
import { buildCapturedOutput, createCutError } from "../exec-cut.js";
|
|
10
10
|
import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
11
11
|
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
12
|
+
import { ADAPTER_START, BOOTSTRAP_START, createSandboxReadFloor, dockerIdentityFlags, engineStart, floorArgv, } from "./sandbox-read-floor.js";
|
|
12
13
|
import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
|
|
13
14
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
14
15
|
const PROVIDER = "local-docker";
|
|
@@ -21,7 +22,8 @@ const unsupported = (op) => ({
|
|
|
21
22
|
error: new RemoteExecutionError("unsupported", `${op} is not supported on a local-docker target (v1: a container is an isolation boundary but not a managed memory snapshot; capabilities.suspendable=false)`),
|
|
22
23
|
});
|
|
23
24
|
export class RemoteLocalDockerExecutionEnv {
|
|
24
|
-
capabilities
|
|
25
|
+
capabilities;
|
|
26
|
+
floor;
|
|
25
27
|
cwd;
|
|
26
28
|
get homeDir() {
|
|
27
29
|
return this.homeSeat.home;
|
|
@@ -35,7 +37,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
35
37
|
handle;
|
|
36
38
|
destroyed = false;
|
|
37
39
|
connecting;
|
|
38
|
-
|
|
40
|
+
connectRefusal;
|
|
39
41
|
homeSeat;
|
|
40
42
|
constructor(config, deps = {}) {
|
|
41
43
|
if (!config.image || !config.image.trim()) {
|
|
@@ -52,6 +54,8 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
52
54
|
this.cwd = this.cfg.mountPath;
|
|
53
55
|
this.homeSeat = new LaneHomeSeat(PROVIDER, config);
|
|
54
56
|
this.spawnFn = deps.spawn ?? spawn;
|
|
57
|
+
this.floor = deps.readFloor ?? createSandboxReadFloor({ lane: PROVIDER, knob: config.sandboxReadFloor });
|
|
58
|
+
this.capabilities = capabilitiesFor({ remoteExec: { provider: PROVIDER }, sandboxReadFloor: config.sandboxReadFloor });
|
|
55
59
|
const id = config.id ?? randomBytes(6).toString("hex");
|
|
56
60
|
const suffix = randomBytes(4).toString("hex");
|
|
57
61
|
this.containerName = `sema-docker-${sanitizeId(id)}-${suffix}`;
|
|
@@ -60,17 +64,15 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
60
64
|
return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
|
|
61
65
|
}
|
|
62
66
|
workspaceHandle() {
|
|
63
|
-
|
|
64
|
-
throw new RemoteExecutionError("connect_failed", "workspaceHandle() called before connect()");
|
|
65
|
-
return this.handle;
|
|
67
|
+
return this.handle ?? { sandboxId: "(unconnected)", provider: PROVIDER, mountPath: this.cfg.mountPath };
|
|
66
68
|
}
|
|
67
69
|
async connect(config) {
|
|
68
70
|
if (config?.snapshotId)
|
|
69
71
|
return unsupported("connect(snapshotId)");
|
|
70
72
|
if (this.destroyed)
|
|
71
73
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
|
|
72
|
-
if (this.
|
|
73
|
-
return { ok: false, error: this.
|
|
74
|
+
if (this.connectRefusal)
|
|
75
|
+
return { ok: false, error: this.connectRefusal };
|
|
74
76
|
if (config?.abortSignal?.aborted)
|
|
75
77
|
return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted") };
|
|
76
78
|
if (this.containerId)
|
|
@@ -130,20 +132,48 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
130
132
|
await this.forceRemove().catch(() => { });
|
|
131
133
|
return { ok: false, error: home };
|
|
132
134
|
}
|
|
135
|
+
await this.floor.attach(this.floorTransport(), { origin: "created", workspaceRoot: this.cfg.mountPath });
|
|
133
136
|
if (this.destroyed) {
|
|
134
137
|
await this.forceRemove().catch(() => { });
|
|
135
138
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
|
|
136
139
|
}
|
|
140
|
+
const filled = await this.fillWorkspace();
|
|
141
|
+
if (!filled.ok || this.destroyed) {
|
|
142
|
+
await this.forceRemove().catch(() => { });
|
|
143
|
+
if (!filled.ok && filled.standing)
|
|
144
|
+
this.connectRefusal = filled.error;
|
|
145
|
+
return { ok: false, error: filled.ok ? new RemoteExecutionError("connect_failed", "execution env destroyed during connect") : filled.error };
|
|
146
|
+
}
|
|
137
147
|
this.containerId = run.value.stdout.trim() || this.containerName;
|
|
138
148
|
this.handle = { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
|
|
139
149
|
if (config?.secrets?.length) {
|
|
140
150
|
}
|
|
141
151
|
return ok(this.handle);
|
|
142
152
|
}
|
|
153
|
+
fillWorkspace() {
|
|
154
|
+
return this.cfg.workspaceFill === undefined ? Promise.resolve({ ok: true }) : this.cfg.workspaceFill.fill(this.fillChannel(), this.cfg.mountPath, "minted");
|
|
155
|
+
}
|
|
156
|
+
fillChannel() {
|
|
157
|
+
const run = async (command, input) => {
|
|
158
|
+
const argv = this.startArgv(ADAPTER_START, command, { shell: "/bin/sh", ...(input !== undefined ? { stdin: true } : {}) });
|
|
159
|
+
if (!argv.ok)
|
|
160
|
+
return { ok: false, error: argv.error.message };
|
|
161
|
+
const r = await this.docker(argv.value, { timeoutMs: this.cfg.commandTimeoutMs, ...(input !== undefined ? { input } : {}) });
|
|
162
|
+
return r.ok ? { ok: true, exitCode: r.value.exitCode, stdout: r.value.stdout, stderr: r.value.stderr } : { ok: false, error: r.error.message };
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
run: (command) => run(command),
|
|
166
|
+
write: async (abs, bytes) => {
|
|
167
|
+
const r = await run(`cat > ${shellQuote(abs)}`, bytes);
|
|
168
|
+
return !r.ok ? r : r.exitCode === 0 ? { ok: true } : { ok: false, error: r.stderr.trim() || `exit ${r.exitCode}` };
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
143
172
|
async homeVerdict(signal) {
|
|
144
173
|
if (!this.homeSeat.asks)
|
|
145
174
|
return undefined;
|
|
146
|
-
const
|
|
175
|
+
const argv = this.startArgv(ADAPTER_START, LANE_HOME_PROBE_COMMAND);
|
|
176
|
+
const probe = argv.ok ? await this.docker(argv.value, { signal, timeoutMs: this.cfg.controlTimeoutMs }) : argv;
|
|
147
177
|
if (!probe.ok && probe.error.code === "aborted")
|
|
148
178
|
return new RemoteExecutionError("aborted", "connect aborted while reading the environment's home");
|
|
149
179
|
const verdict = this.homeSeat.settle(probe.ok && probe.value.exitCode === 0 ? { ok: true, stdout: probe.value.stdout } : { ok: false, reason: probe.ok ? `exit ${probe.value.exitCode}` : probe.error.message });
|
|
@@ -151,7 +181,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
151
181
|
return undefined;
|
|
152
182
|
const error = new RemoteExecutionError("connect_failed", verdict.refusal);
|
|
153
183
|
if (verdict.answered)
|
|
154
|
-
this.
|
|
184
|
+
this.connectRefusal = error;
|
|
155
185
|
return error;
|
|
156
186
|
}
|
|
157
187
|
async suspendVM(_options) {
|
|
@@ -166,8 +196,8 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
166
196
|
async reconnect(sessionToken) {
|
|
167
197
|
if (this.destroyed)
|
|
168
198
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
|
|
169
|
-
if (this.
|
|
170
|
-
return { ok: false, error: this.
|
|
199
|
+
if (this.connectRefusal)
|
|
200
|
+
return { ok: false, error: this.connectRefusal };
|
|
171
201
|
if (sessionToken && sessionToken !== this.containerName) {
|
|
172
202
|
return { ok: false, error: new RemoteExecutionError("connect_failed", `reconnect token '${String(sessionToken)}' does not match this env's container '${this.containerName}'`) };
|
|
173
203
|
}
|
|
@@ -183,8 +213,8 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
183
213
|
const home = this.containerId === undefined ? await this.homeVerdict() : undefined;
|
|
184
214
|
if (home !== undefined)
|
|
185
215
|
return { ok: false, error: home };
|
|
186
|
-
if (this.destroyed || this.
|
|
187
|
-
return { ok: false, error: this.
|
|
216
|
+
if (this.destroyed || this.connectRefusal)
|
|
217
|
+
return { ok: false, error: this.connectRefusal ?? new RemoteExecutionError("connect_failed", "execution env destroyed during reconnect") };
|
|
188
218
|
this.containerId = this.containerName;
|
|
189
219
|
this.handle ??= { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
|
|
190
220
|
return ok(this.handle);
|
|
@@ -199,6 +229,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
199
229
|
this.connecting = undefined;
|
|
200
230
|
this.containerId = undefined;
|
|
201
231
|
await inflight?.catch(() => { });
|
|
232
|
+
this.floor.dispose();
|
|
202
233
|
await this.forceRemove().catch(() => { });
|
|
203
234
|
}
|
|
204
235
|
async cleanup() {
|
|
@@ -213,8 +244,10 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
213
244
|
const ready = await this.ensureConnectedExec();
|
|
214
245
|
if (!ready.ok)
|
|
215
246
|
return ready;
|
|
216
|
-
const execArgs = this.
|
|
217
|
-
|
|
247
|
+
const execArgs = this.startArgv(engineStart(options?.readFloor), command, { cwd: options?.cwd, env: options?.env });
|
|
248
|
+
if (!execArgs.ok)
|
|
249
|
+
return execArgs;
|
|
250
|
+
const r = await this.docker(execArgs.value, {
|
|
218
251
|
signal: options?.abortSignal,
|
|
219
252
|
timeoutMs: options?.timeout != null ? options.timeout * 1000 : this.cfg.commandTimeoutMs,
|
|
220
253
|
onStdout: options?.onStdout,
|
|
@@ -228,8 +261,10 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
228
261
|
const ready = await this.ensureConnectedExec();
|
|
229
262
|
if (!ready.ok)
|
|
230
263
|
throw new RemoteExecutionError("connect_failed", ready.error.message, ready.error);
|
|
231
|
-
const execArgs = this.
|
|
232
|
-
|
|
264
|
+
const execArgs = this.startArgv(engineStart(undefined), command, { cwd: options?.cwd, env: options?.env });
|
|
265
|
+
if (!execArgs.ok)
|
|
266
|
+
throw new RemoteExecutionError("unsupported", execArgs.error.message, execArgs.error);
|
|
267
|
+
const child = this.spawnFn(this.cfg.dockerPath, this.withHostFlag(execArgs.value), { stdio: ["ignore", "pipe", "pipe"], env: this.cliEnv() });
|
|
233
268
|
const queue = [];
|
|
234
269
|
let finished = false;
|
|
235
270
|
let failure;
|
|
@@ -361,7 +396,8 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
361
396
|
if (!ready.ok)
|
|
362
397
|
return ready;
|
|
363
398
|
const abs = this.resolve(p);
|
|
364
|
-
const
|
|
399
|
+
const argv = this.startArgv(ADAPTER_START, `cat ${shellQuote(abs)}`);
|
|
400
|
+
const r = argv.ok ? await this.docker(argv.value, { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, binary: true }) : argv;
|
|
365
401
|
if (!r.ok)
|
|
366
402
|
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
367
403
|
if (r.value.exitCode !== 0)
|
|
@@ -423,7 +459,10 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
423
459
|
if (!c.ok)
|
|
424
460
|
return { ok: false, error: new ExecutionError("shell_unavailable", `local-docker connect failed: ${c.error.message}`, c.error) };
|
|
425
461
|
}
|
|
426
|
-
|
|
462
|
+
const argv = this.startArgv(ADAPTER_START, cmd, { stdin: input !== undefined });
|
|
463
|
+
if (!argv.ok)
|
|
464
|
+
return argv;
|
|
465
|
+
return this.docker(argv.value, { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, ...(input !== undefined ? { input } : {}) });
|
|
427
466
|
}
|
|
428
467
|
async fileInfo(p, abortSignal) {
|
|
429
468
|
return this.posixFs.statFileInfo(p, abortSignal);
|
|
@@ -461,18 +500,68 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
461
500
|
withHostFlag(args) {
|
|
462
501
|
return this.cfg.dockerHost ? ["-H", this.cfg.dockerHost, ...args] : args;
|
|
463
502
|
}
|
|
464
|
-
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
503
|
+
startArgv(start, command, o = {}) {
|
|
504
|
+
const launch = this.floor.wrap(start);
|
|
505
|
+
if (!launch.ok)
|
|
506
|
+
return launch;
|
|
507
|
+
const args = o.stdin ? ["exec", "-i"] : ["exec"];
|
|
508
|
+
args.push(...dockerIdentityFlags(launch.value));
|
|
509
|
+
if (o.containerDefaults !== true) {
|
|
510
|
+
args.push("-w", o.cwd != null ? this.resolve(o.cwd) : this.cwd);
|
|
511
|
+
const mergedEnv = { ...(this.cfg.env ?? {}), ...(o.env ?? {}) };
|
|
512
|
+
for (const [k, v] of Object.entries(mergedEnv))
|
|
513
|
+
args.push("-e", `${k}=${v}`);
|
|
514
|
+
}
|
|
515
|
+
args.push(this.containerName, ...floorArgv(launch.value, [o.shell ?? this.containerShell, "-c", command]));
|
|
516
|
+
return ok(args);
|
|
517
|
+
}
|
|
518
|
+
floorTransport() {
|
|
519
|
+
const control = { timeoutMs: this.cfg.controlTimeoutMs };
|
|
520
|
+
return {
|
|
521
|
+
bootstrap: async (command) => {
|
|
522
|
+
const argv = this.startArgv(BOOTSTRAP_START, command, { shell: "/bin/sh" });
|
|
523
|
+
if (!argv.ok)
|
|
524
|
+
return { ok: false, error: argv.error.message };
|
|
525
|
+
const r = await this.docker(argv.value, control);
|
|
526
|
+
return r.ok ? { ok: true, exitCode: r.value.exitCode, stdout: r.value.stdout, stderr: r.value.stderr } : { ok: false, error: r.error.message };
|
|
527
|
+
},
|
|
528
|
+
place: async (dest, bytes) => {
|
|
529
|
+
const dir = path.posix.dirname(dest);
|
|
530
|
+
const tmp = `${dest}.${randomBytes(6).toString("hex")}`;
|
|
531
|
+
const cmd = `umask 022 && mkdir -p ${shellQuote(dir)} && cat > ${shellQuote(tmp)} && chmod 0755 ${shellQuote(tmp)} && mv -f ${shellQuote(tmp)} ${shellQuote(dest)}`;
|
|
532
|
+
const argv = this.startArgv(BOOTSTRAP_START, cmd, { shell: "/bin/sh", stdin: true });
|
|
533
|
+
if (!argv.ok)
|
|
534
|
+
return { ok: false, error: argv.error.message };
|
|
535
|
+
const r = await this.docker(argv.value, { ...control, input: bytes });
|
|
536
|
+
if (!r.ok)
|
|
537
|
+
return { ok: false, error: r.error.message };
|
|
538
|
+
return r.value.exitCode === 0 ? { ok: true } : { ok: false, error: r.value.stderr.trim() || `exit ${r.value.exitCode}` };
|
|
539
|
+
},
|
|
540
|
+
openRelay: (identity, command, events) => {
|
|
541
|
+
const args = ["exec", "-i", ...dockerIdentityFlags({ mode: "control", identity }), this.containerName, "/bin/sh", "-c", command];
|
|
542
|
+
const child = this.spawnFn(this.cfg.dockerPath, this.withHostFlag(args), { stdio: ["pipe", "pipe", "pipe"], env: this.cliEnv() });
|
|
543
|
+
let closed = false;
|
|
544
|
+
const close = (why) => {
|
|
545
|
+
if (closed)
|
|
546
|
+
return;
|
|
547
|
+
closed = true;
|
|
548
|
+
events.closed(why);
|
|
549
|
+
};
|
|
550
|
+
child.stdout?.setEncoding("utf8");
|
|
551
|
+
child.stdout?.on("data", (d) => events.data(d));
|
|
552
|
+
child.stderr?.on("data", () => { });
|
|
553
|
+
child.stdin?.on("error", () => { });
|
|
554
|
+
child.on("error", (e) => close(`docker exec failed: ${e.message}`));
|
|
555
|
+
child.on("close", (code, signal) => close(`relay exited (${code ?? signal ?? "unknown"})`));
|
|
556
|
+
return {
|
|
557
|
+
send: (line) => void child.stdin?.write(line),
|
|
558
|
+
close: () => {
|
|
559
|
+
child.stdin?.end();
|
|
560
|
+
child.kill("SIGKILL");
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
},
|
|
564
|
+
};
|
|
476
565
|
}
|
|
477
566
|
docker(args, opts) {
|
|
478
567
|
return new Promise((resolve) => {
|
|
@@ -571,7 +660,8 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
571
660
|
}
|
|
572
661
|
async detectContainerShell() {
|
|
573
662
|
try {
|
|
574
|
-
const
|
|
663
|
+
const argv = this.startArgv(ADAPTER_START, "command -v bash", { shell: "/bin/sh", containerDefaults: true });
|
|
664
|
+
const r = argv.ok ? await this.docker(argv.value, { timeoutMs: this.cfg.controlTimeoutMs }) : argv;
|
|
575
665
|
if (r.ok && r.value.exitCode === 0 && r.value.stdout.trim().length > 0)
|
|
576
666
|
this.containerShell = "bash";
|
|
577
667
|
}
|
|
@@ -616,6 +706,10 @@ function errMsg(e) {
|
|
|
616
706
|
return e instanceof Error ? e.message : String(e);
|
|
617
707
|
}
|
|
618
708
|
export function localDockerExecutionEnvFactory(config, deps = {}) {
|
|
619
|
-
|
|
709
|
+
const { workspaceFillOf, ...base } = config;
|
|
710
|
+
return (ctx) => {
|
|
711
|
+
const workspaceFill = workspaceFillOf?.(ctx);
|
|
712
|
+
return new RemoteLocalDockerExecutionEnv({ ...base, id: base.id ?? ctx.taskId ?? ctx.sessionId, ...(workspaceFill !== undefined ? { workspaceFill } : {}) }, deps);
|
|
713
|
+
};
|
|
620
714
|
}
|
|
621
715
|
//# sourceMappingURL=remote-env-local-docker.js.map
|
|
@@ -2,7 +2,7 @@ import type { TaskStatus } from "@sema-agent/core";
|
|
|
2
2
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
3
3
|
import type { Pool as PgPool } from "pg";
|
|
4
4
|
import { type UsageRow } from "../usage-analytics.js";
|
|
5
|
-
import { type ClaimFenced, type CreateRunOutcome, type NewRunMeta, type RunIdemBinding, type RunListQuery, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
|
|
5
|
+
import { type ClaimFenced, type CreateRunOutcome, type FencedRun, type NewRunMeta, type RunIdemBinding, type RunListQuery, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef, type SettledLegMemoryObservation } from "./store-contracts.js";
|
|
6
6
|
import { type SqlDriver, type SqlTxConn } from "./sql-driver.js";
|
|
7
7
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
8
8
|
import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState } from "../workspace-space-commit.js";
|
|
@@ -171,7 +171,7 @@ export declare class SqlRunStore {
|
|
|
171
171
|
/**
|
|
172
172
|
* 认领当写栅栏用(进程内两孪生的契约 = `store-contracts.ts` 的 `InProcessClaimFence`;本孪生是它的事务形):一个事务 ——
|
|
173
173
|
* ① 锁读这条 run 行且它在跑(`task_run … FOR UPDATE`);② 锁读会话的认领行且它是这条 run 的(`task_active … FOR UPDATE`);
|
|
174
|
-
* ③ 刷新这条 run 的活性戳;④ `write`
|
|
174
|
+
* ③ 刷新这条 run 的活性戳;④ `write` 在**同一个事务连接**上跑(另收锁住那一行的 {@link FencedRun});提交即放锁。任一没读到 ⇒ 不持有,不刷新、`write` 不调。`write` 抛或提交失败 ⇒ 整只抛。
|
|
175
175
|
* 🔴 **取锁序 = task_run → task_active**,与 `setTerminal` / `reapStale` / `reapSuspended` 同序(它们先改 run 行、再删认领行):两行用两条语句按序锁,
|
|
176
176
|
* 不用连表锁读(那一形的先后由计划器定,与终局 / 收割相反时两只事务各攥一行成环)。`createRun` 先锁根会话行再插认领行(本序从不碰 `session_meta`)、
|
|
177
177
|
* `deleteBySession` 先锁根会话行、见认领行在就中止,都不与本序成环。锁住之后:收割的改行 / 终局与收割的删认领 / 别人的建行都排在本事务之后,收割器提交后重判 `updated_at` 看到的是刚刷的。
|
|
@@ -180,7 +180,7 @@ export declare class SqlRunStore {
|
|
|
180
180
|
* 🔴 **`write` 不许写 `session_meta`**:本事务持着 `task_active`,而 `createRun` 与改根行的写者都先锁根会话行再碰 `task_*`(全局锁序见 {@link createRun})——
|
|
181
181
|
* 回调去锁根行就是反序,与等着本会话认领的 `createRun` 各攥一行成环。门:`test/submission-root-contract.test.ts` 扫每个调用点的回调体。
|
|
182
182
|
*/
|
|
183
|
-
whileHoldingClaim<T>(sessionId: string, taskId: string, write: (tx: SqlTxConn) => Promise<T>): Promise<ClaimFenced<T>>;
|
|
183
|
+
whileHoldingClaim<T>(sessionId: string, taskId: string, write: (tx: SqlTxConn, run: FencedRun) => Promise<T>): Promise<ClaimFenced<T>>;
|
|
184
184
|
/** The active run's taskId for a session (the `task_active` claim) — lets resume find the suspended run row. */
|
|
185
185
|
getActiveTaskId(sessionId: string): Promise<string | undefined>;
|
|
186
186
|
/** Shared row→record mapper — byte-identical between dialects (both drivers hand back the same column
|
|
@@ -228,6 +228,14 @@ export declare class SqlRunStore {
|
|
|
228
228
|
* 两方言只差占位符(动态子句为零,不像 listRuns/listSessions 那样需要各自的构造器臂)。
|
|
229
229
|
*/
|
|
230
230
|
latestRunForSession(sessionId: string): Promise<LatestRunRef | undefined>;
|
|
231
|
+
/**
|
|
232
|
+
* [ref] —— 这个会话**最近一条已结腿**与它的记忆观测(规则属主 = `store-contracts.ts` 的 {@link SettledLegMemoryObservation};
|
|
233
|
+
* 文件 / 内存两店是它的孪生)。排序口径与 {@link latestRunForSession} 逐字同一句;已结词集按本文件头注的惯例写字面(两方言并排可读),
|
|
234
|
+
* 与属主谓词 `isTerminalRunStatus` 的同源由方言对 parity 门钉(`test/source-hygiene-gate.test.ts` 的语句规则 `settled-leg-observation-read`)。
|
|
235
|
+
* 同一条索引路径(`idx_task_run_session_status` 的 `(session_id, status)` 前缀),**不新加索引**。
|
|
236
|
+
* 结果列走 {@link rowToRun} 同一只解码口与同一个失败方向(读不出 ⇒ 拒,不把一条结了的腿读成「没有观测」)。
|
|
237
|
+
*/
|
|
238
|
+
latestMemoryObservationForSession(sessionId: string): Promise<SettledLegMemoryObservation | undefined>;
|
|
231
239
|
/** GET /v1/sessions (CC /resume picker): DISTINCT sessions newest-first by last activity, keyset-paginated on
|
|
232
240
|
* (last_activity, session_id). Aggregates task_run by session_id — one window pass picks the latest run per
|
|
233
241
|
* session (rn=1) for the preview/status and rolls up first/last/count. Owner-scoped when set (the per-user view).
|
|
@@ -6,7 +6,7 @@ import { pgSanitizeText } from "./pg-safe-json.js";
|
|
|
6
6
|
import { toIso as iso } from "./sql-row-helpers.js";
|
|
7
7
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
8
8
|
import { createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonObjectColumn, readNonNullJsonValueColumn } from "./sql-json-column.js";
|
|
9
|
-
import { RUN_HOLDER_DEATH_CODES, RUN_STALLED_ERROR, readLegacyHolderDeathCode } from "./store-contracts.js";
|
|
9
|
+
import { RUN_HOLDER_DEATH_CODES, RUN_STALLED_ERROR, readLegacyHolderDeathCode, settledLegObservationOf } from "./store-contracts.js";
|
|
10
10
|
import { notifyRunTerminal, runTerminalLogLevel } from "../observability/run-terminal.js";
|
|
11
11
|
import { mysqlDriver, pgDriver, dialectJsonEncoder, SQL_DB_NOW_MS, sqlDbNowMs } from "./sql-driver.js";
|
|
12
12
|
import { isDupKeyError } from "./sql-errors.js";
|
|
@@ -282,15 +282,16 @@ export class SqlRunStore {
|
|
|
282
282
|
async whileHoldingClaim(sessionId, taskId, write) {
|
|
283
283
|
let fenced = { held: false };
|
|
284
284
|
await this.tx(async (conn) => {
|
|
285
|
-
const run = await conn.query(this.q("SELECT task_id FROM task_run WHERE task_id = ? AND status = 'running' FOR UPDATE", "SELECT task_id FROM task_run WHERE task_id = $1 AND status = 'running' FOR UPDATE"), [taskId]);
|
|
286
|
-
|
|
285
|
+
const run = await conn.query(this.q("SELECT task_id, instance_id FROM task_run WHERE task_id = ? AND status = 'running' FOR UPDATE", "SELECT task_id, instance_id FROM task_run WHERE task_id = $1 AND status = 'running' FOR UPDATE"), [taskId]);
|
|
286
|
+
const row = run.rows[0];
|
|
287
|
+
if (row === undefined)
|
|
287
288
|
return;
|
|
288
289
|
const claim = await conn.query(this.q("SELECT task_id FROM task_active WHERE session_id = ? FOR UPDATE", "SELECT task_id FROM task_active WHERE session_id = $1 FOR UPDATE"), [sessionId]);
|
|
289
290
|
const holder = claim.rows[0];
|
|
290
291
|
if (holder === undefined || String(holder.task_id) !== taskId)
|
|
291
292
|
return;
|
|
292
293
|
await conn.query(this.q("UPDATE task_run SET updated_at = ? WHERE task_id = ?", "UPDATE task_run SET updated_at = $1 WHERE task_id = $2"), [new Date(), taskId]);
|
|
293
|
-
fenced = { held: true, value: await write(conn) };
|
|
294
|
+
fenced = { held: true, value: await write(conn, { instanceId: row.instance_id ?? null }) };
|
|
294
295
|
});
|
|
295
296
|
return fenced;
|
|
296
297
|
}
|
|
@@ -412,6 +413,14 @@ export class SqlRunStore {
|
|
|
412
413
|
const r = rows[0];
|
|
413
414
|
return r === undefined ? undefined : { taskId: String(r.task_id), owner: r.owner ?? null, createdAt: iso(r.created_at) };
|
|
414
415
|
}
|
|
416
|
+
async latestMemoryObservationForSession(sessionId) {
|
|
417
|
+
const { rows } = await this.db.query(this.q("SELECT task_id, " + jsonColumnText("mysql", "result") + " FROM task_run WHERE session_id = ? AND status IN ('completed','failed','blocked') ORDER BY created_at DESC, task_id DESC LIMIT 1", "SELECT task_id, " + jsonColumnText("pg", "result") + " FROM task_run WHERE session_id = $1 AND status IN ('completed','failed','blocked') ORDER BY created_at DESC, task_id DESC LIMIT 1"), [sessionId]);
|
|
418
|
+
const r = rows[0];
|
|
419
|
+
if (r === undefined)
|
|
420
|
+
return undefined;
|
|
421
|
+
const taskId = String(r.task_id);
|
|
422
|
+
return settledLegObservationOf(taskId, refusing("read a settled leg as if it had observed no memory", () => decodeResultColumn(r.result, taskId)) ?? null);
|
|
423
|
+
}
|
|
415
424
|
async listSessions(opts) {
|
|
416
425
|
if (this.db.dialect === "mysql") {
|
|
417
426
|
const params = [];
|