@sema-agent/server 7.70.0 → 7.71.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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +6 -3
- package/dist/approval-content-kind.d.ts +13 -7
- package/dist/approval-content-kind.js +3 -1
- package/dist/approval.js +3 -2
- package/dist/bench/s1/live-deps.js +2 -0
- package/dist/boot/execution-env.js +12 -2
- package/dist/boot/memory-consolidation.d.ts +2 -1
- package/dist/boot/parked-revive-gate.d.ts +2 -0
- package/dist/boot/parked-revive-gate.js +5 -1
- package/dist/boot/resolve-spec.js +8 -3
- package/dist/boot/runner-deps.d.ts +16 -43
- package/dist/boot/runner-deps.js +10 -4
- package/dist/brain.d.ts +3 -1
- package/dist/brain.js +2 -1
- package/dist/config-catalog.js +3 -1
- package/dist/config-types.d.ts +31 -1
- package/dist/config-types.js +1 -0
- package/dist/config.js +15 -3
- package/dist/deployment-governance.d.ts +6 -3
- package/dist/deployment-governance.js +2 -1
- package/dist/execution-lane-caps.d.ts +51 -37
- package/dist/execution-lane-caps.js +23 -0
- package/dist/fleet/fleet-bus.d.ts +22 -1
- package/dist/fleet/fleet-bus.js +2 -1
- package/dist/http/server.js +17 -5
- package/dist/index.d.ts +1 -1
- package/dist/leader/wire.js +4 -3
- package/dist/main.js +4 -2
- package/dist/memory-operator-faces.d.ts +1 -1
- package/dist/memory-scope.d.ts +24 -0
- package/dist/memory-scope.js +12 -3
- package/dist/observability/metrics.d.ts +4 -0
- package/dist/observability/metrics.js +2 -0
- package/dist/observability/run-terminal-log.d.ts +4 -3
- package/dist/observability/run-terminal-log.js +4 -15
- package/dist/orchestration/workflow-agent-session-index.d.ts +85 -2
- package/dist/orchestration/workflow-agent-session-index.js +38 -5
- package/dist/parked-decide.d.ts +40 -1
- package/dist/parked-decide.js +19 -2
- package/dist/plugins/checkpoint-store-sql.js +5 -5
- package/dist/plugins/local-checkpoint-store.js +3 -3
- package/dist/plugins/remote-env-adb.d.ts +11 -0
- package/dist/plugins/remote-env-adb.js +2 -0
- package/dist/plugins/remote-env-device.d.ts +9 -0
- package/dist/plugins/remote-env-device.js +3 -0
- package/dist/plugins/remote-env-e2b.d.ts +11 -0
- package/dist/plugins/remote-env-e2b.js +2 -0
- package/dist/plugins/remote-env-host.d.ts +11 -0
- package/dist/plugins/remote-env-host.js +2 -0
- package/dist/plugins/remote-env-k8s.d.ts +11 -0
- package/dist/plugins/remote-env-k8s.js +2 -0
- package/dist/plugins/remote-env-local-docker.d.ts +11 -0
- package/dist/plugins/remote-env-local-docker.js +2 -0
- package/dist/plugins/remote-env-ssh.d.ts +11 -0
- package/dist/plugins/remote-env-ssh.js +2 -0
- package/dist/plugins/store-backend.d.ts +34 -12
- package/dist/plugins/store-backend.js +12 -3
- package/dist/plugins/store-contracts.d.ts +54 -18
- package/dist/plugins/store-contracts.js +17 -30
- package/dist/plugins/workflow-run-store-sql.d.ts +31 -9
- package/dist/plugins/workflow-run-store-sql.js +28 -1
- package/dist/run-local.js +3 -0
- package/dist/task-a2a.js +2 -2
- package/dist/task-mcp.js +2 -2
- package/dist/task-settings.d.ts +21 -8
- package/dist/task-settings.js +2 -2
- package/dist/trace/core-keyset-guard.d.ts +128 -8
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/dist/trace/ledger-events.d.ts +5 -1
- package/dist/trace/ledger-sink.js +8 -1
- package/dist/trace/project.d.ts +21 -4
- package/dist/trace/project.js +5 -3
- package/dist/trace/redact.js +10 -5
- package/package.json +3 -3
package/dist/config-types.d.ts
CHANGED
|
@@ -55,6 +55,24 @@ export interface RetentionSweepConfig {
|
|
|
55
55
|
* 那道拒之后它即刻生效 —— 留着是为了「那天不必有人再猜一个数」。
|
|
56
56
|
*/
|
|
57
57
|
export declare const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
|
|
58
|
+
/**
|
|
59
|
+
* S-202 —— **首 token 看门狗的缺省上限(ms)**,全仓单一属主。
|
|
60
|
+
*
|
|
61
|
+
* 「SSE 已开但迟迟不吐第一个 delta」是唯一一条 core 的 idle 计时器够不着的挂死路(它只在首 token
|
|
62
|
+
* 之后才武装),所以这条看门狗是那条路上唯一的守卫。600s 而不是原来的 120s:自托管后端在长 prefill
|
|
63
|
+
* (大上下文 / 冷缓存 / 排队)下,首字合理地就是要等几分钟——120s 会把一次**正常**的慢启动判成
|
|
64
|
+
* `[network]` 失败去重试,而重试再交同一份 prompt 给同一个排队中的后端,只会更慢更贵。
|
|
65
|
+
*
|
|
66
|
+
* ⚠️ **`0 = 关`语义不变**,提高缺省不碰它;依赖 120s 快失败的部署把
|
|
67
|
+
* `MODEL_FIRST_TOKEN_TIMEOUT_MS=120000` 显式写回即可(env 恒赢)。
|
|
68
|
+
*
|
|
69
|
+
* 🔴 **常量在此、别处只许引用**:解析器腿(`config.ts` 的 `numEnv` 缺省)、目录腿
|
|
70
|
+
* (`config-catalog.ts` 的 `staticDefault`)与 `brain.ts` 的 `DEFAULT_RESILIENCE`(config 对象没带
|
|
71
|
+
* resilience 块时的同姿态兜底)此前各写一份 `120000` 字面量,改一处漏一处 = 对外宣告的缺省与真跑的
|
|
72
|
+
* 不是同一代。`test/config-catalog.test.ts` 的 S-202 两格咬:一格钉三腿同值,一格扫全 `src/` 判
|
|
73
|
+
* 「字面量」这一类写法为红(下一份镜像出生即红)。
|
|
74
|
+
*/
|
|
75
|
+
export declare const MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT = 600000;
|
|
58
76
|
/**
|
|
59
77
|
* [ref] D1/D2 —— consolidation driver 阀门的三根旋钮(语义与三问见
|
|
60
78
|
* {@link ServiceConfigFlat.memoryConsolidationDriver})。纯数据。
|
|
@@ -608,6 +626,18 @@ export interface ServiceConfigFlat {
|
|
|
608
626
|
* snapshot). Self-hosted sandbox lane; auth = explicit K8S_* env or in-cluster service account.
|
|
609
627
|
* - `ssh` → a real host over SSH (NOT isolated/suspendable; [ref] §5). AI-orchestrated batch deploy.
|
|
610
628
|
* - `adb` → a real device over ADB (NOT isolated/suspendable). On-device APP testing. */
|
|
629
|
+
/**
|
|
630
|
+
* S-213④ —— `REMOTE_EXEC_HOME_DIR`:**执行环境的 home 目录**(core `ExecutionEnv.homeDir`),
|
|
631
|
+
* `~/…` 形权限规则(`Edit(~/.ssh/**)`)唯一的基,绝对路径。
|
|
632
|
+
*
|
|
633
|
+
* 部署级旋钮,**跨车道同一根**(不按 provider 分七根 —— 它回答的是同一个问题:「这条部署的手,
|
|
634
|
+
* 跑在哪个用户的 home 下」)。显式声明**恒赢**;缺席时只有 home 属于车道本身的那几条车道有答案
|
|
635
|
+
* (host/in-process = `os.homedir()`、e2b = 平台固定用户),k8s/local-docker(镜像属性)、
|
|
636
|
+
* ssh(目标机登录用户)、adb/device 一律缺席 ⇒ `~/` 规则在下单时被 422 响亮拒。
|
|
637
|
+
* 派生与逐车道理由的**单一属主** = `execution-lane-caps.ts` 的 `executionLaneHomeDir`。
|
|
638
|
+
* 非绝对路径 ⇒ **拒启**([ref] A 档;core 侧同判为 `config.execution_env_home_dir_invalid`)。
|
|
639
|
+
*/
|
|
640
|
+
remoteExecHomeDir?: string;
|
|
611
641
|
remoteExec?: {
|
|
612
642
|
provider: "e2b";
|
|
613
643
|
apiKey: string;
|
|
@@ -1721,7 +1751,7 @@ export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled"
|
|
|
1721
1751
|
* config 这一层它只是一句声明。 */
|
|
1722
1752
|
export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authTokens" | "allowUnauthedWrites" | "corsOrigins" | "principalHeader" | "requirePrincipal" | "autonomy" | "commandPolicy" | "compliancePosture" | "lockedConfigKeys" | "configCatalogEnabled" | "retentionPolicy" | "retentionSweep" | "operatorPrincipals" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "principalJwtMaxTtlSec" | "bindHost" | "bindHostSource">;
|
|
1723
1753
|
/** 组:orchestration(编排 + 执行车道 + 沙箱面 + workflow/后台 agent 存留)。 */
|
|
1724
|
-
export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "sessionAutoTitleSource" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "imageBakes" | "readFace" | "readFaceSource" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
|
|
1754
|
+
export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "remoteExecHomeDir" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "sessionAutoTitleSource" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "imageBakes" | "readFace" | "readFaceSource" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
|
|
1725
1755
|
/** 组:limitsHttp(HTTP 面 + 各类上限/配额/回收窗)。 */
|
|
1726
1756
|
export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "sighupIdleGraceMs" | "parentPid" | "engineAutoExit" | "engineLingerMs" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
|
|
1727
1757
|
/** 组:observability(可观测)。 */
|
package/dist/config-types.js
CHANGED
package/dist/config.js
CHANGED
|
@@ -9,7 +9,7 @@ import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
|
9
9
|
import { isV2ScopeKey } from "./memory-scope.js";
|
|
10
10
|
import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
|
|
11
11
|
import { GIT_API_KINDS, isGitApiKind } from "./git-api-kind.js";
|
|
12
|
-
import { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
|
|
12
|
+
import { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC, MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT } from "./config-types.js";
|
|
13
13
|
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
14
14
|
import { normalizeSshFingerprint } from "./ssh-host-key.js";
|
|
15
15
|
import { readFaceForPosture } from "./read-face-posture.js";
|
|
@@ -856,7 +856,7 @@ function parseModelDomain() {
|
|
|
856
856
|
: undefined,
|
|
857
857
|
resilience: {
|
|
858
858
|
connectTimeoutMs: numEnv("MODEL_CONNECT_TIMEOUT_MS", "30000"),
|
|
859
|
-
firstTokenTimeoutMs: numEnv("MODEL_FIRST_TOKEN_TIMEOUT_MS",
|
|
859
|
+
firstTokenTimeoutMs: numEnv("MODEL_FIRST_TOKEN_TIMEOUT_MS", String(MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT)),
|
|
860
860
|
idleTimeoutMs: numEnv("MODEL_IDLE_TIMEOUT_MS", "300000"),
|
|
861
861
|
circuitBreaker: boolEnv("MODEL_CIRCUIT_BREAKER", false),
|
|
862
862
|
failureThreshold: numEnv("MODEL_CB_FAILURE_THRESHOLD", "5"),
|
|
@@ -1551,6 +1551,18 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1551
1551
|
assertReadDenyBuiltinsResolvable(tiers, exclude);
|
|
1552
1552
|
return { ...(tiers !== undefined ? { readDenyBuiltinTiers: tiers } : {}), ...(exclude !== undefined ? { readDenyBuiltinExclude: exclude } : {}) };
|
|
1553
1553
|
})(),
|
|
1554
|
+
...((h) => (h !== undefined ? { remoteExecHomeDir: h } : {}))((() => {
|
|
1555
|
+
const raw = process.env.REMOTE_EXEC_HOME_DIR;
|
|
1556
|
+
if (raw === undefined || raw === "")
|
|
1557
|
+
return undefined;
|
|
1558
|
+
if (!raw.startsWith("/")) {
|
|
1559
|
+
throw new Error(`REMOTE_EXEC_HOME_DIR="${raw}" is not an absolute path — it is the home directory of the user the EXECUTION ` +
|
|
1560
|
+
`environment runs as (the base a \`~/\` permission rule resolves against), so a relative value cannot be ` +
|
|
1561
|
+
`resolved and would silently guard nothing. Spell it absolutely (e.g. /home/user), or unset it to have ` +
|
|
1562
|
+
`\`~/\` rules refused loudly on this lane.`);
|
|
1563
|
+
}
|
|
1564
|
+
return raw;
|
|
1565
|
+
})()),
|
|
1554
1566
|
remoteExec: process.env.REMOTE_EXEC === "e2b" && process.env.E2B_API_KEY
|
|
1555
1567
|
? {
|
|
1556
1568
|
provider: "e2b",
|
|
@@ -1941,7 +1953,7 @@ const AUTH_GROUP_KEYS = [
|
|
|
1941
1953
|
"principalJwtMaxTtlSec", "bindHost", "bindHostSource",
|
|
1942
1954
|
];
|
|
1943
1955
|
const ORCHESTRATION_GROUP_KEYS = [
|
|
1944
|
-
"remoteExec", "worktreeIsolation", "leaderEnabled", "leaderFanoutEnabled", "routerEnabled", "selfOrchestrationEnabled",
|
|
1956
|
+
"remoteExec", "remoteExecHomeDir", "worktreeIsolation", "leaderEnabled", "leaderFanoutEnabled", "routerEnabled", "selfOrchestrationEnabled",
|
|
1945
1957
|
"selfOrchestrationModels", "selfOrchestrationWorkerIsolation", "forkEnabled", "experimentalObserverAgents",
|
|
1946
1958
|
"schedulerEnabled", "schedulerSessionWakeup", "schedulerSessionLifetime", "schedulerStorePath", "planModeEnabled", "hooksTimeoutMs", "workflowRunStoreBackend",
|
|
1947
1959
|
"workflowOrphanGraceMs", "workflowJournalRetentionMs", "workflowRunRetentionMs", "usageWindows", "workflowAgentsReadOnly",
|
|
@@ -59,8 +59,10 @@ export interface PathAdjudication {
|
|
|
59
59
|
* 形上是一个永不创建的哨兵目录 —— 对写门 fail-safe,对规则 fail-open。理由逐字见
|
|
60
60
|
* `task-settings.ts` 的 `FsWriteGateWiring.taskRoot`。缺席 ⇒ 需要基的规则拼法被 core 整表拒(fail-loud)。 */
|
|
61
61
|
readonly taskRoot?: string;
|
|
62
|
-
/** 🔴 S-177 / codex r2
|
|
63
|
-
*
|
|
62
|
+
/** 🔴 S-177 / codex r2 + S-213④:**执行环境的 home**(`~/…` 形规则的基)。在**本部署的执行车道
|
|
63
|
+
* 真知道自己的 home 时**在场 —— 判据的唯一属主是 `execution-lane-caps.ts` 的 `executionLaneHomeDir`
|
|
64
|
+
* (adapter 的 `ExecutionEnv.homeDir` 座读的是同一个函数)。绝不回落引擎进程自己的 home:远端车道上
|
|
65
|
+
* 那是另一个用户的目录,拿它去匹配 = 一条收紧规则静默守错地方。逐字见
|
|
64
66
|
* `task-settings.ts` 的 `FsWriteGateWiring.taskHome`。 */
|
|
65
67
|
readonly taskHome?: string;
|
|
66
68
|
}
|
|
@@ -85,7 +87,8 @@ export interface DurableApprovalSeat {
|
|
|
85
87
|
* durable resume 驱动、断连后的 detach 腿)ask——durable park 原语义逐字保留([ref] 后无活流腿放行执行
|
|
86
88
|
* 也不会产出空答:coordinator 无 ALS ctx ⇒ 冻结 `{kind:"unavailable"}`(src/question.ts),永不悬挂;
|
|
87
89
|
* park 仍是把问题送到人面前的唯一那条腿,正当性不变、只是反事实前提换了)。
|
|
88
|
-
*
|
|
90
|
+
* 工具名取 `approval-content-kind.ts` 的单一属主(它 re-export core 根导出的 `ASK_USER_QUESTION_TOOL_NAME`);
|
|
91
|
+
* server.ts 的 pre-CAS 守卫读同一枚(S-213①:此处旧注称该常量「core 未根导出」= 失实,曾据此手抄 13 处)。
|
|
89
92
|
* ⚠️ **单一属主**(复审 A2):AskUserQuestion 的 durable 判决只有这一处。任何需要「同参重建」这条判决的
|
|
90
93
|
* 地方(main.ts 的 parkedReviveInheritedGate 父约束链)必须调本工厂,不得自折 core 原形——两份拷贝里
|
|
91
94
|
* 只改一份正是本条 finding 的成因。**登记豁免一处**:leader worker 腿(src/leader/wire.ts provisionWorker)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { join, posix } from "node:path";
|
|
2
2
|
import { FileError, StubExecutionEnv, combinePolicies, createAllowDenyPolicy, createDurableQuestionPolicy, createSensitivePathPolicy, createTranscriptIntegrityPolicy, err, ok, } from "@sema-agent/core";
|
|
3
3
|
import { createDurableAskPolicy } from "./approval.js";
|
|
4
|
+
import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
|
|
4
5
|
export function createDurableQuestionGate(live) {
|
|
5
6
|
if (live === undefined)
|
|
6
7
|
return createDurableQuestionPolicy();
|
|
7
8
|
return {
|
|
8
9
|
check(req) {
|
|
9
|
-
if (req.toolName !==
|
|
10
|
+
if (req.toolName !== ASK_USER_QUESTION_TOOL_NAME)
|
|
10
11
|
return { action: "allow" };
|
|
11
12
|
return live.hasLiveContext()
|
|
12
13
|
? { action: "allow" }
|
|
@@ -1,40 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 执行车道**闭集**与 per-lane 能力表(design device-executor-lane-v2 §4.1 收敛件,车A-1)。
|
|
3
|
-
*
|
|
4
|
-
* ## 根本问题:lane 词表是开放字符串上的十几处独立判别式
|
|
5
|
-
*
|
|
6
|
-
* `config.remoteExec?.provider` 的静态型是 `string | undefined` 的判别联合,但消费面从来不是一处
|
|
7
|
-
* switch —— 而是散在十几个文件里的**独立等式/集合/否定**:`cwdHonored` 比 `=== "host"`、
|
|
8
|
-
* `isRemoteScratchpadLane` 查一个 `Set`、`isSandboxPathAdjudicationLane` 取 `!== "host"` 的否定、
|
|
9
|
-
* `sandboxSendLaneEnabled` 是一串 if。于是「lane 词表加一个词」的病形不是编译错,是**每一处的默认
|
|
10
|
-
* 答案静默错**:新词不在 Set 里 ⇒ 静默 false;不等于 `"host"` ⇒ 静默 true。没有一处会报,每一处都
|
|
11
|
-
* 给出一个**没有人表过态**的答案(DISEASE-SHAPES V1/V2;[ref] 执行车道轴的同族病)。
|
|
12
|
-
*
|
|
13
|
-
* ## 本模块 = 那张表的单源(三件)
|
|
14
|
-
*
|
|
15
|
-
* ① {@link ExecutionLane}:闭集联合。含 `in-process` —— `REMOTE_EXEC` 未设的进程内 stub 形**是**一条
|
|
16
|
-
* 真车道(`boot/resolve-spec.ts` 的 `?? "in-process"`、`config.ts` 的落点记账都已在用这个词),
|
|
17
|
-
* 把它排除在闭集外,普查就少一列。
|
|
18
|
-
* ② {@link executionLaneCaps}:**穷举 switch**(先例 = `capabilities/hands-lane.ts` 的
|
|
19
|
-
* `pickHandsRunner`:无 `default` 臂 ⇒ 联合加词而此处不加臂 = 编译错,而不是运行期落一个默认行)。
|
|
20
|
-
* ③ {@link ExecutionLaneCaps} 的每一位都点名它**今天的判别式坐标**,由对账测试
|
|
21
|
-
* `test/execution-lane-caps.test.ts` 逐位跑真判别式与本表对拍。
|
|
22
|
-
*
|
|
23
|
-
* ## 🔴 本模块是表,不是执法者
|
|
24
|
-
*
|
|
25
|
-
* 消费点今天仍各自判别(把判别式改成读本表 = 分批的后续车)。所以本表的全部价值押在对账测试上:
|
|
26
|
-
* 表与判别式一旦分叉,门红。**唯一**允许的分叉是 {@link PLANNED_LANE_DIVERGENCES} 里逐条登记的
|
|
27
|
-
* 「表是目标态、判别式待某车补」;登记项之外的分叉是漂移,登记项被补齐而不销行同样是红(双向)。
|
|
28
|
-
*
|
|
29
|
-
* ## 能力位的口径
|
|
30
|
-
*
|
|
31
|
-
* 每一位回答的是**车道结构上能不能**,不是「这个部署开没开」:旋钮(`LSP_ENABLED` /
|
|
32
|
-
* `SCHEDULER_ENABLED` / k8s 的 `s3Snapshot`)由消费点在表之上再合取。租户面是唯一的例外 —— 它与
|
|
33
|
-
* 车道语义绑死(host 的单用户前提),所以进了 {@link LaneCapability} 的第三个词而不是靠调用方记住。
|
|
34
|
-
*
|
|
35
|
-
* 依赖纪律:本文件是**叶**(只 type-import config-types),不引 config.ts —— config.ts 反过来引它取
|
|
36
|
-
* {@link RemoteExecProvider}([ref] 必需 env 表按同一联合键控)。
|
|
37
|
-
*/
|
|
38
1
|
import type { ServiceConfigFlat } from "./config-types.js";
|
|
39
2
|
/** 一位能力的三态。`single-user-only` = 车道支持,但只在单用户部署(`requirePrincipal !== true`)开 ——
|
|
40
3
|
* 多租户下这一位必须关(host 家族的 cwd/scheduler/backgroundShell/LSP 都是这一档,理由同源:
|
|
@@ -149,4 +112,55 @@ export interface PlannedLaneDivergence {
|
|
|
149
112
|
* 加行的门槛不变:一条分叉必须写清 today / owner / why 三件,并在补齐的**同一批**里销掉。
|
|
150
113
|
*/
|
|
151
114
|
export declare const PLANNED_LANE_DIVERGENCES: readonly PlannedLaneDivergence[];
|
|
115
|
+
/**
|
|
116
|
+
* E2B 沙箱里 agent 跑成的那个用户的 home。
|
|
117
|
+
*
|
|
118
|
+
* 这是 **E2B 平台的事实**,不是本仓的猜:E2B 的基础模板建 `user` 这个用户,每个 sandbox 的进程都以它
|
|
119
|
+
* 运行,home 恒 `/home/user`;本仓 adapter 自己的工作区根默认值(`plugins/remote-env-e2b.ts` 的
|
|
120
|
+
* `DEFAULT_MOUNT_PATH`)用的就是同一个目录。**若某个部署的自定义模板换了用户**,运维用
|
|
121
|
+
* `REMOTE_EXEC_HOME_DIR` 显式声明覆盖 —— 那条旋钮恒赢,见 {@link executionLaneHomeDir}。
|
|
122
|
+
* ⚠️ 本值**未经真机实测**(本批无 e2b 真机),按平台约定 + 仓内既有默认常量登记。
|
|
123
|
+
*/
|
|
124
|
+
export declare const E2B_SANDBOX_HOME = "/home/user";
|
|
125
|
+
/** {@link executionLaneHomeDir} 读的那两位部署配置(`ServiceConfig` 结构上满足)。 */
|
|
126
|
+
export interface LaneHomeDeployment {
|
|
127
|
+
readonly remoteExec?: {
|
|
128
|
+
readonly provider: RemoteExecProvider;
|
|
129
|
+
} | undefined;
|
|
130
|
+
/** `REMOTE_EXEC_HOME_DIR` —— 运维显式声明的执行环境 home(绝对路径;坏值在 config 层拒启)。 */
|
|
131
|
+
readonly remoteExecHomeDir?: string | undefined;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 「**这条部署的执行环境**以哪个 home 运行」—— `~/…` 形权限规则(`Edit(~/.ssh/**)`)唯一的基。
|
|
135
|
+
*
|
|
136
|
+
* ## 病(接入审计 M13 / DEBTS S-213④)
|
|
137
|
+
* core 的 `ExecutionEnv.homeDir` 是**由 adapter 声明**的座(`engine/harness/types.d.ts:511`,可选):
|
|
138
|
+
* 缺席 ⇒ core 的 `resolvePathPattern` 回 `{missingBase:"home"}` ⇒ 该规则对这条腿判 `unreadable`
|
|
139
|
+
* (fail-closed 的 ask)。四只远端 adapter(e2b/k8s/host/ssh)一个都没声明,而 server 侧的 422 前置门
|
|
140
|
+
* (`task-settings.ts` 的 `rejectUnmatchableSettingsNames`)在 home 未知时整表拒 `~/` 形 ——
|
|
141
|
+
* **于是远端部署根本写不出 `~/` 规则**,恒 422。
|
|
142
|
+
*
|
|
143
|
+
* ## 本函数 = 那个值的**单一属主**
|
|
144
|
+
* 两个消费面读同一份,不许各算各的(两处不同 = 一条规则在 422 门上被拒、却在引擎里编得出来,或反之):
|
|
145
|
+
* ① 每只 adapter 的 `homeDir` 座(`boot/execution-env.ts` 把本函数的答案递进 adapter 配置);
|
|
146
|
+
* ② 规则基 `PathRuleBases.home`(`boot/resolve-spec.ts` / `boot/parked-revive-gate.ts` 的
|
|
147
|
+
* `pathAdjudication.taskHome`)。
|
|
148
|
+
*
|
|
149
|
+
* ## 一条规则,不是七条特判
|
|
150
|
+
* **运维显式声明恒赢**(`REMOTE_EXEC_HOME_DIR`,部署级旋钮,不挂任何客户端表态);没有声明时,
|
|
151
|
+
* 只有「home 是**车道本身**的属性」的那几条车道才有答案:
|
|
152
|
+
* · `host` / `in-process` —— 命令就跑在本进程这台机器、这个用户下,`os.homedir()` 读的就是它
|
|
153
|
+
* (core 自己的 `NodeExecutionEnv` 同判);
|
|
154
|
+
* · `e2b` —— 平台固定用户,见 {@link E2B_SANDBOX_HOME};
|
|
155
|
+
* · `k8s` / `local-docker` —— home 是**镜像**的属性,boot 期无从得知;
|
|
156
|
+
* · `ssh` —— home 是**目标机上那个登录用户**的属性(root 是 `/root`、macOS 是 `/Users/<u>`),
|
|
157
|
+
* 连上之前无从得知,而 422 门要在下单那一刻回答;
|
|
158
|
+
* · `adb` —— Android 没有 POSIX 意义上的登录 home;
|
|
159
|
+
* · `device` —— 员工自己的机器,运行期才连进来。
|
|
160
|
+
* 这五条一律**缺席**(诚实:不猜一个目录去守)。缺席的后果是 `~/` 规则在下单时被 422 响亮拒,
|
|
161
|
+
* 与 core 的 `missingBase:"home"` 同向 —— 这正是负控要的那一格。
|
|
162
|
+
*
|
|
163
|
+
* 认不出的车道 ⇒ `undefined`(调用方 fail-closed)。生产链上不可达:`loadConfig` 的 [ref] 拒启门先咬。
|
|
164
|
+
*/
|
|
165
|
+
export declare function executionLaneHomeDir(deployment: LaneHomeDeployment): string | undefined;
|
|
152
166
|
//# sourceMappingURL=execution-lane-caps.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
1
2
|
const NO = "unsupported";
|
|
2
3
|
const YES = "supported";
|
|
3
4
|
const SU = "single-user-only";
|
|
@@ -163,4 +164,26 @@ export function laneCapabilityHolds(cap, deployment) {
|
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
export const PLANNED_LANE_DIVERGENCES = Object.freeze([]);
|
|
167
|
+
export const E2B_SANDBOX_HOME = "/home/user";
|
|
168
|
+
export function executionLaneHomeDir(deployment) {
|
|
169
|
+
const declared = deployment.remoteExecHomeDir;
|
|
170
|
+
if (declared !== undefined)
|
|
171
|
+
return declared;
|
|
172
|
+
const lane = executionLaneOf(deployment.remoteExec?.provider);
|
|
173
|
+
if (lane === undefined)
|
|
174
|
+
return undefined;
|
|
175
|
+
switch (lane) {
|
|
176
|
+
case "in-process":
|
|
177
|
+
case "host":
|
|
178
|
+
return homedir();
|
|
179
|
+
case "e2b":
|
|
180
|
+
return E2B_SANDBOX_HOME;
|
|
181
|
+
case "k8s":
|
|
182
|
+
case "local-docker":
|
|
183
|
+
case "ssh":
|
|
184
|
+
case "adb":
|
|
185
|
+
case "device":
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
166
189
|
//# sourceMappingURL=execution-lane-caps.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TaskNotificationPayload, TerminalCause, WorkflowRun } from "@sema-agent/core";
|
|
2
|
-
import type { RunRecord } from "../plugins/store-contracts.js";
|
|
2
|
+
import type { RunRecord, UnsuccessfulRunStatus } from "../plugins/store-contracts.js";
|
|
3
|
+
import type { AssertAllKeysHandled } from "../trace/core-keyset-guard.js";
|
|
3
4
|
/** [ref] 幽灵行案的单源判别:一条 `task_notification` 只有在 **agent 族 × 终态** 时才允许打
|
|
4
5
|
* `onChildTerminal`(fleet「subagent 树」只渲 agent 子代)。`background_bash`/`monitor`/`external`
|
|
5
6
|
* 不属 agent fleet 树——它们此前每条都打,fleet-bus 的「无 tick 无 claim」臂给 b\* 与 m\* handle 合成
|
|
@@ -618,4 +619,24 @@ export declare function isTerminalFleetStatus(status: FleetTaskStatus): boolean;
|
|
|
618
619
|
export declare function runStatusToFleet(status: RunStatusForFleet, opts?: {
|
|
619
620
|
awaitingPlanApproval?: boolean;
|
|
620
621
|
}): FleetTaskStatus;
|
|
622
|
+
/**
|
|
623
|
+
* S-213③ —— **编译期同源钉**:fleet 的 `"failed"` 桶必须恰好收下 `RUN_STATUS_CLASS` 判为「非成功终局」
|
|
624
|
+
* 的每一个 core 词。
|
|
625
|
+
*
|
|
626
|
+
* 这个词的病史:`blocked` 曾在本仓有**两个独立写者**且答案互斥 —— 本文件读它为「非成功终局(agent
|
|
627
|
+
* 放弃)⇒ 行离场」,而 `observability/run-terminal-log.ts` 读它为「治理判决,不是失败 ⇒ 不打行」。两处
|
|
628
|
+
* 各自穷举、各自正确地编译,结果是一条 `report_blocked` 的 run 在面板上离场、在引擎日志里一个字都没有。
|
|
629
|
+
* 语义收归 `plugins/store-contracts.ts` 的那张表之后,本行不再自持判断:表把某个词挪出/挪进
|
|
630
|
+
* `unsuccessful`(或 core 新增一个非成功终态)而上面的 switch 没跟上 ⇒ 下面这条收不敛到 `never` ⇒ tsc 红。
|
|
631
|
+
* (`"error"` 是 server 自有别名、不在那张表的辖域内,故不在本钉的两边出现。)
|
|
632
|
+
*/
|
|
633
|
+
type FleetUnsuccessfulBucket = "failed" | "blocked";
|
|
634
|
+
type _GuardFleetUnsuccessfulBucket = AssertAllKeysHandled<Exclude<UnsuccessfulRunStatus, FleetUnsuccessfulBucket>>;
|
|
635
|
+
/** 反向门(与 `core-keyset-guard.ts` 的 `_Guard*Reverse` 同形):桶里留了一个表**不再**判为非成功终局的
|
|
636
|
+
* 词同样红 —— 只有正向门时,把 `blocked` 挪回 `succeeded` 而 switch 照旧塞进 failed 桶,并集覆盖、
|
|
637
|
+
* 差集门一声不吭(本批实测:只加正向门时该变异 tsc rc=0)。两道钉一起加。 */
|
|
638
|
+
type _GuardFleetUnsuccessfulBucketReverse = AssertAllKeysHandled<Exclude<FleetUnsuccessfulBucket, UnsuccessfulRunStatus>>;
|
|
639
|
+
/** 占位使用(防 noUnusedLocals 形 lint;类型别名本身零运行时)。 */
|
|
640
|
+
export type FleetVocabGuards = [_GuardFleetUnsuccessfulBucket, _GuardFleetUnsuccessfulBucketReverse];
|
|
641
|
+
export {};
|
|
621
642
|
//# sourceMappingURL=fleet-bus.d.ts.map
|
package/dist/fleet/fleet-bus.js
CHANGED
|
@@ -634,9 +634,10 @@ export function runStatusToFleet(status, opts) {
|
|
|
634
634
|
case "completed":
|
|
635
635
|
return "completed";
|
|
636
636
|
case "failed":
|
|
637
|
-
case "error":
|
|
638
637
|
case "blocked":
|
|
639
638
|
return "failed";
|
|
639
|
+
case "error":
|
|
640
|
+
return "failed";
|
|
640
641
|
case "suspended":
|
|
641
642
|
return "waiting";
|
|
642
643
|
case "needs_review":
|
package/dist/http/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import { redactErrorMessage, redactHead } from "../observability/run-terminal-lo
|
|
|
3
3
|
import { once } from "node:events";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { uuidv7, isThinkingLevel, expandTiers, resumeWithVerification, CheckpointError, defaultTaskRegistry, validatePendingSteer, subscribeWorkflow, GLOBAL_USAGE_KEY, usageRetryAfterMs, terminalProjection, PAUSE_REGISTRY } from "@sema-agent/core";
|
|
6
|
-
import { decideParkedAgent, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
|
|
6
|
+
import { decideParkedAgent, denyWorkflowParkIdentityLost, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
|
|
7
7
|
import { isModelAllowlisted, matchCatalogModel } from "../model-select.js";
|
|
8
8
|
import {} from "../config-center/facade.js";
|
|
9
9
|
import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isUuidV7, isDestructiveSessionWrite, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
|
|
@@ -33,7 +33,7 @@ import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
|
|
|
33
33
|
import { createWallClockJumpGuard } from "../wall-clock-jump-guard.js";
|
|
34
34
|
import {} from "../observability/rate-limit.js";
|
|
35
35
|
import { withPrincipal } from "../observability/principal-context.js";
|
|
36
|
-
import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
36
|
+
import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
37
37
|
import { redactSecrets } from "../trace/redact.js";
|
|
38
38
|
import { createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
|
|
39
39
|
import { registerEngineNoticeLeg } from "../trace/engine-notice-wire.js";
|
|
@@ -85,6 +85,7 @@ export { coarseStatusForState, errorCodeForExit };
|
|
|
85
85
|
import { sendJson, sendError, httpErrorCode, msg, isSseResponse } from "./send.js";
|
|
86
86
|
import { authorized, systemFor, gatedPrincipal, explicitOperatorOk, isOperator } from "./principal-gate.js";
|
|
87
87
|
import { buildActiveRunConflict, resumeEntryForGate } from "./active-run-conflict.js";
|
|
88
|
+
import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
|
|
88
89
|
export { explicitOperatorOk, isOperator };
|
|
89
90
|
export function flattenServiceDeps(deps) {
|
|
90
91
|
const { stores, coordinators, seams, observability, governance, deployment, knobs, ...flat } = deps;
|
|
@@ -1143,7 +1144,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1143
1144
|
};
|
|
1144
1145
|
}
|
|
1145
1146
|
const paForGrant = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction : undefined;
|
|
1146
|
-
const loadedGrantTool = paForGrant !== undefined && paForGrant.toolName && paForGrant.toolName !==
|
|
1147
|
+
const loadedGrantTool = paForGrant !== undefined && paForGrant.toolName && paForGrant.toolName !== ASK_USER_QUESTION_TOOL_NAME
|
|
1147
1148
|
? paForGrant.toolName
|
|
1148
1149
|
: null;
|
|
1149
1150
|
const boundGrant = onResumeCommitted
|
|
@@ -1208,6 +1209,10 @@ export function createHttpServer(rawDeps) {
|
|
|
1208
1209
|
noteLateSettleIfAccepted(parked);
|
|
1209
1210
|
return parked;
|
|
1210
1211
|
}
|
|
1212
|
+
if (origin?.kind === "workflow_identity_lost") {
|
|
1213
|
+
void ctxPromise.catch(() => { });
|
|
1214
|
+
return denyWorkflowParkIdentityLost(origin);
|
|
1215
|
+
}
|
|
1211
1216
|
if (origin?.kind === "workflow") {
|
|
1212
1217
|
void ctxPromise.catch(() => { });
|
|
1213
1218
|
const plan = planWorkflowParkedDecide(origin, parkedReq);
|
|
@@ -1256,10 +1261,10 @@ export function createHttpServer(rawDeps) {
|
|
|
1256
1261
|
throw e;
|
|
1257
1262
|
}
|
|
1258
1263
|
const pendingTool = cp.pendingAction.toolName;
|
|
1259
|
-
if (decision === "approve" && pendingTool ===
|
|
1264
|
+
if (decision === "approve" && pendingTool === ASK_USER_QUESTION_TOOL_NAME && !answer) {
|
|
1260
1265
|
return { status: 400, body: { error: "pending action is AskUserQuestion — approve must carry body.answer ({ answers: [{ header, selected: string[], note? }] })", errorCode: "request.field_conflict" } };
|
|
1261
1266
|
}
|
|
1262
|
-
if (answer && (decision !== "approve" || pendingTool !==
|
|
1267
|
+
if (answer && (decision !== "approve" || pendingTool !== ASK_USER_QUESTION_TOOL_NAME)) {
|
|
1263
1268
|
return { status: 400, body: { error: `body.answer is only valid when approving a pending AskUserQuestion (decision: "${decision}", pending: "${pendingTool ?? "unknown"}")`, errorCode: "request.field_conflict" } };
|
|
1264
1269
|
}
|
|
1265
1270
|
const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
|
|
@@ -1691,6 +1696,13 @@ export function createHttpServer(rawDeps) {
|
|
|
1691
1696
|
await flush();
|
|
1692
1697
|
await append("wiring_manifest", wiringManifestEventData(ev));
|
|
1693
1698
|
break;
|
|
1699
|
+
case "tool_roster_delta": {
|
|
1700
|
+
await flush();
|
|
1701
|
+
const rosterDelta = toolRosterDeltaEventData(ev);
|
|
1702
|
+
if (rosterDelta !== undefined)
|
|
1703
|
+
await append("tool_roster_delta", rosterDelta);
|
|
1704
|
+
break;
|
|
1705
|
+
}
|
|
1694
1706
|
case "human_input":
|
|
1695
1707
|
await flush();
|
|
1696
1708
|
await append("human_input", humanInputEventData(ev));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { loadConfig, type ServiceConfig } from "./config.js";
|
|
2
2
|
export { createBrain } from "./brain.js";
|
|
3
3
|
export { createSessionStore } from "./plugins/session-store.js";
|
|
4
|
-
export { createStoreBackend, type StoreBackend } from "./plugins/store-backend.js";
|
|
4
|
+
export { createStoreBackend, type StoreBackend, type SessionCaptureRecordSource } from "./plugins/store-backend.js";
|
|
5
5
|
export { CachingSessionStore, type CachingOptions } from "./plugins/caching-session-store.js";
|
|
6
6
|
export type { RunRecord, RunEvent } from "./plugins/store-contracts.js";
|
|
7
7
|
export { runInBackground } from "./runs.js";
|
package/dist/leader/wire.js
CHANGED
|
@@ -6,6 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { Runner, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, delimitUntrusted, hasDestroy, } from "@sema-agent/core";
|
|
8
8
|
import { RemoteContainerExecutionEnv } from "../plugins/remote-env-e2b.js";
|
|
9
|
+
import { E2B_SANDBOX_HOME } from "../execution-lane-caps.js";
|
|
9
10
|
import { createDurableAskPolicy } from "../approval.js";
|
|
10
11
|
import { presignS3Url } from "../plugins/s3-presign.js";
|
|
11
12
|
import { buildUploadScript, uploadStepSuffix, fetchUploadedDiff, withStaging } from "./diffup.js";
|
|
@@ -376,7 +377,7 @@ export function createLeaderRunner(cfg) {
|
|
|
376
377
|
}
|
|
377
378
|
if (!cfg.e2bApiKey)
|
|
378
379
|
throw new Error("leader wire: no envFactory+s3 (factory mode) and no e2bApiKey (static mode)");
|
|
379
|
-
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
|
|
380
|
+
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
|
|
380
381
|
if (!(await env.connect()).ok)
|
|
381
382
|
throw new Error(`connect failed for ${sub.workerId}`);
|
|
382
383
|
await sh(env)(`${body.seedCmd}; cd ${repo}; git checkout -q -b ${sub.branch}`);
|
|
@@ -410,7 +411,7 @@ export function createLeaderRunner(cfg) {
|
|
|
410
411
|
}
|
|
411
412
|
if (!cfg.e2bApiKey)
|
|
412
413
|
throw new Error("leader wire: no envFactory and no e2bApiKey");
|
|
413
|
-
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
|
|
414
|
+
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
|
|
414
415
|
if (!(await env.connect()).ok) {
|
|
415
416
|
await env.destroy().catch(() => { });
|
|
416
417
|
throw new Error("integ connect failed");
|
|
@@ -449,7 +450,7 @@ export function createLeaderRunner(cfg) {
|
|
|
449
450
|
else {
|
|
450
451
|
if (!cfg.e2bApiKey)
|
|
451
452
|
throw new Error("leader wire: repair-loop grader needs an envFactory or e2bApiKey");
|
|
452
|
-
const e2b = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
|
|
453
|
+
const e2b = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
|
|
453
454
|
if (!(await e2b.connect()).ok) {
|
|
454
455
|
await e2b.destroy().catch(() => { });
|
|
455
456
|
throw new Error("repair-loop grader connect failed");
|
package/dist/main.js
CHANGED
|
@@ -131,7 +131,8 @@ async function main() {
|
|
|
131
131
|
await configCenter.applyLocalRemoteExec();
|
|
132
132
|
const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts, } = await openStores({ config, logger, metrics, localRoot });
|
|
133
133
|
const instanceId = uuidv7();
|
|
134
|
-
const
|
|
134
|
+
const sessionCaptureRecords = backend?.sessionCaptureRecords();
|
|
135
|
+
const sessionCaptureRecordStore = sessionCaptureRecords?.();
|
|
135
136
|
const memoryBundleFaces = memoryEngine ? createMemoryBundleFaces(memoryEngine, { ...(sessionCaptureRecordStore !== undefined ? { captureRecordStore: sessionCaptureRecordStore } : {}) }) : undefined;
|
|
136
137
|
const memoryComplianceFaces = memoryEngine
|
|
137
138
|
? createMemoryComplianceFaces(memoryEngine, {
|
|
@@ -271,7 +272,7 @@ async function main() {
|
|
|
271
272
|
sessionShellGateExplicitlyOff: sessionShellGateRegistry.explicitlyOff,
|
|
272
273
|
config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, usageWindowStore, brain,
|
|
273
274
|
pricing, tracer, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine,
|
|
274
|
-
|
|
275
|
+
sessionCaptureRecords,
|
|
275
276
|
memorySyncRunner, toolResultStore: runnerOffloadStore, checkpointStore, sessionPolicyStore, runtimeCapsResolver, fileHistoryStore,
|
|
276
277
|
legacyRewindBoundaryProbe: backend?.legacyRewindBoundaryProbe ? backend.legacyRewindBoundaryProbe.bind(backend) : undefined,
|
|
277
278
|
permissionRuleStore: permissionRuleProvider,
|
|
@@ -330,6 +331,7 @@ async function main() {
|
|
|
330
331
|
const subRunnerSessions = new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 }));
|
|
331
332
|
const subRunnerDeps = {
|
|
332
333
|
...createSharedRunnerDeps({
|
|
334
|
+
metrics,
|
|
333
335
|
config,
|
|
334
336
|
logger,
|
|
335
337
|
hands: commitHands,
|
|
@@ -227,7 +227,7 @@ export interface SessionMemoryStatusFace {
|
|
|
227
227
|
*
|
|
228
228
|
* `captureRecordStore` = [ref]② 的 SQL 载体(main.ts 单实例,与 Runner/其余引擎同源 —— 全站同批注入律,
|
|
229
229
|
* 机器钉 capture-optout-lane ②):`captureOptedOut` / `optOutSource` 两键经它读,多副本部署上答的才是
|
|
230
|
-
*
|
|
230
|
+
* 跨副本的真记录。S-215 起 local 车道在引擎接线时也供(core 文件三腿,同一只控制面,单机字节不变);缺席 ⇒ 记忆面暗。
|
|
231
231
|
*
|
|
232
232
|
* 面**不抛**(core 契约:sessionMemoryStatus never throws —— 每个事实源各自 catch 折缺席键);
|
|
233
233
|
* 不接 onIncident:这条只读路径上今天零 sink 站点,而且缺席键本身就是它的披露形。
|
package/dist/memory-scope.d.ts
CHANGED
|
@@ -104,6 +104,30 @@ export declare function memoryEngineRootFor(config: Pick<ServiceConfig, "memoryE
|
|
|
104
104
|
* 所以它的根必须走这条链而不是 `memoryEngineRootFor` 的分派(codex 修复验证轮 [medium]:分派会把 run-local 的
|
|
105
105
|
* 记忆搬到 `<localDataRoot>/memory-work`,存量记忆与控制面升级后隐形)。 */
|
|
106
106
|
export declare function fileMemoryEngineRoot(config: Pick<ServiceConfig, "memoryEngineDir">, fallbackRoot?: string): string;
|
|
107
|
+
/** 本部署 file 记忆引擎的**平面三坐标**(根 / 记忆目录 / 控制面目录)—— {@link fileMemoryPlaneFor} 一次算齐。 */
|
|
108
|
+
export interface FileMemoryPlane {
|
|
109
|
+
readonly root: string;
|
|
110
|
+
readonly memoryDir: string;
|
|
111
|
+
readonly controlDir: string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* S-215(B-085)—— **file 记忆引擎平面的单铸点**,兼这条腿的**接线门**。
|
|
115
|
+
*
|
|
116
|
+
* 为什么是一只函数而不是两处各自推导:平面坐标有**两个**消费者 —— ① {@link memoryEngineBackendFor}
|
|
117
|
+
* 钉给 `FileMemoryEngineBackend` 的 `memoryDir` + `controlDir`;② local 车道的 capture opt-out **记录店**
|
|
118
|
+
* (`StoreBackend.sessionCaptureRecords()` 喂 core 的 `fileSessionCaptureRecordStore(controlDir)`)。
|
|
119
|
+
* 两者必须是**同一只目录**,否则声明写进 A、引擎读 B ⇒ 一条被「受理」的隐私声明在引擎面根本不存在,
|
|
120
|
+
* 那比一句诚实的 409 更坏。两处各写一份推导链 = 哪天根链改了只改一处的必然漂移,所以推导(根 → 记忆目录 →
|
|
121
|
+
* 控制面目录,三步都在这里、只写一遍)与门合并在此(合并复审 R37:此前 `join(root, "memory")` 在本函数与
|
|
122
|
+
* `memoryEngineBackendFor` 各写一份 —— 记忆目录规则有了第二个写者,现已收回一处)。
|
|
123
|
+
*
|
|
124
|
+
* 返回 `undefined` ⇔ 本部署上 file 记忆引擎**不接线**(多租户 / `MEMORY_ENGINE=off` / 平面分裂车道未 allow)。
|
|
125
|
+
* 门与 `memoryEngineBackendFor` 逐字同源(它就读本函数),所以「有引擎 ⇔ 有控制面 ⇔ 供记录店」三者
|
|
126
|
+
* 在构造上不可能分家。
|
|
127
|
+
*/
|
|
128
|
+
export declare function fileMemoryPlaneFor(config: ServiceConfig, fallbackRoot?: string): FileMemoryPlane | undefined;
|
|
129
|
+
/** 控制面目录那一坐标(local 车道 capture 记录店的依托)—— 只是 {@link fileMemoryPlaneFor} 的投影,不是第二条推导。 */
|
|
130
|
+
export declare function fileMemoryControlDirFor(config: ServiceConfig, fallbackRoot?: string): string | undefined;
|
|
107
131
|
export declare function memoryEngineBackendFor(config: ServiceConfig, fallbackRoot?: string): {
|
|
108
132
|
backend: FileMemoryEngineBackend;
|
|
109
133
|
root: string;
|
package/dist/memory-scope.js
CHANGED
|
@@ -68,14 +68,23 @@ export function memoryEngineRootFor(config, fallbackRoot) {
|
|
|
68
68
|
export function fileMemoryEngineRoot(config, fallbackRoot) {
|
|
69
69
|
return resolveMemoryEngineRoot(config.memoryEngineDir ?? fallbackRoot);
|
|
70
70
|
}
|
|
71
|
-
export function
|
|
71
|
+
export function fileMemoryPlaneFor(config, fallbackRoot) {
|
|
72
72
|
if (config.requirePrincipal === true || !config.memoryEngineEnabled)
|
|
73
73
|
return undefined;
|
|
74
74
|
if (memoryEngineRemoteLanePosture(config)?.posture === "dark")
|
|
75
75
|
return undefined;
|
|
76
76
|
const root = fileMemoryEngineRoot(config, fallbackRoot);
|
|
77
|
-
const
|
|
78
|
-
return {
|
|
77
|
+
const memoryDir = join(root, "memory");
|
|
78
|
+
return { root, memoryDir, controlDir: deriveControlPlaneDir(root, memoryDir) };
|
|
79
|
+
}
|
|
80
|
+
export function fileMemoryControlDirFor(config, fallbackRoot) {
|
|
81
|
+
return fileMemoryPlaneFor(config, fallbackRoot)?.controlDir;
|
|
82
|
+
}
|
|
83
|
+
export function memoryEngineBackendFor(config, fallbackRoot) {
|
|
84
|
+
const plane = fileMemoryPlaneFor(config, fallbackRoot);
|
|
85
|
+
if (plane === undefined)
|
|
86
|
+
return undefined;
|
|
87
|
+
return { backend: new FileMemoryEngineBackend(plane.memoryDir, { controlDir: plane.controlDir }), root: plane.root };
|
|
79
88
|
}
|
|
80
89
|
export function memorySpecForRequest(scope, memoryWrite, defaultScopes, originPolicy, memoryCapture) {
|
|
81
90
|
if (!scope)
|
|
@@ -45,6 +45,10 @@ export interface MetricsSummary {
|
|
|
45
45
|
rateLimited: number;
|
|
46
46
|
costQuotaRejected: number;
|
|
47
47
|
budgetExceeded: Record<string, number>;
|
|
48
|
+
/** 答后撞顶(walltime/turns)的计数。⚠️ 与 {@link budgetExceeded} **口径不同**:本枚在 deps 级通告席上铸,
|
|
49
|
+
* 覆盖**每一条**引擎 run(后台子代 / 委派嵌套腿 / leader 车道);`budgetExceeded` 只由 HTTP 提交腿的
|
|
50
|
+
* `finalizeTaskResult` 递增(= 顶层 HTTP 提交的 run)。两枚相加只在后者那个较窄的总体里成立。 */
|
|
51
|
+
limitCeilingAfterAnswer: Record<string, number>;
|
|
48
52
|
degraded: Record<string, number>;
|
|
49
53
|
degenerate: Record<string, number>;
|
|
50
54
|
memoryConsolidationOps: Record<string, number>;
|
|
@@ -174,6 +174,7 @@ export class Metrics {
|
|
|
174
174
|
rateLimited: ctotal("rate_limited_total"),
|
|
175
175
|
costQuotaRejected: ctotal("cost_quota_rejected_total"),
|
|
176
176
|
budgetExceeded: cby("budget_exceeded_total", "code"),
|
|
177
|
+
limitCeilingAfterAnswer: cby("limit_ceiling_after_answer_total", "origin"),
|
|
177
178
|
degraded: cby("degraded_total", "reason"),
|
|
178
179
|
degenerate: cby("degenerate_total", "class"),
|
|
179
180
|
memoryConsolidationOps: cby("memory_consolidation_ops_total", "op"),
|
|
@@ -215,6 +216,7 @@ export function createMetrics() {
|
|
|
215
216
|
]);
|
|
216
217
|
m.counter("tool_calls_total", "Tool invocations by name and outcome");
|
|
217
218
|
m.counter("budget_exceeded_total", "Tasks failed by a budget/limit ceiling, by errorCode");
|
|
219
|
+
m.counter("limit_ceiling_after_answer_total", "Runs where a walltime/turns ceiling fired AFTER the run had already answered (engine 7.13.0: these end completed with NO limits.* code). Dual of budget_exceeded_total, but NOT over the same population: this one is minted on the deps-level notice sink so it sees EVERY engine run (background children, delegated/nested legs, leader lanes); budget_exceeded_total is incremented only by the HTTP submit legs finalizeTaskResult, i.e. top-level HTTP-submitted runs. Sum them only within that narrower population; see the forwarder comment, by origin");
|
|
218
220
|
m.counter("cost_quota_rejected_total", "Tasks refused because the principal is over its cumulative cost quota");
|
|
219
221
|
m.counter("degraded_total", "Tasks degraded to a cheaper model (1.40), by reason (budget/rate_limit/breaker_open)");
|
|
220
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)");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type RunRecord } from "../plugins/store-contracts.js";
|
|
2
2
|
/** 一次终局写口交给观测面的原料(store 侧装配,**未脱敏** —— 脱敏在本模块内做,见 {@link notifyRunTerminal})。 */
|
|
3
3
|
export interface RunTerminalRecord {
|
|
4
4
|
/** 这条 run 的账本 id = `GET /v1/runs/:id` 的 id(本仓 run 行主键名为 taskId,同一个值)。 */
|
|
@@ -20,8 +20,9 @@ export type RunTerminalObserver = (level: "error" | "info", fields: Record<strin
|
|
|
20
20
|
/**
|
|
21
21
|
* 终局行的级别判据。`undefined` = 不打行。
|
|
22
22
|
*
|
|
23
|
-
* `blocked`
|
|
24
|
-
* `
|
|
23
|
+
* 一句话:**非成功终局才出声**(`failed` / `blocked`),用户主动取消降 `info`。词的分类不在本模块做 ——
|
|
24
|
+
* 它的唯一属主是 `plugins/store-contracts.ts` 的 `RUN_STATUS_CLASS` 表(S-213③:本模块此前手抄了一张
|
|
25
|
+
* 穷举 switch,并在 `blocked` 那一格给出与 fleet 读面互斥的答案)。表的编译期双向钉在属主处。
|
|
25
26
|
*/
|
|
26
27
|
export declare function runTerminalLogLevel(status: RunRecord["status"], errorCode: string | undefined): "error" | "info" | undefined;
|
|
27
28
|
/** 装配点(`main.ts` 唯一)。传 `undefined` 卸载(测试隔离)。 */
|