@sema-agent/server 1.313.0 → 1.314.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/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-types.d.ts +1 -1
- package/dist/elicitation.d.ts +5 -3
- package/dist/elicitation.js +3 -2
- 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 +1 -1
- package/dist/http/server.js +67 -85
- 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/wire.d.ts +1 -1
- package/dist/leader/wire.js +1 -1
- package/dist/main.js +26 -26
- 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/web-search.d.ts +4 -3
- 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 +2 -16
- package/dist/security.js +3 -37
- 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,7 +30,7 @@ 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";
|
|
@@ -634,7 +634,7 @@ export function createHttpServer(deps) {
|
|
|
634
634
|
}
|
|
635
635
|
if (deps.drainState?.draining && req.method === "POST" && isBillableSubmitPath(url)) {
|
|
636
636
|
res.setHeader("retry-after", "15");
|
|
637
|
-
|
|
637
|
+
sendError(res, 503, "draining", "draining", { message: "this instance is draining for shutdown/upgrade — retry against the replacement instance" });
|
|
638
638
|
return;
|
|
639
639
|
}
|
|
640
640
|
if (deps.modelReady && !deps.modelReady() && req.method === "POST" && isBillableSubmitPath(url)) {
|
|
@@ -655,7 +655,7 @@ export function createHttpServer(deps) {
|
|
|
655
655
|
const name = url.slice("/v1/capabilities/scenarios/".length).split("?")[0];
|
|
656
656
|
const detail = deps.scenarioDetails?.[name];
|
|
657
657
|
if (!detail) {
|
|
658
|
-
|
|
658
|
+
sendError(res, 404, "scenario_not_found", "scenario not found");
|
|
659
659
|
return;
|
|
660
660
|
}
|
|
661
661
|
sendJson(res, 200, detail);
|
|
@@ -1718,7 +1718,7 @@ export function createHttpServer(deps) {
|
|
|
1718
1718
|
}
|
|
1719
1719
|
catch (e) {
|
|
1720
1720
|
if (e instanceof CheckpointError && e.code === "steering.invalid_content") {
|
|
1721
|
-
|
|
1721
|
+
sendError(res, 422, "steering.invalid_content", e.message);
|
|
1722
1722
|
return;
|
|
1723
1723
|
}
|
|
1724
1724
|
throw e;
|
|
@@ -1747,7 +1747,7 @@ export function createHttpServer(deps) {
|
|
|
1747
1747
|
return (await cs.setPendingSteer(token, scope, { text, trusted })) ? "parked" : "no-checkpoint";
|
|
1748
1748
|
};
|
|
1749
1749
|
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) =>
|
|
1750
|
+
const sendNotRunning = (error) => sendError(res, 409, "steering.not_running", error);
|
|
1751
1751
|
const live = steerableRuns.get(taskId);
|
|
1752
1752
|
if (live) {
|
|
1753
1753
|
try {
|
|
@@ -1758,7 +1758,7 @@ export function createHttpServer(deps) {
|
|
|
1758
1758
|
catch (e) {
|
|
1759
1759
|
const code = e.code;
|
|
1760
1760
|
if (code === "steering.invalid_content") {
|
|
1761
|
-
|
|
1761
|
+
sendError(res, 422, "steering.invalid_content", e instanceof Error ? e.message : "invalid steering content");
|
|
1762
1762
|
return;
|
|
1763
1763
|
}
|
|
1764
1764
|
if (code !== "steering.not_running")
|
|
@@ -1896,7 +1896,7 @@ export function createHttpServer(deps) {
|
|
|
1896
1896
|
const compactInstructions = compactBody.instructions;
|
|
1897
1897
|
const live = steerableRuns.get(taskId);
|
|
1898
1898
|
if (!live) {
|
|
1899
|
-
|
|
1899
|
+
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
1900
|
return;
|
|
1901
1901
|
}
|
|
1902
1902
|
void live.compact(compactInstructions !== undefined ? { instructions: compactInstructions } : undefined).then((outcome) => {
|
|
@@ -1953,7 +1953,7 @@ export function createHttpServer(deps) {
|
|
|
1953
1953
|
return;
|
|
1954
1954
|
const live = steerableRuns.get(taskId);
|
|
1955
1955
|
if (!live) {
|
|
1956
|
-
|
|
1956
|
+
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
1957
|
return;
|
|
1958
1958
|
}
|
|
1959
1959
|
live.detach(body.toolCallId);
|
|
@@ -1992,10 +1992,7 @@ export function createHttpServer(deps) {
|
|
|
1992
1992
|
return;
|
|
1993
1993
|
}
|
|
1994
1994
|
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
|
-
});
|
|
1995
|
+
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
1996
|
return;
|
|
2000
1997
|
}
|
|
2001
1998
|
const trusted = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
@@ -2007,11 +2004,11 @@ export function createHttpServer(deps) {
|
|
|
2007
2004
|
}
|
|
2008
2005
|
if (wfRun && !runSessionAcceptOk(req, res, { sessionId: wfRun.originatingSessionId ?? null }, "workflow.agent-steer", "workflow not found"))
|
|
2009
2006
|
return;
|
|
2010
|
-
const sendNotRunningWf = (error) =>
|
|
2007
|
+
const sendNotRunningWf = (error) => sendError(res, 409, "steering.not_running", error);
|
|
2011
2008
|
const redacted = redactSteerIn(body.content, label);
|
|
2012
2009
|
const resolution = deps.workflowAgentRegistry?.resolve(wfRunId, label);
|
|
2013
2010
|
if (resolution && resolution.count > 1) {
|
|
2014
|
-
|
|
2011
|
+
sendError(res, 409, "steering.ambiguous_label", `${resolution.count} live agents share label '${label}' in this run — steer target is ambiguous`);
|
|
2015
2012
|
return;
|
|
2016
2013
|
}
|
|
2017
2014
|
const handle = resolution?.handle;
|
|
@@ -2195,27 +2192,23 @@ export function createHttpServer(deps) {
|
|
|
2195
2192
|
const stillParked = details?.error === "parked_pending_approval" || details?.status === "parked";
|
|
2196
2193
|
if (stopVerb && details?.error !== undefined && (details.status === "running" || stillParked || parkResumeWon || parkArbiterUnreachable)) {
|
|
2197
2194
|
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
|
-
});
|
|
2195
|
+
sendError(res, 409, notLocal
|
|
2196
|
+
? "stop.not_local"
|
|
2197
|
+
: parkArbiterUnreachable
|
|
2198
|
+
? "stop.park_arbiter_unreachable"
|
|
2199
|
+
: parkResumeWon
|
|
2200
|
+
? "stop.park_resume_won"
|
|
2201
|
+
: stillParked
|
|
2202
|
+
? "stop.parked"
|
|
2203
|
+
: "stop.not_landed", notLocal
|
|
2204
|
+
? "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)"
|
|
2205
|
+
: parkArbiterUnreachable
|
|
2206
|
+
? "the approval arbitration store was unreachable — this task's row stays parked (not stopped); retry the stop"
|
|
2207
|
+
: parkResumeWon
|
|
2208
|
+
? "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"
|
|
2209
|
+
: stillParked
|
|
2210
|
+
? "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"
|
|
2211
|
+
: `stop did not land (${details.error}) — the process may still be running`, { taskId: runId, target, content: out.content, output: out.details });
|
|
2219
2212
|
return;
|
|
2220
2213
|
}
|
|
2221
2214
|
const g14 = (() => {
|
|
@@ -2264,10 +2257,7 @@ export function createHttpServer(deps) {
|
|
|
2264
2257
|
return;
|
|
2265
2258
|
}
|
|
2266
2259
|
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
|
-
});
|
|
2260
|
+
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
2261
|
return;
|
|
2272
2262
|
}
|
|
2273
2263
|
const trusted = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
@@ -2278,7 +2268,7 @@ export function createHttpServer(deps) {
|
|
|
2278
2268
|
}
|
|
2279
2269
|
if (!runSessionAcceptOk(req, res, run, `subagent.${verb}`))
|
|
2280
2270
|
return;
|
|
2281
|
-
const send409 = (errorCode, error) =>
|
|
2271
|
+
const send409 = (errorCode, error) => sendError(res, 409, errorCode, error);
|
|
2282
2272
|
const redacted = redactSteerIn(body.content, target);
|
|
2283
2273
|
const resolution = deps.subagentSteerRegistry?.resolve(runId, target);
|
|
2284
2274
|
if (resolution && resolution.count > 1) {
|
|
@@ -2964,13 +2954,13 @@ export function createHttpServer(deps) {
|
|
|
2964
2954
|
return;
|
|
2965
2955
|
}
|
|
2966
2956
|
if (typeof body.reason === "string" && body.reason.length > MAX_APPROVAL_REASON_CHARS) {
|
|
2967
|
-
|
|
2957
|
+
sendError(res, 413, "reason_too_large", `reason too large (max ${MAX_APPROVAL_REASON_CHARS} chars)`);
|
|
2968
2958
|
return;
|
|
2969
2959
|
}
|
|
2970
2960
|
const run = await deps.runStore.getRun(taskId);
|
|
2971
2961
|
if (!run) {
|
|
2972
2962
|
if (deps.config.directDoorActive) {
|
|
2973
|
-
|
|
2963
|
+
sendError(res, 401, "principal_unverified", "principal proof required");
|
|
2974
2964
|
return;
|
|
2975
2965
|
}
|
|
2976
2966
|
sendJson(res, 404, { error: "task not found" });
|
|
@@ -2981,7 +2971,7 @@ export function createHttpServer(deps) {
|
|
|
2981
2971
|
const hdr = (n) => { const h = req.headers[n]; return Array.isArray(h) ? h[0] : h; };
|
|
2982
2972
|
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
2973
|
if (!proof.ok) {
|
|
2984
|
-
|
|
2974
|
+
sendError(res, proof.status, proof.errorCode, proof.error);
|
|
2985
2975
|
return;
|
|
2986
2976
|
}
|
|
2987
2977
|
deciderPrincipal = proof.principal;
|
|
@@ -3019,7 +3009,7 @@ export function createHttpServer(deps) {
|
|
|
3019
3009
|
return;
|
|
3020
3010
|
}
|
|
3021
3011
|
if (typeof body.reason === "string" && body.reason.length > MAX_APPROVAL_REASON_CHARS) {
|
|
3022
|
-
|
|
3012
|
+
sendError(res, 413, "reason_too_large", `reason too large (max ${MAX_APPROVAL_REASON_CHARS} chars)`);
|
|
3023
3013
|
return;
|
|
3024
3014
|
}
|
|
3025
3015
|
let remember = false;
|
|
@@ -3033,7 +3023,7 @@ export function createHttpServer(deps) {
|
|
|
3033
3023
|
return;
|
|
3034
3024
|
}
|
|
3035
3025
|
if (deps.config.directDoorActive) {
|
|
3036
|
-
|
|
3026
|
+
sendError(res, 400, "remember_not_in_proof", "remember is not supported on a direct-door worker (not covered by the decision proof)");
|
|
3037
3027
|
return;
|
|
3038
3028
|
}
|
|
3039
3029
|
if (!deps.approvalExemptionStore) {
|
|
@@ -3041,7 +3031,7 @@ export function createHttpServer(deps) {
|
|
|
3041
3031
|
return;
|
|
3042
3032
|
}
|
|
3043
3033
|
if (!body.checkpointToken && !body.boundCallId) {
|
|
3044
|
-
|
|
3034
|
+
sendError(res, 400, "remember_requires_binding", "remember requires the decision binding (echo checkpointToken and/or boundCallId from the pending approval)");
|
|
3045
3035
|
return;
|
|
3046
3036
|
}
|
|
3047
3037
|
remember = true;
|
|
@@ -3061,10 +3051,7 @@ export function createHttpServer(deps) {
|
|
|
3061
3051
|
}
|
|
3062
3052
|
}
|
|
3063
3053
|
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
|
-
});
|
|
3054
|
+
sendError(res, 400, "updated_input_not_in_proof", "updatedInput is not supported on a direct-door worker (not covered by the decision proof)");
|
|
3068
3055
|
return;
|
|
3069
3056
|
}
|
|
3070
3057
|
const binding = {
|
|
@@ -3081,7 +3068,7 @@ export function createHttpServer(deps) {
|
|
|
3081
3068
|
};
|
|
3082
3069
|
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
3070
|
if (!proof.ok) {
|
|
3084
|
-
|
|
3071
|
+
sendError(res, proof.status, proof.errorCode, proof.error);
|
|
3085
3072
|
return;
|
|
3086
3073
|
}
|
|
3087
3074
|
deciderPrincipal = proof.principal;
|
|
@@ -3133,7 +3120,7 @@ export function createHttpServer(deps) {
|
|
|
3133
3120
|
if (deps.approvalStore && url.startsWith("/v1/approvals")) {
|
|
3134
3121
|
const store = deps.approvalStore;
|
|
3135
3122
|
if (deps.config.directApprovalDoor) {
|
|
3136
|
-
|
|
3123
|
+
sendError(res, 503, "approval_door_unconfigured", "approvals are served by the direct door on this worker");
|
|
3137
3124
|
return;
|
|
3138
3125
|
}
|
|
3139
3126
|
if (req.method === "GET" && url === "/v1/approvals") {
|
|
@@ -3291,11 +3278,11 @@ export function createHttpServer(deps) {
|
|
|
3291
3278
|
scope = decodeURIComponent(url.slice("/v1/memory/sync/".length));
|
|
3292
3279
|
}
|
|
3293
3280
|
catch {
|
|
3294
|
-
|
|
3281
|
+
sendError(res, 400, "memory_sync_invalid_scope", "malformed percent-encoding in :scope", { code: "memory_sync_invalid_scope" });
|
|
3295
3282
|
return;
|
|
3296
3283
|
}
|
|
3297
3284
|
if (scope.length === 0) {
|
|
3298
|
-
|
|
3285
|
+
sendError(res, 400, "memory_sync_invalid_scope", "missing :scope", { code: "memory_sync_invalid_scope" });
|
|
3299
3286
|
return;
|
|
3300
3287
|
}
|
|
3301
3288
|
if (scope !== formatUserScope(principal) && !explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
|
|
@@ -3305,7 +3292,7 @@ export function createHttpServer(deps) {
|
|
|
3305
3292
|
const body = await readJson(req);
|
|
3306
3293
|
const parsed = parseMemorySyncRequest(body, scope);
|
|
3307
3294
|
if (!parsed.ok) {
|
|
3308
|
-
|
|
3295
|
+
sendError(res, 422, "memory_sync_invalid_body", parsed.error, { code: "memory_sync_invalid_body" });
|
|
3309
3296
|
return;
|
|
3310
3297
|
}
|
|
3311
3298
|
try {
|
|
@@ -3931,7 +3918,7 @@ export function createHttpServer(deps) {
|
|
|
3931
3918
|
}
|
|
3932
3919
|
catch (e) {
|
|
3933
3920
|
if (e instanceof SessionPolicyError) {
|
|
3934
|
-
|
|
3921
|
+
sendError(res, e.code === "conflict" ? 409 : 403, e.code, e.message);
|
|
3935
3922
|
return;
|
|
3936
3923
|
}
|
|
3937
3924
|
throw e;
|
|
@@ -4047,7 +4034,7 @@ export function createHttpServer(deps) {
|
|
|
4047
4034
|
if (typeof fs.blobSizes === "function") {
|
|
4048
4035
|
const known = (await fs.blobSizes([hash])).get(hash);
|
|
4049
4036
|
if (known !== undefined && known > cap) {
|
|
4050
|
-
|
|
4037
|
+
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
4038
|
return;
|
|
4052
4039
|
}
|
|
4053
4040
|
}
|
|
@@ -4057,7 +4044,7 @@ export function createHttpServer(deps) {
|
|
|
4057
4044
|
return;
|
|
4058
4045
|
}
|
|
4059
4046
|
if (bytes.byteLength > cap) {
|
|
4060
|
-
|
|
4047
|
+
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
4048
|
return;
|
|
4062
4049
|
}
|
|
4063
4050
|
const ct = workspaceContentType(pathParam);
|
|
@@ -4260,11 +4247,7 @@ export function createHttpServer(deps) {
|
|
|
4260
4247
|
}
|
|
4261
4248
|
const body = await readRawBody(req, SYNC_BLOB_MAX_BYTES);
|
|
4262
4249
|
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
|
-
});
|
|
4250
|
+
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
4251
|
return;
|
|
4269
4252
|
}
|
|
4270
4253
|
if (createHash("sha256").update(body).digest("hex") !== hash) {
|
|
@@ -4273,7 +4256,7 @@ export function createHttpServer(deps) {
|
|
|
4273
4256
|
}
|
|
4274
4257
|
const put = await fs.putBlob(hash, new Uint8Array(body));
|
|
4275
4258
|
if (!put.ok) {
|
|
4276
|
-
|
|
4259
|
+
sendError(res, 502, put.error.code, "blob store write failed", { code: put.error.code });
|
|
4277
4260
|
return;
|
|
4278
4261
|
}
|
|
4279
4262
|
res.writeHead(204).end();
|
|
@@ -4325,7 +4308,7 @@ export function createHttpServer(deps) {
|
|
|
4325
4308
|
const dstIds = (await deps.sessionStorage.listEntryIds?.(sessionId)) ?? null;
|
|
4326
4309
|
const relA = classifySyncRelationshipByIds(entryIds, dstIds);
|
|
4327
4310
|
if ((relA.relation === "fork" || relA.relation === "stale") && resolution !== "overwrite-dst") {
|
|
4328
|
-
|
|
4311
|
+
sendError(res, 409, "conflict", "sync conflict", { relation: relA });
|
|
4329
4312
|
return;
|
|
4330
4313
|
}
|
|
4331
4314
|
let payloadVerified = false;
|
|
@@ -4349,7 +4332,7 @@ export function createHttpServer(deps) {
|
|
|
4349
4332
|
for (const snap of snapshots) {
|
|
4350
4333
|
const r = await fss.importManifest(sessionId, snap.key, new Map(snap.manifest), getBlobA);
|
|
4351
4334
|
if (!r.ok) {
|
|
4352
|
-
|
|
4335
|
+
sendError(res, 422, r.error.code === "restore_failed" ? "restore_failed" : "missing_blob", `snapshot import failed (${r.error.code})`);
|
|
4353
4336
|
return;
|
|
4354
4337
|
}
|
|
4355
4338
|
}
|
|
@@ -4371,7 +4354,7 @@ export function createHttpServer(deps) {
|
|
|
4371
4354
|
}
|
|
4372
4355
|
const activeTaskId = await deps.runStore?.getActiveTaskId(sessionId);
|
|
4373
4356
|
if (activeTaskId) {
|
|
4374
|
-
|
|
4357
|
+
sendError(res, 409, "session_active", "session has an active run; sync after it settles", { activeTaskId });
|
|
4375
4358
|
return;
|
|
4376
4359
|
}
|
|
4377
4360
|
const heldLease = importLeases.get(sessionId);
|
|
@@ -4387,12 +4370,8 @@ export function createHttpServer(deps) {
|
|
|
4387
4370
|
}
|
|
4388
4371
|
else {
|
|
4389
4372
|
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
|
-
});
|
|
4373
|
+
sendError(res, 409, "import_in_flight", "an import is already in flight for this session"
|
|
4374
|
+
+ (retryAfterSec !== undefined ? ` (a stalled holder is taken over after ${staleSec}s of inactivity; retry in ~${retryAfterSec}s)` : ""), retryAfterSec !== undefined ? { retryAfterSec } : undefined);
|
|
4396
4375
|
return;
|
|
4397
4376
|
}
|
|
4398
4377
|
}
|
|
@@ -4408,14 +4387,14 @@ export function createHttpServer(deps) {
|
|
|
4408
4387
|
const present = await fss.hasBlobs(all);
|
|
4409
4388
|
const missing = all.find((h) => !present.has(h));
|
|
4410
4389
|
if (missing !== undefined) {
|
|
4411
|
-
|
|
4390
|
+
sendError(res, 422, "missing_blob", "missing blob; PUT all referenced blobs before import");
|
|
4412
4391
|
return;
|
|
4413
4392
|
}
|
|
4414
4393
|
}
|
|
4415
4394
|
else {
|
|
4416
4395
|
for (const hash of all) {
|
|
4417
4396
|
if (!(await deps.fileSnapshotStore.getBlob(hash))) {
|
|
4418
|
-
|
|
4397
|
+
sendError(res, 422, "missing_blob", "missing blob; PUT all referenced blobs before import");
|
|
4419
4398
|
return;
|
|
4420
4399
|
}
|
|
4421
4400
|
}
|
|
@@ -4521,13 +4500,13 @@ export function createHttpServer(deps) {
|
|
|
4521
4500
|
if (e instanceof PrecheckError) {
|
|
4522
4501
|
await pending.handle.abort().catch(() => undefined);
|
|
4523
4502
|
cleanupStaging(stagingId);
|
|
4524
|
-
|
|
4503
|
+
sendError(res, e.status, "invalid_entries", e.message);
|
|
4525
4504
|
return;
|
|
4526
4505
|
}
|
|
4527
4506
|
if (e instanceof SessionError && e.code === "invalid_session") {
|
|
4528
4507
|
await pending.handle.abort().catch(() => undefined);
|
|
4529
4508
|
cleanupStaging(stagingId);
|
|
4530
|
-
|
|
4509
|
+
sendError(res, 422, "invalid_entries", "staged session log failed validation");
|
|
4531
4510
|
return;
|
|
4532
4511
|
}
|
|
4533
4512
|
await pending.handle.abort().catch(() => undefined);
|
|
@@ -4538,7 +4517,7 @@ export function createHttpServer(deps) {
|
|
|
4538
4517
|
if (activeAtCommit) {
|
|
4539
4518
|
await pending.handle.abort().catch(() => undefined);
|
|
4540
4519
|
cleanupStaging(stagingId);
|
|
4541
|
-
|
|
4520
|
+
sendError(res, 409, "session_active", "session started an active run while the import streamed; sync after it settles", { activeTaskId: activeAtCommit });
|
|
4542
4521
|
return;
|
|
4543
4522
|
}
|
|
4544
4523
|
try {
|
|
@@ -4548,7 +4527,7 @@ export function createHttpServer(deps) {
|
|
|
4548
4527
|
for (const snap of pending.snapshots) {
|
|
4549
4528
|
const r = await importManifest(sessionId, snap.key, new Map(snap.manifest), getBlob);
|
|
4550
4529
|
if (!r.ok) {
|
|
4551
|
-
|
|
4530
|
+
sendError(res, 422, r.error.code === "restore_failed" ? "restore_failed" : "missing_blob", `snapshot import failed (${r.error.code})`);
|
|
4552
4531
|
cleanupStaging(stagingId);
|
|
4553
4532
|
return;
|
|
4554
4533
|
}
|
|
@@ -4570,12 +4549,12 @@ export function createHttpServer(deps) {
|
|
|
4570
4549
|
if (e instanceof SyncConflictError) {
|
|
4571
4550
|
await pending.handle.abort().catch(() => undefined);
|
|
4572
4551
|
cleanupStaging(stagingId);
|
|
4573
|
-
|
|
4552
|
+
sendError(res, 409, "conflict", "sync conflict", { relation: e.relation });
|
|
4574
4553
|
return;
|
|
4575
4554
|
}
|
|
4576
4555
|
if (e instanceof SessionPolicyError) {
|
|
4577
4556
|
cleanupStaging(stagingId);
|
|
4578
|
-
|
|
4557
|
+
sendError(res, e.code === "loosen_forbidden" ? 403 : 409, e.code, e.message);
|
|
4579
4558
|
return;
|
|
4580
4559
|
}
|
|
4581
4560
|
cleanupStaging(stagingId);
|
|
@@ -4640,7 +4619,7 @@ export function createHttpServer(deps) {
|
|
|
4640
4619
|
}
|
|
4641
4620
|
const allow = deps.config.attachmentMimeAllowlist;
|
|
4642
4621
|
if (allow && !allow.some((a) => a === mime || (a.endsWith("/*") && mime.startsWith(a.slice(0, -1))))) {
|
|
4643
|
-
|
|
4622
|
+
sendError(res, 415, "attachment.mime_not_allowed", `mime '${mime}' is not in this deployment's allowlist`, { code: "attachment.mime_not_allowed", allowed: allow });
|
|
4644
4623
|
return;
|
|
4645
4624
|
}
|
|
4646
4625
|
const max = deps.config.attachmentMaxBytes ?? 32 * 1024 * 1024;
|
|
@@ -4650,7 +4629,7 @@ export function createHttpServer(deps) {
|
|
|
4650
4629
|
}
|
|
4651
4630
|
catch (err) {
|
|
4652
4631
|
if (err instanceof HttpError && err.status === 413) {
|
|
4653
|
-
|
|
4632
|
+
sendError(res, 413, "attachment.too_large", `attachment exceeds the per-file limit (${max} bytes)`, { code: "attachment.too_large", maxBytes: max });
|
|
4654
4633
|
return;
|
|
4655
4634
|
}
|
|
4656
4635
|
throw err;
|
|
@@ -4772,7 +4751,7 @@ export function createHttpServer(deps) {
|
|
|
4772
4751
|
catch (e) {
|
|
4773
4752
|
const code = e.code;
|
|
4774
4753
|
if (typeof code === "string" && code.startsWith("notify.")) {
|
|
4775
|
-
|
|
4754
|
+
sendError(res, 400, code, e instanceof Error ? e.message : "invalid notification");
|
|
4776
4755
|
return;
|
|
4777
4756
|
}
|
|
4778
4757
|
}
|
|
@@ -5902,7 +5881,7 @@ export function createHttpServer(deps) {
|
|
|
5902
5881
|
return false;
|
|
5903
5882
|
deps.metrics?.inc("fleet_lease_rejected_total");
|
|
5904
5883
|
res.setHeader("retry-after", String(adm.retryAfterSec));
|
|
5905
|
-
|
|
5884
|
+
sendError(res, 429, "quota_exhausted", "budget exhausted (quota lease)", { code: "quota_exhausted", retryAfterSec: adm.retryAfterSec, ...(adm.detail ?? {}) });
|
|
5906
5885
|
return true;
|
|
5907
5886
|
}
|
|
5908
5887
|
function rateLimited(req, res) {
|
|
@@ -6879,6 +6858,9 @@ function sendJson(res, status, body) {
|
|
|
6879
6858
|
res.writeHead(status, { "content-type": "application/json" });
|
|
6880
6859
|
res.end(JSON.stringify(body));
|
|
6881
6860
|
}
|
|
6861
|
+
function sendError(res, status, errorCode, message, extra) {
|
|
6862
|
+
sendJson(res, status, { error: message, errorCode, ...extra });
|
|
6863
|
+
}
|
|
6882
6864
|
function msg(e) {
|
|
6883
6865
|
return e instanceof Error ? e.message : String(e);
|
|
6884
6866
|
}
|
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/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
|
package/dist/leader/wire.js
CHANGED
|
@@ -63,7 +63,7 @@ export function leaderResourceConfig(env = process.env) {
|
|
|
63
63
|
: {}),
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
-
export function
|
|
66
|
+
export function createLeaderRunner(cfg) {
|
|
67
67
|
const W = cfg.workspace ?? "/home/user";
|
|
68
68
|
const repo = `${W}/repo`;
|
|
69
69
|
const ident = cfg.git ?? { name: "leader", email: "leader@local" };
|