@sema-agent/server 7.139.0 → 7.140.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 +181 -0
- package/USAGE.md +19 -13
- package/dist/adoption/runner.js +1 -1
- package/dist/adoption/sql.js +1 -1
- package/dist/approval-card.d.ts +6 -6
- package/dist/bench/s1/live-deps.js +3 -2
- package/dist/bench/s1/row.d.ts +5 -2
- package/dist/bench/s1/row.js +2 -22
- package/dist/boot/adoption.js +1 -1
- package/dist/boot/execution-env.js +12 -10
- package/dist/boot/execution-lane.d.ts +8 -0
- package/dist/boot/execution-lane.js +6 -1
- package/dist/boot/memory-boundary.d.ts +14 -51
- package/dist/boot/memory-boundary.js +10 -20
- package/dist/boot/memory-consolidation.d.ts +15 -11
- package/dist/boot/memory-consolidation.js +23 -12
- package/dist/boot/resolve-spec.d.ts +3 -5
- package/dist/boot/resolve-spec.js +2 -4
- package/dist/boot/runner-deps.d.ts +3 -3
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/stage-03-store-backend.d.ts +2 -6
- package/dist/boot/stage-03-store-backend.js +2 -3
- package/dist/boot/stage-04-stores.d.ts +2 -6
- package/dist/boot/stage-04-stores.js +5 -5
- package/dist/boot/stage-05-execution-env.d.ts +2 -6
- package/dist/boot/stage-06-runners.d.ts +2 -6
- package/dist/boot/stage-06-runners.js +2 -2
- package/dist/boot/stage-07-capability-layer.d.ts +2 -6
- package/dist/boot/stage-08-reapers.d.ts +2 -6
- package/dist/boot/stage-09-leader.d.ts +2 -6
- package/dist/boot/stage-10-http-server.d.ts +2 -6
- package/dist/boot/stage-10-http-server.js +5 -4
- package/dist/boot/stores.d.ts +3 -6
- package/dist/boot/stores.js +27 -40
- package/dist/boot/task-list-lane.d.ts +1 -1
- package/dist/boot/task-list-lane.js +1 -1
- package/dist/boot/web-admin.js +35 -9
- package/dist/capabilities/execution-lane.d.ts +2 -2
- package/dist/config-catalog.js +2 -1
- package/dist/config-provider.d.ts +14 -13
- package/dist/config-provider.js +24 -18
- package/dist/config-types.d.ts +11 -2
- package/dist/config.js +2 -1
- package/dist/delegated-session-owner.d.ts +2 -1
- package/dist/delegated-session-owner.js +1 -1
- package/dist/deployment-shape.d.ts +9 -8
- package/dist/deployment-shape.js +4 -4
- package/dist/execution-lane-caps.d.ts +97 -106
- package/dist/execution-lane-caps.js +67 -83
- package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
- package/dist/http/memory-compliance-failure-reply.js +5 -0
- package/dist/http/memory-layer-reply.d.ts +30 -0
- package/dist/http/memory-layer-reply.js +24 -0
- package/dist/http/resume-legs.js +13 -16
- package/dist/http/retired-service-deps.d.ts +1 -1
- package/dist/http/retired-service-deps.js +1 -1
- package/dist/http/routes/adoption.js +1 -1
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-compliance.js +8 -1
- package/dist/http/routes/memory-consolidation.js +12 -1
- package/dist/http/routes/memory-origin.js +14 -2
- package/dist/http/routes/memory-policy.js +5 -0
- package/dist/http/routes/sessions.js +28 -11
- package/dist/http/routes/tasks.js +4 -2
- package/dist/http/server.d.ts +5 -25
- package/dist/http/server.js +2 -2
- package/dist/identity-source.d.ts +1 -1
- package/dist/identity-source.js +1 -1
- package/dist/lane-home-probe.d.ts +68 -42
- package/dist/lane-home-probe.js +53 -30
- package/dist/leader/wire.js +5 -4
- package/dist/memory-bundle-engine.d.ts +16 -9
- package/dist/memory-bundle-engine.js +29 -6
- package/dist/memory-operator-faces.d.ts +85 -30
- package/dist/memory-operator-faces.js +136 -30
- package/dist/memory-project-layer.d.ts +3 -21
- package/dist/memory-project-layer.js +11 -8
- package/dist/memory-scope-stores.d.ts +73 -0
- package/dist/memory-scope-stores.js +86 -0
- package/dist/memory-scope.d.ts +29 -5
- package/dist/memory-scope.js +16 -4
- package/dist/memory-sync-client.d.ts +20 -7
- package/dist/memory-sync-client.js +31 -13
- package/dist/plugins/adoption-log-sql.js +4 -4
- package/dist/plugins/approval-ask-store-sql.js +14 -14
- package/dist/plugins/approval-nonce-store.js +4 -4
- package/dist/plugins/blob-backend.d.ts +4 -4
- package/dist/plugins/blob-backend.js +7 -7
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/checkpoint-index-columns.js +2 -2
- package/dist/plugins/checkpoint-store-sql.js +16 -16
- package/dist/plugins/device-store-sql.js +19 -19
- package/dist/plugins/ensure-index.d.ts +1 -2
- package/dist/plugins/execution-placement-sql.d.ts +8 -8
- package/dist/plugins/execution-placement-sql.js +8 -8
- package/dist/plugins/file-history-store-sql.d.ts +2 -2
- package/dist/plugins/file-history-store-sql.js +4 -4
- package/dist/plugins/image-bake-store-sql.js +9 -9
- package/dist/plugins/image-index-sql.js +11 -11
- package/dist/plugins/leader-run-store-sql.js +4 -4
- package/dist/plugins/mailbox-store-sql.d.ts +2 -1
- package/dist/plugins/mailbox-store-sql.js +4 -4
- package/dist/plugins/memory-engine-tidb.js +5 -5
- package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
- package/dist/plugins/memory-sync-store-tidb.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +10 -9
- package/dist/plugins/one-time-migrations.js +36 -36
- package/dist/plugins/org-delivery-quota-store.js +2 -2
- package/dist/plugins/outcome-ledger-sql.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
- package/dist/plugins/permission-rule-store-sql.js +16 -16
- package/dist/plugins/remote-env-adb.d.ts +7 -24
- package/dist/plugins/remote-env-adb.js +22 -5
- package/dist/plugins/remote-env-device.d.ts +2 -28
- package/dist/plugins/remote-env-device.js +2 -1
- package/dist/plugins/remote-env-e2b.d.ts +18 -26
- package/dist/plugins/remote-env-e2b.js +51 -4
- package/dist/plugins/remote-env-file-error.d.ts +1 -1
- package/dist/plugins/remote-env-file-error.js +8 -2
- package/dist/plugins/remote-env-host.d.ts +19 -35
- package/dist/plugins/remote-env-host.js +26 -3
- package/dist/plugins/remote-env-k8s.d.ts +3 -14
- package/dist/plugins/remote-env-k8s.js +3 -2
- package/dist/plugins/remote-env-local-docker.d.ts +4 -15
- package/dist/plugins/remote-env-local-docker.js +5 -4
- package/dist/plugins/remote-env-ssh.d.ts +4 -24
- package/dist/plugins/remote-env-ssh.js +4 -3
- package/dist/plugins/resume-anchor-store-sql.js +2 -2
- package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
- package/dist/plugins/retention-lane-store-sql.js +2 -2
- package/dist/plugins/retention-store-sql.d.ts +1 -1
- package/dist/plugins/retention-store-sql.js +6 -6
- package/dist/plugins/run-store-sql.js +15 -15
- package/dist/plugins/schema-consistency.d.ts +6 -6
- package/dist/plugins/schema-consistency.js +1 -1
- package/dist/plugins/schema-ensure-legs.d.ts +2 -3
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/schema-live-columns.d.ts +1 -1
- package/dist/plugins/session-capture-record-store-sql.js +1 -1
- package/dist/plugins/session-rules-column.d.ts +1 -1
- package/dist/plugins/session-rules-column.js +1 -1
- package/dist/plugins/session-workspace-cell.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.js +1 -1
- package/dist/plugins/shared-memory-store-sql.js +2 -2
- package/dist/plugins/sql-driver.d.ts +10 -5
- package/dist/plugins/sql-driver.js +6 -6
- package/dist/plugins/sql-errors.js +1 -1
- package/dist/plugins/store-backend.d.ts +4 -6
- package/dist/plugins/store-backend.js +6 -6
- package/dist/plugins/task-list-store-sql.js +11 -11
- package/dist/plugins/tidb-pool.js +5 -5
- package/dist/plugins/tidb-session-storage.js +4 -4
- package/dist/plugins/tidb-session-store.js +13 -13
- package/dist/plugins/tool-result-store-sql.d.ts +2 -2
- package/dist/plugins/tool-result-store-sql.js +3 -3
- package/dist/plugins/user-input-ledger-sql.js +2 -2
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.js +19 -19
- package/dist/plugins/write-behind-counter.d.ts +2 -3
- package/dist/plugins/write-behind-counter.js +5 -5
- package/dist/resource-suspend.d.ts +14 -12
- package/dist/resource-suspend.js +1 -1
- package/dist/resource-window.d.ts +4 -3
- package/dist/run-local.js +14 -19
- package/dist/runs.d.ts +8 -4
- package/dist/runs.js +49 -65
- package/dist/session-incarnation.d.ts +1 -1
- package/dist/session-incarnation.js +1 -1
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/sql-engine-posture.d.ts +1 -1
- package/dist/sql-engine-posture.js +1 -1
- package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
- package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
- package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
- package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
- package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
- package/dist/task-a2a.js +5 -6
- package/dist/task-mcp.d.ts +20 -7
- package/dist/task-mcp.js +17 -9
- package/dist/terminal.d.ts +27 -0
- package/dist/terminal.js +6 -0
- package/dist/trace/redact.d.ts +3 -1
- package/dist/trace/redact.js +80 -28
- package/dist/trace/self-minted-wire.d.ts +58 -0
- package/dist/trace/self-minted-wire.js +16 -0
- package/dist/web-admin-client/candidate-gate.d.ts +11 -7
- package/dist/web-admin-client/candidate-gate.js +1 -1
- package/dist/web-admin-client/http-client.d.ts +5 -4
- package/dist/web-admin-client/http-client.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +7 -17
- package/dist/web-admin-client/read-warnings.js +5 -9
- package/package.json +2 -2
- package/wire-changes/server@7.140.0.json +184 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FileError, type InheritEnvPolicy, ExecutionError, RemoteExecutionError, SchedulerError, type SchedulerCapability, type ScheduledIntent, type SchedulerContext, type ScheduledTaskId, type ScheduledTaskSummary, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type BoundFileObject, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, BackgroundShellError, type BackgroundShellCapability, type BackgroundShellId, type BackgroundHardKillOutcome, type BackgroundPoll, type BackgroundSpawnOptions, type SecretEnvFinding } from "@sema-agent/core";
|
|
2
|
+
import { type LaneHomeConfig } from "../lane-home-probe.js";
|
|
2
3
|
/** Options accepted by `ExecutionEnv.exec` (core does not re-export the type; derive it from the seam). */
|
|
3
4
|
type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
4
5
|
/** SIGTERM → grace → SIGKILL (no host isolation). 🔴 宽限的**唯一属主**:本 driver 的 `kill` 梯子用它排升级定时器;会话级收敛
|
|
@@ -6,7 +7,7 @@ type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
|
6
7
|
* 这把梯子的用户面宽限取消),不另抄数。启动期读一次;`0` 被 `numEnvOr` 读作缺席(回落 1000),要几乎不等写 `1`。 */
|
|
7
8
|
export declare const HOST_BG_KILL_GRACE_MS: number;
|
|
8
9
|
/** Construction config for {@link RemoteHostExecutionEnv}. */
|
|
9
|
-
export interface HostEnvConfig {
|
|
10
|
+
export interface HostEnvConfig extends LaneHomeConfig {
|
|
10
11
|
/**
|
|
11
12
|
* Base directory under which this adapter creates its per-task working directory. Default {@link os.tmpdir}.
|
|
12
13
|
* The actual workspace is `<workspaceBase>/sema-host-<id>` and is `rm -rf`'d on {@link destroy}.
|
|
@@ -24,15 +25,6 @@ export interface HostEnvConfig {
|
|
|
24
25
|
* own `process.env` is the default base (a local coding agent inherits the user's environment).
|
|
25
26
|
*/
|
|
26
27
|
env?: Record<string, string>;
|
|
27
|
-
/**
|
|
28
|
-
* [ref]④ / core [ref] —— 本执行环境的 **home 目录**(`ExecutionEnv.homeDir` 座,绝对路径,本环境自己的
|
|
29
|
-
* 命名空间里)。`~/…` 形权限规则(`Edit(~/.ssh/**)`)对**跑在本 env 里的调用**就以它为基。
|
|
30
|
-
* 缺席 ⇒ 本 env 声明「我没有 home」,core 对 `~/` 规则判 `unreadable`(fail-closed 的 ask),
|
|
31
|
-
* 绝不回落引擎进程自己的 home(那在远端腿上是另一个用户的另一个目录 —— 静默守错目录正是 [ref] 的病)。
|
|
32
|
-
* 值由 `boot/execution-env.ts` 从**唯一属主** `execution-lane-caps.ts` 的 `executionLaneHomeDir` 递进来;
|
|
33
|
-
* adapter 不自算(两处各算一份 = 422 门与引擎对同一条规则给相反答案)。
|
|
34
|
-
*/
|
|
35
|
-
homeDir?: string;
|
|
36
28
|
/** Per-command wall-clock bound (ms) when the caller gives none. Default {@link DEFAULT_COMMAND_TIMEOUT_MS}. */
|
|
37
29
|
commandTimeoutMs?: number;
|
|
38
30
|
/** Shell-child env policy — core's `InheritEnvPolicy`, read by core's `inheritedHostEnv` (the three words are judged
|
|
@@ -64,29 +56,8 @@ export interface HostEnvConfig {
|
|
|
64
56
|
backgroundShell?: boolean;
|
|
65
57
|
}
|
|
66
58
|
export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, SchedulerCapability, BackgroundShellCapability {
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
* 🔴 **[ref] / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
|
|
70
|
-
* 就是**命令真正跑的那块文件系统**的权威答案(worker 本机的 `fs.realpath`)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
|
|
71
|
-
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
72
|
-
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
73
|
-
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
74
|
-
* 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
75
|
-
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
76
|
-
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
77
|
-
*/
|
|
78
|
-
/** `sharesProcessTable`(core 7.57.0,必填):命令是本进程 spawn 的子进程(同一张进程表),引擎的模式杀守卫对它照 NodeExecutionEnv 一样问 `pgrep`。 */
|
|
79
|
-
/** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
|
|
80
|
-
readonly capabilities: {
|
|
81
|
-
isolation: false;
|
|
82
|
-
suspendable: false;
|
|
83
|
-
canonicalPathAuthoritative: true;
|
|
84
|
-
sharesProcessTable: true;
|
|
85
|
-
hostLocalPaths: true;
|
|
86
|
-
externalContentTarget: false;
|
|
87
|
-
io: true;
|
|
88
|
-
keepsReadFloor: false;
|
|
89
|
-
};
|
|
59
|
+
/** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。 */
|
|
60
|
+
readonly capabilities: import("@sema-agent/core").ExecutionEnvCapabilities;
|
|
90
61
|
/** R7 自唤醒: structural delegation to the injected scheduler backend. `hasScheduler(env)` checks BOTH the method
|
|
91
62
|
* presence (always, since we implement the interface) AND `schedulerCapabilities.supported===true` — which is only
|
|
92
63
|
* true when a backend was injected, so a host env without one stays INERT (the four scheduler tools don't mount). */
|
|
@@ -98,8 +69,13 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
98
69
|
list(ctx: SchedulerContext): Promise<Result<ScheduledTaskSummary[], SchedulerError>>;
|
|
99
70
|
/** Working directory; relative paths resolve against it (ExecutionEnv contract). Starts at the workspace root. */
|
|
100
71
|
cwd: string;
|
|
101
|
-
/** core `ExecutionEnv.homeDir`([ref])
|
|
102
|
-
|
|
72
|
+
/** core `ExecutionEnv.homeDir`([ref]):本机执行用户的 home —— 主张在场 ⇒ 主张(构造时与子进程拿到的 `HOME` 对证过),否则构造时读到的那个 `HOME`,见 {@link LaneHomeSeat}。 */
|
|
73
|
+
get homeDir(): string | undefined;
|
|
74
|
+
/** 家目录的座(主张在场 ⇒ 构造时对证 · 否则构造时实测)。读数 = {@link childHome}:本类自己组合给子进程的那份 env,不 spawn。 */
|
|
75
|
+
private readonly homeSeat;
|
|
76
|
+
/** 家目录:主张与子进程拿到的 `HOME` 对不上 ⇒ 这只环境的拒句;`shellReady` / `fsReady` / `connect` 三道门原样回它,一条命令、一次文件动作都不做。
|
|
77
|
+
* 读数是本类自己组合的 env(纯函数,同一份输入同一个答案)⇒ 构造时判一次、记一辈子。 */
|
|
78
|
+
private readonly homeRefusal;
|
|
103
79
|
/**
|
|
104
80
|
* core 7.49.0 [ref]:shell 子进程**真拿到的** `CDPATH` 条目(`[]` = 没有)—— 删除守卫按它解裸 `cd <dir>`。本车道的命令就是本机子进程,
|
|
105
81
|
* env 是本类自己组合的(继承姿态 + `cfg.env`),所以说得出:读同一只组合({@link childEnv} 的**读面**,不披露、不钉 shell),切法与 core
|
|
@@ -454,6 +430,14 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
454
430
|
* 引擎对点到自有凭据名的白名单在这里**抛**(spawn 时的拒;构造器已先问过同一只判)—— 子进程不起,拒句带名。
|
|
455
431
|
*/
|
|
456
432
|
private childEnv;
|
|
433
|
+
/**
|
|
434
|
+
* 家目录的读数:本类组合给子进程的那份 env 里的 `HOME`(组合层,不 spawn、不记剥除 —— 与 {@link cdPath} 同一只读面)。env 里没有 `HOME` ⇒ 读
|
|
435
|
+
* 账户的家目录(`os.userInfo().homedir`):那正是子进程的 shell 对缺席 `HOME` 的 `~` 展开(POSIX 取账户 passwd 行;win32 是账户的配置目录)。
|
|
436
|
+
* 不读 `os.homedir()`:它先读**服务进程自己的** `HOME` —— 子进程没继承到的那个值(对抗轮 r1 [high] 实测:服务 `HOME` 指别处 + `inheritEnv: []` 时两者分叉)。
|
|
437
|
+
* 账户查不到(无 passwd 行)⇒ 读不出(有主张 ⇒ 不开;没主张 ⇒ 座缺席 + warn)。
|
|
438
|
+
* 残余(引擎 `NodeExecutionEnv.homeDir` 注同一句):POSIX 上 `HOME` 缺席时 shell 里的 `$HOME` 字面量展开为空,与 `~` 不是一个值 —— 本读数按 `~` 记。
|
|
439
|
+
*/
|
|
440
|
+
private childHome;
|
|
457
441
|
private fileError;
|
|
458
442
|
}
|
|
459
443
|
/**
|
|
@@ -12,6 +12,8 @@ import { hostBackgroundShellEnabled, hostExecSpoolEnabled } from "../config.js";
|
|
|
12
12
|
import { IS_WIN32, resolveHostShell, hostShell, pinHostShell, spawnGroupOptions, killTreeHard, killTreeSoft, hardKillGroupNow, collapseWin32EnvKeys, adoptHostForegroundChild } from "./host-platform.js";
|
|
13
13
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
14
14
|
import { FileError, NodeExecutionEnv, inheritEnvPolicyProblem, ExecutionError, RemoteExecutionError, RollingTailBuffer, walkSymlinkChain, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
|
|
15
|
+
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
16
|
+
import { LaneHomeSeat } from "../lane-home-probe.js";
|
|
15
17
|
import { composeHostSubprocessEnv, hostSubprocessEnv, withheldFromHostSubprocess } from "../server-secret-env.js";
|
|
16
18
|
import { nameTooLongCode } from "./remote-env-file-error.js";
|
|
17
19
|
import { trackEphemeralHostWorkspace, untrackEphemeralHostWorkspace } from "./host-workspace-registry.js";
|
|
@@ -38,7 +40,7 @@ const UNSUPPORTED_SCHEDULER_CAPS = {
|
|
|
38
40
|
maxScheduledHorizonSec: 0,
|
|
39
41
|
};
|
|
40
42
|
export class RemoteHostExecutionEnv {
|
|
41
|
-
capabilities = {
|
|
43
|
+
capabilities = capabilitiesFor({ remoteExec: { provider: "host" } });
|
|
42
44
|
get schedulerCapabilities() {
|
|
43
45
|
return this.cfg.scheduler?.schedulerCapabilities ?? UNSUPPORTED_SCHEDULER_CAPS;
|
|
44
46
|
}
|
|
@@ -58,7 +60,11 @@ export class RemoteHostExecutionEnv {
|
|
|
58
60
|
: Promise.resolve({ ok: false, error: new SchedulerError("unsupported", "scheduler not enabled on this worker") });
|
|
59
61
|
}
|
|
60
62
|
cwd;
|
|
61
|
-
homeDir
|
|
63
|
+
get homeDir() {
|
|
64
|
+
return this.homeSeat.home;
|
|
65
|
+
}
|
|
66
|
+
homeSeat;
|
|
67
|
+
homeRefusal;
|
|
62
68
|
get cdPath() {
|
|
63
69
|
const value = this.childEnv(undefined, undefined).CDPATH;
|
|
64
70
|
return value === undefined || value === "" ? [] : value.split(":");
|
|
@@ -140,8 +146,9 @@ export class RemoteHostExecutionEnv {
|
|
|
140
146
|
if (Array.isArray(config.inheritEnv))
|
|
141
147
|
this.cfg.inheritEnv = [...config.inheritEnv];
|
|
142
148
|
this.cwd = this.workspaceDir;
|
|
143
|
-
this.homeDir = config.homeDir;
|
|
144
149
|
this.handle = { sandboxId: this.workspaceDir, provider: PROVIDER, mountPath: this.workspaceDir };
|
|
150
|
+
this.homeSeat = new LaneHomeSeat(PROVIDER, config, IS_WIN32 ? "win32" : "posix");
|
|
151
|
+
this.homeRefusal = this.homeSeat.settle(this.childHome())?.refusal;
|
|
145
152
|
}
|
|
146
153
|
workspaceHandle() {
|
|
147
154
|
return this.handle;
|
|
@@ -151,6 +158,8 @@ export class RemoteHostExecutionEnv {
|
|
|
151
158
|
return unsupported("connect(snapshotId)");
|
|
152
159
|
if (this.destroyed)
|
|
153
160
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
|
|
161
|
+
if (this.homeRefusal !== undefined)
|
|
162
|
+
return { ok: false, error: new RemoteExecutionError("connect_failed", this.homeRefusal) };
|
|
154
163
|
if (config?.abortSignal?.aborted)
|
|
155
164
|
return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted") };
|
|
156
165
|
try {
|
|
@@ -1253,6 +1262,8 @@ export class RemoteHostExecutionEnv {
|
|
|
1253
1262
|
async shellReady() {
|
|
1254
1263
|
if (this.destroyed)
|
|
1255
1264
|
return { ok: false, error: new ExecutionError("shell_unavailable", "execution env already destroyed") };
|
|
1265
|
+
if (this.homeRefusal !== undefined)
|
|
1266
|
+
return { ok: false, error: new ExecutionError("shell_unavailable", this.homeRefusal) };
|
|
1256
1267
|
try {
|
|
1257
1268
|
try {
|
|
1258
1269
|
await resolveHostShell();
|
|
@@ -1284,6 +1295,8 @@ export class RemoteHostExecutionEnv {
|
|
|
1284
1295
|
return (this.spoolRootDir !== undefined && inside(this.spoolRootDir)) || (this.persistent && inside(this.workspaceDir));
|
|
1285
1296
|
}
|
|
1286
1297
|
async fsReady(p, opts) {
|
|
1298
|
+
if (this.homeRefusal !== undefined)
|
|
1299
|
+
return { ok: false, error: new FileError("target_unavailable", this.homeRefusal, this.resolve(p)) };
|
|
1287
1300
|
if (this.destroyed) {
|
|
1288
1301
|
const lingering = opts?.lingeringBgWrite === true && this._bgManager?.hasShells() === true && this.isLingeringMirrorTarget(p);
|
|
1289
1302
|
if (!lingering)
|
|
@@ -1313,6 +1326,16 @@ export class RemoteHostExecutionEnv {
|
|
|
1313
1326
|
pinHostShell(base, spawnShell);
|
|
1314
1327
|
return collapseWin32EnvKeys(base);
|
|
1315
1328
|
}
|
|
1329
|
+
childHome() {
|
|
1330
|
+
try {
|
|
1331
|
+
const env = this.childEnv(undefined, undefined);
|
|
1332
|
+
const home = Object.entries(env).find(([k]) => (IS_WIN32 ? k.toUpperCase() : k) === "HOME")?.[1];
|
|
1333
|
+
return { ok: true, value: home ?? os.userInfo().homedir };
|
|
1334
|
+
}
|
|
1335
|
+
catch (e) {
|
|
1336
|
+
return { ok: false, reason: errMsg(e) };
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1316
1339
|
fileError(e, p, abortSignal) {
|
|
1317
1340
|
if (e instanceof FileError)
|
|
1318
1341
|
return e;
|
|
@@ -290,18 +290,7 @@ export type SandboxLeaseWrite = {
|
|
|
290
290
|
readonly message: string;
|
|
291
291
|
};
|
|
292
292
|
export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, BackgroundShellCapability {
|
|
293
|
-
/**
|
|
294
|
-
* 🔴 **[ref] / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
|
|
295
|
-
* 就是**命令真正跑的那块文件系统**的权威答案(`posix-shell-fs` 的 `canonicalPath` 原语,经 pod exec 解到落点)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
|
|
296
|
-
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
297
|
-
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
298
|
-
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
299
|
-
* 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
300
|
-
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
301
|
-
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
302
|
-
*/
|
|
303
|
-
/** `sharesProcessTable`(core 7.57.0,必填):命令跑在沙箱 pod 自己的 PID 命名空间里(不开 hostPID),不在本进程所在的进程表。 */
|
|
304
|
-
/** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
|
|
293
|
+
/** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。快照店是它的输入(构造期铸,见下)。 */
|
|
305
294
|
readonly capabilities: ExecutionEnvCapabilities;
|
|
306
295
|
cwd: string;
|
|
307
296
|
private podName?;
|
|
@@ -327,9 +316,9 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
327
316
|
* the space restore (`latest` names an archive that is there and did not come back — {@link restoreSnapshot}) and the pod's admission (the cluster created
|
|
328
317
|
* the pod and it ended `Failed` before it ran — {@link createPod}). Neither goes away by asking again: the same request answers the same. */
|
|
329
318
|
private connectRefusal?;
|
|
330
|
-
/** core `ExecutionEnv.homeDir`([ref]):pod 里执行用户的 home ——
|
|
319
|
+
/** core `ExecutionEnv.homeDir`([ref]):pod 里执行用户的 home —— 声明在场 ⇒ 声明(开环境那一拍与 `$HOME` 对证过),否则 pod 就绪那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时 pod 起来之前缺席)。 */
|
|
331
320
|
get homeDir(): string | undefined;
|
|
332
|
-
/** 家目录的座(
|
|
321
|
+
/** 家目录的座(声明在场 ⇒ 开环境那一拍对证 · 否则 pod 就绪那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。与其余读得出家目录的车道同一只判官、同一句拒。 */
|
|
333
322
|
private readonly homeSeat;
|
|
334
323
|
/** core 7.49.0 [ref]:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 pod 里(镜像与 pod spec 的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
|
|
335
324
|
* 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
|
|
@@ -18,6 +18,7 @@ import { buildLivenessCut, commandBoundsOf, laneLivenessOf, LIVENESS_MS_DEFAULT
|
|
|
18
18
|
import { buildCapturedOutput, createCutError } from "../exec-cut.js";
|
|
19
19
|
import { SANDBOX_IPV6_DEFAULT, sandboxEgressGaps, sandboxPodSysctls } from "../sandbox-egress-policy.js";
|
|
20
20
|
import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
21
|
+
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
21
22
|
import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
|
|
22
23
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
23
24
|
import { randomUUID } from "node:crypto";
|
|
@@ -258,7 +259,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
258
259
|
...(config.snapshotExclude && config.snapshotExclude.length > 0 ? { snapshotExclude: config.snapshotExclude } : {}),
|
|
259
260
|
};
|
|
260
261
|
this.cwd = this.cfg.mountPath;
|
|
261
|
-
this.homeSeat = new LaneHomeSeat(
|
|
262
|
+
this.homeSeat = new LaneHomeSeat(PROVIDER, config);
|
|
262
263
|
this.deps = deps ?? {};
|
|
263
264
|
this.laneLiveness = laneLivenessOf(this.cfg.livenessMs, "K8S_LIVENESS_MS");
|
|
264
265
|
const occupant = occupantKeyOf(config.spaceOccupant);
|
|
@@ -268,7 +269,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
268
269
|
this.logger = config.logger;
|
|
269
270
|
this.onWorkspaceSnapshot = config.onWorkspaceSnapshot;
|
|
270
271
|
this.cacheMisses = config.cacheMisses;
|
|
271
|
-
this.capabilities = {
|
|
272
|
+
this.capabilities = capabilitiesFor({ remoteExec: { provider: PROVIDER, s3Snapshot: this.cfg.s3Snapshot } });
|
|
272
273
|
const bgMax = Math.max(1, Math.min(K8S_BG_MAX_TIMEOUT_SEC, Math.floor(this.cfg.timeoutMs / 1000)));
|
|
273
274
|
this.backgroundCapabilities = {
|
|
274
275
|
supported: true,
|
|
@@ -61,22 +61,11 @@ export interface LocalDockerEnvDeps {
|
|
|
61
61
|
spawn?: typeof spawn;
|
|
62
62
|
}
|
|
63
63
|
export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv {
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
/** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
|
|
67
|
-
readonly capabilities: {
|
|
68
|
-
isolation: true;
|
|
69
|
-
suspendable: false;
|
|
70
|
-
canonicalPathAuthoritative: true;
|
|
71
|
-
sharesProcessTable: false;
|
|
72
|
-
hostLocalPaths: false;
|
|
73
|
-
externalContentTarget: false;
|
|
74
|
-
io: true;
|
|
75
|
-
keepsReadFloor: false;
|
|
76
|
-
};
|
|
64
|
+
/** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。 */
|
|
65
|
+
readonly capabilities: import("@sema-agent/core").ExecutionEnvCapabilities;
|
|
77
66
|
/** Working directory inside the container; relative paths resolve against it (ExecutionEnv contract). */
|
|
78
67
|
cwd: string;
|
|
79
|
-
/** core `ExecutionEnv.homeDir`([ref]):本容器里执行用户的 home ——
|
|
68
|
+
/** core `ExecutionEnv.homeDir`([ref]):本容器里执行用户的 home —— 声明在场 ⇒ 声明(开环境那一拍与 `$HOME` 对证过),否则开容器那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
|
|
80
69
|
get homeDir(): string | undefined;
|
|
81
70
|
/** core 7.49.0 [ref]:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在容器里(镜像的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
|
|
82
71
|
* 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
|
|
@@ -97,7 +86,7 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
97
86
|
private connecting?;
|
|
98
87
|
/** 家目录:环境答了而与声明不一致 ⇒ 这只环境的拒,记一次、此后每次连接原样回它(同一镜像同一答案:不再起容器、不再问)。 */
|
|
99
88
|
private homeRefusal?;
|
|
100
|
-
/** 家目录的座(
|
|
89
|
+
/** 家目录的座(声明在场 ⇒ 开环境那一拍对证 · 否则开容器那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。与其余读得出家目录的车道同一只判官、同一句拒。 */
|
|
101
90
|
private readonly homeSeat;
|
|
102
91
|
constructor(config: LocalDockerEnvConfig, deps?: LocalDockerEnvDeps);
|
|
103
92
|
private resolve;
|
|
@@ -8,6 +8,7 @@ import { signalNumber } from "./host-platform.js";
|
|
|
8
8
|
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
|
+
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
11
12
|
import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
|
|
12
13
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
13
14
|
const PROVIDER = "local-docker";
|
|
@@ -20,7 +21,7 @@ const unsupported = (op) => ({
|
|
|
20
21
|
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)`),
|
|
21
22
|
});
|
|
22
23
|
export class RemoteLocalDockerExecutionEnv {
|
|
23
|
-
capabilities = {
|
|
24
|
+
capabilities = capabilitiesFor({ remoteExec: { provider: "local-docker" } });
|
|
24
25
|
cwd;
|
|
25
26
|
get homeDir() {
|
|
26
27
|
return this.homeSeat.home;
|
|
@@ -49,7 +50,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
49
50
|
controlTimeoutMs: config.controlTimeoutMs ?? DEFAULT_CONTROL_TIMEOUT_MS,
|
|
50
51
|
};
|
|
51
52
|
this.cwd = this.cfg.mountPath;
|
|
52
|
-
this.homeSeat = new LaneHomeSeat(
|
|
53
|
+
this.homeSeat = new LaneHomeSeat(PROVIDER, config);
|
|
53
54
|
this.spawnFn = deps.spawn ?? spawn;
|
|
54
55
|
const id = config.id ?? randomBytes(6).toString("hex");
|
|
55
56
|
const suffix = randomBytes(4).toString("hex");
|
|
@@ -420,7 +421,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
420
421
|
if (!this.containerId) {
|
|
421
422
|
const c = await this.connect();
|
|
422
423
|
if (!c.ok)
|
|
423
|
-
return { ok: false, error: new ExecutionError("shell_unavailable", `local-docker connect failed: ${c.error.message}
|
|
424
|
+
return { ok: false, error: new ExecutionError("shell_unavailable", `local-docker connect failed: ${c.error.message}`, c.error) };
|
|
424
425
|
}
|
|
425
426
|
return this.docker(this.execArgv(cmd, undefined, undefined, input !== undefined), { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, ...(input !== undefined ? { input } : {}) });
|
|
426
427
|
}
|
|
@@ -597,7 +598,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
597
598
|
return ok(undefined);
|
|
598
599
|
const c = await this.connect();
|
|
599
600
|
if (!c.ok)
|
|
600
|
-
return { ok: false, error:
|
|
601
|
+
return { ok: false, error: fileErrorFromExec(c.error, this.resolve(p)) };
|
|
601
602
|
return ok(undefined);
|
|
602
603
|
}
|
|
603
604
|
dockerToRemoteError(e, fallback) {
|
|
@@ -53,28 +53,8 @@ export interface SshEnvDeps {
|
|
|
53
53
|
connectOnce?: (signal?: AbortSignal) => Promise<ConnectResult>;
|
|
54
54
|
}
|
|
55
55
|
export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
* 就是**命令真正跑的那块文件系统**的权威答案(`posix-shell-fs` 的 `canonicalPath` 原语,在真机上解到落点)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
|
|
59
|
-
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
60
|
-
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
61
|
-
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
62
|
-
* 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
63
|
-
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
64
|
-
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
65
|
-
*/
|
|
66
|
-
/** `sharesProcessTable`(core 7.57.0,必填):命令跑在 SSH 目标机上;目标恰是本机(或本进程所在容器的宿主)时命令与本进程同表,这一形今天没有配置面可声明 `true`。 */
|
|
67
|
-
/** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
|
|
68
|
-
readonly capabilities: {
|
|
69
|
-
isolation: false;
|
|
70
|
-
suspendable: false;
|
|
71
|
-
canonicalPathAuthoritative: true;
|
|
72
|
-
sharesProcessTable: false;
|
|
73
|
-
hostLocalPaths: false;
|
|
74
|
-
externalContentTarget: false;
|
|
75
|
-
io: true;
|
|
76
|
-
keepsReadFloor: false;
|
|
77
|
-
};
|
|
56
|
+
/** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。 */
|
|
57
|
+
readonly capabilities: import("@sema-agent/core").ExecutionEnvCapabilities;
|
|
78
58
|
/** Working directory; relative paths resolve against it (ExecutionEnv contract). Starts at mountPath. */
|
|
79
59
|
cwd: string;
|
|
80
60
|
private conn?;
|
|
@@ -91,9 +71,9 @@ export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
|
|
|
91
71
|
* =重连风暴);②共享尝试**不携带任何 caller 信号**(首创者的 pre-abort 不得毒化 joiner;单 caller
|
|
92
72
|
* 取消只退它自己的等待,底层握手余命 ≤ readyTimeout 有界,彻底取消归 destroy 代际闸)。 */
|
|
93
73
|
private inFlight?;
|
|
94
|
-
/** core `ExecutionEnv.homeDir`([ref]):目标机上登录用户的 home ——
|
|
74
|
+
/** core `ExecutionEnv.homeDir`([ref]):目标机上登录用户的 home —— 声明在场 ⇒ 声明(开环境那一拍与 `$HOME` 对证过),否则连上之后实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
|
|
95
75
|
get homeDir(): string | undefined;
|
|
96
|
-
/** 家目录的座(
|
|
76
|
+
/** 家目录的座(声明在场 ⇒ 开环境那一拍对证 · 否则连上之后实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。与其余读得出家目录的车道同一只判官、同一句拒。 */
|
|
97
77
|
private readonly homeSeat;
|
|
98
78
|
/** 家目录声明 × 实测的判定,按连接记(同一条连接只问一次;重连换了连接再问 —— 登录用户不变时答案相同,代价一次往返;没声明且已量到绝对路径之后不再问)。 */
|
|
99
79
|
private homeChecked?;
|
|
@@ -7,6 +7,7 @@ import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
|
|
|
7
7
|
import { buildCapturedOutput, createCutError } from "../exec-cut.js";
|
|
8
8
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
9
9
|
import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
10
|
+
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
10
11
|
import { fileErrorFromExec } from "./remote-env-file-error.js";
|
|
11
12
|
import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
|
|
12
13
|
const PROVIDER = "ssh";
|
|
@@ -19,7 +20,7 @@ function exitCodeForSignalKill(signal) {
|
|
|
19
20
|
return 128 + (signalNumber(`SIG${signal}`) ?? 9);
|
|
20
21
|
}
|
|
21
22
|
export class RemoteSshExecutionEnv {
|
|
22
|
-
capabilities = {
|
|
23
|
+
capabilities = capabilitiesFor({ remoteExec: { provider: "ssh" } });
|
|
23
24
|
cwd;
|
|
24
25
|
conn;
|
|
25
26
|
sftpPromise;
|
|
@@ -52,7 +53,7 @@ export class RemoteSshExecutionEnv {
|
|
|
52
53
|
hostKey: config.hostKey,
|
|
53
54
|
};
|
|
54
55
|
this.cwd = this.cfg.mountPath;
|
|
55
|
-
this.homeSeat = new LaneHomeSeat(
|
|
56
|
+
this.homeSeat = new LaneHomeSeat(PROVIDER, config);
|
|
56
57
|
}
|
|
57
58
|
resolve(p) {
|
|
58
59
|
return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
|
|
@@ -579,7 +580,7 @@ export class RemoteSshExecutionEnv {
|
|
|
579
580
|
async sftp(abortSignal) {
|
|
580
581
|
const ready = await this.ensureConnected(abortSignal);
|
|
581
582
|
if (!ready.ok)
|
|
582
|
-
return Promise.reject(ready.error.code === "aborted" ? fileErrorFromExec(ready.error) : new
|
|
583
|
+
return Promise.reject(ready.error.code === "aborted" ? fileErrorFromExec(ready.error) : new FileError("target_unavailable", ready.error.message, undefined, ready.error));
|
|
583
584
|
if (!this.conn)
|
|
584
585
|
return Promise.reject(new Error("SSH not connected"));
|
|
585
586
|
if (!this.sftpPromise) {
|
|
@@ -4,8 +4,8 @@ export class SqlResumeAnchorStore {
|
|
|
4
4
|
constructor(db) {
|
|
5
5
|
this.db = db;
|
|
6
6
|
}
|
|
7
|
-
q(
|
|
8
|
-
return this.db.dialect === "
|
|
7
|
+
q(mysql, pg) {
|
|
8
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
9
9
|
}
|
|
10
10
|
async put(sessionId, eventId, entryId, owner) {
|
|
11
11
|
await this.db.query(this.q("INSERT INTO resume_anchor (session_id, event_id, entry_id, owner, created_at) VALUES (?,?,?,?,?) " +
|
|
@@ -118,7 +118,7 @@ export declare class SqlRetentionLaneStore implements RetentionLaneStore {
|
|
|
118
118
|
protected readonly db: SqlDriver;
|
|
119
119
|
constructor(db: SqlDriver);
|
|
120
120
|
/** 方言取文(两条语句都写在调用点;A12 判据)。 */
|
|
121
|
-
protected q(
|
|
121
|
+
protected q(mysql: string, pg: string): string;
|
|
122
122
|
/**
|
|
123
123
|
* 抢/续租一次(单行 CAS)。抢到 ⇒ `{held:true, fencingToken}`;别人正持着 ⇒ `{held:false}`,调用方
|
|
124
124
|
* **本 tick 零写静默跳过**。
|
|
@@ -7,8 +7,8 @@ export class SqlRetentionLaneStore {
|
|
|
7
7
|
constructor(db) {
|
|
8
8
|
this.db = db;
|
|
9
9
|
}
|
|
10
|
-
q(
|
|
11
|
-
return this.db.dialect === "
|
|
10
|
+
q(mysql, pg) {
|
|
11
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
12
12
|
}
|
|
13
13
|
async acquire(holder, ttlMs, nowMs) {
|
|
14
14
|
const conn = await this.db.connect();
|
|
@@ -183,7 +183,7 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
|
|
|
183
183
|
readonly retention: RetentionDeclaration;
|
|
184
184
|
constructor(db: SqlDriver, opts: SqlRetentionStoreOptions);
|
|
185
185
|
/** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE (A12 判据)。 */
|
|
186
|
-
protected q(
|
|
186
|
+
protected q(mysql: string, pg: string): string;
|
|
187
187
|
/**
|
|
188
188
|
* 域谓词。**四种写法逐条写出来**(方言 × 有主/无主):
|
|
189
189
|
* · 有主:`col = ?` / `col = $n`;
|
|
@@ -155,8 +155,8 @@ export class SqlRetentionStore {
|
|
|
155
155
|
this.db = db;
|
|
156
156
|
this.opts = opts;
|
|
157
157
|
}
|
|
158
|
-
q(
|
|
159
|
-
return this.db.dialect === "
|
|
158
|
+
q(mysql, pg) {
|
|
159
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
160
160
|
}
|
|
161
161
|
domainWhere(col, domain, pgIndex) {
|
|
162
162
|
return domain === ""
|
|
@@ -164,7 +164,7 @@ export class SqlRetentionStore {
|
|
|
164
164
|
: { sql: this.q(`${col} = ?`, `${col} = $${pgIndex}`), params: [domain] };
|
|
165
165
|
}
|
|
166
166
|
idSet(col, ids, pgIndex) {
|
|
167
|
-
return this.db.dialect === "
|
|
167
|
+
return this.db.dialect === "mysql"
|
|
168
168
|
? { sql: `${col} IN (${ids.map(() => "?").join(",")})`, params: [...ids] }
|
|
169
169
|
: { sql: `${col} = ANY($${pgIndex}::text[])`, params: [ids] };
|
|
170
170
|
}
|
|
@@ -210,7 +210,7 @@ export class SqlRetentionStore {
|
|
|
210
210
|
if (keys.length === 0)
|
|
211
211
|
return 0;
|
|
212
212
|
const now = Date.now();
|
|
213
|
-
if (this.db.dialect === "
|
|
213
|
+
if (this.db.dialect === "mysql") {
|
|
214
214
|
const params = [];
|
|
215
215
|
for (const k of keys)
|
|
216
216
|
params.push(domain, kind, k, now);
|
|
@@ -267,7 +267,7 @@ export class SqlRetentionStore {
|
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
horizonArms(pgFirst) {
|
|
270
|
-
return this.db.dialect === "
|
|
270
|
+
return this.db.dialect === "mysql"
|
|
271
271
|
? `((c.deadline IS NOT NULL AND c.deadline <= ?) OR (c.deadline IS NULL AND c.created_at_ms <= ?))`
|
|
272
272
|
: `((c.deadline IS NOT NULL AND c.deadline <= $${pgFirst}) OR (c.deadline IS NULL AND c.created_at_ms <= $${pgFirst + 1}))`;
|
|
273
273
|
}
|
|
@@ -287,7 +287,7 @@ export class SqlRetentionStore {
|
|
|
287
287
|
return Number(rows[0]?.n ?? 0);
|
|
288
288
|
}
|
|
289
289
|
liveReferenceExists() {
|
|
290
|
-
const workflow = this.db.dialect === "
|
|
290
|
+
const workflow = this.db.dialect === "mysql"
|
|
291
291
|
? `EXISTS (SELECT 1 FROM workflow_run wr WHERE wr.status = 'running' AND JSON_UNQUOTE(JSON_EXTRACT(wr.run, '$.originatingSessionId')) = sm.session_id)`
|
|
292
292
|
: `EXISTS (SELECT 1 FROM workflow_run wr WHERE wr.status = 'running' AND wr.run::json->>'originatingSessionId' = sm.session_id)`;
|
|
293
293
|
return (`(sm.placement_kind IS NOT NULL` +
|
|
@@ -47,8 +47,8 @@ export class SqlRunStore {
|
|
|
47
47
|
constructor(db) {
|
|
48
48
|
this.db = db;
|
|
49
49
|
}
|
|
50
|
-
q(
|
|
51
|
-
return this.db.dialect === "
|
|
50
|
+
q(mysql, pg) {
|
|
51
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
52
52
|
}
|
|
53
53
|
isDupKey(err) {
|
|
54
54
|
return isDupKeyError(this.db.dialect, err);
|
|
@@ -103,7 +103,7 @@ export class SqlRunStore {
|
|
|
103
103
|
cells.identitySource,
|
|
104
104
|
cells.identityScope,
|
|
105
105
|
cells.identityPrincipalInc,
|
|
106
|
-
this.db.dialect === "
|
|
106
|
+
this.db.dialect === "mysql"
|
|
107
107
|
? meta.objectivePreview ?? null
|
|
108
108
|
: meta.objectivePreview == null
|
|
109
109
|
? null
|
|
@@ -181,8 +181,8 @@ export class SqlRunStore {
|
|
|
181
181
|
const w = await conn.query(this.q("UPDATE task_run SET status = ?, result = ?, error = ?, error_code = ?, updated_at = ? WHERE task_id = ? AND status IN ('running','suspended','needs_review')", "UPDATE task_run SET status = $1, result = $2::jsonb, error = $3, error_code = $4, updated_at = $5 WHERE task_id = $6 AND status IN ('running','suspended','needs_review')"), [
|
|
182
182
|
status,
|
|
183
183
|
result == null ? null : this.json(result),
|
|
184
|
-
this.db.dialect === "
|
|
185
|
-
this.db.dialect === "
|
|
184
|
+
this.db.dialect === "mysql" ? error : error == null ? null : pgSanitizeText(error),
|
|
185
|
+
this.db.dialect === "mysql" ? errorCode : errorCode == null ? null : pgSanitizeText(errorCode),
|
|
186
186
|
new Date(),
|
|
187
187
|
taskId,
|
|
188
188
|
]);
|
|
@@ -253,7 +253,7 @@ export class SqlRunStore {
|
|
|
253
253
|
}
|
|
254
254
|
async claimSpaceCommitIn(conn, spaceKey, taskId, state, claim) {
|
|
255
255
|
if (!claim.requireOwn) {
|
|
256
|
-
await conn.query(this.q(`INSERT INTO workspace_space_commit (space_key, task_id, state, expires_at_ms) VALUES (?,?,?,${SQL_DB_NOW_MS.
|
|
256
|
+
await conn.query(this.q(`INSERT INTO workspace_space_commit (space_key, task_id, state, expires_at_ms) VALUES (?,?,?,${SQL_DB_NOW_MS.mysql} + ?) ON DUPLICATE KEY UPDATE space_key = space_key`, `INSERT INTO workspace_space_commit (space_key, task_id, state, expires_at_ms) VALUES ($1,$2,$3,${SQL_DB_NOW_MS.pg} + $4::bigint) ON CONFLICT (space_key) DO UPDATE SET space_key = EXCLUDED.space_key`), [spaceKey, taskId, state, claim.boundMs]);
|
|
257
257
|
}
|
|
258
258
|
const row = await this.readSpaceCommitIn(conn, spaceKey, true);
|
|
259
259
|
switch (decideSpaceCommitClaim(row, taskId, claim)) {
|
|
@@ -262,7 +262,7 @@ export class SqlRunStore {
|
|
|
262
262
|
case "insert":
|
|
263
263
|
throw new Error("workspace_space_commit: a space's row vanished under this transaction's own lock");
|
|
264
264
|
case "update": {
|
|
265
|
-
const w = await conn.query(this.q(`UPDATE workspace_space_commit SET task_id = ?, state = ?, expires_at_ms = ${SQL_DB_NOW_MS.
|
|
265
|
+
const w = await conn.query(this.q(`UPDATE workspace_space_commit SET task_id = ?, state = ?, expires_at_ms = ${SQL_DB_NOW_MS.mysql} + ? WHERE space_key = ? AND task_id = ?`, `UPDATE workspace_space_commit SET task_id = $1, state = $2, expires_at_ms = ${SQL_DB_NOW_MS.pg} + $3::bigint WHERE space_key = $4 AND task_id = $5`), [taskId, state, claim.boundMs, spaceKey, row.taskId]);
|
|
266
266
|
return w.affected > 0;
|
|
267
267
|
}
|
|
268
268
|
}
|
|
@@ -318,20 +318,20 @@ export class SqlRunStore {
|
|
|
318
318
|
};
|
|
319
319
|
}
|
|
320
320
|
async getRun(taskId) {
|
|
321
|
-
const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("
|
|
321
|
+
const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE task_id = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE task_id = $1`), [taskId]);
|
|
322
322
|
return rows[0] ? this.rowToRun(rows[0]) : undefined;
|
|
323
323
|
}
|
|
324
324
|
async observeSubmittedRow(conn, taskId, idemKey) {
|
|
325
325
|
if (idemKey !== null) {
|
|
326
|
-
const { rows } = await conn.query(this.q(`SELECT ${runRowCols("
|
|
326
|
+
const { rows } = await conn.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE idem_key = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE idem_key = $1`), [idemKey]);
|
|
327
327
|
if (rows[0])
|
|
328
328
|
return this.rowToRun(rows[0]);
|
|
329
329
|
}
|
|
330
|
-
const { rows } = await conn.query(this.q(`SELECT ${runRowCols("
|
|
330
|
+
const { rows } = await conn.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE task_id = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE task_id = $1`), [taskId]);
|
|
331
331
|
return rows[0] ? this.rowToRun(rows[0]) : undefined;
|
|
332
332
|
}
|
|
333
333
|
async getRunByIdem(idemKey) {
|
|
334
|
-
const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("
|
|
334
|
+
const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE idem_key = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE idem_key = $1`), [idemKey]);
|
|
335
335
|
return rows[0] ? this.rowToRun(rows[0]) : undefined;
|
|
336
336
|
}
|
|
337
337
|
async releaseIdemKey(taskId) {
|
|
@@ -346,7 +346,7 @@ export class SqlRunStore {
|
|
|
346
346
|
async listRuns(opts) {
|
|
347
347
|
if (opts.cursor && Number.isNaN(Date.parse(opts.cursor.createdAt)))
|
|
348
348
|
return [];
|
|
349
|
-
if (this.db.dialect === "
|
|
349
|
+
if (this.db.dialect === "mysql") {
|
|
350
350
|
const where = [];
|
|
351
351
|
const params = [];
|
|
352
352
|
if (opts.status) {
|
|
@@ -372,7 +372,7 @@ export class SqlRunStore {
|
|
|
372
372
|
where.push("(created_at < ? OR (created_at = ? AND task_id < ?))");
|
|
373
373
|
params.push(new Date(opts.cursor.createdAt), new Date(opts.cursor.createdAt), opts.cursor.taskId);
|
|
374
374
|
}
|
|
375
|
-
const sql = `SELECT ${runRowCols("
|
|
375
|
+
const sql = `SELECT ${runRowCols("mysql")} FROM task_run` +
|
|
376
376
|
(where.length ? " WHERE " + where.join(" AND ") : "") +
|
|
377
377
|
" ORDER BY created_at DESC, task_id DESC LIMIT ?";
|
|
378
378
|
params.push(opts.limit);
|
|
@@ -413,7 +413,7 @@ export class SqlRunStore {
|
|
|
413
413
|
return r === undefined ? undefined : { taskId: String(r.task_id), owner: r.owner ?? null, createdAt: iso(r.created_at) };
|
|
414
414
|
}
|
|
415
415
|
async listSessions(opts) {
|
|
416
|
-
if (this.db.dialect === "
|
|
416
|
+
if (this.db.dialect === "mysql") {
|
|
417
417
|
const params = [];
|
|
418
418
|
let innerWhere = "";
|
|
419
419
|
if (opts.owner !== undefined) {
|
|
@@ -533,7 +533,7 @@ export class SqlRunStore {
|
|
|
533
533
|
return m == null ? 0 : Number(m);
|
|
534
534
|
}
|
|
535
535
|
async getEvents(taskId, afterSeq) {
|
|
536
|
-
const { rows } = await this.db.query(this.q(`SELECT seq, type, ${jsonColumnText("
|
|
536
|
+
const { rows } = await this.db.query(this.q(`SELECT seq, type, ${jsonColumnText("mysql", "data")}, ts FROM task_event WHERE task_id = ? AND seq > ? ORDER BY seq ASC`, `SELECT seq, type, ${jsonColumnText("pg", "data")}, ts FROM task_event WHERE task_id = $1 AND seq > $2 ORDER BY seq ASC`), [taskId, afterSeq]);
|
|
537
537
|
return rows.map((r) => ({
|
|
538
538
|
seq: Number(r.seq),
|
|
539
539
|
type: String(r.type),
|