@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.
- package/dist/approval-hmac.d.ts +17 -0
- package/dist/approval-hmac.js +27 -0
- package/dist/auth-bridge.d.ts +3 -2
- package/dist/bench/s1/live-deps.js +2 -2
- package/dist/budget.d.ts +4 -76
- package/dist/budget.js +4 -83
- package/dist/capabilities/sandbox-file-send.d.ts +2 -1
- package/dist/config-center/apply-effective.d.ts +22 -0
- package/dist/config-center/apply-effective.js +283 -0
- package/dist/config-center/http-client.d.ts +23 -0
- package/dist/config-center/http-client.js +109 -0
- package/dist/config-center/restart-signal.d.ts +12 -0
- package/dist/config-center/restart-signal.js +70 -0
- package/dist/config-center/skills-mcp.d.ts +13 -0
- package/dist/config-center/skills-mcp.js +113 -0
- package/dist/config-center/types.d.ts +143 -0
- package/dist/config-center/types.js +2 -0
- package/dist/config-types.d.ts +1 -1
- package/dist/elicitation.d.ts +5 -3
- package/dist/elicitation.js +3 -2
- package/dist/fleet/fleet-bus.js +92 -83
- package/dist/fleet-lease.d.ts +1 -1
- package/dist/fleet-lease.js +1 -1
- package/dist/hooks/hook-llm.d.ts +5 -4
- package/dist/hooks/hook-runner.d.ts +1 -1
- package/dist/hooks/hook-runner.js +19 -10
- package/dist/http/server.d.ts +95 -69
- package/dist/http/server.js +75 -86
- package/dist/index.d.ts +1 -1
- package/dist/key-resolver.d.ts +1 -1
- package/dist/key-resolver.js +1 -1
- package/dist/leader/grader-env-factory.d.ts +1 -1
- package/dist/leader/grader-env-factory.js +2 -2
- package/dist/leader/leader.js +5 -2
- package/dist/leader/wire.d.ts +1 -1
- package/dist/leader/wire.js +170 -166
- package/dist/main.js +487 -476
- package/dist/memory-scope.d.ts +18 -0
- package/dist/memory-scope.js +38 -0
- package/dist/observability/cost-taxonomy.d.ts +34 -0
- package/dist/observability/cost-taxonomy.js +26 -0
- package/dist/observability/prompt-manifest.d.ts +75 -0
- package/dist/observability/prompt-manifest.js +82 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +67 -0
- package/dist/plugins/checkpoint-store-sql.js +224 -0
- package/dist/plugins/image-bake-store-sql.d.ts +53 -0
- package/dist/plugins/image-bake-store-sql.js +463 -0
- package/dist/plugins/k8s-bg-scripts.d.ts +19 -0
- package/dist/plugins/k8s-bg-scripts.js +129 -0
- package/dist/plugins/k8s-exec-protocol.d.ts +20 -0
- package/dist/plugins/k8s-exec-protocol.js +87 -0
- package/dist/plugins/pg-checkpoint-store.d.ts +1 -33
- package/dist/plugins/pg-checkpoint-store.js +1 -189
- package/dist/plugins/pg-cost-quota.js +3 -143
- package/dist/plugins/pg-image-bake.d.ts +1 -40
- package/dist/plugins/pg-image-bake.js +1 -420
- package/dist/plugins/pg-rate-limiter.d.ts +2 -32
- package/dist/plugins/pg-rate-limiter.js +4 -147
- package/dist/plugins/remote-env-k8s.d.ts +5 -38
- package/dist/plugins/remote-env-k8s.js +7 -213
- package/dist/plugins/sql-driver.d.ts +25 -0
- package/dist/plugins/sql-driver.js +59 -0
- package/dist/plugins/tidb-checkpoint-store.d.ts +1 -49
- package/dist/plugins/tidb-checkpoint-store.js +1 -191
- package/dist/plugins/tidb-image-bake.d.ts +1 -38
- package/dist/plugins/tidb-image-bake.js +1 -348
- package/dist/plugins/web-search.d.ts +4 -3
- package/dist/plugins/write-behind-counter.d.ts +14 -3
- package/dist/plugins/write-behind-counter.js +39 -12
- package/dist/principal-jwt.d.ts +44 -0
- package/dist/principal-jwt.js +95 -0
- package/dist/question.d.ts +2 -1
- package/dist/question.js +3 -2
- package/dist/run-local.js +2 -2
- package/dist/runtime-caps-resolver.d.ts +7 -7
- package/dist/runtime-caps-resolver.js +3 -3
- package/dist/security.d.ts +4 -74
- package/dist/security.js +6 -155
- package/dist/sema-registry.d.ts +5 -200
- package/dist/sema-registry.js +4 -567
- package/dist/tool-approval.d.ts +2 -1
- package/dist/tool-approval.js +3 -2
- package/package.json +1 -1
package/dist/http/server.js
CHANGED
|
@@ -30,11 +30,17 @@ import { projectEvents, runSummary, mapTraceEvent, turnEndEventData, contextUsag
|
|
|
30
30
|
import { createLedgerSink } from "../trace/ledger-sink.js";
|
|
31
31
|
import { cacheFamilyOfMirror } from "../budget.js";
|
|
32
32
|
import { projectArtifacts } from "../trace/artifacts.js";
|
|
33
|
-
import { composeSupervisorCost, infraCost, infraUsageFromEvents, hasInfraPricing } from "../
|
|
33
|
+
import { composeSupervisorCost, infraCost, infraUsageFromEvents, hasInfraPricing } from "../observability/cost-taxonomy.js";
|
|
34
34
|
import { redactSecrets, redactDeep, redactedPreview } from "../trace/redact.js";
|
|
35
35
|
import { usageSummary, usageSeries, usageBreakdown } from "../usage-analytics.js";
|
|
36
36
|
import { IdempotencyCache } from "./idempotency.js";
|
|
37
37
|
import { pgHasUnstorable } from "../plugins/pg-safe-json.js";
|
|
38
|
+
export function flattenServiceDeps(deps) {
|
|
39
|
+
const { stores, coordinators, seams, observability, governance, deployment, knobs, ...flat } = deps;
|
|
40
|
+
if (!(stores || coordinators || seams || observability || governance || deployment || knobs))
|
|
41
|
+
return deps;
|
|
42
|
+
return { ...stores, ...coordinators, ...seams, ...observability, ...governance, ...deployment, ...knobs, ...flat };
|
|
43
|
+
}
|
|
38
44
|
export const MAX_USER_SKILLS = 10;
|
|
39
45
|
export const MAX_SKILL_CONTENT_CHARS = 1_048_576;
|
|
40
46
|
export const MAX_SYSTEM_PROMPT_CHARS = 16_384;
|
|
@@ -195,7 +201,8 @@ function bearerPresentedButUnverified(req, config) {
|
|
|
195
201
|
return false;
|
|
196
202
|
return true;
|
|
197
203
|
}
|
|
198
|
-
export function createHttpServer(
|
|
204
|
+
export function createHttpServer(rawDeps) {
|
|
205
|
+
const deps = flattenServiceDeps(rawDeps);
|
|
199
206
|
const idemCache = new IdempotencyCache();
|
|
200
207
|
let sseConnections = 0;
|
|
201
208
|
let sseOwnerLookups = 0;
|
|
@@ -634,7 +641,7 @@ export function createHttpServer(deps) {
|
|
|
634
641
|
}
|
|
635
642
|
if (deps.drainState?.draining && req.method === "POST" && isBillableSubmitPath(url)) {
|
|
636
643
|
res.setHeader("retry-after", "15");
|
|
637
|
-
|
|
644
|
+
sendError(res, 503, "draining", "draining", { message: "this instance is draining for shutdown/upgrade — retry against the replacement instance" });
|
|
638
645
|
return;
|
|
639
646
|
}
|
|
640
647
|
if (deps.modelReady && !deps.modelReady() && req.method === "POST" && isBillableSubmitPath(url)) {
|
|
@@ -655,7 +662,7 @@ export function createHttpServer(deps) {
|
|
|
655
662
|
const name = url.slice("/v1/capabilities/scenarios/".length).split("?")[0];
|
|
656
663
|
const detail = deps.scenarioDetails?.[name];
|
|
657
664
|
if (!detail) {
|
|
658
|
-
|
|
665
|
+
sendError(res, 404, "scenario_not_found", "scenario not found");
|
|
659
666
|
return;
|
|
660
667
|
}
|
|
661
668
|
sendJson(res, 200, detail);
|
|
@@ -1718,7 +1725,7 @@ export function createHttpServer(deps) {
|
|
|
1718
1725
|
}
|
|
1719
1726
|
catch (e) {
|
|
1720
1727
|
if (e instanceof CheckpointError && e.code === "steering.invalid_content") {
|
|
1721
|
-
|
|
1728
|
+
sendError(res, 422, "steering.invalid_content", e.message);
|
|
1722
1729
|
return;
|
|
1723
1730
|
}
|
|
1724
1731
|
throw e;
|
|
@@ -1747,7 +1754,7 @@ export function createHttpServer(deps) {
|
|
|
1747
1754
|
return (await cs.setPendingSteer(token, scope, { text, trusted })) ? "parked" : "no-checkpoint";
|
|
1748
1755
|
};
|
|
1749
1756
|
const sendParked = () => sendJson(res, 202, { taskId, status: "suspended", delivery: "queued", messageId, ...(priority ? { priority } : {}), note: "steer parked on the checkpoint — injected when the run resumes" });
|
|
1750
|
-
const sendNotRunning = (error) =>
|
|
1757
|
+
const sendNotRunning = (error) => sendError(res, 409, "steering.not_running", error);
|
|
1751
1758
|
const live = steerableRuns.get(taskId);
|
|
1752
1759
|
if (live) {
|
|
1753
1760
|
try {
|
|
@@ -1758,7 +1765,7 @@ export function createHttpServer(deps) {
|
|
|
1758
1765
|
catch (e) {
|
|
1759
1766
|
const code = e.code;
|
|
1760
1767
|
if (code === "steering.invalid_content") {
|
|
1761
|
-
|
|
1768
|
+
sendError(res, 422, "steering.invalid_content", e instanceof Error ? e.message : "invalid steering content");
|
|
1762
1769
|
return;
|
|
1763
1770
|
}
|
|
1764
1771
|
if (code !== "steering.not_running")
|
|
@@ -1896,7 +1903,7 @@ export function createHttpServer(deps) {
|
|
|
1896
1903
|
const compactInstructions = compactBody.instructions;
|
|
1897
1904
|
const live = steerableRuns.get(taskId);
|
|
1898
1905
|
if (!live) {
|
|
1899
|
-
|
|
1906
|
+
sendError(res, 409, "compact.not_running", run.status === "running" ? "run is active on another replica — manual compact is replica-local" : `run is ${run.status} — not accepting compaction`);
|
|
1900
1907
|
return;
|
|
1901
1908
|
}
|
|
1902
1909
|
void live.compact(compactInstructions !== undefined ? { instructions: compactInstructions } : undefined).then((outcome) => {
|
|
@@ -1953,7 +1960,7 @@ export function createHttpServer(deps) {
|
|
|
1953
1960
|
return;
|
|
1954
1961
|
const live = steerableRuns.get(taskId);
|
|
1955
1962
|
if (!live) {
|
|
1956
|
-
|
|
1963
|
+
sendError(res, 409, "detach.not_running", run.status === "running" ? "run is active on another replica — detach is replica-local" : `run is ${run.status} — no running tool call to detach`);
|
|
1957
1964
|
return;
|
|
1958
1965
|
}
|
|
1959
1966
|
live.detach(body.toolCallId);
|
|
@@ -1992,10 +1999,7 @@ export function createHttpServer(deps) {
|
|
|
1992
1999
|
return;
|
|
1993
2000
|
}
|
|
1994
2001
|
if (body.content.length > STEER_IN_MAX_REQUEST_CHARS) {
|
|
1995
|
-
|
|
1996
|
-
errorCode: "steer.content_too_large",
|
|
1997
|
-
error: `content exceeds ${STEER_IN_MAX_REQUEST_CHARS} characters (got ${body.content.length}); note only the first ${STEER_IN_MAX_CHARS} are delivered anyway`,
|
|
1998
|
-
});
|
|
2002
|
+
sendError(res, 413, "steer.content_too_large", `content exceeds ${STEER_IN_MAX_REQUEST_CHARS} characters (got ${body.content.length}); note only the first ${STEER_IN_MAX_CHARS} are delivered anyway`);
|
|
1999
2003
|
return;
|
|
2000
2004
|
}
|
|
2001
2005
|
const trusted = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
@@ -2007,11 +2011,11 @@ export function createHttpServer(deps) {
|
|
|
2007
2011
|
}
|
|
2008
2012
|
if (wfRun && !runSessionAcceptOk(req, res, { sessionId: wfRun.originatingSessionId ?? null }, "workflow.agent-steer", "workflow not found"))
|
|
2009
2013
|
return;
|
|
2010
|
-
const sendNotRunningWf = (error) =>
|
|
2014
|
+
const sendNotRunningWf = (error) => sendError(res, 409, "steering.not_running", error);
|
|
2011
2015
|
const redacted = redactSteerIn(body.content, label);
|
|
2012
2016
|
const resolution = deps.workflowAgentRegistry?.resolve(wfRunId, label);
|
|
2013
2017
|
if (resolution && resolution.count > 1) {
|
|
2014
|
-
|
|
2018
|
+
sendError(res, 409, "steering.ambiguous_label", `${resolution.count} live agents share label '${label}' in this run — steer target is ambiguous`);
|
|
2015
2019
|
return;
|
|
2016
2020
|
}
|
|
2017
2021
|
const handle = resolution?.handle;
|
|
@@ -2195,27 +2199,23 @@ export function createHttpServer(deps) {
|
|
|
2195
2199
|
const stillParked = details?.error === "parked_pending_approval" || details?.status === "parked";
|
|
2196
2200
|
if (stopVerb && details?.error !== undefined && (details.status === "running" || stillParked || parkResumeWon || parkArbiterUnreachable)) {
|
|
2197
2201
|
const notLocal = details.error === "not_local";
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
? "
|
|
2210
|
-
:
|
|
2211
|
-
? "the approval
|
|
2212
|
-
:
|
|
2213
|
-
? "
|
|
2214
|
-
:
|
|
2215
|
-
? "this task is durably parked awaiting a tool-approval decision, not actively running — there is no live process to interrupt; resolve (or let expire) the pending approval instead"
|
|
2216
|
-
: `stop did not land (${details.error}) — the process may still be running`,
|
|
2217
|
-
taskId: runId, target, content: out.content, output: out.details,
|
|
2218
|
-
});
|
|
2202
|
+
sendError(res, 409, notLocal
|
|
2203
|
+
? "stop.not_local"
|
|
2204
|
+
: parkArbiterUnreachable
|
|
2205
|
+
? "stop.park_arbiter_unreachable"
|
|
2206
|
+
: parkResumeWon
|
|
2207
|
+
? "stop.park_resume_won"
|
|
2208
|
+
: stillParked
|
|
2209
|
+
? "stop.parked"
|
|
2210
|
+
: "stop.not_landed", notLocal
|
|
2211
|
+
? "this task is not attached to this instance — cannot stop it here (its durable record is still marked running; if its host process is gone, the retention sweep settles it as interrupted)"
|
|
2212
|
+
: parkArbiterUnreachable
|
|
2213
|
+
? "the approval arbitration store was unreachable — this task's row stays parked (not stopped); retry the stop"
|
|
2214
|
+
: parkResumeWon
|
|
2215
|
+
? "the pending tool-approval was resolved (resumed) concurrently with this stop attempt — the resume won the race, so nothing was stopped; re-check the task's current status"
|
|
2216
|
+
: stillParked
|
|
2217
|
+
? "this task is durably parked awaiting a tool-approval decision, not actively running — there is no live process to interrupt; resolve (or let expire) the pending approval instead"
|
|
2218
|
+
: `stop did not land (${details.error}) — the process may still be running`, { taskId: runId, target, content: out.content, output: out.details });
|
|
2219
2219
|
return;
|
|
2220
2220
|
}
|
|
2221
2221
|
const g14 = (() => {
|
|
@@ -2264,10 +2264,7 @@ export function createHttpServer(deps) {
|
|
|
2264
2264
|
return;
|
|
2265
2265
|
}
|
|
2266
2266
|
if (body.content.length > STEER_IN_MAX_REQUEST_CHARS) {
|
|
2267
|
-
|
|
2268
|
-
errorCode: "steer.content_too_large",
|
|
2269
|
-
error: `content exceeds ${STEER_IN_MAX_REQUEST_CHARS} characters (got ${body.content.length}); note only the first ${STEER_IN_MAX_CHARS} are delivered anyway`,
|
|
2270
|
-
});
|
|
2267
|
+
sendError(res, 413, "steer.content_too_large", `content exceeds ${STEER_IN_MAX_REQUEST_CHARS} characters (got ${body.content.length}); note only the first ${STEER_IN_MAX_CHARS} are delivered anyway`);
|
|
2271
2268
|
return;
|
|
2272
2269
|
}
|
|
2273
2270
|
const trusted = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
@@ -2278,7 +2275,7 @@ export function createHttpServer(deps) {
|
|
|
2278
2275
|
}
|
|
2279
2276
|
if (!runSessionAcceptOk(req, res, run, `subagent.${verb}`))
|
|
2280
2277
|
return;
|
|
2281
|
-
const send409 = (errorCode, error) =>
|
|
2278
|
+
const send409 = (errorCode, error) => sendError(res, 409, errorCode, error);
|
|
2282
2279
|
const redacted = redactSteerIn(body.content, target);
|
|
2283
2280
|
const resolution = deps.subagentSteerRegistry?.resolve(runId, target);
|
|
2284
2281
|
if (resolution && resolution.count > 1) {
|
|
@@ -2964,13 +2961,13 @@ export function createHttpServer(deps) {
|
|
|
2964
2961
|
return;
|
|
2965
2962
|
}
|
|
2966
2963
|
if (typeof body.reason === "string" && body.reason.length > MAX_APPROVAL_REASON_CHARS) {
|
|
2967
|
-
|
|
2964
|
+
sendError(res, 413, "reason_too_large", `reason too large (max ${MAX_APPROVAL_REASON_CHARS} chars)`);
|
|
2968
2965
|
return;
|
|
2969
2966
|
}
|
|
2970
2967
|
const run = await deps.runStore.getRun(taskId);
|
|
2971
2968
|
if (!run) {
|
|
2972
2969
|
if (deps.config.directDoorActive) {
|
|
2973
|
-
|
|
2970
|
+
sendError(res, 401, "principal_unverified", "principal proof required");
|
|
2974
2971
|
return;
|
|
2975
2972
|
}
|
|
2976
2973
|
sendJson(res, 404, { error: "task not found" });
|
|
@@ -2981,7 +2978,7 @@ export function createHttpServer(deps) {
|
|
|
2981
2978
|
const hdr = (n) => { const h = req.headers[n]; return Array.isArray(h) ? h[0] : h; };
|
|
2982
2979
|
const proof = verifyDirectDoorProof({ jwt: hdr("x-approval-principal-token"), mac: hdr("x-approval-mac"), kid: hdr("x-approval-mac-kid") }, { sessionId: run.sessionId, decision, reason: typeof body.reason === "string" ? body.reason : null }, deps.config, { actionBinding: false });
|
|
2983
2980
|
if (!proof.ok) {
|
|
2984
|
-
|
|
2981
|
+
sendError(res, proof.status, proof.errorCode, proof.error);
|
|
2985
2982
|
return;
|
|
2986
2983
|
}
|
|
2987
2984
|
deciderPrincipal = proof.principal;
|
|
@@ -3019,7 +3016,7 @@ export function createHttpServer(deps) {
|
|
|
3019
3016
|
return;
|
|
3020
3017
|
}
|
|
3021
3018
|
if (typeof body.reason === "string" && body.reason.length > MAX_APPROVAL_REASON_CHARS) {
|
|
3022
|
-
|
|
3019
|
+
sendError(res, 413, "reason_too_large", `reason too large (max ${MAX_APPROVAL_REASON_CHARS} chars)`);
|
|
3023
3020
|
return;
|
|
3024
3021
|
}
|
|
3025
3022
|
let remember = false;
|
|
@@ -3033,7 +3030,7 @@ export function createHttpServer(deps) {
|
|
|
3033
3030
|
return;
|
|
3034
3031
|
}
|
|
3035
3032
|
if (deps.config.directDoorActive) {
|
|
3036
|
-
|
|
3033
|
+
sendError(res, 400, "remember_not_in_proof", "remember is not supported on a direct-door worker (not covered by the decision proof)");
|
|
3037
3034
|
return;
|
|
3038
3035
|
}
|
|
3039
3036
|
if (!deps.approvalExemptionStore) {
|
|
@@ -3041,7 +3038,7 @@ export function createHttpServer(deps) {
|
|
|
3041
3038
|
return;
|
|
3042
3039
|
}
|
|
3043
3040
|
if (!body.checkpointToken && !body.boundCallId) {
|
|
3044
|
-
|
|
3041
|
+
sendError(res, 400, "remember_requires_binding", "remember requires the decision binding (echo checkpointToken and/or boundCallId from the pending approval)");
|
|
3045
3042
|
return;
|
|
3046
3043
|
}
|
|
3047
3044
|
remember = true;
|
|
@@ -3061,10 +3058,7 @@ export function createHttpServer(deps) {
|
|
|
3061
3058
|
}
|
|
3062
3059
|
}
|
|
3063
3060
|
if (deps.config.directDoorActive && body.updatedInput !== undefined) {
|
|
3064
|
-
|
|
3065
|
-
error: "updatedInput is not supported on a direct-door worker (not covered by the decision proof)",
|
|
3066
|
-
errorCode: "updated_input_not_in_proof",
|
|
3067
|
-
});
|
|
3061
|
+
sendError(res, 400, "updated_input_not_in_proof", "updatedInput is not supported on a direct-door worker (not covered by the decision proof)");
|
|
3068
3062
|
return;
|
|
3069
3063
|
}
|
|
3070
3064
|
const binding = {
|
|
@@ -3081,7 +3075,7 @@ export function createHttpServer(deps) {
|
|
|
3081
3075
|
};
|
|
3082
3076
|
const proof = verifyDirectDoorProof({ jwt: hdr("x-approval-principal-token"), mac: hdr("x-approval-mac"), kid: hdr("x-approval-mac-kid") }, { sessionId, boundCallId: binding.boundCallId, boundInputHash: binding.boundInputHash, decision, reason: typeof body.reason === "string" ? body.reason : null }, deps.config);
|
|
3083
3077
|
if (!proof.ok) {
|
|
3084
|
-
|
|
3078
|
+
sendError(res, proof.status, proof.errorCode, proof.error);
|
|
3085
3079
|
return;
|
|
3086
3080
|
}
|
|
3087
3081
|
deciderPrincipal = proof.principal;
|
|
@@ -3133,7 +3127,7 @@ export function createHttpServer(deps) {
|
|
|
3133
3127
|
if (deps.approvalStore && url.startsWith("/v1/approvals")) {
|
|
3134
3128
|
const store = deps.approvalStore;
|
|
3135
3129
|
if (deps.config.directApprovalDoor) {
|
|
3136
|
-
|
|
3130
|
+
sendError(res, 503, "approval_door_unconfigured", "approvals are served by the direct door on this worker");
|
|
3137
3131
|
return;
|
|
3138
3132
|
}
|
|
3139
3133
|
if (req.method === "GET" && url === "/v1/approvals") {
|
|
@@ -3291,11 +3285,11 @@ export function createHttpServer(deps) {
|
|
|
3291
3285
|
scope = decodeURIComponent(url.slice("/v1/memory/sync/".length));
|
|
3292
3286
|
}
|
|
3293
3287
|
catch {
|
|
3294
|
-
|
|
3288
|
+
sendError(res, 400, "memory_sync_invalid_scope", "malformed percent-encoding in :scope", { code: "memory_sync_invalid_scope" });
|
|
3295
3289
|
return;
|
|
3296
3290
|
}
|
|
3297
3291
|
if (scope.length === 0) {
|
|
3298
|
-
|
|
3292
|
+
sendError(res, 400, "memory_sync_invalid_scope", "missing :scope", { code: "memory_sync_invalid_scope" });
|
|
3299
3293
|
return;
|
|
3300
3294
|
}
|
|
3301
3295
|
if (scope !== formatUserScope(principal) && !explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
|
|
@@ -3305,7 +3299,7 @@ export function createHttpServer(deps) {
|
|
|
3305
3299
|
const body = await readJson(req);
|
|
3306
3300
|
const parsed = parseMemorySyncRequest(body, scope);
|
|
3307
3301
|
if (!parsed.ok) {
|
|
3308
|
-
|
|
3302
|
+
sendError(res, 422, "memory_sync_invalid_body", parsed.error, { code: "memory_sync_invalid_body" });
|
|
3309
3303
|
return;
|
|
3310
3304
|
}
|
|
3311
3305
|
try {
|
|
@@ -3931,7 +3925,7 @@ export function createHttpServer(deps) {
|
|
|
3931
3925
|
}
|
|
3932
3926
|
catch (e) {
|
|
3933
3927
|
if (e instanceof SessionPolicyError) {
|
|
3934
|
-
|
|
3928
|
+
sendError(res, e.code === "conflict" ? 409 : 403, e.code, e.message);
|
|
3935
3929
|
return;
|
|
3936
3930
|
}
|
|
3937
3931
|
throw e;
|
|
@@ -4047,7 +4041,7 @@ export function createHttpServer(deps) {
|
|
|
4047
4041
|
if (typeof fs.blobSizes === "function") {
|
|
4048
4042
|
const known = (await fs.blobSizes([hash])).get(hash);
|
|
4049
4043
|
if (known !== undefined && known > cap) {
|
|
4050
|
-
|
|
4044
|
+
sendError(res, 413, "workspace_file_too_large", "file exceeds the single-file read limit — download it via the archive endpoint", { code: "workspace_file_too_large", sizeBytes: known, limit: cap });
|
|
4051
4045
|
return;
|
|
4052
4046
|
}
|
|
4053
4047
|
}
|
|
@@ -4057,7 +4051,7 @@ export function createHttpServer(deps) {
|
|
|
4057
4051
|
return;
|
|
4058
4052
|
}
|
|
4059
4053
|
if (bytes.byteLength > cap) {
|
|
4060
|
-
|
|
4054
|
+
sendError(res, 413, "workspace_file_too_large", "file exceeds the single-file read limit — download it via the archive endpoint", { code: "workspace_file_too_large", sizeBytes: bytes.byteLength, limit: cap });
|
|
4061
4055
|
return;
|
|
4062
4056
|
}
|
|
4063
4057
|
const ct = workspaceContentType(pathParam);
|
|
@@ -4260,11 +4254,7 @@ export function createHttpServer(deps) {
|
|
|
4260
4254
|
}
|
|
4261
4255
|
const body = await readRawBody(req, SYNC_BLOB_MAX_BYTES);
|
|
4262
4256
|
if (deps.snapshotBlobSqlCapBytes !== undefined && body.byteLength > deps.snapshotBlobSqlCapBytes) {
|
|
4263
|
-
|
|
4264
|
-
code: "blob_too_large_for_sql",
|
|
4265
|
-
errorCode: "blob_too_large_for_sql",
|
|
4266
|
-
error: `blob of ${body.byteLength} bytes exceeds this deployment's SQL snapshot-blob cap (${deps.snapshotBlobSqlCapBytes} bytes — TiDB's txn-entry-size-limit is the low wall at 6MiB by default; the mysql-protocol packet limit sits above it) — configure MinIO object storage (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY) for large snapshot blobs, or raise SNAPSHOT_BLOB_SQL_MAX_BYTES if your deployment lifted those limits`,
|
|
4267
|
-
});
|
|
4257
|
+
sendError(res, 413, "blob_too_large_for_sql", `blob of ${body.byteLength} bytes exceeds this deployment's SQL snapshot-blob cap (${deps.snapshotBlobSqlCapBytes} bytes — TiDB's txn-entry-size-limit is the low wall at 6MiB by default; the mysql-protocol packet limit sits above it) — configure MinIO object storage (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY) for large snapshot blobs, or raise SNAPSHOT_BLOB_SQL_MAX_BYTES if your deployment lifted those limits`, { code: "blob_too_large_for_sql" });
|
|
4268
4258
|
return;
|
|
4269
4259
|
}
|
|
4270
4260
|
if (createHash("sha256").update(body).digest("hex") !== hash) {
|
|
@@ -4273,7 +4263,7 @@ export function createHttpServer(deps) {
|
|
|
4273
4263
|
}
|
|
4274
4264
|
const put = await fs.putBlob(hash, new Uint8Array(body));
|
|
4275
4265
|
if (!put.ok) {
|
|
4276
|
-
|
|
4266
|
+
sendError(res, 502, put.error.code, "blob store write failed", { code: put.error.code });
|
|
4277
4267
|
return;
|
|
4278
4268
|
}
|
|
4279
4269
|
res.writeHead(204).end();
|
|
@@ -4325,7 +4315,7 @@ export function createHttpServer(deps) {
|
|
|
4325
4315
|
const dstIds = (await deps.sessionStorage.listEntryIds?.(sessionId)) ?? null;
|
|
4326
4316
|
const relA = classifySyncRelationshipByIds(entryIds, dstIds);
|
|
4327
4317
|
if ((relA.relation === "fork" || relA.relation === "stale") && resolution !== "overwrite-dst") {
|
|
4328
|
-
|
|
4318
|
+
sendError(res, 409, "conflict", "sync conflict", { relation: relA });
|
|
4329
4319
|
return;
|
|
4330
4320
|
}
|
|
4331
4321
|
let payloadVerified = false;
|
|
@@ -4349,7 +4339,7 @@ export function createHttpServer(deps) {
|
|
|
4349
4339
|
for (const snap of snapshots) {
|
|
4350
4340
|
const r = await fss.importManifest(sessionId, snap.key, new Map(snap.manifest), getBlobA);
|
|
4351
4341
|
if (!r.ok) {
|
|
4352
|
-
|
|
4342
|
+
sendError(res, 422, r.error.code === "restore_failed" ? "restore_failed" : "missing_blob", `snapshot import failed (${r.error.code})`);
|
|
4353
4343
|
return;
|
|
4354
4344
|
}
|
|
4355
4345
|
}
|
|
@@ -4371,7 +4361,7 @@ export function createHttpServer(deps) {
|
|
|
4371
4361
|
}
|
|
4372
4362
|
const activeTaskId = await deps.runStore?.getActiveTaskId(sessionId);
|
|
4373
4363
|
if (activeTaskId) {
|
|
4374
|
-
|
|
4364
|
+
sendError(res, 409, "session_active", "session has an active run; sync after it settles", { activeTaskId });
|
|
4375
4365
|
return;
|
|
4376
4366
|
}
|
|
4377
4367
|
const heldLease = importLeases.get(sessionId);
|
|
@@ -4387,12 +4377,8 @@ export function createHttpServer(deps) {
|
|
|
4387
4377
|
}
|
|
4388
4378
|
else {
|
|
4389
4379
|
const retryAfterSec = staleMs > 0 ? Math.max(1, Math.ceil((staleMs - ageMs) / 1000)) : undefined;
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
+ (retryAfterSec !== undefined ? ` (a stalled holder is taken over after ${staleSec}s of inactivity; retry in ~${retryAfterSec}s)` : ""),
|
|
4393
|
-
errorCode: "import_in_flight",
|
|
4394
|
-
...(retryAfterSec !== undefined ? { retryAfterSec } : {}),
|
|
4395
|
-
});
|
|
4380
|
+
sendError(res, 409, "import_in_flight", "an import is already in flight for this session"
|
|
4381
|
+
+ (retryAfterSec !== undefined ? ` (a stalled holder is taken over after ${staleSec}s of inactivity; retry in ~${retryAfterSec}s)` : ""), retryAfterSec !== undefined ? { retryAfterSec } : undefined);
|
|
4396
4382
|
return;
|
|
4397
4383
|
}
|
|
4398
4384
|
}
|
|
@@ -4408,14 +4394,14 @@ export function createHttpServer(deps) {
|
|
|
4408
4394
|
const present = await fss.hasBlobs(all);
|
|
4409
4395
|
const missing = all.find((h) => !present.has(h));
|
|
4410
4396
|
if (missing !== undefined) {
|
|
4411
|
-
|
|
4397
|
+
sendError(res, 422, "missing_blob", "missing blob; PUT all referenced blobs before import");
|
|
4412
4398
|
return;
|
|
4413
4399
|
}
|
|
4414
4400
|
}
|
|
4415
4401
|
else {
|
|
4416
4402
|
for (const hash of all) {
|
|
4417
4403
|
if (!(await deps.fileSnapshotStore.getBlob(hash))) {
|
|
4418
|
-
|
|
4404
|
+
sendError(res, 422, "missing_blob", "missing blob; PUT all referenced blobs before import");
|
|
4419
4405
|
return;
|
|
4420
4406
|
}
|
|
4421
4407
|
}
|
|
@@ -4521,13 +4507,13 @@ export function createHttpServer(deps) {
|
|
|
4521
4507
|
if (e instanceof PrecheckError) {
|
|
4522
4508
|
await pending.handle.abort().catch(() => undefined);
|
|
4523
4509
|
cleanupStaging(stagingId);
|
|
4524
|
-
|
|
4510
|
+
sendError(res, e.status, "invalid_entries", e.message);
|
|
4525
4511
|
return;
|
|
4526
4512
|
}
|
|
4527
4513
|
if (e instanceof SessionError && e.code === "invalid_session") {
|
|
4528
4514
|
await pending.handle.abort().catch(() => undefined);
|
|
4529
4515
|
cleanupStaging(stagingId);
|
|
4530
|
-
|
|
4516
|
+
sendError(res, 422, "invalid_entries", "staged session log failed validation");
|
|
4531
4517
|
return;
|
|
4532
4518
|
}
|
|
4533
4519
|
await pending.handle.abort().catch(() => undefined);
|
|
@@ -4538,7 +4524,7 @@ export function createHttpServer(deps) {
|
|
|
4538
4524
|
if (activeAtCommit) {
|
|
4539
4525
|
await pending.handle.abort().catch(() => undefined);
|
|
4540
4526
|
cleanupStaging(stagingId);
|
|
4541
|
-
|
|
4527
|
+
sendError(res, 409, "session_active", "session started an active run while the import streamed; sync after it settles", { activeTaskId: activeAtCommit });
|
|
4542
4528
|
return;
|
|
4543
4529
|
}
|
|
4544
4530
|
try {
|
|
@@ -4548,7 +4534,7 @@ export function createHttpServer(deps) {
|
|
|
4548
4534
|
for (const snap of pending.snapshots) {
|
|
4549
4535
|
const r = await importManifest(sessionId, snap.key, new Map(snap.manifest), getBlob);
|
|
4550
4536
|
if (!r.ok) {
|
|
4551
|
-
|
|
4537
|
+
sendError(res, 422, r.error.code === "restore_failed" ? "restore_failed" : "missing_blob", `snapshot import failed (${r.error.code})`);
|
|
4552
4538
|
cleanupStaging(stagingId);
|
|
4553
4539
|
return;
|
|
4554
4540
|
}
|
|
@@ -4570,12 +4556,12 @@ export function createHttpServer(deps) {
|
|
|
4570
4556
|
if (e instanceof SyncConflictError) {
|
|
4571
4557
|
await pending.handle.abort().catch(() => undefined);
|
|
4572
4558
|
cleanupStaging(stagingId);
|
|
4573
|
-
|
|
4559
|
+
sendError(res, 409, "conflict", "sync conflict", { relation: e.relation });
|
|
4574
4560
|
return;
|
|
4575
4561
|
}
|
|
4576
4562
|
if (e instanceof SessionPolicyError) {
|
|
4577
4563
|
cleanupStaging(stagingId);
|
|
4578
|
-
|
|
4564
|
+
sendError(res, e.code === "loosen_forbidden" ? 403 : 409, e.code, e.message);
|
|
4579
4565
|
return;
|
|
4580
4566
|
}
|
|
4581
4567
|
cleanupStaging(stagingId);
|
|
@@ -4640,7 +4626,7 @@ export function createHttpServer(deps) {
|
|
|
4640
4626
|
}
|
|
4641
4627
|
const allow = deps.config.attachmentMimeAllowlist;
|
|
4642
4628
|
if (allow && !allow.some((a) => a === mime || (a.endsWith("/*") && mime.startsWith(a.slice(0, -1))))) {
|
|
4643
|
-
|
|
4629
|
+
sendError(res, 415, "attachment.mime_not_allowed", `mime '${mime}' is not in this deployment's allowlist`, { code: "attachment.mime_not_allowed", allowed: allow });
|
|
4644
4630
|
return;
|
|
4645
4631
|
}
|
|
4646
4632
|
const max = deps.config.attachmentMaxBytes ?? 32 * 1024 * 1024;
|
|
@@ -4650,7 +4636,7 @@ export function createHttpServer(deps) {
|
|
|
4650
4636
|
}
|
|
4651
4637
|
catch (err) {
|
|
4652
4638
|
if (err instanceof HttpError && err.status === 413) {
|
|
4653
|
-
|
|
4639
|
+
sendError(res, 413, "attachment.too_large", `attachment exceeds the per-file limit (${max} bytes)`, { code: "attachment.too_large", maxBytes: max });
|
|
4654
4640
|
return;
|
|
4655
4641
|
}
|
|
4656
4642
|
throw err;
|
|
@@ -4772,7 +4758,7 @@ export function createHttpServer(deps) {
|
|
|
4772
4758
|
catch (e) {
|
|
4773
4759
|
const code = e.code;
|
|
4774
4760
|
if (typeof code === "string" && code.startsWith("notify.")) {
|
|
4775
|
-
|
|
4761
|
+
sendError(res, 400, code, e instanceof Error ? e.message : "invalid notification");
|
|
4776
4762
|
return;
|
|
4777
4763
|
}
|
|
4778
4764
|
}
|
|
@@ -5902,7 +5888,7 @@ export function createHttpServer(deps) {
|
|
|
5902
5888
|
return false;
|
|
5903
5889
|
deps.metrics?.inc("fleet_lease_rejected_total");
|
|
5904
5890
|
res.setHeader("retry-after", String(adm.retryAfterSec));
|
|
5905
|
-
|
|
5891
|
+
sendError(res, 429, "quota_exhausted", "budget exhausted (quota lease)", { code: "quota_exhausted", retryAfterSec: adm.retryAfterSec, ...(adm.detail ?? {}) });
|
|
5906
5892
|
return true;
|
|
5907
5893
|
}
|
|
5908
5894
|
function rateLimited(req, res) {
|
|
@@ -6879,6 +6865,9 @@ function sendJson(res, status, body) {
|
|
|
6879
6865
|
res.writeHead(status, { "content-type": "application/json" });
|
|
6880
6866
|
res.end(JSON.stringify(body));
|
|
6881
6867
|
}
|
|
6868
|
+
function sendError(res, status, errorCode, message, extra) {
|
|
6869
|
+
sendJson(res, status, { error: message, errorCode, ...extra });
|
|
6870
|
+
}
|
|
6882
6871
|
function msg(e) {
|
|
6883
6872
|
return e instanceof Error ? e.message : String(e);
|
|
6884
6873
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,5 +22,5 @@ export { createCouncilTool } from "./capabilities/code-review-council.js";
|
|
|
22
22
|
export { loadSkills, skillsForScenario, type LoadedSkill } from "./capabilities/skills.js";
|
|
23
23
|
export { buildScenarios, selectScenario, type Scenario, type ScenarioBundle, type ScenarioDeps, type ScenarioRequest, } from "./capabilities/scenarios.js";
|
|
24
24
|
export { createAuthorizer, principalFrom, memoryScopeFor, memoryEngineBackendFor, HttpError, type AuthContext, type OwnerAwareSessionStore, } from "./security.js";
|
|
25
|
-
export { createHttpServer, type ServiceDeps, type TaskRequestBody, type RequestAuth, } from "./http/server.js";
|
|
25
|
+
export { createHttpServer, type ServiceDeps, type TaskRequestBody, type RequestAuth, type FlatServiceDeps, type ServiceDepGroups, type ServiceCoreDeps, type ServiceStoreDeps, type ServiceCoordinatorDeps, type ServiceSeamDeps, type ServiceObservabilityDeps, type ServiceGovernanceDeps, type ServiceDeploymentDeps, type ServiceKnobDeps, } from "./http/server.js";
|
|
26
26
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/key-resolver.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Model } from "@sema-agent/core";
|
|
2
2
|
import { type SealedKeyPoison } from "./sealed-key.js";
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function createKeyResolver(modelApiKeyEnv: Record<string, string>, env?: NodeJS.ProcessEnv, modelApiKeys?: Record<string, string | SealedKeyPoison>): ((model: Model) => Promise<{
|
|
4
4
|
apiKey: string;
|
|
5
5
|
} | undefined>) | undefined;
|
|
6
6
|
//# sourceMappingURL=key-resolver.d.ts.map
|
package/dist/key-resolver.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isSealedKeyPoison, SealedKeyPoisonedError } from "./sealed-key.js";
|
|
2
|
-
export function
|
|
2
|
+
export function createKeyResolver(modelApiKeyEnv, env = process.env, modelApiKeys = {}) {
|
|
3
3
|
if (Object.keys(modelApiKeyEnv).length === 0 && Object.keys(modelApiKeys).length === 0)
|
|
4
4
|
return undefined;
|
|
5
5
|
return async (model) => {
|
|
@@ -32,5 +32,5 @@ export interface MintedGrader extends GraderHandle {
|
|
|
32
32
|
immutableOraclePaths: string[];
|
|
33
33
|
destroy: () => Promise<void>;
|
|
34
34
|
}
|
|
35
|
-
export declare function
|
|
35
|
+
export declare function createGraderEnvFactory(_cfg: GraderEnvFactoryCfg): () => Promise<MintedGrader>;
|
|
36
36
|
//# sourceMappingURL=grader-env-factory.d.ts.map
|
|
@@ -63,9 +63,9 @@ export function changedFilesOutOfAllowlist(changedFiles, allowlist) {
|
|
|
63
63
|
export function changedFilesWithinAllowlist(changedFiles, allowlist) {
|
|
64
64
|
return changedFilesOutOfAllowlist(changedFiles, allowlist).length === 0;
|
|
65
65
|
}
|
|
66
|
-
export function
|
|
66
|
+
export function createGraderEnvFactory(_cfg) {
|
|
67
67
|
return async () => {
|
|
68
|
-
throw new Error("
|
|
68
|
+
throw new Error("createGraderEnvFactory: the standalone control-plane grader is the auto-accept slice (LEADER_GRADER_FACTORY) — " +
|
|
69
69
|
"NOT wired in v1 (which reuses the integration sandbox for R1 and re-seeds it for R2). " +
|
|
70
70
|
"See docs/LEADER-REPAIRLOOP-INTEGRATION.md §3.2/§7.");
|
|
71
71
|
};
|
package/dist/leader/leader.js
CHANGED
|
@@ -161,7 +161,9 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
161
161
|
}));
|
|
162
162
|
return out;
|
|
163
163
|
};
|
|
164
|
-
|
|
164
|
+
const runModuleGate = async () => {
|
|
165
|
+
if (!deps.moduleGate)
|
|
166
|
+
return;
|
|
165
167
|
const subById = new Map(subs.map((s) => [s.workerId, s]));
|
|
166
168
|
const maxR = deps.moduleGate.maxRepairs ?? 1;
|
|
167
169
|
await Promise.all(reports.filter((r) => r.status === "completed").map(async (r) => {
|
|
@@ -194,7 +196,8 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
194
196
|
}
|
|
195
197
|
}
|
|
196
198
|
}));
|
|
197
|
-
}
|
|
199
|
+
};
|
|
200
|
+
await runModuleGate();
|
|
198
201
|
const mergeDeps = {
|
|
199
202
|
pullWorkerDiff: async (r) => workerDiff(byId.get(r.workerId)),
|
|
200
203
|
provisionIntegrationSandbox: deps.provisionIntegrationSandbox,
|
package/dist/leader/wire.d.ts
CHANGED
|
@@ -61,6 +61,6 @@ export declare function leaderResourceConfig(env?: NodeJS.ProcessEnv): {
|
|
|
61
61
|
maxSuspends: number;
|
|
62
62
|
};
|
|
63
63
|
};
|
|
64
|
-
export declare function
|
|
64
|
+
export declare function createLeaderRunner(cfg: LeaderWireConfig): (body: LeaderRequestBody) => Promise<LeaderResult>;
|
|
65
65
|
export {};
|
|
66
66
|
//# sourceMappingURL=wire.d.ts.map
|