@sema-agent/server 7.102.0 → 7.103.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +91 -0
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/USAGE.md +107 -6
- package/dist/background-session-reap.d.ts +4 -1
- package/dist/bake-runner/main.d.ts +21 -3
- package/dist/bake-runner/main.js +18 -11
- package/dist/boot/budget-tracing.d.ts +1 -0
- package/dist/boot/budget-tracing.js +3 -2
- package/dist/boot/config-center.d.ts +12 -2
- package/dist/boot/config-center.js +33 -21
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-04-stores.js +2 -2
- package/dist/boot/stage-05-execution-env.d.ts +1 -0
- package/dist/boot/stage-06-runners.d.ts +1 -0
- package/dist/boot/stage-07-capability-layer.d.ts +1 -0
- package/dist/boot/stage-08-reapers.d.ts +1 -0
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +1 -0
- package/dist/boot/stage-10-http-server.d.ts +1 -0
- package/dist/boot/stage-10-http-server.js +6 -1
- package/dist/brain.d.ts +60 -9
- package/dist/brain.js +44 -8
- package/dist/capabilities/center-plugins.d.ts +9 -3
- package/dist/capabilities/center-plugins.js +5 -10
- package/dist/config-catalog.js +9 -5
- package/dist/config-center/apply-effective.d.ts +40 -2
- package/dist/config-center/apply-effective.js +32 -12
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/mcp-revocation.d.ts +2 -2
- package/dist/config-center/mcp-revocation.js +1 -1
- package/dist/config-center/restart-signal.d.ts +39 -12
- package/dist/config-center/restart-signal.js +13 -24
- package/dist/config-types.d.ts +35 -17
- package/dist/config-types.js +3 -3
- package/dist/config.d.ts +7 -0
- package/dist/config.js +43 -17
- package/dist/env-name-allowlist-knobs.d.ts +21 -7
- package/dist/env-name-allowlist-knobs.js +7 -2
- package/dist/fleet-client.d.ts +9 -3
- package/dist/fleet-client.js +2 -0
- package/dist/host-lsp-manager.d.ts +4 -3
- package/dist/host-lsp-manager.js +2 -6
- package/dist/http/dispatch.js +4 -0
- package/dist/http/server.d.ts +4 -0
- package/dist/leader/wire.d.ts +8 -1
- package/dist/leader/wire.js +4 -3
- package/dist/model-entry-refusal.d.ts +32 -9
- package/dist/model-entry-refusal.js +22 -10
- package/dist/model-route-endpoint.d.ts +10 -0
- package/dist/model-route-endpoint.js +6 -2
- package/dist/observability/secret-env-scrub.d.ts +13 -1
- package/dist/observability/secret-env-scrub.js +15 -2
- package/dist/outbound-dispatcher.d.ts +125 -0
- package/dist/outbound-dispatcher.js +211 -0
- package/dist/plugins/host-platform.d.ts +6 -1
- package/dist/plugins/remote-env-adb.d.ts +10 -3
- package/dist/plugins/remote-env-adb.js +24 -20
- package/dist/plugins/remote-env-host.js +3 -11
- package/dist/plugins/remote-env-k8s.d.ts +8 -1
- package/dist/plugins/remote-env-k8s.js +31 -24
- package/dist/plugins/remote-env-ssh.d.ts +8 -1
- package/dist/plugins/remote-env-ssh.js +24 -18
- package/dist/plugins/remote-shell.d.ts +109 -9
- package/dist/plugins/remote-shell.js +83 -0
- package/dist/project-memory.js +3 -6
- package/dist/run-local.js +2 -0
- package/dist/server-secret-env.d.ts +89 -24
- package/dist/server-secret-env.js +21 -1
- package/dist/trace/core-keyset-guard.d.ts +6 -1
- package/dist/trace/project.js +13 -0
- package/dist/trace/redact.d.ts +11 -0
- package/dist/trace/redact.js +1 -0
- package/package.json +4 -3
- package/skills/update-config.md +23 -5
|
@@ -4,6 +4,8 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { loadRemoteExec } from "@sema-agent/settings-schema/node";
|
|
6
6
|
import { CENTER_CREDENTIAL_ENV, skillContentHash } from "@sema-agent/settings-schema";
|
|
7
|
+
import { stableStringify } from "@sema-agent/settings-schema/hash";
|
|
8
|
+
import { buildModelRefusalsSignal } from "../model-entry-refusal.js";
|
|
7
9
|
import { CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, } from "@sema-agent/core";
|
|
8
10
|
import { buildPricing } from "../budget.js";
|
|
9
11
|
import { applyCenterPlugins } from "../capabilities/center-plugins.js";
|
|
@@ -19,7 +21,7 @@ import { createConfigProvider, raceBootFetch, BOOT_FETCH_DEFERRED } from "../con
|
|
|
19
21
|
import { executionLaneCaps, executionLaneOf } from "../execution-lane-caps.js";
|
|
20
22
|
import { createKeyResolver } from "../key-resolver.js";
|
|
21
23
|
import { ensureSealedKeyStore, reportExecutionPublicKey } from "../sealed-key.js";
|
|
22
|
-
import { applyEffective, applyCenterReadFace, mutateInPlace, logEffectiveDiff, applyCenterSkills, resolveMcpServers, resolveA2aPeers, restartReasons, modelPlaneChanged, withModelPlaneOf,
|
|
24
|
+
import { applyEffective, applyCenterReadFace, mutateInPlace, logEffectiveDiff, applyCenterSkills, resolveMcpServers, resolveA2aPeers, restartReasons, modelPlaneChanged, withModelPlaneOf, bootSnapshotOf, fullyLandedForLkg, fetchPromptArtifact } from "../config-center/facade.js";
|
|
23
25
|
import { createMcpRevocationLedger, enabledMcpEntries } from "../config-center/mcp-revocation.js";
|
|
24
26
|
import { SHA256_HEX_RE } from "../digest-form.js";
|
|
25
27
|
import { createConfigApplyLedger, modelPlaneCommitted, refusedGroups } from "../config-center/apply-ledger.js";
|
|
@@ -62,6 +64,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
62
64
|
let planeDeferredNoHandoff;
|
|
63
65
|
let bootSkillStale = false;
|
|
64
66
|
let pendingRestart;
|
|
67
|
+
let modelRefusals;
|
|
65
68
|
const cc = config.configCenter;
|
|
66
69
|
const bootCredential = cc?.credential.read();
|
|
67
70
|
const bootVerdict = bootCredential === undefined ? undefined : judgeCenterCredentialAtBoot(bootCredential);
|
|
@@ -162,6 +165,16 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
162
165
|
if (!same)
|
|
163
166
|
notifySelfReport("restart_signal");
|
|
164
167
|
};
|
|
168
|
+
const publishModelRefusals = (next) => {
|
|
169
|
+
const same = stableStringify(modelRefusals ?? null) === stableStringify(next ?? null);
|
|
170
|
+
modelRefusals = next;
|
|
171
|
+
if (!same)
|
|
172
|
+
notifySelfReport("model_refusals");
|
|
173
|
+
};
|
|
174
|
+
const recordApply = (rep) => {
|
|
175
|
+
applyLedger.recordApply(rep);
|
|
176
|
+
publishModelRefusals(buildModelRefusalsSignal(rep.modelRefusals));
|
|
177
|
+
};
|
|
165
178
|
let centerPrompts;
|
|
166
179
|
let pendingCatalogFaces;
|
|
167
180
|
const retryPendingCatalog = async (phase) => {
|
|
@@ -402,7 +415,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
402
415
|
generation: lkgGen,
|
|
403
416
|
report: (rep) => {
|
|
404
417
|
lkgReport = rep;
|
|
405
|
-
|
|
418
|
+
recordApply(rep);
|
|
406
419
|
},
|
|
407
420
|
});
|
|
408
421
|
applyLedger.applied(lkgGen, lkgR.effective.version);
|
|
@@ -410,12 +423,14 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
410
423
|
const lkgPlane = modelPlaneCommitted(lkgReport);
|
|
411
424
|
if (lkgPlane)
|
|
412
425
|
markRosterLanded(lkgR.effective);
|
|
413
|
-
effective = lkgR.effective;
|
|
426
|
+
effective = bootSnapshotOf(lkgR.effective, lkgPlane ? lkgR.effective : undefined);
|
|
414
427
|
if (lkgPlane)
|
|
415
428
|
appliedPlaneEff = lkgR.effective;
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
429
|
+
if (fullyLandedForLkg(lkgR, lkgReport)) {
|
|
430
|
+
latestEffective = lkgR.effective;
|
|
431
|
+
if (lkgR.etag !== undefined)
|
|
432
|
+
ccEtag = lkgR.etag;
|
|
433
|
+
}
|
|
419
434
|
const cfgModels = (lkgR.effective.models?.models ?? []).filter((m) => m.enabled !== false).length;
|
|
420
435
|
logger.info("config_loaded", { source: "lkg", version: lkgR.effective.version, worker: cc?.worker ?? "(global)", models: cfgModels });
|
|
421
436
|
};
|
|
@@ -450,15 +465,12 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
450
465
|
const r = raced === BOOT_FETCH_DEFERRED ? null : raced;
|
|
451
466
|
if (r) {
|
|
452
467
|
bootApplyPhase = true;
|
|
453
|
-
const bootPromptsRaw = r.effective.prompts;
|
|
454
|
-
const bootPromptsOk = bootPromptsRaw === undefined || validatePromptsDomain(bootPromptsRaw).ok;
|
|
455
|
-
let bootClean = (r.domainErrors ?? []).length === 0 && bootPromptsOk;
|
|
456
468
|
for (const de of r.domainErrors ?? [])
|
|
457
469
|
logger.warn("config_domain_invalid", { domain: de.domain, error: de.error, note: "tolerated at BOOT (corrupt file → schema defaults; dangling ref → reported as-is); the refresh path rejects such a candidate whole and keeps the LKG (§9.5-5)" });
|
|
458
470
|
warnDroppedEntries(r, "boot");
|
|
459
471
|
if (dryRun) {
|
|
460
472
|
logEffectiveDiff(config, r.effective, logger);
|
|
461
|
-
if (
|
|
473
|
+
if (fullyLandedForLkg(r, undefined))
|
|
462
474
|
ccEtag = r.etag;
|
|
463
475
|
}
|
|
464
476
|
else {
|
|
@@ -471,12 +483,11 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
471
483
|
generation: bootGen,
|
|
472
484
|
report: (rep) => {
|
|
473
485
|
bootReport = rep;
|
|
474
|
-
|
|
486
|
+
recordApply(rep);
|
|
475
487
|
},
|
|
476
488
|
});
|
|
477
489
|
applyLedger.applied(bootGen, r.effective.version);
|
|
478
|
-
|
|
479
|
-
bootClean = false;
|
|
490
|
+
const bootClean = fullyLandedForLkg(r, bootReport);
|
|
480
491
|
if (bootClean)
|
|
481
492
|
ccEtag = r.etag;
|
|
482
493
|
await adoptCenterPrompts(r.effective, "boot");
|
|
@@ -494,7 +505,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
494
505
|
}
|
|
495
506
|
if (bootPlaneEff !== undefined)
|
|
496
507
|
markRosterLanded(bootPlaneEff);
|
|
497
|
-
effective =
|
|
508
|
+
effective = bootSnapshotOf(r.effective, bootPlaneEff);
|
|
498
509
|
if (bootPlaneEff !== undefined)
|
|
499
510
|
appliedPlaneEff = bootPlaneEff;
|
|
500
511
|
if (bootClean)
|
|
@@ -553,8 +564,8 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
553
564
|
let keyResolver;
|
|
554
565
|
const mcpRevocations = createMcpRevocationLedger((ev) => logger.info("mcp_revocation_ledger", { ...(ev.revoked.length > 0 ? { revoked: ev.revoked } : {}), ...(ev.restored.length > 0 ? { restored: ev.restored } : {}) }));
|
|
555
566
|
let mcpRevocationWired = false;
|
|
556
|
-
const restartCtx = (consolidationSeat) => ({
|
|
557
|
-
|
|
567
|
+
const restartCtx = (consolidationSeat, degradeRoute) => ({
|
|
568
|
+
degradeRoute,
|
|
558
569
|
pluginsAllowHosts: config.pluginsAllowHosts,
|
|
559
570
|
...(consolidationSeat !== undefined ? { consolidationSeat } : {}),
|
|
560
571
|
...(readFaceEnvHeld.length > 0 ? { readFaceEnvHeld } : {}),
|
|
@@ -592,6 +603,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
592
603
|
selfReportListeners.push(listener);
|
|
593
604
|
},
|
|
594
605
|
restartState: () => pendingRestart,
|
|
606
|
+
modelRefusalsState: () => modelRefusals,
|
|
595
607
|
planeDeferredState: () => planeDeferredNoHandoff,
|
|
596
608
|
configApplyState: () => (configProvider ? applyLedger.view() : undefined),
|
|
597
609
|
centerManagedKeys: () => (configProvider ? centerManagedConfigKeys(latestEffective) : undefined),
|
|
@@ -724,7 +736,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
724
736
|
}
|
|
725
737
|
},
|
|
726
738
|
startRefreshLoop(a) {
|
|
727
|
-
const { pricing, limitSync, swapRunnerDeps, ceilingVerdict, consolidationSeat, checkpointStorePresent } = a;
|
|
739
|
+
const { pricing, limitSync, swapRunnerDeps, ceilingVerdict, consolidationSeat, degradeRoute, checkpointStorePresent } = a;
|
|
728
740
|
if (configProvider) {
|
|
729
741
|
const ccRef = config.configCenter;
|
|
730
742
|
let refreshInFlight = false;
|
|
@@ -800,7 +812,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
800
812
|
generation: tickGen,
|
|
801
813
|
report: (rep) => {
|
|
802
814
|
applyReport = rep;
|
|
803
|
-
|
|
815
|
+
recordApply(rep);
|
|
804
816
|
},
|
|
805
817
|
...(planeChanged ? { swapPlane: swapRunnerDeps } : {}),
|
|
806
818
|
});
|
|
@@ -833,7 +845,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
833
845
|
return "ok";
|
|
834
846
|
}
|
|
835
847
|
const lkgPersisted = await persistLkgDurable(r.effective, r.etag);
|
|
836
|
-
const reasons = restartReasons(effective, r.effective, restartCtx(consolidationSeat?.read()));
|
|
848
|
+
const reasons = restartReasons(effective, r.effective, restartCtx(consolidationSeat?.read(), degradeRoute.read()));
|
|
837
849
|
const skillsChangeDetected = reasons.includes("skills");
|
|
838
850
|
if (bootSkillStale && !skillsChangeDetected)
|
|
839
851
|
reasons.push("skills");
|
|
@@ -932,7 +944,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
932
944
|
generation: lateGen,
|
|
933
945
|
report: (rep) => {
|
|
934
946
|
applyReportLate = rep;
|
|
935
|
-
|
|
947
|
+
recordApply(rep);
|
|
936
948
|
},
|
|
937
949
|
...(planeChangedLate ? { swapPlane: swapRunnerDeps } : {}),
|
|
938
950
|
});
|
|
@@ -967,7 +979,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
967
979
|
latestEffective = r.effective;
|
|
968
980
|
ccEtag = r.etag;
|
|
969
981
|
const lkgPersistedLate = await persistLkgDurable(r.effective, r.etag);
|
|
970
|
-
const reasons = restartReasons(undefined, r.effective, restartCtx(consolidationSeat?.read()));
|
|
982
|
+
const reasons = restartReasons(undefined, r.effective, restartCtx(consolidationSeat?.read(), degradeRoute.read()));
|
|
971
983
|
if (lkgSurvivesRestart && !lkgPersistedLate) {
|
|
972
984
|
const blocked = reasons.splice(0, reasons.length);
|
|
973
985
|
planeDeferredNoHandoff = { version: r.effective.version, since: Date.now(), blocked, candidate: r.effective, ...(r.etag !== undefined ? { candidateEtag: r.etag } : {}) };
|
|
@@ -12,6 +12,7 @@ import { installFileHistoryFailureRecorder } from "../file-history-disclosure.js
|
|
|
12
12
|
import { installSecretEnvScrubRecorder } from "../observability/secret-env-scrub.js";
|
|
13
13
|
import { setRunTerminalLogObserver } from "../observability/run-terminal.js";
|
|
14
14
|
import { setRedactionObserver } from "../trace/redact.js";
|
|
15
|
+
import { installOutboundDispatcher } from "../outbound-dispatcher.js";
|
|
15
16
|
export function runStage01() {
|
|
16
17
|
const config = loadConfig();
|
|
17
18
|
const runStaleFailure = crossInvariant("run-stale-heartbeat").check({ live: config, next: config });
|
|
@@ -56,6 +57,7 @@ export function runStage01() {
|
|
|
56
57
|
note: "LOCAL_DATA_ROOT moved the local backend's DATA root, but config.d / remote-exec.json are still read from the config root (LOCAL_DATA_ROOT is not consulted there). If you meant to move both, set CONFIG_LOCAL_DIR to the same directory.",
|
|
57
58
|
});
|
|
58
59
|
}
|
|
60
|
+
installOutboundDispatcher(logger);
|
|
59
61
|
return { config, localRoot, logger, metrics };
|
|
60
62
|
}
|
|
61
63
|
//# sourceMappingURL=stage-01-config.js.map
|
|
@@ -2,6 +2,7 @@ import type { runStage03 } from "./stage-03-store-backend.js";
|
|
|
2
2
|
export declare function runStage04(ctx: Awaited<ReturnType<typeof runStage03>>): Promise<{
|
|
3
3
|
brain: import("@sema-agent/core").Brain;
|
|
4
4
|
costQuota: import("../observability/cost-quota.js").CostQuota | import("../plugins/pg-cost-quota.js").PgCostQuota | import("../plugins/tidb-cost-quota.js").TiDBCostQuota;
|
|
5
|
+
degradeRouteWatch: import("../brain.js").DegradeRouteWatch;
|
|
5
6
|
counterDegradeHook: (info: {
|
|
6
7
|
table: string;
|
|
7
8
|
kind: string;
|
|
@@ -27,7 +27,7 @@ export async function runStage04(ctx) {
|
|
|
27
27
|
if (adoptionBoot.scanned > 0 || adoptionBoot.error !== undefined) {
|
|
28
28
|
logger.info("adoption_boot_scan", { ...adoptionBoot });
|
|
29
29
|
}
|
|
30
|
-
const { brain, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker, fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileHistoryStore, fileHistoryErasure, } = createBudgetAndTracing({ config, logger, metrics, backend, breakerState });
|
|
30
|
+
const { brain, degradeRouteWatch, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker, fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileHistoryStore, fileHistoryErasure, } = createBudgetAndTracing({ config, logger, metrics, backend, breakerState });
|
|
31
31
|
const permissionRuleStores = config.permissionRulesEnabled && backend ? backend.permissionRule() : undefined;
|
|
32
32
|
const permissionRuleProvider = permissionRuleStores
|
|
33
33
|
? createPermissionRuleStoreProvider({ durable: permissionRuleStores.durable })
|
|
@@ -44,6 +44,6 @@ export async function runStage04(ctx) {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
await auditDormantPermissionRules({ stores: permissionRuleStores, logger, explicit: config.permissionRulesEnabledExplicit });
|
|
47
|
-
return { ...ctx, brain, costQuota, counterDegradeHook, fileHistoryErasure, fileHistoryStore, fleetLease, fleetUsage, memoryBundleFaces, memoryComplianceFaces, memoryOriginFaces, modelUsageTracker, permissionRuleProvider, permissionRuleStores, pricing, promptManifestTracker, ruleConsent, sessionMemoryStatusFace, sessionPolicyStore, sideQueryAccounting, toolResultStore, tracer };
|
|
47
|
+
return { ...ctx, brain, costQuota, degradeRouteWatch, counterDegradeHook, fileHistoryErasure, fileHistoryStore, fleetLease, fleetUsage, memoryBundleFaces, memoryComplianceFaces, memoryOriginFaces, modelUsageTracker, permissionRuleProvider, permissionRuleStores, pricing, promptManifestTracker, ruleConsent, sessionMemoryStatusFace, sessionPolicyStore, sideQueryAccounting, toolResultStore, tracer };
|
|
48
48
|
}
|
|
49
49
|
//# sourceMappingURL=stage-04-stores.js.map
|
|
@@ -15,6 +15,7 @@ export declare function runStage05(ctx: Awaited<ReturnType<typeof runStage04>>):
|
|
|
15
15
|
worktreeReap: (() => Promise<void>) | undefined;
|
|
16
16
|
brain: import("@sema-agent/core").Brain;
|
|
17
17
|
costQuota: import("../observability/cost-quota.js").CostQuota | import("../plugins/pg-cost-quota.js").PgCostQuota | import("../plugins/tidb-cost-quota.js").TiDBCostQuota;
|
|
18
|
+
degradeRouteWatch: import("../brain.js").DegradeRouteWatch;
|
|
18
19
|
counterDegradeHook: (info: {
|
|
19
20
|
table: string;
|
|
20
21
|
kind: string;
|
|
@@ -76,6 +76,7 @@ export declare function runStage06(ctx: Awaited<ReturnType<typeof runStage05>>):
|
|
|
76
76
|
worktreeReap: (() => Promise<void>) | undefined;
|
|
77
77
|
brain: import("@sema-agent/core").Brain;
|
|
78
78
|
costQuota: import("../observability/cost-quota.js").CostQuota | import("../plugins/pg-cost-quota.js").PgCostQuota | import("../plugins/tidb-cost-quota.js").TiDBCostQuota;
|
|
79
|
+
degradeRouteWatch: import("../brain.js").DegradeRouteWatch;
|
|
79
80
|
counterDegradeHook: (info: {
|
|
80
81
|
table: string;
|
|
81
82
|
kind: string;
|
|
@@ -90,6 +90,7 @@ export declare function runStage07(ctx: Awaited<ReturnType<typeof runStage06>>):
|
|
|
90
90
|
worktreeReap: (() => Promise<void>) | undefined;
|
|
91
91
|
brain: import("@sema-agent/core").Brain;
|
|
92
92
|
costQuota: import("../observability/cost-quota.js").CostQuota | import("../plugins/pg-cost-quota.js").PgCostQuota | import("../plugins/tidb-cost-quota.js").TiDBCostQuota;
|
|
93
|
+
degradeRouteWatch: import("../brain.js").DegradeRouteWatch;
|
|
93
94
|
counterDegradeHook: (info: {
|
|
94
95
|
table: string;
|
|
95
96
|
kind: string;
|
|
@@ -103,6 +103,7 @@ export declare function runStage08(ctx: Awaited<ReturnType<typeof runStage07>>):
|
|
|
103
103
|
worktreeReap: (() => Promise<void>) | undefined;
|
|
104
104
|
brain: import("@sema-agent/core").Brain;
|
|
105
105
|
costQuota: import("../observability/cost-quota.js").CostQuota | import("../plugins/pg-cost-quota.js").PgCostQuota | import("../plugins/tidb-cost-quota.js").TiDBCostQuota;
|
|
106
|
+
degradeRouteWatch: import("../brain.js").DegradeRouteWatch;
|
|
106
107
|
counterDegradeHook: (info: {
|
|
107
108
|
table: string;
|
|
108
109
|
kind: string;
|
|
@@ -5,7 +5,7 @@ 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, fileHistoryErasure, handslessRunner, handslessSubRunner, hookAgentRunner, imageBakes, instanceId, localRoot, logger, mailboxStore, metrics, permissionRuleStores, pricing, rateLimiter, retentionExecutor, retentionLaneStore, rosterStore, runStore, runner, sqlWorkflowRunStore, subRunner, taskAttachmentStore, toolApproval, toolResultStore, workflowJournalStore, workflowNotifyGate, workflowNotifyJournal, workflowRecoverOpts, worktreeReap } = 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, workflowJournalStore, workflowNotifyGate, workflowNotifyJournal, workflowRecoverOpts, worktreeReap } = ctx;
|
|
9
9
|
const reaper = startReapers({
|
|
10
10
|
config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore,
|
|
11
11
|
rateLimiter, costQuota, toolResultStore, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap,
|
|
@@ -71,7 +71,7 @@ export function runStage08(ctx) {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
|
-
configCenter.startRefreshLoop({ pricing, limitSync, swapRunnerDeps, ceilingVerdict, checkpointStorePresent: checkpointStore !== undefined, ...(consolidationSeatWatch !== undefined ? { consolidationSeat: consolidationSeatWatch } : {}) });
|
|
74
|
+
configCenter.startRefreshLoop({ pricing, limitSync, swapRunnerDeps, ceilingVerdict, checkpointStorePresent: checkpointStore !== undefined, degradeRoute: degradeRouteWatch, ...(consolidationSeatWatch !== undefined ? { consolidationSeat: consolidationSeatWatch } : {}) });
|
|
75
75
|
configCenter.initKeyResolver();
|
|
76
76
|
return { ...ctx, otelExporter, reaper, retentionLaneStarted };
|
|
77
77
|
}
|
|
@@ -137,6 +137,7 @@ export declare function runStage09(ctx: Awaited<ReturnType<typeof runStage08>>):
|
|
|
137
137
|
worktreeReap: (() => Promise<void>) | undefined;
|
|
138
138
|
brain: import("@sema-agent/core").Brain;
|
|
139
139
|
costQuota: import("../observability/cost-quota.js").CostQuota | import("../plugins/pg-cost-quota.js").PgCostQuota | import("../plugins/tidb-cost-quota.js").TiDBCostQuota;
|
|
140
|
+
degradeRouteWatch: import("../brain.js").DegradeRouteWatch;
|
|
140
141
|
counterDegradeHook: (info: {
|
|
141
142
|
table: string;
|
|
142
143
|
kind: string;
|
|
@@ -137,6 +137,7 @@ export declare function runStage10(ctx: Awaited<ReturnType<typeof runStage09>>):
|
|
|
137
137
|
worktreeReap: (() => Promise<void>) | undefined;
|
|
138
138
|
brain: import("@sema-agent/core").Brain;
|
|
139
139
|
costQuota: import("../observability/cost-quota.js").CostQuota | import("../plugins/pg-cost-quota.js").PgCostQuota | import("../plugins/tidb-cost-quota.js").TiDBCostQuota;
|
|
140
|
+
degradeRouteWatch: import("../brain.js").DegradeRouteWatch;
|
|
140
141
|
counterDegradeHook: (info: {
|
|
141
142
|
table: string;
|
|
142
143
|
kind: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { autoRunOnRecommendationEffective, defaultTaskRegistry } from "@sema-agent/core";
|
|
2
2
|
import { posIntEnv } from "../session-watch.js";
|
|
3
|
-
import { brainSummary } from "../brain.js";
|
|
3
|
+
import { brainSummary, buildFailoverFirstByteNotice } from "../brain.js";
|
|
4
4
|
import { resolveBindHost } from "../config.js";
|
|
5
5
|
import { buildWriteProtectionPosture } from "../write-protection.js";
|
|
6
6
|
import { isPricingConfigured } from "../budget.js";
|
|
@@ -151,6 +151,7 @@ export async function runStage10(ctx) {
|
|
|
151
151
|
planeDeferredState: () => configCenter.planeDeferredState(),
|
|
152
152
|
configApplyState: () => configCenter.configApplyState(),
|
|
153
153
|
centerCredentialState: () => configCenter.centerCredentialState(),
|
|
154
|
+
modelRefusalsState: () => configCenter.modelRefusalsState(),
|
|
154
155
|
mcpRevocations,
|
|
155
156
|
mcpRevocationState: () => configCenter.mcpRevocationState(),
|
|
156
157
|
configRefreshNow: () => configCenter.refreshNow(),
|
|
@@ -241,12 +242,16 @@ export async function runStage10(ctx) {
|
|
|
241
242
|
version: serviceVersion(),
|
|
242
243
|
readiness: () => readinessOf(deployment),
|
|
243
244
|
restart: () => deployment.restartState?.(),
|
|
245
|
+
modelRefusals: () => deployment.modelRefusalsState?.(),
|
|
244
246
|
...(fleetUsage ? { usage: fleetUsage } : {}),
|
|
245
247
|
logger: { info: (m, d) => logger.info(m, d ?? {}), warn: (m, d) => logger.warn(m, d ?? {}) },
|
|
246
248
|
});
|
|
247
249
|
configCenter.onSelfReportChange(() => void fleetClient?.announceNow());
|
|
248
250
|
if (fleetClient)
|
|
249
251
|
logger.info("fleet_client_started", { worker: config.configCenter?.worker, address: buildDisplaySafeUrl(process.env.FLEET_ADVERTISE_ADDRESS ?? "") });
|
|
252
|
+
const failoverFirstByte = buildFailoverFirstByteNotice(config);
|
|
253
|
+
if (failoverFirstByte)
|
|
254
|
+
logger.warn("failover_first_byte_lane", failoverFirstByte);
|
|
250
255
|
logger.info("listening", {
|
|
251
256
|
port: config.port,
|
|
252
257
|
bindHost: bindHost ?? "0.0.0.0/::(all interfaces)",
|
package/dist/brain.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type Brain, type BreakerState } from "@sema-agent/core";
|
|
1
|
+
import { type Brain, type BreakerState, type Model } from "@sema-agent/core";
|
|
2
2
|
import type { ServiceConfig } from "./config-types.js";
|
|
3
|
+
import { type RestartSliceCtx } from "./config-center/restart-signal.js";
|
|
3
4
|
/**
|
|
4
5
|
* 配了快切层(failover ≥2 路 / 断路器)时,**每路**的重试上限。
|
|
5
6
|
*
|
|
@@ -12,12 +13,14 @@ export declare const FAST_FAIL_MAX_RETRIES = 2;
|
|
|
12
13
|
* Build the external "Brain" (LLM) from config.
|
|
13
14
|
*
|
|
14
15
|
* Composition (A1 + the 1.38 resilience stack, ordered timeout-innermost → breaker → failover-outermost):
|
|
15
|
-
* - **Tiered timeouts** (1.38
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* prefill legitimately takes minutes
|
|
20
|
-
* same prompt back to the same queue
|
|
16
|
+
* - **Tiered timeouts** (1.38; re-cut by core 7.33.0 along the segments of a call) are passed into each
|
|
17
|
+
* brain at creation: `firstByteTimeoutMs` (fetch() → response headers; S-753: **passed only when the
|
|
18
|
+
* operator set `MODEL_FIRST_BYTE_TIMEOUT_MS`** — absent = no key, core's own default, which carries an
|
|
19
|
+
* upload allowance and one widened retry of its own), `firstTokenTimeoutMs` (headers → first delta,
|
|
20
|
+
* 600s since S-202 — a self-hosted backend's long prefill legitimately takes minutes, and 120s turned that
|
|
21
|
+
* into a retry handing the same prompt back to the same queue) and `idleTimeoutMs`. The TCP + TLS connect
|
|
22
|
+
* is the transport's own clock (core 7.33.0), not a knob here. env =0 = no engine clock on that segment
|
|
23
|
+
* and the transport's header / body clocks off too (the connect keeps the transport's own clock).
|
|
21
24
|
* - **Circuit breaker** (`createCircuitBreakerBrain`) wraps the *primary* gateway route(s) when
|
|
22
25
|
* `MODEL_CIRCUIT_BREAKER=true`: after N transient failures it opens and fast-fails, so failover
|
|
23
26
|
* switches to the bare backup immediately instead of waiting out each timeout. The last gateway
|
|
@@ -30,14 +33,62 @@ export declare const FAST_FAIL_MAX_RETRIES = 2;
|
|
|
30
33
|
* ↔ vLLM is a *routing* choice, not failover.)
|
|
31
34
|
*
|
|
32
35
|
* When no fallback URL, no Anthropic key, and no resilience thresholds are configured, this returns the
|
|
33
|
-
* bare openai brain (carrying the
|
|
34
|
-
*
|
|
36
|
+
* bare openai brain (carrying the watchdogs: core's own first-byte default unless `MODEL_FIRST_BYTE_TIMEOUT_MS`
|
|
37
|
+
* is set, plus the server's first-token / idle defaults; a knob at 0 = no engine clock on that segment and the
|
|
38
|
+
* transport's header / body clocks off too). The Model itself (id / contextWindow / maxTokens / provider) flows
|
|
35
39
|
* per-request through `Runner({ models })`.
|
|
36
40
|
*/
|
|
37
41
|
export declare function createBrain(config: ServiceConfig, deps?: {
|
|
38
42
|
fetchImpl?: typeof fetch;
|
|
39
43
|
breakerState?: BreakerState;
|
|
40
44
|
}): Brain;
|
|
45
|
+
/** {@link degradeRouteFingerprint} 读的那几张表(全是活引用:refresh 就地换代,读数每次现取)。 */
|
|
46
|
+
export type DegradeRoutePlane = Pick<ServiceConfig, "degrade" | "models" | "gatewayBaseUrl" | "modelApiKeyEnv" | "modelApiKeys">;
|
|
47
|
+
/**
|
|
48
|
+
* S-794:反应式降级壳焊的是哪一只目标 —— **唯一判据**(createBrain 组壳、启动摘要、`degrade-route` 片的两侧读数同读这一只)。
|
|
49
|
+
* 车道没开(非 reactive)或表里没有叫 `degrade.to` 的条目 ⇒ `undefined`(createBrain 不装降级壳)。
|
|
50
|
+
*/
|
|
51
|
+
export declare function degradeTargetOf(config: Pick<ServiceConfig, "degrade" | "models">): Model | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* S-794:降级壳的**路由指纹**(纯数据)—— `degrade-route` 重启片 `baked` / `current` 两侧读数的唯一读法。
|
|
54
|
+
*
|
|
55
|
+
* 壳在 createBrain 那一刻焊死目标那只 `Model` 对象(env 派生或目录派生):请求打 `model.baseUrl`(缺席回网关根)、body 带
|
|
56
|
+
* `model.id` / `maxTokens` / `compat` / …,走哪条腿看 `provider`;refresh 就地换的是表里的**值**,够不到那只对象。于是指纹 =
|
|
57
|
+
* · 那只 Model 经 `catalogEntryFingerprint`(与 swap 门同一张热字段减法:单价**数值**热、在场性不热;其余字段一律算 —— deny-list,
|
|
58
|
+
* 漂移的失败形是多签一次,绝不漏签);
|
|
59
|
+
* · 有效端点根(S-623 同一只解析器 `openaiRouteBaseUrl(baseUrl, gatewayBaseUrl)`);
|
|
60
|
+
* · 它配的凭据**身份**:`apiKeyEnv`(变量名,不是值)与托管密钥**在场位**(不取值:值是热的,且是秘密)。两次查表的键与
|
|
61
|
+
* `fbOwnApiKey` 同(目标的真名,再 `degrade.to`)。
|
|
62
|
+
* 没有焊壳(车道没开 / 没有目标)⇒ `null`。在场 ↔ 缺席的翻转因此自然是一次变化。
|
|
63
|
+
*/
|
|
64
|
+
export declare function degradeRouteFingerprint(config: DegradeRoutePlane): unknown;
|
|
65
|
+
/** `degrade-route` 片的席:boot 焊进去的那一只 vs 活配置此刻的同一只目标(`restartReasons` 的 `degradeRoute` 两侧读数)。 */
|
|
66
|
+
export interface DegradeRouteWatch {
|
|
67
|
+
read(): NonNullable<RestartSliceCtx["degradeRoute"]>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* `create*`:返回带捕获状态(组壳那一刻的指纹)的活对象。**须在 {@link createBrain} 的同一时刻、同一份 config 上调**
|
|
71
|
+
* (生产:boot/budget-tracing.ts 紧随 createBrain)—— 否则 `baked` 就不是壳真焊进去的那一只。与 boot/memory-consolidation.ts
|
|
72
|
+
* `createConsolidationSeatWatch` 同形。
|
|
73
|
+
*/
|
|
74
|
+
export declare function createDegradeRouteWatch(config: DegradeRoutePlane): DegradeRouteWatch;
|
|
75
|
+
/**
|
|
76
|
+
* S-753 复审(opus F2 + codex C1)—— **failover 拓扑 × 首字节段没有部署窗**的启动期披露(纯数据,`build*`;告警由 boot 出)。
|
|
77
|
+
*
|
|
78
|
+
* 缺省下首字节段不铸键(core 自缺省当家,[ref])、`0` = 该段不设引擎钟;而切备只发生在一次调用**失败之后**。于是主网关接了连接
|
|
79
|
+
* 却不回响应头时:缺省 ⇒ 要等 core 的首字节 lane 走完(缺省首窗 + 一次加宽重试;本仓不铸 `firstByteRetries`,S-753 车PA)才切备,
|
|
80
|
+
* 时间预算短于那一段的调用在备网关被尝试之前就被取消 —— 以 core 7.33.x 缺省计 lane ≈ 15 min,code-review council 每个镜头的 180 s 与 team
|
|
81
|
+
* discuss 的 600 s 都在其内(车PA codex r1:600 s 在主网关的加宽重试窗里到点,不是「只剩余量」);
|
|
82
|
+
* `0` ⇒ 首字节 lane 永不切备。启动日志里同行的 `gatewayRetryCap:"fast-fail:…"` 只管共享重试梯子,读起来却像「会快切」—— 这条披露把它补齐。
|
|
83
|
+
* 显式设了正数窗 ⇒ `undefined`(部署已表态);单路由 ⇒ `undefined`(没有备可切)。**不替运维铸窗**:出路是旋钮,不是本仓的数。
|
|
84
|
+
*/
|
|
85
|
+
export declare function buildFailoverFirstByteNotice(config: ServiceConfig): {
|
|
86
|
+
readonly knob: "MODEL_FIRST_BYTE_TIMEOUT_MS";
|
|
87
|
+
readonly fallbacks: number;
|
|
88
|
+
readonly firstByteTimeoutMs: number | null;
|
|
89
|
+
readonly effect: string;
|
|
90
|
+
readonly fix: string;
|
|
91
|
+
} | undefined;
|
|
41
92
|
/** One-line summary of the active brain composition, for the startup log. */
|
|
42
93
|
export declare function brainSummary(config: ServiceConfig): Record<string, unknown>;
|
|
43
94
|
//# sourceMappingURL=brain.d.ts.map
|
package/dist/brain.js
CHANGED
|
@@ -4,6 +4,7 @@ import { buildDefaultResilience } from "./config-types.js";
|
|
|
4
4
|
import { resolveModelApiKey } from "./key-resolver.js";
|
|
5
5
|
import { dispatchBrainLane } from "./model-provider.js";
|
|
6
6
|
import { openaiRouteBaseUrl } from "./model-route-endpoint.js";
|
|
7
|
+
import { catalogEntryFingerprint } from "./config-center/restart-signal.js";
|
|
7
8
|
import { buildDisplaySafeRefusal, buildDisplaySafeUrl } from "./trace/redact.js";
|
|
8
9
|
function isOffDeploymentRoot(baseUrl, config) {
|
|
9
10
|
if (!baseUrl)
|
|
@@ -38,7 +39,7 @@ export function createBrain(config, deps = {}) {
|
|
|
38
39
|
const { fetchImpl, breakerState } = deps;
|
|
39
40
|
const r = config.resilience ?? DEFAULT_RESILIENCE;
|
|
40
41
|
const timeouts = {
|
|
41
|
-
|
|
42
|
+
...(r.firstByteTimeoutMs !== undefined ? { firstByteTimeoutMs: r.firstByteTimeoutMs } : {}),
|
|
42
43
|
firstTokenTimeoutMs: r.firstTokenTimeoutMs,
|
|
43
44
|
idleTimeoutMs: r.idleTimeoutMs,
|
|
44
45
|
};
|
|
@@ -93,7 +94,7 @@ export function createBrain(config, deps = {}) {
|
|
|
93
94
|
: gatewayBrain;
|
|
94
95
|
if (config.degrade?.reactive) {
|
|
95
96
|
const degradeTo = config.degrade.to;
|
|
96
|
-
const fallbackModel = config
|
|
97
|
+
const fallbackModel = degradeTargetOf(config);
|
|
97
98
|
if (fallbackModel) {
|
|
98
99
|
const fbOwnApiKey = () => resolveModelApiKey(fallbackModel.name, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {}) ??
|
|
99
100
|
resolveModelApiKey(degradeTo, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {});
|
|
@@ -135,11 +136,46 @@ export function createBrain(config, deps = {}) {
|
|
|
135
136
|
}
|
|
136
137
|
return brain;
|
|
137
138
|
}
|
|
139
|
+
export function degradeTargetOf(config) {
|
|
140
|
+
return config.degrade?.reactive ? config.models?.[config.degrade.to] : undefined;
|
|
141
|
+
}
|
|
142
|
+
export function degradeRouteFingerprint(config) {
|
|
143
|
+
const target = degradeTargetOf(config);
|
|
144
|
+
if (target === undefined || config.degrade === undefined)
|
|
145
|
+
return null;
|
|
146
|
+
const to = config.degrade.to;
|
|
147
|
+
return {
|
|
148
|
+
model: catalogEntryFingerprint(target),
|
|
149
|
+
endpoint: openaiRouteBaseUrl(target.baseUrl, config.gatewayBaseUrl) ?? null,
|
|
150
|
+
apiKeyEnv: config.modelApiKeyEnv?.[target.name] ?? config.modelApiKeyEnv?.[to] ?? null,
|
|
151
|
+
sealedKey: config.modelApiKeys?.[target.name] !== undefined || config.modelApiKeys?.[to] !== undefined,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export function createDegradeRouteWatch(config) {
|
|
155
|
+
const baked = degradeRouteFingerprint(config);
|
|
156
|
+
return { read: () => ({ baked, current: degradeRouteFingerprint(config) }) };
|
|
157
|
+
}
|
|
158
|
+
export function buildFailoverFirstByteNotice(config) {
|
|
159
|
+
const r = config.resilience ?? DEFAULT_RESILIENCE;
|
|
160
|
+
const fallbacks = config.gatewayFallbackUrls.length;
|
|
161
|
+
if (fallbacks === 0 || (r.firstByteTimeoutMs !== undefined && r.firstByteTimeoutMs > 0))
|
|
162
|
+
return undefined;
|
|
163
|
+
const zero = r.firstByteTimeoutMs === 0;
|
|
164
|
+
return {
|
|
165
|
+
knob: "MODEL_FIRST_BYTE_TIMEOUT_MS",
|
|
166
|
+
fallbacks,
|
|
167
|
+
firstByteTimeoutMs: r.firstByteTimeoutMs ?? null,
|
|
168
|
+
effect: zero
|
|
169
|
+
? "the first-byte lane never fails over a primary that accepts the connection but sends no response headers (0 = no engine clock on that segment); only a run-level limit ends that wait"
|
|
170
|
+
: "a primary that accepts the connection but sends no response headers is failed over only after core's default first-byte lane runs out (the first window, then one widened retry); a call with a time budget shorter than that — the code-review council's 180 s lenses and team discuss's 600 s, with core 7.33.x defaults — is cancelled before the backup is ever tried",
|
|
171
|
+
fix: "set MODEL_FIRST_BYTE_TIMEOUT_MS to the longest response-header wait a healthy primary needs (queue + prefill), so a hung primary is cut over after that window and its one widened retry",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
138
174
|
export function brainSummary(config) {
|
|
139
175
|
const r = config.resilience ?? DEFAULT_RESILIENCE;
|
|
140
176
|
const hasFailover = config.gatewayFallbackUrls.length > 0;
|
|
141
177
|
const breakerActive = r.circuitBreaker && hasFailover;
|
|
142
|
-
const degradeTarget = config
|
|
178
|
+
const degradeTarget = degradeTargetOf(config);
|
|
143
179
|
const degradeEndpoint = degradeTarget ? openaiRouteBaseUrl(degradeTarget.baseUrl, config.gatewayBaseUrl) : undefined;
|
|
144
180
|
return {
|
|
145
181
|
gateway: buildDisplaySafeUrl(config.gatewayBaseUrl ?? "") || null,
|
|
@@ -147,9 +183,9 @@ export function brainSummary(config) {
|
|
|
147
183
|
failover: hasFailover,
|
|
148
184
|
anthropicRoute: Boolean(config.anthropic),
|
|
149
185
|
anthropicCacheBreakpoints: config.anthropic?.cacheBreakpoints ?? null,
|
|
150
|
-
|
|
151
|
-
firstTokenTimeoutMs: r.firstTokenTimeoutMs
|
|
152
|
-
idleTimeoutMs: r.idleTimeoutMs
|
|
186
|
+
firstByteTimeoutMs: r.firstByteTimeoutMs ?? null,
|
|
187
|
+
firstTokenTimeoutMs: r.firstTokenTimeoutMs,
|
|
188
|
+
idleTimeoutMs: r.idleTimeoutMs,
|
|
153
189
|
circuitBreaker: breakerActive,
|
|
154
190
|
circuitBreakerRequested: r.circuitBreaker,
|
|
155
191
|
gatewayRetryCap: config.gatewayMaxRetries !== undefined
|
|
@@ -158,10 +194,10 @@ export function brainSummary(config) {
|
|
|
158
194
|
? `fast-fail:${FAST_FAIL_MAX_RETRIES}`
|
|
159
195
|
: "engine-default",
|
|
160
196
|
circuitBreakerNoop: r.circuitBreaker && !hasFailover ? "needs ≥2 gateway routes" : null,
|
|
161
|
-
reactiveDegradeTo:
|
|
197
|
+
reactiveDegradeTo: degradeTarget !== undefined ? (config.degrade?.to ?? null) : null,
|
|
162
198
|
reactiveDegradeBaseUrl: buildDisplaySafeUrl(degradeEndpoint ?? "") || null,
|
|
163
199
|
...((() => {
|
|
164
|
-
const target =
|
|
200
|
+
const target = degradeTarget;
|
|
165
201
|
if (!target)
|
|
166
202
|
return { reactiveDegradeRoutePairing: null, reactiveDegradeRouteRefusal: null };
|
|
167
203
|
if (dispatchBrainLane(target.provider, config.anthropic !== undefined) === "anthropic") {
|
|
@@ -39,14 +39,19 @@ export type PluginValidation = {
|
|
|
39
39
|
};
|
|
40
40
|
/** 条款 1/2/4:https-only、host 全等白名单、sha 40hex。纯函数,worker 侧纵深复验(不信任 wire 已验过)。 */
|
|
41
41
|
export declare function validatePluginDecl(decl: PluginDeclWire, allowHosts: readonly string[]): PluginValidation;
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* 条款 3:clone 计划(argv 序列)。纯数据,让"步骤"可被锚定。
|
|
44
|
+
*
|
|
45
|
+
* 7.103.0(S-684 残余):计划**只**产 argv。隔离 env(收紧③:GIT_CONFIG_GLOBAL/SYSTEM=/dev/null、GIT_TERMINAL_PROMPT=0)的唯一属主是
|
|
46
|
+
* `gitSubprocessEnv({ isolateConfig: true })` —— 修前这三键写在这里、由 `runGit` 叠在 `{ ...process.env }` 上,一件姿势两个写者,
|
|
47
|
+
* 而剥密恰好落在两者之外。锚点随之移到真 spawn 的 env 上(`test/secret-env-scrub-host-legs.test.ts` R2:假 git 读到的整份 env)。
|
|
48
|
+
*/
|
|
43
49
|
export declare function gitClonePlan(o: {
|
|
44
50
|
url: string;
|
|
45
51
|
dest: string;
|
|
46
52
|
sha?: string;
|
|
47
53
|
}): {
|
|
48
54
|
steps: string[][];
|
|
49
|
-
env: Record<string, string>;
|
|
50
55
|
};
|
|
51
56
|
/** manifest 本体尺寸上限(#97 redesign⑥ V2-⑦:**新的本地输入上限**,非既有格式事实——manifest 只被
|
|
52
57
|
* 消费一个 `skillsPath` 字段,几行 JSON;超界视为畸形输入整条拒装)。 */
|
|
@@ -101,6 +106,7 @@ export declare function applyCenterPlugins(baseline: LoadedSkill[], eff: {
|
|
|
101
106
|
/** 测试口:跳过 validate(materialize 只接受已验声明;真 git 腿用本地 bare repo,validate 层的
|
|
102
107
|
* https-only 单独测——两层分开,谁也不掩护谁)。生产恒 false/缺席。 */
|
|
103
108
|
skipValidate?: boolean;
|
|
104
|
-
|
|
109
|
+
/** 测试口:替换真 git(生产恒缺席)。env 不在这一格上 —— 缺省腿自己经唯一构造取(替身腿本来就不起 git)。 */
|
|
110
|
+
runGit?: (args: string[]) => Promise<void>;
|
|
105
111
|
}): Promise<ApplyPluginsResult>;
|
|
106
112
|
//# sourceMappingURL=center-plugins.d.ts.map
|
|
@@ -4,6 +4,7 @@ import { promises as fsp, realpathSync, existsSync, readFileSync, lstatSync, sta
|
|
|
4
4
|
import { join, resolve, isAbsolute, sep } from "node:path";
|
|
5
5
|
import { assertConfined, loadSkills } from "./skills.js";
|
|
6
6
|
import { buildDisplaySafeUrl, redactSecrets } from "../trace/redact.js";
|
|
7
|
+
import { gitSubprocessEnv } from "../server-secret-env.js";
|
|
7
8
|
const execFileP = promisify(execFile);
|
|
8
9
|
export const MAX_PLUGIN_SOURCE_URL_CHARS = 2048;
|
|
9
10
|
export const DEFAULT_PLUGIN_ALLOW_HOSTS = ["github.com"];
|
|
@@ -53,13 +54,8 @@ export function validatePluginDecl(decl, allowHosts) {
|
|
|
53
54
|
return { ok: true, name, url };
|
|
54
55
|
}
|
|
55
56
|
export function gitClonePlan(o) {
|
|
56
|
-
const env = {
|
|
57
|
-
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
58
|
-
GIT_CONFIG_SYSTEM: "/dev/null",
|
|
59
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
60
|
-
};
|
|
61
57
|
if (o.sha === undefined) {
|
|
62
|
-
return { steps: [["clone", "--depth", "1", "--quiet", o.url, o.dest]]
|
|
58
|
+
return { steps: [["clone", "--depth", "1", "--quiet", o.url, o.dest]] };
|
|
63
59
|
}
|
|
64
60
|
return {
|
|
65
61
|
steps: [
|
|
@@ -67,7 +63,6 @@ export function gitClonePlan(o) {
|
|
|
67
63
|
["-C", o.dest, "fetch", "--depth", "1", "--quiet", o.url, o.sha],
|
|
68
64
|
["-C", o.dest, "checkout", "--quiet", o.sha],
|
|
69
65
|
],
|
|
70
|
-
env,
|
|
71
66
|
};
|
|
72
67
|
}
|
|
73
68
|
export const PLUGIN_MANIFEST_MAX_BYTES = 64 * 1024;
|
|
@@ -130,8 +125,8 @@ export async function applyCenterPlugins(baseline, eff, opts) {
|
|
|
130
125
|
const out = { skills: [...baseline], failures: [], letGo: [], lkgUsed: [] };
|
|
131
126
|
if (declared.length === 0)
|
|
132
127
|
return out;
|
|
133
|
-
const runGit = opts.runGit ?? (async (args
|
|
134
|
-
await execFileP("git", args, { env: {
|
|
128
|
+
const runGit = opts.runGit ?? (async (args) => {
|
|
129
|
+
await execFileP("git", args, { env: gitSubprocessEnv({ site: "server.center-plugins.git-clone", isolateConfig: true }), timeout: 120_000, maxBuffer: 4 * 1024 * 1024 });
|
|
135
130
|
});
|
|
136
131
|
const taken = new Set(baseline.map((s) => s.spec.name));
|
|
137
132
|
for (const decl of declared) {
|
|
@@ -155,7 +150,7 @@ export async function applyCenterPlugins(baseline, eff, opts) {
|
|
|
155
150
|
await fsp.mkdir(staging, { recursive: true });
|
|
156
151
|
const plan = gitClonePlan({ url: v.url, dest: staging, sha: v.sha });
|
|
157
152
|
for (const step of plan.steps)
|
|
158
|
-
await runGit(step
|
|
153
|
+
await runGit(step);
|
|
159
154
|
await fsp.rm(liveDir, { recursive: true, force: true });
|
|
160
155
|
await fsp.mkdir(join(opts.cacheRoot, v.name), { recursive: true });
|
|
161
156
|
await fsp.rename(staging, liveDir);
|