@sema-agent/server 7.118.0 → 7.119.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/MIGRATION.md +85 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +24 -13
- package/deploy/sema-up/README.md +2 -0
- package/deploy/sema-up/chart/templates/minio.yaml +2 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
- package/deploy/sema-up/chart/templates/server.yaml +1 -0
- package/deploy/sema-up/chart/values.yaml +7 -1
- package/deploy/sema-up/cluster-up.sh +20 -8
- package/deploy/sema-up/kube-up.sh +29 -16
- package/deploy/sema-up/lib/buckets.sh +21 -0
- package/deploy/sema-up/lib/health.sh +33 -0
- package/deploy/sema-up/sema-up.sh +14 -9
- package/deploy/sema-up/smoke.sh +16 -6
- package/deploy/sema-up/stack/compose.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
- package/dist/approval-ask-audit-store.js +2 -7
- package/dist/approval-card.d.ts +5 -5
- package/dist/approval.d.ts +51 -5
- package/dist/approval.js +76 -13
- package/dist/audit.d.ts +12 -1
- package/dist/audit.js +62 -2
- package/dist/auto-mode-face.d.ts +2 -3
- package/dist/bake-runner/main.js +1 -6
- package/dist/bake-runner/protocol.d.ts +18 -0
- package/dist/bake-runner/protocol.js +22 -2
- package/dist/bake-runner/runner.d.ts +12 -8
- package/dist/bake-runner/runner.js +5 -9
- package/dist/boot/config-center.js +1 -1
- package/dist/boot/coordinators.js +4 -1
- package/dist/boot/device-lane.d.ts +3 -3
- package/dist/boot/device-lane.js +1 -2
- package/dist/boot/leader.d.ts +4 -4
- package/dist/boot/leader.js +1 -2
- package/dist/boot/parked-revive-gate.d.ts +5 -1
- package/dist/boot/parked-revive-gate.js +8 -2
- package/dist/boot/resolve-spec.js +7 -4
- package/dist/boot/stage-07-capability-layer.js +5 -1
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.js +12 -7
- package/dist/capabilities/center-plugins.d.ts +2 -0
- package/dist/capabilities/center-plugins.js +24 -6
- package/dist/capabilities/code-review-council.js +8 -11
- package/dist/capabilities/scenarios.d.ts +34 -7
- package/dist/capabilities/scenarios.js +33 -6
- package/dist/config-catalog.js +3 -3
- package/dist/config.js +2 -2
- package/dist/deployment-governance.d.ts +5 -2
- package/dist/deployment-governance.js +4 -3
- package/dist/host-decision.d.ts +52 -27
- package/dist/host-decision.js +14 -7
- package/dist/http/admission.js +6 -6
- package/dist/http/resume-legs.js +2 -1
- package/dist/http/routes/approvals-assistant.d.ts +25 -3
- package/dist/http/routes/approvals-assistant.js +9 -5
- package/dist/http/routes/capabilities.js +4 -3
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/runs.js +11 -5
- package/dist/http/routes/sessions.js +20 -16
- package/dist/http/wire-types.d.ts +12 -13
- package/dist/images/manifest.d.ts +7 -1
- package/dist/images/manifest.js +18 -16
- package/dist/memory-store-refusal.d.ts +7 -12
- package/dist/memory-store-refusal.js +4 -5
- package/dist/namespaced-rule-name.d.ts +4 -3
- package/dist/observability/fail-open.d.ts +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/operator-ask.d.ts +6 -5
- package/dist/plugins/approval-exemption-store.d.ts +4 -0
- package/dist/plugins/approval-exemption-store.js +1 -0
- package/dist/plugins/blob-backend.d.ts +13 -0
- package/dist/plugins/blob-backend.js +26 -0
- package/dist/plugins/local-session-store.d.ts +78 -47
- package/dist/plugins/local-session-store.js +152 -122
- package/dist/plugins/one-time-migrations.d.ts +6 -0
- package/dist/plugins/one-time-migrations.js +19 -1
- package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
- package/dist/plugins/snapshot-bucket-presence.js +40 -0
- package/dist/plugins/store-backend.d.ts +17 -6
- package/dist/plugins/store-backend.js +46 -16
- package/dist/plugins/store-open-wait.d.ts +33 -0
- package/dist/plugins/store-open-wait.js +73 -0
- package/dist/rule-entry-list.js +1 -1
- package/dist/run-local.js +4 -3
- package/dist/security.d.ts +5 -3
- package/dist/security.js +4 -0
- package/dist/session-policy-wire.js +7 -12
- package/dist/spec-fields.d.ts +6 -2
- package/dist/store-posture.d.ts +14 -6
- package/dist/store-posture.js +7 -4
- package/dist/task-settings.d.ts +27 -55
- package/dist/task-settings.js +6 -18
- package/dist/tool-approval.d.ts +97 -43
- package/dist/tool-approval.js +120 -31
- package/dist/trace/redact.d.ts +6 -0
- package/dist/trace/redact.js +9 -9
- package/dist/trace/self-minted-wire.d.ts +1 -1
- package/dist/user-input-ledger.d.ts +19 -15
- package/dist/user-input-ledger.js +12 -20
- package/package.json +2 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { createCorruptReadFailOpenSeat, createDurableAbsenceSeat } from "../observability/corrupt-read-seat.js";
|
|
3
|
+
import { createLogger } from "../observability/logger.js";
|
|
3
4
|
import { join } from "node:path";
|
|
4
|
-
import { AdoptionError, FileStorageBackend
|
|
5
|
+
import { AdoptionError, FileStorageBackend } from "@sema-agent/core";
|
|
5
6
|
import { FileWorkflowJournalStore } from "@sema-agent/core";
|
|
6
7
|
import { fileSessionCaptureRecordStore } from "@sema-agent/core";
|
|
7
8
|
import { TiDBOutcomeLedger, PgOutcomeLedger } from "./outcome-ledger-sql.js";
|
|
@@ -12,8 +13,7 @@ import { StoreBootRefusalError } from "./store-contracts.js";
|
|
|
12
13
|
import { buildStoreOpenRefusalTail } from "./store-open-refusal.js";
|
|
13
14
|
import { acquireLocalBackendWithWait } from "./store-lock-wait.js";
|
|
14
15
|
import { NO_SQL_MIGRATIONS, runSqlOneTimeMigrations } from "./one-time-migrations.js";
|
|
15
|
-
import { LocalSessionStore } from "./local-session-store.js";
|
|
16
|
-
import { noteReplayInSessionOpenScope } from "../session-records-skipped-notice.js";
|
|
16
|
+
import { LocalSessionStore, createLocalSessionRepo } from "./local-session-store.js";
|
|
17
17
|
import { LocalCheckpointStore } from "./local-checkpoint-store.js";
|
|
18
18
|
import { FileResumeAnchorStore } from "./file-resume-anchor-store.js";
|
|
19
19
|
import { TiDBApprovalExemptionStore, PgApprovalExemptionStore, FileApprovalExemptionStore } from "./approval-exemption-store.js";
|
|
@@ -30,6 +30,7 @@ import { TiDBSessionPolicyStore, PgSessionPolicyStore } from "./session-policy-s
|
|
|
30
30
|
import { TiDBWorkflowJournalStore, PgWorkflowJournalStore } from "./workflow-journal-store-sql.js";
|
|
31
31
|
import { TiDBFileHistoryStore, PgFileHistoryStore, FILE_HISTORY_BLOB_TABLE, FILE_HISTORY_BLOB_KEY_PREFIX } from "./file-history-store-sql.js";
|
|
32
32
|
import { MinioBlobBackend, SqlBlobBackend } from "./blob-backend.js";
|
|
33
|
+
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
33
34
|
import { fileMemoryControlDirFor } from "../memory-scope.js";
|
|
34
35
|
import { createTidbPool, ensureSchema as ensureTidbSchema } from "./tidb-pool.js";
|
|
35
36
|
import { createPgPool, ensurePgSchema, pgPoolOptions } from "./pg-pool.js";
|
|
@@ -55,6 +56,7 @@ import { createSqlPermissionRuleStores } from "./permission-rule-store-sql.js";
|
|
|
55
56
|
import { createFilePermissionRuleStores } from "./permission-rule-store-file.js";
|
|
56
57
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
57
58
|
import { storeDegradedReasonFor } from "../store-posture.js";
|
|
59
|
+
import { openWithinWait } from "./store-open-wait.js";
|
|
58
60
|
function historyBlobBackend(config, dialect, pool) {
|
|
59
61
|
const s = config.snapshotBlobStore;
|
|
60
62
|
if (!s)
|
|
@@ -240,9 +242,7 @@ class LocalBackend {
|
|
|
240
242
|
throw new Error(`DB_BACKEND=local cannot open data dir ${root}: ${e instanceof Error ? e.message : String(e)}` +
|
|
241
243
|
(tail === undefined ? "" : ` ${tail}`));
|
|
242
244
|
}
|
|
243
|
-
this.sessionStore = new LocalSessionStore(
|
|
244
|
-
onCorruptRead: createCorruptReadFailOpenSeat("server.session-repo.corrupt-record-skipped", noteReplayInSessionOpenScope),
|
|
245
|
-
}), this.fileBackend.root);
|
|
245
|
+
this.sessionStore = new LocalSessionStore(createLocalSessionRepo(this.fileBackend.root), this.fileBackend.root, { log: createLogger(config.logLevel) });
|
|
246
246
|
const corruptReadWarn = (msg, fields) => this.discloseWarn(msg, fields);
|
|
247
247
|
this.runStore = new FileRunStore(this.fileBackend.root, {
|
|
248
248
|
error: (msg, fields) => void (this.onError ?? this.onWarn)?.(msg, fields),
|
|
@@ -344,21 +344,41 @@ export function createStoreBackend(config, onWarn, onError) {
|
|
|
344
344
|
return new TiDBBackend(createTidbPool(config), config);
|
|
345
345
|
}
|
|
346
346
|
export async function openStoreBackendWithFallback(config, logger) {
|
|
347
|
-
const
|
|
347
|
+
const sqlEngine = config.dbBackend === "mysql" || config.dbBackend === "pg" ? config.dbBackend : undefined;
|
|
348
|
+
const mayFallback = config.dbBackend === "local" && !config.dbBackendExplicit;
|
|
348
349
|
let backend;
|
|
350
|
+
let inFlight;
|
|
349
351
|
try {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
352
|
+
const attempt = async () => {
|
|
353
|
+
let built;
|
|
354
|
+
try {
|
|
355
|
+
built = createStoreBackend(config, (msg, meta) => logger.warn(msg, meta), logger.error === undefined ? undefined : (msg, meta) => logger.error(msg, meta));
|
|
356
|
+
inFlight = built;
|
|
357
|
+
if (built)
|
|
358
|
+
await built.ensureSchema();
|
|
359
|
+
inFlight = undefined;
|
|
360
|
+
return built;
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
inFlight = undefined;
|
|
364
|
+
await built?.close().catch(() => undefined);
|
|
365
|
+
throw err;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
backend =
|
|
369
|
+
sqlEngine === undefined
|
|
370
|
+
? await attempt()
|
|
371
|
+
: await openWithinWait(attempt, config.storeLockWaitMs, (w) => logger.warn("store_sql_open_wait", { engine: sqlEngine, endpoint: buildDisplaySafeUrl(sqlStoreCoordinatesAsUrl(config)), waitMs: w.waitMs, error: w.error }));
|
|
353
372
|
}
|
|
354
373
|
catch (err) {
|
|
374
|
+
void inFlight?.close().catch(() => undefined);
|
|
355
375
|
if (err instanceof AdoptionError)
|
|
356
376
|
throw err;
|
|
357
377
|
if (err instanceof StoreBootRefusalError)
|
|
358
378
|
throw err;
|
|
359
379
|
if (!mayFallback) {
|
|
360
|
-
if (
|
|
361
|
-
throw new Error(buildSqlStoreOpenRefusal(config.
|
|
380
|
+
if (sqlEngine !== undefined)
|
|
381
|
+
throw new Error(buildSqlStoreOpenRefusal(sqlEngine, buildDisplaySafeUrl(sqlStoreCoordinatesAsUrl(config)), config.storeLockWaitMs, err), { cause: err });
|
|
362
382
|
throw err;
|
|
363
383
|
}
|
|
364
384
|
logger.warn("store_db_unreachable_fallback_memory", {
|
|
@@ -370,11 +390,21 @@ export async function openStoreBackendWithFallback(config, logger) {
|
|
|
370
390
|
}
|
|
371
391
|
return { backend, degradedReason: undefined };
|
|
372
392
|
}
|
|
373
|
-
|
|
393
|
+
const SQL_HOST = /^(?:[A-Za-z0-9._-]{1,253}|\[[0-9A-Fa-f:.]{2,45}\])$/;
|
|
394
|
+
const SQL_COORDINATES_UNPRINTABLE = "<unprintable-coordinates>";
|
|
395
|
+
export function sqlStoreCoordinatesAsUrl(config) {
|
|
396
|
+
const coords = config.dbBackend === "pg" ? config.pg : config.tidb;
|
|
397
|
+
if (coords === undefined || !SQL_HOST.test(coords.host))
|
|
398
|
+
return SQL_COORDINATES_UNPRINTABLE;
|
|
399
|
+
return `${config.dbBackend === "pg" ? "postgres" : "mysql"}://${coords.host}:${coords.port}`;
|
|
400
|
+
}
|
|
401
|
+
export function buildSqlStoreOpenRefusal(engine, endpoint, waitedMs, err) {
|
|
374
402
|
const coords = engine === "pg" ? "PG_*" : "MYSQL_*";
|
|
375
403
|
const detail = err instanceof Error ? err.message : String(err);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
`
|
|
404
|
+
const waited = waitedMs > 0 ? `after retrying for ${waitedMs}ms (STORE_LOCK_WAIT_MS)` : "on the first attempt (STORE_LOCK_WAIT_MS=0: no wait)";
|
|
405
|
+
return (`DB_BACKEND=${engine}: cannot open the configured ${engine === "pg" ? "PostgreSQL" : "MySQL-protocol"} store at ${endpoint} ${waited} (${detail}) — refusing to boot, because a configured SQL store is never replaced by an in-memory one ` +
|
|
406
|
+
`(SESSION_BACKEND=auto included: sessions, parked runs and every durable record would silently live in memory and vanish on restart). ` +
|
|
407
|
+
`Next: fix the ${coords} coordinates or the store's reachability (the boot wait is STORE_LOCK_WAIT_MS); ` +
|
|
408
|
+
`or run explicitly in memory with DB_BACKEND=memory (and remove the ${coords} coordinates).`);
|
|
379
409
|
}
|
|
380
410
|
//# sourceMappingURL=store-backend.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** 进入等待腿时的**一次性**披露(结构上只在第一次失败时调一次,不是靠一个 flag 挡刷屏)。 */
|
|
2
|
+
export type StoreOpenWaitSink = (fields: {
|
|
3
|
+
readonly waitMs: number;
|
|
4
|
+
readonly error: string;
|
|
5
|
+
}) => void;
|
|
6
|
+
/** 时钟三面(异步形:`sleep` 返回 Promise)。三面都是形参,判据不靠真时间。`deadline` = 一只可取消的到点信号(窗尾打断挂起的尝试用)。 */
|
|
7
|
+
export interface StoreOpenWaitClock {
|
|
8
|
+
now(): number;
|
|
9
|
+
sleep(ms: number): Promise<void>;
|
|
10
|
+
deadline(ms: number): {
|
|
11
|
+
readonly expired: Promise<void>;
|
|
12
|
+
cancel(): void;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** {@link openWithinWait} 抛出的是不是「到窗仍未返回、且从没有过一次真失败」那一形(调用方据此区分「没有证据」与「有一次确定的失败」)。 */
|
|
16
|
+
export declare function isStoreOpenWindowExpired(err: unknown): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* 跑 `open`;失败且窗为正 ⇒ 在窗内每 {@link STORE_LOCK_POLL_MS} 重试。
|
|
19
|
+
*
|
|
20
|
+
* 🔴 **窗尾只有一条规则**:到窗时手上有一次**完成了的**真失败 ⇒ **原样**抛最近那一次(同一个对象,不重铸 —— 库的拒句读驱动原文,桶的拒句读
|
|
21
|
+
* 「缺桶」那只错);**从头到尾一次真失败都没有**(每次尝试都挂着)⇒ 抛 {@link StoreOpenWindowExpired}(「到窗仍未返回」,{@link isStoreOpenWindowExpired} 判别)。
|
|
22
|
+
* · 窗从**第一次尝试开始**算,每次尝试与**剩余**窗赛跑 —— 挂起的尝试到窗尾也被打断(库接受了 TCP 却永不回握手、DDL 被长期锁住);被打断的那次尝试
|
|
23
|
+
* 不再有人等它:它迟到的结局被吞掉(没人接 = 未处理拒绝会杀进程),它手上的资源由调用方的 `open` 自己收(见 `openStoreBackendWithFallback` 的在飞登记)。
|
|
24
|
+
* · 剩余窗 ≤ 0 **不再起新一次尝试**:一次失败之后的等待吃完了窗,就抛那次失败 —— 不起一次注定跟 0 ms 的到点赛跑、又几乎总是输的尝试
|
|
25
|
+
* (那一形会把一次确定的失败换成「到窗仍未返回」:口令错读成库挂住、缺桶读成没有证据)。
|
|
26
|
+
* 窗为 0 ⇒ **只试一次、不设界**(与 7.118.x 同形:一次尝试挂多久由驱动自己的超时决定 —— 本腿不替它造一个)。
|
|
27
|
+
*
|
|
28
|
+
* @param open 一次完整尝试(造后端 + `ensureSchema()` / 一次桶探针);**失败时必须自己把这次造出的池还回去**,窗内会被再调用。
|
|
29
|
+
* @param waitMs `config.storeLockWaitMs`。`> 0` 才等;`0` / 非数 ⇒ 只试一次(`!(waitMs > 0)` 同时盖住「键缺席」的手搓 config)。
|
|
30
|
+
* @param onWait 进入等待腿的一次性披露口(第一次完成的失败时调一次)。
|
|
31
|
+
*/
|
|
32
|
+
export declare function openWithinWait<T>(open: () => Promise<T>, waitMs: number, onWait: StoreOpenWaitSink, clock?: StoreOpenWaitClock): Promise<T>;
|
|
33
|
+
//# sourceMappingURL=store-open-wait.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { STORE_LOCK_POLL_MS } from "./store-lock-wait.js";
|
|
2
|
+
const REAL_CLOCK = {
|
|
3
|
+
now: () => performance.now(),
|
|
4
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
5
|
+
deadline: (ms) => {
|
|
6
|
+
let timer;
|
|
7
|
+
const expired = new Promise((resolve) => {
|
|
8
|
+
timer = setTimeout(resolve, ms);
|
|
9
|
+
});
|
|
10
|
+
return { expired, cancel: () => clearTimeout(timer) };
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
class StoreOpenWindowExpired extends Error {
|
|
14
|
+
constructor(waitMs) {
|
|
15
|
+
super(`the open attempt was still pending when the ${waitMs}ms window ended`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function isStoreOpenWindowExpired(err) {
|
|
19
|
+
return err instanceof StoreOpenWindowExpired;
|
|
20
|
+
}
|
|
21
|
+
export async function openWithinWait(open, waitMs, onWait, clock = REAL_CLOCK) {
|
|
22
|
+
if (!(waitMs > 0))
|
|
23
|
+
return await open();
|
|
24
|
+
const startedAt = clock.now();
|
|
25
|
+
let lastFailure;
|
|
26
|
+
for (;;) {
|
|
27
|
+
const remainingMs = waitMs - (clock.now() - startedAt);
|
|
28
|
+
if (lastFailure !== undefined && remainingMs <= 0)
|
|
29
|
+
throw lastFailure.error;
|
|
30
|
+
const deadline = clock.deadline(Math.max(remainingMs, 0));
|
|
31
|
+
let outcome;
|
|
32
|
+
try {
|
|
33
|
+
outcome = await Promise.race([
|
|
34
|
+
attemptOnce(open).then((value) => ({ kind: "opened", value }), (error) => ({ kind: "failed", error })),
|
|
35
|
+
deadline.expired.then(() => ({ kind: "window_ended" })),
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
deadline.cancel();
|
|
40
|
+
}
|
|
41
|
+
switch (outcome.kind) {
|
|
42
|
+
case "opened":
|
|
43
|
+
return outcome.value;
|
|
44
|
+
case "window_ended":
|
|
45
|
+
if (lastFailure !== undefined)
|
|
46
|
+
throw lastFailure.error;
|
|
47
|
+
throw new StoreOpenWindowExpired(waitMs);
|
|
48
|
+
case "failed": {
|
|
49
|
+
if (lastFailure === undefined)
|
|
50
|
+
onWait({ waitMs, error: outcome.error instanceof Error ? outcome.error.message : String(outcome.error) });
|
|
51
|
+
lastFailure = { error: outcome.error };
|
|
52
|
+
const left = waitMs - (clock.now() - startedAt);
|
|
53
|
+
if (left <= 0)
|
|
54
|
+
throw outcome.error;
|
|
55
|
+
await clock.sleep(Math.min(STORE_LOCK_POLL_MS, left));
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
default: {
|
|
59
|
+
const unhandled = outcome;
|
|
60
|
+
throw new Error(`unhandled open attempt outcome: ${JSON.stringify(unhandled)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function attemptOnce(open) {
|
|
66
|
+
try {
|
|
67
|
+
return open();
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return Promise.reject(err);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=store-open-wait.js.map
|
package/dist/rule-entry-list.js
CHANGED
|
@@ -50,7 +50,7 @@ export function inertRuleEntryGuidance(i) {
|
|
|
50
50
|
case "content_unsupported":
|
|
51
51
|
return "this tool takes no content form here — name the tool itself, or use Bash(<command>) / Bash(<prefix>:*) for a shell command";
|
|
52
52
|
case "grammar":
|
|
53
|
-
return "the entry is not read as written (surrounding whitespace, an empty or unbalanced tool name) — write the tool name exactly, with no padding";
|
|
53
|
+
return "the entry is not read as written (surrounding whitespace, an empty or unbalanced tool name, or an invisible or control character in the tool name) — write the tool name exactly, with no padding";
|
|
54
54
|
default: {
|
|
55
55
|
const unhandled = reason;
|
|
56
56
|
throw new Error(`unhandled allow-entry inert reason: ${String(unhandled)}`);
|
package/dist/run-local.js
CHANGED
|
@@ -33,7 +33,7 @@ import { createInstructionSourceSeat } from "./instruction-source-notice.js";
|
|
|
33
33
|
import { loadDeploymentSkills } from "./capabilities/skills.js";
|
|
34
34
|
import { createRepoClient } from "./capabilities/repo-tools.js";
|
|
35
35
|
import { webSearchConfigFromEnv, createWebSearchBackend } from "./plugins/web-search.js";
|
|
36
|
-
import { buildScenarios, selectScenario, centerScenarios } from "./capabilities/scenarios.js";
|
|
36
|
+
import { buildScenarios, gateScenarioRequest, selectScenario, centerScenarios } from "./capabilities/scenarios.js";
|
|
37
37
|
import { createTaskListLane } from "./boot/task-list-lane.js";
|
|
38
38
|
import { pickHandsRunner, withoutExecutionEnv } from "./capabilities/hands-lane.js";
|
|
39
39
|
import { HttpError } from "./security.js";
|
|
@@ -139,6 +139,7 @@ export const NO_TTY_ASK_CLASS_ROW = {
|
|
|
139
139
|
content_question: { remedy: "terminal_only", autoBudgetReleases: false },
|
|
140
140
|
hook: { remedy: "terminal_only", autoBudgetReleases: false },
|
|
141
141
|
ancestor: { remedy: "terminal_only", autoBudgetReleases: false },
|
|
142
|
+
dangerous_delete: { remedy: "terminal_only", autoBudgetReleases: false },
|
|
142
143
|
unverifiable_delete: { remedy: "terminal_only", autoBudgetReleases: false },
|
|
143
144
|
transcript_write: { remedy: "terminal_only", autoBudgetReleases: false },
|
|
144
145
|
org_rule: { remedy: "terminal_only", autoBudgetReleases: false },
|
|
@@ -592,11 +593,11 @@ export async function runLocal(argv, deps = {}) {
|
|
|
592
593
|
const { overlay } = centerScenarios(scenarioDeps, effective.scenarios.scenarios, Object.keys(scenarios), logger);
|
|
593
594
|
Object.assign(scenarios, overlay);
|
|
594
595
|
}
|
|
595
|
-
|
|
596
|
-
const scenarioName = scenarios[requested] ? requested : "default";
|
|
596
|
+
let scenarioName = args.scenario ?? config.defaultScenario;
|
|
597
597
|
const sessionId = args.session ?? uuidv7();
|
|
598
598
|
let cap;
|
|
599
599
|
try {
|
|
600
|
+
scenarioName = gateScenarioRequest(undefined, args.scenario, config.defaultScenario, Object.keys(scenarios), logger);
|
|
600
601
|
cap = selectScenario(scenarios, scenarioName)({ scenario: scenarioName, objective: args.objective }, undefined, { sessionId });
|
|
601
602
|
}
|
|
602
603
|
catch (err) {
|
package/dist/security.d.ts
CHANGED
|
@@ -72,9 +72,11 @@ export declare const LEGACY_UNOWNED_AMNESTY_TENANT = "default";
|
|
|
72
72
|
* · `runOwnerOk` 三读面(head / audit 单读 / events):`owner === null` 那一支**本来就放行**
|
|
73
73
|
* (判据是 `owner !== null && principal !== owner`),不需要本臂。[ref] 帖文里「单读 404」一句
|
|
74
74
|
* 经亲读实现行**不成立**,已加现状钉(session-head-probe.test.ts)锁住这个事实。
|
|
75
|
-
* · `PUT /v1/sessions/:id/policy` 的 `owner === null ⇒ 409 conflict.session_ownerless`:那是**有意的
|
|
76
|
-
* fail-closed** ——
|
|
77
|
-
*
|
|
75
|
+
* · `GET|PUT /v1/sessions/:id/policy` 的 `owner === null ⇒ 409 conflict.session_ownerless`:那是**有意的
|
|
76
|
+
* fail-closed** —— 规则按「会话 × principal」存、按 run 的 principal 读,而规则路由的存储键就是会话的属主;无主会话没有
|
|
77
|
+
* 这把键,替它读写规则只能**猜**一个 principal(猜错 = 200 说已生效,其实没有哪条 run 读它)。已经存下的规则不受影响:
|
|
78
|
+
* 它们存在当初那个属主的键下,那个 principal 的 run 照读照生效(7.119.0 前 local 后端重启即把属主忘掉,于是那些规则
|
|
79
|
+
* 在跑、却读不到也删不掉 —— 修在属主落盘,不在放宽本门)。放宽它等于制造一条 fail-open,不在本臂辖域。
|
|
78
80
|
* · `…/sync/*`(PULL/PUSH/import):PULL 虽是只读、形同 workspace,但这一族**带一条破坏性 import 腿**
|
|
79
81
|
* (`isDestructiveSessionWrite` 明列),而 [ref]/[ref] 裁的是会话**读面与生命周期**四门。放宽一个
|
|
80
82
|
* 写面族超出裁定射程 —— 要做另立一次表态,不在本臂里顺手带过。
|
package/dist/security.js
CHANGED
|
@@ -75,6 +75,10 @@ export function createAuthorizer(config, sessionStore) {
|
|
|
75
75
|
throw new HttpError(403, "cannot claim a caller-supplied sessionId on a multi-tenant deployment: this worker's session store cannot record session ownership, so the id would stay unowned and the next caller could attach to it. Configure a durable session backend (SESSION_BACKEND), or omit sessionId to start a new session.");
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
else if (owner === null && principal !== undefined && !config.requirePrincipal && register) {
|
|
79
|
+
await register(requested, principal);
|
|
80
|
+
owner = await ownerOf(requested);
|
|
81
|
+
}
|
|
78
82
|
const legacyShellOwner = !config.requirePrincipal && owner === "anon:shell-live" && principal === undefined;
|
|
79
83
|
if (!legacyShellOwner && owner != null && owner !== principal) {
|
|
80
84
|
throw principal
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { PROTOCOL_TABLE, SessionPolicyError, assertSessionRuleNamesCurrent
|
|
2
|
-
import { describeEmptyToolSegment, emptyToolSegmentOf
|
|
1
|
+
import { PROTOCOL_TABLE, SessionPolicyError, assertSessionRuleNamesCurrent } from "@sema-agent/core";
|
|
2
|
+
import { describeEmptyToolSegment, emptyToolSegmentOf } from "./namespaced-rule-name.js";
|
|
3
3
|
const RULE_FIELDS = {
|
|
4
4
|
toolAllow: true,
|
|
5
5
|
toolDeny: true,
|
|
@@ -102,19 +102,14 @@ function describeUnreachableSessionRuleName(u) {
|
|
|
102
102
|
return `${u.field} names "${u.name}", which can never match a tool (${describeEmptyToolSegment(u.ns)}), so nothing was written`;
|
|
103
103
|
}
|
|
104
104
|
function describeLegacyToolName(r) {
|
|
105
|
+
const why = r.guidance !== undefined ? ` (${r.guidance})` : "";
|
|
105
106
|
switch (r.verdict) {
|
|
106
|
-
case "retired":
|
|
107
|
-
|
|
108
|
-
const guidance = v.kind === "retired" ? v.guidance : `renamed "${r.currentName ?? "?"}"`;
|
|
109
|
-
return `${r.field} names "${r.name}", a retired tool name (${guidance}); with that rule in place this session could not start another run, so nothing was written — use the current tool name${r.currentName !== undefined ? ` ("${r.currentName}")` : ""}`;
|
|
110
|
-
}
|
|
107
|
+
case "retired":
|
|
108
|
+
return `${r.field} names "${r.name}", a retired tool name${why}; with that rule in place this session could not start another run, so nothing was written — use the current tool name${r.currentName !== undefined ? ` ("${r.currentName}")` : ""}`;
|
|
111
109
|
case "unprefixed_namespace":
|
|
112
110
|
return `${r.field} names "${r.name}", which contains "__" but no protocol prefix; with that rule in place this session could not start another run, so nothing was written — a protocol tool is named ${protocolToolNameShapes()}`;
|
|
113
|
-
case "unmintable_segment":
|
|
114
|
-
|
|
115
|
-
const guidance = v.kind === "unmintable_segment" ? v.guidance : "a protocol name whose peer segment the engine never mints";
|
|
116
|
-
return `${r.field} names "${r.name}", which can never match a tool (${guidance}), so nothing was written`;
|
|
117
|
-
}
|
|
111
|
+
case "unmintable_segment":
|
|
112
|
+
return `${r.field} names "${r.name}", which can never match a tool${why}, so nothing was written`;
|
|
118
113
|
default: {
|
|
119
114
|
const unhandled = r.verdict;
|
|
120
115
|
throw new Error(`unhandled legacy tool name verdict: ${JSON.stringify(unhandled)}`);
|
package/dist/spec-fields.d.ts
CHANGED
|
@@ -65,8 +65,12 @@ export declare const skillToolListOk: (v: unknown) => v is readonly string[];
|
|
|
65
65
|
export declare function skillToolListRefusal(skill: Record<string, unknown>): string | undefined;
|
|
66
66
|
/** [ref] 单条 agent 定义的形状裁决:null=合法;否则一句可 400 的英文错误(不带索引,调用方自己加)。
|
|
67
67
|
* 白名单外键 fail-loud —— 与 resilience/limits 的「容忍未知键」相反:agents 是能力定义,静默丢字段 =
|
|
68
|
-
* 定义被悄悄削(一个丢掉的 isolation 就是放宽),比 400
|
|
69
|
-
* core
|
|
68
|
+
* 定义被悄悄削(一个丢掉的 isolation 就是放宽),比 400 更危险,故整条拒。`allowTools` / `denyTools` 本门只判形与条数(`toolList`),
|
|
69
|
+
* **名字归引擎一只判**(core 7.45.0 #1377 / #1385,开跑前,三来源同判):没有工具能是的拼法(带空白 / 括号形坏 / 不可见字符)、非字符串条目 ⇒ 整 run 拒
|
|
70
|
+
* `config.agent.unknown_tool`;无人挂的名 ⇒ 不拒、经 `onError`(phase `"config"`)告一次(本仓汇点 `runner_config_advisory`);大小写孪生 / 退役名(`bash`)
|
|
71
|
+
* **在本仓部署形上也只告不拒** —— 主车道的委派工具带 extraTools 工厂(`capabilities/builtin-tools.ts`),引擎开跑前列不全子代工具池(`agentToolPool` 缺席)就不把
|
|
72
|
+
* 像 `Bash` 的名字判成拼错;引擎给宿主「声明可注入名册」的座后本仓声明名册、这两形回到开跑前拒;
|
|
73
|
+
* `denyTools` 的 `Tool(content)` ⇒ 整只工具对该子代藏起并告。本仓不在受理口另做名字对照(那会是同一问的第二个判官)。
|
|
70
74
|
* ⚠️ 键判断必须 Object.hasOwn:`in` 走原型链,JSON 里的 "toString"/"constructor"/"__proto__" 键会被
|
|
71
75
|
* 误判为白名单字段,随后 SHAPES[k].ok 不是函数 ⇒ TypeError(fresh 500 / resume 砖;cross-review 复现)。 */
|
|
72
76
|
export declare function taskAgentIssue(item: unknown): string | null;
|
package/dist/store-posture.d.ts
CHANGED
|
@@ -8,24 +8,32 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ## 形
|
|
10
10
|
* `declared` = 运维的声明(`DB_BACKEND` 词,含缺省推导);`backend` = **装配出的**后端 kind(没装出后端 = `memory`);两者不等是合法的
|
|
11
|
-
* 形(`DB_BACKEND=pg` + `SESSION_BACKEND=memory` = 声明 pg
|
|
11
|
+
* 形(`DB_BACKEND=pg` + `SESSION_BACKEND=memory` = 声明 pg、刻意不持久;缺省推导 local 起不来)—— 两位都上 wire,
|
|
12
12
|
* 由调用方自己读出落差。`degradedReason` present-iff 降级臂走过(按后端族定词,{@link storeDegradedReasonFor})。
|
|
13
13
|
* 顶层 `sql` 并入 `store.sql`(硬 breaking,零别名;对象逐字同形)。
|
|
14
14
|
*/
|
|
15
15
|
import type { StoreBackend as StoreBackendWord } from "@sema-agent/settings-schema/execution-lanes";
|
|
16
|
+
import type { ServiceConfig } from "./config-types.js";
|
|
16
17
|
import type { StoreBackend } from "./plugins/store-backend.js";
|
|
17
18
|
import type { SqlEngineFacts } from "./plugins/sql-driver.js";
|
|
18
19
|
import { type SqlEngineCapability } from "./sql-engine-posture.js";
|
|
19
20
|
/**
|
|
20
|
-
* 降级词闭集(**按后端族**,不按失败发生在哪一步
|
|
21
|
-
* 按调用位置分不出「连不上」与「建表失败」):
|
|
22
|
-
* · `db_unreachable` —— SQL 族(`mysql` / `pg`)在 `SESSION_BACKEND=auto` 下打不开(连接或建表);
|
|
21
|
+
* 降级词闭集(**按后端族**,不按失败发生在哪一步):
|
|
23
22
|
* · `local_unwritable` —— **缺省推导**的 local 文件店构造期失败(mkdir / 数据根锁 / 权限 / 只读挂载 / 磁盘满)。
|
|
23
|
+
*
|
|
24
|
+
* 🔴 SQL 族**没有**降级词:配了 DSN 的库当下打不开 ⇒ 窗内重试、到窗拒启(`plugins/store-open-wait.ts` / `openStoreBackendWithFallback`),
|
|
25
|
+
* 从来不退成内存 —— 修前这里有第二个词 `db_unreachable`(`SESSION_BACKEND=auto` 下 SQL 族打不开 ⇒ 内存 + 本位置位),那是 [ref] P 类的静默 fail-open,随降级臂一并退役。
|
|
24
26
|
*/
|
|
25
|
-
export declare const STORE_DEGRADED_REASONS: readonly ["
|
|
27
|
+
export declare const STORE_DEGRADED_REASONS: readonly ["local_unwritable"];
|
|
26
28
|
export type StoreDegradedReason = (typeof STORE_DEGRADED_REASONS)[number];
|
|
27
|
-
/** 降级臂按后端族定词(穷举 switch:词表加员 ⇒ 编译红)
|
|
29
|
+
/** 降级臂按后端族定词(穷举 switch:词表加员 ⇒ 编译红)。只有缺省推导的 `local` 走得到降级臂;`mysql` / `pg` / `memory` 走到这里 = 装配缺陷,响亮抛。 */
|
|
28
30
|
export declare function storeDegradedReasonFor(declared: StoreBackendWord): StoreDegradedReason;
|
|
31
|
+
/**
|
|
32
|
+
* `/health.sessionBackend` 与 `listening` 日志 `session` 位的**唯一读法**:装配后的 `config.sessionBackend`(`memory` | `tidb`;`tidb` = 「后端自己的 durable 店」的泛指标签)
|
|
33
|
+
* + 装配出的后端 kind ⇒ 对运维说人话的有效店 —— `durable(pg)` / `durable(mysql)` / `durable(local)` / `memory`。`sessionBackend` 缺 backend 的 `tidb`
|
|
34
|
+
* 不替它编一个后端名(那是装配缺陷,`createSessionStore` 会先拒)。部署侧的 smoke(`deploy/sema-up/lib/health.sh`)按这一形读同一位。
|
|
35
|
+
*/
|
|
36
|
+
export declare function sessionStoreLabelOf(sessionBackend: ServiceConfig["sessionBackend"], backendKind: StoreBackend["kind"] | undefined): string;
|
|
29
37
|
/** 装配值(纯数据;`boot/stores.ts` 铸一次,gauge / `/health` / caps 三面同读)。 */
|
|
30
38
|
export interface StorePosture {
|
|
31
39
|
readonly declared: StoreBackendWord;
|
package/dist/store-posture.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { projectSqlEngineCapability } from "./sql-engine-posture.js";
|
|
2
|
-
export const STORE_DEGRADED_REASONS = ["
|
|
2
|
+
export const STORE_DEGRADED_REASONS = ["local_unwritable"];
|
|
3
3
|
export function storeDegradedReasonFor(declared) {
|
|
4
4
|
switch (declared) {
|
|
5
|
-
case "mysql":
|
|
6
|
-
case "pg":
|
|
7
|
-
return "db_unreachable";
|
|
8
5
|
case "local":
|
|
9
6
|
return "local_unwritable";
|
|
7
|
+
case "mysql":
|
|
8
|
+
case "pg":
|
|
9
|
+
throw new Error(`store posture: DB_BACKEND=${declared} never degrades to memory (a configured SQL store is retried then refused, never replaced) — this is a wiring defect`);
|
|
10
10
|
case "memory":
|
|
11
11
|
throw new Error("store posture: DB_BACKEND=memory never opens a backend, so it cannot degrade — this is a wiring defect");
|
|
12
12
|
default: {
|
|
@@ -15,6 +15,9 @@ export function storeDegradedReasonFor(declared) {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
export function sessionStoreLabelOf(sessionBackend, backendKind) {
|
|
19
|
+
return sessionBackend === "tidb" && backendKind !== undefined ? `durable(${backendKind})` : sessionBackend;
|
|
20
|
+
}
|
|
18
21
|
export function buildStorePosture(a) {
|
|
19
22
|
return { declared: a.declared, backend: a.assembled ?? "memory", ...(a.degradedReason !== undefined ? { degradedReason: a.degradedReason } : {}) };
|
|
20
23
|
}
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
* `APPROVAL_REQUIRE` (an ask rule), `APPROVAL_NEVER_AUTO` (a person, mandated), `MANUAL_MODE_SHELL_GATE=always` (shell).
|
|
28
28
|
* A request-carried skill's `allowedTools` is the same member's twin (same grammar, same predicate, same admission judge
|
|
29
29
|
* `rule-entry-list.ts`). Pinned on the real assembly: `test/allow-layer-operator-knobs.test.ts`.
|
|
30
|
-
* - permissionMode(轴B #2 注释保鲜,1.254——[ref]/[ref] 后的真实现):`defaultMode`
|
|
31
|
-
* (default | acceptEdits | plan | bypassPermissions |
|
|
30
|
+
* - permissionMode(轴B #2 注释保鲜,1.254——[ref]/[ref] 后的真实现):`defaultMode` 收 core `PERMISSION_MODES` **全集**
|
|
31
|
+
* (default | acceptEdits | plan | auto | bypassPermissions | dontAsk),bypass/auto 被 HONOR 为 gate-shape 选择
|
|
32
32
|
* ——但深层不变量未变:bypass=「不加 ask 门」,恒不越部署基线(tightenTaskSpec deny-wins,基线 deny/
|
|
33
33
|
* ask 照压);精化语义见 :2xx 行内注释。旧头注的「NEVER honored/三模式白名单」是 pre-[ref] 拍照。
|
|
34
34
|
*
|
|
@@ -51,60 +51,32 @@ import { type ExecutionEnv, type TaskSpec, type ThinkingLevel, type ToolPolicy,
|
|
|
51
51
|
import type { HooksConfig } from "@sema-agent/settings-schema/hooks";
|
|
52
52
|
import { type KeyClosureIssue } from "./request-key-closure.js";
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* 今天的表态(与 7.106.0 逐字同行为):五词受理;`dontAsk`(CC 的「自动拒」档,core 7.34.0 起第六词)**排除** —— 它上不上请求面是一件
|
|
62
|
-
* 新能力(未裁,本表不替它裁);不收它时提交面 400 `request.field_invalid`,续跑重放腿按 {@link coercePermissionMode} 的既有宽读折 `default`。
|
|
63
|
-
* 语义沿革(为什么是这五个、`bypassPermissions` / `auto` 何时进来):[ref] / [ref] / [ref] —— CC 自己的全集是六词,CC 托管 / 远端形收四词
|
|
64
|
-
* (= 本仓 − `bypassPermissions`),本仓 = {CC 远端四词} ∪ {`bypassPermissions`},登记过的偏离;生效模式原样过境 `TaskSpec.permissionMode`(S-877)。
|
|
54
|
+
* 请求面 `permissionMode` 的闭集 = core `PERMISSION_MODES` **全集**(顶层 `permissionMode` / `settings.permissions.defaultMode` /
|
|
55
|
+
* `?permissionMode=` 查询串三处同读)。本仓不持有第二份词表,也不持有「收 / 不收」的表态:判据 = core 的 `isPermissionMode`,型 = core 的
|
|
56
|
+
* `PermissionMode`,wire 句子里的词表只从这里取词({@link PERMISSION_MODE_WORDS})。core 加一个词 ⇒ 请求面自动收它,它在派生腿上的去向由
|
|
57
|
+
* {@link deriveSettingsPolicy} 的穷举 `switch` 逼出来(缺一臂 ⇒ 编译红);core 删 / 改名一个词 ⇒ 本文件里按名写出它的那一臂编译红。
|
|
58
|
+
* 生效模式原样过境 `TaskSpec.permissionMode`(S-877):引擎自己解释模式 —— 含 `dontAsk`(CC 的「自动拒」档:凡是要问的一律由模式拒,零卡,
|
|
59
|
+
* 门记录 `deniedBy:"mode"`),本仓不另写判。
|
|
65
60
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
readonly acceptEdits: "request";
|
|
69
|
-
readonly plan: "request";
|
|
70
|
-
readonly auto: "request";
|
|
71
|
-
readonly bypassPermissions: "request";
|
|
72
|
-
readonly dontAsk: "excluded";
|
|
73
|
-
};
|
|
74
|
-
/** 请求面受理的模式词的型形 —— 从表态表派生(`"request"` 那几行的键)。 */
|
|
75
|
-
export type SettingsPermissionMode = {
|
|
76
|
-
[M in PermissionMode]: (typeof PERMISSION_MODE_REQUEST_FACE)[M] extends "request" ? M : never;
|
|
77
|
-
}[PermissionMode];
|
|
78
|
-
/** 请求面受理的模式词(core 序)= core `PERMISSION_MODES` 过滤表态 `"request"`。wire 门、coerce 两族、两句 400 文案同读这一份。 */
|
|
79
|
-
export declare const SETTINGS_PERMISSION_MODES: readonly SettingsPermissionMode[];
|
|
80
|
-
/** 显式排除集(core 序)= core `PERMISSION_MODES` 过滤表态 `"excluded"`;今天恰是 `["dontAsk"]`。只作披露 / 钉读,不参与判定。 */
|
|
81
|
-
export declare const SETTINGS_PERMISSION_MODES_EXCLUDED: readonly Exclude<PermissionMode, SettingsPermissionMode>[];
|
|
82
|
-
/** 五词表的 wire 文案形(`"a" | "b" | …`)—— 两条 400 的句子从这里取词,不再各抄一份。 */
|
|
83
|
-
export declare const SETTINGS_PERMISSION_MODE_WORDS: string;
|
|
84
|
-
/** 闭集成员判定(`unknown` 入口用)—— 词表的**唯一**运行期读者:wire 门与 coerce 两族同调。 */
|
|
85
|
-
export declare function isSettingsPermissionMode(raw: unknown): raw is SettingsPermissionMode;
|
|
61
|
+
/** 请求面闭集的 wire 文案形(`"a" | "b" | …`,core 序)—— 三处 400(顶层 / bundle / 查询串)的句子从这里取词,不再各抄一份。 */
|
|
62
|
+
export declare const PERMISSION_MODE_WORDS: string;
|
|
86
63
|
/**
|
|
87
|
-
* R4: coerce a raw top-level `body.permissionMode` (the LIGHT per-turn wire — the shell sends
|
|
88
|
-
* CC permission mode RAW, axis-agnostic; the service interprets) to a service-honored mode, or undefined.
|
|
64
|
+
* R4: coerce a raw top-level `body.permissionMode` (the LIGHT per-turn wire — the shell sends
|
|
65
|
+
* the CC permission mode RAW, axis-agnostic; the service interprets) to a service-honored mode, or undefined.
|
|
89
66
|
* 🔴 TIGHTEN-ONLY vs the DEPLOYMENT baseline still holds for every value: the mode only selects the CLIENT-derived
|
|
90
67
|
* gate ({@link deriveSettingsPolicy}); it can never subtract from the deployment/operator policy (applyTaskSettings
|
|
91
|
-
* folds via tightenTaskSpec, deny-wins).
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* coercion would have been a behavior change for them, incl. a headless-deny regression for bypass callers). An
|
|
97
|
-
* UNKNOWN string still coerces to `default` (fail-safe: the most-asking mode).
|
|
98
|
-
* [ref]-②: fresh submits can no longer REACH the unknown-word arm — the HTTP gate 400s a non-five-mode value
|
|
99
|
-
* (server.ts, sibling of promptProfile) and the wire type is a closed enum. This lenient fold stays for the one
|
|
68
|
+
* folds via tightenTaskSpec, deny-wins). The honored set is core's `PERMISSION_MODES` in full — every word passes
|
|
69
|
+
* through verbatim (`dontAsk` included: folding the auto-deny mode to `default`, the asking mode, would turn a refusal
|
|
70
|
+
* into a card). An UNKNOWN string coerces to `default` (fail-safe: the most-asking mode).
|
|
71
|
+
* [ref]-②: fresh submits can no longer REACH the unknown-word arm — the HTTP gate 400s a non-member value
|
|
72
|
+
* (admission.ts, sibling of promptProfile) and the wire type is a closed enum. This lenient fold stays for the one
|
|
100
73
|
* population the gate deliberately does not cover: RESUME replay of a persisted pre-gate body (outputStyle/model
|
|
101
74
|
* two-layer posture — bricking a resume over a value that was legal at submit time is worse than the quiet fold).
|
|
102
75
|
*/
|
|
103
|
-
export declare function coercePermissionMode(raw: unknown):
|
|
104
|
-
/**
|
|
105
|
-
*
|
|
106
|
-
|
|
107
|
-
export declare function coercePermissionModeStrict(raw: unknown): SettingsPermissionMode | undefined;
|
|
76
|
+
export declare function coercePermissionMode(raw: unknown): PermissionMode | undefined;
|
|
77
|
+
/** 闭集的**严格**读法(`/v1/capabilities?permissionMode=` 查询串用它 —— 读面对词表外的值 400,不走上面那条给 resume 重放留的 `default` 宽臂)。
|
|
78
|
+
* 上面的宽读只是本函数外再包一层折叠;词表只有 core 的 `PERMISSION_MODES`(判据 `isPermissionMode`)。 */
|
|
79
|
+
export declare function coercePermissionModeStrict(raw: unknown): PermissionMode | undefined;
|
|
108
80
|
/**
|
|
109
81
|
* S-433 —— 一份**被批准的计划**之后,这条会话继续用的 permission mode(`plan_review` 决裁体的
|
|
110
82
|
* `permissionModeAfter`)。core 的契约句是 `present-plan-tool.js`:「If the plan is approved, the run
|
|
@@ -116,8 +88,8 @@ export declare function coercePermissionModeStrict(raw: unknown): SettingsPermis
|
|
|
116
88
|
* edits」/「Yes, and manually approve edits」)的对位;`plan` 自己(= 不松开)由 `edit`/`reject` 两个
|
|
117
89
|
* 决裁表达,`bypassPermissions` / `auto` 不在这张卡上 —— 一个批准动作不该顺带把部署的 ask 面整个关掉。
|
|
118
90
|
*
|
|
119
|
-
* 🔴 **不手抄词表**:`satisfies readonly
|
|
120
|
-
*
|
|
91
|
+
* 🔴 **不手抄词表**:`satisfies readonly PermissionMode[]` 让「本表的成员必须仍是 core 的模式词」成为**编译期**事实 ——
|
|
92
|
+
* core 某天改名/删词,这里就是红的,而不是静默留一个没人认的词。
|
|
121
93
|
*/
|
|
122
94
|
export declare const PLAN_APPROVE_PERMISSION_MODES_AFTER: readonly ["default", "acceptEdits"];
|
|
123
95
|
/** {@link PLAN_APPROVE_PERMISSION_MODES_AFTER} 的类型形(值表单点,类型从值派生 —— 两处各写一份就会漂)。 */
|
|
@@ -139,7 +111,7 @@ export declare function isPlanApprovePermissionModeAfter(raw: unknown): raw is P
|
|
|
139
111
|
export declare function storedBodyPermissionMode(body: {
|
|
140
112
|
permissionMode?: unknown;
|
|
141
113
|
settings?: unknown;
|
|
142
|
-
}):
|
|
114
|
+
}): PermissionMode | undefined;
|
|
143
115
|
/** L2 ultracode ([ref]): the effective `thinking` level given the explicit `reasoningEffort` and the ultracode
|
|
144
116
|
* preset. ultracode FLOORS at the "ultra" tier — core's single source `resolveReasoningProfile("ultra").thinking`
|
|
145
117
|
* (= "max"), never a hardcoded literal — so an explicit higher tier still wins (none exists above max today;
|
|
@@ -148,7 +120,7 @@ export declare function effectiveThinking(reasoningEffort: unknown, ultracode: b
|
|
|
148
120
|
/** R4: fold a top-level `body.permissionMode` onto the parsed settings as `permissions.defaultMode` (the top-level
|
|
149
121
|
* field is the explicit per-turn intent → it WINS over a bundle defaultMode). Creates a minimal settings object when
|
|
150
122
|
* no `body.settings` bundle was sent. The result flows through {@link applyTaskSettings} (tighten-only). */
|
|
151
|
-
export declare function withPermissionMode(settings: ParsedTaskSettings | undefined, mode:
|
|
123
|
+
export declare function withPermissionMode(settings: ParsedTaskSettings | undefined, mode: PermissionMode): ParsedTaskSettings;
|
|
152
124
|
/**
|
|
153
125
|
* [ref] §6-1 —— 本请求的**生效** permission mode,单点。优先序逐字同 {@link withPermissionMode}:
|
|
154
126
|
* 顶层 `body.permissionMode`(本轮显式表态)赢过 bundle 的 `settings.permissions.defaultMode`;两处
|
|
@@ -161,7 +133,7 @@ export declare function withPermissionMode(settings: ParsedTaskSettings | undefi
|
|
|
161
133
|
*/
|
|
162
134
|
export declare function effectivePermissionMode(body: {
|
|
163
135
|
permissionMode?: unknown;
|
|
164
|
-
}, settings: ParsedTaskSettings | undefined):
|
|
136
|
+
}, settings: ParsedTaskSettings | undefined): PermissionMode | undefined;
|
|
165
137
|
/**
|
|
166
138
|
* S-346 —— [ref] 技能清单预算两旋钮的**值域谓词**(一对具名单铸点)。
|
|
167
139
|
*
|
|
@@ -187,7 +159,7 @@ export interface ParsedTaskSettings {
|
|
|
187
159
|
deny?: string[];
|
|
188
160
|
/** Carried for completeness; v1 folds `ask` into the gate via the deny-wins base (see {@link applyTaskSettings}). */
|
|
189
161
|
ask?: string[];
|
|
190
|
-
defaultMode?:
|
|
162
|
+
defaultMode?: PermissionMode;
|
|
191
163
|
/** [ref] 件⑥([ref] §3.4,[ref];PM 裁 [ref]):CC settings 的 kill-switch `permissions.disableAutoMode`,cli 投影
|
|
192
164
|
* (settingsRulesWire.ts toWireSettings)而此前本白名单丢弃 ⇒ 用户/managed 层关 auto 到不了引擎。语义 = 只能关
|
|
193
165
|
* 不能开(tighten-only):把生效模式 auto 折 default(座不写 ⇒ 不武装)。折叠单点 = {@link effectivePermissionMode}。
|