@sema-agent/server 7.77.0 → 7.78.1
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/dist/approval-card.d.ts +24 -1
- package/dist/approval-card.js +13 -0
- package/dist/boot/resolve-spec.js +3 -0
- package/dist/boot/runner-deps.js +3 -1
- package/dist/config-catalog.js +1 -0
- package/dist/config-center/read-warnings.d.ts +10 -1
- package/dist/config-center/read-warnings.js +5 -1
- package/dist/config-provider.js +2 -2
- package/dist/config-types.d.ts +13 -1
- package/dist/config.js +2 -1
- package/dist/http/routes/approvals-assistant.d.ts +22 -0
- package/dist/http/routes/approvals-assistant.js +4 -1
- package/dist/http/routes/tasks.js +14 -9
- package/dist/http/routes/workflows.js +7 -1
- package/dist/http/server.d.ts +28 -4
- package/dist/http/server.js +47 -13
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/observability/run-terminal-log.js +1 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +7 -3
- package/dist/parked-decide.d.ts +27 -12
- package/dist/parked-decide.js +1 -1
- package/dist/plugins/workflow-run-store-sql.d.ts +7 -3
- package/dist/request-key-closure.d.ts +30 -0
- package/dist/request-key-closure.js +56 -0
- package/dist/runs.js +3 -1
- package/dist/runtime-governance.d.ts +18 -4
- package/dist/runtime-governance.js +2 -2
- package/dist/spec-fields.d.ts +3 -1
- package/dist/spec-fields.js +4 -5
- package/dist/task-settings.d.ts +18 -16
- package/dist/task-settings.js +17 -29
- package/dist/tool-approval.d.ts +35 -3
- package/dist/tool-approval.js +41 -19
- package/dist/trace/core-keyset-guard.d.ts +8 -8
- package/dist/trace/ledger-events.d.ts +7 -1
- package/dist/trace/ledger-sink.js +19 -6
- package/dist/trace/model-segment.d.ts +32 -2
- package/dist/trace/model-segment.js +16 -6
- package/dist/trace/project.d.ts +95 -3
- package/dist/trace/project.js +128 -12
- package/dist/trace/redact.js +159 -13
- package/package.json +4 -4
package/dist/approval-card.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* 没有方法、没有捕获的行为。
|
|
22
22
|
*/
|
|
23
23
|
import { z } from "zod";
|
|
24
|
-
import { type AskEvidenceAbsence, type RuleOffer } from "@sema-agent/core";
|
|
24
|
+
import { type AskEvidenceAbsence, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
|
|
25
25
|
import type { AskRow } from "./plugins/approval-ask-store-sql.js";
|
|
26
26
|
/** 模型自由文本(`sourceAgentName` / `delegation.agentName`)的限长(设计稿 §6.2)。设计 §3.1 把
|
|
27
27
|
* 「限长 + 脱敏」写成 **server 新增责任**(引擎无此层):spawning model 挑的名字是自由文本,
|
|
@@ -269,6 +269,29 @@ export type ProbeCauseProjection = z.infer<typeof ProbeCauseSchema>;
|
|
|
269
269
|
* (截出来的是另一个机器码);`shown` 超限 ⇒ **截条目**,`total` 逐字保真。
|
|
270
270
|
*/
|
|
271
271
|
export declare function readProbeCause(req: unknown): ProbeCauseProjection | undefined;
|
|
272
|
+
/**
|
|
273
|
+
* [ref](core 7.19.0)—— `readRootCandidate.dir` 的**上限**。core 自己的同族界是
|
|
274
|
+
* `READ_ROOT_CANDIDATE_DIR_MAX = 1024`(`src/core/read-root-candidate.ts`),**未从包根导出**,故钉本地
|
|
275
|
+
* 孪生 + 本出处注记(与 `MAX_SKILL_CONTENT_CHARS` 同款处置)。
|
|
276
|
+
*
|
|
277
|
+
* 🔴 超限的方向是**整只丢,不截** —— core 契约 `shell.read_boundary.grant_candidate_is_the_grant`:卡上
|
|
278
|
+
* 显示的串就是人要加进读目录的那个串,截过的目录是**另一个**目录,加进去也清不掉这只 ask。与
|
|
279
|
+
* {@link MAX_PROBE_CAUSE_CODE} 的 `code` 超限整只丢逐字同判据。
|
|
280
|
+
*/
|
|
281
|
+
export declare const MAX_READ_ROOT_CANDIDATE_DIR = 1024;
|
|
282
|
+
/** 窄读的输出形 —— **就是 core 的那只型**,不在本仓再声明一份同形结构(重声明 = 上游改形那天本仓静默不红)。 */
|
|
283
|
+
export type ReadRootCandidateProjection = ReadRootGrantCandidate;
|
|
284
|
+
/**
|
|
285
|
+
* [ref] —— `AskRequest.readRootCandidate` 的边界窄读:**把这个目录加进本会话的读目录,这只 ask 就不会
|
|
286
|
+
* 再出现**。活卡帧的**唯一**铸造点。
|
|
287
|
+
*
|
|
288
|
+
* 🔴 **零判读**:server 既不从命令文本重推目录、也不校验它是不是真的能清掉这只 ask —— 那是 core 已经
|
|
289
|
+
* 做过的判断(它把提议的根放回读边界又走了一遍),在下游再判一次就是同一个事实的第二个判官。
|
|
290
|
+
* 🔴 形不合 ⇒ **按缺席处置**;超限 ⇒ **整只丢**(见 {@link MAX_READ_ROOT_CANDIDATE_DIR})。
|
|
291
|
+
* 🔴 **缺席不是断言**:它同时覆盖「这一族 ask 没有任何目录能清掉」(敏感路径 deny 行、读边界压根读不懂
|
|
292
|
+
* 的命令、递归遍历、非读边界 ask……)与「老引擎」两形 —— 消费端只读在场,永远不读缺席。
|
|
293
|
+
*/
|
|
294
|
+
export declare function readReadRootCandidate(req: unknown): ReadRootCandidateProjection | undefined;
|
|
272
295
|
/**
|
|
273
296
|
* [ref] 件 G2 —— `AskRequest.ruleEvidence` 的形(帧 / `card_json` / 读面共用;窄读入口是
|
|
274
297
|
* {@link readRuleEvidence})。三对成员各 = 值**或**五词命名缺席(`AskEvidenceAbsence`),core 引擎
|
package/dist/approval-card.js
CHANGED
|
@@ -160,6 +160,19 @@ export function readProbeCause(req) {
|
|
|
160
160
|
...(raw.further !== undefined ? { further: family(raw.further) } : {}),
|
|
161
161
|
};
|
|
162
162
|
}
|
|
163
|
+
export const MAX_READ_ROOT_CANDIDATE_DIR = 1024;
|
|
164
|
+
const ReadRootCandidateEnvelopeSchema = z.object({
|
|
165
|
+
readRootCandidate: z.object({ dir: z.string().min(1), clearsThisAsk: z.literal(true) }).optional(),
|
|
166
|
+
});
|
|
167
|
+
export function readReadRootCandidate(req) {
|
|
168
|
+
const parsed = ReadRootCandidateEnvelopeSchema.safeParse(req);
|
|
169
|
+
const raw = parsed.success ? parsed.data.readRootCandidate : undefined;
|
|
170
|
+
if (raw === undefined)
|
|
171
|
+
return undefined;
|
|
172
|
+
if (raw.dir.length > MAX_READ_ROOT_CANDIDATE_DIR)
|
|
173
|
+
return undefined;
|
|
174
|
+
return { dir: raw.dir, clearsThisAsk: true };
|
|
175
|
+
}
|
|
163
176
|
const MAX_EVIDENCE_DOT_ACTOR = 256;
|
|
164
177
|
const MAX_EVIDENCE_DOTS = 16;
|
|
165
178
|
const AskEvidenceAbsenceSchema = z.custom((v) => typeof v === "string" && ASK_EVIDENCE_ABSENCE_VALUES.includes(v));
|
|
@@ -279,6 +279,7 @@ export function createResolveSpec(ctx) {
|
|
|
279
279
|
additionalReadDirectories,
|
|
280
280
|
enablePlanMode: config.planModeEnabled ? true : undefined,
|
|
281
281
|
...(effMode ? { shellGate: shellGateForMode(effMode) } : {}),
|
|
282
|
+
...(effMode === "bypassPermissions" ? { writeFace: "open" } : {}),
|
|
282
283
|
...(effMode === "auto" ? { autoModeRequested: true } : {}),
|
|
283
284
|
selfOrchestration: selfOrchestrationFromBody({ selfOrchestration: body.selfOrchestration === true || parsedSettings.settings?.ultracode === true }, config, Boolean(centerRuntimeCapsResolver)),
|
|
284
285
|
forwardSubagentEvents: body.forwardSubagentEvents === true ? true : undefined,
|
|
@@ -367,6 +368,8 @@ export function createResolveSpec(ctx) {
|
|
|
367
368
|
return merged && config.toolDeferLongtail ? applyLongtailDefer(merged, true) : merged;
|
|
368
369
|
})(cap.tools),
|
|
369
370
|
skills: mergeUserSkills(cap.skills, body.skills, logger),
|
|
371
|
+
...(parsedSettings.settings?.skillListingBudgetFraction !== undefined ? { skillListingBudgetFraction: parsedSettings.settings.skillListingBudgetFraction } : {}),
|
|
372
|
+
...(parsedSettings.settings?.skillListingMaxDescChars !== undefined ? { skillListingMaxDescChars: parsedSettings.settings.skillListingMaxDescChars } : {}),
|
|
370
373
|
mcp: resolveRequestMcp(mcpForScenario(config.mcpServers, scenarioName), body.mcpServers, config, logger, opts?.onNotice !== undefined ? { sessionId: auth?.sessionId, emit: opts.onNotice } : undefined),
|
|
371
374
|
a2a: resolveRequestA2a(a2aForScenario(config.a2aPeers, scenarioName), body.a2aPeers, config, logger),
|
|
372
375
|
promptProvider: centerDecls ? centerPromptProvider(centerDecls, scenarioName) : cap.promptProvider,
|
package/dist/boot/runner-deps.js
CHANGED
|
@@ -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,
|
package/dist/config-catalog.js
CHANGED
|
@@ -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(
|
|
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":
|
package/dist/config-provider.js
CHANGED
|
@@ -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,
|
|
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 =
|
|
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)
|
package/dist/config-types.d.ts
CHANGED
|
@@ -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",
|
|
@@ -6,6 +6,28 @@ import { type RouteCtx } from "../route-ctx.js";
|
|
|
6
6
|
export declare const ASSISTANT_PREEMPT_RE: RegExp;
|
|
7
7
|
export declare const ASSISTANT_RESUME_RE: RegExp;
|
|
8
8
|
export declare const ASSISTANT_PLAN_REVIEW_RE: RegExp;
|
|
9
|
+
/**
|
|
10
|
+
* S-342 —— 「空串的理由 = 没有理由 = 这是一次**裸拒**」。这条语义只适用于**本仓自铸**的那一个域。
|
|
11
|
+
*
|
|
12
|
+
* ## 三个域,三个属主(这才是归一:每个域的读法只写一次,而不是三处各挑一种写法)
|
|
13
|
+
* `body.reason` 在这条路由上会被读三次,而它们的属主**不是同一个**:
|
|
14
|
+
* · **签名域**(直连门的 HMAC 信封第五位)—— 属主是**跨仓字节契约**(§6.2 / `approvalHmacMessage`,
|
|
15
|
+
* 签名器在 cli / SDK)。那份契约明写空串与 `null` 是两个不同的被签值。⇒ **逐字线值,永不折叠**;
|
|
16
|
+
* 在这里折一刀,一份合法签发的空理由证明就永远 401(合并复审 R1 [high] 真复现过)。
|
|
17
|
+
* · **上送域**(`resumePlanReview` / `resumeCheckpoint` 的 `reason` 尾参)—— 属主是**引擎的单铸点**
|
|
18
|
+
* (7.19.0 / [ref] 把「人的结算」收进一个铸点,空文本在那里折成缺席,live 与 durable decide 两条路径同形)。
|
|
19
|
+
* ⇒ **原样透传**;跨仓宪法「源头修复禁下游旁路」:在下游再折一次就是同一语义的第二个写者,
|
|
20
|
+
* 今天两者答案相同,上游哪天改法就变成一条谁都查不出来的分歧。
|
|
21
|
+
* · **本仓自铸域**(幂等回放的**身份**)—— 属主就是本仓:它回答「这次重试与首决是不是同一个决议」。
|
|
22
|
+
* ⇒ **本函数**。此前那里是一个裸真值判(`body.reason ? …`),与上面两处混在一起看就是三种拼法;
|
|
23
|
+
* 收进一只具名读法之后,「什么算一个理由」在本仓只有这一个答案,而另外两个域各自点名了自己的属主。
|
|
24
|
+
*
|
|
25
|
+
* 值域前置门(非串 400 / 超长 413)在两条路由里各自先跑,本函数只做「空串 ⇒ 缺席」这一件事。
|
|
26
|
+
* 纯空白**仍是**一条理由(上游把这一形逐字钉成 current behaviour,本仓跟着它、不自作主张多折一层)。
|
|
27
|
+
*/
|
|
28
|
+
export declare function decisionReasonOf(body: {
|
|
29
|
+
reason?: unknown;
|
|
30
|
+
}): string | undefined;
|
|
9
31
|
/** Structural check for an operator-supplied QuestionAnswer (durable ask resume, TC-5.4). Strict on the
|
|
10
32
|
* load-bearing shape — `answers[]` non-empty, each `{header: string, selected: string[], note?: string}` —
|
|
11
33
|
* so a typo'd payload fails the request instead of resuming the task with an answer the tool can't use. */
|
|
@@ -20,6 +20,9 @@ import { sendResumeOutcome } from "../route-ctx.js";
|
|
|
20
20
|
export const ASSISTANT_PREEMPT_RE = /^\/v1\/assistant\/tasks\/([^/]+)\/preempt$/;
|
|
21
21
|
export const ASSISTANT_RESUME_RE = /^\/v1\/assistant\/tasks\/([^/]+)\/resume$/;
|
|
22
22
|
export const ASSISTANT_PLAN_REVIEW_RE = /^\/v1\/assistant\/tasks\/([^/]+)\/plan_review$/;
|
|
23
|
+
export function decisionReasonOf(body) {
|
|
24
|
+
return typeof body.reason === "string" && body.reason !== "" ? body.reason : undefined;
|
|
25
|
+
}
|
|
23
26
|
export function isQuestionAnswer(v) {
|
|
24
27
|
if (!v || typeof v !== "object" || Array.isArray(v))
|
|
25
28
|
return false;
|
|
@@ -564,7 +567,7 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
|
|
|
564
567
|
payload: {
|
|
565
568
|
...(decision === "approve" && binding.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}),
|
|
566
569
|
...(answer !== undefined ? { answer } : {}),
|
|
567
|
-
...(
|
|
570
|
+
...((r) => (r !== undefined ? { reason: r } : {}))(decisionReasonOf(body)),
|
|
568
571
|
},
|
|
569
572
|
deciderPrincipal,
|
|
570
573
|
explicitOperator,
|
|
@@ -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, liveIdentityFields } 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,17 +487,22 @@ 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
|
}
|
|
493
503
|
else if (ev.type === "text_delta" || ev.type === "text_end" || ev.type === "turn_end" || ev.type === "message_committed" || ev.type === "context_usage") {
|
|
494
504
|
const e = ev;
|
|
495
|
-
const ident =
|
|
496
|
-
...(e.eventId !== undefined ? { eventId: e.eventId } : {}),
|
|
497
|
-
...(e.parentToolCallId !== undefined ? { parentToolCallId: e.parentToolCallId } : {}),
|
|
498
|
-
...(e.sourceTaskId !== undefined ? { sourceTaskId: e.sourceTaskId } : {}),
|
|
499
|
-
...(e.bgAgentId !== undefined ? { bgAgentId: e.bgAgentId } : {}),
|
|
500
|
-
};
|
|
505
|
+
const ident = liveIdentityFields(e);
|
|
501
506
|
const textEnd = ev.type === "text_end" ? textEndEventData(e) : undefined;
|
|
502
507
|
const arm = ev.type === "text_delta"
|
|
503
508
|
? { type: "text_delta", delta: e.delta, ...ident }
|
|
@@ -513,7 +518,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
513
518
|
sseData(res, arm);
|
|
514
519
|
}
|
|
515
520
|
else {
|
|
516
|
-
sseData(res, ev);
|
|
521
|
+
sseData(res, { ...ev });
|
|
517
522
|
}
|
|
518
523
|
}
|
|
519
524
|
});
|
|
@@ -606,7 +611,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
606
611
|
if (deps.toolApproval) {
|
|
607
612
|
const toolApproval = deps.toolApproval;
|
|
608
613
|
prepared.spec.onAsk = windowZero
|
|
609
|
-
? () => Promise.resolve(toolApproval.unattendedAskOutcome())
|
|
614
|
+
? (askReq) => Promise.resolve(toolApproval.unattendedAskOutcome({ taskId: askTaskId, owner: askOwner, toolName: askReq.toolName }))
|
|
610
615
|
: toolApproval.boundAsk({
|
|
611
616
|
owner: askOwner,
|
|
612
617
|
taskId: askTaskId,
|
|
@@ -246,7 +246,12 @@ function projectLegApprovalKeys(agent) {
|
|
|
246
246
|
};
|
|
247
247
|
}
|
|
248
248
|
function projectParkRow(p) {
|
|
249
|
-
return {
|
|
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 } : {}),
|
package/dist/http/server.d.ts
CHANGED
|
@@ -730,18 +730,42 @@ export type FlatServiceDeps = ServiceCoreDeps & Partial<ServiceStoreDeps & Servi
|
|
|
730
730
|
export type ServiceDeps = FlatServiceDeps & ServiceDepGroups;
|
|
731
731
|
/** 分组入参 → 平铺视图(createHttpServer 的第一件事)。一组都没给 ⇒ 原样返回(存量调用零开销、零形变)。 */
|
|
732
732
|
export declare function flattenServiceDeps(deps: ServiceDeps): FlatServiceDeps;
|
|
733
|
-
/** Personalization caps: server-pinned hard limits — a UI may be stricter, never looser. */
|
|
734
|
-
export declare const MAX_USER_SKILLS = 10;
|
|
735
733
|
/** = core `SKILL_CONTENT_MAX_CHARS`(1MB 加载门,core 1.293 起 invoke 全文零截断、超 1MB 整拒;常量在
|
|
736
734
|
* dist/core/runner/synthetic-tools.js:84,未从 core 包根导出,故钉本地孪生+本出处注记)。修7 曾对齐旧
|
|
737
735
|
* 20k 截断门([ref] 飞轮考据:20k 是压缩保留区口径,invoke 时刻用错),core 1.293 撤门后本门跟随回收
|
|
738
|
-
* ([ref]① 收口)——HTTP 面与引擎加载门同值,超限 400 fail-loud 而非引擎侧整拒后难诊断。
|
|
736
|
+
* ([ref]① 收口)——HTTP 面与引擎加载门同值,超限 400 fail-loud 而非引擎侧整拒后难诊断。
|
|
737
|
+
*
|
|
738
|
+
* 🔴 **这是技能面**唯一**剩下的尺寸门,而且它不是超集帽**:引擎在装配期对超 1 MiB 的技能本就整拒
|
|
739
|
+
* (config 阶段错误),门上先答只是把同一个判决搬到调用方看得懂的地方。[ref] 退役的三条帽
|
|
740
|
+
* (条数 / description / name 长度)都没有这个性质——它们是本仓自造的、CC 与引擎都没有的门。 */
|
|
739
741
|
export declare const MAX_SKILL_CONTENT_CHARS = 1048576;
|
|
740
742
|
export declare const MAX_SYSTEM_PROMPT_CHARS = 16384;
|
|
741
743
|
/** Cap on a request's `outputSchema` (JSON-serialized) — an uncapped schema bloats the prompt + the per-turn
|
|
742
744
|
* validation cost. 32KB comfortably holds a rich nested schema while bounding the abuse surface. */
|
|
743
745
|
export declare const MAX_OUTPUT_SCHEMA_CHARS = 32768;
|
|
744
|
-
/**
|
|
746
|
+
/**
|
|
747
|
+
* Validate `body.skills` —— **形**校验 + 一条引擎孪生门。返回错误串(→ 400)或 null。
|
|
748
|
+
*
|
|
749
|
+
* 🔴 **[ref](7.77.0,硬 BREAKING;零别名零双读)**:此前这里有三条本仓自造的拒绝臂,全部退役 ——
|
|
750
|
+
* · `at most 10 skills per request`(条数帽);
|
|
751
|
+
* · `skill.description must be a string of at most 1024 characters`(描述长度帽);
|
|
752
|
+
* · `skill.name must be a non-empty string of at most 64 characters` 里的 **长度**那一半。
|
|
753
|
+
*
|
|
754
|
+
* 退役的判据是**上游两侧都没有同形门**:CC 2.1.259 的技能清单渲染(`cli259.js:23195` 的预算算法)
|
|
755
|
+
* 在超预算时把**描述裁短**、再不够就退成 name-only,**从不**因为条数多或描述长而拒掉一条技能;
|
|
756
|
+
* 它的 `Ir()` 名字校验也只判非空 / 无配对代理 / 无首尾空白 / 无括号逗号控制字符,没有长度帽。
|
|
757
|
+
* 引擎侧同理:唯一整拒的是 1 MiB 的单技能加载门。于是那三条帽是「装了 11 个技能就 400」这种
|
|
758
|
+
* 纯本仓面的伤——收益是零(预算由渲染侧按字节兜),代价是用户装不进自己的技能库。
|
|
759
|
+
*
|
|
760
|
+
* **补偿**(三问的第三问):`settings.skillListingBudgetFraction` / `settings.skillListingMaxDescChars`
|
|
761
|
+
* 两个旋钮直达引擎的渲染预算(per-turn 成本的真属主)。
|
|
762
|
+
* ⚠️ **fraction 是**单向**的**:引擎的预算 = `min(窗口 × fraction, 8 000 字节)`,8 000 是**投递车道的结构
|
|
763
|
+
* 上限**(清单帧与同 turn 的其它附件共用一份完整送达额度)⇒ **调低生效、调高被夹**;调高唯一的用处是让
|
|
764
|
+
* 一个声明窗口小于 200 000 token 的模型把清单预算拿回大窗口的水平。别把这句读成「调用方可以随便加大」。
|
|
765
|
+
* 体积面照旧由**非技能专属**的 8 MiB 请求体上限兜住(`MAX_BODY`),那是一条一直都在的门。
|
|
766
|
+
*
|
|
767
|
+
* 🔴 **server 侧禁裁列表 / 禁截描述**:渲染归引擎(单一属主)。本函数只判「这一条技能的形能不能用」。
|
|
768
|
+
*/
|
|
745
769
|
export declare function validateUserSkills(skills: unknown): string | null;
|
|
746
770
|
/**
|
|
747
771
|
* Managed-Agents-style HTTP/SSE service surface.
|
package/dist/http/server.js
CHANGED
|
@@ -10,6 +10,7 @@ import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifi
|
|
|
10
10
|
import { verifyDirectDoorProof } from "../principal-jwt.js";
|
|
11
11
|
import {} from "../memory-sync.js";
|
|
12
12
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, parseTaskSettings, taskSettingsKeyIssue } from "../task-settings.js";
|
|
13
|
+
import { taskBodyKeyIssue } from "../request-key-closure.js";
|
|
13
14
|
import { selfOrchestrationDenial } from "../task-workflow.js";
|
|
14
15
|
import { centerEntitlementSourceWired } from "../runtime-caps-resolver.js";
|
|
15
16
|
import { parseHooksConfig } from "../hooks/hook-runner.js";
|
|
@@ -33,10 +34,11 @@ import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
|
|
|
33
34
|
import { createWallClockJumpGuard } from "../wall-clock-jump-guard.js";
|
|
34
35
|
import {} from "../observability/rate-limit.js";
|
|
35
36
|
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";
|
|
37
|
+
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
38
|
import { redactSecrets } from "../trace/redact.js";
|
|
38
39
|
import { createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
|
|
39
40
|
import { registerEngineNoticeLeg } from "../trace/engine-notice-wire.js";
|
|
41
|
+
import { reasoningDeltaBytes, reasoningSegmentEventId } from "../trace/model-segment.js";
|
|
40
42
|
import { errorText } from "../observability/err-text.js";
|
|
41
43
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
42
44
|
import { IdempotencyCache, scopedIdempotencyKey } from "./idempotency.js";
|
|
@@ -95,7 +97,6 @@ export function flattenServiceDeps(deps) {
|
|
|
95
97
|
return deps;
|
|
96
98
|
return { ...stores, ...coordinators, ...seams, ...observability, ...governance, ...deployment, ...knobs, ...flat };
|
|
97
99
|
}
|
|
98
|
-
export const MAX_USER_SKILLS = 10;
|
|
99
100
|
export const MAX_SKILL_CONTENT_CHARS = 1_048_576;
|
|
100
101
|
export const MAX_SYSTEM_PROMPT_CHARS = 16_384;
|
|
101
102
|
export const MAX_OUTPUT_SCHEMA_CHARS = 32_768;
|
|
@@ -104,15 +105,13 @@ export function validateUserSkills(skills) {
|
|
|
104
105
|
return null;
|
|
105
106
|
if (!Array.isArray(skills))
|
|
106
107
|
return "skills must be an array of { name, description, content }";
|
|
107
|
-
if (skills.length > MAX_USER_SKILLS)
|
|
108
|
-
return `at most ${MAX_USER_SKILLS} skills per request`;
|
|
109
108
|
for (const s of skills) {
|
|
110
109
|
if (s === null || typeof s !== "object")
|
|
111
110
|
return "each skill must be an object { name, description, content }";
|
|
112
|
-
if (typeof s["name"] !== "string" || s["name"].length === 0
|
|
113
|
-
return "skill.name must be a non-empty string
|
|
114
|
-
if (typeof s["description"] !== "string"
|
|
115
|
-
return "skill.description must be a string
|
|
111
|
+
if (typeof s["name"] !== "string" || s["name"].length === 0)
|
|
112
|
+
return "skill.name must be a non-empty string";
|
|
113
|
+
if (typeof s["description"] !== "string")
|
|
114
|
+
return "skill.description must be a string";
|
|
116
115
|
if (typeof s["content"] !== "string" || s["content"].length === 0 || s["content"].length > MAX_SKILL_CONTENT_CHARS)
|
|
117
116
|
return `skill.content must be a non-empty string of at most ${MAX_SKILL_CONTENT_CHARS} characters (the engine rejects skills over this size outright at load time)`;
|
|
118
117
|
}
|
|
@@ -525,6 +524,13 @@ export function createHttpServer(rawDeps) {
|
|
|
525
524
|
sendError(res, 400, "request.field_invalid", "objective must not be empty or whitespace-only (an empty user message poisons the session history on strict providers)");
|
|
526
525
|
return null;
|
|
527
526
|
}
|
|
527
|
+
{
|
|
528
|
+
const bodyKeyIssue = taskBodyKeyIssue(body);
|
|
529
|
+
if (bodyKeyIssue) {
|
|
530
|
+
sendError(res, 400, "request.body_shape", bodyKeyIssue.message, { unknownKeys: bodyKeyIssue.unknownKeys, unsupportedKeys: bodyKeyIssue.unsupportedKeys });
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
528
534
|
if (body.jobId !== undefined && (typeof body.jobId !== "string" || body.jobId.length === 0 || body.jobId.length > 64)) {
|
|
529
535
|
sendError(res, 400, "request.field_invalid", "jobId must be a non-empty string of at most 64 characters");
|
|
530
536
|
return null;
|
|
@@ -644,6 +650,20 @@ export function createHttpServer(rawDeps) {
|
|
|
644
650
|
sendError(res, 400, "request.field_invalid", "settings.ultracode must be a boolean");
|
|
645
651
|
return null;
|
|
646
652
|
}
|
|
653
|
+
if (st.skillListingBudgetFraction !== undefined) {
|
|
654
|
+
const v = st.skillListingBudgetFraction;
|
|
655
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v <= 0 || v > 1) {
|
|
656
|
+
sendError(res, 400, "request.field_invalid", "settings.skillListingBudgetFraction must be a number greater than 0 and at most 1");
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
if (st.skillListingMaxDescChars !== undefined) {
|
|
661
|
+
const v = st.skillListingMaxDescChars;
|
|
662
|
+
if (typeof v !== "number" || !Number.isSafeInteger(v) || v <= 0) {
|
|
663
|
+
sendError(res, 400, "request.field_invalid", "settings.skillListingMaxDescChars must be a positive integer");
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
647
667
|
if (st.hooks !== undefined && st.hooks !== null) {
|
|
648
668
|
const hooksParsed = parseHooksConfig(st.hooks);
|
|
649
669
|
if (!hooksParsed.config) {
|
|
@@ -1355,7 +1375,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1355
1375
|
if (deps.toolApproval && taskId) {
|
|
1356
1376
|
const toolApproval = deps.toolApproval;
|
|
1357
1377
|
resumeTaskConfig.onAsk = approvalLeg.windowZero
|
|
1358
|
-
? () => Promise.resolve(toolApproval.unattendedAskOutcome())
|
|
1378
|
+
? (askReq) => Promise.resolve(toolApproval.unattendedAskOutcome({ taskId, owner: principal ?? null, toolName: askReq.toolName }))
|
|
1359
1379
|
: toolApproval.boundAsk({
|
|
1360
1380
|
owner: principal ?? null,
|
|
1361
1381
|
taskId,
|
|
@@ -1532,6 +1552,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1532
1552
|
const startSeq = rs && taskId ? await rs.maxSeq(taskId) : 0;
|
|
1533
1553
|
let text = "";
|
|
1534
1554
|
let reasoning = "";
|
|
1555
|
+
let reasoningEventId;
|
|
1535
1556
|
const persistThinking = deps.config.traceThinking;
|
|
1536
1557
|
const serialAppend = createSerialLedgerAppend((s, type, data) => rs.appendEvent(taskId, s, type, data), startSeq);
|
|
1537
1558
|
const append = (type, data) => (rs && taskId ? serialAppend(type, data) : Promise.resolve());
|
|
@@ -1555,8 +1576,9 @@ export function createHttpServer(rawDeps) {
|
|
|
1555
1576
|
const anchor = new TurnAnchorCapture(captureTurnAnchor, () => deps.metrics?.inc("resume_anchor_capture_failed"), captureUserMessageAnchor);
|
|
1556
1577
|
const flush = async () => {
|
|
1557
1578
|
if (reasoning) {
|
|
1558
|
-
await append("reasoning", modelTextEventData({ text: reasoning }));
|
|
1579
|
+
await append("reasoning", modelTextEventData({ text: reasoning, eventId: reasoningEventId }));
|
|
1559
1580
|
reasoning = "";
|
|
1581
|
+
reasoningEventId = undefined;
|
|
1560
1582
|
}
|
|
1561
1583
|
if (text) {
|
|
1562
1584
|
await append("text", modelTextEventData({ text, eventId: anchor.firstTextEventId }));
|
|
@@ -1632,10 +1654,14 @@ export function createHttpServer(rawDeps) {
|
|
|
1632
1654
|
text += ev.delta;
|
|
1633
1655
|
anchor.onText(ev.eventId);
|
|
1634
1656
|
break;
|
|
1635
|
-
case "reasoning_delta":
|
|
1636
|
-
|
|
1637
|
-
|
|
1657
|
+
case "reasoning_delta": {
|
|
1658
|
+
const rb = reasoningDeltaBytes(ev);
|
|
1659
|
+
if (persistThinking && rb !== undefined) {
|
|
1660
|
+
reasoning += rb;
|
|
1661
|
+
reasoningEventId ??= reasoningSegmentEventId(ev.eventId);
|
|
1662
|
+
}
|
|
1638
1663
|
break;
|
|
1664
|
+
}
|
|
1639
1665
|
case "tool_start":
|
|
1640
1666
|
anchor.onTool();
|
|
1641
1667
|
await flush();
|
|
@@ -1687,6 +1713,13 @@ export function createHttpServer(rawDeps) {
|
|
|
1687
1713
|
await append("tool_roster_delta", rosterDelta);
|
|
1688
1714
|
break;
|
|
1689
1715
|
}
|
|
1716
|
+
case "tool_disclosure": {
|
|
1717
|
+
await flush();
|
|
1718
|
+
const disclosure = toolDisclosureEventData(ev);
|
|
1719
|
+
if (disclosure !== undefined)
|
|
1720
|
+
await append("tool_disclosure", disclosure);
|
|
1721
|
+
break;
|
|
1722
|
+
}
|
|
1690
1723
|
case "human_input":
|
|
1691
1724
|
await flush();
|
|
1692
1725
|
await append("human_input", humanInputEventData(ev));
|
|
@@ -2518,6 +2551,7 @@ const WORKER_SWAP_REDEEMABLE = {
|
|
|
2518
2551
|
real_approval_forged: false,
|
|
2519
2552
|
read_face_damaged: false,
|
|
2520
2553
|
read_face_forged: false,
|
|
2554
|
+
write_face_damaged: false,
|
|
2521
2555
|
constraint_chain_missing: false,
|
|
2522
2556
|
};
|
|
2523
2557
|
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
|
-
*
|
|
36
|
-
* · 这一位**不是数组**(旧引擎写的记录 / 被投影剥掉)⇒
|
|
37
|
-
*
|
|
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 的赎回路由。
|