@sema-agent/server 7.120.0 → 7.122.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 +173 -0
- package/README.md +1 -1
- package/README.zh-CN.md +3 -1
- package/USAGE.md +97 -29
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -2
- package/deploy/sema-up/chart/templates/server.yaml +2 -2
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +1 -1
- package/dist/approval-reconciler.d.ts +8 -0
- package/dist/approval-reconciler.js +72 -52
- package/dist/auto-mode-face.d.ts +9 -2
- package/dist/auto-mode-face.js +5 -3
- package/dist/bench/s1/live-deps.js +1 -0
- package/dist/boot/config-center.d.ts +5 -7
- package/dist/boot/config-center.js +33 -9
- package/dist/boot/engine-lease.d.ts +1 -1
- 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 +2 -4
- package/dist/boot/shutdown.d.ts +9 -10
- package/dist/boot/shutdown.js +59 -16
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -9
- package/dist/boot/stage-10-http-server.d.ts +1 -9
- package/dist/boot/stage-10-http-server.js +31 -8
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/memory-notice.d.ts +1 -1
- package/dist/config-catalog.js +12 -6
- package/dist/config-center/center-request.d.ts +13 -1
- package/dist/config-center/center-request.js +25 -10
- package/dist/config-types.d.ts +44 -5
- package/dist/config-types.js +6 -0
- package/dist/config.d.ts +35 -6
- package/dist/config.js +113 -33
- package/dist/fleet/drain-entry.d.ts +112 -0
- package/dist/fleet/drain-entry.js +38 -0
- package/dist/fleet/engine-signal.d.ts +144 -0
- package/dist/fleet/engine-signal.js +60 -0
- package/dist/fleet/serving-lease.d.ts +47 -0
- package/dist/fleet/serving-lease.js +233 -0
- package/dist/fleet-client.d.ts +35 -19
- package/dist/fleet-client.js +49 -25
- package/dist/governance-overrides.d.ts +7 -2
- package/dist/http/admission.js +7 -5
- package/dist/http/approval-sweeps.js +5 -7
- package/dist/http/checkpoint-ctx.d.ts +60 -11
- package/dist/http/checkpoint-ctx.js +23 -9
- package/dist/http/dispatch.js +33 -16
- package/dist/http/health-self-report.d.ts +60 -0
- package/dist/http/health-self-report.js +53 -0
- package/dist/http/principal-gate.d.ts +8 -1
- package/dist/http/principal-gate.js +1 -0
- package/dist/http/readiness.d.ts +25 -5
- package/dist/http/readiness.js +31 -6
- package/dist/http/resume-legs.js +9 -9
- package/dist/http/route-ctx.d.ts +25 -7
- package/dist/http/route-table.d.ts +6 -0
- package/dist/http/route-table.js +8 -1
- package/dist/http/routes/a2a-serve.d.ts +1 -1
- package/dist/http/routes/a2a-serve.js +5 -5
- package/dist/http/routes/admin-drain.d.ts +15 -31
- package/dist/http/routes/admin-drain.js +28 -10
- package/dist/http/routes/approvals-assistant.d.ts +4 -3
- package/dist/http/routes/approvals-assistant.js +9 -9
- package/dist/http/routes/capabilities.js +7 -5
- package/dist/http/routes/devices.js +3 -2
- 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.d.ts +1 -1
- package/dist/http/routes/leader.js +7 -1
- package/dist/http/routes/memory-consolidation.d.ts +1 -1
- package/dist/http/routes/memory-consolidation.js +1 -1
- package/dist/http/routes/notify-wake.d.ts +1 -1
- package/dist/http/routes/notify-wake.js +1 -1
- package/dist/http/routes/rules.js +4 -3
- package/dist/http/routes/runs.d.ts +4 -2
- package/dist/http/routes/runs.js +7 -6
- package/dist/http/routes/sessions.d.ts +1 -0
- package/dist/http/routes/sessions.js +1 -1
- package/dist/http/routes/side-query.d.ts +1 -1
- package/dist/http/routes/side-query.js +1 -1
- package/dist/http/routes/tasks.d.ts +2 -2
- package/dist/http/routes/tasks.js +3 -3
- 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 +10 -21
- package/dist/http/server.js +20 -1
- package/dist/http/stored-body-replay.d.ts +27 -0
- package/dist/http/stored-body-replay.js +23 -0
- package/dist/http/wire-types.d.ts +7 -1
- package/dist/leader/leader.d.ts +22 -1
- package/dist/leader/leader.js +4 -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-reach-scope.d.ts +15 -3
- package/dist/model-reach-scope.js +3 -2
- package/dist/model-route-url.d.ts +13 -20
- package/dist/model-route-url.js +28 -24
- package/dist/model-unreachable-notice.d.ts +2 -1
- package/dist/model-unreachable-notice.js +1 -0
- package/dist/observability/fail-open.d.ts +35 -2
- package/dist/observability/fail-open.js +20 -0
- 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 +14 -1
- package/dist/plugins/park-commit-watch.d.ts +19 -5
- package/dist/plugins/park-commit-watch.js +24 -17
- package/dist/plugins/permission-rule-store-sql.d.ts +2 -1
- package/dist/plugins/remote-env-device.js +0 -1
- package/dist/request-key-closure.js +1 -1
- package/dist/router/route-orchestration.d.ts +4 -2
- package/dist/router/route-orchestration.js +2 -1
- package/dist/rules-consent.d.ts +6 -2
- package/dist/rules-consent.js +3 -0
- package/dist/run-local.js +3 -1
- package/dist/runs.js +1 -1
- package/dist/runtime-governance.d.ts +44 -32
- package/dist/runtime-governance.js +23 -9
- package/dist/sighup-idle.d.ts +7 -1
- package/dist/sighup-idle.js +4 -4
- package/dist/task-settings.d.ts +37 -14
- package/dist/task-settings.js +20 -20
- package/dist/tool-approval.d.ts +13 -0
- package/dist/tool-approval.js +17 -2
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/redact.d.ts +3 -3
- package/dist/trace/self-minted-wire.d.ts +64 -0
- package/dist/trace/self-minted-wire.js +30 -0
- 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
package/dist/boot/shutdown.js
CHANGED
|
@@ -8,14 +8,16 @@ 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, drainDeadlineBeyondBound } from "../fleet/drain-entry.js";
|
|
11
12
|
const HARD_STOP_GRACE_MS = 10_000;
|
|
13
|
+
const FINAL_EXIT_MS = 15_000;
|
|
12
14
|
export function backgroundGraceRemainingMs(a) {
|
|
13
15
|
if (a.softStopped <= 0)
|
|
14
16
|
return 0;
|
|
15
17
|
return Math.max(0, Math.min(a.softStoppedAt + a.graceMs, a.deadlineMs) - a.now);
|
|
16
18
|
}
|
|
17
19
|
export function installShutdownHandlers(ctx) {
|
|
18
|
-
const { config, logger, server, reaper, fleetReconcile, retentionLane, releaseRetentionLease, otelExporter, breakerState, costQuota, rateLimiter, runner, subRunner, lspManager, workflowNotifyJournal, fleetClient, backend, drainState, storeLiveProbe, configCenter, version, deviceHub, toolApproval, parkCommits, } = ctx;
|
|
20
|
+
const { config, logger, server, reaper, fleetReconcile, retentionLane, releaseRetentionLease, otelExporter, breakerState, costQuota, rateLimiter, runner, subRunner, lspManager, workflowNotifyJournal, fleetClient, backend, drainState, storeLiveProbe, configCenter, version, deviceHub, toolApproval, parkCommits, servingLease, } = ctx;
|
|
19
21
|
const dataRoot = config.localDataRoot;
|
|
20
22
|
if (dataRoot) {
|
|
21
23
|
const prior = readCrashLast(dataRoot);
|
|
@@ -69,12 +71,14 @@ export function installShutdownHandlers(ctx) {
|
|
|
69
71
|
};
|
|
70
72
|
process.on("unhandledRejection", onUnhandledRejection);
|
|
71
73
|
let closing = false;
|
|
74
|
+
let closingAt = 0;
|
|
72
75
|
let parentWatch;
|
|
73
76
|
let engineLease;
|
|
74
77
|
const hardShutdown = () => {
|
|
75
78
|
if (closing)
|
|
76
79
|
return;
|
|
77
80
|
closing = true;
|
|
81
|
+
closingAt = Date.now();
|
|
78
82
|
if (dataRoot)
|
|
79
83
|
clearCrashLast(dataRoot);
|
|
80
84
|
const graceDeadlineMs = Date.now() + HARD_STOP_GRACE_MS;
|
|
@@ -101,6 +105,7 @@ export function installShutdownHandlers(ctx) {
|
|
|
101
105
|
parentWatch?.stop();
|
|
102
106
|
engineLease?.stop();
|
|
103
107
|
storeLiveProbe?.stop();
|
|
108
|
+
servingLease?.stop();
|
|
104
109
|
void deviceHub?.close().catch((err) => logger.warn("device_hub_close_failed", { err: String(err) }));
|
|
105
110
|
configCenter?.stopRefreshLoop();
|
|
106
111
|
otelExporter?.stop();
|
|
@@ -133,44 +138,79 @@ export function installShutdownHandlers(ctx) {
|
|
|
133
138
|
.finally(() => backgroundGrace)
|
|
134
139
|
.finally(() => process.exit(0));
|
|
135
140
|
});
|
|
136
|
-
const finalExit = setTimeout(() => { logger.warn("hard_shutdown_final_deadline", { afterMs:
|
|
141
|
+
const finalExit = setTimeout(() => { logger.warn("hard_shutdown_final_deadline", { afterMs: FINAL_EXIT_MS }); process.exit(0); }, FINAL_EXIT_MS);
|
|
137
142
|
finalExit.unref?.();
|
|
143
|
+
drainState.endSubscriptions?.();
|
|
138
144
|
server.closeIdleConnections?.();
|
|
139
145
|
const forceClose = setTimeout(() => server.closeAllConnections?.(), HARD_STOP_GRACE_MS);
|
|
140
146
|
forceClose.unref?.();
|
|
141
147
|
};
|
|
142
148
|
let draining = false;
|
|
143
149
|
let drainStartedAt = 0;
|
|
150
|
+
let firstStopSignalAt;
|
|
144
151
|
let drainTick;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
let current;
|
|
153
|
+
const enterDrain = (entry, bySignal = false, at = Date.now()) => {
|
|
154
|
+
if (current !== undefined)
|
|
155
|
+
return { ...current, state: "already_draining" };
|
|
156
|
+
if (closing)
|
|
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 };
|
|
148
160
|
draining = true;
|
|
149
161
|
drainStartedAt = performance.now();
|
|
162
|
+
firstStopSignalAt = bySignal ? drainStartedAt : undefined;
|
|
163
|
+
const since = at;
|
|
164
|
+
current = { actionId: entry.actionId, since, deadlineAt: entry.deadlineAt };
|
|
150
165
|
drainState.draining = true;
|
|
151
|
-
|
|
152
|
-
drainState.
|
|
166
|
+
drainState.since = since;
|
|
167
|
+
drainState.actionId = entry.actionId;
|
|
168
|
+
drainState.deadlineAt = entry.deadlineAt;
|
|
169
|
+
const windowMs = Math.max(0, entry.deadlineAt - since);
|
|
170
|
+
const deadlineMono = drainStartedAt + windowMs;
|
|
171
|
+
deviceHub?.beginDrain({ graceMs: () => windowMs });
|
|
172
|
+
drainState.yieldInflight?.();
|
|
173
|
+
drainState.endSubscriptions?.();
|
|
174
|
+
toolApproval?.beginDrain();
|
|
153
175
|
void fleetClient?.announceNow();
|
|
154
176
|
const inflight = drainState.inflight?.() ?? 0;
|
|
155
|
-
logger.info("draining_started", { inflight,
|
|
177
|
+
logger.info("draining_started", { actionId: entry.actionId, inflight, deadlineAt: entry.deadlineAt, windowMs });
|
|
156
178
|
if (inflight === 0) {
|
|
157
179
|
hardShutdown();
|
|
158
|
-
return;
|
|
180
|
+
return { ...current, state: "draining" };
|
|
159
181
|
}
|
|
160
182
|
const tick = setInterval(() => {
|
|
161
183
|
const n = drainState.inflight?.() ?? 0;
|
|
162
|
-
const
|
|
163
|
-
if (n === 0 ||
|
|
184
|
+
const now = performance.now();
|
|
185
|
+
if (n === 0 || now >= deadlineMono) {
|
|
164
186
|
clearInterval(tick);
|
|
165
187
|
drainTick = undefined;
|
|
166
|
-
logger.info("drain_complete", { inflight: n, elapsedMs:
|
|
188
|
+
logger.info("drain_complete", { actionId: entry.actionId, inflight: n, elapsedMs: Math.round(now - drainStartedAt), timedOut: n > 0 });
|
|
167
189
|
hardShutdown();
|
|
168
190
|
}
|
|
169
191
|
}, 1_000);
|
|
170
192
|
drainTick = tick;
|
|
193
|
+
return { ...current, state: "draining" };
|
|
194
|
+
};
|
|
195
|
+
const drainOnCause = (cause) => {
|
|
196
|
+
const at = Date.now();
|
|
197
|
+
enterDrain({ actionId: cause, deadlineAt: at + config.drainGraceMs }, DRAIN_CAUSE_COUNTS_AS_FIRST_STOP_SIGNAL[cause], at);
|
|
171
198
|
};
|
|
199
|
+
const enterByRequest = (entry) => enterDrain(entry);
|
|
200
|
+
drainState.enter = enterByRequest;
|
|
172
201
|
const onStopSignalWhileDraining = (signal) => {
|
|
173
|
-
const
|
|
202
|
+
const now = performance.now();
|
|
203
|
+
if (firstStopSignalAt === undefined) {
|
|
204
|
+
firstStopSignalAt = now;
|
|
205
|
+
logger.info("drain_signal_after_request_drain_ignored", {
|
|
206
|
+
signal,
|
|
207
|
+
actionId: current?.actionId,
|
|
208
|
+
deadlineAt: current?.deadlineAt,
|
|
209
|
+
note: "this drain was entered by a drain request / lease denial / self-exit and its deadline is fixed — the first stop signal does not shorten it; a second stop signal at least DRAIN_SECOND_SIGNAL_MIN_GAP_MS later hard-stops",
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const sinceFirst = now - firstStopSignalAt;
|
|
174
214
|
const minGapMs = config.drainSecondSignalMinGapMs;
|
|
175
215
|
if (sinceFirst < minGapMs) {
|
|
176
216
|
logger.info("drain_duplicate_signal_ignored", { signal, sinceFirstMs: Math.floor(sinceFirst), minGapMs });
|
|
@@ -180,7 +220,7 @@ export function installShutdownHandlers(ctx) {
|
|
|
180
220
|
hardShutdown();
|
|
181
221
|
};
|
|
182
222
|
const firstStopSignal = {
|
|
183
|
-
SIGTERM:
|
|
223
|
+
SIGTERM: () => drainOnCause("signal:SIGTERM"),
|
|
184
224
|
SIGINT: hardShutdown,
|
|
185
225
|
};
|
|
186
226
|
const onStopSignal = (signal) => {
|
|
@@ -198,9 +238,10 @@ export function installShutdownHandlers(ctx) {
|
|
|
198
238
|
process.on("SIGINT", onSigint);
|
|
199
239
|
const onSighup = createSighupIdleHandler({
|
|
200
240
|
inflight: () => drainState.inflight?.() ?? 0,
|
|
241
|
+
attached: () => drainState.attachedStreams?.() ?? 0,
|
|
201
242
|
lastActivityAt: () => drainState.lastActivityAt?.() ?? 0,
|
|
202
243
|
isStopped: () => closing || draining,
|
|
203
|
-
escalate:
|
|
244
|
+
escalate: () => drainOnCause("signal:SIGHUP"),
|
|
204
245
|
shutdown: hardShutdown,
|
|
205
246
|
idleGraceMs: config.sighupIdleGraceMs,
|
|
206
247
|
log: (event, fields) => logger.info(event, fields),
|
|
@@ -231,7 +272,7 @@ export function installShutdownHandlers(ctx) {
|
|
|
231
272
|
selfExit: (report) => {
|
|
232
273
|
if (dataRoot)
|
|
233
274
|
writeExitLast(dataRoot, { version, ...report });
|
|
234
|
-
|
|
275
|
+
drainOnCause("self-exit:attachment");
|
|
235
276
|
},
|
|
236
277
|
lingerMs: config.engineAutoExit ? config.engineLingerMs : Number.POSITIVE_INFINITY,
|
|
237
278
|
...(config.parentPid !== undefined
|
|
@@ -259,6 +300,8 @@ export function installShutdownHandlers(ctx) {
|
|
|
259
300
|
}
|
|
260
301
|
parentWatch?.stop();
|
|
261
302
|
engineLease?.stop();
|
|
303
|
+
if (drainState.enter === enterByRequest)
|
|
304
|
+
drainState.enter = undefined;
|
|
262
305
|
},
|
|
263
306
|
};
|
|
264
307
|
}
|
|
@@ -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,
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import type { runStage08 } from "./stage-08-reapers.js";
|
|
2
|
+
import type { DrainState } from "../fleet/drain-entry.js";
|
|
2
3
|
export declare function runStage09(ctx: Awaited<ReturnType<typeof runStage08>>): {
|
|
3
|
-
drainState:
|
|
4
|
-
draining: boolean;
|
|
5
|
-
since?: number;
|
|
6
|
-
reason?: string;
|
|
7
|
-
inflight?: () => number;
|
|
8
|
-
lastActivityAt?: () => number;
|
|
9
|
-
attachedStreams?: () => number;
|
|
10
|
-
lastAttachEndedAt?: () => number;
|
|
11
|
-
};
|
|
4
|
+
drainState: DrainState;
|
|
12
5
|
hookWakeBus: {
|
|
13
6
|
deliver?: (sessionId: string, text: string) => Promise<boolean>;
|
|
14
7
|
};
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import type { runStage09 } from "./stage-09-leader.js";
|
|
2
2
|
export declare function runStage10(ctx: Awaited<ReturnType<typeof runStage09>>): Promise<{
|
|
3
|
-
drainState:
|
|
4
|
-
draining: boolean;
|
|
5
|
-
since?: number;
|
|
6
|
-
reason?: string;
|
|
7
|
-
inflight?: () => number;
|
|
8
|
-
lastActivityAt?: () => number;
|
|
9
|
-
attachedStreams?: () => number;
|
|
10
|
-
lastAttachEndedAt?: () => number;
|
|
11
|
-
};
|
|
3
|
+
drainState: import("../fleet/drain-entry.js").DrainState;
|
|
12
4
|
hookWakeBus: {
|
|
13
5
|
deliver?: (sessionId: string, text: string) => Promise<boolean>;
|
|
14
6
|
};
|
|
@@ -13,9 +13,11 @@ import { SQL_BLOB_DEFAULT_MAX_BYTES } from "../plugins/blob-backend.js";
|
|
|
13
13
|
import { createStoreLiveProbe } from "../store-live-probe.js";
|
|
14
14
|
import { counterStoreLabel } from "../plugins/store-backend.js";
|
|
15
15
|
import { sessionStoreLabelOf } from "../store-posture.js";
|
|
16
|
-
import {
|
|
16
|
+
import { startFleetClientFromConfig } from "../fleet-client.js";
|
|
17
17
|
import { createCenterWiringReader } from "../capabilities/center-wiring.js";
|
|
18
|
-
import {
|
|
18
|
+
import { mintEngineSignal } from "../fleet/engine-signal.js";
|
|
19
|
+
import { createServingLeaseClient } from "../fleet/serving-lease.js";
|
|
20
|
+
import { fleetFenceLaneOf } from "../config.js";
|
|
19
21
|
import { createResolveSpec } from "./resolve-spec.js";
|
|
20
22
|
import { executionLaneOf } from "../execution-lane-caps.js";
|
|
21
23
|
import { startFleetReconciler } from "../fleet/fleet-reconciler.js";
|
|
@@ -133,6 +135,24 @@ export async function runStage10(ctx) {
|
|
|
133
135
|
const storeLiveProbe = backend && backend.kind !== "local" && backend.dbNowMs && config.storeProbeIntervalMs > 0
|
|
134
136
|
? createStoreLiveProbe({ probe: () => backend.dbNowMs(), intervalMs: config.storeProbeIntervalMs, logger })
|
|
135
137
|
: undefined;
|
|
138
|
+
const fenceLane = fleetFenceLaneOf(config);
|
|
139
|
+
const servingLease = fenceLane !== undefined
|
|
140
|
+
? createServingLeaseClient({
|
|
141
|
+
center: fenceLane.center,
|
|
142
|
+
generation: fenceLane.generation,
|
|
143
|
+
instanceId,
|
|
144
|
+
dataRoot: config.localDataRoot,
|
|
145
|
+
outageMaxMs: config.fleetLeaseOutageMaxMs,
|
|
146
|
+
drain: (entry) => {
|
|
147
|
+
const enter = drainState.enter;
|
|
148
|
+
if (enter === undefined)
|
|
149
|
+
throw new Error("serving lease denied before the drain entry was installed — refusing to keep serving without a drain");
|
|
150
|
+
enter(entry);
|
|
151
|
+
},
|
|
152
|
+
drainWindowMs: () => config.drainGraceMs,
|
|
153
|
+
logger: { info: (m, d) => logger.info(m, d ?? {}), warn: (m, d) => logger.warn(m, d ?? {}), error: (m, d) => logger.error(m, d ?? {}) },
|
|
154
|
+
})
|
|
155
|
+
: undefined;
|
|
136
156
|
const deployment = {
|
|
137
157
|
sessionStoreLabel: sessionStoreLabelOf(config.sessionBackend, backend?.kind),
|
|
138
158
|
modelReady: () => configCenter.modelReady(),
|
|
@@ -154,15 +174,16 @@ export async function runStage10(ctx) {
|
|
|
154
174
|
},
|
|
155
175
|
},
|
|
156
176
|
restartState: () => configCenter.restartState(),
|
|
157
|
-
planeDeferredState: () => configCenter.planeDeferredState(),
|
|
158
177
|
configApplyState: () => configCenter.configApplyState(),
|
|
159
178
|
centerCredentialState: () => configCenter.centerCredentialState(),
|
|
160
179
|
modelRefusalsState: () => configCenter.modelRefusalsState(),
|
|
180
|
+
configHandoffState: () => configCenter.configHandoffState(),
|
|
161
181
|
mcpRevocations,
|
|
162
182
|
mcpRevocationState: () => configCenter.mcpRevocationState(),
|
|
163
183
|
configRefreshNow: () => configCenter.refreshNow(),
|
|
164
184
|
configCenterManagedKeys: () => configCenter.centerManagedKeys(),
|
|
165
185
|
drainState,
|
|
186
|
+
servingLease: servingLease ? () => servingLease.view() : undefined,
|
|
166
187
|
storePosture,
|
|
167
188
|
centerWiring: createCenterWiringReader({ config, pullState: () => configCenter.centerPullState(), fleetLease, usageReport: fleetUsage !== undefined }),
|
|
168
189
|
storeLiveState: storeLiveProbe ? () => storeLiveProbe.state() : undefined,
|
|
@@ -244,19 +265,19 @@ export async function runStage10(ctx) {
|
|
|
244
265
|
...("code" in e ? { code: String(e.code) } : {}),
|
|
245
266
|
});
|
|
246
267
|
});
|
|
247
|
-
const fleetClient =
|
|
268
|
+
const fleetClient = startFleetClientFromConfig(config, {
|
|
248
269
|
instanceId,
|
|
249
270
|
version: serviceVersion(),
|
|
250
|
-
|
|
251
|
-
restart: () => deployment.restartState?.(),
|
|
271
|
+
signal: () => mintEngineSignal({ ...deployment, config }),
|
|
252
272
|
modelRefusals: () => deployment.modelRefusalsState?.(),
|
|
273
|
+
...(servingLease ? { onCenterCapabilities: (caps) => servingLease.noteCenterCapabilities(caps) } : {}),
|
|
253
274
|
...(fleetUsage ? { usage: fleetUsage } : {}),
|
|
254
275
|
logger: { info: (m, d) => logger.info(m, d ?? {}), warn: (m, d) => logger.warn(m, d ?? {}) },
|
|
255
276
|
});
|
|
256
277
|
configCenter.onSelfReportChange(() => void fleetClient?.announceNow());
|
|
257
278
|
if (fleetClient) {
|
|
258
|
-
const advertised =
|
|
259
|
-
logger.info("fleet_client_started", { worker: config.configCenter?.worker, usageReport: fleetUsage !== undefined, announce: advertised !==
|
|
279
|
+
const advertised = config.fleetAdvertiseAddress;
|
|
280
|
+
logger.info("fleet_client_started", { worker: config.configCenter?.worker, usageReport: fleetUsage !== undefined, announce: advertised !== undefined, ...(advertised !== undefined ? { address: buildDisplaySafeUrl(advertised) } : {}) });
|
|
260
281
|
}
|
|
261
282
|
const failoverFirstByte = buildFailoverFirstByteNotice(config);
|
|
262
283
|
if (failoverFirstByte)
|
|
@@ -310,7 +331,9 @@ export async function runStage10(ctx) {
|
|
|
310
331
|
deviceHub: deviceLane?.hub,
|
|
311
332
|
retentionLane: retentionLaneStarted,
|
|
312
333
|
releaseRetentionLease: retentionLaneStore !== undefined ? () => retentionLaneStore.release(instanceId) : undefined,
|
|
334
|
+
servingLease,
|
|
313
335
|
});
|
|
336
|
+
servingLease?.resumeStickyDenial();
|
|
314
337
|
return { ...ctx };
|
|
315
338
|
}
|
|
316
339
|
//# sourceMappingURL=stage-10-http-server.js.map
|
|
@@ -39,7 +39,7 @@ import type { NamedKeysOf } from "../request-key-closure.js";
|
|
|
39
39
|
*/
|
|
40
40
|
export declare const COMPOSITE_CAPABILITY_MEMBERS: {
|
|
41
41
|
readonly deviceExecutor: readonly ["enabled", "protocolVersion", "maxInflightPerDevice", "wsPath", "management"];
|
|
42
|
-
readonly fleet: readonly ["stream", "sessionScope", "observe", "resume", "bgNotifyFailClosed", "steerPriority", "hookFailureNotice"];
|
|
42
|
+
readonly fleet: readonly ["stream", "sessionScope", "observe", "resume", "bgNotifyFailClosed", "steerPriority", "hookFailureNotice", "leaseClient", "generationEcho"];
|
|
43
43
|
readonly memoryEngine: readonly ["backend", "vectorMode"];
|
|
44
44
|
readonly store: readonly ["declared", "backend", "durable", "degradedReason", "sql"];
|
|
45
45
|
readonly centerWiring: readonly ["effectivePull", "effectiveRevision", "lease", "leaseHealth", "failMode", "usageReport", "issuer", "caller"];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const COMPOSITE_CAPABILITY_MEMBERS = {
|
|
2
2
|
deviceExecutor: ["enabled", "protocolVersion", "maxInflightPerDevice", "wsPath", "management"],
|
|
3
|
-
fleet: ["stream", "sessionScope", "observe", "resume", "bgNotifyFailClosed", "steerPriority", "hookFailureNotice"],
|
|
3
|
+
fleet: ["stream", "sessionScope", "observe", "resume", "bgNotifyFailClosed", "steerPriority", "hookFailureNotice", "leaseClient", "generationEcho"],
|
|
4
4
|
memoryEngine: ["backend", "vectorMode"],
|
|
5
5
|
store: ["declared", "backend", "durable", "degradedReason", "sql"],
|
|
6
6
|
centerWiring: ["effectivePull", "effectiveRevision", "lease", "leaseHealth", "failMode", "usageReport", "issuer", "caller"],
|
|
@@ -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)`)⇒ 手带在场也一条落盘路都不剩。
|
package/dist/config-catalog.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES } from "@sema-agent/core";
|
|
2
|
+
import { GLOBAL_SCOPE } from "@sema-agent/settings-schema/api/scopes";
|
|
2
3
|
import { CENTER_CREDENTIAL_ENV, DOMAIN_SCHEMAS } from "@sema-agent/settings-schema";
|
|
3
4
|
import { CENTER_CREDENTIAL_RENEW_LEAD_DEFAULT_MS, CENTER_CREDENTIAL_RENEW_LEAD_MAX_MS, CENTER_CREDENTIAL_RENEW_LEAD_MIN_MS } from "./center-credential-renewer.js";
|
|
4
5
|
import { THINKING_FORMATS } from "./model-compat.js";
|
|
5
|
-
import { AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, CONFIG_PROVIDER_WORDS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
|
|
6
|
+
import { AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, CENTER_BASE_URL_RULE, CONFIG_PROVIDER_WORDS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS, FLEET_LEASE_OUTAGE_MAX_DEFAULT_MS, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
|
|
6
7
|
import { webSearchProviderFromEnv, WEB_SEARCH_PROVIDERS } from "./plugins/web-search.js";
|
|
7
8
|
import { AUTONOMY_MODES } from "./runtime-governance.js";
|
|
8
9
|
import { REMOTE_EXEC_PROVIDER_WORDS } from "./execution-lane-caps.js";
|
|
@@ -73,7 +74,7 @@ export const CONFIG_CATALOG = [
|
|
|
73
74
|
r("AUTH_BRIDGE_AUDIENCE", "auth", "string", "registry SSO-JWT 验签期望 aud", { danger: SEC }),
|
|
74
75
|
r("AUTH_BRIDGE_ISSUER", "auth", "url", "registry SSO-JWT issuer(auth bridge 的开关之一;S-671 / C-R100 ① 起**不是**托管证据 —— 多租户只认 REQUIRE_PRINCIPAL=true)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 桥的装配处回退到 configCenter.baseUrl(配了 center 即默认开桥);显式值与回退值在托管判定上同判(都不算)", configKey: "authBridgeIssuer" }),
|
|
75
76
|
r("AUTH_BRIDGE_JWKS_URL", "auth", "url", "registry SSO-JWT 的 JWKS 地址(缺省从 issuer 派生)", { danger: SEC }),
|
|
76
|
-
r("AUTONOMY", "auth", "enum", "运维 autonomy
|
|
77
|
+
r("AUTONOMY", "auth", "enum", "运维 autonomy 模式(read-only ⇒ 只读手 + 写面 roots,每段续跑重折,子代同受、委派照挂;ask ⇒ 恒 shellGate,每段重折;plan ⇒ 提交入口的模式词地板:新提交的任务以 permissionMode:plan 起步,请求词钳到它之下,写与非只读调用在调用时问 / 拒,批准计划之后整条 run 按批准的档跑完);center 腿热且 non-sticky(plan 地板只作用于之后提交的任务)", { danger: SEC, enumValues: AUTONOMY_MODES, configKey: "autonomy", center: cGov("autonomy") }),
|
|
77
78
|
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" }),
|
|
78
79
|
r("AUTO_MODE_CLASSIFIER_TIMEOUT_MS", "approval", "number", `auto 分类器单级判决上限(ms;缺席 = core 缺省;自托管慢网关按排队 + prefill 留足;一级到点没裁 ⇒ 走下一级,走完仍没裁 ⇒ 这次门控调用整条拒;须为 [${AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS.minMs}, ${AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS.maxMs}] 内十进制整数,0 / 小数 / 越界拒启)`, { derivedDefaultNote: "未设/空串 ⇒ 不铸键,跟随 core 缺省(本仓不复制上游数字;core 改缺省本旋钮照映不改)", configKey: "autoModeClassifierTimeoutMs" }),
|
|
79
80
|
r("BAKE_CACHE_BASE", "orchestration", "url", "烤镜像 fetch-once 缓存源 base(注入 --cache-base;绝不受调用方控制)"),
|
|
@@ -93,7 +94,7 @@ export const CONFIG_CATALOG = [
|
|
|
93
94
|
r("CONFIG_BOOT_FETCH_BUDGET_MS", "integrations", "number", "boot 首拉配置的抢答窗(ms;过窗以 env/兜底起服,拉取转后台热应用)", { staticDefault: "1500", configKey: "configBootFetchBudgetMs" }),
|
|
94
95
|
r("CONFIG_CATALOG_ENABLED", "auth", "boolean", "本目录端点 GET /v1/config/catalog 的旋钮(operator-only 面;合规部署可关 ⇒ 路径 404 + 能力位翻假)", { staticDefault: "true", danger: SEC, configKey: "configCatalogEnabled" }),
|
|
95
96
|
r("CONFIG_LKG_DURABLE", "integrations", "boolean", "LKG 跨重启 handoff(严格 'true' 才开:重启先用 LKG 起服,tier 候选借此落地)", { staticDefault: "false" }),
|
|
96
|
-
r("CONFIG_LKG_ENABLED", "integrations", "boolean", "last-known-good 配置盘缓存读写开关(仅远程 provider 有意义)", { staticDefault: "true" }),
|
|
97
|
+
r("CONFIG_LKG_ENABLED", "integrations", "boolean", "last-known-good 配置盘缓存读写开关(仅远程 provider 有意义)", { staticDefault: "true", configKey: "configLkgEnabled" }),
|
|
97
98
|
r("CONFIG_LKG_PATH", "integrations", "path", "LKG 落盘路径(设了 ⇒ 派生读拒行:这条路径与它的真实落点各一行,按路径段串匹配 —— 任意深度出现即拒、大小写不敏感,用户仓里同段串的路径一并读不到;空串拒启)", { derivedDefaultNote: "缺席 ⇒ 配置根(SEMA_CONFIG_DIR,缺省 ~/.sema)下按 worker + 凭证身份分箱派生", configKey: "configLkgPath" }),
|
|
98
99
|
r("CONFIG_LOCAL_DIR", "integrations", "path", "本地配置源根(config.d/<domain>.json);兼作数据根回落链一环", { configKey: "configLocalDir", danger: DATA }),
|
|
99
100
|
r("CONFIG_PROVIDER", "integrations", "enum", "配置源双模选择(闭集,逐字比较;认不出的词 ⇒ 拒启,S-841)", { enumValues: CONFIG_PROVIDER_WORDS, derivedDefaultNote: "缺席:SEMA_REGISTRY_URL 在场 ⇒ remote,否则纯 env(无管道)", configKey: "configProvider" }),
|
|
@@ -127,7 +128,7 @@ export const CONFIG_CATALOG = [
|
|
|
127
128
|
r("DOCKER_PIDS_LIMIT", "orchestration", "number", "local-docker fork-bomb 背栓(--pids-limit;非法值回 adapter 默认 512+warn)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ adapter 默认 512" }),
|
|
128
129
|
r("DOCKER_SANDBOX_ENV", "orchestration", "secret", "local-docker 车道每条 docker exec 以 -e 注入的本进程 env 点名表(NAME,NAME;点到的键从 worker env 原样取,值不进 spec/center/日志;与 LSP_ENV_ALLOW / E2B_SANDBOX_ENV 同一只解析器与「点名放行」谓词:形错 / 点名名单型旋钮拒启且不回显值;本进程环境里没有的名按序号 warn、不转发;只在本车道被选中时求值;原文按凭据类对待:只回在场位、不进任何子进程)", { danger: SEC }),
|
|
129
130
|
r("DRAIN_GRACE_MS", "limitsHttp", "number", "SIGTERM 优雅排空窗(ms;期间新计费提交 503+Retry-After)。出厂默认是**服务器部署**的取值;壳自 spawn 的引擎会被壳注入更短的窗 ⇒ 排壳形关机预算时以本行的 effectiveValue(= 本进程实际生效值)为准,不照抄 staticDefault", { staticDefault: String(10 * 60 * 1000), configKey: "drainGraceMs", center: cLimits("drainGraceMs") }),
|
|
130
|
-
r("DRAIN_SECOND_SIGNAL_MIN_GAP_MS", "limitsHttp", "number", `排空期「第二发信号 = 硬停」的最小间隔(ms;锚 =
|
|
131
|
+
r("DRAIN_SECOND_SIGNAL_MIN_GAP_MS", "limitsHttp", "number", `排空期「第二发信号 = 硬停」的最小间隔(ms;锚 = 本次排空的第一发停止信号 —— SIGTERM / SIGHUP 升级进的排空,入口那一发就是它;排空口 / 租约被拒 / 附着租约自退进的排空,到达的第一发被吸收、不缩短期限。SIGTERM / SIGINT 与锚相隔不足 ⇒ 同一拍的重复投递,一行 drain_duplicate_signal_ignored 后忽略、排空继续;0 = 关,恒按第二发硬停;须为 [${DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS.minMs}, ${DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS.maxMs}] 内十进制整数(上界 = DRAIN_GRACE_MS 地板),小数 / 负数 / 越界拒启)`, { staticDefault: String(DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS), configKey: "drainSecondSignalMinGapMs" }),
|
|
131
132
|
r("DURABLE_APPROVAL", "approval", "boolean", "durable suspend/resume HITL(需 SQL 店;决定 checkpointStore 与 /v1/approvals 面的存在性)", { danger: SEC, derivedDefaultNote: "posture 三态(S-178):显式词恒赢,缺席 = 单用户(REQUIRE_PRINCIPAL≠true)且有耐久店 ⇒ on / 多租户或无耐久店 ⇒ off;生效来源见 GET /v1/diagnostics/wiring 的 serverGates.durableApproval.source", configKey: "durableApproval" }),
|
|
132
133
|
r("E2B_ALLOW_NET", "orchestration", "boolean", "e2b 沙箱出网许可", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 不铸键(adapter 默认允许)" }),
|
|
133
134
|
r("E2B_API_KEY", "orchestration", "secret", "E2B 车道 API 凭据(REMOTE_EXEC=e2b 必需件)", { danger: [...SEC, ...BILL] }),
|
|
@@ -147,8 +148,11 @@ export const CONFIG_CATALOG = [
|
|
|
147
148
|
r("EXPERIMENTAL_OBSERVER_AGENTS", "orchestration", "boolean", "observer 开闸线(实验面 ship-dark;多租户只认 center caps,env 被忽略)", { staticDefault: "false", configKey: "experimentalObserverAgents" }),
|
|
148
149
|
r("FILE_HISTORY_CAPTURE", "store", "boolean", "文件历史**捕获**总开关(三条车道同义)。关 = 不再捕获任何改动文件的首次快照(落 core 的部署捕获位 `RunnerDeps.fileHistoryCapture: \"off\"`,能力词 `fileHistoryCapture` 翻 off);S-724(7.104.0)起**只关捕获**:历史店照接,已存历史照常 restoreFiles / rewindFilesTo / 2c session-sync。关掉 ≠ 删掉:已捕获的历史不被批量删除,仍按会话删除与留存规则清理 —— 要清存量走 DELETE 会话或留存旋钮;没有「整库历史不可用」的旋钮", { staticDefault: "true", danger: DATA, configKey: "fileHistoryCapture" }),
|
|
149
150
|
r("FILE_HISTORY_RETENTION_KEEP", "store", "number", "per-file rewind 历史的保留上限(与 core FileStorageBackend 同域键形,三条车道一套语义)", { danger: DATA, derivedDefaultNote: "缺席 ⇒ core 缺省 100" }),
|
|
150
|
-
r("FLEET_ADVERTISE_ADDRESS", "integrations", "url",
|
|
151
|
+
r("FLEET_ADVERTISE_ADDRESS", "integrations", "url", `fleet 面向 center 公告的本 worker 可达地址(中心车道配齐 + 本键 ⇒ 公告环;空串 = 未设;用量环不看本键)。中心往它后面拼路径,与中心基址三席同一条判据:${CENTER_BASE_URL_RULE}`, { configKey: "fleetAdvertiseAddress" }),
|
|
152
|
+
r("FLEET_GENERATION", "integrations", "string", "fleet 成员的代际(配置渲染器写;引擎回声到心跳与 /health 的 generation)。缺席 ⇔ 非 fleet 成员(不续服务租约、不受围栏);在场必须是规范十进制串(\"0\" / \"7\";空串、前导零、符号、空白拒启)", { danger: SEC, configKey: "fleetGeneration" }),
|
|
151
153
|
r("FLEET_LEASE_FAIL_CLOSED", "integrations", "boolean", "租约连接级失败(中心鉴权拒 / 凭证不可读 / 不可达 / 5xx / 超时 / 坏体)时计费入口拒 503 fleet.lease_unavailable(true)还是放行(false);409 not-lease 与耗尽 429 不受它管;词表外拒启", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 按部署姿态派生:REQUIRE_PRINCIPAL=true ⇒ true(多租:额度是安全轴);单用户 ⇒ false(AP 姿态)。显式值压过派生。生效值见 caps centerWiring.failMode", configKey: "fleetLeaseFailClosed" }),
|
|
154
|
+
r("FLEET_LEASE_OUTAGE_MAX_MS", "integrations", "number", "一台实例「活着而不服务」的最长时长 T_max(ms),两处读它:① 服务租约中心不可达的容忍上限 —— 租约过期满这么久 ⇒ 新计费提交 503 state.lease_expired(取消 / HITL 应答 / 续跑 / 读面照放行,在飞不动,/readyz 不翻),只在 fleet 成员 ∧ 中心宣告 servingLease 能力位时生效;② 排空口 POST /v1/admin/drain 的期限上界 —— deadlineAt 离此刻超过它 ⇒ 400 request.field_invalid,成员与非成员同读;< 60000(租约 TTL 地板)或非整数拒启", { danger: SEC, staticDefault: String(FLEET_LEASE_OUTAGE_MAX_DEFAULT_MS), configKey: "fleetLeaseOutageMaxMs" }),
|
|
155
|
+
r("FLEET_SCOPE", "integrations", "string", "本引擎所在的租户 scope(配置渲染器为多租引擎写;唯一读者 = 排空口 POST /v1/admin/drain 的身份比对,请求不带 scope 也读成缺省 scope)。在场必须是 scope id 形(小写字母 / 数字 / 内部连字符,1–64;空串拒启);与 FLEET_GENERATION 不联动(成员缺席 ⇒ 缺省 scope)。续租体与心跳不带它(scope 由中心凭证绑定)", { danger: SEC, staticDefault: GLOBAL_SCOPE, configKey: "fleetScope" }),
|
|
152
156
|
r("FORK_ENABLED", "orchestration", "boolean", "CC /fork(core Fork 工具)装配开关", { derivedDefaultNote: "posture 三态:缺席 = 单用户 ⇒ on / 多租户 ⇒ off(per-principal entitlement 另判)", configKey: "forkEnabled" }),
|
|
153
157
|
r("GATEWAY_MAX_RETRIES", "modelPlane", "number", "openai 兼容腿每次调用重试上限", { derivedDefaultNote: "缺席 ⇒ 不传引擎,引擎默认当家" }),
|
|
154
158
|
r("GIT_API_BASEURL", "integrations", "url", "repo 工具的 Git host API base", { configKey: "gitApiBaseUrl" }),
|
|
@@ -399,9 +403,11 @@ export const CONFIG_CATALOG = [
|
|
|
399
403
|
r("SEMA_PIP_INDEX", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 pip index 地址"),
|
|
400
404
|
r("SEMA_REGISTRY_ADMIN_TOKEN", "integrations", "secret", "写 center 的管理凭证(仅 boot 上报执行公钥用;刻意不入 ServiceConfig —— 不进 configHash/日志面)", { danger: SEC, scope: "bootstrap-only" }),
|
|
401
405
|
r("SEMA_REGISTRY_DRY_RUN", "integrations", "boolean", "center 腿 dry-run(拉而不应用)", { staticDefault: "false" }),
|
|
406
|
+
r("SEMA_REGISTRY_LIVE_URL", "integrations", "url", `fleet 活性组(心跳 / 注销、服务租约 = settings-schema FLEET_LIVE_PATHS)的中心基址:给管理面在就绪门后、活性面另走一条不看就绪的入口的部署。缺席 = SEMA_REGISTRY_URL。${CENTER_BASE_URL_RULE}`),
|
|
402
407
|
r(CENTER_CREDENTIAL_ENV.token, "integrations", "secret", `center 拉取凭据(值形;与 ${CENTER_CREDENTIAL_ENV.tokenFile} 二选一,都设=拒启)`, { danger: SEC }),
|
|
403
408
|
r(CENTER_CREDENTIAL_ENV.tokenFile, "integrations", "path", `center 拉取凭据文件(每条中心请求都重读 ⇒ 轮换 / 换账号不重启;与 ${CENTER_CREDENTIAL_ENV.token} 二选一,都设=拒启;内容两种类:裸行 = 静态 bearer / JSON 信封 user-session = 人的登录会话(可续签,S-698);文件不可读 / 为空 = 响亮 + 健康态 unreadable,不当作无凭证;内容读不成 / 多租户读到信封 = 拒启)`, { danger: SEC }),
|
|
404
|
-
r("SEMA_REGISTRY_URL", "integrations", "url",
|
|
409
|
+
r("SEMA_REGISTRY_URL", "integrations", "url", `sema-registry 地址(设了=remote 配置源开关)。${CENTER_BASE_URL_RULE}`),
|
|
410
|
+
r("SEMA_REGISTRY_WEB_URL", "integrations", "url", `中心 web 面的固定基址:引擎原样转述到 /health 的 center.webBaseUrl,壳按它拼会话深链(路径归壳)。缺席即缺席(引擎不拼不猜);只在接了中心(SEMA_REGISTRY_URL)时挂。${CENTER_BASE_URL_RULE}`),
|
|
405
411
|
r("SEMA_REGISTRY_WORKER", "integrations", "string", "本 worker 在 center 的名字(per-worker effective)"),
|
|
406
412
|
r("SEMA_UV_INDEX", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 uv index 地址"),
|
|
407
413
|
r("SEMA_UV_PY_MIRROR", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 uv python 分发镜像地址"),
|
|
@@ -8,13 +8,15 @@ export declare const SETTINGS_SCHEMA_MAJOR: string;
|
|
|
8
8
|
/** 一个中心端点 = 基址 + 凭证来源(`ServiceConfig.configCenter` 结构上满足)。 */
|
|
9
9
|
export interface CenterEndpoint {
|
|
10
10
|
readonly baseUrl: string;
|
|
11
|
+
/** 活性组的基址(env `SEMA_REGISTRY_LIVE_URL` 的回声;缺席 ⇒ 活性组也打 {@link baseUrl})。「缺席 = 主基址」这条规则只在 {@link centerFetch} 读。 */
|
|
12
|
+
readonly liveBaseUrl?: string | undefined;
|
|
11
13
|
readonly credential: CenterCredentialSource;
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
16
|
* S-866 ④ —— 「中心车道配齐」的**唯一**谓词:基址 ∧ **声明了**凭证(S-651:按声明,不按此刻读不读得出)∧ worker。
|
|
15
17
|
*
|
|
16
18
|
* 三处同调、不许各抄一份门:租约管理器(`fleet-lease.ts` `createFleetLeaseFromEnv`)、fleet 客户端的用量环
|
|
17
|
-
* (`fleet-client.ts` `
|
|
19
|
+
* (`fleet-client.ts` `startFleetClientFromConfig`)、用量累加器(`boot/budget-tracing.ts`)。公告环另要一件可达性声明
|
|
18
20
|
* `FLEET_ADVERTISE_ADDRESS` —— 那是公告环自己的第四件,不在本谓词里:用量是出站上报,与「中心能不能打到我」无关
|
|
19
21
|
* (7.105.0 及以前累加器与用量环都挂在公告地址上,不公告的引擎于是从不报用量,租约的已耗量只剩中心自己的估计)。
|
|
20
22
|
*/
|
|
@@ -47,6 +49,8 @@ export type CenterRoute = {
|
|
|
47
49
|
readonly kind: "fleet-instances";
|
|
48
50
|
} | {
|
|
49
51
|
readonly kind: "fleet-usage";
|
|
52
|
+
} | {
|
|
53
|
+
readonly kind: "fleet-serving-lease";
|
|
50
54
|
} | {
|
|
51
55
|
readonly kind: "execution-keys";
|
|
52
56
|
};
|
|
@@ -68,6 +72,14 @@ export interface CenterRequestInit {
|
|
|
68
72
|
readonly timeoutMs: number;
|
|
69
73
|
readonly fetchImpl?: typeof fetch;
|
|
70
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* 「后面要拼路径的基址」的拒句 —— 中心基址三席(`SEMA_REGISTRY_URL` / `SEMA_REGISTRY_LIVE_URL` / `SEMA_REGISTRY_WEB_URL`)与本实例的
|
|
77
|
+
* 公告地址(`FLEET_ADVERTISE_ADDRESS`,中心往它后面拼 `/v1/...`)同读。判官是 `url-shape.ts` 那一只(层级式 http(s) URL;Node 的 fetch
|
|
78
|
+
* 认 `file://` / `data:`,配错的地址不许变成读本地文件),本函数只声明这一席禁哪几件({@link PATH_BASE_FORBIDS})。起服时 `config.ts` 判
|
|
79
|
+
* (拒启),请求期 {@link centerFetch} 对同一份基址再判一次(自拼 `ServiceConfig` 的嵌入方也吃同一条规则)。合格 ⇒ `undefined`;否则交回
|
|
80
|
+
* `<subject> <规则词>; got "<展示面安全形>"` —— 拒句里没有从值里解析出来的任何记号(scheme / userinfo / 主机名),捕获点照原样落日志即可。
|
|
81
|
+
*/
|
|
82
|
+
export declare function pathBaseUrlRefusal(subject: string, raw: string): string | undefined;
|
|
71
83
|
/**
|
|
72
84
|
* S-698 —— 凭证续签:`POST {issuer}/api/v1/auth/token/refresh`,form `refresh_token=…`(settings-schema `AUTH_V1_PATHS.tokenRefresh`)。
|
|
73
85
|
* · 以 `issuer` 为根(可带路径前缀;同源与否由续签器先判,本函数只装配);query / fragment 不带过去。
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import settingsSchemaPackage from "@sema-agent/settings-schema/package.json" with { type: "json" };
|
|
2
2
|
import { AUTH_V1_PATHS } from "@sema-agent/settings-schema/api/auth";
|
|
3
|
+
import { FLEET_LIVE_PATHS, FLEET_PATHS } from "@sema-agent/settings-schema/fleet";
|
|
3
4
|
import { CenterCredentialUnavailableError } from "../center-credential.js";
|
|
5
|
+
import { buildHttpUrlRefusal, httpUrlFlaws } from "../url-shape.js";
|
|
4
6
|
export const SCHEMA_MAJOR_HEADER = "x-sema-schema-major";
|
|
5
7
|
export const CENTER_PULL_TIMEOUT_MS = 8000;
|
|
6
8
|
export const SETTINGS_SCHEMA_MAJOR = (() => {
|
|
@@ -43,23 +45,32 @@ function routePath(route, kind) {
|
|
|
43
45
|
case "prompt-blob":
|
|
44
46
|
return `${contentRoot(kind)}/prompts/blobs/${encodeURIComponent(route.digest)}`;
|
|
45
47
|
case "fleet-leases":
|
|
46
|
-
return
|
|
48
|
+
return FLEET_PATHS.quotaLeases;
|
|
47
49
|
case "fleet-instances":
|
|
48
|
-
return
|
|
50
|
+
return FLEET_PATHS.instances;
|
|
49
51
|
case "fleet-usage":
|
|
50
|
-
return
|
|
52
|
+
return FLEET_PATHS.usage;
|
|
53
|
+
case "fleet-serving-lease":
|
|
54
|
+
return FLEET_PATHS.servingLease;
|
|
51
55
|
case "execution-keys":
|
|
52
56
|
return "/api/config/execution-keys";
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
const PATH_BASE_FORBIDS = ["userinfo", "query", "fragment"];
|
|
60
|
+
export function pathBaseUrlRefusal(subject, raw) {
|
|
61
|
+
const flaws = httpUrlFlaws(raw, PATH_BASE_FORBIDS);
|
|
62
|
+
return flaws.length === 0 ? undefined : buildHttpUrlRefusal(subject, raw, flaws);
|
|
63
|
+
}
|
|
64
|
+
function assertPathBaseUrl(raw, what) {
|
|
65
|
+
const refusal = pathBaseUrlRefusal(what, raw);
|
|
66
|
+
if (refusal !== undefined)
|
|
67
|
+
throw new Error(refusal);
|
|
59
68
|
}
|
|
60
69
|
export async function centerTokenRefresh(issuer, refreshToken, init) {
|
|
70
|
+
const flaws = httpUrlFlaws(issuer, []);
|
|
71
|
+
if (flaws.length > 0)
|
|
72
|
+
throw new Error(buildHttpUrlRefusal("center credential issuer", issuer, flaws));
|
|
61
73
|
const url = new URL(issuer);
|
|
62
|
-
assertHttpScheme(url, "center credential issuer");
|
|
63
74
|
url.pathname = `${url.pathname.replace(/\/+$/, "")}${AUTH_V1_PATHS.tokenRefresh}`;
|
|
64
75
|
url.search = "";
|
|
65
76
|
url.hash = "";
|
|
@@ -72,7 +83,7 @@ export async function centerTokenRefresh(issuer, refreshToken, init) {
|
|
|
72
83
|
});
|
|
73
84
|
}
|
|
74
85
|
export async function centerFetch(center, route, init) {
|
|
75
|
-
|
|
86
|
+
assertPathBaseUrl(center.baseUrl, "SEMA_REGISTRY_URL");
|
|
76
87
|
const cred = center.credential.read();
|
|
77
88
|
if (!cred.ok)
|
|
78
89
|
throw new CenterCredentialUnavailableError(cred.reason);
|
|
@@ -83,7 +94,11 @@ export async function centerFetch(center, route, init) {
|
|
|
83
94
|
headers["content-type"] = "application/json";
|
|
84
95
|
if (init.etag)
|
|
85
96
|
headers["if-none-match"] = init.etag;
|
|
86
|
-
const
|
|
97
|
+
const path = routePath(route, cred.kind);
|
|
98
|
+
const liveBase = FLEET_LIVE_PATHS.includes(path) ? center.liveBaseUrl : undefined;
|
|
99
|
+
if (liveBase !== undefined)
|
|
100
|
+
assertPathBaseUrl(liveBase, "SEMA_REGISTRY_LIVE_URL");
|
|
101
|
+
const url = `${(liveBase ?? center.baseUrl).replace(/\/+$/, "")}${path}`;
|
|
87
102
|
const res = await (init.fetchImpl ?? fetch)(url, {
|
|
88
103
|
method: init.method ?? "GET",
|
|
89
104
|
headers,
|