@sema-agent/server 7.78.2 → 7.79.1
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/dist/auto-mode-face.d.ts +23 -11
- package/dist/auto-mode-face.js +5 -2
- package/dist/boot/deferred-sandbox-path-env.d.ts +47 -0
- package/dist/boot/deferred-sandbox-path-env.js +1 -5
- package/dist/boot/resolve-spec.js +21 -2
- package/dist/capabilities/code-review-council.js +2 -1
- package/dist/config.js +4 -3
- package/dist/env-facts.d.ts +61 -0
- package/dist/env-facts.js +32 -6
- package/dist/execution-lane-caps.d.ts +12 -0
- package/dist/execution-lane-caps.js +8 -0
- package/dist/file-history-disclosure.d.ts +53 -0
- package/dist/file-history-disclosure.js +89 -0
- package/dist/http/admission.d.ts +56 -0
- package/dist/http/admission.js +560 -0
- package/dist/http/approval-sweeps.d.ts +21 -0
- package/dist/http/approval-sweeps.js +138 -0
- package/dist/http/dispatch.d.ts +96 -0
- package/dist/http/dispatch.js +499 -0
- package/dist/http/request-guards.d.ts +51 -0
- package/dist/http/request-guards.js +205 -0
- package/dist/http/resume-legs.d.ts +80 -0
- package/dist/http/resume-legs.js +1230 -0
- package/dist/http/server.d.ts +19 -104
- package/dist/http/server.js +23 -2589
- package/dist/main.js +6 -3
- package/dist/model-select.d.ts +4 -0
- package/dist/model-select.js +2 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/plugins/file-history-store-sql.js +12 -11
- package/dist/plugins/remote-env-adb.d.ts +11 -0
- package/dist/plugins/remote-env-adb.js +1 -1
- package/dist/plugins/remote-env-device.d.ts +11 -0
- package/dist/plugins/remote-env-device.js +1 -1
- package/dist/plugins/remote-env-host.d.ts +11 -0
- package/dist/plugins/remote-env-host.js +1 -1
- package/dist/plugins/remote-env-k8s.d.ts +11 -0
- package/dist/plugins/remote-env-k8s.js +1 -1
- package/dist/plugins/remote-env-ssh.d.ts +11 -0
- package/dist/plugins/remote-env-ssh.js +1 -1
- package/dist/run-local.js +2 -1
- package/dist/task-cwd.d.ts +24 -0
- package/dist/task-cwd.js +9 -0
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { DEFAULT_MODEL_REF } from "./model-select.js";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
4
|
-
import { Runner, terminalProjection, createPermissionRuleStoreProvider, InMemoryToolResultStore, TtlSessionStore, uuidv7, defaultTaskRegistry, createAllowDenyPolicy, workflowsCapability, probeSearchBackend, describeStaticWiring,
|
|
5
|
+
import { Runner, terminalProjection, createPermissionRuleStoreProvider, InMemoryToolResultStore, TtlSessionStore, uuidv7, defaultTaskRegistry, createAllowDenyPolicy, workflowsCapability, probeSearchBackend, describeStaticWiring, DEFAULT_SUBAGENT_TOOL_NAME } from "@sema-agent/core";
|
|
5
6
|
import { createSessionTitler } from "./session-titler.js";
|
|
6
7
|
import { posIntEnv } from "./session-watch.js";
|
|
7
8
|
import { selectEnvironmentTool } from "./capabilities/select-environment-tool.js";
|
|
@@ -27,6 +28,7 @@ import { createToolTracer, createPermissionDeniedMeter } from "./observability/t
|
|
|
27
28
|
import { createRegistryJwtVerifier } from "./auth-bridge.js";
|
|
28
29
|
import { createMetrics } from "./observability/metrics.js";
|
|
29
30
|
import { installFailOpenRecorder } from "./observability/fail-open.js";
|
|
31
|
+
import { installFileHistoryFailureRecorder } from "./file-history-disclosure.js";
|
|
30
32
|
import { installSecretEnvScrubRecorder } from "./observability/secret-env-scrub.js";
|
|
31
33
|
import { setRunTerminalObserver } from "./observability/run-terminal-log.js";
|
|
32
34
|
import { setRedactionObserver, redactSecrets } from "./trace/redact.js";
|
|
@@ -95,6 +97,7 @@ async function main() {
|
|
|
95
97
|
const metrics = createMetrics();
|
|
96
98
|
installFailOpenRecorder({ logger, metrics });
|
|
97
99
|
installSecretEnvScrubRecorder({ logger, metrics });
|
|
100
|
+
installFileHistoryFailureRecorder({ logger, metrics });
|
|
98
101
|
logConfigDiagnostics(logger);
|
|
99
102
|
for (const w of drainNumEnvWarnings())
|
|
100
103
|
logger.warn("config_env_invalid_using_default", { env: w.env, raw: w.raw });
|
|
@@ -469,7 +472,7 @@ async function main() {
|
|
|
469
472
|
const ensureChildSessionDurable = checkpointStore && config.sessionBackend === "tidb" ? (sessionId) => ensureChildSessionDurableWithPromotion(sessionStore, subRunnerSessions, sessionId) : undefined;
|
|
470
473
|
const webFetchSummarize = createWebFetchSummarizeLane({ brain, config, getKeyResolver: () => configCenter.getKeyResolver(), logger });
|
|
471
474
|
const scenarioDeps = {
|
|
472
|
-
runner, subRunner, handslessSubRunner, model:
|
|
475
|
+
runner, subRunner, handslessSubRunner, model: DEFAULT_MODEL_REF, skills, repoClient, requirePrincipal: config.requirePrincipal,
|
|
473
476
|
webSearch: webSearch ? webSearch : undefined, metrics, logger, webFetchSummarize,
|
|
474
477
|
backgroundAgentStore: backgroundAgentStore ? backgroundAgentStore : undefined,
|
|
475
478
|
checkpointStore: checkpointStore ? checkpointStore : undefined,
|
|
@@ -692,7 +695,7 @@ async function main() {
|
|
|
692
695
|
engineArmsOnIntent: coreArmsAutoOnIntent(),
|
|
693
696
|
resolveCaps: runtimeCapsPeekResolver,
|
|
694
697
|
classifierModel: () => {
|
|
695
|
-
const route = classifierRouteFor(
|
|
698
|
+
const route = classifierRouteFor(runnerDeps);
|
|
696
699
|
if (route.fault !== undefined)
|
|
697
700
|
logger.debug("auto_mode_classifier_route_unresolved", { fault: route.fault });
|
|
698
701
|
return route.id;
|
package/dist/model-select.d.ts
CHANGED
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
* 🔴 Was a real defect: resolveSpec previously read ONLY the inline @mention, so `body.model` (the picker + the
|
|
24
24
|
* `--model` flag) was SILENTLY DROPPED — the picker UX was a no-op server-side. This restores it.
|
|
25
25
|
*/
|
|
26
|
+
/** 调用方**什么都没选**时这条 task 落到的目录 ref —— 即 `config.models.default` 那一格(引擎侧
|
|
27
|
+
* `resolveModel("default", catalog)` 解它)。**单点**:读面要说「这台部署的主模型是谁」时读同一个词
|
|
28
|
+
* (`auto-mode-face.ts` 的席解析器入参),两处各写一个 `"default"` 字面串正是漂移的成因。 */
|
|
29
|
+
export declare const DEFAULT_MODEL_REF = "default";
|
|
26
30
|
export declare function resolveTaskModel(bodyModel: unknown, objective: string, catalog: Record<string, unknown>, allowlist?: readonly string[]): {
|
|
27
31
|
model: string;
|
|
28
32
|
cleanedObjective: string;
|
package/dist/model-select.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { parseModelMention } from "@sema-agent/core";
|
|
2
|
+
export const DEFAULT_MODEL_REF = "default";
|
|
2
3
|
export function resolveTaskModel(bodyModel, objective, catalog, allowlist) {
|
|
3
4
|
const mention = parseModelMention(objective, Object.keys(catalog));
|
|
4
5
|
const explicit = typeof bodyModel === "string" ? matchCatalogModel(bodyModel, catalog) : undefined;
|
|
5
6
|
const present = bodyModel !== undefined && bodyModel !== null;
|
|
6
7
|
const unknown = present && explicit === undefined;
|
|
7
8
|
const repr = typeof bodyModel !== "string" ? `<non-string:${typeof bodyModel}>` : bodyModel === "" ? "<empty-string>" : bodyModel;
|
|
8
|
-
const model = explicit ?? mention.model ??
|
|
9
|
+
const model = explicit ?? mention.model ?? DEFAULT_MODEL_REF;
|
|
9
10
|
const picked = explicit !== undefined ? { ref: explicit, source: "explicit", requested: repr } : mention.model !== undefined ? { ref: mention.model, source: "mention", requested: mention.model } : undefined;
|
|
10
11
|
const notAllowed = picked !== undefined && !isModelAllowlisted(picked.ref, catalog, allowlist)
|
|
11
12
|
? { requested: picked.requested, resolved: terminalCatalogName(picked.ref, catalog) ?? picked.ref, source: picked.source }
|
|
@@ -221,7 +221,7 @@ export function createMetrics() {
|
|
|
221
221
|
m.counter("degraded_total", "Tasks degraded to a cheaper model (1.40), by reason (budget/rate_limit/breaker_open)");
|
|
222
222
|
m.counter("task_model_not_allowlisted_resume_total", "Resumed legs whose model/compactionModel is outside the @-mention allowlist (allowed to keep running by design — the allowlist gates fresh picks only)");
|
|
223
223
|
m.counter("prompt_constitution_total", "Prompt assembly constitution mode per task (core 1.243 提示词主权): steady state = core; provider-assembled/replaced/legacy warrant eyes");
|
|
224
|
-
m.counter("rewind_snapshot_failed_total", "Per-turn rewind file snapshots that failed, by code (
|
|
224
|
+
m.counter("rewind_snapshot_failed_total", "Per-turn rewind file snapshots that failed, by the engine's FileHistoryResult.error.code (snapshot_failed/aborted/…; `unknown` = the disclosure text carried no readable code). COARSE on purpose — whether a failure is permanent (the same file will fail every turn until the deployment changes) is counted by file_history_permanent_failure_total instead, from the typed error object rather than from prose");
|
|
225
225
|
m.counter("verifications_total", "Tasks run through the verification gate (1.44), by final verdict");
|
|
226
226
|
m.counter("cascade_total", "Tasks run through the model cascade (1.45), by outcome (passed/exhausted)");
|
|
227
227
|
m.counter("degenerate_total", "Degenerate-output failures (1.59), by salvage class (a/b/unknown)");
|
|
@@ -4,6 +4,7 @@ import { SqlBlobBackend } from "./blob-backend.js";
|
|
|
4
4
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
5
5
|
import { isMissingColumnError } from "./sql-errors.js";
|
|
6
6
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
7
|
+
import { discloseCaughtFailure } from "../file-history-disclosure.js";
|
|
7
8
|
export const FILE_HISTORY_BLOB_TABLE = "file_history_blob";
|
|
8
9
|
export const FILE_HISTORY_BLOB_KEY_PREFIX = "file-history/";
|
|
9
10
|
const sha256hex = (s) => createHash("sha256").update(s).digest("hex");
|
|
@@ -116,7 +117,7 @@ export class SqlFileHistoryStore {
|
|
|
116
117
|
return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} kept racing a concurrent retraction — retry` } };
|
|
117
118
|
}
|
|
118
119
|
catch (err) {
|
|
119
|
-
return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${err
|
|
120
|
+
return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${discloseCaughtFailure(err)}` } };
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
async annulIneligibleReason(scope, keyHash, absPath) {
|
|
@@ -189,7 +190,7 @@ export class SqlFileHistoryStore {
|
|
|
189
190
|
return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} was replaced while this retraction was being proven` } };
|
|
190
191
|
}
|
|
191
192
|
catch (err) {
|
|
192
|
-
return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${err
|
|
193
|
+
return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${discloseCaughtFailure(err)}` } };
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
async loadTracked(exec, scope, forUpdate = false) {
|
|
@@ -296,7 +297,7 @@ export class SqlFileHistoryStore {
|
|
|
296
297
|
}
|
|
297
298
|
}
|
|
298
299
|
catch (err) {
|
|
299
|
-
return { ok: false, error: { code: "snapshot_failed", message: err
|
|
300
|
+
return { ok: false, error: { code: "snapshot_failed", message: discloseCaughtFailure(err) } };
|
|
300
301
|
}
|
|
301
302
|
this.activeAttempts.add(lease);
|
|
302
303
|
try {
|
|
@@ -398,7 +399,7 @@ export class SqlFileHistoryStore {
|
|
|
398
399
|
catch (err) {
|
|
399
400
|
await conn.rollback().catch(() => undefined);
|
|
400
401
|
this.spentAttempts.add(lease);
|
|
401
|
-
return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${err
|
|
402
|
+
return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${discloseCaughtFailure(err)}` } };
|
|
402
403
|
}
|
|
403
404
|
finally {
|
|
404
405
|
conn.release();
|
|
@@ -408,7 +409,7 @@ export class SqlFileHistoryStore {
|
|
|
408
409
|
}
|
|
409
410
|
catch (err) {
|
|
410
411
|
this.spentAttempts.add(lease);
|
|
411
|
-
return { ok: false, error: { code: "snapshot_failed", message: err
|
|
412
|
+
return { ok: false, error: { code: "snapshot_failed", message: discloseCaughtFailure(err) } };
|
|
412
413
|
}
|
|
413
414
|
finally {
|
|
414
415
|
this.activeAttempts.delete(lease);
|
|
@@ -475,7 +476,7 @@ export class SqlFileHistoryStore {
|
|
|
475
476
|
});
|
|
476
477
|
}
|
|
477
478
|
catch (err) {
|
|
478
|
-
applied = { outcome: "failed", reason: err
|
|
479
|
+
applied = { outcome: "failed", reason: discloseCaughtFailure(err) };
|
|
479
480
|
}
|
|
480
481
|
if (applied.outcome === "applied")
|
|
481
482
|
ledger.filesChanged.push(t.trackKey);
|
|
@@ -492,7 +493,7 @@ export class SqlFileHistoryStore {
|
|
|
492
493
|
}
|
|
493
494
|
catch (err) {
|
|
494
495
|
const touched = empty.filesChanged.length > 0 || empty.identicalSkipped.length > 0 || empty.refused.length > 0 || empty.failed.length > 0;
|
|
495
|
-
return { outcome: touched ? "partial" : "failed", ...empty, error: { code: "restore_failed", message: err
|
|
496
|
+
return { outcome: touched ? "partial" : "failed", ...empty, error: { code: "restore_failed", message: discloseCaughtFailure(err) } };
|
|
496
497
|
}
|
|
497
498
|
}
|
|
498
499
|
async restorePlan(scope, entryId) {
|
|
@@ -593,7 +594,7 @@ export class SqlFileHistoryStore {
|
|
|
593
594
|
return { ok: true };
|
|
594
595
|
}
|
|
595
596
|
catch (err) {
|
|
596
|
-
return { ok: false, error: { code: "snapshot_failed", message: `putBlob ${hash} write/upload failed: ${err
|
|
597
|
+
return { ok: false, error: { code: "snapshot_failed", message: `putBlob ${hash} write/upload failed: ${discloseCaughtFailure(err)}` } };
|
|
597
598
|
}
|
|
598
599
|
}
|
|
599
600
|
async getBlob(hash) {
|
|
@@ -636,7 +637,7 @@ export class SqlFileHistoryStore {
|
|
|
636
637
|
}
|
|
637
638
|
}
|
|
638
639
|
catch (err) {
|
|
639
|
-
return { ok: false, error: { code: "conflict", message: `adoptScope failed: ${err
|
|
640
|
+
return { ok: false, error: { code: "conflict", message: `adoptScope failed: ${discloseCaughtFailure(err)}` } };
|
|
640
641
|
}
|
|
641
642
|
}
|
|
642
643
|
async scopeEmpty(exec, scope, forUpdate = false) {
|
|
@@ -735,7 +736,7 @@ export class SqlFileHistoryStore {
|
|
|
735
736
|
bytes = await srcGetBlob(hash);
|
|
736
737
|
}
|
|
737
738
|
catch (err) {
|
|
738
|
-
return { ok: false, error: { code: "unsupported_format", message: `source blob ${hash} fetch failed: ${err
|
|
739
|
+
return { ok: false, error: { code: "unsupported_format", message: `source blob ${hash} fetch failed: ${discloseCaughtFailure(err)}` } };
|
|
739
740
|
}
|
|
740
741
|
if (bytes === undefined)
|
|
741
742
|
return { ok: false, error: { code: "not_found", message: `source blob ${hash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
|
|
@@ -786,7 +787,7 @@ export class SqlFileHistoryStore {
|
|
|
786
787
|
}
|
|
787
788
|
}
|
|
788
789
|
catch (err) {
|
|
789
|
-
return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${err
|
|
790
|
+
return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${discloseCaughtFailure(err)}` } };
|
|
790
791
|
}
|
|
791
792
|
}
|
|
792
793
|
}
|
|
@@ -44,9 +44,20 @@ export interface AdbEnvDeps {
|
|
|
44
44
|
* typed retryable (reconnect + retry per idempotency), never a fake command result. Exported for tests. */
|
|
45
45
|
export declare function isAdbTransportLost(stderr: string): boolean;
|
|
46
46
|
export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
|
|
47
|
+
/**
|
|
48
|
+
* 🔴 **S-354 / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
|
|
49
|
+
* 就是**命令真正跑的那块文件系统**的权威答案(`posix-shell-fs` 的 `canonicalPath` 原语,在设备 shell 里解到落点)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
|
|
50
|
+
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
51
|
+
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
52
|
+
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
53
|
+
* 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
54
|
+
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
55
|
+
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
56
|
+
*/
|
|
47
57
|
readonly capabilities: {
|
|
48
58
|
isolation: boolean;
|
|
49
59
|
suspendable: boolean;
|
|
60
|
+
canonicalPathAuthoritative: boolean;
|
|
50
61
|
};
|
|
51
62
|
cwd: string;
|
|
52
63
|
private connected;
|
|
@@ -18,7 +18,7 @@ export function isAdbTransportLost(stderr) {
|
|
|
18
18
|
return /device offline|device '.*' not found|no devices\/emulators found|connection reset|protocol fault|device disconnected/i.test(stderr);
|
|
19
19
|
}
|
|
20
20
|
export class RemoteAdbExecutionEnv {
|
|
21
|
-
capabilities = { isolation: false, suspendable: false };
|
|
21
|
+
capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
|
|
22
22
|
cwd;
|
|
23
23
|
connected = false;
|
|
24
24
|
destroyed = false;
|
|
@@ -94,9 +94,20 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
|
|
|
94
94
|
* 谁要把 executor 的工作区改成临时目录,先来改这一行:那一刻这个 `false` 就变成谎话,park 赎回会
|
|
95
95
|
* 落到一个空工作区上而链路上没有任何一层会报错。
|
|
96
96
|
*/
|
|
97
|
+
/**
|
|
98
|
+
* 🔴 **S-354 / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
|
|
99
|
+
* 就是**命令真正跑的那块文件系统**的权威答案(设备 WS 协议的**必答** `canonicalPath` op)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
|
|
100
|
+
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
101
|
+
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
102
|
+
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
103
|
+
* 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
104
|
+
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
105
|
+
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
106
|
+
*/
|
|
97
107
|
readonly capabilities: {
|
|
98
108
|
isolation: boolean;
|
|
99
109
|
suspendable: boolean;
|
|
110
|
+
canonicalPathAuthoritative: boolean;
|
|
100
111
|
};
|
|
101
112
|
readonly externalContentTarget = true;
|
|
102
113
|
/** [ref]:设备路径不在 worker 宿主文件系统上 —— host-path 回执消费方据此拒绝把它当宿主路径呈现。 */
|
|
@@ -110,7 +110,7 @@ function assertPosixDevicePlatform(os, deviceId) {
|
|
|
110
110
|
export class RemoteDeviceExecutionEnv {
|
|
111
111
|
cfg;
|
|
112
112
|
homeDir;
|
|
113
|
-
capabilities = { isolation: false, suspendable: false };
|
|
113
|
+
capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
|
|
114
114
|
externalContentTarget = true;
|
|
115
115
|
hostLocalPaths = false;
|
|
116
116
|
cwd;
|
|
@@ -58,9 +58,20 @@ export interface HostEnvConfig {
|
|
|
58
58
|
}
|
|
59
59
|
export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, SchedulerCapability, BackgroundShellCapability {
|
|
60
60
|
/** [ref] §9 / DUAL-MODE-DESIGN §5: a `host` env is the operator's real machine — no isolation, no snapshot. */
|
|
61
|
+
/**
|
|
62
|
+
* 🔴 **S-354 / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
|
|
63
|
+
* 就是**命令真正跑的那块文件系统**的权威答案(worker 本机的 `fs.realpath`)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
|
|
64
|
+
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
65
|
+
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
66
|
+
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
67
|
+
* 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
68
|
+
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
69
|
+
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
70
|
+
*/
|
|
61
71
|
readonly capabilities: {
|
|
62
72
|
isolation: boolean;
|
|
63
73
|
suspendable: boolean;
|
|
74
|
+
canonicalPathAuthoritative: boolean;
|
|
64
75
|
};
|
|
65
76
|
/** R7 自唤醒: structural delegation to the injected scheduler backend. `hasScheduler(env)` checks BOTH the method
|
|
66
77
|
* presence (always, since we implement the interface) AND `schedulerCapabilities.supported===true` — which is only
|
|
@@ -34,7 +34,7 @@ const UNSUPPORTED_SCHEDULER_CAPS = {
|
|
|
34
34
|
maxScheduledHorizonSec: 0,
|
|
35
35
|
};
|
|
36
36
|
export class RemoteHostExecutionEnv {
|
|
37
|
-
capabilities = { isolation: false, suspendable: false };
|
|
37
|
+
capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
|
|
38
38
|
get schedulerCapabilities() {
|
|
39
39
|
return this.cfg.scheduler?.schedulerCapabilities ?? UNSUPPORTED_SCHEDULER_CAPS;
|
|
40
40
|
}
|
|
@@ -119,9 +119,20 @@ export { execSocketTlsOptions } from "./k8s-exec-protocol.js";
|
|
|
119
119
|
export declare function errorMessageOf(e: unknown): string;
|
|
120
120
|
export { exitCodeFromStatus } from "./k8s-exec-protocol.js";
|
|
121
121
|
export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, BackgroundShellCapability {
|
|
122
|
+
/**
|
|
123
|
+
* 🔴 **S-354 / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
|
|
124
|
+
* 就是**命令真正跑的那块文件系统**的权威答案(`posix-shell-fs` 的 `canonicalPath` 原语,经 pod exec 解到落点)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
|
|
125
|
+
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
126
|
+
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
127
|
+
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
128
|
+
* 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
129
|
+
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
130
|
+
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
131
|
+
*/
|
|
122
132
|
readonly capabilities: {
|
|
123
133
|
isolation: boolean;
|
|
124
134
|
suspendable: boolean;
|
|
135
|
+
canonicalPathAuthoritative: true;
|
|
125
136
|
};
|
|
126
137
|
cwd: string;
|
|
127
138
|
private podName?;
|
|
@@ -78,7 +78,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
78
78
|
this.cwd = this.cfg.mountPath;
|
|
79
79
|
this.homeDir = config.homeDir;
|
|
80
80
|
this.deps = deps ?? {};
|
|
81
|
-
this.capabilities = { isolation: true, suspendable: !!this.cfg.s3Snapshot };
|
|
81
|
+
this.capabilities = { isolation: true, suspendable: !!this.cfg.s3Snapshot, canonicalPathAuthoritative: true };
|
|
82
82
|
const bgMax = Math.max(1, Math.min(K8S_BG_MAX_TIMEOUT_SEC, Math.floor(this.cfg.timeoutMs / 1000)));
|
|
83
83
|
this.backgroundCapabilities = {
|
|
84
84
|
supported: true,
|
|
@@ -61,9 +61,20 @@ export interface SshEnvDeps {
|
|
|
61
61
|
connectOnce?: (signal?: AbortSignal) => Promise<ConnectResult>;
|
|
62
62
|
}
|
|
63
63
|
export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
|
|
64
|
+
/**
|
|
65
|
+
* 🔴 **S-354 / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
|
|
66
|
+
* 就是**命令真正跑的那块文件系统**的权威答案(`posix-shell-fs` 的 `canonicalPath` 原语,在真机上解到落点)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
|
|
67
|
+
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
68
|
+
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
69
|
+
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
70
|
+
* 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
71
|
+
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
72
|
+
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
73
|
+
*/
|
|
64
74
|
readonly capabilities: {
|
|
65
75
|
isolation: boolean;
|
|
66
76
|
suspendable: boolean;
|
|
77
|
+
canonicalPathAuthoritative: boolean;
|
|
67
78
|
};
|
|
68
79
|
/** Working directory; relative paths resolve against it (ExecutionEnv contract). Starts at mountPath. */
|
|
69
80
|
cwd: string;
|
|
@@ -16,7 +16,7 @@ function exitCodeForSignalKill(signal) {
|
|
|
16
16
|
return 128 + (signalNumber(`SIG${signal}`) ?? 9);
|
|
17
17
|
}
|
|
18
18
|
export class RemoteSshExecutionEnv {
|
|
19
|
-
capabilities = { isolation: false, suspendable: false };
|
|
19
|
+
capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
|
|
20
20
|
cwd;
|
|
21
21
|
conn;
|
|
22
22
|
sftpPromise;
|
package/dist/run-local.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { DEFAULT_MODEL_REF } from "./model-select.js";
|
|
2
3
|
import { terminalProjection } from "@sema-agent/core";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
5
|
import { join } from "node:path";
|
|
@@ -429,7 +430,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
429
430
|
const webSearch = webSearchCfg ? createWebSearchBackend(webSearchCfg) : undefined;
|
|
430
431
|
const taskListLane = createTaskListLane({ logger });
|
|
431
432
|
const scenarioDeps = {
|
|
432
|
-
runner, subRunner, handslessSubRunner, model:
|
|
433
|
+
runner, subRunner, handslessSubRunner, model: DEFAULT_MODEL_REF, skills, repoClient, metrics, logger,
|
|
433
434
|
...(webSearch ? { webSearch } : {}),
|
|
434
435
|
backgroundAgentStore: fileBackend.backgroundAgentStore,
|
|
435
436
|
taskListStoreFor: taskListLane.storeFor,
|
package/dist/task-cwd.d.ts
CHANGED
|
@@ -18,6 +18,30 @@ export declare const MAX_CWD_CHARS = 4096;
|
|
|
18
18
|
* 先改这里**:把判据换成 core 的 `isAbsoluteForFamily`,并把家族从这条 cwd 自己的拼法取。
|
|
19
19
|
* 钉见 `test/task-cwd.test.ts` 的「[ref] 路径家族接缝」格(实测读数,不是推断)。 */
|
|
20
20
|
export declare function isValidCwd(cwd: unknown): cwd is string;
|
|
21
|
+
/**
|
|
22
|
+
* 词法归一:纯字符串运算,折 `.` / `..` / 重复分隔符,**不碰 fs**。
|
|
23
|
+
* `posix.normalize` 把 `..` 在根部截断(`/../x` → `/x`),与「根之上没有东西」的语义一致;尾部分隔符
|
|
24
|
+
* 统一剥掉(根 `/` 除外),让同一目标只有一个 key —— 前缀判域(见 {@link dirCoversPath})靠的就是
|
|
25
|
+
* key 的唯一性。
|
|
26
|
+
*
|
|
27
|
+
* 🔴 **单点**(S-351 起):此前它的一份私有副本住在 `boot/deferred-sandbox-path-env.ts`(沙箱代理的
|
|
28
|
+
* `absolutePath` 词法臂),而写门豁免域与任务根的判域现在读的是同一条律 —— 两份各自归一就是两个 key
|
|
29
|
+
* 空间,而「豁免域盖没盖住任务根」这种判断在两个 key 空间里可以同时答是和否。
|
|
30
|
+
* (core 内部有同形的 `normalizeAbsPathLexically`,但未从包根导出;此处是 Node 标准库的等价运算,
|
|
31
|
+
* 不是它的抄本——若日后 core 导出,这里应改为直接复用。)
|
|
32
|
+
*/
|
|
33
|
+
export declare function normalizeAbsolutePathLexically(p: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* **词法**判域:`dir` 是不是 `target` 本身或它的祖先(= `target` ⊆ `dir`,「等于 ∪ 后代」)。
|
|
36
|
+
*
|
|
37
|
+
* 这是 core `assertExemptDirsDoNotCoverRoot` 那条律的本仓同形(亲读安装树
|
|
38
|
+
* `dist/core/fs-write-gate-policy.js:11-28` → `swallowedFenceRoot` → `isWithin(root, p)`:
|
|
39
|
+
* 先各自词法归一,再判 `p === root` 或 `p` 以 `root + "/"` 起头)。**两端都只做词法**,不 realpath ——
|
|
40
|
+
* 写门的构造期本来就没有 I/O(core 同理),承诺 canonical 是承诺一件此刻做不到的事。
|
|
41
|
+
*
|
|
42
|
+
* 段感知由「归一后 + 分隔符前缀」保证:`/a/bc` **不在** `/a/b` 域内。
|
|
43
|
+
*/
|
|
44
|
+
export declare function dirCoversPath(dir: string, target: string): boolean;
|
|
21
45
|
/**
|
|
22
46
|
* The deployment-level gate: does THIS deployment honor a caller-supplied `cwd`? TRUE only for the single-user
|
|
23
47
|
* `host` lane (see module header). All cloud/container lanes + any multi-tenant deployment → FALSE (cwd ignored).
|
package/dist/task-cwd.js
CHANGED
|
@@ -8,6 +8,15 @@ export function isValidCwd(cwd) {
|
|
|
8
8
|
path.isAbsolute(cwd) &&
|
|
9
9
|
!cwd.split(/[/\\]/).includes(".."));
|
|
10
10
|
}
|
|
11
|
+
export function normalizeAbsolutePathLexically(p) {
|
|
12
|
+
const collapsed = path.posix.normalize(p.replace(/^\/+/, "/"));
|
|
13
|
+
return collapsed.length > 1 ? collapsed.replace(/\/+$/, "") : collapsed;
|
|
14
|
+
}
|
|
15
|
+
export function dirCoversPath(dir, target) {
|
|
16
|
+
const root = normalizeAbsolutePathLexically(dir);
|
|
17
|
+
const p = normalizeAbsolutePathLexically(target);
|
|
18
|
+
return p === root || p.startsWith(root.endsWith("/") ? root : `${root}/`);
|
|
19
|
+
}
|
|
11
20
|
export function cwdHonored(config) {
|
|
12
21
|
return config.remoteExec?.provider === "host" && config.requirePrincipal !== true;
|
|
13
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.79.1",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@sema-agent/core": "7.
|
|
57
|
+
"@sema-agent/core": "7.20.0",
|
|
58
58
|
"@sema-agent/settings-schema": "2.1.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|