@sema-agent/server 7.143.0 → 7.145.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 +295 -0
- package/README.md +3 -0
- package/README.zh-CN.md +1 -0
- package/USAGE.md +34 -16
- package/dist/approval-card.d.ts +31 -14
- package/dist/approval-card.js +8 -8
- package/dist/audit.d.ts +2 -21
- package/dist/audit.js +0 -9
- package/dist/boot/execution-env.js +1 -1
- package/dist/boot/runner-deps.d.ts +10 -5
- package/dist/boot/runner-deps.js +5 -4
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +3 -3
- package/dist/boot/shutdown.d.ts +1 -1
- package/dist/boot/shutdown.js +4 -2
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stage-10-http-server.js +4 -9
- package/dist/capabilities/scenarios.d.ts +14 -18
- package/dist/capabilities/scenarios.js +1 -2
- package/dist/config-d-migration.d.ts +3 -0
- package/dist/delegated-session-owner.d.ts +33 -151
- package/dist/delegated-session-owner.js +1 -83
- package/dist/entry-mode-clamp-notice.d.ts +1 -1
- package/dist/execution-placement.d.ts +7 -4
- package/dist/fleet/fleet-bus.d.ts +13 -3
- package/dist/fleet/fleet-bus.js +17 -3
- package/dist/hooks/classic-stations.d.ts +3 -1
- package/dist/hooks/classic-stations.js +2 -0
- package/dist/http/admission.js +2 -1
- package/dist/http/checkpoint-ctx.d.ts +1 -0
- package/dist/http/checkpoint-ctx.js +1 -0
- package/dist/http/durable-run-leg.js +7 -6
- package/dist/http/principal-gate.d.ts +1 -1
- package/dist/http/refusal-body.d.ts +7 -3
- package/dist/http/refusal-body.js +1 -0
- package/dist/http/resume-legs.js +16 -7
- package/dist/http/route-ctx.d.ts +15 -0
- package/dist/http/routes/approvals-assistant.d.ts +20 -1
- package/dist/http/routes/approvals-assistant.js +28 -6
- package/dist/http/routes/rules.d.ts +1 -1
- package/dist/http/routes/rules.js +5 -2
- package/dist/http/routes/session-sync.js +12 -0
- package/dist/http/routes/tasks.js +12 -9
- package/dist/http/run-meta.d.ts +14 -7
- package/dist/http/run-meta.js +5 -2
- package/dist/http/server.d.ts +13 -5
- package/dist/http/server.js +3 -0
- package/dist/http/submit-ctx.d.ts +12 -0
- package/dist/http/submit-ctx.js +23 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lane-home-probe.d.ts +15 -0
- package/dist/lane-home-probe.js +4 -0
- package/dist/observability/corrupt-read-seat.d.ts +5 -9
- package/dist/observability/corrupt-read-seat.js +1 -2
- package/dist/observability/fail-open.d.ts +26 -10
- package/dist/observability/fail-open.js +26 -10
- package/dist/observability/run-terminal.d.ts +1 -1
- package/dist/observability/run-terminal.js +2 -2
- package/dist/parked-revive-surface.d.ts +6 -7
- package/dist/peer-inbound-face.d.ts +29 -34
- package/dist/peer-inbound-face.js +28 -63
- package/dist/plugins/approval-ask-store-file.d.ts +16 -2
- package/dist/plugins/approval-ask-store-file.js +21 -1
- package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
- package/dist/plugins/approval-ask-store-memory.js +11 -8
- package/dist/plugins/caching-session-store.d.ts +3 -3
- package/dist/plugins/caching-session-store.js +3 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +62 -37
- package/dist/plugins/checkpoint-store-sql.js +123 -40
- package/dist/plugins/file-run-store.d.ts +13 -1
- package/dist/plugins/file-run-store.js +11 -1
- package/dist/plugins/fork-routing-session-store.d.ts +8 -1
- package/dist/plugins/fork-routing-session-store.js +6 -0
- package/dist/plugins/local-checkpoint-store.d.ts +17 -8
- package/dist/plugins/local-checkpoint-store.js +26 -7
- package/dist/plugins/local-session-store.d.ts +105 -34
- package/dist/plugins/local-session-store.js +168 -65
- package/dist/plugins/local-session-window.d.ts +4 -2
- package/dist/plugins/memory-run-store.d.ts +2 -0
- package/dist/plugins/memory-run-store.js +6 -0
- package/dist/plugins/one-time-migrations.d.ts +12 -12
- package/dist/plugins/one-time-migrations.js +13 -13
- package/dist/plugins/pg-session-storage.d.ts +1 -9
- package/dist/plugins/pg-session-storage.js +2 -19
- package/dist/plugins/remote-env-host.d.ts +8 -4
- package/dist/plugins/remote-env-host.js +7 -9
- package/dist/plugins/remote-shell.js +12 -2
- package/dist/plugins/run-store-sql.d.ts +4 -0
- package/dist/plugins/run-store-sql.js +3 -0
- package/dist/plugins/schema-consistency.d.ts +5 -1
- package/dist/plugins/session-fork.d.ts +1 -1
- package/dist/plugins/session-ownership.d.ts +9 -0
- package/dist/plugins/session-ownership.js +6 -0
- package/dist/plugins/session-store.d.ts +13 -16
- package/dist/plugins/session-store.js +9 -10
- package/dist/plugins/sql-json-column.d.ts +24 -2
- package/dist/plugins/sql-json-column.js +38 -3
- package/dist/plugins/store-backend.js +3 -2
- package/dist/plugins/tidb-session-store.d.ts +2 -14
- package/dist/plugins/tidb-session-store.js +2 -20
- package/dist/prompt-screen.d.ts +21 -0
- package/dist/prompt-screen.js +8 -0
- package/dist/rules-consent.d.ts +63 -51
- package/dist/rules-consent.js +6 -10
- package/dist/run-acceptance.d.ts +40 -10
- package/dist/run-acceptance.js +38 -5
- package/dist/run-local.js +2 -1
- package/dist/runs.js +5 -1
- package/dist/security.d.ts +10 -7
- package/dist/session-agent.d.ts +39 -0
- package/dist/session-agent.js +16 -0
- package/dist/session-error-wire.d.ts +25 -26
- package/dist/session-error-wire.js +4 -5
- package/dist/session-incarnation.d.ts +2 -2
- package/dist/session-notify-door.d.ts +2 -2
- package/dist/session-notify-door.js +5 -1
- package/dist/tool-approval.d.ts +3 -1
- package/dist/tool-approval.js +47 -18
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -1
- package/dist/trace/ledger-sink.js +12 -4
- package/dist/trace/model-segment.d.ts +26 -6
- package/dist/trace/model-segment.js +58 -9
- package/dist/trace/project.d.ts +8 -2
- package/dist/trace/project.js +6 -4
- package/dist/trace/redact.d.ts +77 -2
- package/dist/trace/redact.js +234 -99
- package/dist/trace/self-minted-wire.d.ts +160 -9
- package/dist/trace/self-minted-wire.js +52 -3
- package/package.json +3 -3
- package/wire-changes/server@7.144.0.json +291 -0
- package/wire-changes/server@7.145.0.json +146 -0
- package/dist/session-records-skipped-notice.d.ts +0 -21
- package/dist/session-records-skipped-notice.js +0 -27
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🔴 **一只会话在委派树里的位置 —— 一个来源、一只读口、一种 wire 写法**(`agent: { id?, parentSessionId?, rootSessionId? }`)。
|
|
3
|
+
*
|
|
4
|
+
* 来源只有一个:会话行上**存下来的委派元组**(core `SessionPlacementRecord`)—— 引擎在**后台**委派子代(带耐久 agent 行、店声明了委派分区)建会话那一笔交给店、
|
|
5
|
+
* 店原样记下,首写不可变。三只持久会话店都存:SQL 两孪生在 `session_meta` 的 `placement_*` 列(`plugins/session-placement.ts` 的 `placementFromRow`),
|
|
6
|
+
* local 后端(7.141.0 起)在会话存档行头(core 文件 repo)。读口是店的 `placementOf`(一行 PK 探针;店不交它 = 不存元组)。
|
|
7
|
+
*
|
|
8
|
+
* 读者(同一只读口 {@link readSessionAgent}、同一只投影 {@link sessionAgentOf},逐值同答):
|
|
9
|
+
* · 会话视图 `GET /v1/sessions/:id` 体顶层的 `agent`(视图读才付探针;探针抛 ⇒ 视图读响亮失败,`boot/session-faces.ts`);
|
|
10
|
+
* · 运维审批队列 `GET /v1/approvals` 的停驻行(`pending[]` 的 `checkpoint` 族)与 `/v1/approvals/stream` 的 `pending` 帧:行的身份就是它那只会话
|
|
11
|
+
* (决断口 `/v1/approvals/:sessionId/decide`),行上的 `agent` = 同一只会话在视图上的 `agent`;一行读不出 ⇒ 只这一行缺键、F 类兜底计数
|
|
12
|
+
* (`http/routes/approvals-assistant.ts`)。
|
|
13
|
+
* 不另立第二只读法:fleet 行 / 后台 agent 行上的同名事实是引擎别的记录,读面不拿它们回填或改写这里的答案。
|
|
14
|
+
*
|
|
15
|
+
* 投影律(不另铸):`id` = 元组的 `handle`(耐久 agent 行的 `a…` 键,与 fleet 行的 `id`、`GET /v1/agents/roster` 的 `handle` 同一个串);`parentSessionId` /
|
|
16
|
+
* `rootSessionId` = 元组的同名格(派它的会话 / 委派树的根会话);元组缺哪一格这里就缺哪一格;元组的 `scope`(租户键)/ `placedAt` / `kind` 不上 wire。
|
|
17
|
+
* 键在场 = 这是一只委派子代的会话(元组只存了 `kind` 时是 `{}`);键缺席 = 不是 placed 行(宿主会话、同步委派的子代、`subagent_type: "fork"` 的后台子代 ——
|
|
18
|
+
* 它的会话是父会话的一只普通分叉;`DB_BACKEND=local` 到 7.140.x 停驻时拷进宿主店的子代转录,那一份是普通会话行)、这台部署的店不存元组(无 repo 的内存店
|
|
19
|
+
* 〔`DB_BACKEND=memory`〕、嵌入方自建且不声明委派分区的店),或那一读没读成 —— 消费方一律读作「这台 server 没有说」,不读作「不是子代」。
|
|
20
|
+
*/
|
|
21
|
+
import type { SessionPlacementRecord } from "@sema-agent/core";
|
|
22
|
+
/** 一只会话在委派树里的位置(wire 键 `agent` 的值形)。 */
|
|
23
|
+
export interface SessionAgent {
|
|
24
|
+
id?: string;
|
|
25
|
+
parentSessionId?: string;
|
|
26
|
+
rootSessionId?: string;
|
|
27
|
+
}
|
|
28
|
+
/** 交委派元组读口的会话店面(本仓三只持久会话店与缓存包装层都交;内存店不交)。 */
|
|
29
|
+
export interface PlacementReadingSessionStore {
|
|
30
|
+
readonly placementOf?: (sessionId: string) => Promise<SessionPlacementRecord | undefined>;
|
|
31
|
+
}
|
|
32
|
+
/** {@link SessionAgent} 的唯一投影:店答出的委派元组 → `agent` 的值;`undefined`(普通会话 / 店没有这一面)⇒ 调用方不铸键。 */
|
|
33
|
+
export declare function sessionAgentOf(placement: SessionPlacementRecord | undefined): SessionAgent | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* `agent` 的**唯一读口**:店的 `placementOf` 一读 → {@link sessionAgentOf}。店不交 `placementOf` ⇒ `undefined`(这台部署不存元组,不探)。
|
|
36
|
+
* 探针抛 ⇒ 原样上抛 —— 读不出怎么说归读者(视图读响亮失败;队列只缺那一行的键并计数),本函数不替任何一面把故障说成「不是子代」。
|
|
37
|
+
*/
|
|
38
|
+
export declare function readSessionAgent(store: PlacementReadingSessionStore | undefined, sessionId: string): Promise<SessionAgent | undefined>;
|
|
39
|
+
//# sourceMappingURL=session-agent.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function sessionAgentOf(placement) {
|
|
2
|
+
if (placement === undefined)
|
|
3
|
+
return undefined;
|
|
4
|
+
return {
|
|
5
|
+
...(placement.handle !== undefined ? { id: placement.handle } : {}),
|
|
6
|
+
...(placement.parentSessionId !== undefined ? { parentSessionId: placement.parentSessionId } : {}),
|
|
7
|
+
...(placement.rootSessionId !== undefined ? { rootSessionId: placement.rootSessionId } : {}),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export async function readSessionAgent(store, sessionId) {
|
|
11
|
+
const placementOf = store?.placementOf;
|
|
12
|
+
if (placementOf === undefined)
|
|
13
|
+
return undefined;
|
|
14
|
+
return sessionAgentOf(await placementOf.call(store, sessionId));
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=session-agent.js.map
|
|
@@ -9,30 +9,25 @@
|
|
|
9
9
|
* owner 已知时同步口是 200 + failed 体、并照常走计费收尾)。
|
|
10
10
|
*
|
|
11
11
|
* ## 为什么多一个 `face` 参数(而不是单参)
|
|
12
|
-
* core
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* 说成「你交的日志坏了」。⇒ 面是映射的一维,写进属主,不散在消费点。
|
|
12
|
+
* 三个 core 词只在一面上有自己的答复:`invalid_session`(导入门拒调用方交来的日志,亲读 dist:`engine/session/import-validate.js` 与
|
|
13
|
+
* `core/session-log-transfer.js` 的暂存腿)、`placement_refused`、`conflict`(各自在 import 面只有一个来源,见逐词)—— 在 open 面上它们要么从不出现,
|
|
14
|
+
* 要么是 run 的终局码(引擎词汇,原样随 failed 体走)。单参签名装不下「一面有答复、另一面留白」:要么每个消费点写一句按码特判,要么把 import 面的
|
|
15
|
+
* 答复漏到 open 面(把「存着的会话坏了」说成「你交的日志坏了」)。⇒ 面是映射的一维,写进属主,不散在消费点。
|
|
17
16
|
* · `"open"` —— 为某只会话**打开它已存的日志**(提交各腿的引擎 prepare、鉴权门的归属探针、直接读会话的口);
|
|
18
17
|
* · `"import"` —— 校验 / 写入**调用方交来的**日志(2c session-sync 的暂存导入)。
|
|
19
18
|
*
|
|
20
19
|
* ## 逐词(`undefined` = 登记过的留白:这个词在这一面上没有自己的 wire 答复,站点的既有处置照旧)
|
|
21
|
-
* · `corrupt_session` —— 两面同答 **500 `corrupt_session
|
|
20
|
+
* · `corrupt_session` —— 两面同答 **500 `corrupt_session`**。这只会话的存档读不出、唤不醒;core 7.62.0([ref])起也含「读得出、走不成一棵树」那一形
|
|
21
|
+
* (悬空父而回放没丢记录 / 自指 / 成环 / 叶子缺席 / 分叉走链 —— 7.137.0–7.143.0 那一形是 `invalid_session`,见下一词)。不是 4xx:调用方没做错
|
|
22
22
|
* 任何事;不是 503:重试改不了判(修复在运维手里)—— 与会话规则店的 `corrupt` 同一档同一理由。码不另铸(core 的词
|
|
23
23
|
* 原样上 wire,契约既有立场:不为上游码铸孪生码)。三种会话后端同词:文件店由 core 铸,两只 SQL 店由
|
|
24
24
|
* `plugins/session-event-payload.ts` 铸(坏的 `session_event.payload` 行)。
|
|
25
|
-
* · `invalid_session` —— `import` 面 **422 `invalid_entries`**(修前 session-sync 的那一行,码位 / 状态逐字不变)
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* ·
|
|
30
|
-
* (`
|
|
31
|
-
* `errorCode` 取终局码原样):读口答 `corrupt_session`、提交口答 `invalid_session` 就是同一件事两个名字。所以映射只有一格,
|
|
32
|
-
* 读口与提交口的 run 终局分诊({@link sessionErrorHttp} 的 open 面,`resumeAtHttpStatus` 读它)同一只、同码同状态。
|
|
33
|
-
* · **没有 `reason` 机读位**:core 的 `SessionError` 只带 `code` 与句子;悬空父与叶子缺席是同一句 `Entry <id> not found`,自指与成环是同一句
|
|
34
|
-
* `is its own ancestor` —— 本仓分出 {parent_missing, parent_self, parent_cycle} 只有解析句子或再写一只判官两条路(会话视图自己的走链器已在 core 7.54.0 提货时退役:
|
|
35
|
-
* 视图读的就是引擎读的那条分支,见 `plugins/local-session-window.ts` 头注)。要分,请求 core 把种类放进错误(同 `placement_kind` 的先例),不在这里猜。
|
|
25
|
+
* · `invalid_session` —— `import` 面 **422 `invalid_entries`**(修前 session-sync 的那一行,码位 / 状态逐字不变);`open` 面**留白**:core 7.62.0
|
|
26
|
+
* ([ref] / [ref])起它只是导入门的词,打开已存日志从不抛它 —— 走不成一棵树的已存日志是 `corrupt_session`(上一词);回放丢了记录的悬空父不再失败,
|
|
27
|
+
* run 从断点续上并在流上说 `session.lineage_rerooted`。7.137.0–7.143.0 本表在 open 面给过它一格 500 `invalid_session`(那时 core 用它说已存日志的结构坏),
|
|
28
|
+
* 随 core 改判退役(一个词一个意思,[ref];零双读:open 面不再认它,真来了也是通用 500,不替它猜成 `corrupt_session`)。
|
|
29
|
+
* · **没有 `reason` 机读位**(两种结构坏同一个 `corrupt_session`):core 的 `SessionError` 只带 `code` 与句子 —— 要分成因,请求 core 把种类放进错误
|
|
30
|
+
* (同 `placement_kind` 的先例),不在这里解析句子。
|
|
36
31
|
* · `not_found` —— 留白:每一面各有自己的「不存在」答法(404 `not_found.session` / 同步口的 `resume.session_not_found` /
|
|
37
32
|
* 列表里的缺席),码位各不相同,不由本表统一。
|
|
38
33
|
* · `placement_refused` —— `import` 面 **409 `conflict.session_placed`**(机读位 `detail.placement_kind`,固定句);`open` 面留白。
|
|
@@ -40,7 +35,7 @@
|
|
|
40
35
|
* (三只持久会话店的整段换装 / 暂存提交门:目标 id 的 `placement_kind` 在场 ⇒ 不适用),调用方没做错格式、是**目标**不收这种写;
|
|
41
36
|
* open 面上同一个词还含「声明了分区的创建撞上了分区外 / 异组的行」两形(core `TtlSessionStore.assertPlacementAdmission` 的后两条),
|
|
42
37
|
* 那不是「目标是分区会话」。而 run 终局上它是引擎词汇(`terminal.code`),原样随 failed 体走 —— 若在这里分诊成 409,
|
|
43
|
-
* 体上的 `errorCode` 仍是引擎词 `placement_refused`,一个状态两个名字。所以 open
|
|
38
|
+
* 体上的 `errorCode` 仍是引擎词 `placement_refused`,一个状态两个名字。所以 open 面留白。
|
|
44
39
|
* 409 的语义:目标会话**状态**不收这种写(重试同一个 id 同一结果;换一个会话 id 推)—— 与 `conflict.session_ownerless` / `conflict.session_children_alive` 同族。
|
|
45
40
|
* `detail.placement_kind` 是**常量**,不是从错误里读的:core 的 `SessionError` 只带 `code` 与句子,不带种类;今天分区种类闭集只有
|
|
46
41
|
* `"subagent"` 一个词,所以常量就是事实。core 给 `SessionPlacementRecord["kind"]` 加第二个词 ⇒ 下面的守卫编译红,由人裁定种类从哪儿读
|
|
@@ -50,9 +45,12 @@
|
|
|
50
45
|
* · `conflict` —— `import` 面 **409 `conflict`**(固定句 {@link STAGED_IMPORT_OWNER_CHANGED_SENTENCE});`open` 面留白。按码映射时 import 面上这个词只有一个来源:
|
|
51
46
|
* 暂存提交的属主 CAS(`session-sync-kernel.ts` 的 `StagedCommitOwnerChangedError`,三家店在提交事务里比「此刻的属主格 = 这次导入的门判过的那一格」,
|
|
52
47
|
* 不等 ⇒ 回滚)—— 码位与同族的提交期同步冲突(`SyncConflictError` ⇒ 409 `conflict`「sync conflict」)同一个,出路也相同:从第一阶段重来(重新过门)。
|
|
53
|
-
* 本服务自铸的同码具名拒 {@link StampedSessionArchiveError}(local 店:存档仍带更早版本写下的属主戳)不按码映射 —— 出路不同(重来照样撞)
|
|
54
|
-
* 由 {@link sessionErrorWireOf} 按**型**先认:两面同答 409 `conflict` + 固定句 {@link STAMPED_SESSION_ARCHIVE_SENTENCE}
|
|
48
|
+
* 本服务自铸的同码具名拒 {@link StampedSessionArchiveError}(local 店:存档仍带更早版本写下的属主戳)不按码映射 —— 出路不同(重来照样撞),它有**自己的 wire 码**:
|
|
49
|
+
* 由 {@link sessionErrorWireOf} 按**型**先认:两面同答 409 `conflict.session_archive_stamped` + 固定句 {@link STAMPED_SESSION_ARCHIVE_SENTENCE}(与上一句的属主变更拒同状态、不同码:
|
|
50
|
+
* 两件事的出路相反 —— 属主变更从第一阶段重来,带戳存档删掉会话再推 —— 机读方按码分支,不读句子)。
|
|
55
51
|
* open 面上它是 run 的写撞上真并发写,落成那条 run 的 `failed` 终局(`terminal.code: "conflict"`),提交腿不另作处置。
|
|
52
|
+
* 带戳存档的具名拒在提交里排在一切同步冲突判之前(`local-session-store.ts` 的 `assertArchiveAnonymous`,带戳判的唯一读口;fork 的源与第一阶段的早拒问的是同一只):改日志的推送的每一形(干净追加 / 载荷分叉 / fork / stale,带同意与否)都答它的这一句 ——
|
|
53
|
+
* 带戳存档的日志换不了,同步冲突的出路(带同意重来)无从谈起。第一阶段(`POST …/sync/import`)按 id 分类出 fork / stale 而没带同意时,通用冲突的出路同样是死路 ⇒ 路由先问这一口,带戳 ⇒ 第一阶段就答这一句(不等带同意的第二轮)。
|
|
56
54
|
* · `storage` / `unknown` —— 留白:故障类,通用 500 就是它们今天的应答。
|
|
57
55
|
*
|
|
58
56
|
* core 给闭集加一个词 ⇒ 下面的 `switch` 编译红,由人裁定它在两面上的答复,而不是让它静默落进某个恰好存在的臂
|
|
@@ -94,9 +92,10 @@ export declare const PLACED_SESSION_SENTENCE = "this session holds a delegated a
|
|
|
94
92
|
*/
|
|
95
93
|
export declare const STAGED_IMPORT_OWNER_CHANGED_SENTENCE = "session owner changed while the import streamed; nothing was imported \u2014 start the sync again";
|
|
96
94
|
/**
|
|
97
|
-
* local 店的具名拒(唯一铸点 `plugins/local-session-store.ts`):这只会话的引擎存档仍带更早版本写下的属主戳(一次性迁移 M33 改写不了它,或数据根跳过了带这次迁移的版本)
|
|
98
|
-
* ⇒ 不在它上面造新存档(fork),也不原地换它的日志(
|
|
99
|
-
* wire
|
|
95
|
+
* local 店的具名拒(唯一铸点 `plugins/local-session-store.ts` 的 `assertArchiveAnonymous`):这只会话的引擎存档仍带更早版本写下的属主戳(一次性迁移 M33 改写不了它,或数据根跳过了带这次迁移的版本)
|
|
96
|
+
* ⇒ 不在它上面造新存档(fork),也不原地换它的日志(改日志的推送,含第一阶段对 fork / stale 的早拒);零写。`SessionError.code` 是 core 闭集的 `conflict`;句子(给运维日志,点名会话 id)归本错,
|
|
97
|
+
* wire 上发自己的码 `conflict.session_archive_stamped` + 固定句 {@link STAMPED_SESSION_ARCHIVE_SENTENCE}(两面同一码同一句;映射 {@link sessionErrorWireOf}):
|
|
98
|
+
* 同一个 core 词 `conflict` 在 import 面上还有属主变更那一件事(出路相反),机读方要能按码把两件事分开。
|
|
100
99
|
*/
|
|
101
100
|
export declare class StampedSessionArchiveError extends SessionError {
|
|
102
101
|
readonly sessionId: string;
|
|
@@ -107,14 +106,14 @@ export declare class StampedSessionArchiveError extends SessionError {
|
|
|
107
106
|
* 7.104.0 之前写下、此后从没被读过的会话没有这一格,要从存档补铸它(第一次读时的惯例),存档又用不了(会话头坏 / 截断 / 日志结构走不通)——
|
|
108
107
|
* 于是没有任何一位调用方能被判成它的属主。要读它的行事实的店方法(`headOf` / 执行席的读 / 会话元数据读口)抛本错。
|
|
109
108
|
*
|
|
110
|
-
* 码与句子**就是**那只存档故障的(`cause` = 它;码不另铸 —— 只会是 `corrupt_session
|
|
109
|
+
* 码与句子**就是**那只存档故障的(`cause` = 它;码不另铸 —— 只会是 `corrupt_session`,读不出而说不出在不在的 I/O 级故障不换型):
|
|
111
110
|
* 不认识本型的站点看到的与此前逐字相同(`sessionErrorHttp` 按码映射,wire 与运维日志不变)。认识本型的站点 —— 会话读面的属主读
|
|
112
111
|
* (`http/owner-access.ts` 的 `foldOwnerUnreadable`)—— 把它读成「没有这一行」:与对非属主、对不存在的会话同一只 404,原故障进运维日志。
|
|
113
112
|
*/
|
|
114
113
|
export declare class SessionOwnerUnreadableError extends SessionError {
|
|
115
|
-
constructor(
|
|
114
|
+
constructor(fault: SessionError);
|
|
116
115
|
}
|
|
117
|
-
/** {@link StampedSessionArchiveError} 的 wire 固定句(fork
|
|
116
|
+
/** {@link StampedSessionArchiveError} 的 wire 固定句(fork 与改日志的推送〔每一形〕同一句;不点名会话 id;出路在句里 —— 从头再同步照样撞)。 */
|
|
118
117
|
export declare const STAMPED_SESSION_ARCHIVE_SENTENCE = "this session's stored archive still carries an owner stamp written by an earlier server version, so it cannot be forked or have its log replaced; nothing was written \u2014 delete the session and push it again";
|
|
119
118
|
/**
|
|
120
119
|
* 一只 `SessionError` 在一面上的 wire 答复 —— 下发点({@link sendSessionError} 的唯一读法)经这里:本服务自铸的同码具名拒先按型认(它的出路与同码的
|
|
@@ -14,18 +14,17 @@ export class StampedSessionArchiveError extends SessionError {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
export class SessionOwnerUnreadableError extends SessionError {
|
|
17
|
-
constructor(
|
|
18
|
-
super(
|
|
17
|
+
constructor(fault) {
|
|
18
|
+
super("corrupt_session", fault.message, fault);
|
|
19
19
|
this.name = "SessionOwnerUnreadableError";
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
export const STAMPED_SESSION_ARCHIVE_SENTENCE = "this session's stored archive still carries an owner stamp written by an earlier server version, so it cannot be forked or have its log replaced; nothing was written — delete the session and push it again";
|
|
23
23
|
const CORRUPT_SESSION = { status: 500, errorCode: "corrupt_session" };
|
|
24
|
-
const STORED_LOG_INVALID = { status: 500, errorCode: "invalid_session" };
|
|
25
24
|
const INVALID_ENTRIES = { status: 422, errorCode: "invalid_entries", sentence: STAGED_IMPORT_INVALID_SENTENCE };
|
|
26
25
|
const SESSION_PLACED = { status: 409, errorCode: "conflict.session_placed", sentence: PLACED_SESSION_SENTENCE, detail: { placement_kind: PLACEMENT_KIND } };
|
|
27
26
|
const STAGED_IMPORT_OWNER_CHANGED = { status: 409, errorCode: "conflict", sentence: STAGED_IMPORT_OWNER_CHANGED_SENTENCE };
|
|
28
|
-
const STAMPED_SESSION_ARCHIVE = { status: 409, errorCode: "conflict", sentence: STAMPED_SESSION_ARCHIVE_SENTENCE };
|
|
27
|
+
const STAMPED_SESSION_ARCHIVE = { status: 409, errorCode: "conflict.session_archive_stamped", sentence: STAMPED_SESSION_ARCHIVE_SENTENCE };
|
|
29
28
|
export function sessionErrorWireOf(e, face) {
|
|
30
29
|
return e instanceof StampedSessionArchiveError ? STAMPED_SESSION_ARCHIVE : sessionErrorHttp(e.code, face);
|
|
31
30
|
}
|
|
@@ -41,7 +40,7 @@ export function sessionErrorHttp(code, face) {
|
|
|
41
40
|
case "corrupt_session":
|
|
42
41
|
return CORRUPT_SESSION;
|
|
43
42
|
case "invalid_session":
|
|
44
|
-
return face === "import" ? INVALID_ENTRIES :
|
|
43
|
+
return face === "import" ? INVALID_ENTRIES : undefined;
|
|
45
44
|
case "placement_refused":
|
|
46
45
|
return face === "import" ? SESSION_PLACED : undefined;
|
|
47
46
|
case "conflict":
|
|
@@ -91,7 +91,7 @@ export type EmptyIncarnationReading = "refuse" | "absent";
|
|
|
91
91
|
export declare function incarnationFromCell(sessionId: string, cell: unknown, empty?: "refuse"): string;
|
|
92
92
|
export declare function incarnationFromCell(sessionId: string, cell: unknown, empty: "absent"): string | undefined;
|
|
93
93
|
/** 一行原始列 → {@link SessionFace}(属主 + 一只令牌)的唯一口:化身那一格经 {@link incarnationFromCell} 的 `refuse` 读法(空 ⇒ 响亮拒)。
|
|
94
|
-
* 按 id 的读者经 {@link sessionFaceOfHead};批量来源读(
|
|
94
|
+
* 按 id 的读者经 {@link sessionFaceOfHead};批量来源读(一次性迁移 M23 窗内的回填扫描)与 staged 换装的回读直接从行列经它。 */
|
|
95
95
|
export declare function sessionFaceFromRow(sessionId: string, owner: string | null, incarnation: unknown): SessionFace;
|
|
96
96
|
/**
|
|
97
97
|
* 头的叶子那一格读不出 —— 会话**内容**读不出时的那只故障(local 店:会话存档读不出,例如存档首行的会话头坏了;行上的属主与住客照样读得出)。
|
|
@@ -122,7 +122,7 @@ export declare function sessionFaceOfHead(sessionId: string, head: SessionHead):
|
|
|
122
122
|
export declare function sessionFaceOfHead(sessionId: string, head: SessionHead | undefined): SessionFace | undefined;
|
|
123
123
|
/**
|
|
124
124
|
* 「这只会话店有没有按 id 读(`headOf`)」—— 全仓**只判这一处**:授权口、各路由的属主门、能力位、起服门、k8s 工作区空间的每 run 读与
|
|
125
|
-
*
|
|
125
|
+
* 一次性迁移 M23(窗内)的回填读的都是这一只返回值。缺席 = 店没有实装 `headOf`(读不出属主 / 住客 / 叶子,调用方各按自己的 fail-closed 臂处置)。
|
|
126
126
|
*/
|
|
127
127
|
export declare function headReadOf(store: object | undefined): HeadRead | undefined;
|
|
128
128
|
/** 换装沿用的行格(化身 + fork 键):{@link carriedRowCells} 的答案。`forkKey: null` = 没有可沿用的键(新建行 / 行上本来没有)。 */
|
|
@@ -67,7 +67,7 @@ export interface SessionNotifyDoor {
|
|
|
67
67
|
}
|
|
68
68
|
/** 门要读的四样(都由 HTTP 服务交:活流登记簿是它的,run 店、会话店与收件箱是它的依赖)。 */
|
|
69
69
|
export interface SessionNotifyDoorIo {
|
|
70
|
-
/** 会话 → 它的在飞 run(run 店的单活认领读)。部署没有 run 店 ⇒ 交 `undefined` ⇒
|
|
70
|
+
/** 会话 → 它的在飞 run(run 店的单活认领读)。部署没有 run 店 ⇒ 交 `undefined` ⇒ 恒判空闲。读失败按空闲(F 类兜底:门留痕 `server.session-notify.active-run-read-failed`)。 */
|
|
71
71
|
readonly activeTaskIdOf: (sessionId: string) => Promise<string | null | undefined>;
|
|
72
72
|
/** 本副本持活 core 流的腿(taskId → 流)。 */
|
|
73
73
|
readonly liveRuns: ReadonlyMap<string, Pick<TaskStream, "notify">>;
|
|
@@ -97,7 +97,7 @@ export type BackgroundCompletionUndelivered = "root_gone" | "root_replaced" | "d
|
|
|
97
97
|
* · **目标住客 = 见证的那一位**(属主与化身,见证铸点那一读):门在投递动作之前紧邻地复核(头注「复核住客」)—— 不在 ⇒ 不交(`root_gone`);
|
|
98
98
|
* 不是见证的那一位(删了、同 id 被重新登记、属主变了)⇒ 不交(`root_replaced`):这棵树的结果不交给同 id 的下一位住客。条目属主 = 见证的属主;
|
|
99
99
|
* 根无主 ⇒ 无主条目(relay 不是谁的请求,没有请求主体可落)。见证那一读里根行不在(化身 `null`)⇒ 目标住客 = 「无主、无令牌」,只有不铸化身的店上
|
|
100
|
-
* 此刻也无主的根与它同(铸化身的店上它恒不同 ⇒
|
|
100
|
+
* 此刻也无主的根与它同(铸化身的店上它恒不同 ⇒ 不交)。
|
|
101
101
|
* · **事件** = 完成帧的 `task_id` / `status` / `summary` / `result` / `seq`(后台子代的 `seq` = 停驻周期号,同一只子代两次复活的完成在收件箱折叠键上不相撞)。
|
|
102
102
|
* 帧上别的键不过门:门的事件型只有这几位(活臂 core 铸 external,空闲臂入箱同一形),两臂一个形。
|
|
103
103
|
* · 交不出去一律一行 warn(`background_completion_undelivered`,带成因),不抛回引擎(sink 是 fire-and-forget)。
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { externalNotificationInboxEntry } from "./orchestration/workflow-completion-inbox.js";
|
|
2
2
|
import { sameOccupant } from "./execution-placement.js";
|
|
3
|
+
import { recordFailOpen } from "./observability/fail-open.js";
|
|
3
4
|
export function createSessionNotifyDoor(io) {
|
|
4
5
|
async function recheck(target) {
|
|
5
6
|
if (io.headOf === undefined)
|
|
@@ -11,7 +12,10 @@ export function createSessionNotifyDoor(io) {
|
|
|
11
12
|
}
|
|
12
13
|
return {
|
|
13
14
|
async deliver(target, event) {
|
|
14
|
-
const liveTaskId = await io.activeTaskIdOf(target.sessionId).catch(() =>
|
|
15
|
+
const liveTaskId = await io.activeTaskIdOf(target.sessionId).catch((e) => {
|
|
16
|
+
recordFailOpen("server.session-notify.active-run-read-failed", `sessionId=${target.sessionId} err=${e instanceof Error ? e.message : String(e)}`);
|
|
17
|
+
return undefined;
|
|
18
|
+
});
|
|
15
19
|
if (liveTaskId !== undefined && liveTaskId !== null && io.liveRuns.has(liveTaskId)) {
|
|
16
20
|
const withdrawn = await recheck(target);
|
|
17
21
|
if (withdrawn !== undefined)
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -401,7 +401,9 @@ export interface ToolApprovalFrame {
|
|
|
401
401
|
* core 侧同构:**真才带,缺席绝不编 false**(governanceForced 同形)——与 `card.requiresRealApproval`
|
|
402
402
|
* (approval-card.ts,恒在布尔)**两侧契约刻意不同、别混**:卡是 durable 店形(askStore 在场)专属,
|
|
403
403
|
* 本键让 live-only 部署(店缺席、帧无卡)也能读到出身位;安全类标记不得依赖店在场([ref] 结构性
|
|
404
|
-
* 缺口成文)。消费语义(cli 挂点已预埋):在场 ⇒ 一切自动放行让位(含记住的规则与 bypass 姿态)。
|
|
404
|
+
* 缺口成文)。消费语义(cli 挂点已预埋):在场 ⇒ 一切自动放行让位(含记住的规则与 bypass 姿态)。
|
|
405
|
+
* 值 = 条目上那一位([ref]:唯一读法 `askDemandsAPerson` —— 这一腿的请求 ∪ 这只问的记录卡;同一只问被另一腿再问时取严,与呈卡帧
|
|
406
|
+
* `card.risk.requiresRealApproval`、列表行顶层同值)。 */
|
|
405
407
|
requiresRealApproval?: true;
|
|
406
408
|
/**
|
|
407
409
|
* **[ref]**(core 7.26.0;**ADDITIVE**,`"tool_approval"` only)—— **这一问没有任何规则能清掉**
|
package/dist/tool-approval.js
CHANGED
|
@@ -13,7 +13,7 @@ import { errorKindOf } from "./observability/err-text.js";
|
|
|
13
13
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
14
14
|
import { ownerOfCheckpointScope } from "./security.js";
|
|
15
15
|
import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS, noteLandedOnRow, noteRecordedReceipt, readBoundInputHash } from "./approval-ask-machine.js";
|
|
16
|
-
import { currentParkArm, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, adoptStoredLiveFacts, buildApprovalRequestFrame, buildReplayFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readStoredCardEnvelope, readRuleEvidence, readAllowRuleShadowed, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readAskReasons, readClosedWordSeat, buildApprovalCompleteFrame, readRuleOfferList, } from "./approval-card.js";
|
|
16
|
+
import { askDemandsAPerson, storedPersonDemandRecord, currentParkArm, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, adoptStoredLiveFacts, buildApprovalRequestFrame, buildReplayFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readStoredCardEnvelope, readRuleEvidence, readAllowRuleShadowed, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readAskReasons, readClosedWordSeat, buildApprovalCompleteFrame, readRuleOfferList, } from "./approval-card.js";
|
|
17
17
|
import { askOwnerAdmits, askRowCarriesOwner, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
|
|
18
18
|
import { APPROVAL_ENDING_CAUSES, SETTLE_OUTCOME, askRowEndingCause, askRowParkTrigger } from "./approval-deny-reasons.js";
|
|
19
19
|
import { settleByPolicy } from "./approval-policy-settlement.js";
|
|
@@ -119,7 +119,7 @@ function askStandingAnswerFacts(req, governanceForced, deploymentListAsks) {
|
|
|
119
119
|
const deploymentListQuestion = carriesDeploymentListQuestion(deploymentListAsks, classes);
|
|
120
120
|
return {
|
|
121
121
|
governanceForced,
|
|
122
|
-
requiresRealApproval: req
|
|
122
|
+
requiresRealApproval: askDemandsAPerson(req, undefined),
|
|
123
123
|
mandated: req.mandated === true,
|
|
124
124
|
grantBinding: grantBindingOf(req),
|
|
125
125
|
classes,
|
|
@@ -136,7 +136,7 @@ export function carriesDeploymentListQuestion(deploymentListAsks, classes) {
|
|
|
136
136
|
export function policyAskStandingAnswerFacts(req, ask, deploymentListQuestion) {
|
|
137
137
|
return {
|
|
138
138
|
governanceForced: false,
|
|
139
|
-
requiresRealApproval: ask
|
|
139
|
+
requiresRealApproval: askDemandsAPerson(ask, undefined),
|
|
140
140
|
mandated: ask.mandated === true,
|
|
141
141
|
grantBinding: grantBindingOf(req),
|
|
142
142
|
classes: [...classifyAsk(ask, noGateFacts({ toolCallId: req.toolCallId, toolName: req.toolName })).classes],
|
|
@@ -190,6 +190,29 @@ function pendingEntryStandingAnswerFacts(entry) {
|
|
|
190
190
|
deploymentListQuestion: entry.deploymentListQuestion === true,
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
|
+
function projectDecisionFacts(facts, frame, card) {
|
|
194
|
+
const rememberRefused = standingAnswerHome(facts) === "none";
|
|
195
|
+
if (frame !== undefined) {
|
|
196
|
+
if (facts.requiresRealApproval)
|
|
197
|
+
frame.requiresRealApproval = true;
|
|
198
|
+
else
|
|
199
|
+
delete frame.requiresRealApproval;
|
|
200
|
+
if (rememberRefused)
|
|
201
|
+
frame.rememberRefused = true;
|
|
202
|
+
else
|
|
203
|
+
delete frame.rememberRefused;
|
|
204
|
+
}
|
|
205
|
+
if (card === undefined)
|
|
206
|
+
return undefined;
|
|
207
|
+
let out = card;
|
|
208
|
+
if (out.risk.requiresRealApproval !== facts.requiresRealApproval)
|
|
209
|
+
out = { ...out, risk: { ...out.risk, requiresRealApproval: facts.requiresRealApproval } };
|
|
210
|
+
if ((out.rememberRefused === true) !== rememberRefused) {
|
|
211
|
+
const { rememberRefused: _stale, ...rest } = out;
|
|
212
|
+
out = rememberRefused ? { ...rest, rememberRefused: true } : rest;
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
193
216
|
function mandatedFromSeat(seat) {
|
|
194
217
|
switch (seat.state) {
|
|
195
218
|
case "word":
|
|
@@ -1352,7 +1375,6 @@ export class ToolApprovalCoordinator {
|
|
|
1352
1375
|
message: typeof req.message === "string" ? redactDeep(req.message) : "",
|
|
1353
1376
|
...(askReasonsOfReq !== undefined ? { askReasons: askReasonsOfReq } : {}),
|
|
1354
1377
|
...(governanceForced ? { governanceForced: true } : {}),
|
|
1355
|
-
...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
1356
1378
|
...(req.mandated === true ? { mandated: true } : {}),
|
|
1357
1379
|
...(standingAnswerHome(askStandingAnswerFacts(req, governanceForced, deploymentListAsks)) === "none" ? { rememberRefused: true } : {}),
|
|
1358
1380
|
...(typeof req.boundGrantDigest === "string" ? { boundGrantDigest: req.boundGrantDigest, ...(req.boundGrants !== undefined ? { boundGrants: req.boundGrants } : {}) } : {}),
|
|
@@ -1379,10 +1401,11 @@ export class ToolApprovalCoordinator {
|
|
|
1379
1401
|
const expiresAtMs = Math.ceil(Date.now() + effectiveWindowMs);
|
|
1380
1402
|
let askId;
|
|
1381
1403
|
let batchId;
|
|
1382
|
-
const card = this.askStore ? buildApprovalCard(frame, req, req
|
|
1404
|
+
const card = this.askStore ? buildApprovalCard(frame, req, askDemandsAPerson(req, undefined)) : undefined;
|
|
1383
1405
|
let cardForFrame = card;
|
|
1384
1406
|
let persistedExpiresAtMs = expiresAtMs;
|
|
1385
1407
|
let factsOfRecord;
|
|
1408
|
+
let recordCard;
|
|
1386
1409
|
let entryExecutionLane = origin.executionLane;
|
|
1387
1410
|
let lateEnsuredRow;
|
|
1388
1411
|
let lateTerminalRow;
|
|
@@ -1462,6 +1485,7 @@ export class ToolApprovalCoordinator {
|
|
|
1462
1485
|
persistedExpiresAtMs = existingOrCreated.row.expiresAtMs;
|
|
1463
1486
|
entryExecutionLane = existingOrCreated.row.executionLane;
|
|
1464
1487
|
cardForFrame = persistedEnvelope.card;
|
|
1488
|
+
recordCard = persistedEnvelope.card;
|
|
1465
1489
|
factsOfRecord = existingOrCreated.inserted ? undefined : persistedEnvelope.liveFacts;
|
|
1466
1490
|
if (factsOfRecord !== undefined)
|
|
1467
1491
|
adoptStoredLiveFacts(frame, factsOfRecord);
|
|
@@ -1510,6 +1534,8 @@ export class ToolApprovalCoordinator {
|
|
|
1510
1534
|
confirmedRowExists = confirmedRow !== null;
|
|
1511
1535
|
if (confirmedRow !== null)
|
|
1512
1536
|
batchId = confirmedRow.batchId;
|
|
1537
|
+
if (confirmedRow !== null)
|
|
1538
|
+
recordCard = storedPersonDemandRecord(confirmedRow.cardJson);
|
|
1513
1539
|
indeterminate = false;
|
|
1514
1540
|
break;
|
|
1515
1541
|
}
|
|
@@ -1906,22 +1932,15 @@ export class ToolApprovalCoordinator {
|
|
|
1906
1932
|
const legGrantBinding = grantBindingOf(req);
|
|
1907
1933
|
const entryGrantBinding = factsOfRecord === undefined ? legGrantBinding : agreedGrantBinding(legGrantBinding, grantBindingOf(factsOfRecord));
|
|
1908
1934
|
const entryMandated = req.mandated === true || factsOfRecord?.mandated === true;
|
|
1909
|
-
const
|
|
1935
|
+
const entryDemandsAPerson = askDemandsAPerson(req, recordCard) || [...(askId !== undefined ? (this.pendingByAskId.get(askId) ?? []) : [])].some((s) => s.requiresRealApproval === true);
|
|
1936
|
+
cardForFrame = projectDecisionFacts(pendingEntryStandingAnswerFacts({
|
|
1910
1937
|
...(effectiveGovernanceForced ? { governanceForced: true } : {}),
|
|
1911
|
-
...(
|
|
1938
|
+
...(entryDemandsAPerson ? { requiresRealApproval: true } : {}),
|
|
1912
1939
|
...(entryMandated ? { mandated: true } : {}),
|
|
1913
1940
|
grantBinding: entryGrantBinding,
|
|
1914
1941
|
classes: entryClasses,
|
|
1915
1942
|
...(entryDeploymentListQuestion ? { deploymentListQuestion: true } : {}),
|
|
1916
|
-
})
|
|
1917
|
-
if (rememberRefused)
|
|
1918
|
-
frame.rememberRefused = true;
|
|
1919
|
-
else
|
|
1920
|
-
delete frame.rememberRefused;
|
|
1921
|
-
if (cardForFrame !== undefined && (cardForFrame.rememberRefused === true) !== rememberRefused) {
|
|
1922
|
-
const { rememberRefused: _stale, ...cardRest } = cardForFrame;
|
|
1923
|
-
cardForFrame = rememberRefused ? { ...cardRest, rememberRefused: true } : cardRest;
|
|
1924
|
-
}
|
|
1943
|
+
}), frame, cardForFrame);
|
|
1925
1944
|
const cardFrame = askId !== undefined && cardForFrame !== undefined
|
|
1926
1945
|
? buildApprovalRequestFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, card: cardForFrame, expiresAtMs: persistedExpiresAtMs, nowMs: Date.now(), live: frame })
|
|
1927
1946
|
: undefined;
|
|
@@ -1947,7 +1966,7 @@ export class ToolApprovalCoordinator {
|
|
|
1947
1966
|
executionLane: entryExecutionLane,
|
|
1948
1967
|
...(ruleLaneMaterial !== undefined ? { ruleLane: ruleLaneMaterial } : {}),
|
|
1949
1968
|
...(effectiveGovernanceForced ? { governanceForced: true } : {}),
|
|
1950
|
-
...(
|
|
1969
|
+
...(entryDemandsAPerson ? { requiresRealApproval: true } : {}),
|
|
1951
1970
|
...(entryMandated ? { mandated: true } : {}),
|
|
1952
1971
|
...(askId !== undefined && batchId !== undefined ? { askId, batchId } : {}),
|
|
1953
1972
|
};
|
|
@@ -1961,6 +1980,16 @@ export class ToolApprovalCoordinator {
|
|
|
1961
1980
|
this.pendingByAskId.set(askId, siblings);
|
|
1962
1981
|
}
|
|
1963
1982
|
siblings.add(pendingEntry);
|
|
1983
|
+
if (entryDemandsAPerson) {
|
|
1984
|
+
for (const s of siblings) {
|
|
1985
|
+
if (s.requiresRealApproval === true)
|
|
1986
|
+
continue;
|
|
1987
|
+
s.requiresRealApproval = true;
|
|
1988
|
+
const card = projectDecisionFacts(pendingEntryStandingAnswerFacts(s), s.frame, s.cardFrame?.card);
|
|
1989
|
+
if (s.cardFrame !== undefined && card !== undefined && card !== s.cardFrame.card)
|
|
1990
|
+
s.cardFrame = { ...s.cardFrame, card };
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1964
1993
|
}
|
|
1965
1994
|
this.askAudit?.recordAsk({
|
|
1966
1995
|
approvalId: wireApprovalId,
|
|
@@ -2872,7 +2901,7 @@ export class ToolApprovalCoordinator {
|
|
|
2872
2901
|
const registrationId = uuidv7();
|
|
2873
2902
|
const facts = {
|
|
2874
2903
|
...(card.governanceForced === true ? { governanceForced: true } : {}),
|
|
2875
|
-
...(card
|
|
2904
|
+
...(askDemandsAPerson(undefined, card) ? { requiresRealApproval: true } : {}),
|
|
2876
2905
|
...(replayFrame.mandated === true ? { mandated: true } : {}),
|
|
2877
2906
|
grantBinding: { kind: "unreadable" },
|
|
2878
2907
|
...(card.classes !== undefined ? { classes: card.classes } : {}),
|
|
@@ -217,7 +217,7 @@ type _GuardPromptManifestKeysReverse = AssertAllKeysHandled<{
|
|
|
217
217
|
type _GuardPromptManifestKeysNonEmpty = AssertAllKeysHandled<{
|
|
218
218
|
[L in keyof ManifestCore]: [ManifestDeclared<ManifestFaces, L>] extends [never] ? L : never;
|
|
219
219
|
}[keyof ManifestCore]>;
|
|
220
|
-
type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "mandated" | "mandate" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling" | "boundGrantAbsence" | "checkpointId";
|
|
220
|
+
type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "mandated" | "mandate" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling" | "boundGrantAbsence" | "checkpointId" | "rowVerdict";
|
|
221
221
|
type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceSessionId" | "agentId" | "approvalCard" | "restoreMode" | "previewWithheld" | "authoredBy" | "runOrigin" | "requestedByCallId";
|
|
222
222
|
type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
|
|
223
223
|
type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* 同文件 G1 e2e 的全码扇出夹具、以及契约文档附录 D.3 那张表(含**小节标题里的码数** —— 那道门本批
|
|
17
17
|
* 刚立,立完就在下一次加码时自己咬住了)。
|
|
18
18
|
*/
|
|
19
|
-
export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed", "session.
|
|
19
|
+
export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed", "session.lineage_rerooted", "server.model_unreachable", "permission.entry_mode_clamped", "classifier.unavailable", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.rule_row_dropped", "permission.allowed_tool_not_granted", "task.tool_turn_incomplete", "route.finish_reason_unknown", "route.stream_end_ignored", "route.credential_origin_mismatch", "mods.registration_refused", "config.session_rules_earlier_grammar", "advisor.not_attached", "inherit_env.names_withheld", "model.capability_downgraded", "context.media_removed", "mcp.authorization_required", "classifier.seat_rate_limited", "context.tool_schema_removed"];
|
|
20
20
|
export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
|
|
21
21
|
/** 白名单谓词(单点):路由与门都读这一个,不许第二处手抄码串。 */
|
|
22
22
|
export declare function isEngineNoticeWireCode(code: string): code is EngineNoticeWireCode;
|
|
@@ -28,7 +28,7 @@ export const ENGINE_NOTICE_WIRE_CODES = [
|
|
|
28
28
|
"approval.read_root_granted",
|
|
29
29
|
"approval.read_root_grant_rejected",
|
|
30
30
|
"instructions.source_changed",
|
|
31
|
-
"session.
|
|
31
|
+
"session.lineage_rerooted",
|
|
32
32
|
"server.model_unreachable",
|
|
33
33
|
"permission.entry_mode_clamped",
|
|
34
34
|
"classifier.unavailable",
|
|
@@ -4,7 +4,7 @@ import { recordFailOpen } from "../observability/fail-open.js";
|
|
|
4
4
|
import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, compactionStartedEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, turnEndEventData, contextUsageEventData, appendParkRow } from "./project.js";
|
|
5
5
|
import { NotifiedKeys, taskNotificationStreamKeyOf } from "../orchestration/workflow-completion-inbox.js";
|
|
6
6
|
import { isParkLedgerEventType } from "./ledger-events.js";
|
|
7
|
-
import { isModelSegmentBoundary, reasoningDeltaBytes, reasoningSegmentEventId } from "./model-segment.js";
|
|
7
|
+
import { blockText, isModelSegmentBoundary, reasoningDeltaBytes, reasoningSegmentEventId } from "./model-segment.js";
|
|
8
8
|
export function createSerialLedgerAppend(appendEvent, startSeq) {
|
|
9
9
|
let seq = startSeq;
|
|
10
10
|
let chain = Promise.resolve();
|
|
@@ -33,6 +33,7 @@ export function createLedgerSink(opts) {
|
|
|
33
33
|
let seq = 0;
|
|
34
34
|
let chain = Promise.resolve();
|
|
35
35
|
let text = "";
|
|
36
|
+
let block = "";
|
|
36
37
|
let reasoning = "";
|
|
37
38
|
let textEventId;
|
|
38
39
|
let reasoningEventId;
|
|
@@ -55,9 +56,11 @@ export function createLedgerSink(opts) {
|
|
|
55
56
|
reasoning = "";
|
|
56
57
|
reasoningEventId = undefined;
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
const segment = text + blockText(block);
|
|
60
|
+
if (segment) {
|
|
61
|
+
await append("text", modelTextEventData({ text: segment, eventId: textEventId }));
|
|
60
62
|
text = "";
|
|
63
|
+
block = "";
|
|
61
64
|
}
|
|
62
65
|
};
|
|
63
66
|
const onEvent = async (ev) => {
|
|
@@ -67,10 +70,15 @@ export function createLedgerSink(opts) {
|
|
|
67
70
|
switch (ev.type) {
|
|
68
71
|
case "text_delta": {
|
|
69
72
|
const td = ev;
|
|
70
|
-
|
|
73
|
+
block += td.delta;
|
|
71
74
|
textEventId ??= td.eventId;
|
|
72
75
|
break;
|
|
73
76
|
}
|
|
77
|
+
case "text_end": {
|
|
78
|
+
text += blockText(block, ev);
|
|
79
|
+
block = "";
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
74
82
|
case "reasoning_delta": {
|
|
75
83
|
const rd = ev;
|
|
76
84
|
const bytes = reasoningDeltaBytes(rd);
|
|
@@ -46,9 +46,19 @@ export type LiveModelTextFrame = (LiveEventIdentity & {
|
|
|
46
46
|
* 逐块帧(`text_delta` / `reasoning_delta`)此前一个逐字、一个逐块脱敏 —— 跨块的凭据在两者上都留明文。现在逐块帧也只放**同一只脱敏器**判定过的字节:
|
|
47
47
|
* 每条流一只脱敏器的**流式口**(`trace/redact.ts` 的 `createRedactionStream`:喂入 / 取已判定前缀 / 收尾冲刷三口,同一只扫描器、同一张表):每块到达时喂入、
|
|
48
48
|
* 取出已判定的那一截放出(脱敏器自己的未决尾不出门,横跨切点的跨度整条不出);段末冲刷,余下的按整段读法放出,再发段末帧。
|
|
49
|
-
* ⇒ 一段的逐块帧依次拼接 = 该段段末帧的正文 —— 逐字节(凭据形换记号,不含凭据形的文本逐字节不变)
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* ⇒ 一段的逐块帧依次拼接 = 该段段末帧的正文 —— 逐字节(凭据形换记号,不含凭据形的文本逐字节不变)。两族的段 = 账本 `text` / `reasoning` 行的单位(同一只段界谓词)。
|
|
50
|
+
*
|
|
51
|
+
* ## 文字块是段的切片(单位只有一个:段)
|
|
52
|
+
* 引擎的 `text_end` 关的是一个**文字块**,不是段:它是这一块的**交付点**,不收段 —— 段的状态(前文)跨块留着,账本 `text` 行按同一个段拼起来判。
|
|
53
|
+
* 一块的原文只有一个定义({@link blockText}:它的 delta 字节;一个 delta 字节都没有、只有 `text_end` 的块 = 它的正文),活流段流与两条账本腿同读 ——
|
|
54
|
+
* 只有 `text_end` 的块照样进本段的流,由段末帧交付(不另出逐块帧),也照样进账本 `text` 行。
|
|
55
|
+
* 交付点上:① 这一块还押在窗里的字节按整段读法、**文本到此为止**交出(流式口的 `takeAll`;一帧不带 `eventId` 的 `text_delta`,紧贴段末帧之前);
|
|
56
|
+
* ② `text_end.content` = 引擎这一块的正文,**接在本段已喂入的前文之后**判(`textEndEventData(ev, 前文)`:与账本行同一只 `redactModelText`;前文只作判定上下文;
|
|
57
|
+
* 前文与块界都是流式口自己的状态(`blockContext` / `blockBytes` / `endBlock`),登记册不另记 —— 流式口只留判定还需要的那一截前文(「扫描窗」,见 `trace/redact.ts` 流式口顶注),读法与带整段前文逐字节同);
|
|
58
|
+
* ③ 记下块界(`endBlock`)。⇒ 每块的逐块帧拼接 = 该块 `text_end.content`(引擎没修剪那一块时逐字节),各块依次拼接 = 账本 `text` 行(块界上到此为止的判定与整段判定不同时除外,见下)。
|
|
59
|
+
* 判定读不到交付点之后的字节,到此为止的判定可以与整段判定不同:一条凭据形恰横跨块界(交付那一截按到此为止判,读得出的半形出记号,后一块上横跨的那一条整枚换记号),
|
|
60
|
+
* 或交付时认作凭据、后文让它不再是(活流那一截是记号)—— 两种都是账本整段判,
|
|
61
|
+
* 段末核对对不上计一次 `server.live-text.hold-diverged` —— 这是交付点的定义,不是第二套规则(等后文 = 一段引子要扣到下一个结构帧,例如整份工具入参生成完)。
|
|
52
62
|
*
|
|
53
63
|
* ## 帧数与身份(消费端看得见的三件事)
|
|
54
64
|
* · 引擎的每一块 delta 仍恰好一帧、同一枚 `eventId`、原序(`delta` 可以是 `""`:那一块的字节还在窗里)—— 续流游标与 E18 的首块句柄不动;
|
|
@@ -57,8 +67,8 @@ export type LiveModelTextFrame = (LiveEventIdentity & {
|
|
|
57
67
|
* 盖住它的跨度还在判定(未收尾的栅栏 / 引号 / JSON 串值 …)⇒ 判定或段末。
|
|
58
68
|
*
|
|
59
69
|
* ## 段界(与账本腿同一只谓词)
|
|
60
|
-
*
|
|
61
|
-
*
|
|
70
|
+
* 两族都只在本流的段界词({@link isModelSegmentBoundary})与腿终局 / 子代终态({@link LiveModelText.cut})收段;引擎的 `text_end` 是块的交付点(上一节),不收段。
|
|
71
|
+
* 收段顺序:同一批里先散文、后推理。散文段收段时若最后一块之后还有字节(那一块没有 `text_end`:自定义 brain / 截断),余下的照样一帧放出,不铸 `text_end`(诚实缺席)。
|
|
62
72
|
*
|
|
63
73
|
* 🔴 **为什么分账**(codex 对抗复审 R1 两根 [high],亲跑复现后按类修):一条腿上并发子代的逐块帧是**交错**到达的。一只累积器只有两种活法,两条都错:
|
|
64
74
|
* · 「换身份就收段」⇒ A 说一半、B 插一句、A 说另一半 ⇒ A 被切成两段,**跨块的凭据正好落在切口上**;
|
|
@@ -67,7 +77,8 @@ export type LiveModelTextFrame = (LiveEventIdentity & {
|
|
|
67
77
|
*
|
|
68
78
|
* ## 投递态(tail 面:有没有人在听)
|
|
69
79
|
* `onEvent(ev, deliver)`:`deliver = false` 时只攒原文、照常按段界收段,不扫描、不出帧(tail 面的免投影快门);一条流从不投递转为投递时,放行位跳到**此刻**的段尾
|
|
70
|
-
* —— 之前的字节没人收,之后的字节仍按**整段**的上下文判定(晚到的订阅者在 `password=` 之后才开始听,值照样认得出)
|
|
80
|
+
* —— 之前的字节没人收,之后的字节仍按**整段**的上下文判定(晚到的订阅者在 `password=` 之后才开始听,值照样认得出)。段末帧的正文恒是整段(`text_end` 恒是整块)。
|
|
81
|
+
* 没人在听时的块交付点只记块界(不扫描、不出帧)。
|
|
71
82
|
*
|
|
72
83
|
* 状态的界:每条流的条目在它的首块非空 delta 上建、在收段时删;腿终局 {@link LiveModelText.cut} 不带匹配器 = 全收全删。
|
|
73
84
|
* 子代终态那一次带匹配器的收段(转发腿的 `onTaskNotification`)让**活着的子代数**成为上界。
|
|
@@ -102,6 +113,15 @@ export interface LiveModelText {
|
|
|
102
113
|
* 伪装成可锚)。
|
|
103
114
|
*/
|
|
104
115
|
export declare function reasoningSegmentEventId(anchor: unknown): string | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* 🔴 **一个文字块的原文 —— 唯一定义**(三处同读,各自不另判:活流段流 {@link createLiveModelText}、账本写口 `trace/ledger-sink.ts`、续跑腿 `http/resume-legs.ts`):
|
|
118
|
+
* 这一块的 `text_delta` 字节;一个 delta 字节都没有的块(只有 `text_end`)= 它的 `text_end` 正文(非串 ⇒ 空)。
|
|
119
|
+
* `deltas` = 这一块(上一个块界之后)已到的 delta 字节;`end` = 这一块的 `text_end`(块还没收口 ⇒ 不传:原文就是已到的 delta)。
|
|
120
|
+
* 段文本 = 段内各块原文依次拼接 —— 账本 `text` 行与活流段流读的都是它。
|
|
121
|
+
*/
|
|
122
|
+
export declare function blockText(deltas: string, end?: {
|
|
123
|
+
content?: unknown;
|
|
124
|
+
}): string;
|
|
105
125
|
/** 一块 `reasoning_delta` 对段的**贡献字节** —— 非空串才算。账本腿与三条 live 腿共用这一只:空/坏形
|
|
106
126
|
* 增量一个字节都不贡献,既不开段也不参与闩锚 ⇒ 两面结构上不可能把锚闩在不同的帧上(`reasoning_end`
|
|
107
127
|
* 与账本 `reasoning` 行的正文早就逐字节相等,身份面随 [ref] 收进同一条规则)。 */
|