@sema-agent/server 1.205.0 → 1.207.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/brain.d.ts.map +1 -1
- package/dist/brain.js +3 -3
- package/dist/brain.js.map +1 -1
- package/dist/capabilities/scenarios.d.ts +2 -1
- package/dist/capabilities/scenarios.d.ts.map +1 -1
- package/dist/capabilities/scenarios.js +14 -6
- package/dist/capabilities/scenarios.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js +89 -14
- package/dist/http/server.js.map +1 -1
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -1
- package/dist/plugins/file-run-store.d.ts.map +1 -1
- package/dist/plugins/file-run-store.js +8 -6
- package/dist/plugins/file-run-store.js.map +1 -1
- package/dist/plugins/memory-run-store.d.ts.map +1 -1
- package/dist/plugins/memory-run-store.js +7 -5
- package/dist/plugins/memory-run-store.js.map +1 -1
- package/dist/plugins/pg-run-store.d.ts.map +1 -1
- package/dist/plugins/pg-run-store.js +1 -1
- package/dist/plugins/pg-run-store.js.map +1 -1
- package/dist/plugins/tidb-run-store.d.ts.map +1 -1
- package/dist/plugins/tidb-run-store.js +1 -1
- package/dist/plugins/tidb-run-store.js.map +1 -1
- package/package.json +2 -2
package/dist/http/server.js
CHANGED
|
@@ -178,6 +178,7 @@ export function createHttpServer(deps) {
|
|
|
178
178
|
const idemCache = new IdempotencyCache();
|
|
179
179
|
const bakeSubmitLimiter = new RateLimiter(deps.config.imageBakes?.submitRateMax ?? 0, (deps.config.imageBakes?.submitRateWindowSec ?? 60) * 1000);
|
|
180
180
|
const inflightRuns = new Map();
|
|
181
|
+
const cancelledViaVerb = new Set();
|
|
181
182
|
const preemptableRuns = new Map();
|
|
182
183
|
const steerableRuns = new Map();
|
|
183
184
|
const wakeParkMints = new Map();
|
|
@@ -742,7 +743,7 @@ export function createHttpServer(deps) {
|
|
|
742
743
|
if (created.ok)
|
|
743
744
|
deps.sessionTitler?.maybeTitle(prepared.spec.sessionId, prepared.spec.objective);
|
|
744
745
|
if (!created.ok) {
|
|
745
|
-
const conflict = { error: "session already has an active run", activeTaskId: created.activeTaskId };
|
|
746
|
+
const conflict = { error: "session already has an active run — POST /v1/runs/{activeTaskId}/cancel stops it (same-instance interactive runs abort immediately)", activeTaskId: created.activeTaskId };
|
|
746
747
|
res.write(`data: ${JSON.stringify({ type: "done", result: { status: "failed", errorMessage: conflict.error, activeTaskId: conflict.activeTaskId } })}\n\n`);
|
|
747
748
|
return { status: 409, body: conflict };
|
|
748
749
|
}
|
|
@@ -755,8 +756,21 @@ export function createHttpServer(deps) {
|
|
|
755
756
|
const rs = deps.runStore;
|
|
756
757
|
const tid = durableTaskId;
|
|
757
758
|
const owner = principal ?? null;
|
|
758
|
-
|
|
759
|
+
const abortFromVerb = () => {
|
|
760
|
+
cancelledViaVerb.add(tid);
|
|
761
|
+
ac.abort();
|
|
762
|
+
};
|
|
763
|
+
durableHeartbeat = setInterval(() => {
|
|
764
|
+
void rs.heartbeat(tid, owner).catch(() => undefined);
|
|
765
|
+
if (!ac.signal.aborted) {
|
|
766
|
+
void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c)
|
|
767
|
+
abortFromVerb(); }).catch(() => undefined);
|
|
768
|
+
}
|
|
769
|
+
}, HEARTBEAT_MS);
|
|
759
770
|
durableHeartbeat.unref?.();
|
|
771
|
+
inflightRuns.set(tid, ac);
|
|
772
|
+
void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c && !ac.signal.aborted)
|
|
773
|
+
abortFromVerb(); }).catch(() => undefined);
|
|
760
774
|
}
|
|
761
775
|
const fleetPub = durableTaskId
|
|
762
776
|
? fleetRunPublisher(deps.fleetBus, { runId: durableTaskId, scope: gatedPrincipal(req, deps.config) ?? "default", rootTaskId: prepared.spec.sessionId, ...fleetRunLabels(prepared.spec.objective) })
|
|
@@ -841,6 +855,9 @@ export function createHttpServer(deps) {
|
|
|
841
855
|
finalResult = stripCheckpointToken(ev.result);
|
|
842
856
|
if (durableTaskId && finalResult)
|
|
843
857
|
finalResult = { ...finalResult, taskId: durableTaskId };
|
|
858
|
+
if (durableTaskId && cancelledViaVerb.has(durableTaskId) && finalResult?.status === "failed" && !finalResult.errorCode) {
|
|
859
|
+
finalResult = { ...finalResult, errorCode: "cancelled" };
|
|
860
|
+
}
|
|
844
861
|
finalizeTaskResult(ev.result, principal, prepared.spec.objective, prepared.spec.sessionId);
|
|
845
862
|
putRewindAnchor();
|
|
846
863
|
settleFleet(finalResult?.status ?? "completed");
|
|
@@ -947,6 +964,10 @@ export function createHttpServer(deps) {
|
|
|
947
964
|
clearInterval(durableHeartbeat);
|
|
948
965
|
if (durableTaskId && steerableRuns.get(durableTaskId) === liveStreamRef)
|
|
949
966
|
steerableRuns.delete(durableTaskId);
|
|
967
|
+
if (durableTaskId && inflightRuns.get(durableTaskId) === ac) {
|
|
968
|
+
inflightRuns.delete(durableTaskId);
|
|
969
|
+
cancelledViaVerb.delete(durableTaskId);
|
|
970
|
+
}
|
|
950
971
|
for (const evict of subagentHandleEvictions)
|
|
951
972
|
evict();
|
|
952
973
|
if (!runRowSettled && durableTaskId && deps.runStore) {
|
|
@@ -997,33 +1018,62 @@ export function createHttpServer(deps) {
|
|
|
997
1018
|
const tid = uuidv7();
|
|
998
1019
|
const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", runMeta(prepared, source));
|
|
999
1020
|
if (!created.ok)
|
|
1000
|
-
return { status: 409, body: { error: "session already has an active run", activeTaskId: created.activeTaskId } };
|
|
1021
|
+
return { status: 409, body: { error: "session already has an active run — POST /v1/runs/{activeTaskId}/cancel stops it (same-instance interactive runs abort immediately)", activeTaskId: created.activeTaskId } };
|
|
1001
1022
|
deps.sessionTitler?.maybeTitle(prepared.spec.sessionId, prepared.spec.objective);
|
|
1002
1023
|
durableTaskId = tid;
|
|
1003
1024
|
}
|
|
1004
1025
|
if (deps.checkpointStore && prepared.spec.sessionId) {
|
|
1005
1026
|
await deps.checkpointStore.putCtx(prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope });
|
|
1006
1027
|
}
|
|
1028
|
+
const syncCancelCtrl = new AbortController();
|
|
1029
|
+
if (prepared.spec.sessionId)
|
|
1030
|
+
markChildrenStoppedByUserOnAbort(syncCancelCtrl.signal, prepared.spec.sessionId, prepared.auth?.principal);
|
|
1007
1031
|
if (durableTaskId && deps.runStore) {
|
|
1008
1032
|
const rs = deps.runStore;
|
|
1009
1033
|
const tid = durableTaskId;
|
|
1010
1034
|
const owner = principal ?? null;
|
|
1011
|
-
|
|
1035
|
+
const abortFromVerb = () => {
|
|
1036
|
+
cancelledViaVerb.add(tid);
|
|
1037
|
+
syncCancelCtrl.abort();
|
|
1038
|
+
};
|
|
1039
|
+
durableHeartbeat = setInterval(() => {
|
|
1040
|
+
void rs.heartbeat(tid, owner).catch(() => undefined);
|
|
1041
|
+
if (!syncCancelCtrl.signal.aborted) {
|
|
1042
|
+
void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c)
|
|
1043
|
+
abortFromVerb(); }).catch(() => undefined);
|
|
1044
|
+
}
|
|
1045
|
+
}, HEARTBEAT_MS);
|
|
1012
1046
|
durableHeartbeat.unref?.();
|
|
1047
|
+
inflightRuns.set(tid, syncCancelCtrl);
|
|
1048
|
+
void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c && !syncCancelCtrl.signal.aborted)
|
|
1049
|
+
abortFromVerb(); }).catch(() => undefined);
|
|
1050
|
+
}
|
|
1051
|
+
else if (durableTaskId) {
|
|
1052
|
+
inflightRuns.set(durableTaskId, syncCancelCtrl);
|
|
1013
1053
|
}
|
|
1054
|
+
const specWithSignal = { ...prepared.spec, signal: syncCancelCtrl.signal };
|
|
1014
1055
|
let result;
|
|
1056
|
+
let cancelVerbLabel = false;
|
|
1015
1057
|
uncountedBillableInflight++;
|
|
1016
1058
|
try {
|
|
1017
1059
|
result = await withPrincipal(principal, () => prepared.verify
|
|
1018
|
-
? runWithVerification(deps.runner,
|
|
1060
|
+
? runWithVerification(deps.runner, specWithSignal, { maxRounds: prepared.verify.maxRounds })
|
|
1019
1061
|
: prepared.cascade
|
|
1020
|
-
? runCascade(deps.runner,
|
|
1021
|
-
: deps.runner.runTask(
|
|
1062
|
+
? runCascade(deps.runner, specWithSignal, cascadeConfig(deps.config.cascadeLadder, prepared.spec.maxCostUsd))
|
|
1063
|
+
: deps.runner.runTask(specWithSignal));
|
|
1022
1064
|
}
|
|
1023
1065
|
finally {
|
|
1024
1066
|
uncountedBillableInflight--;
|
|
1025
1067
|
if (durableHeartbeat)
|
|
1026
1068
|
clearInterval(durableHeartbeat);
|
|
1069
|
+
if (durableTaskId && inflightRuns.get(durableTaskId) === syncCancelCtrl) {
|
|
1070
|
+
inflightRuns.delete(durableTaskId);
|
|
1071
|
+
cancelVerbLabel = cancelledViaVerb.has(durableTaskId);
|
|
1072
|
+
cancelledViaVerb.delete(durableTaskId);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
if (cancelVerbLabel && result.status === "failed" && !result.errorCode) {
|
|
1076
|
+
result = { ...result, errorCode: "cancelled" };
|
|
1027
1077
|
}
|
|
1028
1078
|
const resumeAtStatus = resumeAtHttpStatus(result);
|
|
1029
1079
|
if (resumeAtStatus) {
|
|
@@ -1099,7 +1149,7 @@ export function createHttpServer(deps) {
|
|
|
1099
1149
|
if (clientTaskId && created.activeTaskId === clientTaskId) {
|
|
1100
1150
|
return { status: 202, body: { taskId: clientTaskId, sessionId, status: "running" } };
|
|
1101
1151
|
}
|
|
1102
|
-
return { status: 409, body: { error: "session already has an active run", activeTaskId: created.activeTaskId } };
|
|
1152
|
+
return { status: 409, body: { error: "session already has an active run — POST /v1/runs/{activeTaskId}/cancel stops it (same-instance interactive runs abort immediately)", activeTaskId: created.activeTaskId } };
|
|
1103
1153
|
}
|
|
1104
1154
|
if (deps.checkpointStore) {
|
|
1105
1155
|
await deps.checkpointStore.putCtx(sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope });
|
|
@@ -1197,7 +1247,7 @@ export function createHttpServer(deps) {
|
|
|
1197
1247
|
const taskId = cancelMatch[1];
|
|
1198
1248
|
const run = await deps.runStore.getRun(taskId);
|
|
1199
1249
|
if (!run) {
|
|
1200
|
-
sendJson(res, 404, { error: "run not found" });
|
|
1250
|
+
sendJson(res, 404, { error: "run not found — the id belongs to no run in this deployment's run store (a run from another server process, or an in-memory store that did not survive a restart, is not visible here)" });
|
|
1201
1251
|
return;
|
|
1202
1252
|
}
|
|
1203
1253
|
if (!runOwnerOk(req, res, run.owner))
|
|
@@ -1245,10 +1295,30 @@ export function createHttpServer(deps) {
|
|
|
1245
1295
|
const err = note;
|
|
1246
1296
|
try {
|
|
1247
1297
|
const result = { taskId, sessionId: run.sessionId, status: "failed", errorCode: "cancelled", errorMessage: err, stats: { turns: 0, tokens: 0 } };
|
|
1248
|
-
|
|
1298
|
+
let lastErr;
|
|
1299
|
+
for (let attempt = 0;; attempt++) {
|
|
1300
|
+
try {
|
|
1301
|
+
await rs.setTerminal(taskId, "failed", result, err);
|
|
1302
|
+
lastErr = undefined;
|
|
1303
|
+
break;
|
|
1304
|
+
}
|
|
1305
|
+
catch (e) {
|
|
1306
|
+
lastErr = e;
|
|
1307
|
+
if (attempt >= 2)
|
|
1308
|
+
break;
|
|
1309
|
+
await new Promise((r) => setTimeout(r, 100 * (attempt + 1)));
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
if (lastErr !== undefined)
|
|
1313
|
+
throw lastErr;
|
|
1249
1314
|
}
|
|
1250
1315
|
catch (e) {
|
|
1251
|
-
sendJson(res, 500, { taskId, error: `cancel progressed (${note}) but could not terminalize the run row — retry cancel; a stuck row is reaped after the stale window (${e instanceof Error ? e.message : String(e)})` });
|
|
1316
|
+
sendJson(res, 500, { taskId, error: `cancel progressed (${note}) but could not terminalize the run row (retried) — retry cancel; a stuck row is reaped after the stale window (${e instanceof Error ? e.message : String(e)})` });
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
const finalRow = await rs.getRun(taskId).catch(() => undefined);
|
|
1320
|
+
if (finalRow && finalRow.errorCode !== "cancelled" && (finalRow.status === "failed" || finalRow.status === "completed" || finalRow.status === "blocked" || finalRow.status === "timeout")) {
|
|
1321
|
+
sendJson(res, 202, { taskId, status: finalRow.status, errorCode: finalRow.errorCode ?? null, note: `session unlocked; the run was terminalized concurrently (${finalRow.errorCode ?? finalRow.status}) before this cancel's write — reporting the actual ledger state` });
|
|
1252
1322
|
return;
|
|
1253
1323
|
}
|
|
1254
1324
|
sendJson(res, 202, { taskId, status: "failed", errorCode: "cancelled", note });
|
|
@@ -1265,7 +1335,10 @@ export function createHttpServer(deps) {
|
|
|
1265
1335
|
}
|
|
1266
1336
|
return;
|
|
1267
1337
|
}
|
|
1268
|
-
inflightRuns.
|
|
1338
|
+
if (inflightRuns.has(taskId)) {
|
|
1339
|
+
cancelledViaVerb.add(taskId);
|
|
1340
|
+
inflightRuns.get(taskId).abort();
|
|
1341
|
+
}
|
|
1269
1342
|
sendJson(res, 202, { taskId, status: "cancelling" });
|
|
1270
1343
|
}
|
|
1271
1344
|
else if (run.status === "suspended" || run.status === "needs_review") {
|
|
@@ -4323,8 +4396,10 @@ export function createHttpServer(deps) {
|
|
|
4323
4396
|
}
|
|
4324
4397
|
finally {
|
|
4325
4398
|
if (taskId) {
|
|
4326
|
-
inflightRuns.
|
|
4327
|
-
|
|
4399
|
+
if (inflightRuns.get(taskId) === cancelCtrl)
|
|
4400
|
+
inflightRuns.delete(taskId);
|
|
4401
|
+
if (preemptableRuns.get(taskId) === preemptCtrl)
|
|
4402
|
+
preemptableRuns.delete(taskId);
|
|
4328
4403
|
if (resumeStreamRef !== undefined && steerableRuns.get(taskId) === resumeStreamRef)
|
|
4329
4404
|
steerableRuns.delete(taskId);
|
|
4330
4405
|
for (const evict of subagentHandleEvictions)
|