@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
|
@@ -23,6 +23,7 @@ export class RemoteSshExecutionEnv {
|
|
|
23
23
|
destroyed = false;
|
|
24
24
|
connEpoch = 0;
|
|
25
25
|
inFlight;
|
|
26
|
+
homeDir;
|
|
26
27
|
cfg;
|
|
27
28
|
injectedConnectOnce;
|
|
28
29
|
constructor(config, deps) {
|
|
@@ -41,6 +42,7 @@ export class RemoteSshExecutionEnv {
|
|
|
41
42
|
hostKey: config.hostKey,
|
|
42
43
|
};
|
|
43
44
|
this.cwd = this.cfg.mountPath;
|
|
45
|
+
this.homeDir = config.homeDir;
|
|
44
46
|
}
|
|
45
47
|
resolve(p) {
|
|
46
48
|
return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
|
|
@@ -116,6 +116,22 @@ export type ToolResultStoreFull = ToolResultStore & {
|
|
|
116
116
|
export type BreakerStateStore = TiDBBreakerState | PgBreakerState;
|
|
117
117
|
export type CostQuotaStore = TiDBCostQuota | PgCostQuota;
|
|
118
118
|
export type RateLimiterStore = TiDBRateLimiter | PgRateLimiter;
|
|
119
|
+
/**
|
|
120
|
+
* S-215 —— capture opt-out **记录载体的按平面取用座**。形与 core 的
|
|
121
|
+
* `RunnerDeps.memoryCaptureRecordStore`(`(plane: { controlDir }) => SessionCaptureRecordStore`)同构,
|
|
122
|
+
* 多一个**不指名平面**的缺省臂:`src()` = 「本部署记忆引擎自己那一只平面」。
|
|
123
|
+
*
|
|
124
|
+
* 🔴 为什么是工厂而不是一只实例(S-215 codex r1 [high] 验真后改形):core 的记忆平面**不止一个**
|
|
125
|
+
* (`prepare-memory.js:162` 的 personal 平面自带 `derivePersonalControlDir(engineRoot)`,与 project 平面的
|
|
126
|
+
* controlDir 不是同一只)。返回单只实例 ⇒ server 把所有平面折到一只目录上,于是升级前落在 personal
|
|
127
|
+
* 控制面的旧 opt-out 记录在升级后读作**缺席**=采集恢复(隐私面的静默 fail-open)。按平面取用后,
|
|
128
|
+
* file 载体与 core 的缺省**逐字节同址**,唯一的变化是这个席位现在**被声明了**(core 的能力信号要的就是它)。
|
|
129
|
+
* SQL 载体**刻意忽略** plane:它按 session_id 键控、跨平面全局 —— opt-out 是**会话**事实而不是平面事实
|
|
130
|
+
* (逐字理由见 boot/runner-deps.ts 该席的注)。
|
|
131
|
+
*/
|
|
132
|
+
export type SessionCaptureRecordSource = (plane?: {
|
|
133
|
+
controlDir: string;
|
|
134
|
+
}) => SessionCaptureRecordStore;
|
|
119
135
|
export interface StoreBackend {
|
|
120
136
|
/** Protocol-truth kind (clay 2026-07-06 wording): "mysql" = any MySQL-protocol server (MySQL / TiDB /
|
|
121
137
|
* MariaDB) behind the one mysql2 pool — the startup summary prints durable(mysql) for all of them. */
|
|
@@ -186,19 +202,25 @@ export interface StoreBackend {
|
|
|
186
202
|
* 「没有行 ⇒ 代码缺省 allow」—— 那是**放宽面**上的静默降级(permissionRule 那条禁令的同一理由)。
|
|
187
203
|
* File 形候真实的 local+governed 部署需求出现再落(候需求档)。 */
|
|
188
204
|
memoryOptOutGrant(): MemoryOptOutGrantStore | undefined;
|
|
189
|
-
/** [ref]②([ref] §2.1b,core 7.0.2 [ref] 件2):会话 capture opt-out
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
205
|
+
/** [ref]②([ref] §2.1b,core 7.0.2 [ref] 件2)/ S-215:会话 capture opt-out **记录**的载体 ——
|
|
206
|
+
* core `RunnerDeps.memoryCaptureRecordStore` / `MemoryEngineOptions.captureRecordStore` 的持久层。
|
|
207
|
+
* 与 §3.1 的**授权表**不是一个轴:那张管「谁**可以** opt-out」(operator 写的 verdict),这张管
|
|
208
|
+
* 「谁**已经** opt-out」(用户写的一次性单向记录)。
|
|
209
|
+
*
|
|
210
|
+
* 返回的是**按平面取用的源**({@link SessionCaptureRecordSource}),不是一只实例 —— core 的记忆平面
|
|
211
|
+
* 不止一只,折平会挪动旧记录的地址(理由全文见该类型的头注)。
|
|
212
|
+
* 载体按车道分形,但**三条车道都供**(S-215 起):SQL 两支 = 双方言 twin(`session_capture_optout`,
|
|
213
|
+
* Promise 形三腿,池即依托 ⇒ 无条件供,`plane` 刻意忽略);`local` = core 的控制面文件三腿
|
|
214
|
+
* `fileSessionCaptureRecordStore(controlDir)` 按平面现造,依托是**记忆引擎的控制面目录** ⇒ 记忆面暗的
|
|
215
|
+
* 部署上该平面不存在,于是这一支**随引擎接线门缺席**(唯一的 `undefined` 成因,逐字理由见 LocalBackend 的实装注)。
|
|
194
216
|
*
|
|
195
|
-
* 🔴
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
sessionCaptureRecords():
|
|
217
|
+
* 🔴 修前 local 恒 `undefined` 且注称「缺席是一个真答案」—— 那句话在**事实层面**成立(单机的正确载体
|
|
218
|
+
* 确实就是文件三腿)、在**接线层面**是假的:core 的 `captureCarrierUnsupported`
|
|
219
|
+
* (core 7.13.0 `prepare-task.js:497`)判的是「这个席位在不在」而不是「文件形能不能用」,叠上
|
|
220
|
+
* `isRemoteExecutionEnv` 的接口形鸭子判(`remote-env.js:21`)把本仓 host lane 一并算作 remote,
|
|
221
|
+
* 于是单机默认部署上 `memoryCapture:"off"` 两条 ingress 结构性不可达(B-085)。
|
|
222
|
+
* 刻意仍不给 local 一个**内存** twin:内存形会在重启时把一条隐私记录静默丢掉(比文件三腿严格更差)。 */
|
|
223
|
+
sessionCaptureRecords(): SessionCaptureRecordSource | undefined;
|
|
202
224
|
/** S-1 legacy-epoch discriminant (core `RunnerDeps.legacyRewindBoundaryProbe`): does the RETIRED whole-tree
|
|
203
225
|
* snapshot epoch hold a boundary for (sessionId, entryId)? Reads the legacy `snapshot_manifest` table.
|
|
204
226
|
* SQL backends only (local never had a whole-tree epoch table). Diagnostic input on the refusal path —
|
|
@@ -2,6 +2,7 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { AdoptionError, FileStorageBackend, FileSessionRepo } from "@sema-agent/core";
|
|
4
4
|
import { FileWorkflowJournalStore } from "@sema-agent/core";
|
|
5
|
+
import { fileSessionCaptureRecordStore } from "@sema-agent/core";
|
|
5
6
|
import { TiDBOutcomeLedger, PgOutcomeLedger } from "./outcome-ledger-sql.js";
|
|
6
7
|
import { FileOutcomeSink } from "./file-outcome-sink.js";
|
|
7
8
|
import { TiDBWorkflowRunStore, TiDBWorkflowCompletionInbox, TiDBWorkflowNotifyJournalStore, TiDBWorkflowAgentSessionIndex, PgWorkflowRunStore, PgWorkflowCompletionInbox, PgWorkflowNotifyJournalStore, PgWorkflowAgentSessionIndex } from "./workflow-run-store-sql.js";
|
|
@@ -20,6 +21,7 @@ import { TiDBSessionPolicyStore, PgSessionPolicyStore } from "./session-policy-s
|
|
|
20
21
|
import { TiDBWorkflowJournalStore, PgWorkflowJournalStore } from "./workflow-journal-store-sql.js";
|
|
21
22
|
import { TiDBFileHistoryStore, PgFileHistoryStore, FILE_HISTORY_BLOB_TABLE, FILE_HISTORY_BLOB_KEY_PREFIX } from "./file-history-store-sql.js";
|
|
22
23
|
import { MinioBlobBackend, SqlBlobBackend } from "./blob-backend.js";
|
|
24
|
+
import { fileMemoryControlDirFor } from "../memory-scope.js";
|
|
23
25
|
import { createTidbPool, ensureSchema as ensureTidbSchema } from "./tidb-pool.js";
|
|
24
26
|
import { createPgPool, ensurePgSchema, pgPoolOptions } from "./pg-pool.js";
|
|
25
27
|
import { TiDBRunStore, PgRunStore } from "./run-store-sql.js";
|
|
@@ -136,7 +138,8 @@ class TiDBBackend {
|
|
|
136
138
|
sessionPolicy() { return new TiDBSessionPolicyStore(this.pool); }
|
|
137
139
|
fileHistory() { return new TiDBFileHistoryStore(this.pool, historyBlobBackend(this.config, "tidb", this.pool), this.config.fileHistory); }
|
|
138
140
|
memoryOptOutGrant() { return new TiDBMemoryOptOutGrantStore(this.pool); }
|
|
139
|
-
sessionCaptureRecords() { return new TiDBSessionCaptureRecordStore(this.pool); }
|
|
141
|
+
sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new TiDBSessionCaptureRecordStore(this.pool)); }
|
|
142
|
+
captureRecordsInst;
|
|
140
143
|
legacyRewindBoundaryProbe(query) { return legacyProbeImpl("tidb", this.pool, query); }
|
|
141
144
|
legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "tidb", this.pool); }
|
|
142
145
|
legacySnapshotPurge(sessionId) { return legacyPurgeImpl("tidb", this.pool, sessionId); }
|
|
@@ -183,7 +186,8 @@ class PgBackend {
|
|
|
183
186
|
sessionPolicy() { return new PgSessionPolicyStore(this.pool); }
|
|
184
187
|
fileHistory() { return new PgFileHistoryStore(this.pool, historyBlobBackend(this.config, "pg", this.pool), this.config.fileHistory); }
|
|
185
188
|
memoryOptOutGrant() { return new PgMemoryOptOutGrantStore(this.pool); }
|
|
186
|
-
sessionCaptureRecords() { return new PgSessionCaptureRecordStore(this.pool); }
|
|
189
|
+
sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new PgSessionCaptureRecordStore(this.pool)); }
|
|
190
|
+
captureRecordsInst;
|
|
187
191
|
legacyRewindBoundaryProbe(query) { return legacyProbeImpl("pg", this.pool, query); }
|
|
188
192
|
legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "pg", this.pool); }
|
|
189
193
|
legacySnapshotPurge(sessionId) { return legacyPurgeImpl("pg", this.pool, sessionId); }
|
|
@@ -259,6 +263,10 @@ class LocalBackend {
|
|
|
259
263
|
this.sendFileLedgerStore = new FileSendFileLedger(this.fileBackend.root);
|
|
260
264
|
this.workflowJournalStore = new FileWorkflowJournalStore(this.fileBackend.root);
|
|
261
265
|
this.outcomeSinkInst = new FileOutcomeSink(this.fileBackend.root);
|
|
266
|
+
const ownControlDir = fileMemoryControlDirFor(config);
|
|
267
|
+
this.captureRecordsSource = ownControlDir === undefined
|
|
268
|
+
? undefined
|
|
269
|
+
: (plane) => fileSessionCaptureRecordStore(plane?.controlDir ?? ownControlDir);
|
|
262
270
|
}
|
|
263
271
|
async ensureSchema() { }
|
|
264
272
|
async close() {
|
|
@@ -304,7 +312,8 @@ class LocalBackend {
|
|
|
304
312
|
sessionPolicy() { return this.fileBackend.sessionPolicyStore; }
|
|
305
313
|
fileHistory() { return this.fileBackend.fileHistoryStore; }
|
|
306
314
|
memoryOptOutGrant() { return undefined; }
|
|
307
|
-
sessionCaptureRecords() { return
|
|
315
|
+
sessionCaptureRecords() { return this.captureRecordsSource; }
|
|
316
|
+
captureRecordsSource;
|
|
308
317
|
workflowJournal() { return this.workflowJournalStore; }
|
|
309
318
|
outcomeSink() { return this.outcomeSinkInst; }
|
|
310
319
|
session() { return this.sessionStore; }
|
|
@@ -108,27 +108,65 @@ export interface RunRecord {
|
|
|
108
108
|
updatedAt: string;
|
|
109
109
|
}
|
|
110
110
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
111
|
+
* `RunRecord["status"]`(= `"running" | TaskStatus`)的**唯一分类表** —— 词表的属主 = 判据的属主,
|
|
112
|
+
* 与类型定义同住一个文件。四个类:
|
|
113
|
+
* · `live` —— 还在跑;
|
|
114
|
+
* · `succeeded` —— 成功终局;
|
|
115
|
+
* · `unsuccessful` —— **非成功**终局。两个词:`failed`(限额/供应商/中止/无效输出)与 `blocked`
|
|
116
|
+
* —— 后者是 core `TerminalCause` 的 `{kind:"blocked", reason}`,d.ts 逐字为「the agent could not
|
|
117
|
+
* finish and said why」= **agent 自报的终态**(说完就结束,不等人、不留 checkpoint)。它**不是**
|
|
118
|
+
* 治理判决:门拒绝那条腿在 core 里走 `paused`/`failed`,永远不铸这个词;
|
|
119
|
+
* · `parked` —— 停在门上还能再动(`suspended` 审批/资源门、`needs_review` 评审门)。
|
|
113
120
|
*
|
|
114
|
-
* 🔴
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* · **删词** ⇒ 集合里留一个死字面量,没有任何东西会说话。
|
|
120
|
-
* 穷举 `switch` 把两个方向都变成**编译期**问题:core 加成员 ⇒ `default` 臂收不敛到 `never` ⇒ tsc 红,
|
|
121
|
-
* 加成员的人被迫当场回答「这个新态算不算终局」;删成员 ⇒ 对应 `case` 变成不可达字面量、同样红。
|
|
122
|
-
* 这条判据不许再被手抄成第二份 —— 两个消费点(`approval-reconciler.ts` 判据 2、`http/routes/runs.ts`
|
|
123
|
-
* 的取消后复读)都从这里取。
|
|
121
|
+
* 🔴 为什么是**一张表**而不是三张 switch(S-213③,2026-09-11):`blocked` 这个词此前在本仓有**两个
|
|
122
|
+
* 独立写者**且答案互斥 —— `observability/run-terminal-log.ts` 判它是「治理判决,不是失败 ⇒ 不打行」,
|
|
123
|
+
* `fleet/fleet-bus.ts` 判它是「非成功终局(agent 放弃)⇒ failed 桶、行离场」。两处各自穷举、各自正确
|
|
124
|
+
* 地编译,而一条 `report_blocked` 的 run 在面板上离场、在引擎日志里一个字都没有。三张手抄的 switch
|
|
125
|
+
* 就是三个写者;收成一张表之后,词的语义只有这一处能改。
|
|
124
126
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
+
* 🔴 为什么这**不是**当年被否掉的 `ReadonlySet<string>`([ref] 二轮扫描 finding):那份集合的门对象是
|
|
128
|
+
* `string`,对词表两个方向都无钉 —— 增词静默判「非终局」、删词留一个死字面量没人说话。本表两个方向
|
|
129
|
+
* 都是**编译期**问题:`satisfies Readonly<Record<RunRecord["status"], RunStatusClass>>` 要求每个词都有
|
|
130
|
+
* 一行(core 加成员 ⇒ 缺行 ⇒ tsc 红,加成员的人被迫当场回答「这个新态算哪一类」),而多出一行
|
|
131
|
+
* core 已经没有的词同样红(超出键)。终局词表真变过(core 5.8.0 把 `"timeout"` 从 `TaskStatus` 删了)。
|
|
132
|
+
*
|
|
133
|
+
* 判据不许再被手抄成第二份:消费点(`approval-reconciler.ts` 判据 2、`http/routes/runs.ts` 的取消后
|
|
134
|
+
* 复读、`observability/run-terminal-log.ts` 的级别判、`fleet/fleet-bus.ts` 的 failed 桶编译钉)都从这里取。
|
|
135
|
+
*/
|
|
136
|
+
export type RunStatusClass = "live" | "succeeded" | "unsuccessful" | "parked";
|
|
137
|
+
export declare const RUN_STATUS_CLASS: {
|
|
138
|
+
readonly running: "live";
|
|
139
|
+
readonly completed: "succeeded";
|
|
140
|
+
readonly failed: "unsuccessful";
|
|
141
|
+
readonly blocked: "unsuccessful";
|
|
142
|
+
readonly suspended: "parked";
|
|
143
|
+
readonly needs_review: "parked";
|
|
144
|
+
};
|
|
145
|
+
/** 表判为「非成功终局」的词——**从表派生**,不是手抄的第二份清单。今天 = `"failed" | "blocked"`。
|
|
146
|
+
* 消费点(`fleet/fleet-bus.ts` 的 failed 桶)拿它做编译期同源钉:表翻面 ⇒ 那道钉当场红。 */
|
|
147
|
+
export type UnsuccessfulRunStatus = {
|
|
148
|
+
[K in RunRecord["status"]]: (typeof RUN_STATUS_CLASS)[K] extends "unsuccessful" ? K : never;
|
|
149
|
+
}[RunRecord["status"]];
|
|
150
|
+
/**
|
|
151
|
+
* 「这一行已经走到**终局**了吗」——终局 = 成功 or 非成功(completed / failed / blocked);
|
|
152
|
+
* 非终局 = running / suspended / needs_review(后两者都还能再动)。
|
|
153
|
+
*
|
|
154
|
+
* 未知词判**非终局**:判成「终局」的代价不可逆 —— `approval-reconciler` 判据 2 会把一张还活着的 ask
|
|
155
|
+
* 永久打成 DENIED/routing_failure,取消路由也会把这一行当终局上报。
|
|
127
156
|
*/
|
|
128
157
|
export declare function isTerminalRunStatus(status: RunRecord["status"]): boolean;
|
|
129
158
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
159
|
+
* 「这一行的终局是**非成功**的吗」(S-213③ 的共享判据)——`failed` 与 `blocked` 两词。
|
|
160
|
+
*
|
|
161
|
+
* 🔴 它回答的问题与 {@link isTerminalRunStatus} **不同**:后者只问「还会不会再动」,本判据问「这次任务
|
|
162
|
+
* 到底成没成」。凡是「这条 run 该不该在失败面上出声/离场」的判断都读这一条,不许再手抄
|
|
163
|
+
* `=== "failed" || === "blocked"`(那正是 `blocked` 长出两个互斥读法的成因)。
|
|
164
|
+
*
|
|
165
|
+
* 未知词判**不是**非成功:对读不懂的词保持沉默,好过按 error 级刷屏。
|
|
166
|
+
*/
|
|
167
|
+
export declare function isUnsuccessfulRunStatus(status: RunRecord["status"]): boolean;
|
|
168
|
+
/**
|
|
169
|
+
* 「这一行**停在门上**(park)了吗」(扫描P2,2026-08-12)。
|
|
132
170
|
*
|
|
133
171
|
* 🔴 为什么必须有名字:park 是**两个词**,不是一个 —— `suspended`(tool_approval / policy_ask / human /
|
|
134
172
|
* resource_limit 门)与 `needs_review`(`plan_review` / dry-run 拦截门,写点 = 各 store 的
|
|
@@ -139,8 +177,6 @@ export declare function isTerminalRunStatus(status: RunRecord["status"]): boolea
|
|
|
139
177
|
*
|
|
140
178
|
* 未知词的方向与 {@link isTerminalRunStatus} 同向保守:判**不是** park。理由是消费点会据此做**不可逆**
|
|
141
179
|
* 动作(cancel 的恢复把手会 CAS-expire 一张 checkpoint);对一个本进程读不懂的状态,少动一次远好过错杀。
|
|
142
|
-
* 真正的执法点是下面那条 `never`:core 的 `TaskStatus` 一加成员就 tsc 红,加成员的人必须当场回答
|
|
143
|
-
* 「这个新态算不算 park」。
|
|
144
180
|
*/
|
|
145
181
|
export declare function isParkedRunStatus(status: RunRecord["status"]): boolean;
|
|
146
182
|
/** One row of the GET /v1/sessions list (CC /resume picker): a DISTINCT session (sessionId) aggregated from its
|
|
@@ -1,37 +1,24 @@
|
|
|
1
1
|
import { parseJsonOr, toIso as iso, normalizeIsoOrNull as isoOrNull } from "./sql-row-helpers.js";
|
|
2
|
+
export const RUN_STATUS_CLASS = {
|
|
3
|
+
running: "live",
|
|
4
|
+
completed: "succeeded",
|
|
5
|
+
failed: "unsuccessful",
|
|
6
|
+
blocked: "unsuccessful",
|
|
7
|
+
suspended: "parked",
|
|
8
|
+
needs_review: "parked",
|
|
9
|
+
};
|
|
10
|
+
function runStatusClassOf(status) {
|
|
11
|
+
return RUN_STATUS_CLASS[status];
|
|
12
|
+
}
|
|
2
13
|
export function isTerminalRunStatus(status) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
case "running":
|
|
9
|
-
case "suspended":
|
|
10
|
-
case "needs_review":
|
|
11
|
-
return false;
|
|
12
|
-
default: {
|
|
13
|
-
const unhandled = status;
|
|
14
|
-
void unhandled;
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
14
|
+
const cls = runStatusClassOf(status);
|
|
15
|
+
return cls === "succeeded" || cls === "unsuccessful";
|
|
16
|
+
}
|
|
17
|
+
export function isUnsuccessfulRunStatus(status) {
|
|
18
|
+
return runStatusClassOf(status) === "unsuccessful";
|
|
18
19
|
}
|
|
19
20
|
export function isParkedRunStatus(status) {
|
|
20
|
-
|
|
21
|
-
case "suspended":
|
|
22
|
-
case "needs_review":
|
|
23
|
-
return true;
|
|
24
|
-
case "running":
|
|
25
|
-
case "completed":
|
|
26
|
-
case "failed":
|
|
27
|
-
case "blocked":
|
|
28
|
-
return false;
|
|
29
|
-
default: {
|
|
30
|
-
const unhandled = status;
|
|
31
|
-
void unhandled;
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
21
|
+
return runStatusClassOf(status) === "parked";
|
|
35
22
|
}
|
|
36
23
|
export function mapBakeRow(r) {
|
|
37
24
|
return {
|
|
@@ -59,7 +59,7 @@ import type { Pool as PgPool } from "pg";
|
|
|
59
59
|
import { summarizeWorkflowRun, type WorkflowRunStore } from "@sema-agent/core";
|
|
60
60
|
import { type WorkflowCompletionInbox, type WorkflowCompletionInboxEntry } from "../orchestration/workflow-completion-inbox.js";
|
|
61
61
|
import type { WorkflowNotifyJournalStore, WorkflowNotifyJournalEntry } from "../orchestration/workflow-notify-journal.js";
|
|
62
|
-
import type
|
|
62
|
+
import { type WorkflowAgentSessionIndex, type WorkflowAgentSessionRow } from "../orchestration/workflow-agent-session-index.js";
|
|
63
63
|
import { type SqlDriver } from "./sql-driver.js";
|
|
64
64
|
type WorkflowRun = Parameters<WorkflowRunStore["put"]>[1];
|
|
65
65
|
/** Row cap guard: a run blob beyond this is NOT stored on update (the prior revision stays) — TiDB's
|
|
@@ -68,19 +68,41 @@ type WorkflowRun = Parameters<WorkflowRunStore["put"]>[1];
|
|
|
68
68
|
export declare const MAX_RUN_BLOB_BYTES: number;
|
|
69
69
|
declare const onWarnType: (msg: string, meta: Record<string, unknown>) => void;
|
|
70
70
|
export type InboxWarn = typeof onWarnType;
|
|
71
|
-
/** Review fix (1.108, lens④ HIGH): an oversize blob must NOT fail the update outright — core's persist chain
|
|
72
|
-
* treats `false` as a CAS loss and does NOT advance its tracked rev, so once a run outgrows the cap EVERY later
|
|
73
|
-
* write (including the TERMINAL one) keeps failing → the durable row is stuck `running` forever and the 24h
|
|
74
|
-
* orphan sweep finalizes a genuinely-completed run as `abandoned`. Instead degrade the PAYLOAD, not the write:
|
|
75
|
-
* step 1 truncate the two unbounded strings (result/error); step 2 drop the heavy arrays (phases/agents/groups)
|
|
76
|
-
* with an explicit marker. Returns null only when even the skeleton is oversize (physics — caller keeps the
|
|
77
|
-
* prior revision, matching the old behavior for that corner). Dialect-neutral (pure JS, no SQL) — shared
|
|
78
|
-
* verbatim by both twins. */
|
|
79
71
|
/** S-107:workflow_run 行的 `error`(脚本抛出的 `err.message`,core 源头不脱)在**两条写腿**(put / update 常态 + oversize 降级)
|
|
80
72
|
* 同一口脱;`result` 由 core 完成时源头脱。幂等,不抛(超大 ⇒ 占位)。 */
|
|
81
73
|
export declare function withRedactedError<T extends {
|
|
82
74
|
error?: string | undefined;
|
|
83
75
|
}>(run: T): T;
|
|
76
|
+
/** Review fix (1.108, lens④ HIGH): an oversize blob must NOT fail the update outright — core's persist chain
|
|
77
|
+
* treats `false` as a CAS loss and does NOT advance its tracked rev, so once a run outgrows the cap EVERY later
|
|
78
|
+
* write (including the TERMINAL one) keeps failing → the durable row is stuck `running` forever and the 24h
|
|
79
|
+
* orphan sweep finalizes a genuinely-completed run as `abandoned`. Instead degrade the PAYLOAD, not the write.
|
|
80
|
+
*
|
|
81
|
+
* 🔴 **S-192(7.71.0):瘦身只裁展示载荷,parked 行的恢复身份是最后才放弃的东西。**
|
|
82
|
+
* 病(修前逐字):step2 写作 `agents: []` ⇒ 一只 parked 的 workflow 子代的 `callKey` / `sessionId` /
|
|
83
|
+
* `parkedCheckpointToken` 随整段消失。server 侧复核「这条 park 仍在此 token 下」的依据**只有这一行**(join 索引
|
|
84
|
+
* 刻意不存它——它是能力;引擎自己的赎回键另住 resume journal),于是 `/decide` 的 workflow 车道在 `run.agents`
|
|
85
|
+
* 里找不到那一行 ⇒ 落 legacy 腿拿一个**错因**的 409 `conflict.resume_context_unavailable`,人的批准无处兑付;
|
|
86
|
+
* 同时 core resume 路径上那道「blob 里 parked 而 journal 缺条目 ⇒ 拒 resume」的防线(`prior.agents`)也随之失明。
|
|
87
|
+
*
|
|
88
|
+
* 阶梯 = 一张**按价值排序的放弃清单**,一级一级试到装得下为止(**判据只有一条:恢复身份最后放弃**):
|
|
89
|
+
* step 1 —— 两个无界字符串(`result` / `error`)截断带标记;
|
|
90
|
+
* step 2 —— `phases` / `groups` 整段丢,`agents` 收敛成**恢复身份行**
|
|
91
|
+
* ({@link buildParkedRecoveryIdentities};非 parked 的行不带恢复身份 ⇒ 整行丢,与修前同);
|
|
92
|
+
* step 3 —— 顶层**观测载荷**(codex r1-[high],红先复现后修):`effectiveArgs` 与 `resultFull` 整段丢、
|
|
93
|
+
* `name` / `description` 先脱后切。core 对 `effectiveArgs` 的成文口径逐字是 OBSERVATION-ONLY
|
|
94
|
+
* (never a gate input,never re-read by the engine;resume 的身份由 journal 的 callKey 携带,
|
|
95
|
+
* 不是这个字段),`resultFull` 同类(取回面),两者都**无界**。修前这一级不存在,于是阶梯
|
|
96
|
+
* 一边原样留着它们、一边把唯一那条 parked 行删掉盖标记 —— 自己违反了自己那条判据,
|
|
97
|
+
* 一条本来赎得回的审批被判死(生产上界 4 MiB 上真复现);
|
|
98
|
+
* step 4 —— 恢复身份行上的**展示名**(`label`)—— 它是这条行上唯一可弃的展示物,身份四位恒在
|
|
99
|
+
* (codex r2-[high],红先复现后修:修前阶梯一边留着每行 200 字符的 label、一边把**全部**身份
|
|
100
|
+
* 删掉盖标记 —— 同一条判据第二次被自己违反;6000 条 CJK label 的 parked 行在生产上界上
|
|
101
|
+
* 实测 `agents:[]` + `parks:6000`,而清空 label 后 6000 条身份**全都装得下**);
|
|
102
|
+
* step 5 —— 连只剩身份的行都装不下 ⇒ 丢掉它们并落**显式不可恢复标记**({@link PARK_IDENTITY_LOSS_KEY}),
|
|
103
|
+
* `/decide` 与 SLA sweep 读到它就**响亮拒**,而不是让一条赎回不了的 park 看起来像普通 miss。
|
|
104
|
+
* Returns null only when even the marked skeleton is oversize (physics — caller keeps the prior revision,
|
|
105
|
+
* matching the old behavior for that corner). Dialect-neutral (pure JS, no SQL) — shared verbatim by both twins. */
|
|
84
106
|
export declare function slimOversizeRun(run: WorkflowRun & {
|
|
85
107
|
id: string;
|
|
86
108
|
scope: string;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { redactErrorMessage, redactHead } from "../observability/run-terminal-log.js";
|
|
2
2
|
import { summarizeWorkflowRun, isTerminalWorkflowStatus, WorkflowRunStoreError } from "@sema-agent/core";
|
|
3
3
|
import { foldKeyFamily, MAX_PENDING_PER_SESSION, PURGE_FENCE_MS, SERVED_FENCE_MS, } from "../orchestration/workflow-completion-inbox.js";
|
|
4
|
+
import { buildParkedRecoveryIdentities, buildParkIdentityLoss, PARK_IDENTITY_LOSS_KEY } from "../orchestration/workflow-agent-session-index.js";
|
|
4
5
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
5
6
|
import { isDupKeyError } from "./sql-errors.js";
|
|
6
7
|
export const MAX_RUN_BLOB_BYTES = 4 * 1024 * 1024;
|
|
8
|
+
const SLIM_HEADER_MAX = 200;
|
|
7
9
|
const onWarnType = (msg, meta) => void [msg, meta];
|
|
8
10
|
export function withRedactedError(run) {
|
|
9
11
|
if (typeof run.error !== "string")
|
|
@@ -22,8 +24,33 @@ export function slimOversizeRun(run, maxBytes) {
|
|
|
22
24
|
let blob = JSON.stringify(step1);
|
|
23
25
|
if (Buffer.byteLength(blob) <= maxBytes)
|
|
24
26
|
return blob;
|
|
25
|
-
const
|
|
27
|
+
const identities = buildParkedRecoveryIdentities(run.agents);
|
|
28
|
+
const shortError = step1.error ? String(step1.error).slice(0, 1000) + MARK : MARK;
|
|
29
|
+
const step2 = { ...step1, phases: [], agents: identities, groups: [], error: shortError };
|
|
26
30
|
blob = JSON.stringify(step2);
|
|
31
|
+
if (Buffer.byteLength(blob) <= maxBytes)
|
|
32
|
+
return blob;
|
|
33
|
+
const step3 = { ...step2 };
|
|
34
|
+
delete step3.effectiveArgs;
|
|
35
|
+
delete step3.resultFull;
|
|
36
|
+
if (typeof step3.name === "string")
|
|
37
|
+
step3.name = redactHead(step3.name, SLIM_HEADER_MAX);
|
|
38
|
+
if (typeof step3.description === "string")
|
|
39
|
+
step3.description = redactHead(step3.description, SLIM_HEADER_MAX);
|
|
40
|
+
blob = JSON.stringify(step3);
|
|
41
|
+
if (Buffer.byteLength(blob) <= maxBytes)
|
|
42
|
+
return blob;
|
|
43
|
+
const bareIdentities = buildParkedRecoveryIdentities(run.agents, 0);
|
|
44
|
+
const step4 = { ...step3, agents: bareIdentities };
|
|
45
|
+
blob = JSON.stringify(step4);
|
|
46
|
+
if (Buffer.byteLength(blob) <= maxBytes)
|
|
47
|
+
return blob;
|
|
48
|
+
const step5 = {
|
|
49
|
+
...step4,
|
|
50
|
+
agents: [],
|
|
51
|
+
...(identities.length > 0 ? { [PARK_IDENTITY_LOSS_KEY]: buildParkIdentityLoss(identities.length) } : {}),
|
|
52
|
+
};
|
|
53
|
+
blob = JSON.stringify(step5);
|
|
27
54
|
return Buffer.byteLength(blob) <= maxBytes ? blob : null;
|
|
28
55
|
}
|
|
29
56
|
export class SqlWorkflowRunStore {
|
package/dist/run-local.js
CHANGED
|
@@ -19,6 +19,7 @@ import { loadConfig, logConfigDiagnostics, parseNumOrFailNonNegative } from "./c
|
|
|
19
19
|
import { createConfigProvider } from "./config-provider.js";
|
|
20
20
|
import { applyEffective, applyCenterReadFace, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./config-center/facade.js";
|
|
21
21
|
import { hostExecutionEnvFactory } from "./plugins/remote-env-host.js";
|
|
22
|
+
import { executionLaneHomeDir } from "./execution-lane-caps.js";
|
|
22
23
|
import { makeLoadProjectMemory, makeProbeInstructionSources } from "./project-memory.js";
|
|
23
24
|
import { loadSkills } from "./capabilities/skills.js";
|
|
24
25
|
import { createRepoClient } from "./capabilities/repo-tools.js";
|
|
@@ -273,6 +274,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
273
274
|
const executionEnvFactory = hostRx
|
|
274
275
|
? hostExecutionEnvFactory({
|
|
275
276
|
workspaceDir,
|
|
277
|
+
...((h) => (h === undefined ? {} : { homeDir: h }))(executionLaneHomeDir(config)),
|
|
276
278
|
backgroundShell: config.requirePrincipal !== true,
|
|
277
279
|
...(hostRx.commandTimeoutMs != null ? { commandTimeoutMs: hostRx.commandTimeoutMs } : {}),
|
|
278
280
|
})
|
|
@@ -369,6 +371,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
369
371
|
],
|
|
370
372
|
});
|
|
371
373
|
const sharedRunnerDeps = createSharedRunnerDeps({
|
|
374
|
+
metrics,
|
|
372
375
|
config,
|
|
373
376
|
governanceSeams,
|
|
374
377
|
logger,
|
package/dist/task-a2a.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveComplianceDenies } from "@sema-agent/core";
|
|
1
|
+
import { resolveComplianceDenies, TOOL_WIRE_NAME_MAX_CHARS } from "@sema-agent/core";
|
|
2
2
|
import { HttpError } from "./security.js";
|
|
3
3
|
export const MAX_REQUEST_A2A_PEERS = 32;
|
|
4
4
|
export function a2aInjectionHonored(config) {
|
|
@@ -19,7 +19,7 @@ function normalizeA2aPeer(e) {
|
|
|
19
19
|
if (!e || typeof e !== "object")
|
|
20
20
|
return null;
|
|
21
21
|
const o = e;
|
|
22
|
-
if (typeof o.name !== "string" || o.name.length === 0 || o.name.length >
|
|
22
|
+
if (typeof o.name !== "string" || o.name.length === 0 || o.name.length > TOOL_WIRE_NAME_MAX_CHARS)
|
|
23
23
|
return null;
|
|
24
24
|
if (typeof o.url !== "string" || !HTTP_URL_RE.test(o.url))
|
|
25
25
|
return null;
|
package/dist/task-mcp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mcpInjectionDroppedNotice, resolveComplianceDenies } from "@sema-agent/core";
|
|
1
|
+
import { mcpInjectionDroppedNotice, resolveComplianceDenies, TOOL_WIRE_NAME_MAX_CHARS } from "@sema-agent/core";
|
|
2
2
|
import { HttpError } from "./security.js";
|
|
3
3
|
import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } from "./mcp-content-origin.js";
|
|
4
4
|
import { readMcpSourceLabel } from "./mcp-source-label.js";
|
|
@@ -26,7 +26,7 @@ function normalizeServer(e, noteBadOrigin) {
|
|
|
26
26
|
if (!e || typeof e !== "object")
|
|
27
27
|
return bad("entry");
|
|
28
28
|
const o = e;
|
|
29
|
-
if (typeof o.name !== "string" || o.name.length === 0 || o.name.length >
|
|
29
|
+
if (typeof o.name !== "string" || o.name.length === 0 || o.name.length > TOOL_WIRE_NAME_MAX_CHARS)
|
|
30
30
|
return bad("name");
|
|
31
31
|
const t = o.transport;
|
|
32
32
|
if (!t || typeof t !== "object")
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -312,15 +312,28 @@ export interface FsWriteGateWiring {
|
|
|
312
312
|
/**
|
|
313
313
|
* 🔴 S-177 / codex r2 [high](验真后修)—— **执行环境的 home**(core `PathRuleBases.home`,`~/…` 形的基)。
|
|
314
314
|
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
315
|
+
* 🔴 **S-213④ 起本字段在远端车道上也可以有值**:四只远端 adapter 此前一个都不声明 `ExecutionEnv.homeDir`,
|
|
316
|
+
* 而本字段那时只在 `remoteExec` 整个缺席时才铸 —— 于是**每一条远端部署的 `~/` 规则都恒 422**(接入审计
|
|
317
|
+
* M13)。修法不是放松下面那道门,而是让部署**真的知道** home:`execution-lane-caps.ts` 的
|
|
318
|
+
* `executionLaneHomeDir` 是唯一属主,`boot/execution-env.ts` 把它递给 adapter 的 `homeDir` 座、
|
|
319
|
+
* `boot/resolve-spec.ts` / `boot/parked-revive-gate.ts` 把**同一个值**递成本字段。两处同源是承重的:
|
|
320
|
+
* 各算一份就会出现「422 门放行而引擎判 unreadable」或反过来。
|
|
319
321
|
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
322
|
+
* 🔴 **S-213⑦ 改真话**(亲读 core 7.12.0 `dist/core/permission-rule-model.js:365-372` /
|
|
323
|
+
* `:340-350`,推翻本注此前的两句):
|
|
324
|
+
* · core 对缺席的 `home` **不**回落 `os.homedir()` —— `resolvePathPattern` 走
|
|
325
|
+
* `lexicalNormalAbsolutePathOf(baseValueOf("home", bases) ?? "")`,base 缺席 ⇒ `undefined` ⇒ 直接回
|
|
326
|
+
* `{missingBase:"home"}`,该调用的射程判 `unreadable`(fail-closed 的 ask,人来清),从不猜一个目录。
|
|
327
|
+
* 换言之引擎侧本来就没有「拿 server 的 home 去匹配远端目标」这条静默腿(那句旧注是本仓自己的推断)。
|
|
328
|
+
* · core **有**「这条规则需要哪些 base」的查询口 —— 根导出的 `ruleBasesNeeded(rule)`
|
|
329
|
+
* (`//abs` ⇒ 空、`~/` ⇒ `["home"]`、`/x` ⇒ `["root"]`、其余 ⇒ `["cwd"]`),`resolvePathPattern`
|
|
330
|
+
* 自己也读它。本仓的判据腿今天读的是 core 解析出的规范 command 前缀,与该查询口**同一分类**、
|
|
331
|
+
* 不是第二套文法;两者哪天分叉由这条注负责被人看见。
|
|
332
|
+
*
|
|
333
|
+
* ⇒ 本字段在**这条部署的执行车道 home 已知**时在场(`executionLaneHomeDir`,见 `execution-lane-caps.ts`)。
|
|
334
|
+
* 缺席时 `~/` 形规则由 {@link rejectUnmatchableSettingsNames} **响亮拒**(422),而不是让它编译成一条
|
|
335
|
+
* core 只会判 `unreadable` 的规则 —— 两道门同向(都不猜目录),422 只是把同一个拒挪到更早、更响的地方:
|
|
336
|
+
* 运维在**下单那一刻**就知道这条规则这条车道读不了,而不是等到跑起来每次都弹一张 ask。
|
|
324
337
|
*/
|
|
325
338
|
taskHome?: string;
|
|
326
339
|
/** The session scratchpad dir (envFacts.scratchpadDir, [ref]③) — writes there are always auto-allowed. */
|
package/dist/task-settings.js
CHANGED
|
@@ -279,8 +279,8 @@ function rejectUnmatchableSettingsNames(perms, pathBases) {
|
|
|
279
279
|
const parsed = parseRuleText(name, behavior);
|
|
280
280
|
if ("rule" in parsed && ruleToolGrammarOf(parsed.rule.tool) === "path" && parsed.rule.command.startsWith("~/")) {
|
|
281
281
|
throw new Error(`${field} has a home-relative path rule (${name}) but this deployment cannot resolve the EXECUTION environment's home directory — ` +
|
|
282
|
-
`
|
|
283
|
-
`Spell the path absolutely (${"Edit(//abs/path/**)"} form), or run the task on a lane whose home this worker knows.`);
|
|
282
|
+
`the engine never guesses one: without a home base the rule resolves to "unreadable" and every matching call stops to ask, so it guards nothing. ` +
|
|
283
|
+
`Spell the path absolutely (${"Edit(//abs/path/**)"} form), declare the lane's home with REMOTE_EXEC_HOME_DIR, or run the task on a lane whose home this worker knows.`);
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
}
|