@sema-agent/server 7.3.0 → 7.5.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/USAGE.md +63 -0
- package/dist/approval-card.d.ts +15 -3
- package/dist/approval-card.js +41 -7
- package/dist/approval-reconciler.d.ts +120 -16
- package/dist/approval-reconciler.js +146 -19
- package/dist/boot/coordinators.js +13 -3
- package/dist/boot/deferred-sandbox-path-env.d.ts +99 -0
- package/dist/boot/deferred-sandbox-path-env.js +279 -0
- package/dist/boot/execution-env.js +11 -1
- package/dist/boot/org-memory.d.ts +6 -0
- package/dist/boot/org-memory.js +1 -1
- package/dist/boot/reapers.d.ts +2 -0
- package/dist/boot/reapers.js +11 -4
- package/dist/boot/resolve-spec.d.ts +3 -2
- package/dist/boot/resolve-spec.js +175 -63
- package/dist/boot/runner-deps.d.ts +23 -1
- package/dist/boot/runner-deps.js +8 -11
- package/dist/boot/workflow-orchestration.d.ts +8 -3
- package/dist/boot/workflow-orchestration.js +23 -1
- package/dist/capabilities/center-prompts.js +4 -1
- package/dist/config-center/apply-effective.js +33 -10
- package/dist/config-types.d.ts +32 -9
- package/dist/config.d.ts +6 -1
- package/dist/config.js +65 -12
- package/dist/elicitation.js +5 -1
- package/dist/env-facts.d.ts +3 -1
- package/dist/env-facts.js +3 -1
- package/dist/fleet/fleet-bus.d.ts +6 -1
- package/dist/fleet/fleet-bus.js +25 -3
- package/dist/governance-ask-marks.d.ts +31 -0
- package/dist/governance-ask-marks.js +122 -0
- package/dist/hooks/hook-runner.d.ts +28 -0
- package/dist/hooks/hook-runner.js +180 -24
- package/dist/http/routes/diagnostics.d.ts +84 -0
- package/dist/http/routes/diagnostics.js +145 -0
- package/dist/http/routes/memory-policy.d.ts +2 -1
- package/dist/http/routes/memory-policy.js +77 -13
- package/dist/http/routes/runs.js +1 -1
- package/dist/http/routes/tasks.js +87 -29
- package/dist/http/server.d.ts +10 -0
- package/dist/http/server.js +29 -12
- package/dist/http/wire-types.d.ts +7 -2
- package/dist/main.js +51 -8
- package/dist/observability/fail-open.d.ts +109 -0
- package/dist/observability/fail-open.js +227 -0
- package/dist/observability/prompt-manifest.d.ts +17 -0
- package/dist/observability/prompt-manifest.js +8 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +57 -1
- package/dist/orchestration/workflow-notify-journal.js +137 -32
- package/dist/parked-decide.js +9 -4
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -2
- package/dist/plugins/approval-ask-store-memory.js +3 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +27 -5
- package/dist/plugins/approval-ask-store-sql.js +9 -2
- package/dist/plugins/background-shell-support.d.ts +1 -1
- package/dist/plugins/background-shell-support.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +62 -6
- package/dist/plugins/checkpoint-store-sql.js +71 -11
- package/dist/plugins/local-checkpoint-store.d.ts +20 -1
- package/dist/plugins/local-checkpoint-store.js +19 -0
- package/dist/plugins/mailbox-store-sql.d.ts +4 -10
- package/dist/plugins/mailbox-store-sql.js +57 -4
- package/dist/question.d.ts +18 -14
- package/dist/question.js +83 -34
- package/dist/runs.d.ts +8 -0
- package/dist/runs.js +15 -2
- package/dist/runtime-governance.d.ts +18 -0
- package/dist/runtime-governance.js +90 -3
- package/dist/task-settings.d.ts +16 -21
- package/dist/task-settings.js +22 -19
- package/dist/tool-approval.d.ts +33 -6
- package/dist/tool-approval.js +95 -30
- package/dist/trace/core-keyset-guard.d.ts +17 -3
- package/dist/trace/project.d.ts +36 -1
- package/dist/trace/project.js +55 -2
- package/package.json +3 -3
- package/dist/boot/lexical-path-env.d.ts +0 -14
- package/dist/boot/lexical-path-env.js +0 -116
|
@@ -2,6 +2,70 @@ import { formatUserScope } from "@sema-agent/core";
|
|
|
2
2
|
import { parseMemorySyncRequest } from "../../memory-sync.js";
|
|
3
3
|
import { sendJson, sendError } from "../send.js";
|
|
4
4
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
5
|
+
const ORG_PREFIX = "org:";
|
|
6
|
+
/** 本面是否让目录参与判决。闭集穷举 switch:新增第三个模式是**编译**错误,不会静默落进某个默认臂
|
|
7
|
+
* ——这条门在授权轴上,静默默认值正是它不许有的东西。 */
|
|
8
|
+
function directoryDecidesThisFace(mode) {
|
|
9
|
+
switch (mode) {
|
|
10
|
+
case "enforce":
|
|
11
|
+
return true;
|
|
12
|
+
case "audit":
|
|
13
|
+
return false; // 见 gateMemoryScope 顶注的 codex R1 段
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* memory-policy 两面(export 读 / sync 写)共用的属主门 —— design/170 件A §7「第二消费者收编」。
|
|
18
|
+
*
|
|
19
|
+
* 收编前本门把 `org:*` 写死 operator-only(注逐字「membership waits for the S3 登记簿」),于是同一进程里
|
|
20
|
+
* 「谁属于 org:acme」有两个答案:core 准入门问授权目录、本面问 operatorPrincipals。收编后 `org:` 成员性
|
|
21
|
+
* 只由 `deps.orgMemoryDirectory` 回答(与准入 seam 同一个实例、同一份缓存)。
|
|
22
|
+
*
|
|
23
|
+
* 判决序(前两级刻意在目录之前 —— 它们不需要目录也成立,顺带让常路零额外 I/O):
|
|
24
|
+
* ① 本人 user 盘逐字匹配 ⇒ 通过;② 显式 operator ⇒ 通过(ops/迁移面不依赖目录在场);
|
|
25
|
+
* ③ `org:` 键 + 目录在场 + **enforce 模式** ⇒ 查目录:瞬时不可用照原样上抛;取数成功但无该键 ⇒
|
|
26
|
+
* `not_found`;`need==="write"` 另要条目 `write === true`(读授予不隐含写授予,与准入面 writeScope
|
|
27
|
+
* 收窄同源);④ 其余(`userproj:` 等目录没有答案的键、无目录源的部署、audit 模式)⇒ `not_found`。
|
|
28
|
+
*
|
|
29
|
+
* **audit 模式为什么在本面等于「不接目录」**(codex 对抗审 R1,2026-08-07):`audit` 是 operator 表态
|
|
30
|
+
* 「这份目录还没被验证过,先别拿它做判决」。拿未验证的目录去开真数据面(读)甚至写面,是这条表态能
|
|
31
|
+
* 造成的最严重后果——方向恰好反了;`audit=零行为变化` 的公开契约在本面因此读作「逐字保持收编前的
|
|
32
|
+
* operator-only」,连额外那一跳目录 I/O 和 enforce 才有的 503 响应形都不引入。org 面的诊断信号属于
|
|
33
|
+
* 准入侧的 `memory_admission_total{outcome,mode}`,不在本面另开一路。
|
|
34
|
+
*
|
|
35
|
+
* 拒绝一律 `not_found` 而非 403:本面的零存在性 oracle 纪律不变(与 run/trace 属主门同口径),
|
|
36
|
+
* 「不是成员」和「没这个 scope」对调用方必须不可区分。
|
|
37
|
+
*/
|
|
38
|
+
async function gateMemoryScope(deps, principal, scope, need) {
|
|
39
|
+
if (scope === formatUserScope(principal))
|
|
40
|
+
return { kind: "allow" };
|
|
41
|
+
if (explicitOperatorOk(principal, deps.config.operatorPrincipals))
|
|
42
|
+
return { kind: "allow" };
|
|
43
|
+
const directory = deps.orgMemoryDirectory;
|
|
44
|
+
if (!scope.startsWith(ORG_PREFIX) || directory === undefined || !directoryDecidesThisFace(deps.config.memoryOrgAdmissionMode)) {
|
|
45
|
+
return { kind: "not_found" };
|
|
46
|
+
}
|
|
47
|
+
const lookup = await directory.lookup(principal);
|
|
48
|
+
if (lookup.kind === "unavailable")
|
|
49
|
+
return { kind: "unavailable", retryAfterMs: lookup.retryAfterMs };
|
|
50
|
+
const grant = lookup.scopes[scope];
|
|
51
|
+
if (grant === undefined)
|
|
52
|
+
return { kind: "not_found" };
|
|
53
|
+
return need === "write" && grant.write !== true ? { kind: "not_found" } : { kind: "allow" };
|
|
54
|
+
}
|
|
55
|
+
/** 门的拒绝臂下发:瞬时 ⇒ 503 `memory.admission_required` + `retryAfterSec`(秒上取整,永不 0 —— C6;
|
|
56
|
+
* 族形与 sync 提交腿的同码响应逐字一致);终局 ⇒ 404 零 oracle。返回 true = 已应答,调用方须 return。 */
|
|
57
|
+
function sendScopeGateRefusal(res, gate) {
|
|
58
|
+
if (gate.kind === "allow")
|
|
59
|
+
return false;
|
|
60
|
+
if (gate.kind === "unavailable") {
|
|
61
|
+
const retryAfterSec = Math.max(1, Math.ceil(gate.retryAfterMs / 1000));
|
|
62
|
+
res.setHeader("retry-after", String(retryAfterSec)); // 体里有等待提示 ⇒ 标准头同值(与同步提交腿的同码应答逐字一致)
|
|
63
|
+
sendError(res, 503, "memory.admission_required", "org memory directory unavailable — authorization could not be established", { retryAfterSec });
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
sendError(res, 404, "not_found.memory_scope", "not found"); // owner gate: no existence oracle
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
5
69
|
export async function handleMemoryPolicy(req, res, url, ctx) {
|
|
6
70
|
const miss = { fell: false };
|
|
7
71
|
await handleMemoryPolicyBody(req, res, url, ctx, miss);
|
|
@@ -17,11 +81,13 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
|
17
81
|
// C-additive 落点). After the global service-credential gate (a credential/SSO-JWT is already
|
|
18
82
|
// verified); the OWNER gate here is the tenant boundary: the verified principal may export exactly its OWN
|
|
19
83
|
// user disk (`scope === formatUserScope(principal)` — core's mint, byte-identical to memoryScopeFor's) or be
|
|
20
|
-
// an explicit operator (explicitOperatorOk, ops/migration face)
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
84
|
+
// an explicit operator (explicitOperatorOk, ops/migration face), or — design/170 件A §7 收编 — a
|
|
85
|
+
// DIRECTORY MEMBER of an `org:` key (the same org-memory directory instance core's admission seam reads,
|
|
86
|
+
// so one process holds ONE answer to "who belongs to org:acme"). Anything else = 404, NEVER 403 (zero
|
|
87
|
+
// existence oracle, run/trace owner-gate parity); a directory that cannot answer = 503, never 404.
|
|
88
|
+
// `userproj:*` stays operator-only by construction (the directory has no answer for that key shape).
|
|
89
|
+
// proj:* keys of repo-backed projects never live in this DB (铁律: git is their authority) — an owner
|
|
90
|
+
// asking for one just gets an empty set, honestly.
|
|
25
91
|
if (url === "/v1/memory/export" && req.method === "GET") {
|
|
26
92
|
// Auth BEFORE availability (501-before-auth precedent): the backend posture must not oracle
|
|
27
93
|
// to a caller who couldn't read anything anyway.
|
|
@@ -39,10 +105,8 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
|
39
105
|
sendError(res, 400, "request.query_invalid", "missing ?scope=<key>");
|
|
40
106
|
return;
|
|
41
107
|
}
|
|
42
|
-
if (
|
|
43
|
-
sendError(res, 404, "not_found.memory_scope", "not found"); // owner gate: no existence oracle
|
|
108
|
+
if (sendScopeGateRefusal(res, await gateMemoryScope(deps, principal, scope, "read")))
|
|
44
109
|
return;
|
|
45
|
-
}
|
|
46
110
|
try {
|
|
47
111
|
const entries = await deps.memoryExport(scope);
|
|
48
112
|
sendJson(res, 200, { scope, exportedAt: new Date().toISOString(), entries });
|
|
@@ -57,8 +121,8 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
|
57
121
|
// plan 随数据走——reconcile/nextSyncBaseline 是 core 纯函数,由持数据的本 server 在 performMemorySync
|
|
58
122
|
// (memory-sync.ts) 里直接调用;此处只持 HTTP 半场). Gate ORDER mirrors the export face verbatim:
|
|
59
123
|
// auth (401) → backend posture (501, file 形态诚实拒绝——单用户 file 面自己就是 TOC 侧,没有中心
|
|
60
|
-
// 权威半场可服务) → owner gate (404 zero-oracle: verified principal 的 user
|
|
61
|
-
// operator;
|
|
124
|
+
// 权威半场可服务) → owner gate (404 zero-oracle: verified principal 的 user 盘逐字匹配 / explicit
|
|
125
|
+
// operator / `org:` 键的目录成员且条目 write:true;userproj: 仍 operator-only) → 验型 (422 typed)。
|
|
62
126
|
if (req.method === "POST" && url.startsWith("/v1/memory/sync/")) {
|
|
63
127
|
const principal = gatedPrincipal(req, deps.config); // direct-door safe: verified identity, never the spoofable header
|
|
64
128
|
if (!principal) {
|
|
@@ -83,10 +147,10 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
|
83
147
|
sendError(res, 400, "memory_sync_invalid_scope", "missing :scope");
|
|
84
148
|
return;
|
|
85
149
|
}
|
|
86
|
-
|
|
87
|
-
|
|
150
|
+
// 写面:门与 export 面同一个 `gateMemoryScope`,只在 `need` 上分家 —— org 条目须 `write === true`
|
|
151
|
+
// 才放行(读授予不隐含写授予)。属主/operator 两级与读面逐字同判。
|
|
152
|
+
if (sendScopeGateRefusal(res, await gateMemoryScope(deps, principal, scope, "write")))
|
|
88
153
|
return;
|
|
89
|
-
}
|
|
90
154
|
const body = await readJson(req); // 413/400 (oversize / bad JSON) via the typed top-level catch — auth already done
|
|
91
155
|
const parsed = parseMemorySyncRequest(body, scope);
|
|
92
156
|
if (!parsed.ok) {
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -157,7 +157,7 @@ async function streamRunEvents(req, res, deps, runStore, taskId, staleMs) {
|
|
|
157
157
|
// 🔴 开关谓词是**必须**的(codex 复审第二轮 high,2026-08-06):原按「关的时候表里本就没行 ⇒
|
|
158
158
|
// 无需分支」落地,但这个前提在**回滚**下不成立 —— 开着协议的副本(或更新版本)已经落下的
|
|
159
159
|
// `STREAM_PENDING` 行在关掉开关后**仍在表里**,无谓词的重放腿会把它们照常投上 wire,
|
|
160
|
-
//
|
|
160
|
+
// 「开关关 = 逐字零变化」(§6.5/§7.2)当场破。谓词同时省掉了关闭态每次开流的一次 store 往返
|
|
161
161
|
// 与那份 store 故障暴露面。钉:场景9(零帧 **且** 零 store 读 —— 只钉零帧会放过「读了但没投」)。
|
|
162
162
|
preamble: async (signal) => {
|
|
163
163
|
if (!deps.config.streamApproval.enabled)
|
|
@@ -6,7 +6,7 @@ import { defaultSubagentTailBus, projectTailFrame } from "../../fleet/subagent-t
|
|
|
6
6
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "../../orchestration/workflow-completion-inbox.js";
|
|
7
7
|
import { createLedgerSink } from "../../trace/ledger-sink.js";
|
|
8
8
|
import { redactSecrets } from "../../trace/redact.js";
|
|
9
|
-
import { contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage } from "../../trace/project.js";
|
|
9
|
+
import { contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, wiringManifestOperatorEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage } from "../../trace/project.js";
|
|
10
10
|
import { cascadeConfig, runMeta } from "../run-meta.js";
|
|
11
11
|
import { scopedIdempotencyKey } from "../idempotency.js";
|
|
12
12
|
import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
@@ -14,7 +14,8 @@ import { collectSsePreamble } from "../sse-log.js";
|
|
|
14
14
|
import { buildApprovalPreamble, buildApprovalPreambleSseFrames } from "../../approval-card.js";
|
|
15
15
|
import { createApprovalCardEmitter, resolveApprovalLeg, resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
16
16
|
import { buildActiveRunConflict, toDoneFrameResult } from "../active-run-conflict.js";
|
|
17
|
-
import { headerStr, gatedPrincipal } from "../principal-gate.js";
|
|
17
|
+
import { headerStr, gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
18
|
+
import { failOpenTagForDroppedFrame, recordFailOpen } from "../../observability/fail-open.js";
|
|
18
19
|
/** #135([C95]/[C96]):live 腿 SSE 数据帧的**唯一**出口 —— 载荷带账本可锚 `eventId`(非空 string)的帧
|
|
19
20
|
* 前缀 `id: <eventId>` 行(SSE 游标=账本 eventId,壳/SDK 拿它锚进账本或断线换道 events tail 续读);
|
|
20
21
|
* 无 eventId 的帧**不发行**,EventSource 沿用上一游标 —— 绝不合成占位 id(那会把不可锚的位置伪装成可锚)。
|
|
@@ -157,14 +158,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
157
158
|
// relay keeps burning billable tokens to completion (council). Mirrors the closed-flag pattern of the
|
|
158
159
|
// poll-based SSE endpoints, and additionally cancels the generator via spec.signal.
|
|
159
160
|
const ac = new AbortController();
|
|
160
|
-
//
|
|
161
|
-
// Ctrl+C on the shell drops the SSE and this abort kills the run, but the mark was only wired on the
|
|
162
|
-
// cancel-verb/resume legs, so background children reaped by THIS abort read "parent"/"system" instead
|
|
163
|
-
// of "user". Same registration discipline as runs.ts (FIRST on the signal, before core's teardown
|
|
164
|
-
// listeners). Owner = sessionId (the sync leg's core-canonical taskId — resolveSpec sets no spec.taskId,
|
|
165
|
-
// same key as the resume leg); skipSessionScoped inside the helper keeps CC Backgrounded children unmarked.
|
|
166
|
-
if (prepared.spec.sessionId)
|
|
167
|
-
markChildrenStoppedByUserOnAbort(ac.signal, prepared.spec.sessionId, prepared.auth?.principal);
|
|
161
|
+
// 🔴 stoppedBy 归因的注册点在**下面**(createRun 认领成功之后),不在这里 —— #168 件3,见那里的注。
|
|
168
162
|
let closed = false;
|
|
169
163
|
let detachLogged = false; // [854]①b:detach 断连 info 只打一条(req/res 两个 close listener 都可能进来)
|
|
170
164
|
const onDisconnect = () => {
|
|
@@ -199,6 +193,11 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
199
193
|
if (!res.writableEnded)
|
|
200
194
|
onDisconnect();
|
|
201
195
|
});
|
|
196
|
+
// #154 件一:本连接的 `wiring_manifest` 帧投哪一形。判据 = `explicitOperatorOk`(**不是** `isOperator`:
|
|
197
|
+
// 空 `OPERATOR_PRINCIPALS` 必须是「没有人是 operator」;把它读成「所有人都是」会让未配名单的部署把
|
|
198
|
+
// 治理面发给每一个订阅者)。**每连接求一次**、不逐帧求:身份在一条连接内不会变,而逐帧调用会让
|
|
199
|
+
// 每帧都重跑一遍凭证解析 —— 更重要的是,身份只在这一处判,帧循环里没有第二个可漂的判据。
|
|
200
|
+
const wiringOperatorFace = explicitOperatorOk(gatedPrincipal(req, deps.config), deps.config.operatorPrincipals);
|
|
202
201
|
// #151 车3 §5.1:sync 腿的**开流重放**——`sseHeaders` + meta 帧之后、`streamBody` 之前,把同一
|
|
203
202
|
// (owner, session) 的未决审批卡投成 `approval_request` preamble 帧。
|
|
204
203
|
//
|
|
@@ -219,7 +218,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
219
218
|
// 跨腿投递,租户门必须带上,不能让 A 租户的开流看到 B 租户的卡。
|
|
220
219
|
// 有界(2s)+ fail-open:store 抛或挂起都只记一次 warn、零卡继续开流(§5.3)。
|
|
221
220
|
// 🔴 开关谓词与 runs.ts 同源同理由(codex 复审第二轮 high:回滚下表里可能有残留 STREAM_PENDING
|
|
222
|
-
//
|
|
221
|
+
// 行,无谓词会破「开关关 = 逐字零变化」;顺带省掉关闭态每次开流的一次 store 往返)。钉:场景9。
|
|
223
222
|
const replaySessionId = deps.config.streamApproval.enabled ? prepared.spec.sessionId : undefined;
|
|
224
223
|
if (replaySessionId) {
|
|
225
224
|
const replayOwner = gatedPrincipal(req, deps.config) ?? null;
|
|
@@ -317,6 +316,23 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
317
316
|
}
|
|
318
317
|
durableTaskId = tid;
|
|
319
318
|
}
|
|
319
|
+
// ── stoppedBy 归因(#168 件3:注册点必须晚于 run 认领)────────────────────────────────────
|
|
320
|
+
// probe-stoppedby: a client DISCONNECT on the sync stream lane is a USER stop too — Ctrl+C on the
|
|
321
|
+
// shell drops the SSE and this abort kills the run, but the mark was only wired on the
|
|
322
|
+
// cancel-verb/resume legs, so background children reaped by THIS abort read "parent"/"system"
|
|
323
|
+
// instead of "user". Same registration discipline as runs.ts (FIRST on the signal, before core's
|
|
324
|
+
// teardown listeners). Owner = sessionId (the sync leg's core-canonical taskId — resolveSpec sets no
|
|
325
|
+
// spec.taskId, same key as the resume leg); skipSessionScoped inside the helper keeps CC Backgrounded
|
|
326
|
+
// children unmarked.
|
|
327
|
+
//
|
|
328
|
+
// 🔴 **位置即正确性**(#168 件3,3c codex R3 定性的先存病):这条规则说的是「本请求断连 ⇒ 把
|
|
329
|
+
// owner=sessionId 的 task-scoped 子代记成人为停止」,而**只有抢到了这条 session 的请求**才有资格
|
|
330
|
+
// 那么说。注册早于 `createRun` 时,一次 pre-start 断连会把**另一条活 run** 的子代标成 user-stopped
|
|
331
|
+
// (认领随后 409,本请求根本没代表过这条 session)。窗 = SSE 头/首帧 + 开流 preamble(有界 2s)+
|
|
332
|
+
// 一次 DB 往返。认领失败那条路在上面就 return 了 ⇒ 一次都不注册。
|
|
333
|
+
// 无 durable 账本(无 runStore / 无 sessionId)的部署没有认领这回事,注册点与从前等价。
|
|
334
|
+
if (prepared.spec.sessionId)
|
|
335
|
+
markChildrenStoppedByUserOnAbort(ac.signal, prepared.spec.sessionId, prepared.auth?.principal);
|
|
320
336
|
// [900]①/[906]③ detach 车道逐事件落账:sync 腿历史上只 createRun+setTerminal(events 账本恒空,
|
|
321
337
|
// detach 后壳只能轮询终局)。opt-in 车道把 for-await 的每个事件喂给与 bg 腿(runs.ts)共享的
|
|
322
338
|
// LedgerSink(src/trace/ledger-sink.ts —— 单一 switch,落账 type/data 形状与 GET /v1/runs/:id/events
|
|
@@ -751,12 +767,17 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
751
767
|
sseData(res, { type: "compaction_outcome", ...compactionOutcomeEventData(ev) });
|
|
752
768
|
}
|
|
753
769
|
else if (ev.type === "wiring_manifest") {
|
|
754
|
-
// 🔴 core 5.14.0 design/173
|
|
755
|
-
//
|
|
756
|
-
//
|
|
757
|
-
//
|
|
758
|
-
//
|
|
759
|
-
sseData(res, {
|
|
770
|
+
// 🔴 core 5.14.0 design/173 新臂。`manifest.governance` 盖着 `audience:"operator"`,core 把
|
|
771
|
+
// 失败方向写死成「no projection ⇒ do not disclose」——所以默认(租户)形整段剥除,含**对该段
|
|
772
|
+
// 取的哈希** `configFingerprint`(无盐 + 4 布尔 = 16 组合谕示器)。
|
|
773
|
+
// #154 件一:分叉只在这条 live 腿(**唯一知道调用方身份的腿**),两条 durable 腿恒存剥后形
|
|
774
|
+
// ——账本写时不知道未来读者是谁,存全量 = 把 operator-only 面写进租户可读介质。
|
|
775
|
+
sseData(res, {
|
|
776
|
+
type: "wiring_manifest",
|
|
777
|
+
...(wiringOperatorFace
|
|
778
|
+
? wiringManifestOperatorEventData(ev)
|
|
779
|
+
: wiringManifestEventData(ev)),
|
|
780
|
+
});
|
|
760
781
|
}
|
|
761
782
|
else if (ev.type === "human_input") {
|
|
762
783
|
// core 5.14.0 design/171 新臂:谁把什么喂进了这条 run(本帧不带正文,只带载体/署名)。
|
|
@@ -803,21 +824,40 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
803
824
|
const askTaskId = durableTaskId ?? uuidv7();
|
|
804
825
|
const askOwner = gatedPrincipal(req, deps.config) ?? null;
|
|
805
826
|
const emitAsk = (frame) => {
|
|
806
|
-
|
|
807
|
-
|
|
827
|
+
// #157(codex R2 复审 HIGH):流已断时这里是**静默 no-op** —— 上游 coordinator 的 try/catch
|
|
828
|
+
// 因此拿不到任何信号,一次断连就被记成"投递成功",该 ask 挂到 TTL 才按无人应答结算。当时
|
|
829
|
+
// 方向不动,但**必须留痕**:否则 `open-frame-undelivered` 只覆盖"持久化抛错",覆盖不到真正
|
|
830
|
+
// 常见的那条路。
|
|
831
|
+
// ⚠️ **question 半场已在 #173 换了方向**(a318fa1):question / question_complete 两型帧改走
|
|
832
|
+
// 下方 `emitQuestion` 的断流 THROW,于是它们**到不了**本 no-op 臂(两道守卫同步相邻、其间无
|
|
833
|
+
// await);实测 park 墙钟 8002ms → <160ms。今天真正还落在这条静默臂上的只有 elicitation 族
|
|
834
|
+
// 与其它运行流帧。别再据本段把 question 的 TTL 悬挂当作现行行为。
|
|
835
|
+
// `destroyed` 与 `writableEnded` 一起判——emitApproval 用的就是这对谓词。
|
|
836
|
+
// 分类按帧型走(`failOpenTagForDroppedFrame`):这条闭包同时驮着 open 帧、完成面包屑与
|
|
837
|
+
// 其它运行流帧,一刀切会让**一次**断连把保护型计数记两次,真 open 失败率被面包屑盖住。
|
|
838
|
+
if (res.writableEnded || res.destroyed) {
|
|
839
|
+
recordFailOpen(failOpenTagForDroppedFrame(frame.type), `frame=${frame.type}`);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
res.write(`event: ${frame.type}\ndata: ${JSON.stringify(frame)}\n\n`);
|
|
808
843
|
};
|
|
809
844
|
// P1 ①②: this new turn is a stream-open for the session — push any async-workflow completions that
|
|
810
845
|
// finished since (frames ride the SAME connection, before the run's own events; the shell dedups by
|
|
811
846
|
// runId). `askOwner` = the VERIFIED principal → only this caller's own completions are emitted.
|
|
812
847
|
// 🔒 THROW on a dead/ended stream instead of emitAsk's silent no-op (1.80 review: a disconnect
|
|
813
848
|
// between request-send and this drain made the two-phase ack a FALSE success — the entry was
|
|
814
|
-
// removed with no frame ever written; throwing keeps it pending for the next leg).
|
|
815
|
-
//
|
|
849
|
+
// removed with no frame ever written; throwing keeps it pending for the next leg). This wrapper was
|
|
850
|
+
// once the ONLY one that threw; since #173 (a318fa1) emitQuestion, emitApproval and writeLive all
|
|
851
|
+
// throw on a dead stream too. Of the HITL family only ELICIT still rides emitAsk's no-op — but emitAsk
|
|
852
|
+
// is NOT elicit-only: SendUserFile's `file_link` (below) and other runtime frames also write straight
|
|
853
|
+
// through it, so its dead-stream drop surface is wider than the HITL lanes (file_link is covered by its
|
|
854
|
+
// own durable twin, not by a throw).
|
|
816
855
|
// 🔴 drain 案: write these as ANONYMOUS `data:` frames, NOT emitAsk's NAMED
|
|
817
856
|
// `event: <type>` form — the run's own live-leg task_notification frames on this SAME connection
|
|
818
857
|
// are anonymous (L1392/L1474), so a shell parsing the default message channel silently never saw
|
|
819
|
-
// a drained frame (the Monitor "park✓ drain✗" fingerprint).
|
|
820
|
-
//
|
|
858
|
+
// a drained frame (the Monitor "park✓ drain✗" fingerprint). The HITL frames keep their own
|
|
859
|
+
// established NAMED-event contract — elicit still writes straight through emitAsk, question rides
|
|
860
|
+
// emitQuestion (same named-event write, one extra dead-stream guard in front, #173).
|
|
821
861
|
await emitPendingWorkflowCompletions(deps.workflowCompletionInbox, prepared.spec.sessionId, askOwner, (frame) => {
|
|
822
862
|
if (closed || res.writableEnded || res.destroyed)
|
|
823
863
|
throw new Error("stream ended before the completion frame was written");
|
|
@@ -839,11 +879,26 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
839
879
|
: streamBody;
|
|
840
880
|
// #152 复审 A1:`deliverable` 是 durable 部署上 park-vs-live 判决的投递面谓词(见 question.ts 该键注)。
|
|
841
881
|
// 本腿的投递面就是这条 SSE 连接,而 ①b detach 车道下客户端断连**不 abort、不结束这条腿**——run 继续在
|
|
842
|
-
// 本 runWithContext
|
|
843
|
-
//
|
|
844
|
-
//
|
|
882
|
+
// 本 runWithContext 作用域里跑。谓词让判决面此时如实读作「无活流」⇒ durable park(运维可经 /decide
|
|
883
|
+
// 补答),而不是 allow 后挂满 ttl。
|
|
884
|
+
// #173([2937] test 墙钟反证定谳):question 开帧的 emit 不再共享 emitAsk 的静默 no-op——死流上
|
|
885
|
+
// no-op = 开帧「假投递成功」,coordinator 等满 TTL 才以 unavailable 结算(实测 park 墙钟 ≈TTL,
|
|
886
|
+
// 断连→ask 间隔拉到 15s 也一样:判决在 gate 时刻,而 gate 早已判过的窗口里 ask 只剩 emit 一道门)。
|
|
887
|
+
// 收紧与 emitApproval 同形:断流 THROW ⇒ #166 的 delivery==="failed" 臂**立即** settle
|
|
888
|
+
// unavailable,不再等满 TTL。
|
|
889
|
+
// ⚠️ 收紧买到的是**结算速度**,不是可恢复性(2026-08-07 亲读 core 5.16 定):本闭包只在工具已
|
|
890
|
+
// 执行时被调用,即 `createDurableQuestionGate` 已判 allow、core 已过 `suspendAsk` 挂起点 ⇒ 这一
|
|
891
|
+
// 形落 `declined_unavailable` 自答续跑卡(赎回既有审批的腿返 isError),**不铸 checkpoint**。
|
|
892
|
+
// 真正 park 的是判决时投递面就不可达的腿(`deliverable`/`hasLiveContext` 读 false ⇒ 门判 ask),
|
|
893
|
+
// 它们根本走不到这里。别把两形写成一句。
|
|
894
|
+
// elicitation 保持 no-op(其 decline 语义 fail-closed,#166 车登记另议)。
|
|
895
|
+
const emitQuestion = (frame) => {
|
|
896
|
+
if (res.writableEnded || res.destroyed)
|
|
897
|
+
throw new Error("live stream ended — question frame undeliverable");
|
|
898
|
+
emitAsk(frame);
|
|
899
|
+
};
|
|
845
900
|
const withQuestion = () => deps.question
|
|
846
|
-
? deps.question.runWithContext({ taskId: askTaskId, owner: askOwner, emit:
|
|
901
|
+
? deps.question.runWithContext({ taskId: askTaskId, owner: askOwner, emit: emitQuestion, abortSignal: ac.signal, deliverable: () => !res.writableEnded && !res.destroyed }, withElicit)
|
|
847
902
|
: withElicit();
|
|
848
903
|
// [816]/[820]②: the live tool-approval context — SAME id/owner/emit/signal as question/elicit
|
|
849
904
|
// (nested ALS, each its own store). sessionId keys the "allow all edits this session" state.
|
|
@@ -853,8 +908,11 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
853
908
|
// 🔒 approval 帧不用共享 emitAsk 的静默 no-op 语义:res 已结束时 no-op = 卡「假投递成功」,
|
|
854
909
|
// coordinator 等满 TTL 落 deny —— G1 "unavailable"→park 腿在真 wiring 里就不可达(cross-review
|
|
855
910
|
// 抓获)。这里 THROW,coordinator 的 emit-catch 按「无活人可达」返 "unavailable"(durable 部署
|
|
856
|
-
// park;非 durable 由 core fail-closed deny,行为不劣于旧 no-op)
|
|
857
|
-
//
|
|
911
|
+
// park;非 durable 由 core fail-closed deny,行为不劣于旧 no-op)。
|
|
912
|
+
// 当年本注写的「仅 approval 帧收紧」已被 #173 推进一格:question 帧此后与本臂**同形**收紧(见上面
|
|
913
|
+
// `emitQuestion`)。**HITL 三族里**仍留在共享 emitAsk 静默 no-op 上的只有 elicitation(其 decline
|
|
914
|
+
// 结算本身就是 fail-closed,收紧收益小,另议);emitAsk 本身还驮着 SendUserFile 的 `file_link` 等
|
|
915
|
+
// 非 HITL 帧,那些不在本条收紧的辖域内(file_link 的补偿是它自己的 durable 双写,不是 throw)。
|
|
858
916
|
const emitApproval = (frame) => {
|
|
859
917
|
if (res.writableEnded || res.destroyed)
|
|
860
918
|
throw new Error("live stream ended — approval card undeliverable");
|
package/dist/http/server.d.ts
CHANGED
|
@@ -266,6 +266,11 @@ export interface ServiceSeamDeps {
|
|
|
266
266
|
* (MEMORY_ENGINE_BACKEND=pg|tidb); absent / file posture ⇒ 501 honestly (a single-user file plane
|
|
267
267
|
* IS the TOC side of a sync — it has no central-authority half to serve). */
|
|
268
268
|
memorySync?: (scope: string, syncReq: MemorySyncRequest) => Promise<MemorySyncResponse>;
|
|
269
|
+
/** design/170 件A §7 —— org 记忆授权目录,`boot/org-memory.ts` 装配的**同一个实例**(准入 seam 与
|
|
270
|
+
* 本面共享它的 TTL 缓存/退避窗/gen 高水位)。memory-policy 面的 `org:` 属主门用它回答成员性:
|
|
271
|
+
* 读面要该 scope 在授权表里、写面另要条目 `write === true`。缺席=本部署没有目录源 ⇒ `org:` 键
|
|
272
|
+
* 逐字保持 operator-only(收编前行为),绝不因目录不接线而放宽。 */
|
|
273
|
+
orgMemoryDirectory?: import("../org-memory-admission.js").OrgMemoryDirectory;
|
|
269
274
|
/** WEB-SETUP-RECON 附B — org sessionMirror 治理态的 SERVICE 半场 = **观测面(audit),非执法面**。
|
|
270
275
|
* 裁定 (c):「该镜像而未镜像」发生在数据产生端(壳)——本 server 是镜像**目标**,只看得见到达的 push;
|
|
271
276
|
* 没到达的会话对 server 不存在(无客户端会话清单、无心跳契约),required 锁的真执法只能在壳(CC
|
|
@@ -413,6 +418,11 @@ export interface ServiceDeploymentDeps {
|
|
|
413
418
|
ageMs: number;
|
|
414
419
|
error?: string;
|
|
415
420
|
} | undefined;
|
|
421
|
+
/** #154 件三:core `describeStaticWiring(deps, specTemplate)` 的 boot 产物 —— `GET /v1/diagnostics/wiring`
|
|
422
|
+
* 的 `static` 半场,**与启动期拒启自检读的是同一份**(诊断页与拒启判据结构性同源)。
|
|
423
|
+
* operator-only 面,故整份原样下发(含 `governance` 段:本面就是那个段的受众)。
|
|
424
|
+
* 缺席 = 这个进程不是由 composition root 装起来的(测试夹具形)⇒ 该路由诚实地不存在(404)。 */
|
|
425
|
+
staticWiring?: import("@sema-agent/core").WiringManifest;
|
|
416
426
|
/** ① core ruling — SPLIT: `capabilities.workflows` = the ENGINE-CAN axis, boot-computed from core's own
|
|
417
427
|
* `workflowsCapability(deps)` (hardened script runner ∧ governance), NOT the `Boolean(workflowRunStore)` store
|
|
418
428
|
* proxy. Orthogonal to `workflowsList` (the durable-list axis = `workflowRunStore`). Falls back to the store
|
package/dist/http/server.js
CHANGED
|
@@ -29,6 +29,7 @@ import { streamSseLog } from "./sse-log.js";
|
|
|
29
29
|
import { clampVerifyRounds, verifyRoundsFromBody } from "./verify-rounds.js"; // design/97 R9: the shape's neutral leaf (route-ctx.ts type-imports VerifyRoundsSpec from here, not from this file)
|
|
30
30
|
import { handleCapabilities } from "./routes/capabilities.js";
|
|
31
31
|
import { handleObservability } from "./routes/observability.js";
|
|
32
|
+
import { handleDiagnostics } from "./routes/diagnostics.js";
|
|
32
33
|
import { handleMemoryPolicy } from "./routes/memory-policy.js";
|
|
33
34
|
import { handleSessionsList } from "./routes/sessions-list.js";
|
|
34
35
|
import { handleSessions, createSessionsLocal } from "./routes/sessions.js";
|
|
@@ -591,6 +592,9 @@ export function createHttpServer(rawDeps) {
|
|
|
591
592
|
// design/158 A9:观测只读面(routes/observability.ts):/v1/outcomes + /v1/usage。
|
|
592
593
|
if (await handleObservability(req, res, url, ctx))
|
|
593
594
|
return;
|
|
595
|
+
// #154 件三:装配自证只读面(routes/diagnostics.ts):GET /v1/diagnostics/wiring(operator-only)。
|
|
596
|
+
if (await handleDiagnostics(req, res, url, ctx))
|
|
597
|
+
return;
|
|
594
598
|
// design/158 A9:memory 导出/同步 + /v1/policy 只读面(routes/memory-policy.ts)。
|
|
595
599
|
if (await handleMemoryPolicy(req, res, url, ctx))
|
|
596
600
|
return;
|
|
@@ -1452,9 +1456,14 @@ export function createHttpServer(rawDeps) {
|
|
|
1452
1456
|
// "approve a content-ask with no answer" (runtask.js: `taskConfig.onQuestion ?? deps.onQuestion` is
|
|
1453
1457
|
// `undefined` or QUESTION_AWAITS_RESUME), but on an ASK_QUESTION_ENABLED deployment the spec no longer
|
|
1454
1458
|
// stamps the sentinel and `deps.onQuestion` is the coordinator, so core's condition is never met and it
|
|
1455
|
-
// does NOT refuse.
|
|
1456
|
-
//
|
|
1457
|
-
//
|
|
1459
|
+
// does NOT refuse.
|
|
1460
|
+
// ⚠️ The ORIGINAL rationale — "the redeemed leg would take the coordinator's empty 'no human' default" —
|
|
1461
|
+
// is DISPROVED and must not be repeated: since #166 QuestionCoordinator never synthesizes an empty answer
|
|
1462
|
+
// set (every no-human arm returns `{kind:"unavailable"}`), and core 5.16 answers a redeemed AskUserQuestion
|
|
1463
|
+
// with an isError ("will not silently self-answer") rather than a fabricated verdict. The guard STAYS on a
|
|
1464
|
+
// cheaper rationale: the checkpoint is consumed by the CAS BEFORE that revive can report unavailable, so
|
|
1465
|
+
// the operator loses the parked question for a revive that could only fail — refusing pre-CAS costs nothing
|
|
1466
|
+
// and gives a precise error. Do not delete it (parked twin: src/parked-decide.ts, same rationale).
|
|
1458
1467
|
// - an answer is only meaningful on an approve of a question gate; on a deny or a non-question gate it
|
|
1459
1468
|
// signals the operator is deciding a different pending action than the one parked — reject, don't guess.
|
|
1460
1469
|
if (decision === "approve" && pendingTool === "AskUserQuestion" && !answer) {
|
|
@@ -1623,14 +1632,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1623
1632
|
// un-cancellable (cancel 202'd + flagged the row, but nothing honored it). markResuming reset the flag,
|
|
1624
1633
|
// so only a cancel issued DURING this leg aborts it.
|
|
1625
1634
|
const cancelCtrl = new AbortController();
|
|
1626
|
-
// stoppedBy
|
|
1627
|
-
// leg core's canonical taskId == sessionId (the rebuilt taskConfig carries no spec.taskId), so a
|
|
1628
|
-
// task-scoped child's owner KEY VALUE coincides with a session-scoped child's — the old `list()` path
|
|
1629
|
-
// couldn't tell them apart (no sessionScoped flag exposed) and took an honest degrade. The seam filters
|
|
1630
|
-
// by the EXPLICIT flag (`skipSessionScoped:true` inside the helper), so only task-scoped children take
|
|
1631
|
-
// the "user" marker; session-scoped ones a cancelled parent deliberately does NOT stop stay unmarked
|
|
1632
|
-
// (CC Backgrounded semantics). Owner = sessionId (this leg's canonical key), NOT the run-row taskId.
|
|
1633
|
-
markChildrenStoppedByUserOnAbort(cancelCtrl.signal, sessionId, principal);
|
|
1635
|
+
// 🔴 stoppedBy 归因的注册点在**下面**(markResuming 认领成功之后),不在这里 —— #168 件3,见那里的注。
|
|
1634
1636
|
// design/80 seam #2: a resumed leg is itself preempt-able. Register a FRESH preempt controller (markResuming
|
|
1635
1637
|
// also reset preempt_requested), thread a LIVE preemptSignal into the resume taskConfig below, and poll the
|
|
1636
1638
|
// durable preempt flag in the heartbeat — so the scheduler can preempt the RESUMED work once it makes
|
|
@@ -1644,6 +1646,19 @@ export function createHttpServer(rawDeps) {
|
|
|
1644
1646
|
claimedRow = await deps.runStore.markResuming(taskId);
|
|
1645
1647
|
if (!claimedRow)
|
|
1646
1648
|
return { status: 409, body: { error: "run is not in a resumable (suspended) state (already resumed, decided, or expired)", errorCode: "conflict.not_resumable" } };
|
|
1649
|
+
// stoppedBy — resume leg NOW marked (core 1.256 delivered the mark-by-owner seam): on this
|
|
1650
|
+
// leg core's canonical taskId == sessionId (the rebuilt taskConfig carries no spec.taskId), so a
|
|
1651
|
+
// task-scoped child's owner KEY VALUE coincides with a session-scoped child's — the old `list()` path
|
|
1652
|
+
// couldn't tell them apart (no sessionScoped flag exposed) and took an honest degrade. The seam filters
|
|
1653
|
+
// by the EXPLICIT flag (`skipSessionScoped:true` inside the helper), so only task-scoped children take
|
|
1654
|
+
// the "user" marker; session-scoped ones a cancelled parent deliberately does NOT stop stay unmarked
|
|
1655
|
+
// (CC Backgrounded semantics). Owner = sessionId (this leg's canonical key), NOT the run-row taskId.
|
|
1656
|
+
//
|
|
1657
|
+
// 🔴 位置(#168 件3,与 /v1/tasks/stream 同族):归因规则只有**抢到这条 run 的腿**才有资格挂 ——
|
|
1658
|
+
// `markResuming` 输掉的那一支(别的副本已经在 resume,或行已被 reap)在上面就 409 走人,从此一次
|
|
1659
|
+
// 都不注册。本腿此前把它挂在 CAS 之前,只是因为 `cancelCtrl` 的唯一 abort 源(inflightRuns / 心跳
|
|
1660
|
+
// 轮询)也都在 CAS 之后注册才没出事 —— 那是巧合不是保证。
|
|
1661
|
+
markChildrenStoppedByUserOnAbort(cancelCtrl.signal, sessionId, principal);
|
|
1647
1662
|
// MF-Fleet (#7): the row is now `running` again → re-publish it LIVE to the fleet (re-appears as the same row
|
|
1648
1663
|
// that was sitting "waiting" while parked). After onStart only — never if markResuming lost the CAS (a sibling
|
|
1649
1664
|
// owns the row). onTerminal at every settle below keeps the row's lifecycle in lock-step with the run-store row.
|
|
@@ -1948,7 +1963,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1948
1963
|
case "wiring_manifest":
|
|
1949
1964
|
await flush();
|
|
1950
1965
|
await append("wiring_manifest", wiringManifestEventData(ev));
|
|
1951
|
-
break; // 🔴 core 5.14.0 design/173:resume 腿自证清单(每条腿发自己那份)
|
|
1966
|
+
break; // 🔴 core 5.14.0 design/173:resume 腿自证清单(每条腿发自己那份)。本腿与 durable 账本腿共用**同一个**租户面构造器,剥 `governance` 段(audience:"operator",no projection ⇒ do not disclose)——账本写时不知道未来读者是谁,存全量 = 把 operator-only 面写进租户可读介质。⚠️ 「三腿同源」自 #154 件一起不再成立:live sync 腿(routes/tasks.ts)按连接身份分叉,operator 连接走 `wiringManifestOperatorEventData`。所以判据是「**durable 两腿恒剥、live 腿按身份分叉**」,不是「分头挑键即泄露」——别照旧注把已落地的 operator 面当泄露删掉,也别给第四条腿抄错构造器:先问这条腿知不知道读者是谁
|
|
1952
1967
|
case "human_input":
|
|
1953
1968
|
await flush();
|
|
1954
1969
|
await append("human_input", humanInputEventData(ev));
|
|
@@ -2753,6 +2768,8 @@ const ROUTE_LABEL_LITERALS = new Set([
|
|
|
2753
2768
|
// 名册门的枚举器当时只认 `url === …`,整条路由对门隐形(枚举器已一并补上)。
|
|
2754
2769
|
"/v1/fleet/stream",
|
|
2755
2770
|
"/v1/memory/export", "/v1/sendfile-links",
|
|
2771
|
+
// #154:装配自证读面(operator-only)。低频但**每次都在排障现场被打**,落进 `other` 桶等于排障时看不见。
|
|
2772
|
+
"/v1/diagnostics/wiring",
|
|
2756
2773
|
]);
|
|
2757
2774
|
/** Stable, low-cardinality route label for metrics/logs (ids collapsed to `:id`).
|
|
2758
2775
|
* [#104] 字面量**先于**模式:此前模式先查,五条精确路由被形状桶吞掉(`/v1/approvals/stream`
|
|
@@ -235,8 +235,13 @@ export interface TaskRequestBody {
|
|
|
235
235
|
* 后下一轮把占位符换成真 schema。
|
|
236
236
|
* 为什么要上 wire:cli 的 #181 取证表明,忠实跟随广告 schema 解码的 provider(openai-completions 车道)
|
|
237
237
|
* 在 `static` 下会**无界循环** —— 每轮发 `{}` → invalidArgumentsRejection → 下一轮广告仍空,纠错回路
|
|
238
|
-
* 结构上无效。core
|
|
239
|
-
*
|
|
238
|
+
* 结构上无效。core 的缺省链是 `spec.toolMaterializeStrategy ?? env ?? <core 缺省>`,而**进程级 env 说不了
|
|
239
|
+
* 「每个模型」的话**(模型目录车道 per-model api 可混),所以必须有一个**按任务**的位子——这条理由与
|
|
240
|
+
* core 缺省是哪个词无关,故不随其变。
|
|
241
|
+
* ⚠️ core 缺省本身已翻:5.15.0 BREAKING 起 `<core 缺省>` = **`"swap"`**(5.14 世代是 `"static"`;
|
|
242
|
+
* core 把 `"static"` 重新定性为「a strict explicit opt-in」)。本注上一版还写着 `?? "static"`,照它推
|
|
243
|
+
* 「不挂键 ⇒ 落 static」会反。缺省词的属主是 core,本仓不复述第二份——要确认取值去读 core 的
|
|
244
|
+
* `prepare-task` 与其 CHANGELOG。
|
|
240
245
|
* 缺省不挂键(交给 core 的 env/缺省链);未知词 400 fail-loud(与 promptProfile/permissionMode 同姿势 ——
|
|
241
246
|
* 静默折缺省会让调用方以为切了策略却没切)。 */
|
|
242
247
|
toolMaterializeStrategy?: "static" | "swap";
|