@sema-agent/server 7.121.0 → 7.123.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 +175 -0
- package/README.md +6 -1
- package/README.zh-CN.md +6 -1
- package/USAGE.md +57 -18
- package/dist/approval-reconciler.d.ts +8 -0
- package/dist/approval-reconciler.js +72 -52
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +52 -22
- package/dist/auto-mode-face.d.ts +16 -5
- package/dist/auto-mode-face.js +7 -4
- package/dist/bench/s1/live-deps.js +1 -0
- package/dist/boot/config-center.js +3 -0
- package/dist/boot/leader.js +1 -0
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +2 -1
- package/dist/boot/resolve-spec.js +1 -4
- package/dist/boot/shutdown.js +6 -1
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/capabilities/memory-notice.d.ts +1 -1
- package/dist/config-catalog.js +4 -4
- package/dist/config-center/apply-effective.d.ts +1 -1
- package/dist/config-center/apply-effective.js +5 -3
- package/dist/config-center/center-request.d.ts +6 -9
- package/dist/config-center/center-request.js +11 -27
- package/dist/config-center/http-client.js +3 -3
- package/dist/config-center/read-warnings.d.ts +56 -35
- package/dist/config-center/read-warnings.js +67 -19
- package/dist/config-center/types.d.ts +7 -8
- package/dist/config-lkg.d.ts +12 -9
- package/dist/config-lkg.js +38 -39
- package/dist/config-provider.d.ts +3 -2
- package/dist/config-provider.js +2 -2
- package/dist/config-types.d.ts +20 -9
- package/dist/config-types.js +1 -1
- package/dist/config.d.ts +10 -10
- package/dist/config.js +92 -127
- package/dist/fleet/drain-entry.d.ts +28 -3
- package/dist/fleet/drain-entry.js +3 -0
- package/dist/fleet/engine-signal.d.ts +54 -23
- package/dist/fleet/engine-signal.js +29 -5
- package/dist/fleet/serving-lease.js +13 -15
- package/dist/fleet-client.d.ts +8 -6
- package/dist/fleet-client.js +4 -3
- package/dist/governance-overrides.d.ts +7 -2
- package/dist/http/admission.js +7 -5
- package/dist/http/approval-sweeps.js +2 -2
- package/dist/http/checkpoint-ctx.d.ts +60 -11
- package/dist/http/checkpoint-ctx.js +23 -9
- package/dist/http/dispatch.js +22 -19
- package/dist/http/health-self-report.d.ts +3 -0
- package/dist/http/resume-legs.js +8 -8
- package/dist/http/route-ctx.d.ts +19 -3
- package/dist/http/route-table.d.ts +3 -0
- package/dist/http/route-table.js +4 -0
- package/dist/http/routes/admin-drain.d.ts +5 -2
- package/dist/http/routes/admin-drain.js +14 -2
- package/dist/http/routes/approvals-assistant.d.ts +1 -0
- package/dist/http/routes/approvals-assistant.js +6 -6
- package/dist/http/routes/capabilities.js +7 -5
- package/dist/http/routes/fleet.d.ts +1 -0
- package/dist/http/routes/fleet.js +1 -1
- package/dist/http/routes/images.d.ts +1 -0
- package/dist/http/routes/images.js +1 -1
- package/dist/http/routes/leader.js +6 -0
- package/dist/http/routes/runs.d.ts +2 -0
- package/dist/http/routes/runs.js +5 -4
- package/dist/http/routes/sessions.d.ts +1 -0
- package/dist/http/routes/sessions.js +2 -5
- package/dist/http/routes/trace-usage.d.ts +1 -0
- package/dist/http/routes/trace-usage.js +1 -1
- package/dist/http/routes/workflows.d.ts +1 -0
- package/dist/http/routes/workflows.js +1 -1
- package/dist/http/send.d.ts +13 -0
- package/dist/http/send.js +9 -0
- package/dist/http/server.d.ts +3 -0
- package/dist/http/stored-body-replay.d.ts +28 -0
- package/dist/http/stored-body-replay.js +22 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/leader/leader.d.ts +23 -1
- package/dist/leader/leader.js +6 -1
- package/dist/leader/repair-wire.d.ts +1 -1
- package/dist/leader/repair-wire.js +2 -2
- package/dist/leader/run-entry.d.ts +27 -0
- package/dist/leader/run-entry.js +29 -0
- package/dist/leader/wire.d.ts +10 -0
- package/dist/leader/wire.js +15 -10
- package/dist/model-entry-refusal.d.ts +18 -9
- package/dist/model-entry-refusal.js +19 -14
- package/dist/model-route-endpoint.d.ts +21 -1
- package/dist/model-route-endpoint.js +9 -3
- package/dist/model-route-gap.d.ts +103 -0
- package/dist/model-route-gap.js +80 -0
- package/dist/model-route-url.d.ts +13 -20
- package/dist/model-route-url.js +28 -24
- package/dist/namespaced-rule-name.d.ts +16 -30
- package/dist/namespaced-rule-name.js +2 -20
- package/dist/observability/fail-open.d.ts +23 -10
- package/dist/observability/fail-open.js +8 -8
- package/dist/plugins/approval-ask-store-file.d.ts +1 -0
- package/dist/plugins/approval-ask-store-file.js +3 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +1 -0
- package/dist/plugins/approval-ask-store-memory.js +6 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +12 -0
- package/dist/plugins/approval-ask-store-sql.js +4 -0
- package/dist/plugins/one-time-migrations.js +40 -0
- package/dist/plugins/park-commit-watch.d.ts +19 -5
- package/dist/plugins/park-commit-watch.js +24 -17
- package/dist/plugins/session-policy-store-sql.js +1 -2
- package/dist/plugins/workflow-run-store-sql.d.ts +4 -2
- package/dist/router/route-orchestration.d.ts +4 -2
- package/dist/router/route-orchestration.js +2 -1
- package/dist/rule-entry-list.d.ts +3 -13
- package/dist/rule-entry-list.js +1 -12
- package/dist/run-local.js +4 -2
- package/dist/runtime-governance.d.ts +67 -32
- package/dist/runtime-governance.js +28 -9
- package/dist/session-policy-wire.d.ts +47 -49
- package/dist/session-policy-wire.js +41 -36
- package/dist/session-sync.d.ts +5 -1
- package/dist/session-sync.js +2 -1
- package/dist/sighup-idle.d.ts +7 -1
- package/dist/sighup-idle.js +4 -4
- package/dist/spec-fields.d.ts +4 -4
- package/dist/spec-fields.js +1 -4
- package/dist/task-settings.d.ts +36 -14
- package/dist/task-settings.js +20 -23
- package/dist/tool-approval.d.ts +6 -0
- package/dist/tool-approval.js +13 -1
- package/dist/trace/redact.d.ts +3 -3
- package/dist/unattended-permission-mode.d.ts +17 -13
- package/dist/unattended-permission-mode.js +3 -1
- package/dist/url-shape.d.ts +31 -0
- package/dist/url-shape.js +41 -0
- package/package.json +3 -3
- package/scripts/drain-check-local.mjs +1 -1
|
@@ -6,6 +6,7 @@ import { buildRevokeFrame } from "./approval-card.js";
|
|
|
6
6
|
import { encodeCheckpointScope } from "./security.js";
|
|
7
7
|
const RECONCILE_STORE_TIMEOUT_MS = 10_000;
|
|
8
8
|
const RECONCILE_SEGMENT_BUDGET_MS = 30_000;
|
|
9
|
+
const PARK_COMMIT_BIND_BUDGET_MS = 2_000;
|
|
9
10
|
function withDeadline(op, label, timeoutMs = RECONCILE_STORE_TIMEOUT_MS) {
|
|
10
11
|
let timer;
|
|
11
12
|
return Promise.race([
|
|
@@ -80,6 +81,13 @@ export function decideReconcileAction(input) {
|
|
|
80
81
|
}
|
|
81
82
|
export function createApprovalReconciler(deps) {
|
|
82
83
|
const { askStore, checkpoints, runs, logger, metrics, batchLimit, pendingGraceMs, adhocGraceMs, orphanTtlMs, unattendedPolicy, emitRevoke } = deps;
|
|
84
|
+
const warn = (event, fields) => {
|
|
85
|
+
try {
|
|
86
|
+
logger.warn(event, fields);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
}
|
|
90
|
+
};
|
|
83
91
|
const revoke = (row, askIds, reason, nowMs) => {
|
|
84
92
|
if (askIds.length === 0 || !emitRevoke)
|
|
85
93
|
return;
|
|
@@ -87,39 +95,32 @@ export function createApprovalReconciler(deps) {
|
|
|
87
95
|
emitRevoke(buildRevokeFrame(row.batchId, askIds, reason, nowMs, row.taskId), { owner: row.owner, sessionId: row.sessionId, taskId: row.taskId });
|
|
88
96
|
}
|
|
89
97
|
catch (err) {
|
|
90
|
-
|
|
91
|
-
logger.warn("approval_revoke_emit_failed", { batchId: row.batchId, count: askIds.length, err: err instanceof Error ? err.message : String(err) });
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
}
|
|
98
|
+
warn("approval_revoke_emit_failed", { batchId: row.batchId, count: askIds.length, err: err instanceof Error ? err.message : String(err) });
|
|
95
99
|
}
|
|
96
100
|
};
|
|
97
|
-
const
|
|
101
|
+
const bindAtGate = async (ask, nowMs, deadlineMs) => {
|
|
102
|
+
const expired = () => deadlineMs !== undefined && Date.now() >= deadlineMs;
|
|
98
103
|
const batchRow = await withDeadline(askStore.getBatch(ask.batchId), "getBatch");
|
|
99
104
|
let batchState = batchRow?.state ?? "MISSING";
|
|
100
105
|
let batchBoundAskId = batchRow?.boundAskId ?? null;
|
|
101
106
|
const irreversibleBatch = batchState === "ABORTED" || (batchState === "ROUTING_BOUND" && batchBoundAskId !== null && batchBoundAskId !== ask.askId);
|
|
102
107
|
let gateMatch;
|
|
103
|
-
if (checkpoints && !irreversibleBatch) {
|
|
108
|
+
if (checkpoints && !irreversibleBatch && !expired()) {
|
|
104
109
|
const candidates = await withDeadline(checkpoints.findCheckpointCandidatesForAsk(encodeCheckpointScope(ask.owner), ask.sessionId, ask.toolCallId, ask.createdAtMs), "findCheckpointCandidatesForAsk");
|
|
105
110
|
gateMatch = classifyGateMatch(ask, candidates);
|
|
106
111
|
if (gateMatch.kind === "hash_mismatch") {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
}
|
|
112
|
+
warn("approval_reconcile_hash_mismatch", {
|
|
113
|
+
askId: ask.askId,
|
|
114
|
+
taskId: ask.taskId,
|
|
115
|
+
toolCallId: ask.toolCallId,
|
|
116
|
+
candidates: gateMatch.candidates,
|
|
117
|
+
reason: "ask and checkpoint minted the same bound input under the same (sourceTaskId, toolCallId) identity but the digests differ — " +
|
|
118
|
+
"the known causes are deployment-side (a hook/policy mutating an object it still holds a reference to between the two mints, " +
|
|
119
|
+
"or a stateful getter returning a fresh value per read), not out-of-band interference; this ask keeps its fail-safe route (no park, no denial)",
|
|
120
|
+
});
|
|
120
121
|
}
|
|
121
122
|
const match = gateMatch.kind === "match" ? gateMatch.candidate : undefined;
|
|
122
|
-
if (match) {
|
|
123
|
+
if (match && !expired()) {
|
|
123
124
|
const res = await withDeadline(askStore.bindBatch(ask.batchId, ask.askId, {
|
|
124
125
|
gateToken: match.token,
|
|
125
126
|
gateBoundCallId: match.boundCallId,
|
|
@@ -127,12 +128,20 @@ export function createApprovalReconciler(deps) {
|
|
|
127
128
|
}), "bindBatch");
|
|
128
129
|
if (res.ok) {
|
|
129
130
|
revoke(ask, res.voidedSiblings, "superseded_by_park", nowMs);
|
|
130
|
-
return {
|
|
131
|
+
return { bound: true, match: gateMatch };
|
|
131
132
|
}
|
|
132
133
|
batchState = res.batchState;
|
|
133
134
|
batchBoundAskId = res.boundAskId ?? null;
|
|
134
135
|
}
|
|
135
136
|
}
|
|
137
|
+
return { bound: false, ...(gateMatch !== undefined ? { match: gateMatch } : {}), batchState, batchBoundAskId, irreversibleBatch };
|
|
138
|
+
};
|
|
139
|
+
const reconcileOne = async (ask, nowMs) => {
|
|
140
|
+
const attempt = await bindAtGate(ask, nowMs);
|
|
141
|
+
if (attempt.bound)
|
|
142
|
+
return { outcome: "parked", match: attempt.match };
|
|
143
|
+
const { batchState, batchBoundAskId, irreversibleBatch } = attempt;
|
|
144
|
+
const gateMatch = attempt.match;
|
|
136
145
|
const run = runs && !irreversibleBatch ? ((await withDeadline(runs.getRun(ask.taskId), "getRun")) ?? null) : null;
|
|
137
146
|
const action = decideReconcileAction({ ask, candidates: [], run, batchState, batchBoundAskId, nowMs, adhocGraceMs, orphanTtlMs, allowBind: false });
|
|
138
147
|
switch (action.kind) {
|
|
@@ -149,7 +158,7 @@ export function createApprovalReconciler(deps) {
|
|
|
149
158
|
if (!won)
|
|
150
159
|
return { outcome: "held", match: gateMatch };
|
|
151
160
|
if (action.reason === VOID_REASONS.ORPHAN_TTL_EXCEEDED) {
|
|
152
|
-
|
|
161
|
+
warn("approval_ask_orphan_voided", { askId: ask.askId, taskId: ask.taskId, ageMs: nowMs - ask.createdAtMs, ttlMs: orphanTtlMs });
|
|
153
162
|
}
|
|
154
163
|
revoke(ask, [ask.askId], action.reason === VOID_REASONS.BATCH_BOUND_ELSEWHERE ? "superseded_by_park" : "aborted", nowMs);
|
|
155
164
|
return { outcome: "voided", match: gateMatch };
|
|
@@ -160,6 +169,37 @@ export function createApprovalReconciler(deps) {
|
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
};
|
|
172
|
+
const parkCommits = deps.parkCommits;
|
|
173
|
+
const budgetMs = deps.parkCommitBindBudgetMs ?? PARK_COMMIT_BIND_BUDGET_MS;
|
|
174
|
+
const bindCommittedPark = async (calls) => {
|
|
175
|
+
const deadlineMs = Date.now() + budgetMs;
|
|
176
|
+
const work = (async () => {
|
|
177
|
+
const rows = await askStore.listParkingBySession(calls.sessionId);
|
|
178
|
+
for (const ask of rows) {
|
|
179
|
+
if (!calls.toolCallIds.includes(ask.toolCallId))
|
|
180
|
+
continue;
|
|
181
|
+
if (Date.now() >= deadlineMs) {
|
|
182
|
+
warn("approval_park_commit_bind_failed", { askId: ask.askId, taskId: ask.taskId, err: `park-commit bind budget (${budgetMs}ms) spent before this row`, fallback: "periodic_sweep" });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
if ((await bindAtGate(ask, Date.now(), deadlineMs)).bound)
|
|
187
|
+
metrics?.inc("approval_reconciled_total", { outcome: "parked" }, 1);
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
warn("approval_park_commit_bind_failed", { askId: ask.askId, taskId: ask.taskId, err: err instanceof Error ? err.message : String(err), fallback: "periodic_sweep" });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
})();
|
|
194
|
+
parkCommits?.track(work);
|
|
195
|
+
try {
|
|
196
|
+
await withDeadline(work, "bindCommittedPark", budgetMs);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
warn("approval_park_commit_bind_failed", { sessionId: calls.sessionId, err: err instanceof Error ? err.message : String(err), fallback: "periodic_sweep" });
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
parkCommits?.onParkCommitted(bindCommittedPark);
|
|
163
203
|
return {
|
|
164
204
|
async runOnce(nowMs) {
|
|
165
205
|
const stats = {
|
|
@@ -184,11 +224,7 @@ export function createApprovalReconciler(deps) {
|
|
|
184
224
|
for (const ask of parking) {
|
|
185
225
|
if (Date.now() >= parkingDeadline) {
|
|
186
226
|
stats.budgetExhausted += 1;
|
|
187
|
-
|
|
188
|
-
logger.warn("approval_reconcile_budget_exhausted", { segment: "PARKING", processed: stats.parked + stats.denied + stats.voided + stats.held + stats.failed, scanned: stats.scanned, budgetMs: RECONCILE_SEGMENT_BUDGET_MS });
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
}
|
|
227
|
+
warn("approval_reconcile_budget_exhausted", { segment: "PARKING", processed: stats.parked + stats.denied + stats.voided + stats.held + stats.failed, scanned: stats.scanned, budgetMs: RECONCILE_SEGMENT_BUDGET_MS });
|
|
192
228
|
break;
|
|
193
229
|
}
|
|
194
230
|
if (ask.boundInputHash === null)
|
|
@@ -224,11 +260,7 @@ export function createApprovalReconciler(deps) {
|
|
|
224
260
|
}
|
|
225
261
|
catch (err) {
|
|
226
262
|
stats.failed += 1;
|
|
227
|
-
|
|
228
|
-
logger.warn("approval_reconcile_row_failed", { askId: ask.askId, taskId: ask.taskId, err: err instanceof Error ? err.message : String(err) });
|
|
229
|
-
}
|
|
230
|
-
catch {
|
|
231
|
-
}
|
|
263
|
+
warn("approval_reconcile_row_failed", { askId: ask.askId, taskId: ask.taskId, err: err instanceof Error ? err.message : String(err) });
|
|
232
264
|
}
|
|
233
265
|
}
|
|
234
266
|
const pending = await withDeadline(askStore.listByState("STREAM_PENDING", batchLimit), "listByState(STREAM_PENDING)");
|
|
@@ -236,11 +268,7 @@ export function createApprovalReconciler(deps) {
|
|
|
236
268
|
for (const row of pending) {
|
|
237
269
|
if (Date.now() >= orphanDeadline) {
|
|
238
270
|
stats.budgetExhausted += 1;
|
|
239
|
-
|
|
240
|
-
logger.warn("approval_reconcile_budget_exhausted", { segment: "STREAM_PENDING", expired: stats.orphansExpired, scanned: pending.length, budgetMs: RECONCILE_SEGMENT_BUDGET_MS });
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
243
|
-
}
|
|
271
|
+
warn("approval_reconcile_budget_exhausted", { segment: "STREAM_PENDING", expired: stats.orphansExpired, scanned: pending.length, budgetMs: RECONCILE_SEGMENT_BUDGET_MS });
|
|
244
272
|
break;
|
|
245
273
|
}
|
|
246
274
|
if (row.expiresAtMs + pendingGraceMs >= nowMs)
|
|
@@ -254,23 +282,15 @@ export function createApprovalReconciler(deps) {
|
|
|
254
282
|
}
|
|
255
283
|
catch (err) {
|
|
256
284
|
stats.failed += 1;
|
|
257
|
-
|
|
258
|
-
logger.warn("approval_orphan_expire_failed", { askId: row.askId, taskId: row.taskId, policy: unattendedPolicy, err: err instanceof Error ? err.message : String(err) });
|
|
259
|
-
}
|
|
260
|
-
catch {
|
|
261
|
-
}
|
|
285
|
+
warn("approval_orphan_expire_failed", { askId: row.askId, taskId: row.taskId, policy: unattendedPolicy, err: err instanceof Error ? err.message : String(err) });
|
|
262
286
|
}
|
|
263
287
|
}
|
|
264
288
|
if (stats.unmatchableNoHash > 0) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
catch {
|
|
273
|
-
}
|
|
289
|
+
warn("approval_reconcile_rows_unmatchable", {
|
|
290
|
+
rows: stats.unmatchableNoHash,
|
|
291
|
+
scanned: stats.scanned,
|
|
292
|
+
reason: "bound_input_hash is NULL — criterion 1 (identity ∧ hash) cannot match; rows fall through to the run-terminal / adhoc / orphan arms",
|
|
293
|
+
});
|
|
274
294
|
}
|
|
275
295
|
if (stats.parked + stats.denied + stats.voided + stats.orphansExpired > 0) {
|
|
276
296
|
metrics?.inc("approval_reconciled_total", { outcome: "parked" }, stats.parked);
|
package/dist/audit.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export declare function buildSessionAuditLocal(root: string, sessionId: string):
|
|
|
48
48
|
* 🔴 **会话视图的读面口 —— `GET /v1/sessions/:id` 的全部取法(整读 / `?tail` / `?before` / `?blobCap` / `?message=N`)共用这一只**。
|
|
49
49
|
* 次序 = **选 → 脱 → 截**(路由 `http/routes/sessions.ts`):整条消息的选窗不切字符串,先选窗不改任何一条的脱敏读数、只把脱敏的量降到选中的那几条;
|
|
50
50
|
* `blobCap` 的截断会切字符串(预览切在密钥中间,半截密钥认不出来)⇒ 截断恒在脱敏之后。
|
|
51
|
-
* `messages` 缺席 ⇒ 不铸键;非数组 ⇒ 深扫;其余键(`owner` / `promptEpoch` / `model` …)
|
|
51
|
+
* `messages` 缺席 ⇒ 不铸键;非数组 ⇒ 深扫;其余键(`owner` / `promptEpoch` / `model` …)逐字。每条消息都回同一引用 ⇒ **同一引用**(带结构叶的消息恒重建,见 {@link readByTable})。
|
|
52
52
|
*/
|
|
53
53
|
export declare function redactSessionViewForRead<V extends object>(view: V): V;
|
|
54
54
|
/** Window `messages` — `tail=N` (last N) or `before=<offset>&limit=N` ([max(0,before-N), before)).
|
package/dist/audit.js
CHANGED
|
@@ -45,48 +45,78 @@ async function auditFromStorage(storage, sessionId) {
|
|
|
45
45
|
}
|
|
46
46
|
const deepScrub = (v) => redactDeepWith(v, redactModelText);
|
|
47
47
|
const textSlot = (v) => (typeof v === "string" ? redactModelText(v) : deepScrub(v));
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
function readLeaf(treatment, v) {
|
|
49
|
+
switch (treatment) {
|
|
50
|
+
case "text": return textSlot(v);
|
|
51
|
+
case "structure": return deepScrub(v);
|
|
52
|
+
case "parts": return readParts(v);
|
|
53
|
+
case "verbatim": return v;
|
|
54
|
+
default: {
|
|
55
|
+
const unhandled = treatment;
|
|
56
|
+
throw new Error(`unhandled leaf treatment: ${String(unhandled)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const isPlainRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
61
|
+
function readByTable(node, table) {
|
|
62
|
+
let changed = false;
|
|
63
|
+
const entries = Object.entries(node).map(([key, v]) => {
|
|
64
|
+
const next = readLeaf(Object.hasOwn(table, key) ? table[key] : "structure", v);
|
|
65
|
+
if (next !== v)
|
|
66
|
+
changed = true;
|
|
67
|
+
return [key, next];
|
|
68
|
+
});
|
|
69
|
+
return changed ? Object.fromEntries(entries) : node;
|
|
54
70
|
}
|
|
55
71
|
const PART_READ_TREATMENT = {
|
|
56
|
-
text:
|
|
57
|
-
thinking:
|
|
58
|
-
toolCall:
|
|
59
|
-
image:
|
|
60
|
-
document:
|
|
72
|
+
text: { type: "verbatim", text: "text" },
|
|
73
|
+
thinking: { type: "verbatim", thinking: "text", thinkingSignature: "verbatim", redacted: "verbatim" },
|
|
74
|
+
toolCall: { type: "verbatim", id: "verbatim", wireId: "verbatim", name: "verbatim", arguments: "structure", executionMode: "verbatim" },
|
|
75
|
+
image: { type: "verbatim", data: "verbatim", mimeType: "verbatim" },
|
|
76
|
+
document: { type: "verbatim", data: "verbatim", mimeType: "verbatim" },
|
|
61
77
|
};
|
|
62
78
|
function readParts(content) {
|
|
63
79
|
if (!Array.isArray(content))
|
|
64
80
|
return textSlot(content);
|
|
65
81
|
const next = content.map((part) => {
|
|
66
|
-
if (
|
|
82
|
+
if (isPlainRecord(part)) {
|
|
67
83
|
const type = part.type;
|
|
68
84
|
if (typeof type === "string" && Object.hasOwn(PART_READ_TREATMENT, type)) {
|
|
69
|
-
return PART_READ_TREATMENT[type]
|
|
85
|
+
return readByTable(part, PART_READ_TREATMENT[type]);
|
|
70
86
|
}
|
|
71
87
|
}
|
|
72
88
|
return deepScrub(part);
|
|
73
89
|
});
|
|
74
90
|
return next.every((x, i) => x === content[i]) ? content : next;
|
|
75
91
|
}
|
|
76
|
-
const readContent = (m) => scrubSlot(m, "content", readParts);
|
|
77
92
|
const MESSAGE_READ_TREATMENT = {
|
|
78
|
-
user:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
user: {
|
|
94
|
+
role: "verbatim", content: "verbatim", timestamp: "verbatim", enginePrefixChars: "verbatim", engineSegments: "verbatim",
|
|
95
|
+
engineMinted: "verbatim", engineNoteKey: "verbatim", provenance: "verbatim", actor: "verbatim",
|
|
96
|
+
},
|
|
97
|
+
custom: { role: "verbatim", customType: "verbatim", content: "verbatim", display: "verbatim", details: "verbatim", provenance: "verbatim", timestamp: "verbatim" },
|
|
98
|
+
assistant: {
|
|
99
|
+
role: "verbatim", content: "parts", errorMessage: "text", diagnostics: "structure", repetition: "structure",
|
|
100
|
+
api: "verbatim", provider: "verbatim", model: "verbatim", responseModel: "verbatim", responseId: "verbatim", requestId: "verbatim",
|
|
101
|
+
isApiErrorMessage: "verbatim", apiErrorStatus: "verbatim", apiErrorCode: "verbatim", usage: "verbatim", usageMissing: "verbatim",
|
|
102
|
+
stopReason: "verbatim", errorKind: "verbatim", inputTooLongRuledOut: "verbatim", partialFinalized: "verbatim",
|
|
103
|
+
staticReasoningCut: "verbatim", timestamp: "verbatim",
|
|
104
|
+
},
|
|
105
|
+
toolResult: { role: "verbatim", toolCallId: "verbatim", toolName: "verbatim", content: "parts", details: "structure", isError: "verbatim", timestamp: "verbatim" },
|
|
106
|
+
bashExecution: {
|
|
107
|
+
role: "verbatim", command: "text", output: "text", exitCode: "verbatim", cancelled: "verbatim", truncated: "verbatim",
|
|
108
|
+
fullOutputPath: "verbatim", timestamp: "verbatim", excludeFromContext: "verbatim",
|
|
109
|
+
},
|
|
110
|
+
compactionSummary: {
|
|
111
|
+
role: "verbatim", summary: "text", details: "structure", tokensBefore: "verbatim", timestamp: "verbatim", elidedMessages: "verbatim",
|
|
112
|
+
unsummarizedMessages: "verbatim", tokensAfter: "verbatim", firstKeptEntryId: "verbatim",
|
|
113
|
+
},
|
|
84
114
|
};
|
|
85
115
|
function readMessage(m) {
|
|
86
|
-
if (
|
|
116
|
+
if (isPlainRecord(m)) {
|
|
87
117
|
const role = m.role;
|
|
88
118
|
if (typeof role === "string" && Object.hasOwn(MESSAGE_READ_TREATMENT, role)) {
|
|
89
|
-
return MESSAGE_READ_TREATMENT[role]
|
|
119
|
+
return readByTable(m, MESSAGE_READ_TREATMENT[role]);
|
|
90
120
|
}
|
|
91
121
|
}
|
|
92
122
|
return deepScrub(m);
|
package/dist/auto-mode-face.d.ts
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
*
|
|
16
16
|
* 本模块按**server 侧可知的同三项**算一份「若此刻提交会不会武装」的裁决,供壳(cli `sema doctor permissions`)
|
|
17
17
|
* 与人自查。三项分别对应三个未武装原因(判序 = 谓词的合取序,先判到的先报):
|
|
18
|
-
* · `mode_not_auto` —— 本次意图不是 auto(`?permissionMode=` 查询串给的;缺席 = 按 auto 意图答);
|
|
18
|
+
* · `mode_not_auto` —— 本次意图不是 auto(`?permissionMode=` 查询串给的;缺席 = 按 auto 意图答),且原样到得了引擎(本部署的入口地板没改它);
|
|
19
|
+
* · `operator_floor` —— 本部署的运维入口地板(`AUTONOMY=plan`)把本次意图改成了别的词、那个词不是 auto(`runtime-governance.ts`
|
|
20
|
+
* `entryPermissionMode` 的 `clamped`,与受理门同一只函数)—— 到得了引擎的词由运维定,出路在运维换档,不在请求的词;
|
|
19
21
|
* · `deployment_incapable` —— 装配面没挂 `RunnerDeps.autoMode` 分类器席位(生产装配恒挂;桩/一次性 CLI 不挂);
|
|
20
22
|
* · `org_denied` —— per-principal caps 的 `autoMode === false` 且本地 deny 未设(center 显式 deny、center
|
|
21
23
|
* 硬失败的 fail-closed 拒项);
|
|
@@ -34,7 +36,8 @@
|
|
|
34
36
|
* 规则集少一整支;**不再读任何 dist 文本**。
|
|
35
37
|
*
|
|
36
38
|
* ## 闭集纪律([ref])
|
|
37
|
-
*
|
|
39
|
+
* 七词:core [ref] seam ⑤ + [ref] PM 裁定的六词 + `operator_floor`(意图被运维入口地板改词;与 `local_denied` / `org_denied` 同理按「指路不同」拆出);
|
|
40
|
+
* `AUTO_MODE_UNARMED_REASON_SEMANTICS` 是 `Record<Reason, string>` 锁步表——
|
|
38
41
|
* 词表加员而语义表不加、或反之,都是编译红。本面**不**给壳发 prose(机器码上 wire,人话归壳),语义表只
|
|
39
42
|
* 服务于文档/测试对拍。
|
|
40
43
|
*
|
|
@@ -75,12 +78,13 @@ export type ClassifierRoute = {
|
|
|
75
78
|
readonly id: undefined;
|
|
76
79
|
readonly fault: string;
|
|
77
80
|
};
|
|
78
|
-
/** core [ref] seam ⑤
|
|
79
|
-
export declare const AUTO_MODE_UNARMED_REASONS: readonly ["mode_not_auto", "deployment_incapable", "org_denied", "local_denied", "settings_denied", "resolver_fault"];
|
|
81
|
+
/** 未武装原因闭集(七词:core [ref] seam ⑤ + [ref] settings_denied + 入口地板改词 operator_floor;顺序 = 判序)。 */
|
|
82
|
+
export declare const AUTO_MODE_UNARMED_REASONS: readonly ["mode_not_auto", "operator_floor", "deployment_incapable", "org_denied", "local_denied", "settings_denied", "resolver_fault"];
|
|
80
83
|
export type AutoModeUnarmedReason = (typeof AUTO_MODE_UNARMED_REASONS)[number];
|
|
81
84
|
/** 锁步表:词表与语义一一对应(加员/删员任一侧不同步 = tsc 红)。语义只供文档/测试,不上 wire。 */
|
|
82
85
|
export declare const AUTO_MODE_UNARMED_REASON_SEMANTICS: {
|
|
83
|
-
readonly mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\")";
|
|
86
|
+
readonly mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\"),且原样到得了引擎(本部署的运维入口地板没改它)—— 出路是请求 auto";
|
|
87
|
+
readonly operator_floor: "本部署的运维入口地板(AUTONOMY=plan)把本次意图改成了别的词、那个词不是 auto —— 与受理门同一只函数,提交时到得了引擎的词由运维定;出路是找运维换档,改请求的词到不了 auto";
|
|
84
88
|
readonly deployment_incapable: "装配面没挂 RunnerDeps.autoMode 分类器席位(本部署/本进程形不具备分类器)";
|
|
85
89
|
readonly org_denied: "per-principal caps.autoMode === false 且本地 deny 未设:center 显式 deny / center 硬失败 fail-closed 拒项";
|
|
86
90
|
readonly local_denied: "本地 catalog 键 PERMISSIONS_DISABLE_AUTO_MODE=true(center/settings 键名 permissions.disableAutoMode)把 caps.autoMode 折成 false";
|
|
@@ -118,11 +122,18 @@ export type AutoModeVerdict = {
|
|
|
118
122
|
* 三项合取的 server 侧裁决。`requestedMode` 缺席 = 按 auto 意图答(「若请求会不会武装」);`principal` =
|
|
119
123
|
* 调用方经门验证的身份(匿名 ⇒ undefined ⇒ center 客户端答 undefined,本地 deny 层照样罩)。
|
|
120
124
|
*
|
|
125
|
+
* 意图那一项判的是**提交之后到得了引擎的那个词**:本次意图先过本部署的运维入口地板(`floor`,调用方传 `runtime-governance.ts`
|
|
126
|
+
* `autonomyModeFloor(<活 config 的 AUTONOMY>)` —— 与受理门盖章读同一只属主),经 `entryPermissionMode`(受理门盖章调的同一只函数)。
|
|
127
|
+
* 到得了引擎的词不是 auto ⇒ 未武装,原因按「谁定了那个词」分:地板改了词(`clamped`)⇒ `operator_floor`;词原样 ⇒ `mode_not_auto`。
|
|
128
|
+
* `AUTONOMY=plan` 部署上意图缺席 / auto / 任何比 plan 松的词 ⇒ `operator_floor`;意图本身是 `plan` ⇒ `mode_not_auto`。
|
|
129
|
+
*
|
|
121
130
|
* 🔴 resolver 抛错**不吞**:合成腿的每一层都自带 fail-closed/缺席臂(center 客户端 catch-all、grant 腿 onFault),
|
|
122
131
|
* 走到这里还在抛 = 装配缺陷,读面 500 比一句编出来的 reason 诚实。
|
|
123
132
|
*/
|
|
124
133
|
export declare function judgeAutoModeArming(face: AutoModeFaceDeps | undefined, input: {
|
|
125
134
|
readonly requestedMode: PermissionMode | undefined;
|
|
135
|
+
/** 本部署此刻的运维入口地板(`autonomyModeFloor(config.autonomy)`;`undefined` = 没有地板)。必填:不传地板不是「没有地板」的合法拼法。 */
|
|
136
|
+
readonly floor: PermissionMode | undefined;
|
|
126
137
|
readonly principal: string | undefined;
|
|
127
138
|
/** 本地 `PERMISSIONS_DISABLE_AUTO_MODE`(= catalog/settings 键 `permissions.disableAutoMode`)是否置 true —— caps false 时分词
|
|
128
139
|
* `local_denied` / `org_denied`(seam ⑤;两者对壳的指路不同:改本机 env vs 找组织)。 */
|
package/dist/auto-mode-face.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { classifierSeatModel } from "@sema-agent/core";
|
|
2
2
|
import { resolveMainModel } from "./model-select.js";
|
|
3
|
+
import { entryPermissionMode } from "./runtime-governance.js";
|
|
3
4
|
export function classifierRouteFor(deps) {
|
|
4
5
|
try {
|
|
5
6
|
const main = resolveMainModel(deps);
|
|
@@ -9,9 +10,10 @@ export function classifierRouteFor(deps) {
|
|
|
9
10
|
return { id: undefined, fault: err instanceof Error ? err.message : String(err) };
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
|
-
export const AUTO_MODE_UNARMED_REASONS = ["mode_not_auto", "deployment_incapable", "org_denied", "local_denied", "settings_denied", "resolver_fault"];
|
|
13
|
+
export const AUTO_MODE_UNARMED_REASONS = ["mode_not_auto", "operator_floor", "deployment_incapable", "org_denied", "local_denied", "settings_denied", "resolver_fault"];
|
|
13
14
|
export const AUTO_MODE_UNARMED_REASON_SEMANTICS = {
|
|
14
|
-
mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\")",
|
|
15
|
+
mode_not_auto: "本次意图不是 auto(壳发的 permissionMode 不是 \"auto\"),且原样到得了引擎(本部署的运维入口地板没改它)—— 出路是请求 auto",
|
|
16
|
+
operator_floor: "本部署的运维入口地板(AUTONOMY=plan)把本次意图改成了别的词、那个词不是 auto —— 与受理门同一只函数,提交时到得了引擎的词由运维定;出路是找运维换档,改请求的词到不了 auto",
|
|
15
17
|
deployment_incapable: "装配面没挂 RunnerDeps.autoMode 分类器席位(本部署/本进程形不具备分类器)",
|
|
16
18
|
org_denied: "per-principal caps.autoMode === false 且本地 deny 未设:center 显式 deny / center 硬失败 fail-closed 拒项",
|
|
17
19
|
local_denied: "本地 catalog 键 PERMISSIONS_DISABLE_AUTO_MODE=true(center/settings 键名 permissions.disableAutoMode)把 caps.autoMode 折成 false",
|
|
@@ -21,8 +23,9 @@ export const AUTO_MODE_UNARMED_REASON_SEMANTICS = {
|
|
|
21
23
|
export async function judgeAutoModeArming(face, input) {
|
|
22
24
|
const route = face?.seatMounted ? face.classifierModel?.() : undefined;
|
|
23
25
|
const model = route !== undefined ? { model: route } : {};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
const entry = entryPermissionMode(input.requestedMode ?? "auto", input.floor);
|
|
27
|
+
if (entry.effective !== "auto")
|
|
28
|
+
return { armed: false, reason: entry.clamped ? "operator_floor" : "mode_not_auto", ...model };
|
|
26
29
|
if (face === undefined || !face.seatMounted)
|
|
27
30
|
return { armed: false, reason: "deployment_incapable" };
|
|
28
31
|
let caps;
|
|
@@ -370,6 +370,7 @@ async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId,
|
|
|
370
370
|
process.env.LEADER_WORKER_BUDGET_USD = String((rt.maxCostUsd ?? 1.0) / 2);
|
|
371
371
|
const run = createLeaderRunner({
|
|
372
372
|
brain, models, roles, pricing, e2bApiKey: rt.e2bApiKey, workspace: W,
|
|
373
|
+
autonomy: () => undefined,
|
|
373
374
|
...(s1DebugGit() ? { logger: { warn: (m, x) => console.error(`[S1_DEBUG_LEADER warn] ${m}`, x ? JSON.stringify(x).slice(0, 300) : ""), info: (m, x) => console.error(`[S1_DEBUG_LEADER info] ${m}`, x ? JSON.stringify(x).slice(0, 200) : "") } } : {}),
|
|
374
375
|
});
|
|
375
376
|
const teamSeedWrite = (trap.seedFiles ?? [])
|
|
@@ -133,6 +133,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
133
133
|
logger.warn("config_entry_dropped", {
|
|
134
134
|
domain: d.domain,
|
|
135
135
|
entry: d.entry,
|
|
136
|
+
reasonCode: d.reasonCode,
|
|
136
137
|
error: d.error,
|
|
137
138
|
version: r.effective.version,
|
|
138
139
|
phase,
|
|
@@ -419,6 +420,8 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
419
420
|
logger.warn("config_lkg_unusable", { path: lkgPath, reason: "center credential identity changed while the LKG was being read — not used", note: "env fallback serves; the current identity's next pull re-establishes its own LKG" });
|
|
420
421
|
return null;
|
|
421
422
|
}
|
|
423
|
+
if (read.migrated !== undefined)
|
|
424
|
+
logger.info("config_lkg_migrated", { to: lkgPath, note: read.migrated });
|
|
422
425
|
logger.info("config_loaded_lkg", { path: lkgPath, savedAt: read.lkg.savedAt, why, scope, note: lkgReadNote(scope) });
|
|
423
426
|
const lkgEffective = read.lkg.effective;
|
|
424
427
|
candidateIdentity.set(lkgEffective, identity);
|
package/dist/boot/leader.js
CHANGED
|
@@ -51,6 +51,7 @@ export function createLeaderFace(ctx) {
|
|
|
51
51
|
retentionPolicy: governanceSeams.retentionPolicy,
|
|
52
52
|
},
|
|
53
53
|
deploymentPosture: () => buildDeploymentPostureSeats(config),
|
|
54
|
+
autonomy: () => config.autonomy,
|
|
54
55
|
...(ctx.tracer ? { tracer: ctx.tracer } : {}),
|
|
55
56
|
...(config.usageWindows && ctx.usageWindowStore
|
|
56
57
|
? { usageWindows: config.usageWindows, usageWindowStore: ctx.usageWindowStore }
|
package/dist/boot/reapers.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ import type { TaskAttachmentStore } from "../plugins/task-attachment-store.js";
|
|
|
24
24
|
import type { FileHistoryErasureFace } from "../file-history-capture.js";
|
|
25
25
|
import type { ApprovalNonceStore, CheckpointStoreFull, CostQuotaStore, ImageBake, RateLimiterStore, ServiceWorkflowJournalStore, StoreBackend, ToolResultStoreFull } from "../plugins/store-backend.js";
|
|
26
26
|
import type { ToolApprovalCoordinator } from "../tool-approval.js";
|
|
27
|
+
import type { ParkCommitWatch } from "../plugins/park-commit-watch.js";
|
|
27
28
|
/** [ref] / [ref]§五(sema-comms `audits/failopen-governance-176.md` §7 "Z 类 1 条"):default consecutive-failure
|
|
28
29
|
* count before a reaper leg's swallowed error gets a `warn` line. The ~15 `.catch(() => undefined)` sites
|
|
29
30
|
* below are correct in DIRECTION (a hiccuping periodic sweep must never crash the whole tick loop — one bad
|
|
@@ -68,6 +69,8 @@ export interface ReapersCtx {
|
|
|
68
69
|
subRunner: Runner;
|
|
69
70
|
runStore: ReturnType<StoreBackend["run"]> | undefined;
|
|
70
71
|
checkpointStore: CheckpointStoreFull | undefined;
|
|
72
|
+
/** S-1280:checkpoint 店的 park 提交观察层(stage-06 与店同一处铸;在场 ⇔ 有 checkpoint 店)。对账收敛器订阅它,在停车行提交那一拍收口 PARKING。 */
|
|
73
|
+
parkCommits: ParkCommitWatch | undefined;
|
|
71
74
|
rateLimiter: RateLimiter | RateLimiterStore | undefined;
|
|
72
75
|
costQuota: CostQuota | CostQuotaStore | undefined;
|
|
73
76
|
toolResultStore: ToolResultStoreFull | undefined;
|
package/dist/boot/reapers.js
CHANGED
|
@@ -56,7 +56,7 @@ export function createPlacementAwareReapSessions(deps) {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
export function startReapers(ctx) {
|
|
59
|
-
const { config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, rateLimiter, costQuota, toolResultStore, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap, workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore, backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore, getRunDenySweep, userInputLedger, } = ctx;
|
|
59
|
+
const { config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, parkCommits, rateLimiter, costQuota, toolResultStore, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap, workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore, backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore, getRunDenySweep, userInputLedger, } = ctx;
|
|
60
60
|
const leg = (p) => {
|
|
61
61
|
if (p !== undefined)
|
|
62
62
|
trackMaintenanceLeg(p);
|
|
@@ -122,6 +122,7 @@ export function startReapers(ctx) {
|
|
|
122
122
|
orphanTtlMs: config.streamApproval.orphanTtlMs,
|
|
123
123
|
unattendedPolicy: config.unattendedApprovalPolicy,
|
|
124
124
|
...(toolApproval ? { emitRevoke: (frame, target) => void toolApproval.emitRevoke(frame, target) } : {}),
|
|
125
|
+
parkCommits,
|
|
125
126
|
});
|
|
126
127
|
})();
|
|
127
128
|
const reapSessions = createPlacementAwareReapSessions({ host: backend?.session(), routed: subRunner.sessions });
|
|
@@ -26,7 +26,7 @@ import { resourceSuspendOptIn } from "../resource-suspend.js";
|
|
|
26
26
|
import { isIsolatedExecEnv, routeServiceTask, supPostureOverrides } from "../router/route-orchestration.js";
|
|
27
27
|
import { tightenGoverned } from "../governance-overrides.js";
|
|
28
28
|
import { gateExecutionLane } from "../runtime-caps-resolver.js";
|
|
29
|
-
import { applyRuntimeGovernance
|
|
29
|
+
import { applyRuntimeGovernance } from "../runtime-governance.js";
|
|
30
30
|
import { HttpError, createCodedHttpError, encodeCheckpointScope } from "../security.js";
|
|
31
31
|
import { memoryProjectKeyFor, memorySpecForRequest } from "../memory-scope.js";
|
|
32
32
|
import { inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
|
|
@@ -599,9 +599,6 @@ export function createResolveSpec(ctx) {
|
|
|
599
599
|
throw new HttpError(422, `request settings cannot be applied as written: ${e.message}`);
|
|
600
600
|
}
|
|
601
601
|
}
|
|
602
|
-
if (governed.handsReadOnly === true && Array.isArray(governed.tools)) {
|
|
603
|
-
governed = { ...governed, tools: stripDelegationTools(governed.tools) };
|
|
604
|
-
}
|
|
605
602
|
if (shouldDiscloseNoPersistentMemory({
|
|
606
603
|
memoryFaceMounted: memoryFaceMounted(governed.memory),
|
|
607
604
|
declaredCapable: effectiveMemoryPersistenceCapable(config),
|
package/dist/boot/shutdown.js
CHANGED
|
@@ -8,7 +8,7 @@ import { installProcessExitBoundary } from "./exit-boundary.js";
|
|
|
8
8
|
import { createEngineLeaseTracker } from "./engine-lease.js";
|
|
9
9
|
import { writeExitLast, readExitLast, clearExitLast } from "./exit-last.js";
|
|
10
10
|
import { isScriptRealmRejection, describeRejectionReason } from "../orchestration/hardened-vm-runner.js";
|
|
11
|
-
import { DRAIN_CAUSE_COUNTS_AS_FIRST_STOP_SIGNAL, HARD_STOP_ACTION_ID } from "../fleet/drain-entry.js";
|
|
11
|
+
import { DRAIN_CAUSE_COUNTS_AS_FIRST_STOP_SIGNAL, HARD_STOP_ACTION_ID, drainDeadlineBeyondBound } from "../fleet/drain-entry.js";
|
|
12
12
|
const HARD_STOP_GRACE_MS = 10_000;
|
|
13
13
|
const FINAL_EXIT_MS = 15_000;
|
|
14
14
|
export function backgroundGraceRemainingMs(a) {
|
|
@@ -140,6 +140,7 @@ export function installShutdownHandlers(ctx) {
|
|
|
140
140
|
});
|
|
141
141
|
const finalExit = setTimeout(() => { logger.warn("hard_shutdown_final_deadline", { afterMs: FINAL_EXIT_MS }); process.exit(0); }, FINAL_EXIT_MS);
|
|
142
142
|
finalExit.unref?.();
|
|
143
|
+
drainState.endSubscriptions?.();
|
|
143
144
|
server.closeIdleConnections?.();
|
|
144
145
|
const forceClose = setTimeout(() => server.closeAllConnections?.(), HARD_STOP_GRACE_MS);
|
|
145
146
|
forceClose.unref?.();
|
|
@@ -154,6 +155,8 @@ export function installShutdownHandlers(ctx) {
|
|
|
154
155
|
return { ...current, state: "already_draining" };
|
|
155
156
|
if (closing)
|
|
156
157
|
return { actionId: HARD_STOP_ACTION_ID, since: closingAt, deadlineAt: closingAt + FINAL_EXIT_MS, state: "already_draining" };
|
|
158
|
+
if (entry.deadlineBoundMs !== undefined && drainDeadlineBeyondBound(entry.deadlineAt, at, entry.deadlineBoundMs))
|
|
159
|
+
return { state: "deadline_beyond_bound", boundMs: entry.deadlineBoundMs };
|
|
157
160
|
draining = true;
|
|
158
161
|
drainStartedAt = performance.now();
|
|
159
162
|
firstStopSignalAt = bySignal ? drainStartedAt : undefined;
|
|
@@ -167,6 +170,7 @@ export function installShutdownHandlers(ctx) {
|
|
|
167
170
|
const deadlineMono = drainStartedAt + windowMs;
|
|
168
171
|
deviceHub?.beginDrain({ graceMs: () => windowMs });
|
|
169
172
|
drainState.yieldInflight?.();
|
|
173
|
+
drainState.endSubscriptions?.();
|
|
170
174
|
toolApproval?.beginDrain();
|
|
171
175
|
void fleetClient?.announceNow();
|
|
172
176
|
const inflight = drainState.inflight?.() ?? 0;
|
|
@@ -234,6 +238,7 @@ export function installShutdownHandlers(ctx) {
|
|
|
234
238
|
process.on("SIGINT", onSigint);
|
|
235
239
|
const onSighup = createSighupIdleHandler({
|
|
236
240
|
inflight: () => drainState.inflight?.() ?? 0,
|
|
241
|
+
attached: () => drainState.attachedStreams?.() ?? 0,
|
|
237
242
|
lastActivityAt: () => drainState.lastActivityAt?.() ?? 0,
|
|
238
243
|
isStopped: () => closing || draining,
|
|
239
244
|
escalate: () => drainOnCause("signal:SIGHUP"),
|
|
@@ -5,9 +5,9 @@ import { startRetentionLane, RETENTION_LEASE_TTL_FACTOR } from "./retention-lane
|
|
|
5
5
|
import { createLimitSync } from "./limit-sync.js";
|
|
6
6
|
import { runDenySweep } from "./late-binds.js";
|
|
7
7
|
export function runStage08(ctx) {
|
|
8
|
-
const { approvalNonceStore, backend, backgroundAgentStore, ceilingVerdict, checkpointStore, config, configCenter, consolidationSeatWatch, costQuota, degradeRouteWatch, fileHistoryErasure, handslessRunner, handslessSubRunner, hookAgentRunner, imageBakes, instanceId, localRoot, logger, mailboxStore, metrics, permissionRuleStores, pricing, rateLimiter, retentionExecutor, retentionLaneStore, rosterStore, runStore, runner, sqlWorkflowRunStore, subRunner, taskAttachmentStore, toolApproval, toolResultStore, userInputLedger, workflowJournalStore, workflowNotifyGate, workflowNotifyJournal, workflowRecoverOpts, worktreeReap } = ctx;
|
|
8
|
+
const { approvalNonceStore, backend, backgroundAgentStore, ceilingVerdict, checkpointStore, parkCommits, config, configCenter, consolidationSeatWatch, costQuota, degradeRouteWatch, fileHistoryErasure, handslessRunner, handslessSubRunner, hookAgentRunner, imageBakes, instanceId, localRoot, logger, mailboxStore, metrics, permissionRuleStores, pricing, rateLimiter, retentionExecutor, retentionLaneStore, rosterStore, runStore, runner, sqlWorkflowRunStore, subRunner, taskAttachmentStore, toolApproval, toolResultStore, userInputLedger, workflowJournalStore, workflowNotifyGate, workflowNotifyJournal, workflowRecoverOpts, worktreeReap } = ctx;
|
|
9
9
|
const reaper = startReapers({
|
|
10
|
-
config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore,
|
|
10
|
+
config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, parkCommits,
|
|
11
11
|
rateLimiter, costQuota, toolResultStore, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap,
|
|
12
12
|
workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore,
|
|
13
13
|
backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore, userInputLedger,
|
|
@@ -67,7 +67,7 @@ export interface MemoryFaceFacts {
|
|
|
67
67
|
/** 本请求命中场景的手带表态([ref] 判别位)。`none` = 该场景跑在不挂 `executionEnvFactory` 的 Runner 上,
|
|
68
68
|
* core 的手带 band(Bash/Edit/Write/…)整批不 mount。 */
|
|
69
69
|
hands: ScenarioHands;
|
|
70
|
-
/** 折完 governance+settings 后的最终 `handsReadOnly`(plan
|
|
70
|
+
/** 折完 governance+settings 后的最终 `handsReadOnly`(`AUTONOMY=read-only` 收窄;plan 模式不点亮它 —— plan 的手带照挂、调用时由引擎问)。手带只剩读腿。 */
|
|
71
71
|
handsReadOnly: boolean;
|
|
72
72
|
/** 折完之后的最终 `TaskSpec.excludeAllTools`(S-493;core 7.8.0 [ref])。`true` = **整只**工具面卸空
|
|
73
73
|
* (core `prepareDeferClassify` 对它直接 `roster.unmountWhere(() => true)`)⇒ 手带在场也一条落盘路都不剩。
|