@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.
Files changed (194) hide show
  1. package/MIGRATION.md +181 -0
  2. package/USAGE.md +19 -13
  3. package/dist/adoption/runner.js +1 -1
  4. package/dist/adoption/sql.js +1 -1
  5. package/dist/approval-card.d.ts +6 -6
  6. package/dist/bench/s1/live-deps.js +3 -2
  7. package/dist/bench/s1/row.d.ts +5 -2
  8. package/dist/bench/s1/row.js +2 -22
  9. package/dist/boot/adoption.js +1 -1
  10. package/dist/boot/execution-env.js +12 -10
  11. package/dist/boot/execution-lane.d.ts +8 -0
  12. package/dist/boot/execution-lane.js +6 -1
  13. package/dist/boot/memory-boundary.d.ts +14 -51
  14. package/dist/boot/memory-boundary.js +10 -20
  15. package/dist/boot/memory-consolidation.d.ts +15 -11
  16. package/dist/boot/memory-consolidation.js +23 -12
  17. package/dist/boot/resolve-spec.d.ts +3 -5
  18. package/dist/boot/resolve-spec.js +2 -4
  19. package/dist/boot/runner-deps.d.ts +3 -3
  20. package/dist/boot/runner-deps.js +1 -0
  21. package/dist/boot/stage-03-store-backend.d.ts +2 -6
  22. package/dist/boot/stage-03-store-backend.js +2 -3
  23. package/dist/boot/stage-04-stores.d.ts +2 -6
  24. package/dist/boot/stage-04-stores.js +5 -5
  25. package/dist/boot/stage-05-execution-env.d.ts +2 -6
  26. package/dist/boot/stage-06-runners.d.ts +2 -6
  27. package/dist/boot/stage-06-runners.js +2 -2
  28. package/dist/boot/stage-07-capability-layer.d.ts +2 -6
  29. package/dist/boot/stage-08-reapers.d.ts +2 -6
  30. package/dist/boot/stage-09-leader.d.ts +2 -6
  31. package/dist/boot/stage-10-http-server.d.ts +2 -6
  32. package/dist/boot/stage-10-http-server.js +5 -4
  33. package/dist/boot/stores.d.ts +3 -6
  34. package/dist/boot/stores.js +27 -40
  35. package/dist/boot/task-list-lane.d.ts +1 -1
  36. package/dist/boot/task-list-lane.js +1 -1
  37. package/dist/boot/web-admin.js +35 -9
  38. package/dist/capabilities/execution-lane.d.ts +2 -2
  39. package/dist/config-catalog.js +2 -1
  40. package/dist/config-provider.d.ts +14 -13
  41. package/dist/config-provider.js +24 -18
  42. package/dist/config-types.d.ts +11 -2
  43. package/dist/config.js +2 -1
  44. package/dist/delegated-session-owner.d.ts +2 -1
  45. package/dist/delegated-session-owner.js +1 -1
  46. package/dist/deployment-shape.d.ts +9 -8
  47. package/dist/deployment-shape.js +4 -4
  48. package/dist/execution-lane-caps.d.ts +97 -106
  49. package/dist/execution-lane-caps.js +67 -83
  50. package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
  51. package/dist/http/memory-compliance-failure-reply.js +5 -0
  52. package/dist/http/memory-layer-reply.d.ts +30 -0
  53. package/dist/http/memory-layer-reply.js +24 -0
  54. package/dist/http/resume-legs.js +13 -16
  55. package/dist/http/retired-service-deps.d.ts +1 -1
  56. package/dist/http/retired-service-deps.js +1 -1
  57. package/dist/http/routes/adoption.js +1 -1
  58. package/dist/http/routes/memory-bundle.js +3 -0
  59. package/dist/http/routes/memory-compliance.js +8 -1
  60. package/dist/http/routes/memory-consolidation.js +12 -1
  61. package/dist/http/routes/memory-origin.js +14 -2
  62. package/dist/http/routes/memory-policy.js +5 -0
  63. package/dist/http/routes/sessions.js +28 -11
  64. package/dist/http/routes/tasks.js +4 -2
  65. package/dist/http/server.d.ts +5 -25
  66. package/dist/http/server.js +2 -2
  67. package/dist/identity-source.d.ts +1 -1
  68. package/dist/identity-source.js +1 -1
  69. package/dist/lane-home-probe.d.ts +68 -42
  70. package/dist/lane-home-probe.js +53 -30
  71. package/dist/leader/wire.js +5 -4
  72. package/dist/memory-bundle-engine.d.ts +16 -9
  73. package/dist/memory-bundle-engine.js +29 -6
  74. package/dist/memory-operator-faces.d.ts +85 -30
  75. package/dist/memory-operator-faces.js +136 -30
  76. package/dist/memory-project-layer.d.ts +3 -21
  77. package/dist/memory-project-layer.js +11 -8
  78. package/dist/memory-scope-stores.d.ts +73 -0
  79. package/dist/memory-scope-stores.js +86 -0
  80. package/dist/memory-scope.d.ts +29 -5
  81. package/dist/memory-scope.js +16 -4
  82. package/dist/memory-sync-client.d.ts +20 -7
  83. package/dist/memory-sync-client.js +31 -13
  84. package/dist/plugins/adoption-log-sql.js +4 -4
  85. package/dist/plugins/approval-ask-store-sql.js +14 -14
  86. package/dist/plugins/approval-nonce-store.js +4 -4
  87. package/dist/plugins/blob-backend.d.ts +4 -4
  88. package/dist/plugins/blob-backend.js +7 -7
  89. package/dist/plugins/breaker-state-sql.js +2 -2
  90. package/dist/plugins/checkpoint-index-columns.js +2 -2
  91. package/dist/plugins/checkpoint-store-sql.js +16 -16
  92. package/dist/plugins/device-store-sql.js +19 -19
  93. package/dist/plugins/ensure-index.d.ts +1 -2
  94. package/dist/plugins/execution-placement-sql.d.ts +8 -8
  95. package/dist/plugins/execution-placement-sql.js +8 -8
  96. package/dist/plugins/file-history-store-sql.d.ts +2 -2
  97. package/dist/plugins/file-history-store-sql.js +4 -4
  98. package/dist/plugins/image-bake-store-sql.js +9 -9
  99. package/dist/plugins/image-index-sql.js +11 -11
  100. package/dist/plugins/leader-run-store-sql.js +4 -4
  101. package/dist/plugins/mailbox-store-sql.d.ts +2 -1
  102. package/dist/plugins/mailbox-store-sql.js +4 -4
  103. package/dist/plugins/memory-engine-tidb.js +5 -5
  104. package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
  105. package/dist/plugins/memory-sync-store-tidb.js +3 -3
  106. package/dist/plugins/one-time-migrations.d.ts +10 -9
  107. package/dist/plugins/one-time-migrations.js +36 -36
  108. package/dist/plugins/org-delivery-quota-store.js +2 -2
  109. package/dist/plugins/outcome-ledger-sql.js +2 -2
  110. package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
  111. package/dist/plugins/permission-rule-store-sql.js +16 -16
  112. package/dist/plugins/remote-env-adb.d.ts +7 -24
  113. package/dist/plugins/remote-env-adb.js +22 -5
  114. package/dist/plugins/remote-env-device.d.ts +2 -28
  115. package/dist/plugins/remote-env-device.js +2 -1
  116. package/dist/plugins/remote-env-e2b.d.ts +18 -26
  117. package/dist/plugins/remote-env-e2b.js +51 -4
  118. package/dist/plugins/remote-env-file-error.d.ts +1 -1
  119. package/dist/plugins/remote-env-file-error.js +8 -2
  120. package/dist/plugins/remote-env-host.d.ts +19 -35
  121. package/dist/plugins/remote-env-host.js +26 -3
  122. package/dist/plugins/remote-env-k8s.d.ts +3 -14
  123. package/dist/plugins/remote-env-k8s.js +3 -2
  124. package/dist/plugins/remote-env-local-docker.d.ts +4 -15
  125. package/dist/plugins/remote-env-local-docker.js +5 -4
  126. package/dist/plugins/remote-env-ssh.d.ts +4 -24
  127. package/dist/plugins/remote-env-ssh.js +4 -3
  128. package/dist/plugins/resume-anchor-store-sql.js +2 -2
  129. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  130. package/dist/plugins/retention-lane-store-sql.js +2 -2
  131. package/dist/plugins/retention-store-sql.d.ts +1 -1
  132. package/dist/plugins/retention-store-sql.js +6 -6
  133. package/dist/plugins/run-store-sql.js +15 -15
  134. package/dist/plugins/schema-consistency.d.ts +6 -6
  135. package/dist/plugins/schema-consistency.js +1 -1
  136. package/dist/plugins/schema-ensure-legs.d.ts +2 -3
  137. package/dist/plugins/schema-ensure-legs.js +2 -2
  138. package/dist/plugins/schema-live-columns.d.ts +1 -1
  139. package/dist/plugins/session-capture-record-store-sql.js +1 -1
  140. package/dist/plugins/session-rules-column.d.ts +1 -1
  141. package/dist/plugins/session-rules-column.js +1 -1
  142. package/dist/plugins/session-workspace-cell.d.ts +1 -1
  143. package/dist/plugins/session-workspace-cell.js +1 -1
  144. package/dist/plugins/shared-memory-store-sql.js +2 -2
  145. package/dist/plugins/sql-driver.d.ts +10 -5
  146. package/dist/plugins/sql-driver.js +6 -6
  147. package/dist/plugins/sql-errors.js +1 -1
  148. package/dist/plugins/store-backend.d.ts +4 -6
  149. package/dist/plugins/store-backend.js +6 -6
  150. package/dist/plugins/task-list-store-sql.js +11 -11
  151. package/dist/plugins/tidb-pool.js +5 -5
  152. package/dist/plugins/tidb-session-storage.js +4 -4
  153. package/dist/plugins/tidb-session-store.js +13 -13
  154. package/dist/plugins/tool-result-store-sql.d.ts +2 -2
  155. package/dist/plugins/tool-result-store-sql.js +3 -3
  156. package/dist/plugins/user-input-ledger-sql.js +2 -2
  157. package/dist/plugins/workflow-journal-store-sql.js +5 -5
  158. package/dist/plugins/workflow-run-store-sql.js +19 -19
  159. package/dist/plugins/write-behind-counter.d.ts +2 -3
  160. package/dist/plugins/write-behind-counter.js +5 -5
  161. package/dist/resource-suspend.d.ts +14 -12
  162. package/dist/resource-suspend.js +1 -1
  163. package/dist/resource-window.d.ts +4 -3
  164. package/dist/run-local.js +14 -19
  165. package/dist/runs.d.ts +8 -4
  166. package/dist/runs.js +49 -65
  167. package/dist/session-incarnation.d.ts +1 -1
  168. package/dist/session-incarnation.js +1 -1
  169. package/dist/session-policy-wire.d.ts +1 -1
  170. package/dist/sql-engine-posture.d.ts +1 -1
  171. package/dist/sql-engine-posture.js +1 -1
  172. package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
  173. package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
  174. package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
  175. package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
  176. package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
  177. package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
  178. package/dist/task-a2a.js +5 -6
  179. package/dist/task-mcp.d.ts +20 -7
  180. package/dist/task-mcp.js +17 -9
  181. package/dist/terminal.d.ts +27 -0
  182. package/dist/terminal.js +6 -0
  183. package/dist/trace/redact.d.ts +3 -1
  184. package/dist/trace/redact.js +80 -28
  185. package/dist/trace/self-minted-wire.d.ts +58 -0
  186. package/dist/trace/self-minted-wire.js +16 -0
  187. package/dist/web-admin-client/candidate-gate.d.ts +11 -7
  188. package/dist/web-admin-client/candidate-gate.js +1 -1
  189. package/dist/web-admin-client/http-client.d.ts +5 -4
  190. package/dist/web-admin-client/http-client.js +2 -2
  191. package/dist/web-admin-client/read-warnings.d.ts +7 -17
  192. package/dist/web-admin-client/read-warnings.js +5 -9
  193. package/package.json +2 -2
  194. 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
- /** [ref] §9 / DUAL-MODE-DESIGN §5: a `host` env is the operator's real machine — no isolation, no snapshot. */
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]):本沙箱/目标机上执行用户的 home。见配置同名字段。 */
102
- readonly homeDir?: string | undefined;
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 = { isolation: false, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: true, hostLocalPaths: true, externalContentTarget: false, io: true, keepsReadFloor: false };
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 —— 声明胜,否则 pod 就绪那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时 pod 起来之前缺席)。 */
319
+ /** core `ExecutionEnv.homeDir`([ref]):pod 里执行用户的 home —— 声明在场 ⇒ 声明(开环境那一拍与 `$HOME` 对证过),否则 pod 就绪那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时 pod 起来之前缺席)。 */
331
320
  get homeDir(): string | undefined;
332
- /** 家目录的座(声明胜 · 否则 pod 就绪那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。本车道对声明对证(与 local-docker / ssh 同一只判官、同一句拒)。 */
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({ lane: PROVIDER, declared: config.homeDir, verifiesDeclared: true, onAbsent: config.onHomeAbsent });
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 = { isolation: true, suspendable: !!this.cfg.s3Snapshot, canonicalPathAuthoritative: true, sharesProcessTable: false, hostLocalPaths: false, externalContentTarget: false, io: true, keepsReadFloor: false };
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
- /** DUAL-MODE-DESIGN §5: a container is a real isolation boundary; v1 has no managed memory snapshot. */
65
- /** `sharesProcessTable`(core 7.57.0,必填):命令跑在本机容器自己的 PID 命名空间里(不传 `--pid=host`),不在本进程所在的进程表。 */
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 —— 声明胜,否则开容器那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
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
- /** 家目录的座(声明胜 · 否则开容器那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。本车道对声明对证。 */
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 = { isolation: true, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: false, hostLocalPaths: false, externalContentTarget: false, io: true, keepsReadFloor: false };
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({ lane: PROVIDER, declared: config.homeDir, verifiesDeclared: true, onAbsent: config.onHomeAbsent });
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: new FileError("unknown", `local-docker connect failed: ${c.error.message}`, this.resolve(p), c.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
- * 🔴 **[ref] / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
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 —— 声明胜,否则连上之后实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
74
+ /** core `ExecutionEnv.homeDir`([ref]):目标机上登录用户的 home —— 声明在场 ⇒ 声明(开环境那一拍与 `$HOME` 对证过),否则连上之后实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
95
75
  get homeDir(): string | undefined;
96
- /** 家目录的座(声明胜 · 否则连上之后实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。本车道对声明对证。 */
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 = { isolation: false, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: false, hostLocalPaths: false, externalContentTarget: false, io: true, keepsReadFloor: false };
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({ lane: PROVIDER, declared: config.homeDir, verifiesDeclared: true, onAbsent: config.onHomeAbsent });
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 Error(ready.error.message));
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(tidb, pg) {
8
- return this.db.dialect === "tidb" ? tidb : pg;
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(tidb: string, pg: string): string;
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(tidb, pg) {
11
- return this.db.dialect === "tidb" ? tidb : pg;
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(tidb: string, pg: string): string;
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(tidb, pg) {
159
- return this.db.dialect === "tidb" ? tidb : pg;
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 === "tidb"
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 === "tidb") {
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 === "tidb"
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 === "tidb"
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(tidb, pg) {
51
- return this.db.dialect === "tidb" ? tidb : pg;
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 === "tidb"
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 === "tidb" ? error : error == null ? null : pgSanitizeText(error),
185
- this.db.dialect === "tidb" ? errorCode : errorCode == null ? null : pgSanitizeText(errorCode),
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.tidb} + ?) 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]);
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.tidb} + ? 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]);
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("tidb")} FROM task_run WHERE task_id = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE task_id = $1`), [taskId]);
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("tidb")} FROM task_run WHERE idem_key = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE idem_key = $1`), [idemKey]);
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("tidb")} FROM task_run WHERE task_id = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE task_id = $1`), [taskId]);
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("tidb")} FROM task_run WHERE idem_key = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE idem_key = $1`), [idemKey]);
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 === "tidb") {
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("tidb")} FROM task_run` +
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 === "tidb") {
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("tidb", "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]);
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),