@sema-agent/server 7.43.0 → 7.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* design/158 B6 —— POSIX shell-out 族的共享文件面基座。
|
|
3
|
-
*
|
|
4
|
-
* 服务对象 = {k8s, ssh, adb, local-docker} 四条「发一条 shell 命令 + 分类 stderr」的腿(lens3 C2 实测
|
|
5
|
-
* 42–69% 同构)。**host(node:fs)与 e2b(SDK)是原生 API 腿,不并**(13–27% 是假共性,强行合并会造出
|
|
6
|
-
* 一个假基座)。ssh 的 fileInfo/listDir 走 SFTP 原生,只接入本基座的 shell-out 子集。
|
|
7
|
-
*
|
|
8
|
-
* 五条裁决(B6-posix-shell-fs.md,test/posix-shell-fs.test.ts 逐条钉):
|
|
9
|
-
* R1 所有 path 实参前带 `--`(ssh 形):resolve() 先绝对化让风险只剩纵深,但**基座不依赖调用侧不变量**。
|
|
10
|
-
* R2 错误分类走 `classifyFsStderr`(k8s 形:分类 + 硬失败/命令缺失护栏 + 站点显式 fallback)。
|
|
11
|
-
* R3 listDir 用 `ls -1A`(adb 此前 `-1` 丢 dotfiles —— agent 场景 .env/.gitignore 是一等公民)。
|
|
12
|
-
* R4 createTempFile 后缀=尾-X 模板 + `mv`(纯 POSIX;GNU `--suffix` 在 BSD/macOS 远端直接失败;
|
|
13
|
-
* 模板尾-X 约束的复发面见 adb 2026-07-25 修案)。
|
|
14
|
-
* R5 temp 位置参数化(workspace 腿 `-p <dir>`,ssh `-t`)—— 语义差异,不判正形。
|
|
15
|
-
*
|
|
16
|
-
* readLink 契约(逐字承各腿原注):必须返回**原始链接目标**(`readlink` 不带 `-f`),core 拿到后自己拼
|
|
17
|
-
* 相对目标并走带跳数上限的循环;链接目标含换行是病态形,按 trim 处理,不假装支持。
|
|
18
|
-
* statFileInfo 契约:**lstat 语义**(`stat` 不带 `-L`,不跟链)—— symlink 自身可被 fileInfo 判出,
|
|
19
|
-
* core 的敏感路径/写门靠这个(exists→canonicalPath 失败→fileInfo 判 symlink→readLink)。
|
|
20
|
-
*/
|
|
21
1
|
import path from "node:path";
|
|
22
2
|
import { FileError, } from "@sema-agent/core";
|
|
23
3
|
import { shellQuote, kindFromMode } from "./remote-shell.js";
|
|
@@ -53,7 +33,6 @@ export function createPosixShellFs(deps) {
|
|
|
53
33
|
},
|
|
54
34
|
async statFileInfo(p, abortSignal) {
|
|
55
35
|
const abs = resolve(p);
|
|
56
|
-
// %s size / %Y mtime(epoch s)/ %f raw hex mode —— GNU、busybox、toybox 三家一致;无 `-L` = lstat。
|
|
57
36
|
const r = await run(`stat -c '%s %Y %f' -- ${shellQuote(abs)}`, abortSignal);
|
|
58
37
|
if (!r.ok)
|
|
59
38
|
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
@@ -79,7 +58,6 @@ export function createPosixShellFs(deps) {
|
|
|
79
58
|
const out = [];
|
|
80
59
|
for (const name of names) {
|
|
81
60
|
const info = await fs.statFileInfo(path.posix.join(abs, name), abortSignal);
|
|
82
|
-
// 竞态容忍(承各腿原形):条目在 ls 与 stat 之间消失/不可读时降级为占位行,不让整个列表失败。
|
|
83
61
|
if (info.ok)
|
|
84
62
|
out.push(info.value);
|
|
85
63
|
else
|
|
@@ -89,11 +67,6 @@ export function createPosixShellFs(deps) {
|
|
|
89
67
|
},
|
|
90
68
|
async exists(p, abortSignal) {
|
|
91
69
|
const abs = resolve(p);
|
|
92
|
-
// R6(k8s 形=正形,原注全额承接):`[ -e ]` 经 stdout 答 yes/no —— 不依赖 stderr(鲁棒于 fast-exit
|
|
93
|
-
// 下 stderr 被截断的竞态),单往返。`|| [ -L ]` 是 2026-07-25 补的跨腿分叉:`[ -e ]` **跟随**符号链接,
|
|
94
|
-
// 悬空链接(`latest -> ./build-123` 未构建)会答 false,而 core 的 canonicalizeTarget 第一步就是
|
|
95
|
-
// exists —— 答 false ⇒ 走「目标不存在」分支,敏感路径门拿到的键是无害的链接名而非真目标(core 注释
|
|
96
|
-
// 点名要 fail-closed 防的绕过形)。`[ -e ] || [ -L ]` = lstat 语义,与 host 腿对齐。
|
|
97
70
|
const r = await run(`{ [ -e ${shellQuote(abs)} ] || [ -L ${shellQuote(abs)} ]; } && printf yes || printf no`, abortSignal);
|
|
98
71
|
if (!r.ok)
|
|
99
72
|
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
@@ -106,7 +79,6 @@ export function createPosixShellFs(deps) {
|
|
|
106
79
|
},
|
|
107
80
|
async createDir(p, options) {
|
|
108
81
|
const abs = resolve(p);
|
|
109
|
-
// sftp/一次性 mkdir 都非递归 ⇒ 恒 `-p`(对已存在目录也幂等)——承 ssh/adb/k8s 原形。
|
|
110
82
|
const r = await run(`mkdir -p -- ${shellQuote(abs)}`, options?.abortSignal);
|
|
111
83
|
if (!r.ok)
|
|
112
84
|
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
@@ -122,7 +94,7 @@ export function createPosixShellFs(deps) {
|
|
|
122
94
|
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
123
95
|
if (r.value.exitCode !== 0) {
|
|
124
96
|
if (options?.force && /no such file/i.test(r.value.stderr))
|
|
125
|
-
return ok(undefined);
|
|
97
|
+
return ok(undefined);
|
|
126
98
|
return { ok: false, error: classifyFsStderr(r.value.stderr, abs, "unknown") };
|
|
127
99
|
}
|
|
128
100
|
return ok(undefined);
|
|
@@ -138,8 +110,6 @@ export function createPosixShellFs(deps) {
|
|
|
138
110
|
const sfx = options?.suffix ?? "";
|
|
139
111
|
const dir = deps.tempDir();
|
|
140
112
|
const where = dir === null ? "-t" : `-p ${shellQuote(dir)}`;
|
|
141
|
-
// R4:`mktemp` 要求 X 在**末尾**,后缀不能折进模板(折进去产出的是一个带字面 XXXXXX 的文件名,
|
|
142
|
-
// 第二次调用同名撞车)。正解=合法尾-X 模板建(基名唯一),后缀 `mv` 上去 —— 目标名因基名唯一而唯一。
|
|
143
113
|
const cmd = sfx
|
|
144
114
|
? `f=$(mktemp ${where} ${shellQuote(`${prefix}XXXXXX`)}) && mv -- "$f" "$f"${shellQuote(sfx)} && printf '%s' "$f"${shellQuote(sfx)}`
|
|
145
115
|
: `mktemp ${where} ${shellQuote(`${prefix}XXXXXX`)}`;
|
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `RemoteAdbExecutionEnv` — the ADB implementation of core's `RemoteExecutionEnv` seam (design/61). A PEER
|
|
3
|
-
* adapter to SSH/E2B (NOT chained). Use case: an AI driving on-device APP testing over `adb` (a real phone /
|
|
4
|
-
* emulator).
|
|
5
|
-
*
|
|
6
|
-
* 🔴 **An ADB target is a REAL device, not a disposable sandbox** (design/61 §5) — same posture as SSH:
|
|
7
|
-
* - `capabilities = { isolation: false, suspendable: false }` — actions are PERMANENT; the leader tightens
|
|
8
|
-
* autonomy + the design/37 gate + HITL run on a non-isolated env (design/53).
|
|
9
|
-
* - `destroy()` / `cleanup()` **`adb disconnect`** the (TCP) device — they do NOT wipe/reset the phone.
|
|
10
|
-
* - `suspendVM`/`resumeVM`/`postResumeInit` → typed `"unsupported"` (a physical device isn't snapshotable).
|
|
11
|
-
* `reconnect` re-runs `adb connect`.
|
|
12
|
-
*
|
|
13
|
-
* 🔴 **First-attach is flaky → retry** (design/61 §2A): a fresh `adb connect` / a device that is still
|
|
14
|
-
* `unauthorized` (USB-debug prompt) or `offline` surfaces `auth_transient`, which `connect` retries via
|
|
15
|
-
* `withRetry` (idempotent establishment only). A genuinely rejected device → `auth_failed` (permanent).
|
|
16
|
-
*
|
|
17
|
-
* 🔴 **Credentials / device identity** are control-plane config (the device `serial` / `host:port`), injected
|
|
18
|
-
* to the ADAPTER — never the workspace (design/61 §5 #2).
|
|
19
|
-
*
|
|
20
|
-
* **APP install** is NOT a seam method: the agent pushes the APK with `writeFile` then runs `pm install` (or
|
|
21
|
-
* `adb install` is reachable via the device's own shell) through `exec` — the ExecutionEnv stays generic.
|
|
22
|
-
*
|
|
23
|
-
* ⚠️ **Device-untested:** built mirroring the SSH adapter (which IS proven against a real host); no physical
|
|
24
|
-
* device/emulator was available to integration-test the `adb` paths. The contracts are locked by unit tests;
|
|
25
|
-
* the live `adb` round-trip needs a device run before production use.
|
|
26
|
-
*/
|
|
27
1
|
import path from "node:path";
|
|
28
2
|
import os from "node:os";
|
|
29
3
|
import fs from "node:fs/promises";
|
|
@@ -40,8 +14,6 @@ const unsupported = (op) => ({
|
|
|
40
14
|
error: new RemoteExecutionError("unsupported", `${op} is not supported on an ADB device (real hardware — not snapshotable; capabilities.suspendable=false)`),
|
|
41
15
|
});
|
|
42
16
|
const isTcp = (serial) => /:\d+$/.test(serial);
|
|
43
|
-
/** [R78]#1: adb stderr markers that mean the DEVICE TRANSPORT died mid-op (not a normal command failure) —
|
|
44
|
-
* typed retryable (reconnect + retry per idempotency), never a fake command result. Exported for tests. */
|
|
45
17
|
export function isAdbTransportLost(stderr) {
|
|
46
18
|
return /device offline|device '.*' not found|no devices\/emulators found|connection reset|protocol fault|device disconnected/i.test(stderr);
|
|
47
19
|
}
|
|
@@ -49,12 +21,6 @@ export class RemoteAdbExecutionEnv {
|
|
|
49
21
|
capabilities = { isolation: false, suspendable: false };
|
|
50
22
|
cwd;
|
|
51
23
|
connected = false;
|
|
52
|
-
/** 🔴 拆除是**终态**(2026-07-25 补齐:此前本适配器是同族里唯一没有这个概念的一条腿)。
|
|
53
|
-
* 没有它的后果实测:`destroy()` 只清 `connected`,于是任何残留引用**一次 `connect()` 就把 env 整个复活**,
|
|
54
|
-
* `exec` 又能在真设备上跑;而 `reconnect(sessionToken)` 就在同一个接缝上摆着。读面更松 —— 它连
|
|
55
|
-
* `connected` 都不查,所以 destroy 之后 `readTextFile` 照样从真设备把文件读出来。
|
|
56
|
-
* 同姿态的 ssh 腿早有先例(`remote-env-ssh.ts`「destroy 后一律不复活,显式 connect/reconnect 同禁」),
|
|
57
|
-
* 五条兄弟腿全部 fail-closed,只有 adb 例外 —— 补齐它。 */
|
|
58
24
|
destroyed = false;
|
|
59
25
|
cfg;
|
|
60
26
|
spawnFn;
|
|
@@ -78,14 +44,11 @@ export class RemoteAdbExecutionEnv {
|
|
|
78
44
|
workspaceHandle() {
|
|
79
45
|
return { sandboxId: this.cfg.serial, provider: PROVIDER, mountPath: this.cwd };
|
|
80
46
|
}
|
|
81
|
-
// ─────────────────────────────── lifecycle ───────────────────────────────
|
|
82
47
|
async connect(config) {
|
|
83
|
-
// 拆除后一律不复活(显式 connect / reconnect 同禁)—— 与 ssh 腿逐字同口径:被拆除的 env 是 teardown
|
|
84
|
-
// 的产物,重连它等于把已释放的真实设备重新挂上,而那时已经没有谁负责再断开它。要新连接就建新 env。
|
|
85
48
|
if (this.destroyed)
|
|
86
49
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "ADB env destroyed — create a fresh env instead of reviving") };
|
|
87
50
|
const policy = {
|
|
88
|
-
retryableCodes: ["auth_transient", "connect_failed", "timeout"],
|
|
51
|
+
retryableCodes: ["auth_transient", "connect_failed", "timeout"],
|
|
89
52
|
maxAttempts: this.cfg.retry.maxAttempts,
|
|
90
53
|
backoffMs: (failed) => this.cfg.retry.backoffMs * failed,
|
|
91
54
|
};
|
|
@@ -105,7 +68,6 @@ export class RemoteAdbExecutionEnv {
|
|
|
105
68
|
if (/cannot connect|connection refused|no route/.test(out))
|
|
106
69
|
return { ok: false, error: new RemoteExecutionError("connect_failed", c.value.stdout.trim()) };
|
|
107
70
|
}
|
|
108
|
-
// Confirm the device is actually usable (not unauthorized/offline) before declaring connected.
|
|
109
71
|
const st = await this.adb(["-s", this.cfg.serial, "get-state"], { signal, timeoutMs: this.cfg.commandTimeoutMs });
|
|
110
72
|
if (!st.ok)
|
|
111
73
|
return { ok: false, error: new RemoteExecutionError("connect_failed", `adb get-state failed: ${st.error.message}`, st.error) };
|
|
@@ -135,23 +97,19 @@ export class RemoteAdbExecutionEnv {
|
|
|
135
97
|
this.connected = false;
|
|
136
98
|
return this.connect();
|
|
137
99
|
}
|
|
138
|
-
/** `adb disconnect` the TCP device (USB: just drop our flag). Does NOT reset the phone — idempotent, never throws. */
|
|
139
100
|
async destroy() {
|
|
140
101
|
if (isTcp(this.cfg.serial))
|
|
141
102
|
await this.adb(["disconnect", this.cfg.serial], { timeoutMs: 10_000 }).catch(() => { });
|
|
142
103
|
this.connected = false;
|
|
143
|
-
this.destroyed = true;
|
|
104
|
+
this.destroyed = true;
|
|
144
105
|
}
|
|
145
106
|
async cleanup() {
|
|
146
107
|
return this.destroy();
|
|
147
108
|
}
|
|
148
|
-
// ─────────────────────────────── exec ───────────────────────────────
|
|
149
109
|
async exec(command, options) {
|
|
150
110
|
if (!this.connected)
|
|
151
111
|
return { ok: false, error: new ExecutionError("shell_unavailable", "ADB device not connected — call connect() first") };
|
|
152
112
|
const wrapped = this.withCwdEnv(command, options?.cwd, options?.env);
|
|
153
|
-
// exec-out (shell protocol v2, NO pty): clean separated stdout/stderr + a real exit code. `adb shell` allocates
|
|
154
|
-
// a pty that merges stderr into stdout and does CRLF translation — wrong for capture/streaming (council §9.2).
|
|
155
113
|
const r = await this.adb(["-s", this.cfg.serial, "exec-out", wrapped], {
|
|
156
114
|
signal: options?.abortSignal,
|
|
157
115
|
timeoutMs: options?.timeout != null ? options.timeout * 1000 : this.cfg.commandTimeoutMs,
|
|
@@ -160,10 +118,8 @@ export class RemoteAdbExecutionEnv {
|
|
|
160
118
|
});
|
|
161
119
|
if (!r.ok)
|
|
162
120
|
return { ok: false, error: r.error };
|
|
163
|
-
// A device-transport drop mid-op is NOT a command result — typed "transport_lost" since core 1.89
|
|
164
|
-
// (outcome UNKNOWN — reconnect, then retry only per the op's idempotency).
|
|
165
121
|
if (r.value.exitCode !== 0 && isAdbTransportLost(r.value.stderr)) {
|
|
166
|
-
this.connected = false;
|
|
122
|
+
this.connected = false;
|
|
167
123
|
return { ok: false, error: new ExecutionError("transport_lost", `adb transport lost mid-command: ${r.value.stderr.trim().slice(0, 200)} — reconnect + retry per idempotency`) };
|
|
168
124
|
}
|
|
169
125
|
return ok(r.value);
|
|
@@ -172,7 +128,7 @@ export class RemoteAdbExecutionEnv {
|
|
|
172
128
|
if (!this.connected)
|
|
173
129
|
throw new RemoteExecutionError("connect_failed", "ADB device not connected — call connect() first");
|
|
174
130
|
const wrapped = this.withCwdEnv(command, options?.cwd, options?.env);
|
|
175
|
-
const child = this.spawnFn(this.cfg.adbPath, ["-s", this.cfg.serial, "exec-out", wrapped], { stdio: ["ignore", "pipe", "pipe"] });
|
|
131
|
+
const child = this.spawnFn(this.cfg.adbPath, ["-s", this.cfg.serial, "exec-out", wrapped], { stdio: ["ignore", "pipe", "pipe"] });
|
|
176
132
|
const queue = [];
|
|
177
133
|
let finished = false;
|
|
178
134
|
let failure;
|
|
@@ -188,8 +144,6 @@ export class RemoteAdbExecutionEnv {
|
|
|
188
144
|
let paused = false;
|
|
189
145
|
let onAbort;
|
|
190
146
|
const teardown = () => {
|
|
191
|
-
// every exit path (normal end, timeout/failure throw, consumer early-break via .return()): drop the abort
|
|
192
|
-
// listener (council #5) + kill the child if still running.
|
|
193
147
|
if (onAbort) {
|
|
194
148
|
options?.signal?.removeEventListener("abort", onAbort);
|
|
195
149
|
onAbort = undefined;
|
|
@@ -199,7 +153,6 @@ export class RemoteAdbExecutionEnv {
|
|
|
199
153
|
child.kill("SIGKILL");
|
|
200
154
|
}
|
|
201
155
|
catch {
|
|
202
|
-
/* noop */
|
|
203
156
|
}
|
|
204
157
|
}
|
|
205
158
|
};
|
|
@@ -219,12 +172,12 @@ export class RemoteAdbExecutionEnv {
|
|
|
219
172
|
lastChunk = Date.now();
|
|
220
173
|
queue.push(chunk);
|
|
221
174
|
if (queue.length >= 512 && !paused) {
|
|
222
|
-
paused = true;
|
|
175
|
+
paused = true;
|
|
223
176
|
child.stdout.pause();
|
|
224
177
|
}
|
|
225
178
|
signalReady();
|
|
226
179
|
};
|
|
227
|
-
let stderrTail = "";
|
|
180
|
+
let stderrTail = "";
|
|
228
181
|
child.stdout.on("data", (d) => enqueue({ type: "stdout", data: d.toString() }));
|
|
229
182
|
child.stderr.on("data", (d) => {
|
|
230
183
|
stderrTail = (stderrTail + d.toString()).slice(-512);
|
|
@@ -237,12 +190,9 @@ export class RemoteAdbExecutionEnv {
|
|
|
237
190
|
});
|
|
238
191
|
child.on("close", (code, signal) => {
|
|
239
192
|
if ((code ?? 0) !== 0 && isAdbTransportLost(stderrTail) && !failure) {
|
|
240
|
-
// device transport died mid-stream — typed retryable, not a fake exit ([R78]#1)
|
|
241
193
|
this.connected = false;
|
|
242
194
|
failure = new RemoteExecutionError("transport_lost", `adb transport lost mid-stream: ${stderrTail.trim().slice(0, 200)}`);
|
|
243
195
|
}
|
|
244
|
-
// code==null WITH a signal = the adb child was killed by an external signal (SIGKILL/OOM) — conventional
|
|
245
|
-
// 128+signo, NOT a fake success 0 ([R78]#2; the second `close` argument was dropped before this fix).
|
|
246
196
|
exitCode = code ?? (signal ? 128 + (signalNumber(signal) ?? 9) : 0);
|
|
247
197
|
finished = true;
|
|
248
198
|
signalReady();
|
|
@@ -260,7 +210,6 @@ export class RemoteAdbExecutionEnv {
|
|
|
260
210
|
if (finished)
|
|
261
211
|
break;
|
|
262
212
|
const waitMs = idleMs ? Math.max(1, idleMs - (Date.now() - lastChunk)) : 30_000;
|
|
263
|
-
// [4947] A-002.11 sibling: clear the losing timer — wake() firing first used to leave it armed.
|
|
264
213
|
let waitTimer;
|
|
265
214
|
try {
|
|
266
215
|
await new Promise((r) => {
|
|
@@ -284,15 +233,12 @@ export class RemoteAdbExecutionEnv {
|
|
|
284
233
|
teardown();
|
|
285
234
|
}
|
|
286
235
|
}
|
|
287
|
-
// ─────────────────────────────── FileSystem (adb shell + push/exec-out) ───────────────────────────────
|
|
288
236
|
async absolutePath(p) {
|
|
289
237
|
return ok(this.resolve(p));
|
|
290
238
|
}
|
|
291
239
|
async joinPath(parts) {
|
|
292
240
|
return ok(path.posix.join(...parts));
|
|
293
241
|
}
|
|
294
|
-
/** 文件面的终态门 —— 读面此前**连 `connected` 都不查**,所以 destroy 之后还能从真设备读出文件内容。
|
|
295
|
-
* 与 host/local-docker/k8s/e2b 的姿态对齐(它们的文件面都在 destroy 后 fail-closed)。 */
|
|
296
242
|
fsGate(p) {
|
|
297
243
|
if (this.destroyed)
|
|
298
244
|
return { ok: false, error: new FileError("unknown", "execution env already destroyed", this.resolve(p)) };
|
|
@@ -303,7 +249,6 @@ export class RemoteAdbExecutionEnv {
|
|
|
303
249
|
if (gated)
|
|
304
250
|
return gated;
|
|
305
251
|
const abs = this.resolve(p);
|
|
306
|
-
// exec-out streams the file's raw bytes (binary-safe); stderr carries cat's error if any.
|
|
307
252
|
const r = await this.adb(["-s", this.cfg.serial, "exec-out", `cat ${shellQuote(abs)}`], { signal: abortSignal, timeoutMs: this.cfg.dataTimeoutMs, binary: true });
|
|
308
253
|
if (!r.ok)
|
|
309
254
|
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
@@ -317,8 +262,6 @@ export class RemoteAdbExecutionEnv {
|
|
|
317
262
|
return { ok: false, error: new FileError("is_directory", errOut, abs) };
|
|
318
263
|
return { ok: false, error: new FileError("unknown", errOut, abs) };
|
|
319
264
|
}
|
|
320
|
-
// exec-out gives no exit code on the wire, but the adb transport itself can fail non-zero with empty stderr
|
|
321
|
-
// (device dropped mid-read) — don't mistake that for a legit empty file (council #6).
|
|
322
265
|
if (r.value.exitCode !== 0)
|
|
323
266
|
return { ok: false, error: new FileError("unknown", `adb exec-out cat exited ${r.value.exitCode}`, abs) };
|
|
324
267
|
return ok(r.value.stdoutBytes ?? new Uint8Array(0));
|
|
@@ -343,16 +286,11 @@ export class RemoteAdbExecutionEnv {
|
|
|
343
286
|
const dir = path.posix.dirname(abs);
|
|
344
287
|
if (dir && dir !== "." && dir !== "/") {
|
|
345
288
|
const mk = await this.exec(`mkdir -p ${shellQuote(dir)}`, { abortSignal });
|
|
346
|
-
// 🔴 `!mk.ok` 也必须 fail-closed(2026-07-25 补):原来只看 `exitCode !== 0`,于是 **exec 本身失败**
|
|
347
|
-
// (设备/连接在这一拍掉线 ⇒ `transport_lost` / `shell_unavailable`)被整条吞掉,后面照样推载荷,
|
|
348
|
-
// 而整个 `writeFile` 返回 **ok:true** —— 调用方看到的成功与真实故障点完全脱节。同族的
|
|
349
|
-
// local-docker / k8s 用的正是 `if (!mk.ok) return`,这两条腿是漏的。
|
|
350
289
|
if (!mk.ok)
|
|
351
290
|
return { ok: false, error: fileErrorFromExec(mk.error, dir) };
|
|
352
291
|
if (mk.value.exitCode !== 0)
|
|
353
292
|
return { ok: false, error: new FileError("unknown", mk.value.stderr.trim() || `mkdir -p failed for ${dir}`, dir) };
|
|
354
293
|
}
|
|
355
|
-
// Stage to a local temp then `adb push` (binary-safe; avoids shell-escaping the payload).
|
|
356
294
|
let local;
|
|
357
295
|
try {
|
|
358
296
|
local = path.join(os.tmpdir(), `adb-push-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
@@ -391,9 +329,6 @@ export class RemoteAdbExecutionEnv {
|
|
|
391
329
|
merged.set(add, base.length);
|
|
392
330
|
return this.writeFile(abs, merged, abortSignal);
|
|
393
331
|
}
|
|
394
|
-
// ── design/158 B6:以下 POSIX shell-out 文件面全部委托共享基座(posix-shell-fs.ts)──────────────
|
|
395
|
-
// 五裁决(`--`/classifyFsStderr/ls -1A/mv 后缀形/temp 位置参数化)与 readLink・lstat 契约见基座头注;
|
|
396
|
-
// 委托钉在 test/posix-shell-fs.test.ts(腿内不再自拼 readlink/realpath 命令串)。
|
|
397
332
|
posixFsInst;
|
|
398
333
|
get posixFs() {
|
|
399
334
|
return (this.posixFsInst ??= createPosixShellFs({
|
|
@@ -429,42 +364,27 @@ export class RemoteAdbExecutionEnv {
|
|
|
429
364
|
async createTempFile(options) {
|
|
430
365
|
return this.posixFs.createTempFile(options);
|
|
431
366
|
}
|
|
432
|
-
// ─────────────────────────────── helpers ───────────────────────────────
|
|
433
|
-
/** Spawn the adb binary with the given args; collect stdout/stderr (+ raw bytes when binary), bound by timeout/abort. */
|
|
434
367
|
adb(args, opts) {
|
|
435
368
|
return new Promise((resolve) => {
|
|
436
369
|
let settled = false;
|
|
437
|
-
// D5 pipe-destroy grace: armed by killGrace() on abort/timeout; destroys stdout/stderr pipes if `close`
|
|
438
|
-
// never fires (a re-parented grandchild holding the FD). Cleared in finish() — the real `close` routes
|
|
439
|
-
// through finish() — so the timer never leaks. Does NOT change the synchronous settle below.
|
|
440
370
|
let forceSettleTimer;
|
|
441
371
|
const child = this.spawnFn(this.cfg.adbPath, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
442
|
-
// SIGKILL the child then arm the D5 pipe-destroy grace (only on the abort/timeout paths — a normal `close`
|
|
443
|
-
// never calls this). Idempotent: only one timer is ever armed.
|
|
444
372
|
const killGrace = () => {
|
|
445
373
|
try {
|
|
446
374
|
child.kill("SIGKILL");
|
|
447
375
|
}
|
|
448
|
-
catch {
|
|
376
|
+
catch { }
|
|
449
377
|
forceSettleTimer ??= armPipeDestroyGrace(child);
|
|
450
378
|
};
|
|
451
|
-
// 🔴 binary caveat (D2): this helper is SHARED by the model-facing TEXT exec (must be bounded — a
|
|
452
|
-
// `yes`/`cat big` could OOM the durable worker) and by readBinaryFile (`exec-out cat`, opts.binary=true,
|
|
453
|
-
// which MUST keep EVERY byte or binary/large reads silently corrupt). So we branch on opts.binary:
|
|
454
|
-
// - binary=true → full Buffer[] + concat (byte-exact stdoutBytes; readBinaryFile path).
|
|
455
|
-
// - binary=false → bounded rolling-TAIL for stdout (keep last 8MB + dropped-byte marker, O(maxBytes)).
|
|
456
|
-
// stderr is ALWAYS bounded — it never carries the binary payload (just cat's error text).
|
|
457
379
|
const binary = !!opts?.binary;
|
|
458
|
-
const outBufs = [];
|
|
459
|
-
const outTail = binary ? undefined : new RollingTailBuffer();
|
|
460
|
-
const errBuf = new RollingTailBuffer();
|
|
380
|
+
const outBufs = [];
|
|
381
|
+
const outTail = binary ? undefined : new RollingTailBuffer();
|
|
382
|
+
const errBuf = new RollingTailBuffer();
|
|
461
383
|
const finish = (r) => {
|
|
462
384
|
if (settled)
|
|
463
385
|
return;
|
|
464
386
|
settled = true;
|
|
465
387
|
clearTimeout(timer);
|
|
466
|
-
// D5 grace cleared in the `close` handler (pipes closed naturally), NOT here — abort/timeout reaches
|
|
467
|
-
// finish() BEFORE `close`, so clearing here would cancel the grace before it can free a leaked FD.
|
|
468
388
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
469
389
|
resolve(r);
|
|
470
390
|
};
|
|
@@ -487,7 +407,7 @@ export class RemoteAdbExecutionEnv {
|
|
|
487
407
|
outBufs.push(d);
|
|
488
408
|
else
|
|
489
409
|
outTail.push(d);
|
|
490
|
-
opts?.onStdout?.(d.toString());
|
|
410
|
+
opts?.onStdout?.(d.toString());
|
|
491
411
|
});
|
|
492
412
|
child.stderr.on("data", (d) => {
|
|
493
413
|
errBuf.push(d);
|
|
@@ -496,15 +416,11 @@ export class RemoteAdbExecutionEnv {
|
|
|
496
416
|
child.on("error", (e) => finish({ ok: false, error: new ExecutionError("spawn_error", `adb spawn failed (is '${this.cfg.adbPath}' installed?): ${e.message}`, e) }));
|
|
497
417
|
child.on("close", (code, signal) => {
|
|
498
418
|
if (forceSettleTimer)
|
|
499
|
-
clearTimeout(forceSettleTimer);
|
|
419
|
+
clearTimeout(forceSettleTimer);
|
|
500
420
|
const err = errBuf.result();
|
|
501
421
|
const stderr = markTruncated(err.text, err.droppedBytes);
|
|
502
|
-
// code==null WITH a signal = the adb child was killed by an external signal (SIGKILL/OOM) —
|
|
503
|
-
// conventional 128+signo, NOT a fake success 0 ([R78]#2; the second `close` argument was dropped
|
|
504
|
-
// before this fix).
|
|
505
422
|
const exitCode = code ?? (signal ? 128 + (signalNumber(signal) ?? 9) : 0);
|
|
506
423
|
if (binary) {
|
|
507
|
-
// byte-exact: readBinaryFile decodes stdoutBytes; never truncate.
|
|
508
424
|
const stdoutBytes = Buffer.concat(outBufs);
|
|
509
425
|
finish(ok({ stdout: stdoutBytes.toString("utf8"), stderr, exitCode, stdoutBytes: new Uint8Array(stdoutBytes) }));
|
|
510
426
|
}
|
|
@@ -527,11 +443,6 @@ export class RemoteAdbExecutionEnv {
|
|
|
527
443
|
return parts.join(" ");
|
|
528
444
|
}
|
|
529
445
|
}
|
|
530
|
-
/**
|
|
531
|
-
* `ExecutionEnvFactory` for the ADB backend — one fresh (unconnected) env per task against the same device
|
|
532
|
-
* (lazy: `adb connect` on first use). Config (serial) is control-plane-supplied; the Runner owns the lifetime
|
|
533
|
-
* and calls `destroy()` (= `adb disconnect`, never a device wipe) on task end.
|
|
534
|
-
*/
|
|
535
446
|
export function adbExecutionEnvFactory(config) {
|
|
536
447
|
return (_ctx) => new RemoteAdbExecutionEnv(config);
|
|
537
448
|
}
|