@sema-agent/server 7.11.0 → 7.12.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/USAGE.md +56 -0
- package/dist/auth-keys.d.ts +28 -4
- package/dist/auth-keys.js +60 -15
- package/dist/boot/parked-revive-gate.d.ts +18 -2
- package/dist/boot/parked-revive-gate.js +136 -14
- package/dist/boot/permission-rules-audit.d.ts +49 -0
- package/dist/boot/permission-rules-audit.js +57 -0
- package/dist/boot/resolve-spec.js +43 -12
- package/dist/budget.js +22 -0
- package/dist/config-types.d.ts +17 -9
- package/dist/config.d.ts +28 -2
- package/dist/config.js +348 -79
- package/dist/governance-ask-marks.js +8 -2
- package/dist/http/route-ctx.d.ts +6 -3
- package/dist/http/routes/approvals-assistant.js +2 -1
- package/dist/http/routes/capabilities.js +44 -9
- package/dist/http/routes/rules.d.ts +19 -7
- package/dist/http/routes/rules.js +180 -4
- package/dist/http/server.d.ts +4 -2
- package/dist/http/server.js +81 -3
- package/dist/http/wire-types.d.ts +48 -0
- package/dist/main.js +19 -1
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/observability/metrics.js +2 -1
- package/dist/observability/tool-trace.d.ts +5 -1
- package/dist/observability/tool-trace.js +33 -6
- package/dist/parked-decide.d.ts +13 -3
- package/dist/parked-decide.js +10 -1
- package/dist/plugins/permission-rule-store-file.d.ts +83 -0
- package/dist/plugins/permission-rule-store-file.js +371 -0
- package/dist/plugins/permission-rule-store-sql.d.ts +7 -0
- package/dist/plugins/permission-rule-store-sql.js +11 -0
- package/dist/plugins/store-backend.d.ts +12 -6
- package/dist/plugins/store-backend.js +58 -9
- package/dist/rules-consent.d.ts +69 -1
- package/dist/rules-consent.js +43 -1
- package/dist/run-local.js +120 -13
- package/dist/runtime-governance.js +9 -3
- package/dist/task-settings.d.ts +44 -0
- package/dist/task-settings.js +57 -1
- package/dist/tool-approval.d.ts +6 -1
- package/dist/tool-approval.js +106 -27
- package/dist/trace/core-keyset-guard.d.ts +13 -2
- package/dist/trace/project.d.ts +19 -2
- package/dist/trace/project.js +24 -4
- package/package.json +2 -2
package/dist/rules-consent.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ImportPreview, type ImportResult, type ImportedSettingsLayer, type RuleScope } from "@sema-agent/core";
|
|
1
|
+
import { type ImportPreview, type ImportResult, type ImportedSettingsLayer, type PersistedAllowRule, type RemoveResult, type RuleScope } from "@sema-agent/core";
|
|
2
2
|
import type { PermissionRuleStoreProvider, RuleApprovalRecordStore } from "@sema-agent/core";
|
|
3
3
|
import { type RuleImportTicket, type RuleTicketRedeemResult } from "./plugins/permission-rule-store-sql.js";
|
|
4
4
|
/**
|
|
@@ -35,6 +35,19 @@ export interface RuleConsentStores {
|
|
|
35
35
|
release(ticketId: string, principal: string, mustRemainValidMs?: number): Promise<boolean>;
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* 一个 backend 交出来的**完整**规则店束 = 同意车道要的三面 + boot 期审计要的窄读口。
|
|
40
|
+
*
|
|
41
|
+
* 🔴 为什么它住在这里而不是某个 `plugins/*-sql.ts`:`StoreBackend.permissionRule()` 现在有**两个**
|
|
42
|
+
* 实现(SQL 双方言 + local File),把返回类型钉成其中任何一个的具体类型都会让另一个只能靠断言挤进去。
|
|
43
|
+
* 结构接口是这条 seam 唯一站得住的形——两个实现各自满足它,消费点(main 装配 / 同意车道 / 撤销面
|
|
44
|
+
* 路由)一个字都不用知道行躺在哪里。
|
|
45
|
+
*/
|
|
46
|
+
export interface PermissionRuleStoreBundle extends RuleConsentStores {
|
|
47
|
+
/** #203 §3:库里已有几只规则桶(一 owner 一桶)。boot 期休眠行审计的**唯一**依赖;
|
|
48
|
+
* 为什么数桶不数规则、以及读失败必须响亮,见 `boot/permission-rules-audit.ts` 与两个实现处的注。 */
|
|
49
|
+
countBuckets(): Promise<number>;
|
|
50
|
+
}
|
|
38
51
|
/** 导入票的存活窗。人从「看到预览」到「按下确认」是一次交互,不是一段会话——十分钟宽到不会误伤,
|
|
39
52
|
* 窄到一张泄漏的票不会常驻。运维旋钮暂不开(没有部署形要求它可调;要开时走 config.ts 的既有姿势)。 */
|
|
40
53
|
export declare const RULE_IMPORT_TICKET_TTL_MS: number;
|
|
@@ -101,6 +114,39 @@ export interface RuleImportLayerInput {
|
|
|
101
114
|
root: string;
|
|
102
115
|
content: string;
|
|
103
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* scope 的 **wire 形**:`global` | `project:<root>` 的判别式串(design/203 v2 §6 F5「显式成文」)。
|
|
119
|
+
*
|
|
120
|
+
* 🔴 为什么是一个串而不是把 core 的 `{kind, root}` 对象直接上 wire:这一份表示要同时当**列举结果里的
|
|
121
|
+
* 一列**、**查询参数**、**删除请求体的一个字段**和**分页游标的一部分**。四处若各用各的形,「删掉我刚才
|
|
122
|
+
* 列出来的那一行」就变成一次跨形状翻译 —— 而那正是最容易漂的地方。判别式串在四处逐字相同,而且
|
|
123
|
+
* `project:` 前缀让「这条规则是项目内的」在一眼扫日志时就成立。
|
|
124
|
+
*
|
|
125
|
+
* `root` 里可以含冒号(路径合法),所以只切**第一个**冒号 —— 用 `split(":")` 取 [1] 会把
|
|
126
|
+
* `project:/a:b` 悄悄截成 `/a`,那是一条**放宽面**上的静默改写(更短的 root 覆盖更多 cwd)。
|
|
127
|
+
*/
|
|
128
|
+
export declare function serializeRuleScope(scope: RuleScope): string;
|
|
129
|
+
/** {@link serializeRuleScope} 的逆。读不出形 ⇒ `undefined`(调用方 400,绝不猜一个 global 出来 ——
|
|
130
|
+
* 猜 global 会把一次「删项目内规则」的请求变成一次删不掉任何东西的 no-op,或者更糟)。 */
|
|
131
|
+
export declare function parseRuleScope(text: string): RuleScope | undefined;
|
|
132
|
+
/** 一条规则在 wire 上的行 = 一个逻辑 (rule, scope) 对(design/203 v2 §6 F5)。
|
|
133
|
+
* `adds` **如实上 wire**、不折叠成单值:同一条规则可以被批准过多次(不同 dot、不同来源、不同时刻),
|
|
134
|
+
* 折成「一个 addedAt」会让「这条规则是我导入的还是我点过卡的」在治理面上不可分。 */
|
|
135
|
+
export interface PersistedRuleWireRow {
|
|
136
|
+
rule: string;
|
|
137
|
+
/** {@link serializeRuleScope} 的判别式串。 */
|
|
138
|
+
scope: string;
|
|
139
|
+
tool: PersistedAllowRule["tool"];
|
|
140
|
+
match: PersistedAllowRule["match"];
|
|
141
|
+
/** 去规范化的命令(core 的匹配器信的就是它 + `match`,不是重新解析 `rule`)。 */
|
|
142
|
+
command: string;
|
|
143
|
+
adds: PersistedAllowRule["adds"];
|
|
144
|
+
}
|
|
145
|
+
/** 列举结果。`rev` = 这只桶的 OCC 版本,游标绑它(见路由侧的游标注)。 */
|
|
146
|
+
export interface PersistedRuleListing {
|
|
147
|
+
rows: PersistedRuleWireRow[];
|
|
148
|
+
rev: number;
|
|
149
|
+
}
|
|
104
150
|
export interface RuleConsentLane {
|
|
105
151
|
/** 卡道兑付口:一次 ask 决议携规则确认 ⇒ prepare→confirm→redeem 一气呵成。 */
|
|
106
152
|
persistCardRule(input: {
|
|
@@ -118,6 +164,28 @@ export interface RuleConsentLane {
|
|
|
118
164
|
prepareImport(principal: string, layers: readonly RuleImportLayerInput[]): Promise<RuleImportPrepared>;
|
|
119
165
|
/** 导入道:原子消费票 → confirm → redeemRuleBatch。 */
|
|
120
166
|
redeemImport(principal: string, ticket: string): Promise<RuleImportRedeemed>;
|
|
167
|
+
/**
|
|
168
|
+
* 撤销面读半场:列出一位 principal 名下**活着**的规则(墓碑已折算)。
|
|
169
|
+
*
|
|
170
|
+
* 排序 = (scope, rule) 字典序,**确定性**:分页游标是 keyset 形,而 keyset 的全部前提就是「同一份
|
|
171
|
+
* 数据每次以同一个顺序出现」。core 的 `list()` 不承诺顺序(SQL 形按写入顺序、File 形按文件内顺序),
|
|
172
|
+
* 所以序在这里定,不在店里。
|
|
173
|
+
*/
|
|
174
|
+
listRules(principal: string): Promise<PersistedRuleListing>;
|
|
175
|
+
/**
|
|
176
|
+
* 撤销面写半场。**恒经 core `removePersistedRule`**(design/203 §2 裁定)——它产墓碑,而墓碑是
|
|
177
|
+
* `screenRuleSyncState` 的筛子保证「别的副本不把这条规则回灌回来」的唯一凭据。直接删 SQL 行/文件行
|
|
178
|
+
* 是**无墓碑硬删**:sync 的 join 会让它复活,而运维以为自己收回了权限。
|
|
179
|
+
*
|
|
180
|
+
* 返回值逐字是 core 的 `RemoveResult` 三态(removed / no-op / failed),**不塌**:
|
|
181
|
+
* `removed.stillLive:true` 意思是「墓碑落了,但本次调用期间又落了一次新的批准,规则按 add-wins
|
|
182
|
+
* 仍然活着」——那是一个**有名字的真结果**,不是异常,更不是「撤销完成」。
|
|
183
|
+
*/
|
|
184
|
+
removeRule(input: {
|
|
185
|
+
principal: string;
|
|
186
|
+
rule: string;
|
|
187
|
+
scope: RuleScope;
|
|
188
|
+
}): Promise<RemoveResult>;
|
|
121
189
|
}
|
|
122
190
|
export declare function createRuleConsentLane(stores: RuleConsentStores, opts?: {
|
|
123
191
|
ticketTtlMs?: number;
|
package/dist/rules-consent.js
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
import { randomUUID } from "node:crypto";
|
|
37
37
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
38
|
-
import { confirmRuleApproval, prepareCardApproval, prepareCcImport, redeemRuleBatch, redeemRuleTicket, } from "@sema-agent/core";
|
|
38
|
+
import { confirmRuleApproval, prepareCardApproval, prepareCcImport, redeemRuleBatch, redeemRuleTicket, removePersistedRule, } from "@sema-agent/core";
|
|
39
39
|
import { buildRulePayloadHash } from "./plugins/permission-rule-store-sql.js";
|
|
40
40
|
/** 导入票的存活窗。人从「看到预览」到「按下确认」是一次交互,不是一段会话——十分钟宽到不会误伤,
|
|
41
41
|
* 窄到一张泄漏的票不会常驻。运维旋钮暂不开(没有部署形要求它可调;要开时走 config.ts 的既有姿势)。 */
|
|
@@ -58,6 +58,35 @@ export const RULE_IMPORT_RETRY_AFTER_SEC = 2;
|
|
|
58
58
|
* 「几百次 SQL」而不是「几万次」。
|
|
59
59
|
*/
|
|
60
60
|
export const MAX_IMPORT_CANDIDATES = 200;
|
|
61
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
62
|
+
// #203 §2 —— 撤销面(`GET /v1/rules` + `DELETE /v1/rules`)的车道半场
|
|
63
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* scope 的 **wire 形**:`global` | `project:<root>` 的判别式串(design/203 v2 §6 F5「显式成文」)。
|
|
66
|
+
*
|
|
67
|
+
* 🔴 为什么是一个串而不是把 core 的 `{kind, root}` 对象直接上 wire:这一份表示要同时当**列举结果里的
|
|
68
|
+
* 一列**、**查询参数**、**删除请求体的一个字段**和**分页游标的一部分**。四处若各用各的形,「删掉我刚才
|
|
69
|
+
* 列出来的那一行」就变成一次跨形状翻译 —— 而那正是最容易漂的地方。判别式串在四处逐字相同,而且
|
|
70
|
+
* `project:` 前缀让「这条规则是项目内的」在一眼扫日志时就成立。
|
|
71
|
+
*
|
|
72
|
+
* `root` 里可以含冒号(路径合法),所以只切**第一个**冒号 —— 用 `split(":")` 取 [1] 会把
|
|
73
|
+
* `project:/a:b` 悄悄截成 `/a`,那是一条**放宽面**上的静默改写(更短的 root 覆盖更多 cwd)。
|
|
74
|
+
*/
|
|
75
|
+
export function serializeRuleScope(scope) {
|
|
76
|
+
return scope.kind === "global" ? "global" : `project:${scope.root}`;
|
|
77
|
+
}
|
|
78
|
+
/** {@link serializeRuleScope} 的逆。读不出形 ⇒ `undefined`(调用方 400,绝不猜一个 global 出来 ——
|
|
79
|
+
* 猜 global 会把一次「删项目内规则」的请求变成一次删不掉任何东西的 no-op,或者更糟)。 */
|
|
80
|
+
export function parseRuleScope(text) {
|
|
81
|
+
if (text === "global")
|
|
82
|
+
return { kind: "global" };
|
|
83
|
+
if (!text.startsWith("project:"))
|
|
84
|
+
return undefined;
|
|
85
|
+
const root = text.slice("project:".length);
|
|
86
|
+
// 空 root 硬拒:`{kind:"project", root:""}` 在 core 的 `pathWithinRoot` 下**包含每一个 cwd**
|
|
87
|
+
// (空前缀包含一切)——一条项目内规则就此悄悄升成全局规则(cc-import 那侧同判据,同一条 codex 发现)。
|
|
88
|
+
return root === "" ? undefined : { kind: "project", root };
|
|
89
|
+
}
|
|
61
90
|
export function createRuleConsentLane(stores, opts) {
|
|
62
91
|
const deps = { provider: stores.provider, approvals: stores.approvals };
|
|
63
92
|
const ticketTtlMs = opts?.ticketTtlMs ?? RULE_IMPORT_TICKET_TTL_MS;
|
|
@@ -193,6 +222,19 @@ export function createRuleConsentLane(stores, opts) {
|
|
|
193
222
|
return await indeterminate(err instanceof Error ? err.message : String(err));
|
|
194
223
|
}
|
|
195
224
|
},
|
|
225
|
+
async listRules(principal) {
|
|
226
|
+
// `list()` 已经把墓碑折算掉(`applyTombstones`)⇒ 这里拿到的就是**活着**的规则,治理面看到的
|
|
227
|
+
// 与引擎放行时看到的是同一份。空桶 = 空表 + rev 0,不是错误(「这个人没有规则」是合法状态)。
|
|
228
|
+
const stored = await stores.provider.forPrincipal(principal).list();
|
|
229
|
+
const rows = stored.rules.map((r) => ({ rule: r.rule, scope: serializeRuleScope(r.scope), tool: r.tool, match: r.match, command: r.command, adds: r.adds }));
|
|
230
|
+
rows.sort((a, b) => (a.scope < b.scope ? -1 : a.scope > b.scope ? 1 : a.rule < b.rule ? -1 : a.rule > b.rule ? 1 : 0));
|
|
231
|
+
return { rows, rev: stored.rev };
|
|
232
|
+
},
|
|
233
|
+
async removeRule(input) {
|
|
234
|
+
// 🔴 一行也不自己写:OCC 重试、墓碑铸造、`stillLive` 的读回全在 core 那条原语里,而它的语义
|
|
235
|
+
// (add-wins、observed-remove、失败必须由读回确认「什么都没写」)是 core 的单一属主。
|
|
236
|
+
return await removePersistedRule({ rule: input.rule, scope: input.scope, principal: input.principal, provider: stores.provider });
|
|
237
|
+
},
|
|
196
238
|
};
|
|
197
239
|
}
|
|
198
240
|
//# sourceMappingURL=rules-consent.js.map
|
package/dist/run-local.js
CHANGED
|
@@ -26,6 +26,16 @@
|
|
|
26
26
|
* allow-all arm and a gated `ask` is answered by {@link createLocalApprover} — the TTY human, or a
|
|
27
27
|
* fail-closed deny — instead of parking a checkpoint nobody will ever redeem.
|
|
28
28
|
*
|
|
29
|
+
* 🔴 **design/201 的 permissionMode → shellGate 翻译表 SCOPED-OUT 本腿**(设计稿 §5,显式裁定而非欠账)。
|
|
30
|
+
* 这条腿的 operator 就是亲手起这个进程的用户,它**没有 permission mode 这个概念**(一次性 CLI 收不到
|
|
31
|
+
* 客户端表态),今昔都落 core 的缺省 `shellGate: off`。给它硬塞一个 classify 档,在没有 HITL 面的形态下
|
|
32
|
+
* 等于把每一次 shell 调用送进 auto-deny —— 那不是加固,是废掉 run-local。部署治理段(上一段)照旧全接:
|
|
33
|
+
* `MANUAL_MODE_SHELL_GATE` 想要门,operator 在自己的 `.env` 里说一句就有。同族的另一条 scoped-out 是
|
|
34
|
+
* parked-revive 赎回腿(body 全盲,core 的 `InheritedGate` 以 `max(seed, live)` 兜底,自洽)。
|
|
35
|
+
* 「三腿不共享翻译点」是设计,不是漏接:名册对账钉在
|
|
36
|
+
* `test/operator-knob-client-posture-matrix.test.ts`(`shellGateForMode` 已并入构造点名册,第四条腿哪天
|
|
37
|
+
* 接上它必须同时挂矩阵腿)。
|
|
38
|
+
*
|
|
29
39
|
* Usage: run-local "<objective>" [--root <dir>] [--json] [--scenario <name>]
|
|
30
40
|
*
|
|
31
41
|
* `main()` is a thin wrapper over the testable {@link runLocal}, which takes argv + injectable
|
|
@@ -38,7 +48,7 @@ import { realpathSync } from "node:fs";
|
|
|
38
48
|
import { createInterface } from "node:readline/promises";
|
|
39
49
|
import { fileURLToPath } from "node:url";
|
|
40
50
|
import { loadRemoteExec } from "@sema-agent/registry-core/node";
|
|
41
|
-
import { Runner, uuidv7, parseModelMention, FileStorageBackend, NodeExecutionEnv, NodeLspManager, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore } from "@sema-agent/core";
|
|
51
|
+
import { Runner, uuidv7, parseModelMention, AdoptionError, FileStorageBackend, NodeExecutionEnv, NodeLspManager, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore } from "@sema-agent/core";
|
|
42
52
|
import { hasOperatorGateIntent } from "./approval.js";
|
|
43
53
|
import { createBrain } from "./brain.js";
|
|
44
54
|
import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "./deployment-governance.js";
|
|
@@ -60,7 +70,10 @@ import { pickHandsRunner, withoutExecutionEnv } from "./capabilities/hands-lane.
|
|
|
60
70
|
import { HttpError } from "./security.js";
|
|
61
71
|
import { memoryEngineBackendFor, memorySpecForRequest } from "./memory-scope.js";
|
|
62
72
|
import { createMemorySyncRunner, createMemorySyncTransport } from "./memory-sync-client.js";
|
|
63
|
-
import { buildPricing, cappedCeiling } from "./budget.js";
|
|
73
|
+
import { buildPricing, cappedCeiling, createTracer } from "./budget.js";
|
|
74
|
+
import { createSharedRunnerDeps } from "./boot/runner-deps.js";
|
|
75
|
+
import { createOrgMemoryAdmissionWiring } from "./boot/org-memory.js";
|
|
76
|
+
import { createPermissionDeniedMeter } from "./observability/tool-trace.js";
|
|
64
77
|
import { createKeyResolver } from "./key-resolver.js";
|
|
65
78
|
import { createLogger } from "./observability/logger.js";
|
|
66
79
|
import { createMetrics } from "./observability/metrics.js";
|
|
@@ -389,11 +402,35 @@ export async function runLocal(argv, deps = {}) {
|
|
|
389
402
|
// concurrent instance throws in the constructor — surface a clear message instead of a stack.
|
|
390
403
|
let fileBackend;
|
|
391
404
|
try {
|
|
392
|
-
fileBackend = new FileStorageBackend({
|
|
405
|
+
fileBackend = new FileStorageBackend({
|
|
406
|
+
// 🔴 腐读披露座(codex 复审 R1-medium,#205 件3 连带):本批把 `sessionPolicyStore` 接进了两只
|
|
407
|
+
// Runner,而 core 对**坏掉的**策略文件是 documented fail-open —— 读不出来按「没有规则」处理。
|
|
408
|
+
// 这条 fail-open 本身是 core 的裁定(抛会让整条任务死、也会自锁修复写),但它**不许无声**
|
|
409
|
+
// (#157 安全轴纪律)。一个座覆盖 core 今天转发的三张脸,后果**各不相同**,所以文案只陈述共同事实
|
|
410
|
+
// (「一次持久读被当成缺席」)并把后果按脸分列,不下统一结论(codex R2-medium:原文案把「少了
|
|
411
|
+
// 会话规则这一层」写成「整个任务无约束」——那会误导事故定级,而且对另外两张脸根本不成立)。
|
|
412
|
+
// ENOENT 不触发(那是真缺席)。
|
|
413
|
+
root,
|
|
414
|
+
onCorruptRead: (info) => logger.warn("file_store_corrupt_read", {
|
|
415
|
+
path: info.path,
|
|
416
|
+
reason: info.reason,
|
|
417
|
+
...(info.sessionId !== undefined ? { sessionId: info.sessionId } : {}),
|
|
418
|
+
...(info.principal !== undefined ? { principal: info.principal } : {}),
|
|
419
|
+
note: "a durable read was treated as ABSENT because the bytes were unreadable (never a plain ENOENT). " +
|
|
420
|
+
"Consequence depends on which store read it: session-policy = this run lost its SUBTRACTIVE session-rule " +
|
|
421
|
+
"layer (deployment policy / hooks / shell gate still applied); session repo = a listing silently omitted " +
|
|
422
|
+
"rows; file-snapshot = a snapshot or scope read as missing. Inspect the named path before deciding.",
|
|
423
|
+
}),
|
|
424
|
+
});
|
|
393
425
|
}
|
|
394
426
|
catch (e) {
|
|
395
427
|
printErr(`cannot open local data dir ${root}: ${e instanceof Error ? e.message : String(e)}`);
|
|
396
|
-
|
|
428
|
+
// 🔴 core 5.23.0([3372] 件④):这个构造器的第二个拒绝面是 design/183 的 I6 adoption boot 门。
|
|
429
|
+
// 那一类失败**不能**再补「换一个 --root」这句 —— 换根 = 丢下一个迁移到一半的数据根(与
|
|
430
|
+
// `plugins/store-backend.ts` 的 local 臂同案同判)。core 的拒绝句已自带出路,上面那行已如实打印。
|
|
431
|
+
if (!(e instanceof AdoptionError)) {
|
|
432
|
+
printErr("(another run-local/engine instance may own it; finish it first, or use a different --root)");
|
|
433
|
+
}
|
|
397
434
|
return 2;
|
|
398
435
|
}
|
|
399
436
|
const sessionStore = fileBackend.sessionStore;
|
|
@@ -496,12 +533,74 @@ export async function runLocal(argv, deps = {}) {
|
|
|
496
533
|
...(ctx.classification !== undefined ? { classification: ctx.classification } : {}),
|
|
497
534
|
err: String(err),
|
|
498
535
|
});
|
|
499
|
-
|
|
536
|
+
// ⚠️ `createOrgMemoryAdmissionWiring` 有一条 **throw** 路径:`MEMORY_ORG_DIRECTORY_JSON` 形坏时
|
|
537
|
+
// `parseOrgDirectoryStatic` 启动期炸(config 层按 A4 分层刻意不校验它)。server 那边炸=进程拒启,
|
|
538
|
+
// 正确;但本腿的既定 UX 是「doctor 文案 + 退 2」(同 assertGuardPatternsUsable / remote-exec-file-invalid
|
|
539
|
+
// 两处),把栈抛给在终端里敲命令的人是回归。另一条 throw(多租户 org 键 + 无目录源)在本腿**结构上
|
|
540
|
+
// 不可达**——上面已把 `REQUIRE_PRINCIPAL` 强制成 false。
|
|
541
|
+
let orgMemoryAdmission;
|
|
542
|
+
try {
|
|
543
|
+
orgMemoryAdmission = createOrgMemoryAdmissionWiring({ config, logger, metrics });
|
|
544
|
+
}
|
|
545
|
+
catch (e) {
|
|
546
|
+
printErr(`MEMORY_ORG_DIRECTORY_JSON is invalid: ${e instanceof Error ? e.message : String(e)}`);
|
|
547
|
+
printErr(`(local config root: ${root} — fix it in ${join(root, ".env")} or your shell env, or unset it)`);
|
|
548
|
+
await fileBackend.dispose();
|
|
549
|
+
return 2;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* [3397]①②③ / #205 件3 —— run-local 的两份 Runner deps 走 **server 同一只共享基座**
|
|
553
|
+
* (`createSharedRunnerDeps`,main.ts:432 的同款展开)。
|
|
554
|
+
*
|
|
555
|
+
* 为什么必须收编:这两份 deps 此前各自手写同源键,而漏配**没有任何编译期或运行期信号** —— 复扫亲验
|
|
556
|
+
* 到三处真漂移:`toolResultStore` 只在主 runner(委派子代的 `ReadToolResult(ref)` 恒空,正是 core
|
|
557
|
+
* 1.219 修过的那个病在 sub 腿复发)、`config.tiers` **全文件零接线**(而上面的 `applyEffective` 真在
|
|
558
|
+
* 填它 ⇒ tier 词 / CC 别名在这条腿上解不出来)、`hooks` / `tracer` / `sessionPolicyStore` 两处都没有。
|
|
559
|
+
* 基座是一份,新增共享键不可能只挂一半。
|
|
560
|
+
*
|
|
561
|
+
* ── 逐参裁定(local 车道**真差异**保留,不为收编硬造 stub)───────────────────────────────────
|
|
562
|
+
* · `brain` / `pricing` / `promptSource` / `executionEnvFactory` / `lspManager`:本腿真有,直供。
|
|
563
|
+
* · `config`(基座据以取 models / roles / **tiers** / usageWindows):本腿真有 —— tiers 正是本件修的漏。
|
|
564
|
+
* · `tracer`:`createTracer(metrics)` 真形。配额/舰队/prompt-manifest 四个可选跟踪器是 server 面的
|
|
565
|
+
* 设施,本腿没有 ⇒ 不传(不是 stub:那几个参数本就可选,缺席即该轴不记账)。
|
|
566
|
+
* · `deploymentHooks`:`createPermissionDeniedMeter(metrics)` —— main.ts 在**没有** toolTracer 时的
|
|
567
|
+
* 同一只值(tool-trace 落库面是 server 侧设施,一次性 CLI 无处落)。
|
|
568
|
+
* · `sessionPolicyStore`:`fileBackend.sessionPolicyStore`(core 的 File 形,其文档原话就是「本地部署
|
|
569
|
+
* 要让会话规则跨重启存活就接这里」)。E6 规则是 subtract-only ⇒ 方向只会更紧;同一个数据根上的
|
|
570
|
+
* 本地 HTTP 服务写下的规则,这条腿本就该认。
|
|
571
|
+
* · `usageWindowStore`:与 `config.usageWindows` **成对**(main.ts 同规:两键同真同假)—— 配了窗才接
|
|
572
|
+
* File 账本,没配窗时接一个空账本只会让 core 以为这条腿有治理窗。
|
|
573
|
+
* · `toolResultStore`:`fileBackend.toolResultStore`(本件的主修点:现在主/子同源)。
|
|
574
|
+
* · `orgMemoryAdmission`:`createOrgMemoryAdmissionWiring(...)` 真形。本腿 `REQUIRE_PRINCIPAL` 被强制
|
|
575
|
+
* false(见上),所以那条多租户拒启探测结构上不可达;产物在无 center / 无 `MEMORY_ORG_DIRECTORY_JSON`
|
|
576
|
+
* 时是「resolver 缺席 + deployment 自证集」——即 `MEMORY_SCOPE=org:x` 的本地用户从此走得通自证通道。
|
|
577
|
+
* · `backgroundAgentStore` / `mailboxStore` / `rosterStore`:**undefined** —— core 的 `FileStorageBackend`
|
|
578
|
+
* 根本不供这三个店(durable 后台子代 / tier-3 懒复活 / 持久名册都是 server+SQL 面的设施)。造个内存
|
|
579
|
+
* 冒牌货等于让 core 以为「具名子代能跨进程复活」,而一次性 CLI 的进程下一秒就没了。
|
|
580
|
+
* · `sharedMemoryStores`:**undefined** —— 供给面只有 SQL 形(`store-backend.ts` 明写:单机造 file 形
|
|
581
|
+
* 等于给一个人的部署做「团队共享」,能力面会因此说谎)。
|
|
582
|
+
*/
|
|
583
|
+
const sharedRunnerDeps = createSharedRunnerDeps({
|
|
584
|
+
config,
|
|
500
585
|
brain,
|
|
501
|
-
models: config.models,
|
|
502
|
-
roles: config.roles,
|
|
503
586
|
pricing,
|
|
587
|
+
tracer: createTracer(metrics),
|
|
504
588
|
promptSource,
|
|
589
|
+
executionEnvFactory,
|
|
590
|
+
lspManager,
|
|
591
|
+
backgroundAgentStore: undefined,
|
|
592
|
+
mailboxStore: undefined,
|
|
593
|
+
rosterStore: undefined,
|
|
594
|
+
deploymentHooks: createPermissionDeniedMeter(metrics),
|
|
595
|
+
toolResultStore: fileBackend.toolResultStore,
|
|
596
|
+
sessionPolicyStore: fileBackend.sessionPolicyStore,
|
|
597
|
+
usageWindowStore: config.usageWindows ? fileBackend.usageWindowStore : undefined,
|
|
598
|
+
orgMemoryAdmission,
|
|
599
|
+
sharedMemoryStores: undefined,
|
|
600
|
+
});
|
|
601
|
+
const runnerDeps = {
|
|
602
|
+
...sharedRunnerDeps,
|
|
603
|
+
// ── 以下为主 runner 的差异键(不在共享基座;逐个有因)──────────────────────────────────────
|
|
505
604
|
// [931]① clay 拍:run-local=Sema 品牌本地形态,commit 尾注接 Sema 署名(core 1.300 缺省已翻转不署)。
|
|
506
605
|
hands: { commitCoAuthor: "Sema <noreply@vivi-ai.com>" },
|
|
507
606
|
sessionStore,
|
|
@@ -512,11 +611,6 @@ export async function runLocal(argv, deps = {}) {
|
|
|
512
611
|
// stderr)后其余告警——compaction / prompt-cache / mcp / memory / hook 的降级——也不再无声。
|
|
513
612
|
onError: engineOnError,
|
|
514
613
|
onAsk,
|
|
515
|
-
// core 1.219 (dogfood: "ReadToolResult(ref) 恒空"): durable tool-result refs on the TOC CLI
|
|
516
|
-
// lane too — offloaded full text survives a process restart (one file per ref under the data root).
|
|
517
|
-
toolResultStore: fileBackend.toolResultStore,
|
|
518
|
-
...(executionEnvFactory ? { executionEnvFactory } : {}),
|
|
519
|
-
...(lspManager ? { lspManager } : {}),
|
|
520
614
|
// design/113 C4: run-local IS the single-user host lane (cwd = --workspace ?? process.cwd()) — the CLI path where
|
|
521
615
|
// CLAUDE.md project-awareness most belongs. Wire it unless explicitly disabled.
|
|
522
616
|
...(config.projectMemoryEnabled
|
|
@@ -530,7 +624,12 @@ export async function runLocal(argv, deps = {}) {
|
|
|
530
624
|
// [1367]① fork server half(main.ts subRunner 同款):ForkRoutingSessionStore——fork/resume 形
|
|
531
625
|
// (requireExisting)→ host 文件店优先(core 1.350 hostSessionFork 在宿主店 fork,拆店=响亮
|
|
532
626
|
// resume.session_not_found),普通子任务 → 私有 TTL 店(throwaway 姿势保留)。
|
|
533
|
-
|
|
627
|
+
// #205 件3:与主 runner **同一只**共享基座展开(上面那份 `sharedRunnerDeps`)——此前这里是一行手写
|
|
628
|
+
// 字面量,`toolResultStore`/`tiers`/`hooks`/`tracer`/`sessionPolicyStore` 全漏,子代 lane 静默降级。
|
|
629
|
+
// 差异键只剩会话店与两个座位;**checkpointStore 有意不给**(main.ts 的 sub 腿给它是为了子代 durable
|
|
630
|
+
// park,而本腿的 park 设施结构上不存在:一次性 CLI 没有 `/decide` 赎回腿,gated ask 由 TTY 真人当场答
|
|
631
|
+
// ——上面 `buildOnlySensitiveBaselineWarning` 传 `durableEnabled: false` 记的就是同一件事实)。
|
|
632
|
+
const subRunnerDeps = { ...sharedRunnerDeps, sessionStore: new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 })), onError: engineOnError, onAsk };
|
|
534
633
|
const subRunner = new Runner(subRunnerDeps);
|
|
535
634
|
// #196:无手孪生一对 —— **mirrors main.ts**(那边是 `handslessRunner` / `handslessSubRunner` 两只,同 deps
|
|
536
635
|
// 摘掉 executionEnvFactory)。run-local 是同形装配点,漏这一对 = 本地 lane 上 scan/council/team 的最终
|
|
@@ -619,6 +718,14 @@ export async function runLocal(argv, deps = {}) {
|
|
|
619
718
|
// design/129: run-local IS the pure TOC lane → session-scoped background children
|
|
620
719
|
// (CC Backgrounded semantics; parity with the server's single-user posture).
|
|
621
720
|
backgroundScope: "session",
|
|
721
|
+
// [1909]⑧(core 5.23.0 `TaskSpec.oneShot`,#205 件1 的**本腿**半场,codex R2-high 抓获):这条腿
|
|
722
|
+
// 是**定义上**的一次性提交 —— 本文件头注第一句就是「跑完一条任务、打印 TaskResult、退出」,
|
|
723
|
+
// `runLocal` 返回后 Runner 全被 dispose、进程即散。缺席这个键时,core 会照默认(交互态)给后台
|
|
724
|
+
// 委派 / run_workflow 发「结束回合,你会被通知」的回执,而这里**没有下一个回合**能接住那条通知 ——
|
|
725
|
+
// 那正是本键被造出来要消灭的丢结果病族(BGB drilldown case 2)。
|
|
726
|
+
// 与 HTTP 腿的差别在**来源**而不在语义:那边是调用方表态(缺席不写键),这边是**进程形态的事实**,
|
|
727
|
+
// 所以无条件为真,不看任何 body/表态(本腿根本收不到客户端表态,同 permissionMode 的 scoped-out 理由)。
|
|
728
|
+
oneShot: true,
|
|
622
729
|
principal: scope, // stable local memory/identity scope (single-user TOC; --user overrides)
|
|
623
730
|
// design/138 S1: enable the memory engine for this run (spec.memory is core's per-task activation half of
|
|
624
731
|
// the deps.memoryBackend switch). Scope = the --user identity (default "local", the single TOC user).
|
|
@@ -319,9 +319,15 @@ export function applyRuntimeGovernance(base, governance) {
|
|
|
319
319
|
const candidate = overrides.shellGate !== undefined && SHELL_GATE_RANK[overrides.shellGate] >= SHELL_GATE_RANK[governance.manualModeShellGate]
|
|
320
320
|
? overrides.shellGate
|
|
321
321
|
: governance.manualModeShellGate;
|
|
322
|
-
// base 已更严 ⇒ 省略,让 base 原样保留(省略=行为等价,不 throw)
|
|
323
|
-
//
|
|
324
|
-
//
|
|
322
|
+
// base 已更严 ⇒ 省略,让 base 原样保留(省略=行为等价,不 throw)。**仍然装配上不可达、纯防御**
|
|
323
|
+
// ——但论证已随 design/201 换了一条:
|
|
324
|
+
// · 前提(改):resolveSpec 的 spec 字面量**现在会写 shellGate** —— 显式 permissionMode 的翻译档
|
|
325
|
+
// (`shellGateForMode`,阶段④,governance 之前),所以 base.shellGate 不再恒缺席。
|
|
326
|
+
// · 结论(不变):这一支照旧不可达。翻译表的**上限是 classify**(rank 1),而能走到这里的 candidate
|
|
327
|
+
// 下限也是 classify(旋钮词表只有 classify/always,且 candidate 已取过与 autonomy 派生值的较大者)
|
|
328
|
+
// ⇒ candidate ≥ base 恒成立。SUP 路由姿态(resolve-spec 的 supPostureOverrides)是 governance 之
|
|
329
|
+
// **后**才叠的,够不到这里的 base。
|
|
330
|
+
// 若将来有人给 base 种上更严的值(例:翻译表新增一个 ⇒ always 的模式词),这里省略而不是让
|
|
325
331
|
// tightenTaskSpec 因「override 更松」throw——candidate 已是 autonomy 派生值与旋钮的较大者,省略它不会
|
|
326
332
|
// 丢掉 autonomy 那一半(autonomy 只产 "always",即最高 rank,永远不会落进这一支)。
|
|
327
333
|
if (SHELL_GATE_RANK[candidate] >= SHELL_GATE_RANK[base.shellGate ?? "off"])
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -70,6 +70,50 @@ export declare function effectiveThinking(reasoningEffort: unknown, ultracode: b
|
|
|
70
70
|
* field is the explicit per-turn intent → it WINS over a bundle defaultMode). Creates a minimal settings object when
|
|
71
71
|
* no `body.settings` bundle was sent. The result flows through {@link applyTaskSettings} (tighten-only). */
|
|
72
72
|
export declare function withPermissionMode(settings: ParsedTaskSettings | undefined, mode: SettingsPermissionMode): ParsedTaskSettings;
|
|
73
|
+
/**
|
|
74
|
+
* design/201 §6-1 —— 本请求的**生效** permission mode,单点。优先序逐字同 {@link withPermissionMode}:
|
|
75
|
+
* 顶层 `body.permissionMode`(本轮显式表态)赢过 bundle 的 `settings.permissions.defaultMode`;两处
|
|
76
|
+
* 都没有 ⇒ `undefined` = **无表态**(调用点据此不写键,而不是替调用方选一个默认值)。
|
|
77
|
+
*
|
|
78
|
+
* 🔴 为什么必须是一只函数:同一个「生效模式」此前在 resolve-spec 里被算了两次(hooks 腿的
|
|
79
|
+
* `permission_mode` 载荷、settings 折叠腿的 `withPermissionMode`),三审同点判定翻译表**不得**成为
|
|
80
|
+
* 第三个算点 —— 三处各算各的,任何一次优先序修订都会让三面分家,而分家在这条轴上的形态是
|
|
81
|
+
* 「壳发的 bundle bypass 在 A 面生效、在 B 面没生效」这种最难被外部发现的静默偏差。
|
|
82
|
+
*/
|
|
83
|
+
export declare function effectivePermissionMode(body: {
|
|
84
|
+
permissionMode?: unknown;
|
|
85
|
+
}, settings: ParsedTaskSettings | undefined): SettingsPermissionMode | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* design/201 §2 —— 显式 permission mode → `TaskSpec.shellGate` 档位的**翻译表(单一真源)**。
|
|
88
|
+
*
|
|
89
|
+
* · `bypassPermissions` ⇒ `"off"` —— CC `--dangerously-skip-permissions` 的对位裁定
|
|
90
|
+
* (与 fs-write 面的 bypass 臂两面归一,见 {@link deriveSettingsPolicy})。
|
|
91
|
+
* · `auto` ⇒ `"classify"` —— **design/201 §8 开口按施工期新披露亲裁改译**(原裁定字面是 off,
|
|
92
|
+
* §8 保留「core auto 分类器覆盖面有新披露时可独立再裁,表驱动一行」——新披露见下段):
|
|
93
|
+
*
|
|
94
|
+
* ⚠️ **`auto` 这一行有一条待裁的开口(design/201 §8 明列「core auto 分类器覆盖面有新披露时可独立再裁,
|
|
95
|
+
* 表驱动一行」)。施工期对抗复审给出了那条新披露,已亲读安装包核实**:
|
|
96
|
+
* · core 只在**已经产生 `ask`** 之后才咨询分类器(`dist/core/hooks.js`:`if (input.autoMode &&
|
|
97
|
+
* decision.action === "ask" …)`);
|
|
98
|
+
* · 而 `shellGate:"off"` 下 core **不铸任何 shell 面的门**(`dist/core/runner/prepare-task.js` 的
|
|
99
|
+
* `shellGate === "off"` 臂反而发一条 `classification:"shell-gate-off"` 的 onError:「真可写 Bash 挂着
|
|
100
|
+
* 却没有 shell 安全轴折叠」)。
|
|
101
|
+
* 两条合起来:`auto` × off ⇒ Bash 既不产 ask、分类器也就永不被咨询 —— off 档下「筛选权交给分类器」
|
|
102
|
+
* 不成立。故 auto 归 classify 组:分类器坐在 classify 产的 ask 之上,语义才真是「交给分类器」
|
|
103
|
+
* (良性只读命令由分类器自动放行,其余 ask;这正是 auto 模式的本义)。发车帖向 [3378] 裁定链披露此
|
|
104
|
+
* 一行偏离;core auto 分类器若来日在 off 档下也有咨询点,可再裁回(表驱动一行)。
|
|
105
|
+
* · `default` / `acceptEdits` / `plan` ⇒ `"classify"` —— 这一档原先由壳无条件注入 `MANUAL_MODE_SHELL_GATE`
|
|
106
|
+
* 供给(车道缺省走了 operator 通道),现在归位到表态轴。`plan` 本身 handsReadOnly(core 明写此档下
|
|
107
|
+
* shellGate 无效),给它 classify 纯为一致性。
|
|
108
|
+
*
|
|
109
|
+
* 🔴 **入参非可选**:缺席(无表态)不是这张表的一行 —— 它的语义是「不写这个键」,只能在调用点判。
|
|
110
|
+
* 把它折进来会逼出一个 `undefined` 返回值,而那正是「写 off」与「不写」被混同的入口(core 的
|
|
111
|
+
* 缺席默认是 off,但**写**一个 off 会成为 governance 的 base,与缺席不是同一件事)。
|
|
112
|
+
*
|
|
113
|
+
* 🔴 **闭集 exhaustive switch,无 default 臂**:五个模式词是封闭词表,新增一个模式而不更新本表
|
|
114
|
+
* 是**编译错误**(#157 的安全轴纪律:词表的未知项不许有静默兜底臂)。
|
|
115
|
+
*/
|
|
116
|
+
export declare function shellGateForMode(effMode: SettingsPermissionMode): "off" | "classify";
|
|
73
117
|
/** The validated, service-trusted subset of `SemaSettings` we project onto the spec. `env` is parsed only to REPORT
|
|
74
118
|
* it as received-but-deferred when off the host lane (never silently dropped); a MALFORMED `hooks` likewise reports
|
|
75
119
|
* deferred (submit 路径另有 400 fail-loud),valid `hooks` 进 applied shape(hook-runner 阶段一)。 */
|
package/dist/task-settings.js
CHANGED
|
@@ -78,6 +78,60 @@ export function withPermissionMode(settings, mode) {
|
|
|
78
78
|
const base = settings ?? {};
|
|
79
79
|
return { ...base, permissions: { ...base.permissions, defaultMode: mode } };
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* design/201 §6-1 —— 本请求的**生效** permission mode,单点。优先序逐字同 {@link withPermissionMode}:
|
|
83
|
+
* 顶层 `body.permissionMode`(本轮显式表态)赢过 bundle 的 `settings.permissions.defaultMode`;两处
|
|
84
|
+
* 都没有 ⇒ `undefined` = **无表态**(调用点据此不写键,而不是替调用方选一个默认值)。
|
|
85
|
+
*
|
|
86
|
+
* 🔴 为什么必须是一只函数:同一个「生效模式」此前在 resolve-spec 里被算了两次(hooks 腿的
|
|
87
|
+
* `permission_mode` 载荷、settings 折叠腿的 `withPermissionMode`),三审同点判定翻译表**不得**成为
|
|
88
|
+
* 第三个算点 —— 三处各算各的,任何一次优先序修订都会让三面分家,而分家在这条轴上的形态是
|
|
89
|
+
* 「壳发的 bundle bypass 在 A 面生效、在 B 面没生效」这种最难被外部发现的静默偏差。
|
|
90
|
+
*/
|
|
91
|
+
export function effectivePermissionMode(body, settings) {
|
|
92
|
+
return coercePermissionMode(body.permissionMode) ?? settings?.permissions?.defaultMode;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* design/201 §2 —— 显式 permission mode → `TaskSpec.shellGate` 档位的**翻译表(单一真源)**。
|
|
96
|
+
*
|
|
97
|
+
* · `bypassPermissions` ⇒ `"off"` —— CC `--dangerously-skip-permissions` 的对位裁定
|
|
98
|
+
* (与 fs-write 面的 bypass 臂两面归一,见 {@link deriveSettingsPolicy})。
|
|
99
|
+
* · `auto` ⇒ `"classify"` —— **design/201 §8 开口按施工期新披露亲裁改译**(原裁定字面是 off,
|
|
100
|
+
* §8 保留「core auto 分类器覆盖面有新披露时可独立再裁,表驱动一行」——新披露见下段):
|
|
101
|
+
*
|
|
102
|
+
* ⚠️ **`auto` 这一行有一条待裁的开口(design/201 §8 明列「core auto 分类器覆盖面有新披露时可独立再裁,
|
|
103
|
+
* 表驱动一行」)。施工期对抗复审给出了那条新披露,已亲读安装包核实**:
|
|
104
|
+
* · core 只在**已经产生 `ask`** 之后才咨询分类器(`dist/core/hooks.js`:`if (input.autoMode &&
|
|
105
|
+
* decision.action === "ask" …)`);
|
|
106
|
+
* · 而 `shellGate:"off"` 下 core **不铸任何 shell 面的门**(`dist/core/runner/prepare-task.js` 的
|
|
107
|
+
* `shellGate === "off"` 臂反而发一条 `classification:"shell-gate-off"` 的 onError:「真可写 Bash 挂着
|
|
108
|
+
* 却没有 shell 安全轴折叠」)。
|
|
109
|
+
* 两条合起来:`auto` × off ⇒ Bash 既不产 ask、分类器也就永不被咨询 —— off 档下「筛选权交给分类器」
|
|
110
|
+
* 不成立。故 auto 归 classify 组:分类器坐在 classify 产的 ask 之上,语义才真是「交给分类器」
|
|
111
|
+
* (良性只读命令由分类器自动放行,其余 ask;这正是 auto 模式的本义)。发车帖向 [3378] 裁定链披露此
|
|
112
|
+
* 一行偏离;core auto 分类器若来日在 off 档下也有咨询点,可再裁回(表驱动一行)。
|
|
113
|
+
* · `default` / `acceptEdits` / `plan` ⇒ `"classify"` —— 这一档原先由壳无条件注入 `MANUAL_MODE_SHELL_GATE`
|
|
114
|
+
* 供给(车道缺省走了 operator 通道),现在归位到表态轴。`plan` 本身 handsReadOnly(core 明写此档下
|
|
115
|
+
* shellGate 无效),给它 classify 纯为一致性。
|
|
116
|
+
*
|
|
117
|
+
* 🔴 **入参非可选**:缺席(无表态)不是这张表的一行 —— 它的语义是「不写这个键」,只能在调用点判。
|
|
118
|
+
* 把它折进来会逼出一个 `undefined` 返回值,而那正是「写 off」与「不写」被混同的入口(core 的
|
|
119
|
+
* 缺席默认是 off,但**写**一个 off 会成为 governance 的 base,与缺席不是同一件事)。
|
|
120
|
+
*
|
|
121
|
+
* 🔴 **闭集 exhaustive switch,无 default 臂**:五个模式词是封闭词表,新增一个模式而不更新本表
|
|
122
|
+
* 是**编译错误**(#157 的安全轴纪律:词表的未知项不许有静默兜底臂)。
|
|
123
|
+
*/
|
|
124
|
+
export function shellGateForMode(effMode) {
|
|
125
|
+
switch (effMode) {
|
|
126
|
+
case "bypassPermissions":
|
|
127
|
+
return "off";
|
|
128
|
+
case "auto":
|
|
129
|
+
case "default":
|
|
130
|
+
case "acceptEdits":
|
|
131
|
+
case "plan":
|
|
132
|
+
return "classify";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
81
135
|
/** Cap on `outputStyle` length — it lands in the system prompt, so it shares the `MAX_SYSTEM_PROMPT_CHARS` (16384)
|
|
82
136
|
* posture (an uncapped prompt body is a per-turn token-cost hole; adversarial review). Exported so the HTTP layer
|
|
83
137
|
* (prepareSpec) can 400 fail-loud on submit with the SAME bound this defensive parse enforces on every path. */
|
|
@@ -470,7 +524,9 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
|
|
|
470
524
|
* Returns `base` untouched when the parsed settings project nothing onto the spec.
|
|
471
525
|
*/
|
|
472
526
|
// ([1557]§四 的 SHELL_GATE_RANK 副本与 shellGateSafe 守卫已随 #153 搬 runtime-governance ——
|
|
473
|
-
// settings 折叠不再触碰 TaskSpec.shellGate,base 上 governance 层施加的值原样透传。
|
|
527
|
+
// settings 折叠不再触碰 TaskSpec.shellGate,base 上 governance 层施加的值原样透传。design/201 的
|
|
528
|
+
// {@link shellGateForMode} 也不改这句话:那只纯函数产的是 governance 的 base,由 resolve-spec 阶段④
|
|
529
|
+
// 写进 spec 字面量;本折叠拿到的 `base` 里可能因此已经带着一档 shellGate,而它照旧原样透传。)
|
|
474
530
|
export function applyTaskSettings(base, settings, gate, workflowGate) {
|
|
475
531
|
const { toolPolicy, handsReadOnly, enablePlanMode } = deriveSettingsPolicy(settings, gate, workflowGate);
|
|
476
532
|
let next = base;
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -146,7 +146,12 @@ export type ToolApprovalDecision = "allow" | "allow_session" | "deny";
|
|
|
146
146
|
*/
|
|
147
147
|
export type RuleRefusalReason = Extract<CardRulePersisted, {
|
|
148
148
|
ok: false;
|
|
149
|
-
}>["reason"] | "rule_lane_unavailable" | "rule_input_edited" | "rule_not_offered" | "rule_store_error"
|
|
149
|
+
}>["reason"] | "rule_lane_unavailable" | "rule_input_edited" | "rule_not_offered" | "rule_store_error"
|
|
150
|
+
/** #204 件7:这只 ask 的门来自运维治理层(`governanceForced`)⇒ 它不进规则车道。**与
|
|
151
|
+
* `rule_lane_unavailable` 刻意分词**:那个说的是「这台部署压根不供规则」(壳可以从此不渲这一格),
|
|
152
|
+
* 这个说的是「规则车道好好的,只是**这一只** ask 归 operator 管」—— 折成同一个词会让壳把一台正常
|
|
153
|
+
* 部署整条车道判死。 */
|
|
154
|
+
| "rule_governance_forced";
|
|
150
155
|
/** Validate the respond body — the closed three-choice enum (rationale in the module header). */
|
|
151
156
|
export declare function parseToolApprovalResponse(body: unknown): {
|
|
152
157
|
ok: true;
|