@sema-agent/server 7.77.0 → 7.78.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.
@@ -156,7 +156,9 @@ export function createRunnerDeps(ctx) {
156
156
  onElicit: elicitation ? elicitation.elicit : undefined,
157
157
  onQuestion: question ? question.question : undefined,
158
158
  onAsk: createRunnerDepsOnAsk(toolApproval),
159
- autoMode: {},
159
+ autoMode: {
160
+ ...(config.autoModeClassifierFallback !== undefined ? { classifierFallback: config.autoModeClassifierFallback } : {}),
161
+ },
160
162
  sessionStore,
161
163
  memoryBackend: memoryEngine ? memoryEngine.backend : undefined,
162
164
  memoryEngineDir: memoryEngine ? memoryEngine.root : undefined,
@@ -73,6 +73,7 @@ export const CONFIG_CATALOG = [
73
73
  r("AUTH_BRIDGE_ISSUER", "auth", "string", "registry SSO-JWT issuer(auth bridge 的开关之一)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 用 configCenter.baseUrl(配了 center 即默认开桥)" }),
74
74
  r("AUTH_BRIDGE_JWKS_URL", "auth", "url", "registry SSO-JWT 的 JWKS 地址(缺省从 issuer 派生)", { danger: SEC }),
75
75
  r("AUTONOMY", "auth", "enum", "运维 autonomy 模式,tighten-only 叠到每个 TaskSpec(read-only/plan ⇒ 只读手,ask ⇒ 恒 shellGate);center 腿热且 non-sticky", { danger: SEC, enumValues: ["read-only", "ask", "plan", "auto"], configKey: "autonomy", center: cGov("autonomy") }),
76
+ 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" }),
76
77
  r("BAKE_CACHE_BASE", "orchestration", "url", "烤镜像 fetch-once 缓存源 base(注入 --cache-base;绝不受调用方控制)"),
77
78
  r("BAKE_IMAGE_REGISTRY", "orchestration", "string", "烤镜像自动登记的 registry host 前缀(repo=registry/profile)"),
78
79
  r("BAKE_LEASE_MS", "orchestration", "number", "bake 租约窗(ms;runner 心跳推进,过期被 reaper 抢)", { staticDefault: "90000" }),
@@ -9,12 +9,21 @@
9
9
  *
10
10
  * 截断:schema 的键列表有界(64),超界带 `truncatedFrom`(真实总数)——文案如实带上,消费方永远不把有界列表当穷举。
11
11
  * 词表是闭集:`switch` 穷举 `kind`,schema 加新 kind 这里编译红,不会静默漏臂。
12
+ *
13
+ * **归族只有一条判据**(下游只有一个消费者——候选门:非空 ⇒ 这棵树整拒、保 LKG):
14
+ * 运维**写了而没生效**的 ⇒ 候选门错误;值**已被收编接受**(写了、生效了、只是换了个位置或被夹住)⇒ 不入诊断面。
15
+ * settings-schema 2.0.0 的两员新告警各自入族,没有新开第三档:`entry-dropped`(逐条容错丢掉的那条条目——
16
+ * 运维写下的模型/服务器没进目录,与 `domain-defaulted` 是同一件事的细/粗两档)归前者;
17
+ * `legacy-governance-lifted`(0.5 期 runtime 文档里的治理三闸被抬进 governance 域)归后者——那几个键**生效了**
18
+ * (`buildEffective` 用抬升结果解析 governance),把它折成错误会让一份合法的 0.5 期存量配置每一次 refresh 都被
19
+ * 候选门整拒,而它在 1.10.0 上零告警零 domainErrors(亲验读数)。兼容臂的关窗观测归属主(settings-schema 的
20
+ * `onWarning` 流),不借候选门这条带副作用的通道传信息。
12
21
  */
13
22
  import type { EffectiveReadWarning } from "@sema-agent/settings-schema";
14
23
  export type FoldedReadWarning = {
15
24
  readonly domain: string;
16
25
  readonly error: string;
17
26
  };
18
- /** null = 不入诊断面(`hosts-grandfathered`:值已被收编接受,与本地店同口径)。 */
27
+ /** null = 不入诊断面(`hosts-grandfathered` / `legacy-governance-lifted`:值已被收编接受,与本地店同口径)。 */
19
28
  export declare function foldEffectiveReadWarning(w: EffectiveReadWarning): FoldedReadWarning | null;
20
29
  //# sourceMappingURL=read-warnings.d.ts.map
@@ -1,5 +1,6 @@
1
1
  const MAX_ERROR_CHARS = 600;
2
2
  const cap = (s) => s.slice(0, MAX_ERROR_CHARS);
3
+ const errorText = (e) => (e instanceof Error ? e.message : String(e));
3
4
  const keyList = (prefix, keys, truncatedFrom) => {
4
5
  const total = truncatedFrom ?? keys.length;
5
6
  const budget = MAX_ERROR_CHARS - prefix.length;
@@ -18,9 +19,12 @@ const keyList = (prefix, keys, truncatedFrom) => {
18
19
  export function foldEffectiveReadWarning(w) {
19
20
  switch (w.kind) {
20
21
  case "hosts-grandfathered":
22
+ case "legacy-governance-lifted":
21
23
  return null;
22
24
  case "domain-defaulted":
23
- return { domain: w.domain, error: cap(w.error instanceof Error ? w.error.message : String(w.error)) };
25
+ return { domain: w.domain, error: cap(errorText(w.error)) };
26
+ case "entry-dropped":
27
+ return { domain: w.domain, error: cap(`entry dropped by domain schema: ${w.path} (${errorText(w.error)})`) };
24
28
  case "unknown-keys-dropped":
25
29
  return { domain: w.domain, error: keyList("unknown keys dropped by domain schema: ", w.keys, w.truncatedFrom) };
26
30
  case "unknown-keys-carried":
@@ -2,7 +2,7 @@ import { readMcpSourceLabel } from "./mcp-source-label.js";
2
2
  import { createHmac, randomBytes } from "node:crypto";
3
3
  import { foldEffectiveReadWarning } from "./config-center/read-warnings.js";
4
4
  import { FileConfigStore } from "@sema-agent/settings-schema/node";
5
- import { findSkillContent, skillContentHash, refIntegrityIssues, siblingResolver, } from "@sema-agent/settings-schema";
5
+ import { findSkillContent, skillContentHash, refIntegrityIssues, createSiblingResolver, } from "@sema-agent/settings-schema";
6
6
  import { redactSecrets } from "./trace/redact.js";
7
7
  import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetchSkillContent, ConfigCenterHttpError, } from "./config-center/facade.js";
8
8
  import { createLogger } from "./observability/logger.js";
@@ -74,7 +74,7 @@ export class LocalConfigProvider {
74
74
  if (etag !== undefined && etag === tag)
75
75
  return null;
76
76
  const refErrors = [];
77
- const resolve = siblingResolver(store, eff);
77
+ const resolve = createSiblingResolver(store, eff);
78
78
  for (const d of ["rosters", "workers", "models", "collab", "systems", "entitlement"]) {
79
79
  const issues = await refIntegrityIssues(d, eff[d], resolve);
80
80
  if (issues.length > 0)
@@ -1440,6 +1440,18 @@ export interface ServiceConfigFlat {
1440
1440
  * 「缺席=不阻」⇒ 壳发 `permissionMode:"auto"` 即武装)。词表外的值 boot 拒启(安全轴旋钮不许静默回默认)。
1441
1441
  * 合成点 = `boot/runtime-caps.ts` 最外层(applyLocalAutoModeDeny);读面 = `/v1/capabilities.permissionModeAuto`。 */
1442
1442
  permissionsDisableAutoMode: boolean;
1443
+ /** 7.78.0(core 7.18.0 [ref])—— **分类器席答不出话时怎么办**(`AUTO_MODE_CLASSIFIER_FALLBACK`,
1444
+ * 部署级旋钮,闭二词 `main` | `none`,词表外的值 boot 拒启)。
1445
+ *
1446
+ * 为什么这一格必须由部署说了算:core 7.18.0 把**缺省**从「分类器跑不出裁决 ⇒ 这一次门控调用整条拒」
1447
+ * 改成「主模型代判」(`main`)。auto 模式里的分类器是一道**筛子**,而 `main` 让**被筛的那只模型**
1448
+ * 兼任筛子 —— 这是权限轴上的一次放宽,愿不愿意接受它是部署的事,不是引擎的事。`none` = 只留分类器
1449
+ * 那一级梯子 = **7.77.0 及以前的行为逐字**。
1450
+ *
1451
+ * 🔴 **缺席 = 不铸键**(与 `readFace` 族同律,不复制上游缺省):上游改缺省之日起本仓不会成为第二份
1452
+ * 真源。所以缺席时这台机器跟随 core 的 `main`,运维面由 DEPLOY-PREREQS 的升级段点名(「默认变了」
1453
+ * 是升级须知,不是一个可以静默过去的实现细节)。 */
1454
+ autoModeClassifierFallback?: "main" | "none";
1443
1455
  /** S-111([ref] §4.4)—— CC `settings.crossSessionInbound` 的**部署/组织层**(引擎层形的 `managed` 层,
1444
1456
  * CC `policySettings` 对位):本部署对入站跨会话消息的治理三态。`accept` 投递 / `hold` 停在收件会话的待审
1445
1457
  * 队列 / `refuse` 整体退出车道。**未设 = 本层不设**(不是 accept):引擎读作「managed 层未表态」,由其余层
@@ -1814,7 +1826,7 @@ export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "ses
1814
1826
  export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "envModel" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "atModelAllowlist" | "cascadeLadder" | "degrade">;
1815
1827
  /** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
1816
1828
  * 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
1817
- export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "durableApprovalSource" | "streamAskWindowMsSource" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "writeProtectedPaths" | "manualModeShellGate" | "permissionsDisableAutoMode" | "crossSessionInbound" | "crossSessionDialogExpiry">;
1829
+ export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "durableApprovalSource" | "streamAskWindowMsSource" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "writeProtectedPaths" | "manualModeShellGate" | "permissionsDisableAutoMode" | "autoModeClassifierFallback" | "crossSessionInbound" | "crossSessionDialogExpiry">;
1818
1830
  /** 组:memory(记忆面 + TOC 同步腿)。 */
1819
1831
  export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memoryPersistenceCapable" | "memorySync" | "memoryEmbedder" | "memoryOrgAdmissionMode" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "memoryConsolidationDriver" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
1820
1832
  /** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
package/dist/config.js CHANGED
@@ -1162,6 +1162,7 @@ function parseApprovalDomain(ctx) {
1162
1162
  `Refusing to boot rather than treating a misspelled value as "unset": that silently ran the deployment with NO shell gate while the operator believed one was configured`);
1163
1163
  })(process.env.MANUAL_MODE_SHELL_GATE),
1164
1164
  permissionsDisableAutoMode: boolEnv("PERMISSIONS_DISABLE_AUTO_MODE", false),
1165
+ ...optionalEnumKey("autoModeClassifierFallback", "AUTO_MODE_CLASSIFIER_FALLBACK", process.env.AUTO_MODE_CLASSIFIER_FALLBACK, ["main", "none"]),
1165
1166
  ...optionalEnumKey("crossSessionInbound", "CROSS_SESSION_INBOUND", process.env.CROSS_SESSION_INBOUND, CROSS_SESSION_INBOUND_SETTINGS),
1166
1167
  ...optionalEnumKey("crossSessionDialogExpiry", "CROSS_SESSION_DIALOG_EXPIRY", process.env.CROSS_SESSION_DIALOG_EXPIRY, CROSS_SESSION_DIALOG_EXPIRY_VALUES),
1167
1168
  mcpElicitation: {
@@ -1993,7 +1994,7 @@ const APPROVAL_GROUP_KEYS = [
1993
1994
  "approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
1994
1995
  "approvalHmacKeys", "durableApproval", "durableApprovalSource", "streamAskWindowMsSource", "directApprovalDoor", "directDoorActive", "resourceSuspend",
1995
1996
  "resourceSuspendTtlSec", "resourceSuspendTotalTokens", "resourceSuspendTotalBudgetUsd", "resourceSuspendMaxSlices", "askQuestionEnabled", "questionThrottle", "toolApprovalEnabled", "permissionRulesEnabled", "permissionRulesEnabledExplicit", "streamAskWindowMarginMs", "streamApproval", "unattendedApprovalPolicy", "mcpElicitation",
1996
- "sensitiveWritePatterns", "writeProtectedPaths", "manualModeShellGate", "permissionsDisableAutoMode", "crossSessionInbound", "crossSessionDialogExpiry",
1997
+ "sensitiveWritePatterns", "writeProtectedPaths", "manualModeShellGate", "permissionsDisableAutoMode", "autoModeClassifierFallback", "crossSessionInbound", "crossSessionDialogExpiry",
1997
1998
  ];
1998
1999
  const MEMORY_GROUP_KEYS = [
1999
2000
  "memoryEngineEnabled", "memoryEngineDir", "memoryEngineRemoteLaneAllowed", "memoryEngineBackend", "memoryScope",
@@ -13,7 +13,7 @@ import { clearTurnActivity, readTurnActivityMs, recordTurnActivity } from "../..
13
13
  import { mintCancelledResult } from "../../run-cancel-context.js";
14
14
  import { recordResourceWindow } from "../../resource-window.js";
15
15
  import { redactSecrets } from "../../trace/redact.js";
16
- import { contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, wiringManifestOperatorEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, textEndEventData } from "../../trace/project.js";
16
+ import { contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, wiringManifestOperatorEventData, toolRosterDeltaEventData, toolProgressEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, textEndEventData } from "../../trace/project.js";
17
17
  import { cascadeConfig, runMeta } from "../run-meta.js";
18
18
  import { scopedIdempotencyKey } from "../idempotency.js";
19
19
  import { sendJson, sendError, sseHeaders } from "../send.js";
@@ -487,6 +487,16 @@ async function handleTasksBody(req, res, url, ctx, miss) {
487
487
  if (rosterDelta !== undefined)
488
488
  sseData(res, { type: "tool_roster_delta", ...rosterDelta });
489
489
  }
490
+ else if (ev.type === "tool_progress") {
491
+ const progress = toolProgressEventData(ev);
492
+ if (progress !== undefined)
493
+ sseData(res, { type: "tool_progress", ...progress });
494
+ }
495
+ else if (ev.type === "tool_disclosure") {
496
+ const disclosure = toolDisclosureEventData(ev);
497
+ if (disclosure !== undefined)
498
+ sseData(res, { type: "tool_disclosure", ...disclosure });
499
+ }
490
500
  else if (ev.type === "human_input") {
491
501
  sseData(res, { type: "human_input", ...humanInputEventData(ev) });
492
502
  }
@@ -246,7 +246,12 @@ function projectLegApprovalKeys(agent) {
246
246
  };
247
247
  }
248
248
  function projectParkRow(p) {
249
- return { callKey: p.callKey, sessionId: p.sessionId, originRunId: p.originRunId };
249
+ return {
250
+ callKey: p.callKey,
251
+ sessionId: p.sessionId,
252
+ originRunId: p.originRunId,
253
+ ...(p.originUnconfirmed === true ? { originUnconfirmed: true } : {}),
254
+ };
250
255
  }
251
256
  function summarizeWorkflowDetail(run) {
252
257
  const isDone = (s) => s === "completed" || s === "failed";
@@ -343,6 +348,7 @@ function summarizeWorkflowDetail(run) {
343
348
  ...(run.error !== undefined ? { error: redactErrorMessage(run.error) ?? "" } : {}),
344
349
  ...(run.errorCode !== undefined ? { errorCode: run.errorCode } : {}),
345
350
  ...(run.parks !== undefined ? { parks: run.parks.map(projectParkRow) } : {}),
351
+ ...(run.resumeAdmissionIncomplete === true ? { resumeAdmissionIncomplete: true } : {}),
346
352
  ...(run.result !== undefined ? { result: redactSecrets(run.result) } : {}),
347
353
  phases,
348
354
  ...(unphasedView ? { unphased: unphasedView } : {}),
@@ -33,7 +33,7 @@ import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
33
33
  import { createWallClockJumpGuard } from "../wall-clock-jump-guard.js";
34
34
  import {} from "../observability/rate-limit.js";
35
35
  import { withPrincipal } from "../observability/principal-context.js";
36
- import { modelTextEventData, turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
36
+ import { modelTextEventData, turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
37
37
  import { redactSecrets } from "../trace/redact.js";
38
38
  import { createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
39
39
  import { registerEngineNoticeLeg } from "../trace/engine-notice-wire.js";
@@ -1687,6 +1687,13 @@ export function createHttpServer(rawDeps) {
1687
1687
  await append("tool_roster_delta", rosterDelta);
1688
1688
  break;
1689
1689
  }
1690
+ case "tool_disclosure": {
1691
+ await flush();
1692
+ const disclosure = toolDisclosureEventData(ev);
1693
+ if (disclosure !== undefined)
1694
+ await append("tool_disclosure", disclosure);
1695
+ break;
1696
+ }
1690
1697
  case "human_input":
1691
1698
  await flush();
1692
1699
  await append("human_input", humanInputEventData(ev));
@@ -2518,6 +2525,7 @@ const WORKER_SWAP_REDEEMABLE = {
2518
2525
  real_approval_forged: false,
2519
2526
  read_face_damaged: false,
2520
2527
  read_face_forged: false,
2528
+ write_face_damaged: false,
2521
2529
  constraint_chain_missing: false,
2522
2530
  };
2523
2531
  function checkpointRowRedeemableElsewhere(reason) {
@@ -29,6 +29,10 @@ export declare const FAIL_OPEN_TAGS: {
29
29
  readonly cls: "F";
30
30
  readonly note: "⑪(core 7.8.0 design/388,`trace/project.ts` 的 `wiring_manifest.tools` 段与 `tool_roster_delta` 构造器共用):载荷过不了 core 自己的 typebox 判形器(`ToolRoster` / `ToolRosterDelta`)⇒ 那一段缺席 / 那一帧不发,**绝不半张脸上 wire**。放行的最坏后果=消费端这条腿看不到工具名册(壳退回按名字分组、用通用审批卡),不影响任何裁决——名册是观测/渲染面,门在引擎里早已判完。反方向(把一份判不了形的名册照发)更坏:消费端会把读不出的行当成「这只工具没挂」。计数让「某个世代的产生者一直在发坏名册」显形。";
31
31
  };
32
+ readonly "server.trace.projection-input-malformed": {
33
+ readonly cls: "F";
34
+ readonly note: "7.78.0(core 7.18.0 #741/#786/#789/#790,`trace/project.ts` 的五张新投影脸共用一条规则):`tool_progress` / `tool_disclosure` 两帧的必需位读不出 ⇒ **整帧不发**;`wiring_manifest.hooks[]` / `wiring_manifest.lsp` / `context_usage.sections[]` 三段的容器形或成员形读不出 ⇒ 那一行/那一段被丢、全丢光即键缺席。规则一句话:**在场但读不出 ⇒ 计一次并丢弃;缺席从不计数**(缺席是这几张脸上的一等状态,契约把它定义成一个正面事实 —— `hooks` 缺席 = 「本条腿没有自己的钩子记录」、`sections` 缺席 = 「这条腿没装配出段 IR」、`lsp` 缺席 = 「本部署没接 LSP」)。放行的最坏后果=这条腿的消费端少一张观测/渲染面(壳退回上一份已知状态或通用卡片),**不影响任何裁决**——门在引擎里早已判完。反方向(半张脸照发)严格更坏:消费端会把读不出的行当成那个正面事实。计数是**唯一**能把「坏世代 / 第三方 producer 一直在发坏载荷」与那个正面事实分开的位。";
35
+ };
32
36
  readonly "server.task-mcp.source-label-dropped": {
33
37
  readonly cls: "F";
34
38
  readonly note: "B-036 / S-143(`mcp-source-label.ts readMcpSourceLabel`,请求腿 + center 腿共用):`mcpServers[].source`(分组标签,显示用、开集)坏形——空串 / 超 80 字 / 非字符串——**丢键留 server**。放行的最坏后果=这台 server 在 wiring_manifest.mcp[] 里没有分组标签(难看),不是门/权限/凭证;本地 config.d 腿由 settings-schema 1.7.0 在解析处拒,故本臂只在请求腿与远端 /effective 直读 JSON 腿可达。";
@@ -20,6 +20,10 @@ export const FAIL_OPEN_TAGS = {
20
20
  cls: "F",
21
21
  note: "⑪(core 7.8.0 design/388,`trace/project.ts` 的 `wiring_manifest.tools` 段与 `tool_roster_delta` 构造器共用):载荷过不了 core 自己的 typebox 判形器(`ToolRoster` / `ToolRosterDelta`)⇒ 那一段缺席 / 那一帧不发,**绝不半张脸上 wire**。放行的最坏后果=消费端这条腿看不到工具名册(壳退回按名字分组、用通用审批卡),不影响任何裁决——名册是观测/渲染面,门在引擎里早已判完。反方向(把一份判不了形的名册照发)更坏:消费端会把读不出的行当成「这只工具没挂」。计数让「某个世代的产生者一直在发坏名册」显形。",
22
22
  },
23
+ "server.trace.projection-input-malformed": {
24
+ cls: "F",
25
+ note: "7.78.0(core 7.18.0 #741/#786/#789/#790,`trace/project.ts` 的五张新投影脸共用一条规则):`tool_progress` / `tool_disclosure` 两帧的必需位读不出 ⇒ **整帧不发**;`wiring_manifest.hooks[]` / `wiring_manifest.lsp` / `context_usage.sections[]` 三段的容器形或成员形读不出 ⇒ 那一行/那一段被丢、全丢光即键缺席。规则一句话:**在场但读不出 ⇒ 计一次并丢弃;缺席从不计数**(缺席是这几张脸上的一等状态,契约把它定义成一个正面事实 —— `hooks` 缺席 = 「本条腿没有自己的钩子记录」、`sections` 缺席 = 「这条腿没装配出段 IR」、`lsp` 缺席 = 「本部署没接 LSP」)。放行的最坏后果=这条腿的消费端少一张观测/渲染面(壳退回上一份已知状态或通用卡片),**不影响任何裁决**——门在引擎里早已判完。反方向(半张脸照发)严格更坏:消费端会把读不出的行当成那个正面事实。计数是**唯一**能把「坏世代 / 第三方 producer 一直在发坏载荷」与那个正面事实分开的位。",
26
+ },
23
27
  "server.task-mcp.source-label-dropped": {
24
28
  cls: "F",
25
29
  note: "B-036 / S-143(`mcp-source-label.ts readMcpSourceLabel`,请求腿 + center 腿共用):`mcpServers[].source`(分组标签,显示用、开集)坏形——空串 / 超 80 字 / 非字符串——**丢键留 server**。放行的最坏后果=这台 server 在 wiring_manifest.mcp[] 里没有分组标签(难看),不是门/权限/凭证;本地 config.d 腿由 settings-schema 1.7.0 在解析处拒,故本臂只在请求腿与远端 /effective 直读 JSON 腿可达。",
@@ -101,6 +101,7 @@ const RESULT_KEY_TREATMENT = {
101
101
  pendingApproval: "verbatim",
102
102
  approvalWaitedMs: "verbatim",
103
103
  effectiveReadFace: "verbatim",
104
+ effectiveWriteFace: "verbatim",
104
105
  effectiveReadDenyPatterns: "verbatim",
105
106
  effectiveMemoryScopes: "verbatim",
106
107
  stats: "verbatim",
@@ -32,9 +32,13 @@ export type WorkflowRunParkKey = NonNullable<WorkflowRun["parks"]>[number];
32
32
  * 盘上 run blob → `parks[]` 的**唯一读者**(宪法 [ref] §2:进程外字节禁裸 `as` 铸形;与本文件
33
33
  * {@link readAgentSessionLedgerLine} 同一条姿态)。
34
34
  *
35
- * 判据与 core 的 resume 准入**同源**(亲读安装树 `dist/orchestration/workflow.js:2217-2226`):
36
- * · 这一位**不是数组**(旧引擎写的记录 / 被投影剥掉)⇒ 读不出来。core 对这样的记录整条拒 resume
37
- * (`workflow.park_truth_unreadable` / `parks_unreadable`),本仓因此也兑付不了它 —— 答缺席,由调用方响亮拒;
35
+ * 判据(7.78.0 / core 7.18.0 [ref] 复核后的口径):
36
+ * · 这一位**不是数组**(旧引擎写的记录 / 被投影剥掉)⇒ **本读者**读不出来 ⇒ 答缺席,由调用方响亮拒。
37
+ * ⚠️ 这已**不再**等于「core 也拒 resume」:core 7.18.0 起对 `parks` 缺席的记录从 journal 的 `parked`
38
+ * 臂 + 记录自己 `parked` 腿行的 `parkedCheckpointToken` 派生候选并逐个验真,非空即准入(三臂的第二臂)。
39
+ * 本读者仍答缺席的理由是另一条:那条派生只发生在**准入期**,赎回键从记录上读不出来 —— 服务端要么拒,
40
+ * 要么在自己这边重算 core 的三臂(被 [ref] 统一掉的双读)。恢复动作因此是「去 resume 这条 run」,
41
+ * 逐字见 `parked-decide.ts` 的 `denyWorkflowParksUnreadable` 顶注;
38
42
  * · **任一条目形不合 ⇒ 整只读不出来**(整档成败制,与本文件账本行同判)。core 对同一形抛
39
43
  * `park_binding_broken` 整条拒 resume;「丢掉坏的、留下好的」会静默铸出一份**比真相少几条 park** 的行集,
40
44
  * 而这只读者的消费面正是 park 的赎回路由。
@@ -49,16 +49,26 @@ export type ParkOrigin = {
49
49
  park: WorkflowRunParkKey;
50
50
  }
51
51
  /**
52
- * S-252 —— **这条记录的 park 真源读不出来**(第四种出身,不是第四条车道)。
52
+ * S-252 —— **这条记录的 park 真源在本车道读不出来**(第四种出身,不是第四条车道)。
53
53
  *
54
54
  * 索引行还在、run 行也还在,但记录上没有一份读得出来的 `parks[]`:旧引擎(core < 7.17.0)写下的存量行
55
- * 根本没有这一位,或者某一条条目的形坏了。而这一位是**引擎自己**的 park 责任表 —— core 的 resume 准入
56
- * 读不到它就整条拒(`workflow.park_truth_unreadable` / `parks_unreadable`,亲读安装树
57
- * `dist/orchestration/workflow.js:2217-2218`;条目形坏 ⇒ `park_binding_broken`,`:2222-2224`)。
58
- * ⇒ 这条决定**结构上**送不出去:答 200「已交给宿主的下一跑」就是一张兑付不了的回执。
55
+ * 根本没有这一位,或者某一条条目的形坏了。而这一位是**引擎自己**的 park 责任表,也是本车道把调用方的
56
+ * checkpoint join 到某一条 park 的**单一真源** ⇒ 这条决定**结构上**送不出去:答 200「已交给宿主的
57
+ * 下一跑」就是一张兑付不了的回执。
59
58
  * 此前这一格与「索引落后一拍 / 这条 run 已不再负责它」两种**正常** miss 完全同形,于是落 legacy 腿拿
60
59
  * 一个错因 409。判别式据真源的**可读性**把这一格分出来,三个消费点各自响亮处置(decide 拒 + 不驱动
61
60
  * 宿主续跑;SLA sweep 照旧静默跳过——它同样复核不了)。
61
+ *
62
+ * 🔴 **7.78.0 / core 7.18.0([ref]):本臂的「为什么拒」没变,「拒了之后干什么」变了。**
63
+ * 旧注(与旧拒文)断言 core 的 resume 准入对 `!Array.isArray(prior.parks)` 一律整条拒,并据此告诉运维
64
+ * 「这条 run 恢复不了,开一条新的」。core 7.18.0 把那条准入拆成**三臂**([ref]「走 a″」),该断言
65
+ * 自此为假:①记录带 `parks` ⇒ 那张表独自作数(不变);②**`parks` 缺席 ⇒ 候选从 journal 的 `parked` 臂
66
+ * 与记录自己 `parked` 腿行的 `parkedCheckpointToken` 取并集派生**,非空即准入(每个派生候选仍逐个
67
+ * `checkpointStore.get(token)` 验真);③缺席且并集为空 ⇒ 仍拒(`parks_unreadable`,条件收窄到这一格)。
68
+ * ⇒ 存量记录**多数可以被 resume 救回来**,而旧拒文让人去开新 run = 把一条救得回来的 run 劝弃。
69
+ * 本车道**仍然拒**(core 逐字:那种记录的赎回键是在准入期派生的、**从记录上读不出来**;本腿要么拒、
70
+ * 要么就得在服务端重算 core 的三臂 —— 后者正是这次统一掉的双读),但恢复动作改成真话:**去 resume
71
+ * 这条 run**。准入一旦走完,core 会把 `parks` 写上记录([ref]),下一次 `/decide` 就走正常的 workflow 臂。
62
72
  */
63
73
  | {
64
74
  kind: "workflow_parks_unreadable";
@@ -228,13 +238,18 @@ export type WorkflowParkedDecidePlan = {
228
238
  /**
229
239
  * S-252 —— **park 真源读不出来的那条记录的响亮拒**(与上面那族拒臂同一种返回形)。
230
240
  *
231
- * 为什么是拒而不是「尽力而为地投递一次」(机理按安装树 core 7.17.1 `dist/orchestration/workflow.js` 亲读):
232
- * · 记录上的 `parks[]` 是**引擎自己**的 park 责任表,而且是**下一次 resume 的准入输入**:
233
- * `!Array.isArray(prior.parks)` ⇒ 整条拒 resume(`workflow.park_truth_unreadable` / `parks_unreadable`,
234
- * :2217-2218);某一条条目形坏 ⇒ `park_binding_broken`(:2222-2224)。两者都不是「少一条 park」,
235
- * 是**这条 run 再也 resume 不了** —— 而本车道的投递腿恰恰是让宿主去 `Workflow({resumeFromRunId})`。
236
- * ⇒ 决定结构上送不出去。答 200「已交给宿主的下一跑」就是一张兑付不了的回执:卡还 pending、人以为决完了。
237
- * ⇒ 拒,并把真话说出来:这条 run 恢复不了,开一条新的;卡仍 pending,什么都没有被伪造。
241
+ * 为什么是拒而不是「尽力而为地投递一次」:记录上的 `parks[]` 是本车道把调用方的 checkpoint join 到某一条
242
+ * park 的**单一真源**。读不出来 ⇒ 这条决定结构上送不出去,答 200「已交给宿主的下一跑」就是一张兑付不了的
243
+ * 回执:卡还 pending、人以为决完了。⇒ 拒,并把真话说出来;卡仍 pending,什么都没有被伪造。
244
+ *
245
+ * 🔴 **7.78.0 / core 7.18.0([ref]):真话变了一句,拒没变。** 亲读安装树 7.18.0 的三臂准入(判别式顶注逐字
246
+ * 记着):`parks` 缺席时 core **从 journal 的 `parked` 臂 + 记录自己 `parked` 腿行的 `parkedCheckpointToken`
247
+ * 派生候选**并逐个向 checkpoint 店验真,非空即准入 —— 所以「这条 run 恢复不了」自 7.18.0 起对**多数**存量
248
+ * 记录是假的,照旧文答就是劝人弃掉一条救得回来的 run。本腿仍拒的理由是另一条(core 逐字):那种记录的赎回键
249
+ * 是**在准入期派生的、从记录上读不出来**,服务端要么拒、要么重算 core 的三臂(被这次统一掉的双读)。
250
+ * ⇒ 恢复动作 = **去 resume 这条 run**(`Workflow({resumeFromRunId})`):准入走完 core 会把 `parks` 写上
251
+ * 记录([ref]),下一次 `/decide` 走正常 workflow 臂。若引擎那一步也拒(journal 与腿行都给不出候选,
252
+ * core 的第三臂),那才是真的恢复不了。
238
253
  *
239
254
  * 这条臂的受害集 = **存量持久数据**(core < 7.17.0 的引擎写下、升级后还 parked 着的 run 记录)。硬 breaking
240
255
  * 三句里持久数据那一句要的就是这个形:响亮拒,不做静默双读(读 `agents[]` 补一份身份正是被退役的双算)。
@@ -89,7 +89,7 @@ export function denyWorkflowParksUnreadable(origin) {
89
89
  return {
90
90
  status: 409,
91
91
  body: {
92
- error: "this parked workflow agent's run record carries no readable list of the parks the run owns — the engine writes that list when it parks, and it is the only proof that this checkpoint token belongs to that run. A record written by an engine older than the one deployed here (or one whose entries are damaged) is refused by a resume of the run itself for the same reason, so the decision is NOT delivered rather than answered with a receipt nothing can redeem. The approval card stays pending and nothing is forged: this run cannot be resumed — start a new run",
92
+ error: "this parked workflow agent's run record carries no readable list of the parks the run owns — the engine writes that list when it parks, and it is the only proof that this checkpoint token belongs to that run. This route cannot join your checkpoint to a park without it, so the decision is NOT delivered rather than answered with a receipt nothing can redeem. The approval card stays pending and nothing is forged. Resume the run itself instead: the engine derives the parks such a record owns from its journal and its own parked legs, proves each against the checkpoint store, and writes the list onto the record once that admission completes — after which this route works normally. Only if the engine also refuses that resume is the run truly unrecoverable",
93
93
  errorCode: "decide.workflow_parks_unreadable",
94
94
  runId: origin.runId,
95
95
  },
@@ -80,9 +80,13 @@ export declare function withRedactedError<T extends {
80
80
  *
81
81
  * 🔴 **`parks[]` 是这条阶梯唯一不许碰的东西**(core 7.17.0 [ref] 义务②)。
82
82
  * 它不是观测位,是**下一次 resume 的准入输入**:core 的 resume 准入在任何 agent 被派发之前读记录上的
83
- * 这一位,读不到就整条拒(`workflow.park_truth_unreadable` / `parks_unreadable`)——不是「少一条 park」,
84
- * 是**这条 run 再也 resume 不了**;它同时也是本仓 `/decide` 的 workflow 车道复核「这条 park 属于这条 run」
85
- * 的**单一真源**(S-252)。实现上三级全部走 `{...上一级}` 展开、没有任何一级点名裁它,机器背书 =
83
+ * 这一位。🔴 **7.78.0 / core 7.18.0([ref])口径更新**:读不到**不再**是整条拒 —— core 改成三臂,缺席时
84
+ * 从 journal 的 `parked` 臂 + 记录自己 `parked` 腿行的 `parkedCheckpointToken` 派生候选逐个验真,非空即
85
+ * 准入,空才拒(`parks_unreadable` 条件收窄到那一格)。但**本店剥它仍是灾难级**:带 `parks` 的记录走
86
+ * 第一臂,「那张表独自作数」—— 剥成缺席就把一条走第一臂的记录推去走派生臂,而派生的两个缓存只装这条 run
87
+ * **自己做出**的 park,继承来、从未跑到的那一条**只存在于被剥掉的那张表里**,于是它要么被漏掉、要么被
88
+ * 当成不存在而在 resume 时活着重跑([ref] 正要消灭的形)。它同时也是本仓 `/decide` 的 workflow 车道复核
89
+ * 「这条 park 属于这条 run」的**单一真源**(S-252)。实现上三级全部走 `{...上一级}` 展开、没有任何一级点名裁它,机器背书 =
86
90
  * `test/slim-oversize-run.test.ts` 里那两格(最狠的一级上 `parks` 仍逐字完整 + 负控:记录本来没有
87
91
  * 这一位时不许凭空补 `[]`)。缺席 ⇒ 不补:补 `[]` 会把「这版引擎没写这个位」伪造成「这条 run 没有 park」,
88
92
  * 而 core 对两者的处置相反。
@@ -50,7 +50,7 @@ type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded,
50
50
  type AskFrameServerMinted = "type" | "approvalId" | "argsOmitted" | "outcome" | "governanceForced" | "expiresAtMs" | "expiresInMs" | "serverNowMs" | "parked" | "inputHasBidi";
51
51
  type _GuardAskFrame = AssertAllKeysHandled<Exclude<keyof ToolApprovalFrame, AskProjected | AskFrameServerMinted>>;
52
52
  type _GuardAskFrameReverse = AssertAllKeysHandled<Exclude<AskProjected | AskFrameServerMinted, keyof ToolApprovalFrame>>;
53
- type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end" | "tool_roster_delta";
53
+ type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end" | "tool_roster_delta" | "tool_progress" | "tool_disclosure";
54
54
  type _GuardTaskEvent = AssertAllKeysHandled<Exclude<TaskEvent["type"], TaskEventHandled>>;
55
55
  type _GuardTaskEventReverse = AssertAllKeysHandled<Exclude<TaskEventHandled, TaskEvent["type"]>>;
56
56
  type MailboxProjected = "seq" | "from" | "content" | "sentAt" | "hopChain" | "peerMeta";
@@ -59,7 +59,7 @@ type _GuardMailboxReverse = AssertAllKeysHandled<Exclude<MailboxProjected, keyof
59
59
  type MailboxAppendProjected = "from" | "content" | "sentAt" | "hopChain" | "peerMeta";
60
60
  type _GuardMailboxAppend = AssertAllKeysHandled<Exclude<keyof MailboxAppendMessage, MailboxAppendProjected>>;
61
61
  type _GuardMailboxAppendReverse = AssertAllKeysHandled<Exclude<MailboxAppendProjected, keyof MailboxAppendMessage>>;
62
- type WiringSectionProjected = "schemaVersion" | "leg" | "ask" | "question" | "interaction" | "elicit" | "parkLane" | "session" | "fleet" | "permissionRules" | "modelGate" | "autoMode" | "mcp" | "tools";
62
+ type WiringSectionProjected = "schemaVersion" | "leg" | "ask" | "question" | "interaction" | "elicit" | "parkLane" | "session" | "fleet" | "permissionRules" | "modelGate" | "autoMode" | "mcp" | "tools" | "hooks" | "lsp";
63
63
  type WiringSectionExcluded = "governance" | "configFingerprint";
64
64
  type _GuardWiringSections = AssertAllKeysHandled<Exclude<keyof WiringManifest, WiringSectionProjected | WiringSectionExcluded>>;
65
65
  type _GuardWiringSectionsReverse = AssertAllKeysHandled<Exclude<WiringSectionProjected | WiringSectionExcluded, keyof WiringManifest>>;
@@ -122,10 +122,10 @@ type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "prev
122
122
  type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
123
123
  type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
124
124
  type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested";
125
- type TaskSpecUnwired = "excludeAllTools" | "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars";
125
+ type TaskSpecUnwired = "excludeAllTools" | "writeFace" | "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars";
126
126
  type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
127
127
  type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
128
- type TaskResultForwarded = "taskId" | "runId" | "sessionId" | "terminal" | "model" | "effectiveReasoning" | "result" | "salvagedOutput" | "remoteEnvFailures" | "apiFailure" | "retryAfterMs" | "degraded" | "structuredOutput" | "rewindNotes" | "haltedOnUserRejection" | "haltedByUser" | "strandedHumanAnswers" | "pendingApproval" | "approvalWaitedMs" | "effectiveReadFace" | "effectiveReadDenyPatterns" | "effectiveMemoryScopes" | "stats" | "editedFiles";
128
+ type TaskResultForwarded = "taskId" | "runId" | "sessionId" | "terminal" | "model" | "effectiveReasoning" | "result" | "salvagedOutput" | "remoteEnvFailures" | "apiFailure" | "retryAfterMs" | "degraded" | "structuredOutput" | "rewindNotes" | "haltedOnUserRejection" | "haltedByUser" | "strandedHumanAnswers" | "pendingApproval" | "approvalWaitedMs" | "effectiveReadFace" | "effectiveReadDenyPatterns" | "effectiveWriteFace" | "effectiveMemoryScopes" | "stats" | "editedFiles";
129
129
  type TaskResultStripped = never;
130
130
  type _GuardTaskResult = AssertAllKeysHandled<Exclude<keyof TaskResult, TaskResultForwarded | TaskResultStripped>>;
131
131
  type _GuardTaskResultReverse = AssertAllKeysHandled<Exclude<TaskResultForwarded | TaskResultStripped, keyof TaskResult>>;
@@ -139,7 +139,7 @@ type GateOutcomeProjected = "disposition" | "settlement" | "origin";
139
139
  type _GuardGateOutcome = AssertAllKeysHandled<Exclude<keyof GateOutcome, GateOutcomeProjected>>;
140
140
  type _GuardGateOutcomeReverse = AssertAllKeysHandled<Exclude<GateOutcomeProjected, keyof GateOutcome>>;
141
141
  type GateDispositionKeys = GateDisposition extends infer D ? (D extends object ? keyof D : never) : never;
142
- type GateDispositionProjected = "kind" | "deniedBy" | "cause";
142
+ type GateDispositionProjected = "kind" | "deniedBy" | "cause" | "classifier";
143
143
  type _GuardGateDisposition = AssertAllKeysHandled<Exclude<GateDispositionKeys, GateDispositionProjected>>;
144
144
  type _GuardGateDispositionReverse = AssertAllKeysHandled<Exclude<GateDispositionProjected, GateDispositionKeys>>;
145
145
  type SettlementProjected = "kind" | "who" | "when" | "note";
@@ -148,12 +148,12 @@ type _GuardSettlementReverse = AssertAllKeysHandled<Exclude<SettlementProjected,
148
148
  type SettlementWhoProjected = "party" | "approver" | "window";
149
149
  type _GuardSettlementWho = AssertAllKeysHandled<Exclude<SettlementWhoKeys, SettlementWhoProjected>>;
150
150
  type _GuardSettlementWhoReverse = AssertAllKeysHandled<Exclude<SettlementWhoProjected, SettlementWhoKeys>>;
151
- type WorkflowRunForwarded = "id" | "scope" | "status" | "startedAt" | "endedAt" | "createdAt" | "rev" | "parks" | "name" | "description" | "result" | "error" | "errorCode" | "agentFailures" | "stats" | "phases" | "agents" | "groups";
151
+ type WorkflowRunForwarded = "id" | "scope" | "status" | "startedAt" | "endedAt" | "createdAt" | "rev" | "parks" | "name" | "description" | "result" | "error" | "errorCode" | "agentFailures" | "stats" | "phases" | "agents" | "groups" | "resumeAdmissionIncomplete";
152
152
  type WorkflowRunStripped = "sourceTaskId" | "originatingSessionId" | "effectiveArgs" | "budgetOvershoot" | "timeoutInterruption" | "resultFull" | "completionId" | "resume" | "journalSkips";
153
153
  type _GuardWorkflowRun = AssertAllKeysHandled<Exclude<keyof WorkflowRun, WorkflowRunForwarded | WorkflowRunStripped>>;
154
154
  type _GuardWorkflowRunReverse = AssertAllKeysHandled<Exclude<WorkflowRunForwarded | WorkflowRunStripped, keyof WorkflowRun>>;
155
155
  type ParkRowKeys = keyof NonNullable<WorkflowRun["parks"]>[number];
156
- type ParkRowProjected = "callKey" | "sessionId" | "originRunId";
156
+ type ParkRowProjected = "callKey" | "sessionId" | "originRunId" | "originUnconfirmed";
157
157
  type ParkRowWithheld = "token";
158
158
  type _GuardParkRow = AssertAllKeysHandled<Exclude<ParkRowKeys, ParkRowProjected | ParkRowWithheld>>;
159
159
  type _NoDoubleBookParkRow = AssertAllKeysHandled<Extract<ParkRowProjected, ParkRowWithheld>>;
@@ -35,7 +35,13 @@ export type LedgerEventType = "reasoning" | "text" | "tool_start" | "tool_end" |
35
35
  /** S-212(7.71.0):运行期名册变更(core 7.8.0 [ref] ⑪)。**快照语义**——`delta.roster` 是整只
36
36
  * 变更后名册,消费端收到即整只替换,所以回放不需要任何合并;它的耐久面因此不是 `wiring_manifest.tools`
37
37
  * 的重复,而是那份开腿快照**之后**的每一次改动(修前两条 durable 腿零 case ⇒ 回放只见开头一版)。 */
38
- | "tool_roster_delta" | "human_input" | "task_progress" | "task_notification" | "turn_end"
38
+ | "tool_roster_delta"
39
+ /** [ref](core 7.18.0):本条腿的**延迟工具普查**快照(policy / thresholdPercent? / deferred[] / activated[])。
40
+ * 与 `tool_roster_delta` 同判、同理由——**快照语义**(整只替换,回放零合并),而开腿那一份住在
41
+ * `wiring_manifest` 之后的首帧、**之后每一次激活**只活在本帧上。整帧缺席 = 这条腿什么都没延迟。
42
+ * 🔴 `tool_progress`([ref])**刻意不在本表**:core 的 `tool_progress.ephemeral` 契约逐字「LIVE ONLY …
43
+ * core never persists this frame」,与 `task_progress` / `status` 同一处置(两条 durable 腿零 case)。 */
44
+ | "tool_disclosure" | "human_input" | "task_progress" | "task_notification" | "turn_end"
39
45
  /** park 行名**两个**(`appendParked(kind)` 逐字取自 `result.status`)—— 运行期闭集见
40
46
  * {@link PARK_LEDGER_EVENT_TYPES}(本联合是它的类型面,两者由下方的双向断言锁步)。 */
41
47
  | "suspended" | "needs_review" | "done" | "failed" | "suggestions" | "model_usage" | "prompt_assembled" | "config_assembled" | "file_link"
@@ -1,5 +1,5 @@
1
1
  import { terminalProjection } from "@sema-agent/core";
2
- import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
2
+ import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
3
3
  import { NotifiedKeys, taskNotificationStreamKey } from "../orchestration/workflow-completion-inbox.js";
4
4
  import { isParkLedgerEventType } from "./ledger-events.js";
5
5
  import { isModelSegmentBoundary } from "./model-segment.js";
@@ -97,6 +97,12 @@ export function createLedgerSink(opts) {
97
97
  await append("tool_roster_delta", rosterDelta);
98
98
  break;
99
99
  }
100
+ case "tool_disclosure": {
101
+ const disclosure = toolDisclosureEventData(ev);
102
+ if (disclosure !== undefined)
103
+ await append("tool_disclosure", disclosure);
104
+ break;
105
+ }
100
106
  case "human_input":
101
107
  await append("human_input", humanInputEventData(ev));
102
108
  break;
@@ -2,7 +2,7 @@ import { reasoningEndEventData } from "./project.js";
2
2
  const BOUNDARY = new Set([
3
3
  "tool_start", "tool_end", "status", "context_usage", "compacted", "diagnostics",
4
4
  "steering_injected", "compaction_outcome", "workspace_changed", "wiring_manifest",
5
- "tool_roster_delta", "human_input", "task_notification", "turn_end",
5
+ "tool_roster_delta", "human_input", "task_notification", "turn_end", "tool_disclosure",
6
6
  ]);
7
7
  export function isModelSegmentBoundary(type) {
8
8
  return BOUNDARY.has(type);
@@ -236,6 +236,63 @@ export declare function toolEndEventData(ev: {
236
236
  eventId?: string;
237
237
  parentToolCallId?: string;
238
238
  }): Record<string, unknown>;
239
+ /**
240
+ * `tool_progress` 的白名单投影(core 7.18.0 [ref])—— 一只**仍在跑**的工具调用的存活 tick。
241
+ *
242
+ * ## 这一帧只上 live,不落账(判据是 core 的契约,不是本仓的偏好)
243
+ * core `@contract tool_progress.ephemeral` 逐字:「LIVE ONLY, like `task_progress`/`status`: core never
244
+ * persists this frame (no session-tree entry, no checkpoint state, no resume replay)」。本仓对
245
+ * `task_progress` / `status` 正是同一处置(两条 durable 腿零 case),本帧照抄。
246
+ * 另一条 core 契约把「不落账」从偏好抬成正确性:`@contract tool_progress.cadence` 逐字「A call that
247
+ * finishes inside one interval emits NO frame at all」⇒ **帧的缺席从来不是证据**;落账会让回放面凭空
248
+ * 多出一份「这只调用跑了多久」的账,而那件事的真源是 `tool_end`(core 明写 `elapsedTimeSeconds`
249
+ * **不等于** `tool_end` 的 duration)。
250
+ *
251
+ * ## 信任划线
252
+ * · `toolCallId` / `toolName` —— 引擎铸的 id 与名册里的工具名(与 `tool_start` 逐字同源)⇒ verbatim。
253
+ * · `elapsedTimeSeconds` —— 引擎铸的整秒,单调、一 call 一个原点 ⇒ 有限数才铸键。
254
+ * · `output` —— 🔴 **UNTRUSTED RAW**,core 逐字「exactly as `tool_end.output`: process output that may
255
+ * carry secrets, paths or PII; core bounds only its SIZE, and a consumer MUST redact before persisting
256
+ * or displaying it」⇒ 与 `tool_end.output` **同一只** `redactDeep`,一个字节的例外都不开。
257
+ * · `totalLines` / `totalBytes` —— 引擎计数;core 的缺席律逐字:三者**同进同退**(在场=「观察到了输出」,
258
+ * 缺席=「一个字节都没观察到」,**从不是**「与上一帧相比没变」)。所以本投影按 `output` 的在场判三者:
259
+ * `output` 读不出 ⇒ 三键一起缺席,不单独留下两个计数(那会把「没输出」谎报成「有输出但读不出」)。
260
+ */
261
+ export declare function toolProgressEventData(ev: {
262
+ toolCallId?: unknown;
263
+ toolName?: unknown;
264
+ elapsedTimeSeconds?: unknown;
265
+ output?: unknown;
266
+ totalLines?: unknown;
267
+ totalBytes?: unknown;
268
+ eventId?: unknown;
269
+ parentToolCallId?: unknown;
270
+ }): Record<string, unknown> | undefined;
271
+ /**
272
+ * `tool_disclosure` 的白名单投影(core 7.18.0 [ref])—— 本条腿的**延迟工具普查**:哪些挂载的工具今天只是
273
+ * 名字占位、模型已经激活了哪些、以及是哪条策略判的。
274
+ *
275
+ * ## 三腿对称(与 `tool_roster_delta` 同判、同理由)
276
+ * 开腿那一刻的快照住在 `wiring_manifest` 之后的**第一帧**,而**之后每一次激活**只活在本帧上。不落账 ⇒
277
+ * 断线重连 / 后台跑完再回来的耐久面永远停在开腿版本,而壳正是据它判「这只工具现在还是不是占位名」。
278
+ * 本帧是**整只快照**语义(`deferred` / `activated` 全量,消费端整只替换),所以回放不需要合并。
279
+ *
280
+ * ## 缺席语义(core 逐字,别读错)
281
+ * · **整帧缺席** = 「这条腿什么都没延迟」,**不是**「这个 build 没有这个面」、也不是「未知」;
282
+ * · `thresholdPercent` 缺席 = `always` / `never` 两档下**根本没查过阈值**,不许读成「取了默认百分比」;
283
+ * · 一条 resume 腿的**首帧** `activated` 可能非空(引擎重播的),**不许**读成「这条腿激活了它们」。
284
+ *
285
+ * 成员全是闭形值(策略闭词 + 数字 + 名册里的工具名)⇒ 零脱敏,与 `tool_roster_delta` 的名册行同待遇。
286
+ * `policy` **不在本仓枚举**(闭集词表属主在 core;抄一份会把 core 加的新词吞成缺席)。
287
+ */
288
+ export declare function toolDisclosureEventData(ev: {
289
+ policy?: unknown;
290
+ thresholdPercent?: unknown;
291
+ deferred?: unknown;
292
+ activated?: unknown;
293
+ eventId?: unknown;
294
+ parentToolCallId?: unknown;
295
+ }): Record<string, unknown> | undefined;
239
296
  /** The `task_progress` payload (a subagent's live progress tick) — EXPLICIT WHITELIST shared by all
240
297
  * three legs (bg runs.ts + resume append + the sync live SSE frame): taskId/parent linkage/token rollup verbatim,
241
298
  * `name` UNTRUSTED (core sanitizes for rendering, does NOT secret-redact — a taskName can be secret-shaped) →
@@ -679,6 +736,7 @@ export declare function contextUsageEventData(ev: {
679
736
  usedTokens?: unknown;
680
737
  windowTokens?: unknown;
681
738
  compactAtTokens?: unknown;
739
+ sections?: unknown;
682
740
  eventId?: unknown;
683
741
  parentToolCallId?: unknown;
684
742
  }): Record<string, unknown>;
@@ -110,8 +110,12 @@ function screenedGateOrThrow(v) {
110
110
  recordFailOpen("server.trace.gate-outcome-defective", "disposition is not one of the two arms");
111
111
  return undefined;
112
112
  }
113
+ const classifierFallbackRaw = d.classifier !== null && typeof d.classifier === "object" ? d.classifier.fallback : undefined;
113
114
  const shapeDefect = (d.kind === "denied" && typeof d.deniedBy !== "string" && "disposition.deniedBy is not a string") ||
114
115
  (d.kind === "denied" && d.cause !== undefined && typeof d.cause !== "string" && "disposition.cause is not a string") ||
116
+ (d.classifier !== undefined && (d.classifier === null || typeof d.classifier !== "object") && "disposition.classifier is not an object") ||
117
+ (classifierFallbackRaw !== undefined && (classifierFallbackRaw === null || typeof classifierFallbackRaw !== "object") && "disposition.classifier.fallback is not an object") ||
118
+ (classifierFallbackRaw !== undefined && classifierFallbackRaw !== null && typeof classifierFallbackRaw === "object" && typeof classifierFallbackRaw.cause !== "string" && "disposition.classifier.fallback.cause is not a string") ||
115
119
  (g.settlement !== undefined && (g.settlement === null || typeof g.settlement !== "object") && "settlement is not an object") ||
116
120
  (g.settlement !== undefined && g.settlement !== null && typeof g.settlement.kind !== "string" && "settlement.kind is not a string") ||
117
121
  (g.origin !== undefined && typeof g.origin !== "string" && "origin is not a string") ||
@@ -125,6 +129,16 @@ function screenedGateOrThrow(v) {
125
129
  recordFailOpen("server.trace.gate-outcome-defective", defects.join("; "));
126
130
  return undefined;
127
131
  }
132
+ const attribution = d.classifier;
133
+ const classifier = attribution === undefined
134
+ ? undefined
135
+ : {
136
+ modelRequested: attribution.modelRequested,
137
+ modelUsed: attribution.modelUsed,
138
+ ...(attribution.fallback === undefined
139
+ ? {}
140
+ : { fallback: { from: attribution.fallback.from, to: attribution.fallback.to, cause: attribution.fallback.cause } }),
141
+ };
128
142
  const settlement = g.settlement === undefined ? undefined : projectSettlement(g.settlement);
129
143
  if (g.settlement !== undefined && settlement === undefined) {
130
144
  recordFailOpen("server.trace.gate-outcome-defective", "settlement.who is not an object with a party in the closed set");
@@ -132,8 +146,8 @@ function screenedGateOrThrow(v) {
132
146
  }
133
147
  return {
134
148
  disposition: d.kind === "denied"
135
- ? { kind: "denied", deniedBy: d.deniedBy, ...(d.cause === undefined ? {} : { cause: d.cause }) }
136
- : { kind: "allowed" },
149
+ ? { kind: "denied", deniedBy: d.deniedBy, ...(d.cause === undefined ? {} : { cause: d.cause }), ...(classifier === undefined ? {} : { classifier }) }
150
+ : { kind: "allowed", ...(classifier === undefined ? {} : { classifier }) },
137
151
  ...(settlement === undefined ? {} : { settlement }),
138
152
  ...(g.origin === undefined ? {} : { origin: g.origin }),
139
153
  };
@@ -210,6 +224,43 @@ export function toolEndEventData(ev) {
210
224
  ...identityFields(ev),
211
225
  };
212
226
  }
227
+ const dropProjectedFace = (what) => {
228
+ recordFailOpen("server.trace.projection-input-malformed", what);
229
+ return undefined;
230
+ };
231
+ export function toolProgressEventData(ev) {
232
+ const id = typeof ev.toolCallId === "string" && ev.toolCallId !== "" ? ev.toolCallId : undefined;
233
+ const name = typeof ev.toolName === "string" && ev.toolName !== "" ? ev.toolName : undefined;
234
+ if (id === undefined || name === undefined)
235
+ return dropProjectedFace("tool_progress is missing toolCallId/toolName");
236
+ const n = (v) => (typeof v === "number" && Number.isFinite(v) ? v : undefined);
237
+ const hasOutput = typeof ev.output === "string";
238
+ return {
239
+ toolCallId: id,
240
+ toolName: name,
241
+ ...(n(ev.elapsedTimeSeconds) !== undefined ? { elapsedTimeSeconds: n(ev.elapsedTimeSeconds) } : {}),
242
+ ...(hasOutput ? { output: redactDeep(ev.output) } : {}),
243
+ ...(hasOutput && n(ev.totalLines) !== undefined ? { totalLines: n(ev.totalLines) } : {}),
244
+ ...(hasOutput && n(ev.totalBytes) !== undefined ? { totalBytes: n(ev.totalBytes) } : {}),
245
+ ...identityFields(ev),
246
+ };
247
+ }
248
+ export function toolDisclosureEventData(ev) {
249
+ const policy = typeof ev.policy === "string" && ev.policy !== "" ? ev.policy : undefined;
250
+ const names = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === "string" && x !== "") : undefined);
251
+ const deferred = names(ev.deferred);
252
+ const activated = names(ev.activated);
253
+ if (policy === undefined || deferred === undefined || activated === undefined)
254
+ return dropProjectedFace("tool_disclosure is missing policy/deferred/activated");
255
+ const threshold = typeof ev.thresholdPercent === "number" && Number.isFinite(ev.thresholdPercent) ? ev.thresholdPercent : undefined;
256
+ return {
257
+ policy,
258
+ ...(threshold !== undefined ? { thresholdPercent: threshold } : {}),
259
+ deferred,
260
+ activated,
261
+ ...identityFields(ev),
262
+ };
263
+ }
213
264
  export function taskProgressEventData(ev) {
214
265
  return {
215
266
  taskId: ev.taskId,
@@ -390,6 +441,41 @@ export function wiringManifestEventData(ev) {
390
441
  })();
391
442
  const toolsIn = m.tools;
392
443
  const tools = toolsIn === undefined ? undefined : Value.Check(ToolRoster, toolsIn) ? toolsIn : (recordFailOpen("server.trace.tool-roster-malformed", "wiring_manifest.tools did not fit core ToolRoster"), undefined);
444
+ const hooksIn = m.hooks;
445
+ const hooks = (() => {
446
+ if (hooksIn === undefined)
447
+ return undefined;
448
+ if (!Array.isArray(hooksIn))
449
+ return dropProjectedFace("wiring_manifest.hooks is not an array");
450
+ const rows = [];
451
+ for (const raw of hooksIn) {
452
+ const e = sec(raw);
453
+ if (e === undefined) {
454
+ dropProjectedFace("wiring_manifest.hooks[] row is not an object");
455
+ continue;
456
+ }
457
+ const family = str(e.family);
458
+ const owner = str(e.owner);
459
+ if (family === undefined || owner === undefined) {
460
+ dropProjectedFace("wiring_manifest.hooks[] row is missing family/owner");
461
+ continue;
462
+ }
463
+ rows.push({ family, owner, ...(e.observational === true ? { observational: true } : {}) });
464
+ }
465
+ return rows.length > 0 ? rows : undefined;
466
+ })();
467
+ const lspIn = sec(m.lsp);
468
+ const lsp = (() => {
469
+ if (m.lsp === undefined)
470
+ return undefined;
471
+ if (lspIn === undefined)
472
+ return dropProjectedFace("wiring_manifest.lsp is not an object");
473
+ const mounted = bool(lspIn.mounted);
474
+ if (mounted === undefined)
475
+ return dropProjectedFace("wiring_manifest.lsp.mounted is not a boolean");
476
+ const lane = str(lspIn.lane);
477
+ return { mounted, ...(lane === undefined ? {} : { lane }) };
478
+ })();
393
479
  const mcpIn = m.mcp;
394
480
  const mcp = (() => {
395
481
  if (!Array.isArray(mcpIn))
@@ -430,6 +516,8 @@ export function wiringManifestEventData(ev) {
430
516
  ...(autoMode !== undefined ? { autoMode } : {}),
431
517
  ...(tools !== undefined ? { tools } : {}),
432
518
  ...(mcp !== undefined ? { mcp } : {}),
519
+ ...(hooks !== undefined ? { hooks } : {}),
520
+ ...(lsp !== undefined ? { lsp } : {}),
433
521
  ...identityFields(ev),
434
522
  };
435
523
  }
@@ -535,9 +623,32 @@ export function contextUsageEventData(ev) {
535
623
  ...(n(ev.usedTokens) !== undefined ? { usedTokens: n(ev.usedTokens) } : {}),
536
624
  ...(n(ev.windowTokens) !== undefined ? { windowTokens: n(ev.windowTokens) } : {}),
537
625
  ...(n(ev.compactAtTokens) !== undefined ? { compactAtTokens: n(ev.compactAtTokens) } : {}),
626
+ ...contextUsageSectionsField(ev.sections),
538
627
  ...identityFields(ev),
539
628
  };
540
629
  }
630
+ function contextUsageSectionsField(sections) {
631
+ if (sections === undefined)
632
+ return {};
633
+ if (!Array.isArray(sections)) {
634
+ dropProjectedFace("context_usage.sections is not an array");
635
+ return {};
636
+ }
637
+ const out = [];
638
+ for (const raw of sections) {
639
+ if (typeof raw !== "object" || raw === null) {
640
+ dropProjectedFace("context_usage.sections[] element is not an object");
641
+ continue;
642
+ }
643
+ const { id, tokens, kind } = raw;
644
+ if (typeof id !== "string" || id === "" || typeof tokens !== "number" || !Number.isFinite(tokens)) {
645
+ dropProjectedFace("context_usage.sections[] element is missing id/tokens");
646
+ continue;
647
+ }
648
+ out.push({ id, tokens, ...(typeof kind === "string" && kind !== "" ? { kind } : {}) });
649
+ }
650
+ return out.length > 0 ? { sections: out } : {};
651
+ }
541
652
  export function turnEndEventData(ev) {
542
653
  return {
543
654
  ...(ev.usage ? { usage: ev.usage } : {}),
@@ -711,6 +822,7 @@ function mapTraceEventInner(type, seq, data) {
711
822
  case "wiring_manifest":
712
823
  case "human_input":
713
824
  case "tool_roster_delta":
825
+ case "tool_disclosure":
714
826
  return null;
715
827
  default:
716
828
  return null;
@@ -233,12 +233,150 @@ let anyValueArm;
233
233
  function anyValueArmRe() {
234
234
  return (anyValueArm ??= new RegExp(`(?:${VALUE_ARM_BODIES.join("|")})`, "y"));
235
235
  }
236
- function collectRegexSpans(g, repl, order, text, out) {
236
+ const HEAD_CLASS_ESCAPES = new Set(["d", "D", "w", "W", "s", "S"]);
237
+ const HEAD_LITERAL_ESCAPES = new Set(["\\", "/", "-", ".", "+", "*", "?", "(", ")", "[", "]", "{", "}", "|", "^", "$"]);
238
+ function headOfAtoms(atoms, src) {
239
+ const tail = atoms[atoms.length - 1];
240
+ if (tail === undefined || !tail.unbounded || tail.cls === undefined) {
241
+ throw new Error(`redact: arm-pool pattern /${src}/ must end in a greedy unbounded single-class value run`);
242
+ }
243
+ let pos = 0;
244
+ let head = 0;
245
+ for (let k = 0; k < atoms.length - 1; k++) {
246
+ const a = atoms[k];
247
+ if (a.cls !== tail.cls)
248
+ head = pos + a.len;
249
+ pos += a.len;
250
+ }
251
+ return head;
252
+ }
253
+ const HEAD_GROUP_OPEN = /^\((\?:|\?=|\?!|\?<=|\?<!)/;
254
+ function parseHeadAlternative(src, from) {
255
+ let seqs = [[]];
256
+ let i = from;
257
+ let closed = false;
258
+ while (i < src.length) {
259
+ const c = src[i];
260
+ if (c === "|" || c === ")")
261
+ break;
262
+ if (closed)
263
+ throw new Error(`redact: arm-pool pattern /${src}/ has trailing syntax after its value run`);
264
+ let base;
265
+ if (c === "(") {
266
+ const open = HEAD_GROUP_OPEN.exec(src.slice(i));
267
+ if (open === null)
268
+ throw new Error(`redact: head derivation cannot bound group at ${i} in /${src}/`);
269
+ const mark = open[1];
270
+ const inner = parseHeadAlternatives(src, i + 1 + mark.length);
271
+ if (src[inner.next] !== ")")
272
+ throw new Error(`redact: unbalanced group at ${i} in /${src}/`);
273
+ i = inner.next + 1;
274
+ if (mark !== "?:")
275
+ continue;
276
+ if (inner.alts.some((alt) => alt.some((a) => a.unbounded))) {
277
+ seqs = inner.alts.flatMap((alt) => seqs.map((s) => [...s, ...alt]));
278
+ closed = true;
279
+ continue;
280
+ }
281
+ base = { len: Math.max(...inner.alts.map((alt) => alt.reduce((n, a) => n + a.len, 0))), cls: undefined };
282
+ }
283
+ else if (c === "[") {
284
+ let k = i + 1;
285
+ while (k < src.length && src[k] !== "]")
286
+ k += src[k] === "\\" ? 2 : 1;
287
+ if (k >= src.length)
288
+ throw new Error(`redact: unterminated character class at ${i} in /${src}/`);
289
+ base = { len: 1, cls: src.slice(i, k + 1) };
290
+ i = k + 1;
291
+ }
292
+ else if (c === "\\") {
293
+ const e = src[i + 1];
294
+ if (e === undefined)
295
+ throw new Error(`redact: dangling escape at ${i} in /${src}/`);
296
+ if (HEAD_CLASS_ESCAPES.has(e))
297
+ base = { len: 1, cls: `\\${e}` };
298
+ else if (HEAD_LITERAL_ESCAPES.has(e))
299
+ base = { len: 1, cls: undefined };
300
+ else
301
+ throw new Error(`redact: head derivation cannot bound escape \\${e} at ${i} in /${src}/`);
302
+ i += 2;
303
+ }
304
+ else if (c === "." || c === "^" || c === "$" || c === "*" || c === "+" || c === "?" || c === "{") {
305
+ throw new Error(`redact: head derivation cannot bound ${JSON.stringify(c)} at ${i} in /${src}/`);
306
+ }
307
+ else {
308
+ base = { len: 1, cls: undefined };
309
+ i += 1;
310
+ }
311
+ let atom = { ...base, unbounded: false };
312
+ const q = src[i];
313
+ if (q === "*" || q === "+") {
314
+ atom = { ...base, unbounded: true };
315
+ i += 1;
316
+ }
317
+ else if (q === "?") {
318
+ i += 1;
319
+ }
320
+ else if (q === "{") {
321
+ const m = /^\{(\d+)(,(\d*))?\}/.exec(src.slice(i));
322
+ if (m === null)
323
+ throw new Error(`redact: head derivation cannot bound quantifier at ${i} in /${src}/`);
324
+ const lo = Number(m[1]);
325
+ const hi = m[2] === undefined ? lo : m[3] === "" ? -1 : Number(m[3]);
326
+ atom = hi < 0 ? { ...base, unbounded: true } : { len: base.len * hi, cls: base.cls, unbounded: false };
327
+ i += m[0].length;
328
+ }
329
+ if (src[i] === "?")
330
+ throw new Error(`redact: head derivation cannot bound a lazy quantifier at ${i} in /${src}/`);
331
+ if (atom.unbounded)
332
+ closed = true;
333
+ for (const s of seqs)
334
+ s.push(atom);
335
+ }
336
+ return { seqs, next: i };
337
+ }
338
+ function parseHeadAlternatives(src, from) {
339
+ const alts = [];
340
+ let i = from;
341
+ for (;;) {
342
+ const one = parseHeadAlternative(src, i);
343
+ alts.push(...one.seqs);
344
+ i = one.next;
345
+ if (src[i] !== "|")
346
+ break;
347
+ i += 1;
348
+ }
349
+ return { alts, next: i };
350
+ }
351
+ function literalHeadLength(re) {
352
+ if (/[uv]/.test(re.flags))
353
+ throw new Error(`redact: head derivation does not support the u/v flags on /${re.source}/`);
354
+ const src = re.source;
355
+ const parsed = parseHeadAlternatives(src, 0);
356
+ if (parsed.next !== src.length)
357
+ throw new Error(`redact: head derivation stopped at ${parsed.next} in /${src}/`);
358
+ return Math.max(...parsed.alts.map((alt) => headOfAtoms(alt, src)));
359
+ }
360
+ function scanResumeAfterHit(start, end, head) {
361
+ return Math.max(start + 1, end - Math.max(head - 1, 0));
362
+ }
363
+ function headWindowCandidates(at, maxHead) {
364
+ const out = [];
365
+ let last = -1;
366
+ for (const e of at) {
367
+ for (let p = Math.max(e - Math.max(maxHead - 1, 0), last + 1, 0); p <= e; p++) {
368
+ out.push({ p, back: e - p });
369
+ last = p;
370
+ }
371
+ }
372
+ return out;
373
+ }
374
+ function collectRegexSpans(g, head, repl, order, text, out) {
237
375
  g.lastIndex = 0;
238
376
  for (let m = g.exec(text); m !== null; m = g.exec(text)) {
239
377
  const end = m.index + m[0].length;
240
378
  out.push({ start: m.index, end, repl, order });
241
- g.lastIndex = end > m.index ? end : m.index + 1;
379
+ g.lastIndex = scanResumeAfterHit(m.index, end, head);
242
380
  }
243
381
  }
244
382
  function resolveSpanOverlaps(spans) {
@@ -265,10 +403,10 @@ function applySpans(text, spans) {
265
403
  parts.push(text.slice(copied));
266
404
  return { out: parts.join(""), count: spans.length };
267
405
  }
268
- function collectValueArmSpans(g, y, repl, order, text, out) {
406
+ function collectValueArmSpans(g, y, head, repl, order, text, out) {
269
407
  const tails = escapedSeparatorTails(text);
270
408
  if (tails === undefined) {
271
- collectRegexSpans(g, repl, order, text, out);
409
+ collectRegexSpans(g, head, repl, order, text, out);
272
410
  return;
273
411
  }
274
412
  let pos = 0;
@@ -314,12 +452,14 @@ function collectValueArmSpans(g, y, repl, order, text, out) {
314
452
  else
315
453
  break;
316
454
  out.push({ start: idx, end, repl, order });
317
- pos = end;
455
+ pos = scanResumeAfterHit(idx, end, head);
318
456
  }
319
457
  }
320
- function collectStickySpans(y, repl, order, text, at, out) {
458
+ function collectStickySpans(y, head, repl, order, text, at, out) {
321
459
  let pos = 0;
322
- for (const p of at) {
460
+ for (const { p, back } of at) {
461
+ if (back >= head)
462
+ continue;
323
463
  if (p < pos)
324
464
  continue;
325
465
  y.lastIndex = p;
@@ -328,7 +468,7 @@ function collectStickySpans(y, repl, order, text, at, out) {
328
468
  continue;
329
469
  const end = p + m[0].length;
330
470
  out.push({ start: p, end, repl, order });
331
- pos = end;
471
+ pos = scanResumeAfterHit(p, end, head);
332
472
  }
333
473
  }
334
474
  const WORD_SEGMENT_CLASS = "[A-Za-z0-9._~+/-]";
@@ -1000,17 +1140,22 @@ const armPoolRule = (() => {
1000
1140
  const arms = [...SHAPE_SECRET_PATTERNS, ...FRAGMENT_SECRET_PATTERNS].map(([re, repl], order) => {
1001
1141
  if (/\$./.test(repl))
1002
1142
  throw new Error(`redact: arm-pool replacement ${JSON.stringify(repl)} must not use $N refs`);
1003
- return { g: re, y: VALUE_ARM_STICKY.get(re), repl, order };
1143
+ const y = VALUE_ARM_STICKY.get(re);
1144
+ const head = literalHeadLength(re);
1145
+ if (y !== undefined && head === 0)
1146
+ throw new Error(`redact: boundary arm /${re.source}/ has a zero-length structural head — the head-window screen would skip every candidate position`);
1147
+ return { g: re, y, head, repl, order };
1004
1148
  });
1005
- const boundaryArms = arms.flatMap((a) => (a.y === undefined ? [] : [{ y: a.y, repl: a.repl, order: a.order }]));
1149
+ const boundaryArms = arms.flatMap((a) => (a.y === undefined ? [] : [{ y: a.y, head: a.head, repl: a.repl, order: a.order }]));
1150
+ const maxBoundaryHead = Math.max(1, ...boundaryArms.map((a) => a.head));
1006
1151
  return {
1007
1152
  run: (text, wantCount) => {
1008
1153
  const spans = [];
1009
1154
  for (const a of arms) {
1010
1155
  if (a.y === undefined)
1011
- collectRegexSpans(a.g, a.repl, a.order, text, spans);
1156
+ collectRegexSpans(a.g, a.head, a.repl, a.order, text, spans);
1012
1157
  else
1013
- collectValueArmSpans(a.g, a.y, a.repl, a.order, text, spans);
1158
+ collectValueArmSpans(a.g, a.y, a.head, a.repl, a.order, text, spans);
1014
1159
  }
1015
1160
  const tried = new Set();
1016
1161
  const freshEnds = (from) => {
@@ -1026,8 +1171,9 @@ const armPoolRule = (() => {
1026
1171
  };
1027
1172
  for (let at = freshEnds(0); at.length > 0;) {
1028
1173
  const before = spans.length;
1174
+ const cand = headWindowCandidates(at, maxBoundaryHead);
1029
1175
  for (const a of boundaryArms)
1030
- collectStickySpans(a.y, a.repl, a.order, text, at, spans);
1176
+ collectStickySpans(a.y, a.head, a.repl, a.order, text, cand, spans);
1031
1177
  at = freshEnds(before);
1032
1178
  }
1033
1179
  const kept = resolveSpanOverlaps(spans);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.77.0",
3
+ "version": "7.78.0",
4
4
  "description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -54,8 +54,8 @@
54
54
  "build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
55
55
  },
56
56
  "dependencies": {
57
- "@sema-agent/core": "7.17.7",
58
- "@sema-agent/settings-schema": "1.10.0",
57
+ "@sema-agent/core": "7.18.0",
58
+ "@sema-agent/settings-schema": "2.1.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",
61
61
  "mysql2": "^3.22.4",
@@ -69,7 +69,7 @@
69
69
  "sharp": "^0.35.3"
70
70
  },
71
71
  "devDependencies": {
72
- "@sema-agent/sdk": "9.3.0",
72
+ "@sema-agent/sdk": "9.4.0",
73
73
  "@types/libsodium-wrappers": "^0.7.14",
74
74
  "@types/node": "22.10.2",
75
75
  "@types/pg": "^8.20.0",