@sema-agent/server 7.114.0 → 7.115.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +73 -1
- package/USAGE.md +35 -2
- package/deploy/sema-up/cluster-up.sh +13 -1
- package/deploy/sema-up/kube-up.sh +13 -1
- package/deploy/sema-up/preflight.sh +13 -1
- package/deploy/sema-up/sema-up.sh +13 -1
- package/deploy/sema-up/smoke.sh +13 -1
- package/dist/adoption/wire.d.ts +0 -3
- package/dist/approval-card.d.ts +21 -24
- package/dist/approval-card.js +12 -0
- package/dist/approval.js +5 -2
- package/dist/boot/exit-boundary.d.ts +3 -2
- package/dist/boot/exit-boundary.js +3 -0
- package/dist/boot/resolve-spec.js +4 -1
- package/dist/boot/shutdown.d.ts +19 -0
- package/dist/boot/shutdown.js +12 -1
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/config-catalog.js +2 -1
- package/dist/config-center/apply-effective.js +11 -11
- package/dist/config-provider.d.ts +16 -0
- package/dist/config-provider.js +11 -2
- package/dist/config-types.d.ts +20 -1
- package/dist/config.d.ts +41 -18
- package/dist/config.js +36 -13
- package/dist/deployment-governance.d.ts +15 -4
- package/dist/deployment-governance.js +20 -2
- package/dist/fleet/fleet-bus.d.ts +1 -22
- package/dist/hooks/hook-runner.d.ts +29 -3
- package/dist/hooks/hook-runner.js +30 -17
- package/dist/http/admission.js +7 -2
- package/dist/http/approval-sweeps.js +20 -0
- package/dist/http/checkpoint-ctx.d.ts +68 -0
- package/dist/http/checkpoint-ctx.js +36 -0
- package/dist/http/durable-run-leg.js +2 -1
- package/dist/http/resume-legs.js +9 -0
- package/dist/http/route-ctx.d.ts +7 -0
- package/dist/http/routes/a2a-serve.d.ts +1 -3
- package/dist/http/routes/a2a-serve.js +0 -2
- package/dist/http/routes/admin-memory-optout.d.ts +0 -2
- package/dist/http/routes/admin-memory-optout.js +0 -1
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/diagnostics.d.ts +5 -0
- package/dist/http/routes/diagnostics.js +2 -0
- package/dist/http/routes/sessions.js +1 -1
- package/dist/http/routes/tasks.js +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/leader/wire.js +2 -1
- package/dist/local-data-root.d.ts +1 -1
- package/dist/mcp-content-origin.d.ts +0 -8
- package/dist/model-entry-refusal.d.ts +10 -2
- package/dist/model-entry-refusal.js +19 -3
- package/dist/observability/fail-open.d.ts +5 -8
- package/dist/permission-rule-list.d.ts +81 -0
- package/dist/permission-rule-list.js +17 -0
- package/dist/run-local.js +4 -1
- package/dist/runtime-governance.d.ts +21 -1
- package/dist/runtime-governance.js +6 -1
- package/dist/security.js +2 -2
- package/dist/session-policy-wire.d.ts +5 -4
- package/dist/task-settings.d.ts +6 -5
- package/dist/task-settings.js +16 -30
- package/dist/tool-approval.d.ts +16 -4
- package/dist/tool-approval.js +2 -1
- package/dist/trace/core-keyset-guard.d.ts +9 -4
- package/dist/trace/project.js +9 -0
- package/package.json +4 -4
- package/scripts/{retired-mode-seed-drain-local.mjs → drain-check-local.mjs} +147 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HooksConfig } from "@sema-agent/settings-schema/hooks";
|
|
1
|
+
import { HooksConfig, type HookEventName } from "@sema-agent/settings-schema/hooks";
|
|
2
2
|
import type { Hooks } from "@sema-agent/core";
|
|
3
3
|
import type { Logger } from "../observability/logger.js";
|
|
4
4
|
import type { HookNoticeObservation } from "../fleet/fleet-bus.js";
|
|
@@ -80,6 +80,10 @@ export interface ParseHooksConfigResult {
|
|
|
80
80
|
* 拒放集合相对 7.113.0 的一处放宽(披露在 CHANGELOG / MIGRATION):超过上限的字面别名 matcher 7.113.0 拒(当时字面判不设上限),自 7.114.0 两档都判未判、受理。
|
|
81
81
|
*
|
|
82
82
|
* 错误句只回显**目录里的**别名与正名(闭集字符串),不回显调用方的原字节(S-716 姿势)。
|
|
83
|
+
*
|
|
84
|
+
* 🔴 **判决是数据,句子与体的机读位同出一只铸点**(S-1025;client-core 请托,additive):{@link deadToolMatcherIssue} 回 {@link DeadToolMatcherIssue}(event / alias / canonical)
|
|
85
|
+
* 而不是句子;{@link buildDeadToolMatcherRefusal} 是**唯一**把它铸成拒句 + `field` + `detail` 的函数,受理门只取它的返回、**不解析句子**。体上多出的两键让三端不必解析英文就能把这只拒
|
|
86
|
+
* 与普通提交失败分流、提示用户改 matcher;句子逐字同 7.114.0、零新码。
|
|
83
87
|
*/
|
|
84
88
|
/** 超此长度的 matcher 不在受理面读(两档同一个上限;未判,reason = oversize)。与 {@link MAX_HOOK_COMMAND_CHARS} 同量纲的服务侧防线:真实 matcher(精确名 / 交替 / `mcp__.*`)远在其内;目录全部工具名交替 ~0.5 KiB。 */
|
|
85
89
|
export declare const MAX_MATCHER_EVAL_CHARS = 8192;
|
|
@@ -95,13 +99,35 @@ export interface MatcherEvalBudget {
|
|
|
95
99
|
readonly budgetMs?: number;
|
|
96
100
|
readonly now?: () => number;
|
|
97
101
|
}
|
|
102
|
+
/** 一条受理面判决(纯数据):`event` 事件下的某只 matcher 选中了退役别名 `alias`、却选不中它的正名 `canonical`。`alias` / `canonical` 恒取自 core 静态目录(闭集字符串,不含调用方原字节)。 */
|
|
103
|
+
export interface DeadToolMatcherIssue {
|
|
104
|
+
readonly event: HookEventName;
|
|
105
|
+
readonly alias: string;
|
|
106
|
+
readonly canonical: string;
|
|
107
|
+
}
|
|
108
|
+
/** {@link buildDeadToolMatcherRefusal} 的返回(纯数据):拒句 + 400 体上的两个机读位。`field` 恒为字面 `"settings.hooks"`;`detail` 与 {@link DeadToolMatcherIssue} 同三键(逐个挑出,不 spread)。 */
|
|
109
|
+
export interface DeadToolMatcherRefusal {
|
|
110
|
+
readonly message: string;
|
|
111
|
+
readonly field: "settings.hooks";
|
|
112
|
+
readonly detail: {
|
|
113
|
+
readonly event: HookEventName;
|
|
114
|
+
readonly alias: string;
|
|
115
|
+
readonly canonical: string;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 判决 → 拒句 + 机读位。**拒句的唯一铸点**:句子逐字 = 7.114.0 的那一句(`test/hooks-alias-matcher-s995.test.ts` R7 冻结);受理门 `admission.ts` 用返回值填 400 体,
|
|
120
|
+
* 不从句子里反解字段。
|
|
121
|
+
*/
|
|
122
|
+
export declare function buildDeadToolMatcherRefusal(issue: DeadToolMatcherIssue): DeadToolMatcherRefusal;
|
|
98
123
|
/**
|
|
99
|
-
* fresh 提交面专用判据(S-924 / S-995):配置里第一条「永远打不中」的工具事件 matcher
|
|
124
|
+
* fresh 提交面专用判据(S-924 / S-995):配置里第一条「永远打不中」的工具事件 matcher 的判决数据({@link DeadToolMatcherIssue});没有 ⇒ undefined。
|
|
125
|
+
* 拒句与 400 体的机读位由 {@link buildDeadToolMatcherRefusal} 从这份数据铸(S-1025)。
|
|
100
126
|
* 入参是 {@link parseHooksConfig} 已校验过的配置;`depth` 由调用点按车道定(见头注:`taskHooksHonored(config) ? "regex" : "literal"`)。
|
|
101
127
|
* `budget` **仅供测试注入**({@link MatcherEvalBudget});生产调用点只传前两个实参。
|
|
102
128
|
* **只有 `admission.ts` 的 fresh 提交门调它** —— resume 重放不调(理由见上面的头注)。未判的 matcher 过 `recordFailOpen`(tag `MATCHER_UNJUDGED_TAG`)。
|
|
103
129
|
*/
|
|
104
|
-
export declare function deadToolMatcherIssue(config: HooksConfig, depth: MatcherEvalDepth, budget?: MatcherEvalBudget):
|
|
130
|
+
export declare function deadToolMatcherIssue(config: HooksConfig, depth: MatcherEvalDepth, budget?: MatcherEvalBudget): DeadToolMatcherIssue | undefined;
|
|
105
131
|
/**
|
|
106
132
|
* 把 wire 上的 untrusted `settings.hooks` 校验成契约 {@link HooksConfig} + 服务侧上限。
|
|
107
133
|
* 错误返回 error 字符串而不 throw——submit 路径 400 fail-loud、resume 防御路径 warn-drop,由调用方选。
|
|
@@ -142,6 +142,13 @@ function deadAliasInMatcher(matcher, depth, budget) {
|
|
|
142
142
|
}
|
|
143
143
|
return undefined;
|
|
144
144
|
}
|
|
145
|
+
export function buildDeadToolMatcherRefusal(issue) {
|
|
146
|
+
return {
|
|
147
|
+
message: `settings.hooks.${issue.event}: matcher "${issue.alias}" is an alias; tool-event matchers match the tool's wire name (PostToolUse / PostToolUseFailure since 7.107.0) — use "${issue.canonical}"`,
|
|
148
|
+
field: "settings.hooks",
|
|
149
|
+
detail: { event: issue.event, alias: issue.alias, canonical: issue.canonical },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
145
152
|
export function deadToolMatcherIssue(config, depth, budget = {}) {
|
|
146
153
|
const now = budget.now ?? (() => performance.now());
|
|
147
154
|
const deadline = { deadlineMs: now() + (budget.budgetMs ?? ADMISSION_MATCHER_EVAL_BUDGET_MS), now };
|
|
@@ -158,7 +165,7 @@ export function deadToolMatcherIssue(config, depth, budget = {}) {
|
|
|
158
165
|
recordFailOpen(MATCHER_UNJUDGED_TAG, `event=${event} reason=${verdict.reason} matcherChars=${group.matcher.length}`);
|
|
159
166
|
continue;
|
|
160
167
|
}
|
|
161
|
-
return
|
|
168
|
+
return { event, alias: verdict.alias, canonical: verdict.canonical };
|
|
162
169
|
}
|
|
163
170
|
}
|
|
164
171
|
return undefined;
|
|
@@ -855,6 +862,7 @@ export function createTaskHooks(config, ctx) {
|
|
|
855
862
|
let currentInput = input;
|
|
856
863
|
let rewritten = false;
|
|
857
864
|
let asked;
|
|
865
|
+
let allowedFor;
|
|
858
866
|
let unknownDecision = false;
|
|
859
867
|
const contexts = [];
|
|
860
868
|
const deadlineAt = Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000;
|
|
@@ -868,11 +876,12 @@ export function createTaskHooks(config, ctx) {
|
|
|
868
876
|
}
|
|
869
877
|
if (!m)
|
|
870
878
|
continue;
|
|
879
|
+
const groupInput = currentInput;
|
|
871
880
|
const payload = {
|
|
872
881
|
...basePayload(ctx, tctx.permissionMode),
|
|
873
882
|
hook_event_name: "PreToolUse",
|
|
874
883
|
tool_name: toolName,
|
|
875
|
-
tool_input:
|
|
884
|
+
tool_input: groupInput,
|
|
876
885
|
tool_use_id: tctx.toolCallId,
|
|
877
886
|
};
|
|
878
887
|
const singles = await runMatchingEntries("PreToolUse", [{ ...group, matcher: undefined }], toolName, payload, ctx, onceFired, deadlineAt, `PreToolUse:${gi}`);
|
|
@@ -913,10 +922,13 @@ export function createTaskHooks(config, ctx) {
|
|
|
913
922
|
asked = clip(typeof h.permissionDecisionReason === "string" ? h.permissionDecisionReason : "a PreToolUse hook requires approval", MAX_HOOK_FEEDBACK_CHARS);
|
|
914
923
|
}
|
|
915
924
|
if (decision === "allow" || decision === undefined) {
|
|
916
|
-
|
|
925
|
+
const rewrites = h.updatedInput !== null && typeof h.updatedInput === "object";
|
|
926
|
+
if (rewrites) {
|
|
917
927
|
currentInput = h.updatedInput;
|
|
918
928
|
rewritten = true;
|
|
919
929
|
}
|
|
930
|
+
if (decision === "allow")
|
|
931
|
+
allowedFor = { input: rewrites ? h.updatedInput : groupInput };
|
|
920
932
|
}
|
|
921
933
|
else if (decision !== "deny" && decision !== "ask") {
|
|
922
934
|
unknownDecision = true;
|
|
@@ -946,13 +958,11 @@ export function createTaskHooks(config, ctx) {
|
|
|
946
958
|
...buildHookContextField(contexts),
|
|
947
959
|
};
|
|
948
960
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
};
|
|
955
|
-
}
|
|
961
|
+
const carried = { ...(rewritten ? { updatedInput: currentInput } : {}), ...buildHookContextField(contexts) };
|
|
962
|
+
if (allowedFor !== undefined && allowedFor.input === currentInput)
|
|
963
|
+
return { action: "allow", ...carried };
|
|
964
|
+
if (rewritten || contexts.length > 0)
|
|
965
|
+
return carried;
|
|
956
966
|
return undefined;
|
|
957
967
|
};
|
|
958
968
|
hooks.preToolUse = preToolUseHook;
|
|
@@ -1324,6 +1334,7 @@ export function composeHooks(deployment, task) {
|
|
|
1324
1334
|
let current = input;
|
|
1325
1335
|
const contexts = [];
|
|
1326
1336
|
let asked;
|
|
1337
|
+
let allowedFor;
|
|
1327
1338
|
for (const slot of [d.preToolUse?.bind(d), t.preToolUse?.bind(t)]) {
|
|
1328
1339
|
if (!slot)
|
|
1329
1340
|
continue;
|
|
@@ -1343,6 +1354,10 @@ export function composeHooks(deployment, task) {
|
|
|
1343
1354
|
asked = r;
|
|
1344
1355
|
break;
|
|
1345
1356
|
case "allow":
|
|
1357
|
+
if (updatedInput !== undefined)
|
|
1358
|
+
current = updatedInput;
|
|
1359
|
+
allowedFor = { input: current };
|
|
1360
|
+
break;
|
|
1346
1361
|
case undefined:
|
|
1347
1362
|
if (updatedInput !== undefined)
|
|
1348
1363
|
current = updatedInput;
|
|
@@ -1362,13 +1377,11 @@ export function composeHooks(deployment, task) {
|
|
|
1362
1377
|
...buildHookContextField(contexts),
|
|
1363
1378
|
};
|
|
1364
1379
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
};
|
|
1371
|
-
}
|
|
1380
|
+
const carried = { ...(current !== input ? { updatedInput: current } : {}), ...buildHookContextField(contexts) };
|
|
1381
|
+
if (allowedFor !== undefined && allowedFor.input === current)
|
|
1382
|
+
return { action: "allow", ...carried };
|
|
1383
|
+
if (current !== input || contexts.length > 0)
|
|
1384
|
+
return carried;
|
|
1372
1385
|
return undefined;
|
|
1373
1386
|
};
|
|
1374
1387
|
}
|
package/dist/http/admission.js
CHANGED
|
@@ -7,7 +7,7 @@ import { taskBodyKeyIssue } from "../request-key-closure.js";
|
|
|
7
7
|
import { FILE_HISTORY_DISABLED_NO_RESTORE, fileHistoryRestoreConflict, inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
|
|
8
8
|
import { selfOrchestrationDenial } from "../task-workflow.js";
|
|
9
9
|
import { centerEntitlementSourceWired } from "../runtime-caps-resolver.js";
|
|
10
|
-
import { deadToolMatcherIssue, parseHooksConfig } from "../hooks/hook-runner.js";
|
|
10
|
+
import { buildDeadToolMatcherRefusal, deadToolMatcherIssue, parseHooksConfig } from "../hooks/hook-runner.js";
|
|
11
11
|
import { judgeRequestWebSearch } from "../plugins/web-search.js";
|
|
12
12
|
import { normalizeApproachNotice, unknownTaskAgentModelRef, validateTaskAgents } from "../spec-fields.js";
|
|
13
13
|
import { isValidCwd, MAX_ADDITIONAL_DIRS, MAX_CWD_CHARS } from "../task-cwd.js";
|
|
@@ -202,7 +202,8 @@ export function createAdmission(ctx) {
|
|
|
202
202
|
}
|
|
203
203
|
const deadMatcher = deadToolMatcherIssue(hooksParsed.config, taskHooksHonored(deps.config) ? "regex" : "literal");
|
|
204
204
|
if (deadMatcher !== undefined) {
|
|
205
|
-
|
|
205
|
+
const refusal = buildDeadToolMatcherRefusal(deadMatcher);
|
|
206
|
+
sendError(res, 400, "request.field_invalid", refusal.message, { field: refusal.field, detail: refusal.detail });
|
|
206
207
|
return null;
|
|
207
208
|
}
|
|
208
209
|
}
|
|
@@ -420,6 +421,10 @@ export function createAdmission(ctx) {
|
|
|
420
421
|
sendError(res, 400, "request.field_invalid", "limits.budgetStreamCancel is not caller-settable here — the engine default applies (armed whenever a cost budget is present); remove it");
|
|
421
422
|
return null;
|
|
422
423
|
}
|
|
424
|
+
if (l.brainCallGuardrailMs !== undefined) {
|
|
425
|
+
sendError(res, 400, "request.field_invalid", "limits.brainCallGuardrailMs is not caller-settable here — the engine's default wall on a single brain call applies (one hour); remove it");
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
423
428
|
for (const k of ["maxWalltimeMs", "maxOutputTokens", "maxTurns"]) {
|
|
424
429
|
const v = l[k];
|
|
425
430
|
if (v !== undefined && (typeof v !== "number" || !Number.isInteger(v) || v < 1)) {
|
|
@@ -7,6 +7,8 @@ import { createWallClockJumpGuard } from "../wall-clock-jump-guard.js";
|
|
|
7
7
|
import { hasServiceAuth } from "./dispatch.js";
|
|
8
8
|
import { gatedPrincipal } from "./principal-gate.js";
|
|
9
9
|
import { readinessOf, billableRefusalOf } from "./readiness.js";
|
|
10
|
+
import { readResumeCtx } from "./resume-ctx-read.js";
|
|
11
|
+
import { allowListReadingUnstated } from "./checkpoint-ctx.js";
|
|
10
12
|
const BLOCKED_SCALAR_KEYS = ["errorCode", "blockedBy", "field", "reason", "count", "cap", "index"];
|
|
11
13
|
function blockedScalars(body) {
|
|
12
14
|
const out = {};
|
|
@@ -64,17 +66,35 @@ export function createApprovalSweeps(ctx) {
|
|
|
64
66
|
return false;
|
|
65
67
|
}
|
|
66
68
|
};
|
|
69
|
+
const allowListUnservable = async (sessionId) => {
|
|
70
|
+
const read = await readResumeCtx(cs.getCtx(sessionId));
|
|
71
|
+
if (read.kind === "corrupt")
|
|
72
|
+
return false;
|
|
73
|
+
return read.kind === "ok" && allowListReadingUnstated(read.ctx);
|
|
74
|
+
};
|
|
67
75
|
let parkedSkipped = 0;
|
|
76
|
+
const allowListSkipped = [];
|
|
68
77
|
for (const { sessionId } of expired) {
|
|
69
78
|
if (await isParkedOwned(sessionId)) {
|
|
70
79
|
parkedSkipped += 1;
|
|
71
80
|
continue;
|
|
72
81
|
}
|
|
82
|
+
if (await allowListUnservable(sessionId)) {
|
|
83
|
+
allowListSkipped.push(sessionId);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
73
86
|
await resumeCheckpoint(sessionId, "deny", "approval SLA expired — auto-denied", { decidedBy: "sla_timeout" }).then((r) => { if (r.status >= 400)
|
|
74
87
|
deps.logger?.warn?.("deny_sweep_resume_blocked", { sessionId, status: r.status, ...blockedScalars(r.body) }); }, (e) => deps.logger?.warn?.("deny_sweep_resume_failed", { sessionId, err: redactHead(e instanceof Error ? e.message : String(e), 2000) }));
|
|
75
88
|
}
|
|
76
89
|
if (parkedSkipped > 0)
|
|
77
90
|
deps.logger?.info?.("deny_sweep_parked_skipped", { count: parkedSkipped });
|
|
91
|
+
if (allowListSkipped.length > 0) {
|
|
92
|
+
deps.logger?.warn?.("deny_sweep_allow_list_reading_skipped", {
|
|
93
|
+
count: allowListSkipped.length,
|
|
94
|
+
sessionIds: allowListSkipped,
|
|
95
|
+
note: "these expired approvals belong to tasks suspended by a server version that read settings.permissions.allow as a whitelist; this version does not replay them, so the SLA deny cannot be delivered. They stay suspended (session lock held) until the reaper collects them at their terminal time — cancel them (POST /v1/runs/:id/cancel) to release the sessions now",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
78
98
|
}
|
|
79
99
|
async function redeemParkedAsk(req) {
|
|
80
100
|
const cs = deps.checkpointStore;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 续跑重建输入(`checkpoint_ctx` 的行体,{@link CheckpointCtx})的**写形**,以及持久化体里 `settings.permissions.allow` 的**读法记号** —— S-1034(7.115.0)。
|
|
3
|
+
*
|
|
4
|
+
* ## 病
|
|
5
|
+
* 7.115.0 把 `settings.permissions.allow` 从「名单」(名单外的工具一律拒)改成「allow 规则」(命中 = 带理由的放行,未命中 = 不表态、照常判)。
|
|
6
|
+
* 续跑家族重建 spec 时把**持久化的提交体**原样重放进 `resolveSpec`(`resume-legs.ts` 的 `resolveResumeSpec`)——
|
|
7
|
+
* 一条在 ≤7.114.0 上停驻、体里带非空 `allow` 的行,升级后续跑就被按新读法读:名单外的工具不再被拒。持久面上的静默语义翻转(放宽方向)。
|
|
8
|
+
* 坏形 `allow`(`Task` / `mcp__figma` / `Bash(npm:*)` …)的旧行则每次续跑都 409、SLA 拒扫腿逐拍重试直到收割 —— 一张本来放不开任何门的停驻被砖化。
|
|
9
|
+
*
|
|
10
|
+
* ## 规则(一条;持久数据的「响亮拒 + 一次性迁移」,零双读)
|
|
11
|
+
* 续跑重放持久化体之前问一句:**体里的 `allow` 在生效(非空数组)而本行没有「按表态读」的记号 ⇒ 拒**。
|
|
12
|
+
* 行留停驻、字节不动(拒发生在认领之前,run 行、卡、ctx 一个字节都不写);出路 = 作为新任务重提,这条任务若还停驻着先取消(`task_done` 唤醒停驻与 workflow 宿主那条腿没有可取消的 run)。
|
|
13
|
+
* **没有**「按旧名单语义继续读」的臂:那是第二种读法,正是本规则要消灭的东西。
|
|
14
|
+
* · 记号 = ≥7.115.0 写下的行在体里带非空 `allow` 时,ctx 上的 `permissionsAllowReading: "statement"`(闭集一词)。
|
|
15
|
+
* 写点 = 每一次**新的**持久化写({@link buildCheckpointCtx} 是它们的唯一构造口);续跑腿的写回传的是读进来的那只 ctx(或其展开),记号随行,不重新判。
|
|
16
|
+
* · 体里没有 `allow`(或空数组 —— 7.114.0 的名单判据 `acceptedRuleList` 对空数组同样读成「没有名单」)的行不写记号、不受本规则影响。
|
|
17
|
+
*
|
|
18
|
+
* ## 判据与 7.114.0 的关系(如实)
|
|
19
|
+
* 7.114.0 只在三桶整体合法时才读名单;判据这里只看「`allow` 是非空数组」,比 7.114.0 的「名单生效」宽一格(坏桶的行 7.114.0 也不读名单)——
|
|
20
|
+
* 方向 = 多拒,不会漏拒;而坏桶的体当年就被 fresh 受理门拒在门外,盘上本不该有。
|
|
21
|
+
*
|
|
22
|
+
* ## 何时删(一次性迁移,不是长期双读)
|
|
23
|
+
* 本判据、拒臂、记号写点与 `CheckpointCtx.permissionsAllowReading` 一起删,删除条件 = 所有 ≤7.114.0 停驻的行都已结束或被收割
|
|
24
|
+
* (停驻行的终局时刻 = 落盘起 `TERMINAL_BACKSTOP_MS`,见 `plugins/checkpoint-store-sql.ts`)之后的那个版本。删的时候记号的读者一起删:
|
|
25
|
+
* 届时盘上不会再有没记号却带 `allow` 的行,而有记号的行读不读它都一样。
|
|
26
|
+
*/
|
|
27
|
+
import type { CheckpointCtx } from "./route-ctx.js";
|
|
28
|
+
import type { TaskRequestBody } from "./wire-types.js";
|
|
29
|
+
/** 读法记号的闭集(一词):这一行体里的 `settings.permissions.allow` 是按「allow 规则(表态)」写下、也该按它读的。 */
|
|
30
|
+
export declare const PERMISSIONS_ALLOW_READINGS: readonly ["statement"];
|
|
31
|
+
export type PermissionsAllowReading = (typeof PERMISSIONS_ALLOW_READINGS)[number];
|
|
32
|
+
/**
|
|
33
|
+
* 体里的 `settings.permissions.allow` 是否**在生效**:非空数组(与 7.114.0 名单判据 `acceptedRuleList` 同一个「在」——空数组 = 没有)。
|
|
34
|
+
* 入参是持久化的 JSON(可能来自任何旧版本),逐层判形,不信型面。
|
|
35
|
+
*/
|
|
36
|
+
export declare function bodyCarriesAllowList(body: TaskRequestBody): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* 一次**新的**持久化写的 ctx(提交腿 / `POST /v1/runs` 族 / leader worker)—— 写记号的唯一口。
|
|
39
|
+
*
|
|
40
|
+
* 调用方是「本进程刚按表态读过这份体」的写点:fresh 提交(受理门按 7.115.0 读过 `allow`),或已经过了续跑拒臂的重放腿
|
|
41
|
+
* (`startFreshHostTurn` 把宿主的存量体交给 `POST /v1/runs` 同一条链 —— 它在那之前调过 `resolveResumeSpecOrBlocked`,没记号却带 `allow` 的体到不了这里)。
|
|
42
|
+
* 续跑腿的**写回**不经本函数:它传读进来的 ctx(或其展开),记号随行;在写回口重新判会把一条没记号的旧行「洗」成新行。
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildCheckpointCtx(fields: {
|
|
45
|
+
body: TaskRequestBody;
|
|
46
|
+
memoryScope?: string | undefined;
|
|
47
|
+
unattendedLeg?: true;
|
|
48
|
+
}): CheckpointCtx;
|
|
49
|
+
/**
|
|
50
|
+
* 本行能否被按 7.115.0 的读法重放:体里 `allow` 在生效而行上没有记号 ⇒ **不能**(`true` = 拒)。
|
|
51
|
+
* 记号的值不在闭集里(手改 / 将来的写者)⇒ 同样拒:认不得的记号不是证据。
|
|
52
|
+
*/
|
|
53
|
+
export declare function allowListReadingUnstated(ctx: CheckpointCtx): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 拒的答复 —— 码与落点照「退役 mode-seed 行」的先例(`checkpoint.unsupported_version`,409,`retriable:true` = 那张卡仍 pending;**不是**「原样重试会成功」)。
|
|
56
|
+
* 拒在认领之前,所以没有 `taskId`(行没抢到就不造键)。句子说原因(这条任务停在 `allow` 还是名单的版本上)与出路(作为新任务重提,仍停驻的先取消;要「只许这些工具」用会话规则 `toolAllow` / 每请求 `excludeTools`)。
|
|
57
|
+
*
|
|
58
|
+
* 🔴 形是给冻结门看的(`api-error-text-freeze` 的 resume 家族收集器):`status: <三位数>, body: { error: "<整句双引号字面>", errorCode: "<码>"`,中间只许有空白。
|
|
59
|
+
*/
|
|
60
|
+
export declare function allowListReadingRefusal(): {
|
|
61
|
+
status: number;
|
|
62
|
+
body: {
|
|
63
|
+
error: string;
|
|
64
|
+
errorCode: string;
|
|
65
|
+
retriable: true;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=checkpoint-ctx.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const PERMISSIONS_ALLOW_READINGS = ["statement"];
|
|
2
|
+
export function bodyCarriesAllowList(body) {
|
|
3
|
+
const settings = body?.settings;
|
|
4
|
+
if (settings === null || typeof settings !== "object" || Array.isArray(settings))
|
|
5
|
+
return false;
|
|
6
|
+
const permissions = settings["permissions"];
|
|
7
|
+
if (permissions === null || typeof permissions !== "object" || Array.isArray(permissions))
|
|
8
|
+
return false;
|
|
9
|
+
const allow = permissions["allow"];
|
|
10
|
+
return Array.isArray(allow) && allow.length > 0;
|
|
11
|
+
}
|
|
12
|
+
export function buildCheckpointCtx(fields) {
|
|
13
|
+
return {
|
|
14
|
+
body: fields.body,
|
|
15
|
+
...(fields.memoryScope !== undefined ? { memoryScope: fields.memoryScope } : {}),
|
|
16
|
+
...(fields.unattendedLeg === true ? { unattendedLeg: true } : {}),
|
|
17
|
+
...(bodyCarriesAllowList(fields.body) ? { permissionsAllowReading: "statement" } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function allowListReadingUnstated(ctx) {
|
|
21
|
+
if (!bodyCarriesAllowList(ctx.body))
|
|
22
|
+
return false;
|
|
23
|
+
const reading = ctx.permissionsAllowReading;
|
|
24
|
+
return !(typeof reading === "string" && PERMISSIONS_ALLOW_READINGS.includes(reading));
|
|
25
|
+
}
|
|
26
|
+
export function allowListReadingRefusal() {
|
|
27
|
+
return {
|
|
28
|
+
status: 409,
|
|
29
|
+
body: {
|
|
30
|
+
error: "this task was suspended by an earlier server version, which read settings.permissions.allow as a whitelist (every tool not on the list was refused); this version reads that list as a statement that the named tools are allowed, not as a whitelist, so continuing would give the task more tools than it was submitted with. Nothing was decided and nothing was resumed, and retrying will be refused the same way: submit the work again as a new task, and if this task is still suspended cancel it first (POST /v1/runs/:id/cancel). To allow only certain tools, use the session rules' toolAllow (PUT /v1/sessions/:id/policy) or the request's excludeTools.",
|
|
31
|
+
errorCode: "checkpoint.unsupported_version",
|
|
32
|
+
retriable: true,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=checkpoint-ctx.js.map
|
|
@@ -8,6 +8,7 @@ import { commitPlacement, devicePlacementErrorBody } from "./device-placement.js
|
|
|
8
8
|
import { commitSessionInheritEnv } from "./session-inherit-env.js";
|
|
9
9
|
import { clearTurnActivity, readTurnActivityMs } from "../turn-activity.js";
|
|
10
10
|
import { recordResourceWindow } from "../resource-window.js";
|
|
11
|
+
import { buildCheckpointCtx } from "./checkpoint-ctx.js";
|
|
11
12
|
export async function mintAndDriveDurableRun(ctx, runStore, args) {
|
|
12
13
|
const { deps } = ctx;
|
|
13
14
|
const { inflightRuns, preemptableRuns, steerableRuns } = ctx.registry;
|
|
@@ -31,7 +32,7 @@ export async function mintAndDriveDurableRun(ctx, runStore, args) {
|
|
|
31
32
|
return { status: placementReject.status, body: devicePlacementErrorBody(placementReject) };
|
|
32
33
|
commitSessionInheritEnv(deps, args.prepared.spec.sessionId, args.prepared.body);
|
|
33
34
|
if (deps.checkpointStore) {
|
|
34
|
-
await deps.checkpointStore.putCtx(sessionId, { body: args.prepared.body, memoryScope: args.prepared.auth?.memoryScope });
|
|
35
|
+
await deps.checkpointStore.putCtx(sessionId, buildCheckpointCtx({ body: args.prepared.body, memoryScope: args.prepared.auth?.memoryScope }));
|
|
35
36
|
}
|
|
36
37
|
const anchorOwner = args.prepared.auth?.principal ?? null;
|
|
37
38
|
const getLeafId = deps.sessionStorage?.getLeafId?.bind(deps.sessionStorage);
|
package/dist/http/resume-legs.js
CHANGED
|
@@ -42,6 +42,7 @@ import { commitPlacement, devicePlacementErrorBody } from "./device-placement.js
|
|
|
42
42
|
import { commitSessionInheritEnv } from "./session-inherit-env.js";
|
|
43
43
|
import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
|
|
44
44
|
import { readResumeCtx, resumeCtxCorruptAnswer } from "./resume-ctx-read.js";
|
|
45
|
+
import { allowListReadingRefusal, allowListReadingUnstated } from "./checkpoint-ctx.js";
|
|
45
46
|
import { httpErrorCode } from "./send.js";
|
|
46
47
|
function isCheckpointReopenedFailure(r) {
|
|
47
48
|
const code = failureCodeOf(r.terminal);
|
|
@@ -398,7 +399,13 @@ export function createResumeLegs(ctx) {
|
|
|
398
399
|
});
|
|
399
400
|
return { spec, engineNotices };
|
|
400
401
|
};
|
|
402
|
+
const refuseAllowListReading = (sessionId) => {
|
|
403
|
+
deps.logger?.warn?.("resume_refused_allow_list_reading", { sessionId, note: "this session's stored request carries settings.permissions.allow written by a server that read it as a whitelist; it is not replayed under the current reading — the task stays suspended until someone cancels it" });
|
|
404
|
+
return allowListReadingRefusal();
|
|
405
|
+
};
|
|
401
406
|
const resolveResumeSpecOrBlocked = async (ctx, req, auth) => {
|
|
407
|
+
if (allowListReadingUnstated(ctx))
|
|
408
|
+
return { ok: false, answer: refuseAllowListReading(auth.sessionId) };
|
|
402
409
|
try {
|
|
403
410
|
const { spec, engineNotices } = await resolveResumeSpec(ctx, req, auth);
|
|
404
411
|
return { ok: true, spec, engineNotices };
|
|
@@ -1332,6 +1339,8 @@ export function createResumeLegs(ctx) {
|
|
|
1332
1339
|
return refuse(resumeCtxCorruptAnswer(ctxRead.where));
|
|
1333
1340
|
if (ctxRead.kind === "absent")
|
|
1334
1341
|
return refuse({ status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } });
|
|
1342
|
+
if (allowListReadingUnstated(ctxRead.ctx))
|
|
1343
|
+
return refuse(refuseAllowListReading(sessionId));
|
|
1335
1344
|
return { ok: true, prepared: { sessionId, token, cp, ctx: ctxRead.ctx } };
|
|
1336
1345
|
}
|
|
1337
1346
|
async function drivePlanReview(prepared, decision, editedPlan, reason, req, acceptEarly, permissionModeAfter) {
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -161,6 +161,13 @@ export interface CheckpointCtx {
|
|
|
161
161
|
* 传原 ctx / 其展开 ⇒ 标随行。缺席 = 不是无人值守腿(HTTP 提交腿;7.106.0 前落盘的行 —— 那时这些腿不铸模式,checkpoint 里也没有)。
|
|
162
162
|
*/
|
|
163
163
|
unattendedLeg?: true;
|
|
164
|
+
/**
|
|
165
|
+
* 🔴 S-1034(7.115.0)—— 体里 `settings.permissions.allow` 的**读法记号**:在场 = 这一行是 ≥7.115.0 写下的、体里带非空 `allow`,
|
|
166
|
+
* 它按「allow 规则(表态)」写、也按它读。**服务端写、不来自请求体**(与 {@link unattendedLeg} 同为 `body` 的兄弟键)。
|
|
167
|
+
* 缺席而体里带非空 `allow` = ≤7.114.0 停驻的行(那时 `allow` 是名单)⇒ 续跑腿响亮拒、行字节不动(规则与删除条件的属主 = `http/checkpoint-ctx.ts` 顶注)。
|
|
168
|
+
* 写点只有 `buildCheckpointCtx`(同模块;新的持久化写);续跑腿的写回传原 ctx / 其展开 ⇒ 记号随行。体里没有 `allow` 的行不写它。
|
|
169
|
+
*/
|
|
170
|
+
permissionsAllowReading?: import("./checkpoint-ctx.js").PermissionsAllowReading;
|
|
164
171
|
}
|
|
165
172
|
/** B4①(命名化):`driveResumeIntoRunLog` 的入参形(resume 家族共用)——原为 9 成员内联匿名对象。 */
|
|
166
173
|
export interface DriveResumeArgs {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
import {
|
|
2
|
+
import type { A2ATaskState } from "@sema-agent/core";
|
|
3
3
|
import type { A2aServeConfig } from "../../config-types.js";
|
|
4
4
|
import { type RunRecord } from "../../plugins/store-contracts.js";
|
|
5
5
|
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
@@ -69,8 +69,6 @@ export declare function a2aStateForRun(run: Pick<RunRecord, "status" | "errorCod
|
|
|
69
69
|
* `capabilities.a2aServe` 的子串,好让 `route-shape-roster` 层E 如实认出它们。
|
|
70
70
|
*/
|
|
71
71
|
export declare function handleA2a(req: IncomingMessage, res: ServerResponse, match: RouteMatch<A2aRpcRouteId>, ctx: RouteCtx): Promise<void>;
|
|
72
|
-
/** 九词闭集的对外可见锚(测试用它证明我们只发闭集词;也让「加一个自造词」在这里就被看见)。 */
|
|
73
|
-
export declare const A2A_SERVE_TASK_STATES: readonly A2ATaskState[];
|
|
74
72
|
export declare const A2A_SERVE_ROUTES: readonly [{
|
|
75
73
|
readonly id: "a2a-agent-card";
|
|
76
74
|
readonly path: "/.well-known/agent-card.json";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { persistedPlaneOf } from "../../terminal.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { memoRedactedRead, redactErrorMessage } from "../../observability/run-terminal.js";
|
|
4
|
-
import { A2A_TASK_STATES } from "@sema-agent/core";
|
|
5
4
|
import { isParkedRunStatus, isTerminalRunStatus } from "../../plugins/store-contracts.js";
|
|
6
5
|
import { HttpError } from "../../security.js";
|
|
7
6
|
import { scopedIdempotencyKey } from "../idempotency.js";
|
|
@@ -396,7 +395,6 @@ export async function handleA2a(req, res, match, ctx) {
|
|
|
396
395
|
function sendRpc(res, body) {
|
|
397
396
|
sendJson(res, 200, body);
|
|
398
397
|
}
|
|
399
|
-
export const A2A_SERVE_TASK_STATES = A2A_TASK_STATES;
|
|
400
398
|
export const A2A_SERVE_ROUTES = [
|
|
401
399
|
{ id: "a2a-agent-card", path: "/.well-known/agent-card.json", methods: ["GET"] },
|
|
402
400
|
];
|
|
@@ -42,8 +42,6 @@
|
|
|
42
42
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
43
43
|
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
44
44
|
export declare const ADMIN_MEMORY_OPTOUT_PATH = "/v1/admin/memory-optout";
|
|
45
|
-
/** 带参口(`:principal`)⇒ 路由声明表(`http/route-table.ts`)里走 `pattern` 行,不是 `path` 行。 */
|
|
46
|
-
export declare const ADMIN_MEMORY_OPTOUT_OVERRIDE_RE: RegExp;
|
|
47
45
|
export declare function handleMemoryOptOut(req: IncomingMessage, res: ServerResponse, match: RouteMatch<MemoryOptOutRouteId>, ctx: RouteCtx): Promise<void>;
|
|
48
46
|
export declare const MEMORY_OPTOUT_ROUTES: readonly [{
|
|
49
47
|
readonly id: "memory-optout-grants-list";
|
|
@@ -4,7 +4,6 @@ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../pri
|
|
|
4
4
|
import { assertNever } from "../route-ctx.js";
|
|
5
5
|
import { memoryOptOutPrincipalShapeProblem } from "../../plugins/memory-optout-grant-store-sql.js";
|
|
6
6
|
export const ADMIN_MEMORY_OPTOUT_PATH = "/v1/admin/memory-optout";
|
|
7
|
-
export const ADMIN_MEMORY_OPTOUT_OVERRIDE_RE = /^\/v1\/admin\/memory-optout\/([^/]+)$/;
|
|
8
7
|
const PutBodySchema = z.object({ allow: z.boolean(), note: z.string().min(1).max(500).optional() }).strict();
|
|
9
8
|
export async function handleMemoryOptOut(req, res, match, ctx) {
|
|
10
9
|
const { deps } = ctx;
|
|
@@ -122,6 +122,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
122
122
|
subagentSteer: Boolean(deps.subagentSteerRegistry) && Boolean(deps.runStore),
|
|
123
123
|
subagentResume: Boolean(deps.subagentSteerRegistry) && Boolean(deps.runStore),
|
|
124
124
|
taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: cwdHonored(deps.config) || deviceCwdHonored(deps.config), hooks: taskHooksHonored(deps.config) },
|
|
125
|
+
settings: { permissions: { allowGrant: true } },
|
|
125
126
|
permissionModeAuto: {
|
|
126
127
|
accepted: true,
|
|
127
128
|
classifierSeat: deps.autoModeFace?.seatMounted === true,
|
|
@@ -32,6 +32,9 @@ export interface ServerWiringGates {
|
|
|
32
32
|
streamAskWindowMs: PostureKnobReading<number>;
|
|
33
33
|
sessionAutoTitle: PostureKnobReading<boolean>;
|
|
34
34
|
checkpointStore: boolean;
|
|
35
|
+
/** 7.115.0:运维的 allow 规则(`APPROVAL_GRANT`)原样 —— 空表 = 旋钮未设。规则名是工具名不是秘密,所以本面给全名单(条数 = 长度);
|
|
36
|
+
* 本位**只**在运维面(operator-only,门在路由上),不进 `/v1/capabilities` 的客户端能力位:它说的是部署替谁表了态,不是调用方能用什么。 */
|
|
37
|
+
approvalGrant: readonly string[];
|
|
35
38
|
}
|
|
36
39
|
/** `buildServerWiringGates` 的入参 = 三个谓词各自的**真输入**(不接受已算好的布尔:那样这里读到的就
|
|
37
40
|
* 不再是装配现场的事实,而是别处算完可能已漂的复述)。 */
|
|
@@ -49,6 +52,8 @@ export interface ServerWiringGateInput {
|
|
|
49
52
|
streamAskWindowMsSource: PostureSource;
|
|
50
53
|
sessionAutoTitle: boolean;
|
|
51
54
|
sessionAutoTitleSource: PostureSource;
|
|
55
|
+
/** 7.115.0:`config.approvalGrant` 本身(读面不重算)。 */
|
|
56
|
+
approvalGrant: readonly string[];
|
|
52
57
|
}
|
|
53
58
|
/** 纯数据产物(`build*`):三个门的当前读数。`streamApproval` 走**单一谓词**
|
|
54
59
|
* `resolveStreamApprovalGate` —— 与 `/v1/capabilities` 的 `streamApproval` 位、与回决口的 501 门同一个
|
|
@@ -35,6 +35,7 @@ export function buildServerWiringGates(input) {
|
|
|
35
35
|
postureMeaning: "this knob has no posture arm — its default is the same on every deployment shape",
|
|
36
36
|
}),
|
|
37
37
|
checkpointStore: input.checkpointStore !== undefined,
|
|
38
|
+
approvalGrant: [...input.approvalGrant],
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
export function buildStaticWiringAudit(facts) {
|
|
@@ -121,6 +122,7 @@ export async function handleDiagnostics(req, res, match, ctx) {
|
|
|
121
122
|
streamAskWindowMsSource: deps.config.streamAskWindowMsSource,
|
|
122
123
|
sessionAutoTitle: deps.config.sessionAutoTitle === true,
|
|
123
124
|
sessionAutoTitleSource: deps.config.sessionAutoTitleSource,
|
|
125
|
+
approvalGrant: deps.config.approvalGrant,
|
|
124
126
|
}),
|
|
125
127
|
});
|
|
126
128
|
return;
|
|
@@ -5,7 +5,7 @@ import { windowMessages, truncateMessageBlobs } from "../../audit.js";
|
|
|
5
5
|
import { redactSecrets } from "../../trace/redact.js";
|
|
6
6
|
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
7
7
|
import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.js";
|
|
8
|
-
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS,
|
|
8
|
+
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
|
|
9
9
|
import { readWritableSessionPermissionRules, describeSessionPermissionRulesProblem } from "../../session-policy-wire.js";
|
|
10
10
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders } from "../send.js";
|
|
11
11
|
import { gatedPrincipal, explicitOperatorOk, runOwnerReadable, sendPrincipalRefusal, sendUnauthorized } from "../principal-gate.js";
|
|
@@ -27,6 +27,7 @@ import { headerStr, gatedPrincipal, explicitOperatorOk } from "../principal-gate
|
|
|
27
27
|
import { failOpenTagForDroppedFrame, recordFailOpen } from "../../observability/fail-open.js";
|
|
28
28
|
import { buildSyncSubmitBody } from "../sync-submit-body.js";
|
|
29
29
|
import { assertNever } from "../route-ctx.js";
|
|
30
|
+
import { buildCheckpointCtx } from "../checkpoint-ctx.js";
|
|
30
31
|
import { withModelReachScope } from "../../model-reach-scope.js";
|
|
31
32
|
import { createInstructionSelectionLeg } from "../../instruction-selection.js";
|
|
32
33
|
const DISCONNECT_PARK_GRACE_MS = 10_000;
|
|
@@ -252,7 +253,7 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
252
253
|
let ledgerSink;
|
|
253
254
|
let ledgerTerminal = false;
|
|
254
255
|
if (deps.checkpointStore && prepared.spec.sessionId) {
|
|
255
|
-
await deps.checkpointStore.putCtx(prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope });
|
|
256
|
+
await deps.checkpointStore.putCtx(prepared.spec.sessionId, buildCheckpointCtx({ body: prepared.body, memoryScope: prepared.auth?.memoryScope }));
|
|
256
257
|
}
|
|
257
258
|
if (durableTaskId && deps.runStore) {
|
|
258
259
|
const rs = deps.runStore;
|
|
@@ -804,7 +805,7 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
804
805
|
}
|
|
805
806
|
commitSessionInheritEnv(deps, prepared.spec.sessionId, prepared.body);
|
|
806
807
|
if (deps.checkpointStore && prepared.spec.sessionId) {
|
|
807
|
-
await deps.checkpointStore.putCtx(prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope });
|
|
808
|
+
await deps.checkpointStore.putCtx(prepared.spec.sessionId, buildCheckpointCtx({ body: prepared.body, memoryScope: prepared.auth?.memoryScope }));
|
|
808
809
|
}
|
|
809
810
|
const syncCancelCtrl = createRunAbort();
|
|
810
811
|
if (prepared.spec.sessionId)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { loadConfig, type ServiceConfig } from "./config.js";
|
|
2
|
-
export { composeExecutionLane, assertExecutionLaneInvariants } from "./boot/execution-lane.js";
|
|
2
|
+
export { composeExecutionLane, assertExecutionLaneInvariants, type ExecutionLaneComposeCtx } from "./boot/execution-lane.js";
|
|
3
3
|
export { createBrain } from "./brain.js";
|
|
4
4
|
export { createSessionStore } from "./plugins/session-store.js";
|
|
5
5
|
export { createStoreBackend, type StoreBackend, type SessionCaptureRecordSource } from "./plugins/store-backend.js";
|
package/dist/leader/wire.js
CHANGED
|
@@ -6,6 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { Runner, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, delimitUntrusted, hasDestroy, } from "@sema-agent/core";
|
|
8
8
|
import { withUnattendedLeg } from "../unattended-permission-mode.js";
|
|
9
|
+
import { buildCheckpointCtx } from "../http/checkpoint-ctx.js";
|
|
9
10
|
import { RemoteContainerExecutionEnv } from "../plugins/remote-env-e2b.js";
|
|
10
11
|
import { E2B_SANDBOX_HOME } from "../execution-lane-caps.js";
|
|
11
12
|
import { createDurableAskPolicy } from "../approval.js";
|
|
@@ -267,7 +268,7 @@ export function createLeaderRunner(cfg) {
|
|
|
267
268
|
const provisionWorker = async (sub) => {
|
|
268
269
|
const sessionId = `leader-${sub.workerId}-${Date.now()}`;
|
|
269
270
|
const durable = cfg.durable?.();
|
|
270
|
-
const resumeCtx = { body: { objective: sub.spec.objective }, unattendedLeg: true };
|
|
271
|
+
const resumeCtx = buildCheckpointCtx({ body: { objective: sub.spec.objective }, unattendedLeg: true });
|
|
271
272
|
let durableSpec = {};
|
|
272
273
|
if (durable) {
|
|
273
274
|
const d = durable;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 链(第一个非空者胜,空串视作缺席):`LOCAL_DATA_ROOT` → `CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `<home>/.ai-agent`。
|
|
5
5
|
*
|
|
6
6
|
* 为什么是一只**纯函数、env 与 home 都由调用方递入**:本函数有两个读者 —— 服务自己的 `loadConfig`(递 `process.env` / `os.homedir()`)与随包发的运维脚本
|
|
7
|
-
* `scripts/
|
|
7
|
+
* `scripts/drain-check-local.mjs`(经 `dist/local-data-root.js` 取同一只函数,缺省数据根不再自抄一份链:抄本只对了一半,
|
|
8
8
|
* 设了 `AGENT_DATA_DIR` 的部署不带参数跑脚本会读到另一个目录并报「干净」)。纯叶、零反向依赖 ⇒ 脚本引它不会把整个服务装载起来。
|
|
9
9
|
*
|
|
10
10
|
* ⚠️ 与**配置根**(`configLocalRoot`:`CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `~/.ai-agent`,不吃 `LOCAL_DATA_ROOT`)不是同一条链 —— 两根分家的那一形由
|
|
@@ -19,15 +19,8 @@
|
|
|
19
19
|
* 单向钉挡不住第二种(手滑把 `"local"` 打成 `"locale"` 只会让一个合法声明被静默拒),所以两条都要。
|
|
20
20
|
*/
|
|
21
21
|
import type { ToolContentOrigin } from "@sema-agent/core";
|
|
22
|
-
/** 编译期断言:`T` 必须收敛到 `never`(有残余 ⇒ tsc 红)。 */
|
|
23
|
-
type AssertNever<T extends never> = T;
|
|
24
22
|
/** core `ToolContentOrigin` 的执法副本 —— **唯一**一份(见模块头注)。 */
|
|
25
23
|
export declare const CONTENT_ORIGIN_WORDS: readonly ["local", "execution", "external"];
|
|
26
|
-
type ContentOriginWord = (typeof CONTENT_ORIGIN_WORDS)[number];
|
|
27
|
-
type _ContentOriginCovers = AssertNever<Exclude<ToolContentOrigin, ContentOriginWord>>;
|
|
28
|
-
type _ContentOriginNoStray = AssertNever<Exclude<ContentOriginWord, ToolContentOrigin>>;
|
|
29
|
-
/** (占位使用,防 noUnusedLocals;类型别名零运行时。) */
|
|
30
|
-
export type ContentOriginWordTableGuards = [_ContentOriginCovers, _ContentOriginNoStray];
|
|
31
24
|
/**
|
|
32
25
|
* 词表内?—— 两个平面共用的**那一只**谓词。
|
|
33
26
|
*
|
|
@@ -38,5 +31,4 @@ export type ContentOriginWordTableGuards = [_ContentOriginCovers, _ContentOrigin
|
|
|
38
31
|
export declare function isContentOriginWord(v: unknown): v is ToolContentOrigin;
|
|
39
32
|
/** 坏值的**可打印**摘要(进 400 文案 / warn 元数据)——中和 + 截长,绝不把任意长度的调用方输入原样回显。 */
|
|
40
33
|
export declare function describeBadContentOrigin(v: unknown): string;
|
|
41
|
-
export {};
|
|
42
34
|
//# sourceMappingURL=mcp-content-origin.d.ts.map
|