@sema-agent/server 7.91.2 → 7.92.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 +8 -0
- package/dist/approval-ask-machine.d.ts +1 -1
- package/dist/approval-reconciler.d.ts +1 -1
- package/dist/approval.d.ts +1 -1
- package/dist/bench/s1/repair-oracle-adapter.d.ts +1 -1
- package/dist/bench/s1/runner-ctx.d.ts +1 -1
- package/dist/boot/runner-deps.d.ts +9 -9
- package/dist/capabilities/memory-notice.d.ts +27 -10
- package/dist/capabilities/memory-notice.js +21 -2
- package/dist/config-catalog.js +2 -1
- package/dist/config-center/types.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/env-facts.d.ts +1 -1
- package/dist/execution-lane-caps.d.ts +1 -1
- package/dist/file-history-disclosure.d.ts +2 -2
- package/dist/fleet/fleet-bus.d.ts +7 -1
- package/dist/fleet/fleet-bus.js +5 -3
- package/dist/http/admission.d.ts +1 -1
- package/dist/http/idempotency.d.ts +27 -0
- package/dist/http/route-ctx.d.ts +2 -5
- package/dist/http/routes/memory-compliance.d.ts +1 -1
- package/dist/http/routes/memory-origin.d.ts +2 -2
- package/dist/http/routes/rules.js +3 -1
- package/dist/http/routes/runs.js +3 -6
- package/dist/http/routes/session-sync.js +3 -2
- package/dist/http/routes/sessions.js +3 -2
- package/dist/http/routes/tasks.js +35 -19
- package/dist/http/wire-types.d.ts +29 -2
- package/dist/leader/wire.d.ts +1 -1
- package/dist/lsp/manager.d.ts +2 -2
- package/dist/memory-operator-faces.d.ts +13 -13
- package/dist/memory-scope.d.ts +2 -2
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/observability/metrics.js +2 -1
- package/dist/orchestration/workflow-agent-session-index.d.ts +2 -2
- package/dist/peer-directory.d.ts +3 -2
- package/dist/permission-rule-vocab.d.ts +16 -1
- package/dist/plugins/file-run-store.d.ts +1 -1
- package/dist/plugins/local-session-store.d.ts +1 -1
- package/dist/plugins/memory-session-policy-store.d.ts +22 -3
- package/dist/plugins/memory-session-policy-store.js +11 -4
- package/dist/plugins/permission-rule-store-sql.js +16 -7
- package/dist/plugins/pg-pool.js +11 -0
- package/dist/plugins/session-policy-store-sql.d.ts +51 -3
- package/dist/plugins/session-policy-store-sql.js +77 -38
- package/dist/plugins/sql-errors.d.ts +25 -0
- package/dist/plugins/sql-errors.js +10 -0
- package/dist/plugins/store-backend.d.ts +5 -5
- package/dist/plugins/store-contracts.d.ts +2 -2
- package/dist/plugins/tidb-pool.d.ts +1 -1
- package/dist/plugins/tidb-pool.js +19 -0
- package/dist/rules-consent.d.ts +16 -9
- package/dist/rules-consent.js +14 -35
- package/dist/run-cancel-context.d.ts +1 -1
- package/dist/run-local.d.ts +1 -1
- package/dist/runtime-caps-resolver.d.ts +10 -10
- package/dist/runtime-governance.d.ts +2 -2
- package/dist/session-policy-wire.d.ts +25 -1
- package/dist/session-policy-wire.js +11 -0
- package/dist/spec-fields.d.ts +4 -4
- package/dist/task-cwd.d.ts +1 -1
- package/dist/task-settings.d.ts +2 -2
- package/dist/tool-approval.d.ts +1 -1
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/project.d.ts +25 -1
- package/dist/trace/project.js +4 -0
- package/dist/trace/redact.d.ts +1 -1
- package/dist/trace/redact.js +8 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -170,7 +170,7 @@ The server is configured entirely through environment variables. The most import
|
|
|
170
170
|
| `SENSITIVE_WRITE_PATTERNS` | core's recommended set | Sensitive-path write deny list; comma-separated value replaces the set, `off` **or an empty value** disables. Applied unconditionally at the governance layer (independent of client permission mode, lane or settings presence) — including on the `run-local` leg. A value that cannot compile into a guard set (e.g. `/`, a pattern with no path segment) refuses to start |
|
|
171
171
|
| `WRITE_PROTECTED_EXTRA` | unset (engine default table) | **Adds** rows to core's write-protection table (the literal-name table whose hit demotes a surviving `allow` to `ask` on Write/Edit/NotebookEdit). Comma-separated bare names (a bare name matches ANY path segment; a name containing `/` matches a consecutive segment run) or a JSON array of `"name"` strings / `{name, kind}` rows (`kind`: `basename` \| `segment` \| `segment-run`). The value is composed as `[...WRITE_PROTECTED_DEFAULT_TABLE, …]`, so **no default row can be lost**. Unset = no seat is wired = the engine's default table is in force (this server never copies that table). Form discrimination is by content, not by first character: a value containing any JSON structural character (`[ ] { } "`) is parsed as JSON and **must** be a top-level array (a missing pair of brackets refuses to start instead of being split into junk bare names). An empty value, a malformed entry (glob metacharacter, unknown kind, kind/name mismatch) or setting this together with `WRITE_PROTECTED_TABLE_REPLACE` **refuses to start**. Read faces: `GET /v1/capabilities` → `writeProtection.{armed,rows,replaced}`; `GET /v1/diagnostics/wiring` → `writeProtection.{rows,source,droppedDefaultRows}` (operator-only) |
|
|
172
172
|
| `WRITE_PROTECTED_TABLE_REPLACE` | unset (engine default table) | **Replaces the whole** write-protection table (core's seat is whole-table by contract). JSON array only — deliberately no comma shorthand, because a slipped bare string would swap 51 default rows for one. `[]` = the explicit "no write-protection table at all" posture. Replacing logs one **loud** boot line naming every default row you dropped (`write_protection_table_replaced`; the empty posture logs `write_protection_table_disabled`) — use `WRITE_PROTECTED_EXTRA` when you meant to ADD. Same refuse-to-start conditions as its sibling, plus: both knobs set = two writers on one surface = refuses to start |
|
|
173
|
-
| `MANUAL_MODE_SHELL_GATE` | unset | `always`\|`classify` — tighten `Bash` into the approval chain, applied unconditionally at the governance layer (≥7.1.0: independent of client permission mode, lane, or settings presence). **Unset is not "off"**: since 7.12.0 the caller's explicit `permissionMode` supplies the baseline this knob tightens from (`bypassPermissions` → `off`, `auto`/`default`/`acceptEdits`/`plan` → `classify`; **no** mode stated → core's `off` default). Since 7.73.0 (core 7.15.0) `off` no longer means "no gate at all": the READ BOUNDARY (built-in read-deny tiers + workspace containment) is judged under **every** doctrine,
|
|
173
|
+
| `MANUAL_MODE_SHELL_GATE` | unset | `always`\|`classify` — tighten `Bash` into the approval chain, applied unconditionally at the governance layer (≥7.1.0: independent of client permission mode, lane, or settings presence). **Unset is not "off"**: since 7.12.0 the caller's explicit `permissionMode` supplies the baseline this knob tightens from (`bypassPermissions` → `off`, `auto`/`default`/`acceptEdits`/`plan` → `classify`; **no** mode stated → core's `off` default). Since 7.73.0 (core 7.15.0) `off` no longer means "no gate at all": the READ BOUNDARY (built-in read-deny tiers + workspace containment) is judged under **every** doctrine, and `shellGate` governs only the RESIDUAL shell risk. Since 7.92.0 (core 7.25.0) that boundary is ONE read station judged before approval, and its three outcomes differ: a **deny-listed read is REFUSED** on the `off` lane as on every other face — no card, no stored rule and no approver's yes can release it (`tool_end.errorCode`/`structured` = `read_path_denied` with the matched `target`/`pattern`, `gate.disposition.deniedBy = "read_boundary"`); a **recursive read form is ENUMERATED** — it runs with zero asks when no deny row sits under the tree, is refused naming that row when one does, and still raises ONE mandated approval when the tree is too large to enumerate; an **out-of-root read** is unchanged and still raises exactly ONE mandated approval (no stored rule and no auto-mode classifier can clear it). Non-readers (`rm`/`curl`/`git`/`npm`) and ordinary in-workspace reads stay unasked on that lane. This knob only ever raises that baseline — it has no relax half, so `off` is accepted as an explicit **no-op** (a boot line says so; not symmetric with `SENSITIVE_WRITE_PATTERNS=off`, which really does clear a set). **Any other value refuses to start** (7.12.0, BREAKING for a deployment that had a typo: it was previously treated as unset, i.e. silently no gate at all) |
|
|
174
174
|
| `PERMISSIONS_DISABLE_AUTO_MODE` | `false` | Local mirror of CC `permissions.disableAutoMode` — the **org deny** bit for `permissionMode:"auto"` (paired with core's intent-arming rule). **Tighten-only**: `true` folds every principal's `runtimeCaps.autoMode` to `false` (a center grant cannot flip it back); unset leaves caps untouched, so on a center-less box a shell asking for `auto` **arms** the classifier once the paired core (intent-arming rule "requested ∧ classifier seat ∧ `autoMode !== false`" — absence is not a deny) is installed; on core 7.2.0 the engine still uses the old "org grant" rule (`permissionModeAuto.intentArming:false`), so the self-check answers `armed:true` only for a center-granted principal and `deployment_incapable` on a center-less box. Boolean word table; any other value refuses to start. Self-check: `GET /v1/capabilities?permissionMode=auto` → `permissionModeAuto.{armed, reason, model}` (USAGE §9.4) |
|
|
175
175
|
| `SCRATCHPAD_SWEEP_TTL_MS` | 7 days | Idle-reap window for per-session scratchpad dirs (by dir mtime; `0` disables). The scratchpad is **ephemeral by contract**: replica-local disk, NOT part of the durable-suspend persistence set — a resume on a different replica, or after a sweep, starts with an empty dir (same two-track posture as the Agent SDK hosting doc: conversation persists, working-directory artifacts don't). Raise/disable only on single-replica deployments that park approvals for longer than the window |
|
|
176
176
|
| `MODEL_CONNECT_TIMEOUT_MS` | `30000` | Gateway connect timeout |
|
package/USAGE.md
CHANGED
|
@@ -681,6 +681,14 @@ READ_DENY_PATTERNS='[{"pattern":"**/secrets/**"},{"pattern":".ssh","caseSensitiv
|
|
|
681
681
|
零卡执行。**要老姿态就显式写** `READ_DENY_BUILTIN_TIERS=credentials,browser,wallet,agent-config`
|
|
682
682
|
(运维面全文见 `docs/DEPLOY-PREREQS.md` 的 core 7.22.0 段)。`open` 与这张表仍是**两个并列机制**:
|
|
683
683
|
`open` 放开的是「工作区外」那道围栏,内建表判的是名字 —— 只是后者现在默认空着。
|
|
684
|
+
· 🔴 **命中这三张表的结局自 core 7.25.0 起是「拒」,不是「问」**(三只旋钮的名字 / 语义 / 缺席读法一字
|
|
685
|
+
未变,变的是结局词):一条命中拒表的读在**每张脸每个时刻**都被拒 —— 不出审批卡、持久 allow 规则
|
|
686
|
+
清不掉、值守的人点「同意」也放不掉。拒因在 `tool_end` 上:`errorCode` 与 `structured` 都是
|
|
687
|
+
`read_path_denied`(带 `target` 与**命中的那条 pattern**),`gate.disposition.deniedBy = "read_boundary"`。
|
|
688
|
+
同批**根内的内容遍历**(`grep -r` / `diff -r` / `rg`)改成**枚举**:树下没有拒表行 ⇒ 零卡执行,
|
|
689
|
+
有 ⇒ 点名那一行拒。⇒ **要让某条路径重新读得到,就把它从这三张表里拿掉**(删掉那条自配模式 / 档位
|
|
690
|
+
不选 / 用 `READ_DENY_BUILTIN_EXCLUDE` 删掉那一行);三问与运维面全文见
|
|
691
|
+
`docs/DEPLOY-PREREQS.md` 的 `core 7.25.0 提货` 段。
|
|
684
692
|
· 「这一档到底是谁定的」由 operator 面 `GET /v1/diagnostics/wiring` 的 `readFace` 段答:
|
|
685
693
|
`{face, source, note}`,`source` ∈ `env`(本机 env 显式)/ `center`(组织下发)/ `posture`(单机派生)/
|
|
686
694
|
`engine-default`(没钉,引擎当家),`note` 是指路句。
|
|
@@ -23,7 +23,7 @@ export declare function canBatchTransition(from: BatchState, to: BatchState): bo
|
|
|
23
23
|
* 被推翻)—— 这段注释是「为什么是这五个部件」的唯一书面记录:
|
|
24
24
|
*
|
|
25
25
|
* ── `runId`(新增轴,封死跨 run 复用)────────────────────────────────────────────────────────────────
|
|
26
|
-
* core 给 `AskRequest.sourceTaskId` 填的是 **sessionId**(`prepare-task.js
|
|
26
|
+
* core 给 `AskRequest.sourceTaskId` 填的是 **sessionId**(`prepare-task.js` 的 `askSourceIdentity`),
|
|
27
27
|
* 不是 wire run id。而原轴给每条新 run 的首腿都重置 `leg=0` ⇒ 同一会话的**下一次全新提交**若撞上同一
|
|
28
28
|
* `toolCallId`([ref] 定稿 §3.1 记录了部分网关按**请求编号**而非全局编号铸它)会整组重现,
|
|
29
29
|
* `ensureAsk` 的幂等 upsert 于是命中上一 run 已 `DECIDED(approve)` 的旧行并把它回放成 `true` ——
|
|
@@ -134,7 +134,7 @@ export interface ReconcileStats {
|
|
|
134
134
|
* (下面每 tick 一条 warn),并被写成协议开关翻真的前置判据之一。
|
|
135
135
|
*
|
|
136
136
|
* ✅ **该前置判据已销账**([ref] 翻真验证车,2026-08-07 亲验 core 5.15.0):字段在
|
|
137
|
-
* `core/tool-policy.d.ts
|
|
137
|
+
* `core/tool-policy.d.ts`,真码 `core/tool-policy.js` 每一只 ask 都填 ——
|
|
138
138
|
* 判据 1 现在会真命中(钉:test/stream-approval-on-e2e.test.ts 件4 末条,同构两行只差 hash 一位,
|
|
139
139
|
* 有 hash 的 bind 成 PARKED、无 hash 的进本计数)。
|
|
140
140
|
*
|
package/dist/approval.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export declare function assertGateIntentServiceable(config: {
|
|
|
43
43
|
* [ref] 件⑩ —— **center 的 `forceDurableGate` 在无 checkpoint 店的部署上会静默 inert**,补一条 boot 披露。
|
|
44
44
|
*
|
|
45
45
|
* 事实链(core 7.2.0 亲读):`RuntimeCaps.forceDurableGate:true` 让引擎**合成**一个
|
|
46
|
-
* `durableApproval{scope: principal}`(`dist/core/types.d.ts
|
|
46
|
+
* `durableApproval{scope: principal}`(`dist/core/types.d.ts`),好让 policy 的 `ask` 挂到
|
|
47
47
|
* durable wire 上。但「挂得上去」的前提是这台 worker 真有 checkpoint 店 —— 而本仓的构造条件逐字是
|
|
48
48
|
* `backend?.checkpoint && config.durableApproval`(main.ts)。`DURABLE_APPROVAL` 关 ⇒ 店缺席 ⇒ 合成出来
|
|
49
49
|
* 的那把锁**没有锁孔**:core 侧退化成非 durable 的 ask 链,center 那条「fleet-wide interactive runs gate」
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* from scoring), never a withhold-credit.
|
|
19
19
|
*
|
|
20
20
|
* 🔴 The repair leg MUST still pass `workerEnv` to `runRepairLoop` (NOT just here) — the §5.1 identity check is
|
|
21
|
-
* SKIPPED when `workerEnv` is undefined (`repair-loop.d.ts
|
|
21
|
+
* SKIPPED when `workerEnv` is undefined (`repair-loop.d.ts`), silently disabling the anti-reward-hack guard.
|
|
22
22
|
* arms.ts is responsible for that; this adapter only wires the oracle closure.
|
|
23
23
|
*/
|
|
24
24
|
import type { ExecutionEnv, RepairOracle } from "@sema-agent/core";
|
|
@@ -56,7 +56,7 @@ export interface BenchBudget {
|
|
|
56
56
|
/**
|
|
57
57
|
* An injectable deterministic clock (SPEC §2). EVERY Runner in EVERY arm is built with `now: ctx.now`, and the
|
|
58
58
|
* reviewer's modeled think-time advances `advanceClock(ms)` — so `stats.humanReview.totalWaitMs` is reproducible
|
|
59
|
-
* and NEVER consumes real wall-time (`RunnerDeps.now`, core types.d.ts
|
|
59
|
+
* and NEVER consumes real wall-time (`RunnerDeps.now`, core types.d.ts / [ref] §4.3 flaky-guard).
|
|
60
60
|
*
|
|
61
61
|
* 🔴 CLOCK-ADVANCE OWNERSHIP (review #10 — HARNESS-OWNS-CLOCK, the single fold point per arm): the modeled review
|
|
62
62
|
* wait is folded into ctx.clock by the HARNESS exactly once per arm — SUP in driveSupSuspendResume (per leg), TEAM
|
|
@@ -64,7 +64,7 @@ export declare function createRunnerDepsOnAsk(toolApproval: ToolApprovalCoordina
|
|
|
64
64
|
export declare const CONFIG_ADVISORY_CLASSIFICATIONS: ReadonlySet<string>;
|
|
65
65
|
/** S-386:core 的 [ref] 前缀缓存**断裂**探测器交给宿主的根因**闭集**,以及每个词的日志级别。
|
|
66
66
|
* 闭集**不抄词**:词源直接取 core 公开导出的 `CacheBreakFinding["cause"]`(`@sema-agent/core` 根导出,
|
|
67
|
-
* `dist/index.d.ts
|
|
67
|
+
* `dist/index.d.ts`;实现在 `dist/core/cache-break-detector.d.ts`),于是 core 加词/删词 ⇒
|
|
68
68
|
* `Record<PromptCacheBreakCause, …>` 少键/多键 = **编译错**(CLAUDE.md [ref]「词表是闭集 + 穷举」)。
|
|
69
69
|
* ⚠️ 首版在这里**手抄**了五个字面量,并在本注与 CHANGELOG 里声称「core 加词 = 编译错」—— 复审实测
|
|
70
70
|
* 证伪(给 core d.ts 加词后 tsc 照绿,只有一格正则扫描会红):抄来的联合与 core 无编译期联系。
|
|
@@ -107,8 +107,8 @@ export interface RunnerDepsCtx {
|
|
|
107
107
|
*
|
|
108
108
|
* 本键从「差异键」升进{@link createSharedRunnerDeps 共享基座}。修前它**只**挂在 subRunner 上,主
|
|
109
109
|
* runner 靠 per-task `spec.checkpointStore` 拿店 —— 而 core 的 `run_workflow` 是挂在**主** Runner
|
|
110
|
-
* (`prepare-caps-and-workflow.js
|
|
111
|
-
*
|
|
110
|
+
* (`prepare-caps-and-workflow.js` 的 `runner: runnerSelf`;⚠️ S-516:这里**不写 dist 行号** ——
|
|
111
|
+
* B-060 票据当年引的那组坐标每版都在漂,复核一律按符号名搜)上的。
|
|
112
112
|
*
|
|
113
113
|
* ⚠️ **B-060 当初写下的病理有一半已被 core 7.14.0 消解,如实订正**([ref] 的 `CheckpointSeat` 重构):
|
|
114
114
|
* · **7.13.0 及以前**:`orchestration/workflow-primitives.js` 只在父侧
|
|
@@ -117,16 +117,16 @@ export interface RunnerDepsCtx {
|
|
|
117
117
|
* ⇒ `parkLane {capable:false, reasons:["no_checkpoint_store"]}` ⇒ 受门的调用不是 park 而是
|
|
118
118
|
* `delegation.ask_unresolvable{parkLaneExisted:false}` 直接拒(过度拒绝)。
|
|
119
119
|
* · **7.14.0 起**:core 在 prepare 一次铸出 `checkpointSeat = checkpointSeatOf(spec, resolved)`
|
|
120
|
-
* (`prepare-task.js
|
|
121
|
-
* `parentCheckpointStore: checkpointSeat`(`prepare-caps-and-workflow.js
|
|
122
|
-
* `workflow-primitives.js
|
|
120
|
+
* (`prepare-task.js`),两条委派车道**逐字复制**它到子代 —— workflow 腿经
|
|
121
|
+
* `parentCheckpointStore: checkpointSeat`(`prepare-caps-and-workflow.js`)落到
|
|
122
|
+
* `workflow-primitives.js`(现在的条件是「父侧有座 ∧(座是 disabled ∨ 子 spec 没自带店)」⇒
|
|
123
123
|
* **真店也透传**),Task 腿经 `ToolExecuteContext.checkpointStoreForChildren`。
|
|
124
124
|
* ⇒ 「子代拿不到店」这条病路 core 侧已经堵死。**但本席仍然承重**,理由是那只座**本身**就从
|
|
125
125
|
* `resolveCheckpointStore(spec, deps)` 来:主 runner 这一格空着、而 spec 又没带店时,座就是
|
|
126
126
|
* `undefined`,子代照样什么都拿不到。本席不是 core 单源的第二个写者,它就是 core 读的那个 deps 格。
|
|
127
127
|
*
|
|
128
128
|
* 🔴 host 腿的读数判据是**core 侧硬的**,不是本仓的但愿:`resolveCheckpointStore`
|
|
129
|
-
* (`checkpoint-store.js
|
|
129
|
+
* (`checkpoint-store.js`,7.14.0 亲核)**spec 优先**——`spec.checkpointStore` 在场就用它、
|
|
130
130
|
* `"disabled"` 就是关、只有缺席才落 deps。而本仓 host 腿恒经 `resolveSpec` 的 durableEnabled 分支盖上
|
|
131
131
|
* **同一个实例**,且 `durableEnabled ⇔ checkpointStore !== undefined`(`boot/coordinators.ts:70` 与
|
|
132
132
|
* main.ts 的构造条件同源)⇒ 每一条经 `resolveSpec` 的腿看到的店逐字同一只。
|
|
@@ -138,7 +138,7 @@ export interface RunnerDepsCtx {
|
|
|
138
138
|
* 哪天有人给这两处接了不同的店,失败是响亮的而不是静默半场。
|
|
139
139
|
*
|
|
140
140
|
* ⚠️ **如实登记一处不经解析器的直读**(codex 交叉复审提名,亲读 core dist 确认):
|
|
141
|
-
* `core/runner/run-terminal-adoption.js
|
|
141
|
+
* `core/runner/run-terminal-adoption.js`(7.14.0 亲核)读的是
|
|
142
142
|
* `runner.deps.checkpointStore` **本身**,不走 `resolveCheckpointStore`,**也不走座** —— 所以 7.14.0
|
|
143
143
|
* 的座重构对它一个字节都没改,本席对它仍是唯一来源。它是「宿主任务 park 时,把没排空的 steer /
|
|
144
144
|
* follow-up 迁移到 park token 上」那一步 —— 修前主 runner 这一格是空的,于是**整段迁移静默不跑**
|
|
@@ -305,7 +305,7 @@ export declare function createEngineNoticeSeat(logger: {
|
|
|
305
305
|
*
|
|
306
306
|
* 纯数据(键值全是 config 透传,无闭包)⇒ `build*`(CLAUDE.md 工厂律)。键**恒在场**、缺席 = `undefined`
|
|
307
307
|
* ([ref] S4 house style;core 消费点全按 `!== undefined` / `??` 判 —— dist
|
|
308
|
-
* `prepare-hands-readface.js
|
|
308
|
+
* `prepare-hands-readface.js`、`prepare-config-doors.js`、`prepare-task.js` 亲读,
|
|
309
309
|
* 「键为 undefined」与「键缺席」对引擎逐字等价)。**不复制引擎缺省**(`roots`/`static-face`/`carry`/`open`/
|
|
310
310
|
* CC parity 20/200 一个都不抄):上游改缺省之日起本仓不会成为第二份真源。
|
|
311
311
|
*/
|
|
@@ -18,19 +18,36 @@
|
|
|
18
18
|
* roster 把落盘路**整批**卸掉时,手带在场也等于没有写通道),但负控仍然成立:必须**全部**已知落盘路
|
|
19
19
|
* 都不在才算断链 —— `excludeTools:["Write"]` 不算(Bash/Edit 还在),请求面摘一个工具名改不动结论。
|
|
20
20
|
*/
|
|
21
|
-
import { type PromptProvider } from "@sema-agent/core";
|
|
21
|
+
import { type PromptProvider, type ToolCatalogEntry } from "@sema-agent/core";
|
|
22
22
|
import type { ScenarioHands } from "./hands-lane.js";
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* 「已知落盘路」的判据,作用在 core **静态工具目录**的一行上(S-503 / [ref])—— 与 core 自己的
|
|
25
|
+
* `rosterCanPersist` 推断逐条同形,取的是同一批目录属性,不是它结论的抄件:
|
|
26
|
+
* · `mountedBy` 落在 core 的 `HANDS_BAND_TAGS` 里 —— 手带 band 之外的定义(记忆面工具、调度族)
|
|
27
|
+
* 不在本判别式说得着的面上:本判别式的输入全是**手带事实**(`hands` / `handsReadOnly` /
|
|
28
|
+
* `excludeTools`),把一只 band 外的写工具算进来,就是拿一条根本没挂的通道去撤回披露(fail-open);
|
|
29
|
+
* · `face.effect !== "read"`(缺席读 `write`,与 core 同缺省)—— 只读 shell 壳在这里出局,它与全壳
|
|
30
|
+
* 共用 `Bash` 这个 wire 名,靠的正是这一位分开;
|
|
31
|
+
* · `face.pathTarget.access !== "read"`(文件写工具)**或** `face.family === "shell"`(可写 shell)。
|
|
28
32
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
33
|
+
* 🔴 这条析取式**不是** effect=write 的全集:core 逐字裁过「a generic write-effect tool does NOT count
|
|
34
|
+
* — a mail sender's side effect is not a memory store」。`TaskStop` 正是那种(effect=write、没有路径
|
|
35
|
+
* 目标、不是 shell),它在这条判据下自己就出局 —— 改前那张手抄名单要靠人记得别把它写进去。
|
|
36
|
+
*
|
|
37
|
+
* 一条都派生不出来 ⇒ **抛**(不是回一个空集):空集会让下面那条判据读成「一条落盘路都不剩」⇒ 每台
|
|
38
|
+
* 有手的机器都开始披露「本部署没有持久记忆」,而那句话在那时是假的。目录换形是提货期事实(静态数据,
|
|
39
|
+
* 与请求/环境无关:要么恒抛要么恒不抛),所以这一抛是**提货门**,不是运行期风险。
|
|
40
|
+
*/
|
|
41
|
+
export declare function persistencePathToolNamesOf(entries: readonly ToolCatalogEntry[]): readonly string[];
|
|
42
|
+
/**
|
|
43
|
+
* 「已知落盘路」的工具名闭集 —— **由 core 的静态工具目录派生**,本仓不再存第二份名表
|
|
44
|
+
* (S-503 / [ref];改前是手抄的四名表 Write / Edit / NotebookEdit / Bash,没有任何差集门)。
|
|
45
|
+
*
|
|
46
|
+
* 为什么手抄在这一位上特别危险:core 改名/加一只写工具,手抄表静默失配成「没有一个写工具被排除」
|
|
47
|
+
* (fail-open 方向)—— `excludeTools` 把真正的落盘路全排掉了,而我们对着一张旧名单答「还剩」。
|
|
48
|
+
* 判据的属主现在是 {@link persistencePathToolNamesOf},值的属主是 core 的目录行。
|
|
32
49
|
*/
|
|
33
|
-
export declare const KNOWN_PERSISTENCE_TOOL_NAMES: readonly [
|
|
50
|
+
export declare const KNOWN_PERSISTENCE_TOOL_NAMES: readonly string[];
|
|
34
51
|
/** 披露段在 `prompt.assembled` 清单里的段 id(`core/` 命名空间是 core 保留的,部署段一律自家前缀)。 */
|
|
35
52
|
export declare const NO_PERSISTENT_MEMORY_SECTION_ID = "sema/memory.no-persistent";
|
|
36
53
|
/** 判别式的全部输入 —— 每一位都是**部署/装配期事实**,没有一位来自请求体的自我表态。 */
|
|
@@ -55,7 +72,7 @@ export interface MemoryFaceFacts {
|
|
|
55
72
|
* (core `prepareDeferClassify` 对它直接 `roster.unmountWhere(() => true)`)⇒ 手带在场也一条落盘路都不剩。
|
|
56
73
|
* 🔴 它与下面那张名单是**同一个语义面**(「哪些工具不在 roster 上」)的两种声明,判据合成一只
|
|
57
74
|
* {@link isUnmounted} —— 上游同版的姊妹谓词 `fullShellReachable` 也正是把 `spec.excludeAllTools !== true`
|
|
58
|
-
* 当一个合取项(`prepare-inherited-gate.js
|
|
75
|
+
* 当一个合取项(`prepare-inherited-gate.js` 亲读),本仓与它同口径。 */
|
|
59
76
|
excludeAllTools: boolean;
|
|
60
77
|
/** 折完之后的最终 `TaskSpec.excludeTools`(roster 真卸载,不是延迟披露)。**全部**已知落盘路都被排掉
|
|
61
78
|
* ⇒ 手带在场也等于没有写通道 —— core 的 `rosterCanPersist` 也是按排除后的 roster 算的,这里跟上。 */
|
|
@@ -1,6 +1,25 @@
|
|
|
1
|
-
import { NO_PERSISTENT_MEMORY_NOTICE } from "@sema-agent/core";
|
|
1
|
+
import { describeToolCatalog, HANDS_BAND_TAGS, NO_PERSISTENT_MEMORY_NOTICE } from "@sema-agent/core";
|
|
2
2
|
import { hasConstitutionAnchors } from "../task-settings.js";
|
|
3
|
-
export
|
|
3
|
+
export function persistencePathToolNamesOf(entries) {
|
|
4
|
+
const names = new Set();
|
|
5
|
+
for (const e of entries) {
|
|
6
|
+
if (!e.mountedBy.some((tag) => HANDS_BAND_TAGS.has(tag)))
|
|
7
|
+
continue;
|
|
8
|
+
if ((e.face.effect ?? "write") === "read")
|
|
9
|
+
continue;
|
|
10
|
+
const pt = e.face.pathTarget;
|
|
11
|
+
if (!((pt !== undefined && pt.access !== "read") || e.face.family === "shell"))
|
|
12
|
+
continue;
|
|
13
|
+
names.add(e.name);
|
|
14
|
+
}
|
|
15
|
+
if (names.size === 0) {
|
|
16
|
+
throw new Error("core tool catalog yields no hand-band persistence path (file-write target or writable shell) — " +
|
|
17
|
+
"the #148 disclosure would start claiming 'no persistent memory' on every machine that HAS hands. " +
|
|
18
|
+
"Re-read core's rosterCanPersist inference and re-derive persistencePathToolNamesOf.");
|
|
19
|
+
}
|
|
20
|
+
return Object.freeze([...names]);
|
|
21
|
+
}
|
|
22
|
+
export const KNOWN_PERSISTENCE_TOOL_NAMES = persistencePathToolNamesOf(describeToolCatalog());
|
|
4
23
|
export const NO_PERSISTENT_MEMORY_SECTION_ID = "sema/memory.no-persistent";
|
|
5
24
|
function isUnmounted(facts, name) {
|
|
6
25
|
return facts.excludeAllTools || (facts.excludeTools?.includes(name) ?? false);
|
package/dist/config-catalog.js
CHANGED
|
@@ -2,6 +2,7 @@ import { CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES } fr
|
|
|
2
2
|
import { THINKING_FORMATS } from "./model-compat.js";
|
|
3
3
|
import { RESILIENCE_DEFAULTS } from "./config-types.js";
|
|
4
4
|
import { webSearchConfigFromEnv, WEB_SEARCH_PROVIDERS } from "./plugins/web-search.js";
|
|
5
|
+
import { AUTONOMY_MODES } from "./runtime-governance.js";
|
|
5
6
|
import { REMOTE_EXEC_PROVIDER_WORDS } from "./execution-lane-caps.js";
|
|
6
7
|
import { CENTER_READ_FACE_LIVE_KEYS } from "./config-center/read-face.js";
|
|
7
8
|
import { MODEL_PROVIDER_WORDS } from "./model-provider.js";
|
|
@@ -75,7 +76,7 @@ export const CONFIG_CATALOG = [
|
|
|
75
76
|
r("AUTH_BRIDGE_AUDIENCE", "auth", "string", "registry SSO-JWT 验签期望 aud", { danger: SEC }),
|
|
76
77
|
r("AUTH_BRIDGE_ISSUER", "auth", "url", "registry SSO-JWT issuer(auth bridge 的开关之一)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 用 configCenter.baseUrl(配了 center 即默认开桥)" }),
|
|
77
78
|
r("AUTH_BRIDGE_JWKS_URL", "auth", "url", "registry SSO-JWT 的 JWKS 地址(缺省从 issuer 派生)", { danger: SEC }),
|
|
78
|
-
r("AUTONOMY", "auth", "enum", "运维 autonomy 模式,tighten-only 叠到每个 TaskSpec(read-only/plan ⇒ 只读手,ask ⇒ 恒 shellGate);center 腿热且 non-sticky", { danger: SEC, enumValues:
|
|
79
|
+
r("AUTONOMY", "auth", "enum", "运维 autonomy 模式,tighten-only 叠到每个 TaskSpec(read-only/plan ⇒ 只读手,ask ⇒ 恒 shellGate);center 腿热且 non-sticky", { danger: SEC, enumValues: AUTONOMY_MODES, configKey: "autonomy", center: cGov("autonomy") }),
|
|
79
80
|
r("AUTO_MODE_CLASSIFIER_FALLBACK", "approval", "enum", "auto 分类器席答不出裁决时的梯子(main=主模型代判 / none=只留分类器那一级,整条拒)", { enumValues: ["main", "none"], danger: SEC, derivedDefaultNote: "未设/空串 ⇒ 不铸键,跟随引擎缺省 main(core 7.18.0 起;空串按未设,与其余词表旋钮同判);none = 7.77.0 及以前的行为逐字;词表外的值启动期拒启。权限轴放宽,升级须知见 DEPLOY-PREREQS", configKey: "autoModeClassifierFallback" }),
|
|
80
81
|
r("BAKE_CACHE_BASE", "orchestration", "url", "烤镜像 fetch-once 缓存源 base(注入 --cache-base;绝不受调用方控制)"),
|
|
81
82
|
r("BAKE_IMAGE_REGISTRY", "orchestration", "string", "烤镜像自动登记的 registry host 前缀(repo=registry/profile)"),
|
|
@@ -253,7 +253,7 @@ export interface EffectiveConfig {
|
|
|
253
253
|
version: number;
|
|
254
254
|
updatedAt: string;
|
|
255
255
|
/** [ref]B-6:default/tierGroups/activeTierGroup 收进类型面(registry-core 0.13.0 已声明,
|
|
256
|
-
* types.d.ts
|
|
256
|
+
* types.d.ts)——此前 wire 面靠「JSON verbatim + 消费方结构读」逃逸,投影/消费两侧
|
|
257
257
|
* 各自 as 宽铸。tierGroups 上游是 ZodDefault(合规 center 恒发),但本类型描述的是**到货 JSON**
|
|
258
258
|
* (含非合规/旧存量 center),故仍标 optional——消费方 resolveActiveTiers 自身容缺。 */
|
|
259
259
|
models: {
|
package/dist/config.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare function parseAutonomy(raw: string | undefined): Autonomy | undef
|
|
|
16
16
|
/** [ref]-T1 — parse `USAGE_WINDOWS`(单键 JSON 数组;逐窗散键在窗数可变时组合爆炸,JSON 是唯一
|
|
17
17
|
* 封闭形)。unset/空串 → undefined(关)。fail-loud 族(numEnv 同纪律):坏 JSON/非数组/空数组/
|
|
18
18
|
* 坏值/未知 anchor 全部 boot throw 点名——「设了但无意义」静默关掉=部署方以为有治理实际没有。
|
|
19
|
-
* [ref](core 7.0.0 `UsageWindow.maxCostUsd`,dist/core/usage-window-store.d.ts
|
|
19
|
+
* [ref](core 7.0.0 `UsageWindow.maxCostUsd`,dist/core/usage-window-store.d.ts 亲读):
|
|
20
20
|
* `maxTokens` 与 `maxCostUsd` 是两只**独立** ceiling——each optional on its own、**at least one
|
|
21
21
|
* required**(双缺=拒:a window with neither admits nothing it can govern),whichever fills first
|
|
22
22
|
* 即耗尽;缺席的 ceiling 不参与(非 0、非 ∞)。修前这里的白名单重铸只认三键,$ 键配了也被静默
|
package/dist/env-facts.d.ts
CHANGED
|
@@ -117,7 +117,7 @@ export declare function screenShellScratchpadDir(raw: unknown, opts: {
|
|
|
117
117
|
* ## 病形(引擎侧的硬拒,不是判决期的宽严)
|
|
118
118
|
* 写门的豁免域就是 scratchpad(`exemptDirs: [scratchpadDir]`,`task-settings.ts` 的
|
|
119
119
|
* `createFsWriteGatePolicy` 那一句),而门的根是这条 task 的 cwd。core 7.20.0 起,**豁免域等于根或是
|
|
120
|
-
* 根的祖先 ⇒ 构造期抛**(亲读安装树 `dist/core/fs-write-gate-policy.js
|
|
120
|
+
* 根的祖先 ⇒ 构造期抛**(亲读安装树 `dist/core/fs-write-gate-policy.js`,
|
|
121
121
|
* `code = "config.write_exempt_dir_covers_root"`)——理由逐字:一条盖住根的豁免把根下**每一次写**都自动
|
|
122
122
|
* 批准,那就是整个工作区,门宁可拒绝被建出来,也不肯带着一扇静默关掉的写治理去跑。
|
|
123
123
|
*
|
|
@@ -159,7 +159,7 @@ export interface LaneHomeDeployment {
|
|
|
159
159
|
* 「**这条部署的执行环境**以哪个 home 运行」—— `~/…` 形权限规则(`Edit(~/.ssh/**)`)唯一的基。
|
|
160
160
|
*
|
|
161
161
|
* ## 病(接入审计 M13 / DEBTS S-213④)
|
|
162
|
-
* core 的 `ExecutionEnv.homeDir` 是**由 adapter 声明**的座(`engine/harness/types.d.ts
|
|
162
|
+
* core 的 `ExecutionEnv.homeDir` 是**由 adapter 声明**的座(`engine/harness/types.d.ts`,可选):
|
|
163
163
|
* 缺席 ⇒ core 的 `resolvePathPattern` 回 `{missingBase:"home"}` ⇒ 该规则对这条腿判 `unreadable`
|
|
164
164
|
* (fail-closed 的 ask)。四只远端 adapter(e2b/k8s/host/ssh)一个都没声明,而 server 侧的 422 前置门
|
|
165
165
|
* (`task-settings.ts` 的 `rejectUnmatchableSettingsNames`)在 home 未知时整表拒 `~/` 形 ——
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* 就只剩散文,**机器一位都读不到**。两条腿各自的后果:
|
|
9
9
|
*
|
|
10
10
|
* · **snapshot(边界捕获)腿**:引擎把失败包成一句散文交给 `RunnerDeps.onError({phase:"rewind"})`
|
|
11
|
-
* (core 7.20.0 `dist/core/runner/prepare-file-history.js
|
|
11
|
+
* (core 7.20.0 `dist/core/runner/prepare-file-history.js` 的 `wrapFileHistoryEntry`),服务那条臂从文本里抠**第一个**
|
|
12
12
|
* 括号词当 metric label —— 而第一个括号词恒是引擎自己的闭集码。实测:「这个文件永远存不进这口店」
|
|
13
13
|
* 与「连接被重置」在 `rewind_snapshot_failed_total` 上**同形** `code="snapshot_failed"`。
|
|
14
14
|
* · **trackEdit(首触备份)腿**:本服务用引擎默认的 `onTrackFailure:"refuse"`(`boot/runner-deps.ts` 亲读),
|
|
15
15
|
* 那条路 **完全不走 onError** —— 引擎把 `(track_failed: <本店 message>)` 念给模型,末句是
|
|
16
|
-
* 「Retry when the file-history store is healthy.」(`prepare-file-history.js
|
|
16
|
+
* 「Retry when the file-history store is healthy.」(`prepare-file-history.js`)。对这枚码那是永远
|
|
17
17
|
* 不会到来的一天,而**运维面一个计数都没有**:这条腿今天在遥测上完全不可见。
|
|
18
18
|
*
|
|
19
19
|
* ## 修法(源头留痕,不走文本通道)
|
|
@@ -9,7 +9,7 @@ import type { AssertAllKeysHandled } from "../trace/core-keyset-guard.js";
|
|
|
9
9
|
*
|
|
10
10
|
* 🔴 [ref]([ref]):判别**本体已换 core 的 {@link isDelegatedAgentTerminal}**。此前这里是一份手写语义
|
|
11
11
|
* 孪生(自己枚举 `completed|failed|killed|cancelled`),头注自陈「与 core 语义同源——core 根导出后换装
|
|
12
|
-
* import 删本镜像」。core 现已从包根导出(`index.d.ts
|
|
12
|
+
* import 删本镜像」。core 现已从包根导出(`index.d.ts`),且它的终态集与 pending 店的驱逐偏好共用
|
|
13
13
|
* 同一份 `TERMINAL_STATUSES`(core 原话:「so "terminal" can never mean two things in this module」)——
|
|
14
14
|
* 孪生留在这里等于让 fleet 面板对「终态」的理解可以和引擎分家。本函数保留为**具名的 fleet 语义位**
|
|
15
15
|
* (调用点读的是「这条通知该不该打 fleet 的 onChildTerminal」,不是「core 怎么判终态」)。 */
|
|
@@ -49,6 +49,12 @@ export interface FleetTaskRow {
|
|
|
49
49
|
* `name` (the objective-preview). Populated TODAY on SUBAGENT CHILD rows, where `task_progress.name` IS the type
|
|
50
50
|
* (BC-2). SECRET-SAFE: redacted before emit, same discipline as `name`. */
|
|
51
51
|
agentType?: string;
|
|
52
|
+
/** 队友的**实例名** = `Agent({name})` 的显式出生名。源 = core 7.25.0 的 `BackgroundChildEvent.spawnName`
|
|
53
|
+
* ([ref] S0;spawn/tick/terminal 三帧都带,出生点按 `^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$` 校验过、永不 `main`)。
|
|
54
|
+
* 🔴 与 `name` 分域:`name` 是**显示名**(源 `description ?? name`,模型自由文本);本键是**身份键**,
|
|
55
|
+
* 消费端按串相等把「@某个名字」绑到这一行(同类型两个子代在 `agentType` 上不可分,在本键上可分)。
|
|
56
|
+
* ADDITIVE / tolerate-absent:**缺席 = 一次匿名委派**(不是「不知道它叫什么」)—— 7.25.0 之前本键在本仓
|
|
57
|
+
* 没有任何源,恒缺席。SECRET-SAFE:与 `name` 同一道边界(emit 前 redact)。 */
|
|
52
58
|
agentName?: string;
|
|
53
59
|
/** Owning principal (= the run's scope). The `/v1/fleet/stream` route filters to the caller's scope (owner-gated)
|
|
54
60
|
* unless the caller is fleet-wide (operator / trace-token). NOT rendered — a tenant-isolation field. */
|
package/dist/fleet/fleet-bus.js
CHANGED
|
@@ -463,8 +463,8 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
463
463
|
};
|
|
464
464
|
remember(e.taskId, m);
|
|
465
465
|
claimIfKnown(e, m);
|
|
466
|
-
const
|
|
467
|
-
bus.publishTask({ id: e.taskId, ...(
|
|
466
|
+
const displayName = e.description ?? e.name;
|
|
467
|
+
bus.publishTask({ id: e.taskId, ...(displayName !== undefined ? { name: redactSecrets(displayName) } : {}), ...(e.agentType ? { agentType: redactSecrets(e.agentType) } : {}), ...(e.spawnName ? { agentName: e.spawnName } : {}), ...rowTags(m), status: "running", tokens: 0, ...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}), ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}) });
|
|
468
468
|
dlog("bg_child_event", { kind: "spawn", taskId: e.taskId, sessionScoped: e.sessionScoped, scope: m.tenantScope, hostSessionId: m.hostSessionId ?? null, parentTaskId: m.parentTaskId ?? null });
|
|
469
469
|
};
|
|
470
470
|
const handleTick = (e, m) => {
|
|
@@ -488,16 +488,18 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
488
488
|
const tokens = sumUsage(e.usage);
|
|
489
489
|
const name = e.name ? redactSecrets(e.name) : undefined;
|
|
490
490
|
const agentType = e.agentType ? redactSecrets(e.agentType) : undefined;
|
|
491
|
+
const agentName = e.spawnName;
|
|
491
492
|
const currentAction = e.currentAction ? redactSecrets(e.currentAction) : undefined;
|
|
492
493
|
const currentTool = e.currentTool
|
|
493
494
|
? { toolName: e.currentTool.toolName, ...(e.currentTool.target !== undefined ? { target: redactSecrets(e.currentTool.target).slice(0, 300) } : {}) }
|
|
494
495
|
: undefined;
|
|
495
|
-
bus.publishTask({ id: e.taskId, ...rowTags(m), status: "running", ...(tokens !== undefined ? { tokens } : {}), ...(name ? { name } : {}), ...(agentType ? { agentType } : {}), ...(currentAction ? { currentAction } : {}), ...(currentTool ? { currentTool } : {}), ...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}), ...(e.usage?.toolUses !== undefined ? { toolUses: e.usage.toolUses } : {}), ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}) });
|
|
496
|
+
bus.publishTask({ id: e.taskId, ...rowTags(m), status: "running", ...(tokens !== undefined ? { tokens } : {}), ...(name ? { name } : {}), ...(agentType ? { agentType } : {}), ...(agentName ? { agentName } : {}), ...(currentAction ? { currentAction } : {}), ...(currentTool ? { currentTool } : {}), ...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}), ...(e.usage?.toolUses !== undefined ? { toolUses: e.usage.toolUses } : {}), ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}) });
|
|
496
497
|
};
|
|
497
498
|
const handleTerminal = (e, m) => {
|
|
498
499
|
const status = e.status === "completed" ? "completed" : e.status === "killed" ? "killed" : "failed";
|
|
499
500
|
bus.publishTask({
|
|
500
501
|
id: e.taskId, ...rowTags(m), status,
|
|
502
|
+
...(e.spawnName ? { agentName: e.spawnName } : {}),
|
|
501
503
|
...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}),
|
|
502
504
|
...(e.stoppedBy ? { stoppedBy: e.stoppedBy } : {}),
|
|
503
505
|
...(e.resumable !== undefined ? { resumable: e.resumable } : {}),
|
package/dist/http/admission.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { HttpError } from "../security.js";
|
|
|
9
9
|
import type { PreparedTaskSubmission, RouteHelpers } from "./route-ctx.js";
|
|
10
10
|
import type { FlatServiceDeps } from "./server.js";
|
|
11
11
|
/** = core `SKILL_CONTENT_MAX_CHARS`(1MB 加载门,core 1.293 起 invoke 全文零截断、超 1MB 整拒;常量在
|
|
12
|
-
* dist/core/runner/synthetic-tools.js
|
|
12
|
+
* dist/core/runner/synthetic-tools.js,未从 core 包根导出,故钉本地孪生+本出处注记)。修7 曾对齐旧
|
|
13
13
|
* 20k 截断门([ref] 飞轮考据:20k 是压缩保留区口径,invoke 时刻用错),core 1.293 撤门后本门跟随回收
|
|
14
14
|
* ([ref]① 收口)——HTTP 面与引擎加载门同值,超限 400 fail-loud 而非引擎侧整拒后难诊断。
|
|
15
15
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WiringManifestReceipt } from "./wire-types.js";
|
|
1
2
|
/**
|
|
2
3
|
* In-memory, TTL'd dedup cache keyed by an opaque string (an Idempotency-Key).
|
|
3
4
|
*
|
|
@@ -41,4 +42,30 @@ export declare class IdempotencyCache<T> {
|
|
|
41
42
|
* three fields can't be confused. undefined raw key → undefined (no dedup, unchanged).
|
|
42
43
|
*/
|
|
43
44
|
export declare function scopedIdempotencyKey(raw: string | undefined, source: string | null, principal: string | undefined): string | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* 提交腿的**运行结局**(进幂等缓存的那一只)—— `status` + `body` 是它一直以来的两位;S-528 起多一位
|
|
47
|
+
* `wiringManifest`。
|
|
48
|
+
*
|
|
49
|
+
* 🔴 **为什么回执挂在 outcome 上、不铸进 `body`**(codex 对抗复审 [medium],红先复现后修):
|
|
50
|
+
* `Idempotency-Key` 的作用域是 `(credential source, principal, raw key)`,**不含端点** —— 同一个 key 先
|
|
51
|
+
* 走 `/v1/tasks/stream`、再走 `/v1/tasks`,命中的是前者缓存的那份体。回执若铸在同步腿的体里,这条
|
|
52
|
+
* **确实产了帧**的 run 在非流式面上就成了「键缺席」,而 present-iff 的读法会把它读成「这台引擎不给
|
|
53
|
+
* 回执」。挂在 outcome 上 = 它是**这条 run 的属性**(哪条腿跑的都一样),由各腿自己的应答构造器在
|
|
54
|
+
* 送出那一刻按自己的 wire 形挂。拆缓存键也能消歧,但那会让换腿的重试**重复计费** —— 比缺一个键坏。
|
|
55
|
+
*
|
|
56
|
+
* 🔴 **租户面安全(别在复审里当成漏)**:缓存的那份回执是按**第一位调用方**的 `explicitOperatorOk` 投的,
|
|
57
|
+
* 而键本身就按 `principal` 分区(`scopedIdempotencyKey`)⇒ 命中它的只可能是**同一个** principal,同一份
|
|
58
|
+
* 部署 operator 名单下求值恒同 ⇒ operator 面不可能经重放落到租户手里。跨租户同 key 是**缓存未命中**
|
|
59
|
+
* (各自重跑、各自归账),这一位从 BL-3 起就是这么设计的。
|
|
60
|
+
*
|
|
61
|
+
* 对照:同一条 200 体上的 `notice` 仍铸在 `body` 里,因为它说的是**本次请求**带没带 verify/cascade
|
|
62
|
+
* (重放腿在 `prepareSpec` 之前就答了,结构上不知道这件事);回执说的是**那条 run**。两者的归属不同,
|
|
63
|
+
* 不是可以统一的两处写法。
|
|
64
|
+
*/
|
|
65
|
+
export interface SubmitOutcome {
|
|
66
|
+
status: number;
|
|
67
|
+
body: unknown;
|
|
68
|
+
/** S-528:本条 run 的起手接线回执(present-iff 引擎真产了 `wiring_manifest` 帧)。 */
|
|
69
|
+
wiringManifest?: WiringManifestReceipt;
|
|
70
|
+
}
|
|
44
71
|
//# sourceMappingURL=idempotency.d.ts.map
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type { HostDecision } from "../host-decision.js";
|
|
|
19
19
|
import type { TaskStream, TaskSpec, TaskResult, QuestionAnswer, CheckpointToken, ResumeOutcome, RunInternals, Runner, EngineNotice } from "@sema-agent/core";
|
|
20
20
|
import type { FlatServiceDeps, RequestAuth } from "./server.js";
|
|
21
21
|
import type { TaskRequestBody, DecideBinding } from "./wire-types.js";
|
|
22
|
-
import type { IdempotencyCache } from "./idempotency.js";
|
|
22
|
+
import type { IdempotencyCache, SubmitOutcome } from "./idempotency.js";
|
|
23
23
|
import type { ImagesLocal } from "./routes/images.js";
|
|
24
24
|
import type { SessionsLocal } from "./routes/sessions.js";
|
|
25
25
|
import type { SessionSyncLocal } from "./routes/session-sync.js";
|
|
@@ -27,10 +27,7 @@ import type { PlanApprovePermissionModeAfter } from "../task-settings.js";
|
|
|
27
27
|
import type { VerifyRoundsSpec } from "./verify-rounds.js";
|
|
28
28
|
/** 跨域可变状态 = lens1 §C2 实测的那 8 条「运行期登记簿」,一个对象。 */
|
|
29
29
|
export interface RunRegistry {
|
|
30
|
-
idemCache: IdempotencyCache<
|
|
31
|
-
status: number;
|
|
32
|
-
body: unknown;
|
|
33
|
-
}>;
|
|
30
|
+
idemCache: IdempotencyCache<SubmitOutcome>;
|
|
34
31
|
inflightRuns: Map<string, AbortController>;
|
|
35
32
|
preemptableRuns: Map<string, AbortController>;
|
|
36
33
|
cancelledViaVerb: Set<string>;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* ── 为什么是 operator-only ─────────────────────────────────────────────────────────────────────
|
|
10
10
|
* 与 [ref] 的 bundle 两口同族(memory-bundle.ts 的同名段):
|
|
11
11
|
* · erase 是**治理写面**,而且是本族里爆炸半径最大的一口 —— 整 scope / 整会话删除是**合法**选择子,
|
|
12
|
-
* 而 core 的大规模删除熔丝对它**显式不适用**(engine.d.ts
|
|
12
|
+
* 而 core 的大规模删除熔丝对它**显式不适用**(engine.d.ts 逐字:那道熔丝守的是「文件在 harvest
|
|
13
13
|
* 时悄悄不见了」的事故形,一次显式授权的抹除靠在收执里逐 id 列举来自证);
|
|
14
14
|
* · provenance 是**跨租户治理读**:一条 entry 的绑定 / 贡献会话 / 污染标记 / 托管链事件全在里面,
|
|
15
15
|
* 按定义超出任何单个 principal 的自助边界。
|
|
@@ -97,7 +97,7 @@ export declare function buildClearanceRowWire(r: OriginClearanceRow): {
|
|
|
97
97
|
detail?: string | undefined;
|
|
98
98
|
eventId: string;
|
|
99
99
|
at: number;
|
|
100
|
-
to: "
|
|
100
|
+
to: "failed" | "done";
|
|
101
101
|
requestId: string;
|
|
102
102
|
}[];
|
|
103
103
|
custodyBytes: number;
|
|
@@ -105,7 +105,7 @@ export declare function buildClearanceRowWire(r: OriginClearanceRow): {
|
|
|
105
105
|
requestId: string;
|
|
106
106
|
reason: string;
|
|
107
107
|
at: number;
|
|
108
|
-
status: "
|
|
108
|
+
status: "failed" | "pending" | "done";
|
|
109
109
|
originUnknownKeys?: string[] | undefined;
|
|
110
110
|
clearanceId: string;
|
|
111
111
|
entryId: string;
|
|
@@ -312,7 +312,9 @@ async function handleRuleRevoke(res, rawBody, caller, lane, operatorPrincipals)
|
|
|
312
312
|
sendJson(res, 200, { status: "removed", rev: outcome.rev, stillLive: outcome.stillLive });
|
|
313
313
|
return;
|
|
314
314
|
case "no-op": {
|
|
315
|
-
const stillLive = (await lane.listRules(target.principal)).rows.some((r) => sameWireRuleIdentity(r, { behavior: parsed.data.behavior, rule: parsed.data.rule, scope: serializeRuleScope(scope) }))
|
|
315
|
+
const stillLive = (await lane.listRules(target.principal)).rows.some((r) => sameWireRuleIdentity(r, { behavior: parsed.data.behavior, rule: parsed.data.rule, scope: serializeRuleScope(scope) }))
|
|
316
|
+
? "yes"
|
|
317
|
+
: "no";
|
|
316
318
|
sendJson(res, 200, { status: "no-op", rev: outcome.rev, stillLive });
|
|
317
319
|
return;
|
|
318
320
|
}
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -1057,12 +1057,9 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1057
1057
|
}
|
|
1058
1058
|
const g14 = (() => {
|
|
1059
1059
|
const d = out.details;
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
return { cursorSemantics: d.details && "bytesDroppedBeforeCursor" in d.details ? "cursor" : "full" };
|
|
1064
|
-
if (d.type === "monitor" || d.type === "background_agent")
|
|
1065
|
-
return { cursorSemantics: "full" };
|
|
1060
|
+
const replay = d?.details?.replay;
|
|
1061
|
+
if (replay === "cursor" || replay === "full")
|
|
1062
|
+
return { cursorSemantics: replay };
|
|
1066
1063
|
return {};
|
|
1067
1064
|
})();
|
|
1068
1065
|
sendJson(res, 200, { taskId: runId, target, content: out.content, output: out.details, ...g14 });
|
|
@@ -13,7 +13,7 @@ const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
|
|
|
13
13
|
const SYNC_IMPORT_BATCH = 500;
|
|
14
14
|
import { SHA256_HEX_RE } from "../../digest-form.js";
|
|
15
15
|
import { assertNever } from "../route-ctx.js";
|
|
16
|
-
import { readSessionRulesRecords, describeSessionRulesRecordProblem } from "../../session-policy-wire.js";
|
|
16
|
+
import { readSessionRulesRecords, describeSessionRulesRecordProblem, sessionPolicyErrorHttp } from "../../session-policy-wire.js";
|
|
17
17
|
export function createSessionSyncLocal() {
|
|
18
18
|
const pendingStagings = new Map();
|
|
19
19
|
const importLeases = new Map();
|
|
@@ -501,7 +501,8 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
501
501
|
}
|
|
502
502
|
if (e instanceof SessionPolicyError) {
|
|
503
503
|
cleanupStaging(stagingId);
|
|
504
|
-
|
|
504
|
+
const h = sessionPolicyErrorHttp(e.code);
|
|
505
|
+
sendError(res, h.status, h.errorCode, e.message);
|
|
505
506
|
return;
|
|
506
507
|
}
|
|
507
508
|
cleanupStaging(stagingId);
|
|
@@ -5,7 +5,7 @@ import { windowMessages, truncateMessageBlobs } from "../../audit.js";
|
|
|
5
5
|
import { redactSecrets } from "../../trace/redact.js";
|
|
6
6
|
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
7
7
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
|
|
8
|
-
import { readSessionPermissionRules, describeSessionPermissionRulesProblem } from "../../session-policy-wire.js";
|
|
8
|
+
import { readSessionPermissionRules, describeSessionPermissionRulesProblem, sessionPolicyErrorHttp } from "../../session-policy-wire.js";
|
|
9
9
|
import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
10
10
|
import { gatedPrincipal, explicitOperatorOk, runOwnerReadable, sendPrincipalRefusal } from "../principal-gate.js";
|
|
11
11
|
import { assertNever } from "../route-ctx.js";
|
|
@@ -768,7 +768,8 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
768
768
|
}
|
|
769
769
|
catch (e) {
|
|
770
770
|
if (e instanceof SessionPolicyError) {
|
|
771
|
-
|
|
771
|
+
const h = sessionPolicyErrorHttp(e.code);
|
|
772
|
+
sendError(res, h.status, h.errorCode, e.message);
|
|
772
773
|
return;
|
|
773
774
|
}
|
|
774
775
|
throw e;
|