@sema-agent/server 1.313.0 → 1.315.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.
Files changed (83) hide show
  1. package/dist/approval-hmac.d.ts +17 -0
  2. package/dist/approval-hmac.js +27 -0
  3. package/dist/auth-bridge.d.ts +3 -2
  4. package/dist/bench/s1/live-deps.js +2 -2
  5. package/dist/budget.d.ts +4 -76
  6. package/dist/budget.js +4 -83
  7. package/dist/capabilities/sandbox-file-send.d.ts +2 -1
  8. package/dist/config-center/apply-effective.d.ts +22 -0
  9. package/dist/config-center/apply-effective.js +283 -0
  10. package/dist/config-center/http-client.d.ts +23 -0
  11. package/dist/config-center/http-client.js +109 -0
  12. package/dist/config-center/restart-signal.d.ts +12 -0
  13. package/dist/config-center/restart-signal.js +70 -0
  14. package/dist/config-center/skills-mcp.d.ts +13 -0
  15. package/dist/config-center/skills-mcp.js +113 -0
  16. package/dist/config-center/types.d.ts +143 -0
  17. package/dist/config-center/types.js +2 -0
  18. package/dist/config-types.d.ts +1 -1
  19. package/dist/elicitation.d.ts +5 -3
  20. package/dist/elicitation.js +3 -2
  21. package/dist/fleet/fleet-bus.js +92 -83
  22. package/dist/fleet-lease.d.ts +1 -1
  23. package/dist/fleet-lease.js +1 -1
  24. package/dist/hooks/hook-llm.d.ts +5 -4
  25. package/dist/hooks/hook-runner.d.ts +1 -1
  26. package/dist/hooks/hook-runner.js +19 -10
  27. package/dist/http/server.d.ts +95 -69
  28. package/dist/http/server.js +75 -86
  29. package/dist/index.d.ts +1 -1
  30. package/dist/key-resolver.d.ts +1 -1
  31. package/dist/key-resolver.js +1 -1
  32. package/dist/leader/grader-env-factory.d.ts +1 -1
  33. package/dist/leader/grader-env-factory.js +2 -2
  34. package/dist/leader/leader.js +5 -2
  35. package/dist/leader/wire.d.ts +1 -1
  36. package/dist/leader/wire.js +170 -166
  37. package/dist/main.js +487 -476
  38. package/dist/memory-scope.d.ts +18 -0
  39. package/dist/memory-scope.js +38 -0
  40. package/dist/observability/cost-taxonomy.d.ts +34 -0
  41. package/dist/observability/cost-taxonomy.js +26 -0
  42. package/dist/observability/prompt-manifest.d.ts +75 -0
  43. package/dist/observability/prompt-manifest.js +82 -0
  44. package/dist/plugins/checkpoint-store-sql.d.ts +67 -0
  45. package/dist/plugins/checkpoint-store-sql.js +224 -0
  46. package/dist/plugins/image-bake-store-sql.d.ts +53 -0
  47. package/dist/plugins/image-bake-store-sql.js +463 -0
  48. package/dist/plugins/k8s-bg-scripts.d.ts +19 -0
  49. package/dist/plugins/k8s-bg-scripts.js +129 -0
  50. package/dist/plugins/k8s-exec-protocol.d.ts +20 -0
  51. package/dist/plugins/k8s-exec-protocol.js +87 -0
  52. package/dist/plugins/pg-checkpoint-store.d.ts +1 -33
  53. package/dist/plugins/pg-checkpoint-store.js +1 -189
  54. package/dist/plugins/pg-cost-quota.js +3 -143
  55. package/dist/plugins/pg-image-bake.d.ts +1 -40
  56. package/dist/plugins/pg-image-bake.js +1 -420
  57. package/dist/plugins/pg-rate-limiter.d.ts +2 -32
  58. package/dist/plugins/pg-rate-limiter.js +4 -147
  59. package/dist/plugins/remote-env-k8s.d.ts +5 -38
  60. package/dist/plugins/remote-env-k8s.js +7 -213
  61. package/dist/plugins/sql-driver.d.ts +25 -0
  62. package/dist/plugins/sql-driver.js +59 -0
  63. package/dist/plugins/tidb-checkpoint-store.d.ts +1 -49
  64. package/dist/plugins/tidb-checkpoint-store.js +1 -191
  65. package/dist/plugins/tidb-image-bake.d.ts +1 -38
  66. package/dist/plugins/tidb-image-bake.js +1 -348
  67. package/dist/plugins/web-search.d.ts +4 -3
  68. package/dist/plugins/write-behind-counter.d.ts +14 -3
  69. package/dist/plugins/write-behind-counter.js +39 -12
  70. package/dist/principal-jwt.d.ts +44 -0
  71. package/dist/principal-jwt.js +95 -0
  72. package/dist/question.d.ts +2 -1
  73. package/dist/question.js +3 -2
  74. package/dist/run-local.js +2 -2
  75. package/dist/runtime-caps-resolver.d.ts +7 -7
  76. package/dist/runtime-caps-resolver.js +3 -3
  77. package/dist/security.d.ts +4 -74
  78. package/dist/security.js +6 -155
  79. package/dist/sema-registry.d.ts +5 -200
  80. package/dist/sema-registry.js +4 -567
  81. package/dist/tool-approval.d.ts +2 -1
  82. package/dist/tool-approval.js +3 -2
  83. package/package.json +1 -1
@@ -0,0 +1,17 @@
1
+ import type { ApprovalHmacKey } from "./auth-keys.js";
2
+ export declare function approvalHmacMessage(env: {
3
+ sessionId: string;
4
+ boundCallId?: string | null;
5
+ boundInputHash?: string | null;
6
+ decision: string;
7
+ reason?: string | null;
8
+ }): string;
9
+ export declare const MAX_APPROVAL_REASON_CHARS = 4096;
10
+ export declare function verifyApprovalHmac(env: {
11
+ sessionId: string;
12
+ boundCallId?: string | null;
13
+ boundInputHash?: string | null;
14
+ decision: string;
15
+ reason?: string | null;
16
+ }, mac: string, kid: string | undefined, keys: ApprovalHmacKey[]): boolean;
17
+ //# sourceMappingURL=approval-hmac.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ export function approvalHmacMessage(env) {
3
+ return JSON.stringify([env.sessionId, env.boundCallId ?? null, env.boundInputHash ?? null, env.decision, env.reason ?? null]);
4
+ }
5
+ export const MAX_APPROVAL_REASON_CHARS = 4_096;
6
+ export function verifyApprovalHmac(env, mac, kid, keys) {
7
+ if (keys.length === 0)
8
+ return false;
9
+ let supplied;
10
+ try {
11
+ supplied = Buffer.from(mac, "hex");
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ if (supplied.length === 0)
17
+ return false;
18
+ const ordered = kid ? [...keys.filter((k) => k.kid === kid), ...keys.filter((k) => k.kid !== kid)] : keys;
19
+ const msg = approvalHmacMessage(env);
20
+ for (const k of ordered) {
21
+ const expected = createHmac("sha256", k.key).update(msg).digest();
22
+ if (expected.length === supplied.length && timingSafeEqual(expected, supplied))
23
+ return true;
24
+ }
25
+ return false;
26
+ }
27
+ //# sourceMappingURL=approval-hmac.js.map
@@ -23,7 +23,8 @@ export type VerifyOutcome = {
23
23
  ok: false;
24
24
  error: AuthBridgeErrorCode;
25
25
  };
26
- export declare function createRegistryJwtVerifier(opts: RegistryJwtVerifierOptions): {
26
+ export interface RegistryJwtVerifier {
27
27
  verify: (bearer: string) => Promise<VerifyOutcome>;
28
- };
28
+ }
29
+ export declare function createRegistryJwtVerifier(opts: RegistryJwtVerifierOptions): RegistryJwtVerifier;
29
30
  //# sourceMappingURL=auth-bridge.d.ts.map
@@ -4,7 +4,7 @@ import { createBrain } from "../../brain.js";
4
4
  import { buildPricing } from "../../budget.js";
5
5
  import { RemoteContainerExecutionEnv } from "../../plugins/remote-env-e2b.js";
6
6
  import { createDurableAskPolicy } from "../../approval.js";
7
- import { buildLeaderRunner } from "../../leader/wire.js";
7
+ import { createLeaderRunner } from "../../leader/wire.js";
8
8
  import { runOracle as runStandaloneOracle } from "./oracle.js";
9
9
  import { makeRepairOracle } from "./repair-oracle-adapter.js";
10
10
  import { assertDistinctEnvs } from "./runner-ctx.js";
@@ -358,7 +358,7 @@ async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId,
358
358
  process.env.LEADER_ENABLED = "true";
359
359
  process.env.LEADER_WORKER_BUDGET_USD = String((rt.maxCostUsd ?? 1.0) / 2);
360
360
  process.env.LEADER_CONFLICT_ROUNDS = process.env.LEADER_CONFLICT_ROUNDS ?? "2";
361
- const run = buildLeaderRunner({
361
+ const run = createLeaderRunner({
362
362
  brain, models, roles, pricing, e2bApiKey: rt.e2bApiKey, workspace: W,
363
363
  ...(process.env.S1_DEBUG_GIT ? { 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) : "") } } : {}),
364
364
  });
package/dist/budget.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { Metrics } from "./observability/metrics.js";
3
3
  import type { QuotaTracker } from "./observability/cost-quota.js";
4
4
  import type { FleetUsageAccumulator } from "./fleet-client.js";
5
5
  import type { FleetLeaseManager } from "./fleet-lease.js";
6
+ import { type PromptManifestTracker } from "./observability/prompt-manifest.js";
6
7
  export declare function buildPricing(models: Record<string, Model>): Record<string, ModelPricing>;
7
8
  export declare function cardinalityGuard(cap: number, onOverflow: () => void): (value: string) => string;
8
9
  export interface ModelUsageDelta {
@@ -20,88 +21,15 @@ export declare class ModelUsageTracker {
20
21
  drain(taskId: string): Record<string, ModelUsageDelta> | undefined;
21
22
  clear(taskId: string): void;
22
23
  }
23
- export interface PromptManifestRecord {
24
- constitution: string;
25
- blocks: Array<{
26
- id: string;
27
- chars: number;
28
- hash: string;
29
- }>;
30
- sections?: Array<{
31
- id: string;
32
- slot: string;
33
- carrier: string;
34
- cadence: string;
35
- cacheClass: string;
36
- chars: number;
37
- hash: string;
38
- contentHash?: string;
39
- }>;
40
- tools?: Array<{
41
- wireName: string;
42
- aliases: string[];
43
- contractId: string;
44
- implementationRevision: string;
45
- cardId: string;
46
- shapeDigest: string;
47
- wireSchemaDigest: string;
48
- }>;
49
- totalChars: number;
50
- }
51
- export declare function promptManifestRecordOf(e: {
52
- constitution: string;
53
- blocks: Array<{
54
- id: string;
55
- chars: number;
56
- hash: string;
57
- }>;
58
- sections?: Array<{
59
- id: string;
60
- slot: string;
61
- carrier: string;
62
- cadence: string;
63
- cacheClass: string;
64
- chars: number;
65
- hash: string;
66
- contentHash?: string;
67
- }>;
68
- tools?: Array<{
69
- wireName: string;
70
- aliases: string[];
71
- contractId: string;
72
- implementationRevision: string;
73
- cardId: string;
74
- shapeDigest: string;
75
- wireSchemaDigest: string;
76
- }>;
77
- totalChars: number;
78
- }): PromptManifestRecord;
79
- export declare function configAssembledRecordOf(e: {
80
- catalogVersion?: unknown;
81
- fields?: unknown;
82
- overrideReasons?: unknown;
83
- ts?: unknown;
84
- }): Record<string, unknown> | undefined;
85
- export declare class PromptManifestTracker {
86
- private readonly byTask;
87
- private readonly configByTask;
88
- private readonly active;
89
- private leaseCounter;
90
- register(taskId: string): number;
91
- record(taskId: string, m: PromptManifestRecord): void;
92
- recordConfig(taskId: string, rec: Record<string, unknown>): void;
93
- drainConfig(taskId: string): Array<Record<string, unknown>> | undefined;
94
- drain(taskId: string): PromptManifestRecord[] | undefined;
95
- clear(taskId: string, lease: number): void;
96
- }
97
- export declare function buildTracer(metrics: Pick<Metrics, "inc" | "observe">, costQuota?: QuotaTracker, modelUsage?: ModelUsageTracker, fleetUsage?: FleetUsageAccumulator, fleetLease?: FleetLeaseManager, quotaWeightFor?: (model: string) => number, promptManifests?: PromptManifestTracker): TracerHook;
24
+ export { type PromptManifestRecord, promptManifestRecordOf, configAssembledRecordOf, PromptManifestTracker, } from "./observability/prompt-manifest.js";
25
+ export declare function createTracer(metrics: Pick<Metrics, "inc" | "observe">, costQuota?: QuotaTracker, modelUsage?: ModelUsageTracker, fleetUsage?: FleetUsageAccumulator, fleetLease?: FleetLeaseManager, quotaWeightFor?: (model: string) => number, promptManifests?: PromptManifestTracker): TracerHook;
98
26
  export declare function cacheFamilyOfMirror(model: {
99
27
  api?: string;
100
28
  params?: {
101
29
  promptCacheFamily?: string;
102
30
  };
103
31
  } | undefined): "input-includes-cached" | "input-excludes-cached" | undefined;
104
- export declare function buildSideQueryAccountant(metrics: Pick<Metrics, "inc">, costQuota?: QuotaTracker, fleetUsage?: FleetUsageAccumulator, fleetLease?: FleetLeaseManager, quotaWeightFor?: (model: string) => number): (principal: string | undefined, r: {
32
+ export declare function createSideQueryAccountant(metrics: Pick<Metrics, "inc">, costQuota?: QuotaTracker, fleetUsage?: FleetUsageAccumulator, fleetLease?: FleetLeaseManager, quotaWeightFor?: (model: string) => number): (principal: string | undefined, r: {
105
33
  model: string;
106
34
  family?: "input-includes-cached" | "input-excludes-cached";
107
35
  usage?: {
package/dist/budget.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { modelCostToPricing } from "@sema-agent/core";
2
2
  import { currentPrincipal } from "./observability/principal-context.js";
3
+ import { promptManifestRecordOf, configAssembledRecordOf } from "./observability/prompt-manifest.js";
3
4
  export function buildPricing(models) {
4
5
  const pricing = {};
5
6
  for (const m of Object.values(models))
@@ -61,88 +62,8 @@ export class ModelUsageTracker {
61
62
  }
62
63
  clear(taskId) { this.byTask.delete(taskId); this.active.delete(taskId); }
63
64
  }
64
- export function promptManifestRecordOf(e) {
65
- return {
66
- constitution: String(e.constitution),
67
- blocks: (Array.isArray(e.blocks) ? e.blocks : []).map((b) => ({ id: String(b.id), chars: b.chars, hash: String(b.hash) })),
68
- ...(Array.isArray(e.sections)
69
- ? { sections: e.sections.map((s) => ({ id: String(s.id), slot: String(s.slot), carrier: String(s.carrier), cadence: String(s.cadence), cacheClass: String(s.cacheClass), chars: s.chars, hash: String(s.hash), ...(typeof s.contentHash === "string" ? { contentHash: s.contentHash } : {}) })) }
70
- : {}),
71
- ...(Array.isArray(e.tools)
72
- ? { tools: e.tools.map((t) => ({ wireName: String(t.wireName), aliases: (Array.isArray(t.aliases) ? t.aliases : []).map(String), contractId: String(t.contractId), implementationRevision: String(t.implementationRevision), cardId: String(t.cardId), shapeDigest: String(t.shapeDigest), wireSchemaDigest: String(t.wireSchemaDigest) })) }
73
- : {}),
74
- totalChars: e.totalChars,
75
- };
76
- }
77
- export function configAssembledRecordOf(e) {
78
- const rec = {
79
- catalogVersion: e.catalogVersion,
80
- fields: e.fields,
81
- ...(e.overrideReasons !== undefined ? { overrideReasons: e.overrideReasons } : {}),
82
- ts: e.ts,
83
- };
84
- try {
85
- if (JSON.stringify(rec).length > 32_768)
86
- return undefined;
87
- }
88
- catch {
89
- return undefined;
90
- }
91
- return rec;
92
- }
93
- export class PromptManifestTracker {
94
- byTask = new Map();
95
- configByTask = new Map();
96
- active = new Map();
97
- leaseCounter = 0;
98
- register(taskId) {
99
- if (!taskId)
100
- return 0;
101
- const lease = ++this.leaseCounter;
102
- this.active.set(taskId, lease);
103
- return lease;
104
- }
105
- record(taskId, m) {
106
- if (!taskId || !this.active.has(taskId))
107
- return;
108
- const q = this.byTask.get(taskId);
109
- if (q)
110
- q.push(m);
111
- else
112
- this.byTask.set(taskId, [m]);
113
- }
114
- recordConfig(taskId, rec) {
115
- if (!taskId || !this.active.has(taskId))
116
- return;
117
- const q = this.configByTask.get(taskId);
118
- if (q)
119
- q.push(rec);
120
- else
121
- this.configByTask.set(taskId, [rec]);
122
- }
123
- drainConfig(taskId) {
124
- const q = this.configByTask.get(taskId);
125
- if (!q || q.length === 0)
126
- return undefined;
127
- this.configByTask.delete(taskId);
128
- return q;
129
- }
130
- drain(taskId) {
131
- const q = this.byTask.get(taskId);
132
- if (!q || q.length === 0)
133
- return undefined;
134
- this.byTask.delete(taskId);
135
- return q;
136
- }
137
- clear(taskId, lease) {
138
- if (this.active.get(taskId) !== lease)
139
- return;
140
- this.byTask.delete(taskId);
141
- this.active.delete(taskId);
142
- this.configByTask.delete(taskId);
143
- }
144
- }
145
- export function buildTracer(metrics, costQuota, modelUsage, fleetUsage, fleetLease, quotaWeightFor, promptManifests) {
65
+ export { promptManifestRecordOf, configAssembledRecordOf, PromptManifestTracker, } from "./observability/prompt-manifest.js";
66
+ export function createTracer(metrics, costQuota, modelUsage, fleetUsage, fleetLease, quotaWeightFor, promptManifests) {
146
67
  const toolName = cardinalityGuard(256, () => metrics.inc("metric_label_overflow_total", { label: "tool_name" }));
147
68
  const modelId = cardinalityGuard(256, () => metrics.inc("metric_label_overflow_total", { label: "model" }));
148
69
  const providerName = cardinalityGuard(64, () => metrics.inc("metric_label_overflow_total", { label: "provider" }));
@@ -200,7 +121,7 @@ export function cacheFamilyOfMirror(model) {
200
121
  return "input-includes-cached";
201
122
  return model.api === "anthropic-messages" || model.api === "bedrock-converse-stream" ? "input-excludes-cached" : "input-includes-cached";
202
123
  }
203
- export function buildSideQueryAccountant(metrics, costQuota, fleetUsage, fleetLease, quotaWeightFor) {
124
+ export function createSideQueryAccountant(metrics, costQuota, fleetUsage, fleetLease, quotaWeightFor) {
204
125
  return (principal, r) => {
205
126
  const input = typeof r.usage?.input === "number" ? r.usage.input : 0;
206
127
  const output = typeof r.usage?.output === "number" ? r.usage.output : 0;
@@ -16,8 +16,9 @@ export interface SandboxFileSendDeps {
16
16
  prepare: (filename: string | undefined, scope?: string) => PreparedDirectUpload;
17
17
  maxBytes: number;
18
18
  }
19
- export declare function createSandboxFileSend(deps: SandboxFileSendDeps): (path: string, ctx: {
19
+ export type SandboxFileSend = (path: string, ctx: {
20
20
  taskId?: string;
21
21
  principal?: string;
22
22
  }) => Promise<IssuedFileLink>;
23
+ export declare function createSandboxFileSend(deps: SandboxFileSendDeps): SandboxFileSend;
23
24
  //# sourceMappingURL=sandbox-file-send.d.ts.map
@@ -0,0 +1,22 @@
1
+ import type { ServiceConfig } from "../config-types.js";
2
+ import { type SealedKeyOpener } from "../sealed-key.js";
3
+ import type { Logger } from "../observability/logger.js";
4
+ import type { EffectiveConfig } from "./types.js";
5
+ export declare function mutateInPlace<V>(target: Record<string, V>, source: Record<string, V>): void;
6
+ export declare function applyEffective(config: ServiceConfig, eff: EffectiveConfig, logger?: Logger, opts?: {
7
+ teamsOnly?: boolean;
8
+ sealedKeys?: SealedKeyOpener;
9
+ deferModelPlane?: boolean;
10
+ }): void;
11
+ export declare const RUNTIME_GATE_KEYS: readonly ["rateLimitPerMin", "approvalRequire", "maxTaskCostUsd", "maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec"];
12
+ export type RuntimeGateKey = (typeof RUNTIME_GATE_KEYS)[number];
13
+ export declare function runtimeGatePresent(rt: NonNullable<EffectiveConfig["runtime"]>, key: RuntimeGateKey): boolean;
14
+ export declare function applyRuntimeGates(config: ServiceConfig, rt: EffectiveConfig["runtime"], logger?: Logger): void;
15
+ export declare function applyRuntimeHot(config: ServiceConfig, rt: EffectiveConfig["runtime"], logger?: Logger): void;
16
+ export declare function resolveDefaultModelName(eff: EffectiveConfig, has: (name: string) => boolean, fallback: string, onDangling?: (source: string, name: string) => void): {
17
+ name: string;
18
+ source: string;
19
+ };
20
+ export declare function logEffectiveDiff(config: ServiceConfig, eff: EffectiveConfig, logger?: Logger): void;
21
+ export declare function runtimeHasActiveGate(rt: EffectiveConfig["runtime"]): boolean;
22
+ //# sourceMappingURL=apply-effective.d.ts.map
@@ -0,0 +1,283 @@
1
+ import { isThinkingLevel } from "@sema-agent/core";
2
+ import { resolveActiveTiers, SEALED_BOX_ALG } from "@sema-agent/registry-core";
3
+ import { sealedKeyPoison } from "../sealed-key.js";
4
+ import { applyAutoCompactWindow, parseAutonomy } from "../config.js";
5
+ import { validateCommandRules } from "../runtime-governance.js";
6
+ import { projectCollabToWorkflows, registerCollabWorkflows } from "../capabilities/collab-workflows.js";
7
+ import { registerTeams } from "../capabilities/team.js";
8
+ const asModelThinking = (v) => (v && isThinkingLevel(v) && v !== "off" ? v : undefined);
9
+ const charsPerTokenOf = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined);
10
+ function toModel(m, envDefaults = {}) {
11
+ const thinkingDefault = asModelThinking(m.defaultThinking) ?? envDefaults.defaultThinking;
12
+ const effortLevels = (m.reasoningEffortLevels ? m.reasoningEffortLevels.map(asModelThinking).filter((x) => x !== undefined) : undefined) ?? envDefaults.reasoningEffortLevels ?? [];
13
+ const model = {
14
+ id: m.id,
15
+ name: m.name,
16
+ provider: m.provider,
17
+ api: m.api,
18
+ baseUrl: m.baseUrl ?? "",
19
+ reasoning: m.reasoning !== undefined ? Boolean(m.reasoning) : (envDefaults.reasoning ?? true),
20
+ input: m.vision ? ["text", "image"] : ["text"],
21
+ contextWindow: m.contextWindow ?? envDefaults.contextWindow ?? 262144,
22
+ maxTokens: m.maxTokens ?? envDefaults.maxTokens ?? 4096,
23
+ ...(charsPerTokenOf(m.charsPerToken) !== undefined || envDefaults.charsPerToken !== undefined
24
+ ? { charsPerToken: charsPerTokenOf(m.charsPerToken) ?? envDefaults.charsPerToken }
25
+ : {}),
26
+ cost: m.cost
27
+ ? { input: m.cost.input, output: m.cost.output, cacheRead: m.cost.cacheRead ?? 0, cacheWrite: m.cost.cacheWrite ?? 0 }
28
+ : { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
29
+ params: { ...(m.tier ? { tier: m.tier } : {}), promptCacheFamily: m.provider === "anthropic" ? "input-excludes-cached" : "input-includes-cached" },
30
+ ...(thinkingDefault ? { defaultThinking: thinkingDefault } : {}),
31
+ ...(effortLevels.length > 0 && m.provider !== "anthropic" ? { compat: { reasoningEffortLevels: effortLevels } } : {}),
32
+ ...(m.extraBody ? { extraBody: m.extraBody } : {}),
33
+ ...(Array.isArray(m.promptGuidance) && m.promptGuidance.length > 0 ? { promptGuidance: m.promptGuidance.map(String) } : {}),
34
+ };
35
+ const explicitAct = m.autoCompactTokens;
36
+ applyAutoCompactWindow(model, typeof explicitAct === "number" ? explicitAct : undefined);
37
+ return model;
38
+ }
39
+ export function mutateInPlace(target, source) {
40
+ for (const k of Object.keys(target))
41
+ delete target[k];
42
+ Object.assign(target, source);
43
+ }
44
+ export function applyEffective(config, eff, logger, opts = {}) {
45
+ if (!opts.teamsOnly && !eff.version) {
46
+ logger?.warn("sema_registry_unpublished", {
47
+ version: eff.version,
48
+ note: "empty/unpublished effective config — using env + built-in fallback; publish a config or unset CONFIG_PUBLISH_MODE",
49
+ });
50
+ }
51
+ const enabled = (eff.models?.models ?? []).filter((m) => m.enabled !== false);
52
+ if (enabled.length > 0 && opts.deferModelPlane !== true) {
53
+ const models = {};
54
+ const modelApiKeyEnv = {};
55
+ const modelApiKeys = {};
56
+ const envDefaults = { maxTokens: config.model.maxTokens, contextWindow: config.model.contextWindow, reasoning: config.model.reasoning, charsPerToken: config.model.charsPerToken, defaultThinking: config.model.defaultThinking, reasoningEffortLevels: config.model.compat?.reasoningEffortLevels };
57
+ for (const m of enabled) {
58
+ const rawBaseUrl = m.baseUrl;
59
+ let safeUrl = rawBaseUrl;
60
+ if (rawBaseUrl) {
61
+ let ok = false;
62
+ try {
63
+ const u = new URL(rawBaseUrl);
64
+ ok = u.protocol === "http:" || u.protocol === "https:";
65
+ }
66
+ catch {
67
+ ok = false;
68
+ }
69
+ if (ok)
70
+ logger?.warn("sema_registry_model_base_url", { model: m.name, baseUrl: rawBaseUrl, hint: "registry-directed gateway override — this model's key authenticates AGAINST THIS URL" });
71
+ else {
72
+ logger?.warn("sema_registry_model_base_url_rejected", { model: m.name, baseUrl: String(rawBaseUrl).slice(0, 120), hint: "only http(s) URLs are honored — falling back to the boot-env gateway" });
73
+ safeUrl = undefined;
74
+ }
75
+ }
76
+ models[m.name] = toModel(safeUrl === rawBaseUrl ? m : { ...m, baseUrl: safeUrl }, envDefaults);
77
+ if (m.sealedApiKey) {
78
+ const s = m.sealedApiKey;
79
+ if (s.alg !== SEALED_BOX_ALG) {
80
+ logger?.warn("sema_registry_model_sealed_key_bad_alg", { model: m.name, alg: String(s.alg), expected: SEALED_BOX_ALG, hint: "unsupported sealed-box alg — key POISONED: tasks on this model fail loud (no apiKeyEnv fallback, no gateway fallback; contract v1 pins the alg)" });
81
+ modelApiKeys[m.name] = sealedKeyPoison(s.publicKeyId, "unsupported_alg");
82
+ }
83
+ else if (!opts.sealedKeys) {
84
+ logger?.warn("sema_registry_model_sealed_key_no_store", { model: m.name, publicKeyId: s.publicKeyId, hint: "no sealed-key store on this host (boot custody init failed or lane unwired) — key POISONED: tasks on this model fail loud (no apiKeyEnv fallback, no gateway fallback)" });
85
+ modelApiKeys[m.name] = sealedKeyPoison(s.publicKeyId, "no_key_store");
86
+ }
87
+ else if (!opts.sealedKeys.has(s.publicKeyId)) {
88
+ logger?.warn("sema_registry_model_sealed_key_no_private_key", { model: m.name, publicKeyId: s.publicKeyId, held: opts.sealedKeys.ids(), hint: "ciphertext targets a private key this host doesn't hold (rotated away / other host) — key POISONED: tasks on this model fail loud until the operator re-pastes the key in the web UI (it seals against the newest registered public key)" });
89
+ modelApiKeys[m.name] = sealedKeyPoison(s.publicKeyId, "stale_ciphertext");
90
+ }
91
+ else {
92
+ try {
93
+ modelApiKeys[m.name] = opts.sealedKeys.open(s.ciphertext, s.publicKeyId);
94
+ }
95
+ catch (err) {
96
+ logger?.warn("sema_registry_model_sealed_key_open_failed", { model: m.name, publicKeyId: s.publicKeyId, err: String(err), hint: "corrupt/foreign ciphertext — key POISONED: tasks on this model fail loud (no apiKeyEnv fallback, no gateway fallback)" });
97
+ modelApiKeys[m.name] = sealedKeyPoison(s.publicKeyId, "sealed_decrypt_failed", err instanceof Error ? err.name : "unknown");
98
+ }
99
+ }
100
+ }
101
+ else if (m.apiKeyEnv) {
102
+ if (process.env[m.apiKeyEnv])
103
+ modelApiKeyEnv[m.name] = m.apiKeyEnv;
104
+ else
105
+ logger?.warn("sema_registry_model_key_env_missing", { model: m.name, apiKeyEnv: m.apiKeyEnv, hint: "env var not set in this service — model falls back to the gateway key" });
106
+ }
107
+ if (m.provider === "anthropic" && !config.anthropic) {
108
+ logger?.warn("sema_registry_model_no_brain", { model: m.name, provider: m.provider, hint: "set ANTHROPIC_API_KEY in this service's env, else it mis-routes to the gateway brain" });
109
+ }
110
+ }
111
+ config.modelApiKeyEnv = modelApiKeyEnv;
112
+ config.modelApiKeys = modelApiKeys;
113
+ const quotaWeights = {};
114
+ for (const m of enabled) {
115
+ const qw = typeof m.quotaWeight === "number" && Number.isFinite(m.quotaWeight) && m.quotaWeight > 0 ? m.quotaWeight : undefined;
116
+ if (qw !== undefined) {
117
+ quotaWeights[m.name] = qw;
118
+ if (m.id && m.id !== m.name)
119
+ quotaWeights[m.id] = qw;
120
+ }
121
+ }
122
+ mutateInPlace(config.modelQuotaWeights, quotaWeights);
123
+ const roles = {};
124
+ for (const [role, tgt] of Object.entries(eff.models.roles ?? {})) {
125
+ if ("model" in tgt)
126
+ roles[role] = tgt.model;
127
+ else if ("select" in tgt)
128
+ roles[role] = { select: tgt.select };
129
+ }
130
+ const activeTiers = resolveActiveTiers(eff.models) ?? {};
131
+ const picked = resolveDefaultModelName(eff, (n) => models[n] !== undefined, enabled[0].name, (source, name) => logger?.warn("sema_registry_default_dangling", { source, name, hint: "explicit default names a model that is not in the enabled catalog — falling to the next source" }));
132
+ const defaultName = picked.name;
133
+ const defaultSource = picked.source;
134
+ models.default = models[defaultName];
135
+ mutateInPlace(config.models, models);
136
+ config.model = config.models.default;
137
+ if (Object.keys(roles).length > 0)
138
+ mutateInPlace(config.roles, roles);
139
+ mutateInPlace(config.tiers, activeTiers);
140
+ logger?.info("sema_registry_models", { count: enabled.length, default: config.model.id, defaultSource, roles: Object.keys(roles), tiers: Object.keys(config.tiers), sealedKeys: Object.values(modelApiKeys).filter((v) => typeof v === "string").length, sealedPoisoned: Object.values(modelApiKeys).filter((v) => typeof v !== "string").length });
141
+ }
142
+ const gatesView = eff.governance ? { ...eff.runtime, ...eff.governance } : eff.runtime;
143
+ if (!opts.teamsOnly)
144
+ applyRuntimeGates(config, gatesView, logger);
145
+ applyRuntimeHot(config, gatesView, logger);
146
+ if (opts.deferModelPlane !== true) {
147
+ const teams = {};
148
+ for (const t of (eff.teams?.teams ?? []).filter((t) => t.enabled !== false)) {
149
+ teams[t.name] = {
150
+ name: t.name,
151
+ members: t.members.map((mm) => ({ role: mm.role, ...(mm.model ? { model: mm.model } : {}), modelRole: mm.modelRole, systemPrompt: mm.systemPrompt })),
152
+ rounds: t.rounds,
153
+ synthesizer: t.synthesizer ? { role: t.synthesizer.role, modelRole: t.synthesizer.modelRole, systemPrompt: t.synthesizer.systemPrompt } : undefined,
154
+ };
155
+ }
156
+ registerTeams(teams);
157
+ if (Object.keys(teams).length > 0)
158
+ logger?.info("sema_registry_teams", { teams: Object.keys(teams) });
159
+ const projected = projectCollabToWorkflows(eff.collab?.templates);
160
+ registerCollabWorkflows(projected.workflows);
161
+ if (Object.keys(projected.workflows).length > 0)
162
+ logger?.info("sema_registry_collab_workflows", { workflows: Object.keys(projected.workflows) });
163
+ if (projected.skipped.length > 0)
164
+ logger?.warn("sema_registry_collab_skipped", { skipped: projected.skipped });
165
+ if (projected.notes.length > 0)
166
+ logger?.info("sema_registry_collab_notes", { notes: projected.notes });
167
+ }
168
+ const projDomain = eff.projects;
169
+ const rawProjects = (projDomain && typeof projDomain === "object" && !Array.isArray(projDomain)
170
+ ? "projects" in projDomain && typeof projDomain.projects === "object"
171
+ ? (projDomain.projects ?? {})
172
+ : projDomain
173
+ : {}) ?? {};
174
+ const projects = {};
175
+ for (const [pid, reg] of Object.entries(rawProjects)) {
176
+ if (!reg || typeof reg !== "object" || Array.isArray(reg))
177
+ continue;
178
+ const r = reg;
179
+ projects[pid] = {
180
+ ...(typeof r.displayName === "string" ? { displayName: r.displayName } : {}),
181
+ ...(Array.isArray(r.gitRemotes) ? { gitRemotes: r.gitRemotes.filter((x) => typeof x === "string") } : {}),
182
+ ...(Array.isArray(r.defaultScopes) ? { defaultScopes: r.defaultScopes.filter((x) => typeof x === "string") } : {}),
183
+ };
184
+ }
185
+ mutateInPlace(config.projects, projects);
186
+ if (Object.keys(projects).length > 0)
187
+ logger?.info("sema_registry_projects", { projects: Object.keys(projects) });
188
+ }
189
+ export const RUNTIME_GATE_KEYS = ["rateLimitPerMin", "approvalRequire", "maxTaskCostUsd", "maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec"];
190
+ export function runtimeGatePresent(rt, key) {
191
+ const v = rt[key];
192
+ if (key === "approvalRequire")
193
+ return Array.isArray(v);
194
+ if (key === "costQuotaWindowSec")
195
+ return typeof v === "number" && v > 0;
196
+ return typeof v === "number";
197
+ }
198
+ export function applyRuntimeGates(config, rt, logger) {
199
+ if (!rt)
200
+ return;
201
+ const applied = {};
202
+ for (const key of RUNTIME_GATE_KEYS) {
203
+ if (!runtimeGatePresent(rt, key))
204
+ continue;
205
+ config[key] = rt[key];
206
+ applied[key] = rt[key];
207
+ }
208
+ if (Object.keys(applied).length > 0)
209
+ logger?.info("sema_registry_runtime", applied);
210
+ }
211
+ export function applyRuntimeHot(config, rt, logger) {
212
+ const applied = {};
213
+ const envAutonomy = parseAutonomy(process.env.AUTONOMY);
214
+ const nextAutonomy = rt?.autonomy !== undefined ? rt.autonomy : envAutonomy;
215
+ if (config.autonomy !== nextAutonomy) {
216
+ config.autonomy = nextAutonomy;
217
+ applied.autonomy = nextAutonomy ?? "(env-baseline)";
218
+ }
219
+ if (rt?.commandPolicy !== undefined) {
220
+ const errors = validateCommandRules(rt.commandPolicy);
221
+ if (errors.length > 0) {
222
+ logger?.error("sema_registry_commandpolicy_invalid", { errors, kept: config.commandPolicy?.length ?? 0 });
223
+ }
224
+ else if (JSON.stringify(config.commandPolicy) !== JSON.stringify(rt.commandPolicy)) {
225
+ config.commandPolicy = rt.commandPolicy;
226
+ applied.commandPolicy = rt.commandPolicy.length;
227
+ }
228
+ }
229
+ else if (config.commandPolicy !== undefined) {
230
+ config.commandPolicy = undefined;
231
+ applied.commandPolicy = "(env-baseline)";
232
+ }
233
+ if (Object.keys(applied).length > 0)
234
+ logger?.info("sema_registry_runtime_hot", applied);
235
+ }
236
+ const DEFAULT_ROLE_TIER_LADDER = ["pro", "flash", "lite"];
237
+ export function resolveDefaultModelName(eff, has, fallback, onDangling) {
238
+ const activeTiers = resolveActiveTiers(eff.models) ?? {};
239
+ const structural = eff.models?.default;
240
+ const rolesDefault = eff.models?.roles?.default?.model;
241
+ const candidates = [
242
+ { source: "models.default", name: typeof structural === "string" && structural.length > 0 ? structural : undefined },
243
+ { source: "roles.default", name: typeof rolesDefault === "string" && rolesDefault.length > 0 ? rolesDefault : undefined },
244
+ ...DEFAULT_ROLE_TIER_LADDER.map((t) => ({ source: `tierGroup.${t}`, name: activeTiers[t] })),
245
+ ];
246
+ for (const c of candidates) {
247
+ if (!c.name)
248
+ continue;
249
+ if (has(c.name))
250
+ return { name: c.name, source: c.source };
251
+ onDangling?.(c.source, c.name);
252
+ }
253
+ return { name: fallback, source: "enabled[0]" };
254
+ }
255
+ export function logEffectiveDiff(config, eff, logger) {
256
+ const enabled = (eff.models?.models ?? []).filter((m) => m.enabled !== false);
257
+ const centerModels = enabled.map((m) => ({ name: m.name, id: m.id, provider: m.provider, apiKeyEnv: m.apiKeyEnv ?? null, tier: m.tier ?? null }));
258
+ const envModels = Object.entries(config.models).map(([name, m]) => ({ name, id: m.id, provider: m.provider }));
259
+ const centerRoles = Object.fromEntries(Object.entries(eff.models?.roles ?? {}).map(([r, t]) => [r, "model" in t ? t.model : { select: t.select }]));
260
+ const centerTeams = (eff.teams?.teams ?? []).filter((t) => t.enabled !== false).map((t) => t.name);
261
+ logger?.warn("sema_registry_dry_run", {
262
+ note: "DRY RUN — registry config NOT applied; unset SEMA_REGISTRY_DRY_RUN to go live",
263
+ version: eff.version,
264
+ wouldOverrideModels: enabled.length > 0,
265
+ wouldDefaultModel: enabled.length > 0 ? resolveDefaultModelName(eff, (n) => enabled.some((m) => m.name === n), enabled[0].name).name : null,
266
+ currentDefaultModel: config.model.id,
267
+ models: { center: centerModels, envDerived: envModels },
268
+ roles: { center: centerRoles, current: config.roles },
269
+ teams: { centerWouldRegister: centerTeams },
270
+ skills: { centerWouldLoad: (eff.skills?.skills ?? []).filter((s) => s.enabled !== false).map((s) => s.name) },
271
+ mcp: { centerWouldRegister: (eff.mcp?.servers ?? []).filter((s) => s.enabled !== false).map((s) => s.name) },
272
+ runtime: {
273
+ center: eff.runtime ?? null,
274
+ current: Object.fromEntries(RUNTIME_GATE_KEYS.map((k) => [k, config[k]])),
275
+ },
276
+ });
277
+ }
278
+ export function runtimeHasActiveGate(rt) {
279
+ if (!rt)
280
+ return false;
281
+ return RUNTIME_GATE_KEYS.some((k) => runtimeGatePresent(rt, k));
282
+ }
283
+ //# sourceMappingURL=apply-effective.js.map
@@ -0,0 +1,23 @@
1
+ import type { EntitlementRuntimeCaps } from "@sema-agent/registry-core";
2
+ import type { ScenarioRuling } from "../capabilities/scenarios.js";
3
+ import type { EffectiveConfig, ExecutionRuling } from "./types.js";
4
+ export declare function fetchEffective(baseUrl: string, token: string, etag: string | undefined, fetchImpl?: typeof fetch, worker?: string): Promise<{
5
+ effective: EffectiveConfig;
6
+ etag?: string;
7
+ } | null>;
8
+ export declare function fetchPrincipalCaps(baseUrl: string, token: string, principal: string, etag: string | undefined, fetchImpl?: typeof fetch, worker?: string): Promise<{
9
+ runtimeCaps: EntitlementRuntimeCaps | null;
10
+ configured: boolean;
11
+ scenario?: ScenarioRuling;
12
+ execution?: ExecutionRuling;
13
+ executionDrift?: string;
14
+ etag?: string;
15
+ } | null>;
16
+ export declare class ConfigCenterHttpError extends Error {
17
+ readonly status: number;
18
+ constructor(message: string, status: number);
19
+ }
20
+ export declare function fetchSkillContent(baseUrl: string, token: string, contentHash: string, fetchImpl?: typeof fetch): Promise<string>;
21
+ export declare function fetchPromptArtifact(baseUrl: string, token: string, artifactDigest: string, fetchImpl?: typeof fetch): Promise<unknown>;
22
+ export declare function fetchPromptBlob(baseUrl: string, token: string, contentDigest: string, fetchImpl?: typeof fetch): Promise<string>;
23
+ //# sourceMappingURL=http-client.d.ts.map