@sema-agent/server 7.79.3 → 7.80.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot/stage-07-capability-layer.js +2 -1
- package/dist/capabilities/skills.d.ts +10 -0
- package/dist/capabilities/skills.js +3 -1
- package/dist/http/admission.js +3 -1
- package/dist/http/dispatch.js +28 -8
- package/dist/http/resume-legs.js +4 -3
- package/dist/http/route-ctx.d.ts +101 -4
- package/dist/http/route-ctx.js +4 -0
- package/dist/http/route-table.d.ts +71 -43
- package/dist/http/route-table.js +111 -187
- package/dist/http/routes/a2a-serve.d.ts +18 -3
- package/dist/http/routes/a2a-serve.js +25 -23
- package/dist/http/routes/admin-config-refresh.d.ts +9 -2
- package/dist/http/routes/admin-config-refresh.js +32 -31
- package/dist/http/routes/admin-drain.d.ts +9 -2
- package/dist/http/routes/admin-drain.js +32 -31
- package/dist/http/routes/admin-memory-optout.d.ts +28 -2
- package/dist/http/routes/admin-memory-optout.js +27 -40
- package/dist/http/routes/adoption.d.ts +15 -2
- package/dist/http/routes/adoption.js +52 -52
- package/dist/http/routes/agents-roster.d.ts +9 -2
- package/dist/http/routes/agents-roster.js +82 -80
- package/dist/http/routes/approvals-assistant.d.ts +58 -5
- package/dist/http/routes/approvals-assistant.js +38 -39
- package/dist/http/routes/attachments.d.ts +22 -2
- package/dist/http/routes/attachments.js +81 -72
- package/dist/http/routes/capabilities.d.ts +21 -2
- package/dist/http/routes/capabilities.js +174 -170
- package/dist/http/routes/config-catalog.d.ts +9 -2
- package/dist/http/routes/config-catalog.js +28 -31
- package/dist/http/routes/devices.d.ts +24 -2
- package/dist/http/routes/devices.js +19 -21
- package/dist/http/routes/diagnostics.d.ts +9 -2
- package/dist/http/routes/diagnostics.js +41 -43
- package/dist/http/routes/fleet.d.ts +11 -2
- package/dist/http/routes/fleet.js +23 -22
- package/dist/http/routes/images.d.ts +63 -9
- package/dist/http/routes/images.js +53 -46
- package/dist/http/routes/leader.d.ts +20 -2
- package/dist/http/routes/leader.js +43 -40
- package/dist/http/routes/memory-bundle.d.ts +15 -2
- package/dist/http/routes/memory-bundle.js +38 -38
- package/dist/http/routes/memory-compliance.d.ts +16 -4
- package/dist/http/routes/memory-compliance.js +44 -45
- package/dist/http/routes/memory-consolidation.d.ts +14 -2
- package/dist/http/routes/memory-consolidation.js +66 -66
- package/dist/http/routes/memory-origin.d.ts +23 -4
- package/dist/http/routes/memory-origin.js +65 -65
- package/dist/http/routes/memory-policy.d.ts +21 -2
- package/dist/http/routes/memory-policy.js +119 -115
- package/dist/http/routes/notify-wake.d.ts +21 -11
- package/dist/http/routes/notify-wake.js +118 -116
- package/dist/http/routes/observability.d.ts +16 -2
- package/dist/http/routes/observability.js +54 -51
- package/dist/http/routes/retention-ops.d.ts +19 -2
- package/dist/http/routes/retention-ops.js +20 -22
- package/dist/http/routes/rules.d.ts +36 -2
- package/dist/http/routes/rules.js +22 -24
- package/dist/http/routes/run-memory-capture.d.ts +10 -3
- package/dist/http/routes/run-memory-capture.js +84 -84
- package/dist/http/routes/runs.d.ts +102 -22
- package/dist/http/routes/runs.js +79 -96
- package/dist/http/routes/session-sync.d.ts +41 -2
- package/dist/http/routes/session-sync.js +26 -24
- package/dist/http/routes/sessions-list.d.ts +16 -2
- package/dist/http/routes/sessions-list.js +78 -75
- package/dist/http/routes/sessions.d.ts +77 -2
- package/dist/http/routes/sessions.js +50 -47
- package/dist/http/routes/shared-memory.d.ts +19 -2
- package/dist/http/routes/shared-memory.js +65 -63
- package/dist/http/routes/side-query.d.ts +11 -2
- package/dist/http/routes/side-query.js +81 -79
- package/dist/http/routes/tasks.d.ts +17 -2
- package/dist/http/routes/tasks.js +18 -11
- package/dist/http/routes/trace-usage.d.ts +40 -2
- package/dist/http/routes/trace-usage.js +46 -35
- package/dist/http/routes/workflows.d.ts +35 -3
- package/dist/http/routes/workflows.js +29 -29
- package/dist/http/wire-types.d.ts +15 -9
- package/dist/parked-decide.d.ts +9 -0
- package/dist/parked-decide.js +3 -3
- package/dist/run-local.js +2 -1
- package/dist/spec-fields.js +33 -30
- package/dist/task-cwd.d.ts +21 -0
- package/dist/task-cwd.js +3 -0
- package/dist/trace/core-keyset-guard.d.ts +6 -1
- package/dist/trace/project.d.ts +20 -0
- package/dist/trace/project.js +15 -1
- package/package.json +2 -2
package/dist/http/routes/runs.js
CHANGED
|
@@ -25,19 +25,10 @@ import { mintCancelledResult } from "../../run-cancel-context.js";
|
|
|
25
25
|
import { buildCrossSliceUsage, readResourceWindow } from "../../resource-window.js";
|
|
26
26
|
import { headerStr, gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
27
27
|
import { ActorAssertionWire } from "@sema-agent/settings-schema";
|
|
28
|
+
import { assertNever } from "../route-ctx.js";
|
|
28
29
|
import { mintAndDriveDurableRun } from "../durable-run-leg.js";
|
|
29
30
|
import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
|
|
30
31
|
import { withPrincipal } from "../../observability/principal-context.js";
|
|
31
|
-
export const RUN_ID_RE = /^\/v1\/runs\/([^/]+)(\/events)?$/;
|
|
32
|
-
export const RUN_CANCEL_RE = /^\/v1\/runs\/([^/]+)\/cancel$/;
|
|
33
|
-
export const RUN_STEER_RE = /^\/v1\/runs\/([^/]+)\/steer$/;
|
|
34
|
-
export const RUN_DETACH_RE = /^\/v1\/runs\/([^/]+)\/detach$/;
|
|
35
|
-
export const RUN_COMPACT_RE = /^\/v1\/runs\/([^/]+)\/compact$/;
|
|
36
|
-
export const RUN_INTERRUPT_RE = /^\/v1\/runs\/([^/]+)\/interrupt$/;
|
|
37
|
-
export const ELICIT_RESPOND_RE = /^\/v1\/elicitations\/([^/]+)\/respond$/;
|
|
38
|
-
export const QUESTION_RESPOND_RE = /^\/v1\/questions\/([^/]+)\/respond$/;
|
|
39
|
-
export const TOOL_APPROVAL_RESPOND_RE = /^\/v1\/tool-approvals\/([^/]+)\/respond$/;
|
|
40
|
-
export const TASK_ASK_DECISION_RE = /^\/v1\/tasks\/([^/]+)\/asks\/([^/]+)\/decision$/;
|
|
41
32
|
const ASK_PARKING_RETRY_AFTER_SEC = 2;
|
|
42
33
|
const ASK_ACTOR_LABEL_MAX = 256;
|
|
43
34
|
const AskDecisionActorSchema = ActorAssertionWire.strip();
|
|
@@ -73,13 +64,7 @@ export function projectDecisionActor(raw) {
|
|
|
73
64
|
const parsed = AskDecisionActorSchema.safeParse(raw);
|
|
74
65
|
return parsed.success ? parsed.data : undefined;
|
|
75
66
|
}
|
|
76
|
-
|
|
77
|
-
export const RUN_SUBAGENT_RESUME_RE = /^\/v1\/runs\/([^/]+)\/subagents\/([^/]+)\/resume$/;
|
|
78
|
-
export const RUN_SUBAGENT_OUTPUT_RE = /^\/v1\/runs\/([^/]+)\/subagents\/([^/]+)\/output$/;
|
|
79
|
-
export const RUN_SUBAGENT_STREAM_RE = /^\/v1\/runs\/([^/]+)\/subagents\/([^/]+)\/stream$/;
|
|
80
|
-
export const RUN_TASK_OUTPUT_RE = /^\/v1\/runs\/([^/]+)\/tasks\/([^/]+)\/output$/;
|
|
81
|
-
export const RUN_TASK_STOP_RE = /^\/v1\/runs\/([^/]+)\/tasks\/([^/]+)\/stop$/;
|
|
82
|
-
async function subagentReadGate(req, res, deps, runId) {
|
|
67
|
+
async function subagentReadGate(req, res, deps, runId, search) {
|
|
83
68
|
const principal = gatedPrincipal(req, deps.config);
|
|
84
69
|
if (deps.config.requirePrincipal && principal === undefined) {
|
|
85
70
|
sendPrincipalRefusal(res, req, deps.config);
|
|
@@ -91,7 +76,7 @@ async function subagentReadGate(req, res, deps, runId) {
|
|
|
91
76
|
sendError(res, 404, "not_found.run", RUN_NOT_FOUND_MESSAGE);
|
|
92
77
|
return null;
|
|
93
78
|
}
|
|
94
|
-
const callerSession = new
|
|
79
|
+
const callerSession = new URLSearchParams(search).get("session");
|
|
95
80
|
if (!trusted && run.sessionId && callerSession !== run.sessionId) {
|
|
96
81
|
sendError(res, 404, "not_found.run", RUN_NOT_FOUND_MESSAGE);
|
|
97
82
|
return null;
|
|
@@ -149,23 +134,13 @@ async function streamRunEvents(req, res, deps, runStore, taskId, staleMs) {
|
|
|
149
134
|
staleFrame: () => ({ event: "failed", data: { type: "failed", errorMessage: "run stalled (instance lost?)" } }),
|
|
150
135
|
}, taskId, staleMs);
|
|
151
136
|
}
|
|
152
|
-
export async function handleRuns(req, res,
|
|
153
|
-
const miss = { fell: false };
|
|
154
|
-
await handleRunsBody(req, res, url, ctx, miss);
|
|
155
|
-
return !miss.fell;
|
|
156
|
-
}
|
|
157
|
-
export async function handleRunVerbs(req, res, url, ctx) {
|
|
158
|
-
const miss = { fell: false };
|
|
159
|
-
await handleRunVerbsBody(req, res, url, ctx, miss);
|
|
160
|
-
return !miss.fell;
|
|
161
|
-
}
|
|
162
|
-
async function handleRunsBody(req, res, url, ctx, miss) {
|
|
137
|
+
export async function handleRuns(req, res, match, ctx) {
|
|
163
138
|
const { deps } = ctx;
|
|
164
139
|
const { idemCache, inflightRuns, preemptableRuns, cancelledViaVerb, steerableRuns, wakeParkMints } = ctx.registry;
|
|
165
140
|
const { readJson, rateLimited, quotaExceeded, leaseDenied, usageWindowDenied, runOwnerOk, runSessionAcceptOk } = ctx.helpers;
|
|
166
141
|
const { prepareSpec } = ctx.legs;
|
|
167
142
|
const source = ctx.req.source;
|
|
168
|
-
if (
|
|
143
|
+
if (match.decl.id === "runs-submit") {
|
|
169
144
|
if (!deps.runStore) {
|
|
170
145
|
sendError(res, 501, "capability.run_store_required", "async runs require a durable run store (DB_BACKEND=mysql|pg|local)");
|
|
171
146
|
return;
|
|
@@ -189,13 +164,12 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
189
164
|
sendJson(res, resp.status, resp.body);
|
|
190
165
|
return;
|
|
191
166
|
}
|
|
192
|
-
|
|
193
|
-
if (runMatch) {
|
|
167
|
+
if (match.decl.id === "run-detail" || match.decl.id === "run-events") {
|
|
194
168
|
if (!deps.runStore) {
|
|
195
169
|
sendError(res, 501, "capability.run_store_required", "async runs require a durable run store (DB_BACKEND=mysql|pg|local)");
|
|
196
170
|
return;
|
|
197
171
|
}
|
|
198
|
-
const taskId =
|
|
172
|
+
const taskId = match.params.taskId;
|
|
199
173
|
if (deps.config.requirePrincipal && !gatedPrincipal(req, deps.config)) {
|
|
200
174
|
sendPrincipalRefusal(res, req, deps.config);
|
|
201
175
|
return;
|
|
@@ -207,9 +181,9 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
207
181
|
}
|
|
208
182
|
if (!runOwnerOk(req, res, run.owner))
|
|
209
183
|
return;
|
|
210
|
-
if (!runSessionAcceptOk(req, res, run,
|
|
184
|
+
if (!runSessionAcceptOk(req, res, run, match.decl.id === "run-events" ? "run.events" : "run.poll"))
|
|
211
185
|
return;
|
|
212
|
-
if (
|
|
186
|
+
if (match.decl.id === "run-events") {
|
|
213
187
|
await streamRunEvents(req, res, deps, deps.runStore, taskId, deps.config.runStaleSec * 1000);
|
|
214
188
|
}
|
|
215
189
|
else {
|
|
@@ -272,8 +246,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
272
246
|
}
|
|
273
247
|
return;
|
|
274
248
|
}
|
|
275
|
-
|
|
276
|
-
if (cancelMatch) {
|
|
249
|
+
if (match.decl.id === "run-cancel") {
|
|
277
250
|
if (!deps.runStore) {
|
|
278
251
|
sendError(res, 501, "capability.run_store_required", "async runs require a durable run store (DB_BACKEND=mysql|pg|local)");
|
|
279
252
|
return;
|
|
@@ -282,7 +255,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
282
255
|
sendPrincipalRefusal(res, req, deps.config);
|
|
283
256
|
return;
|
|
284
257
|
}
|
|
285
|
-
const taskId =
|
|
258
|
+
const taskId = match.params.taskId;
|
|
286
259
|
const run = await deps.runStore.getRun(taskId);
|
|
287
260
|
if (!run) {
|
|
288
261
|
sendError(res, 404, "not_found.run", runNotFoundMessage(taskId));
|
|
@@ -377,7 +350,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
377
350
|
continue;
|
|
378
351
|
}
|
|
379
352
|
if (now?.status === "running") {
|
|
380
|
-
sendError(res, 409, "conflict.run_not_running", "the cancel flag could not be set: this run was not in 'running' state at the moment of each attempt (it keeps flipping between running and a
|
|
353
|
+
sendError(res, 409, "conflict.run_not_running", "the cancel flag could not be set: this run was not in 'running' state at the moment of each attempt (it keeps flipping between running and a pending-decision state; the `status` below is the word the row actually had) — retry cancel", { taskId, status: now.status });
|
|
381
354
|
return;
|
|
382
355
|
}
|
|
383
356
|
sendJson(res, 202, { taskId, status: now?.status ?? "failed", cancelRequested: now?.cancelRequested ?? false, note: "run already terminal — cancel is a no-op" });
|
|
@@ -397,8 +370,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
397
370
|
}
|
|
398
371
|
return;
|
|
399
372
|
}
|
|
400
|
-
|
|
401
|
-
if (steerMatch) {
|
|
373
|
+
if (match.decl.id === "run-steer") {
|
|
402
374
|
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
|
|
403
375
|
return;
|
|
404
376
|
if (!deps.runStore) {
|
|
@@ -410,7 +382,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
410
382
|
sendPrincipalRefusal(res, req, deps.config);
|
|
411
383
|
return;
|
|
412
384
|
}
|
|
413
|
-
const taskId =
|
|
385
|
+
const taskId = match.params.taskId;
|
|
414
386
|
let body;
|
|
415
387
|
try {
|
|
416
388
|
body = ((await readJson(req)) ?? {});
|
|
@@ -556,7 +528,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
556
528
|
sendDuplicateKey();
|
|
557
529
|
return;
|
|
558
530
|
}
|
|
559
|
-
sendNotRunning("run is no longer
|
|
531
|
+
sendNotRunning("run is no longer waiting on a pending decision (resolved or expired)");
|
|
560
532
|
return;
|
|
561
533
|
}
|
|
562
534
|
if (run.status === "running") {
|
|
@@ -565,7 +537,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
565
537
|
sendNotRunning("run is live on another replica — a live steer must reach the replica holding the stream; resend it there, or use POST /v1/runs/:id/cancel for a run-level stop");
|
|
566
538
|
}
|
|
567
539
|
else {
|
|
568
|
-
sendNotRunning("no live steerable face for this run on this replica — verify
|
|
540
|
+
sendNotRunning("no live steerable face for this run on this replica — a verify or cascade submission exposes none (those return a result, not a live stream); use POST /v1/runs/:id/cancel for a run-level stop");
|
|
569
541
|
}
|
|
570
542
|
return;
|
|
571
543
|
}
|
|
@@ -665,7 +637,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
665
637
|
return;
|
|
666
638
|
}
|
|
667
639
|
if (mintResult.noResumeCtx) {
|
|
668
|
-
sendError(res, 409, "conflict.resume_context_unavailable", "run already ended and this session's resume context is gone — a steer
|
|
640
|
+
sendError(res, 409, "conflict.resume_context_unavailable", "run already ended and this session's resume context is gone — a steer queued now could never be delivered by POST /v1/sessions/:id/wake; submit a new task instead");
|
|
669
641
|
return;
|
|
670
642
|
}
|
|
671
643
|
if (mintResult.parked) {
|
|
@@ -676,8 +648,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
676
648
|
sendNotRunning(`run is ${run.status} — not accepting steers`);
|
|
677
649
|
return;
|
|
678
650
|
}
|
|
679
|
-
|
|
680
|
-
if (interruptMatch) {
|
|
651
|
+
if (match.decl.id === "run-interrupt") {
|
|
681
652
|
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
|
|
682
653
|
return;
|
|
683
654
|
if (!deps.runStore) {
|
|
@@ -689,7 +660,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
689
660
|
sendPrincipalRefusal(res, req, deps.config);
|
|
690
661
|
return;
|
|
691
662
|
}
|
|
692
|
-
const taskId =
|
|
663
|
+
const taskId = match.params.taskId;
|
|
693
664
|
let interruptRaw;
|
|
694
665
|
try {
|
|
695
666
|
interruptRaw = await readJson(req);
|
|
@@ -752,7 +723,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
752
723
|
const sendInterruptNotRunning = (error) => sendError(res, 409, "steering.not_running", error);
|
|
753
724
|
const sendNoLiveTurn = (status, streamJustSettled) => {
|
|
754
725
|
if (isParkedRunStatus(status)) {
|
|
755
|
-
sendError(res, 409, "interrupt.nothing_in_flight", "run is
|
|
726
|
+
sendError(res, 409, "interrupt.nothing_in_flight", "run is waiting on a pending decision — there is no in-flight turn to interrupt; to deliver input use POST /v1/runs/:id/steer (it waits with the pending decision and is delivered when the run resumes)");
|
|
756
727
|
return;
|
|
757
728
|
}
|
|
758
729
|
if (streamJustSettled) {
|
|
@@ -765,7 +736,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
765
736
|
sendError(res, 409, "interrupt.not_held", "run is live on another replica — interrupt must reach the replica holding the stream; resend it there, or use POST /v1/runs/:id/cancel for a run-level stop");
|
|
766
737
|
}
|
|
767
738
|
else {
|
|
768
|
-
sendError(res, 409, "interrupt.not_held", "no live interruptible turn face for this run on this replica — verify
|
|
739
|
+
sendError(res, 409, "interrupt.not_held", "no live interruptible turn face for this run on this replica — a verify or cascade submission exposes none (those return a result, not a live stream); use POST /v1/runs/:id/cancel for a run-level stop");
|
|
769
740
|
}
|
|
770
741
|
return;
|
|
771
742
|
}
|
|
@@ -822,8 +793,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
822
793
|
sendJson(res, 202, { taskId, status: run.status, delivery: "queued-immediate", messageId: interruptMessageId, note: "interrupt accepted — the cut is best-effort: a task.turn_interrupted event on the run stream is the proof a turn was actually cut; with nothing in flight the text lands at the next turn boundary" });
|
|
823
794
|
return;
|
|
824
795
|
}
|
|
825
|
-
|
|
826
|
-
if (compactMatch) {
|
|
796
|
+
if (match.decl.id === "run-compact") {
|
|
827
797
|
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
|
|
828
798
|
return;
|
|
829
799
|
if (!deps.runStore) {
|
|
@@ -835,7 +805,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
835
805
|
sendPrincipalRefusal(res, req, deps.config);
|
|
836
806
|
return;
|
|
837
807
|
}
|
|
838
|
-
const taskId =
|
|
808
|
+
const taskId = match.params.taskId;
|
|
839
809
|
const run = await deps.runStore.getRun(taskId);
|
|
840
810
|
if (!run) {
|
|
841
811
|
sendError(res, 404, "not_found.run", RUN_NOT_FOUND_MESSAGE);
|
|
@@ -889,8 +859,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
889
859
|
sendJson(res, 202, { taskId, status: "running", delivery: "accepted", note: "compaction will run at the next turn boundary; a compacted{trigger:'manual'} event rides the run stream if anything is summarized" });
|
|
890
860
|
return;
|
|
891
861
|
}
|
|
892
|
-
|
|
893
|
-
if (detachMatch) {
|
|
862
|
+
if (match.decl.id === "run-detach") {
|
|
894
863
|
if (rateLimited(req, res))
|
|
895
864
|
return;
|
|
896
865
|
if (!deps.runStore) {
|
|
@@ -902,7 +871,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
902
871
|
sendPrincipalRefusal(res, req, deps.config);
|
|
903
872
|
return;
|
|
904
873
|
}
|
|
905
|
-
const taskId =
|
|
874
|
+
const taskId = match.params.taskId;
|
|
906
875
|
let body;
|
|
907
876
|
try {
|
|
908
877
|
body = ((await readJson(req)) ?? {});
|
|
@@ -940,24 +909,23 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
940
909
|
sendJson(res, 202, { taskId, toolCallId: body.toolCallId, delivery: "requested", note: "if the tool call is running in a detach-capable env it settles early with 'moved to background; task_id=b*'; otherwise the request is a no-op (fail-safe)" });
|
|
941
910
|
return;
|
|
942
911
|
}
|
|
943
|
-
|
|
912
|
+
return assertNever(match.decl.id);
|
|
944
913
|
}
|
|
945
|
-
async function
|
|
914
|
+
export async function handleRunVerbs(req, res, match, ctx) {
|
|
946
915
|
const { deps } = ctx;
|
|
947
916
|
const { readJson, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, safeDecode, runSessionAcceptOk } = ctx.helpers;
|
|
948
|
-
|
|
949
|
-
if (subOutputMatch) {
|
|
917
|
+
if (match.decl.id === "run-subagent-output") {
|
|
950
918
|
if (!deps.runStore || !deps.subagentTaskOutput) {
|
|
951
919
|
sendError(res, 501, "capability.run_store_required", "subagent output reads require the run store");
|
|
952
920
|
return;
|
|
953
921
|
}
|
|
954
|
-
const runId = safeDecode(
|
|
955
|
-
const target = safeDecode(
|
|
922
|
+
const runId = safeDecode(match.params.runId);
|
|
923
|
+
const target = safeDecode(match.params.sub);
|
|
956
924
|
if (runId === null || target === null) {
|
|
957
925
|
sendError(res, 400, "request.path_malformed", "malformed subagent path (invalid percent-encoding)");
|
|
958
926
|
return;
|
|
959
927
|
}
|
|
960
|
-
const gated = await subagentReadGate(req, res, deps, runId);
|
|
928
|
+
const gated = await subagentReadGate(req, res, deps, runId, match.search);
|
|
961
929
|
if (!gated)
|
|
962
930
|
return;
|
|
963
931
|
const { run } = gated;
|
|
@@ -971,19 +939,18 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
971
939
|
sendJson(res, 200, { taskId: runId, target, content: out.content, output: out.details });
|
|
972
940
|
return;
|
|
973
941
|
}
|
|
974
|
-
|
|
975
|
-
if (subStreamMatch) {
|
|
942
|
+
if (match.decl.id === "run-subagent-stream") {
|
|
976
943
|
if (!deps.runStore || !deps.subagentTaskOutput) {
|
|
977
944
|
sendError(res, 501, "capability.run_store_required", "subagent streams require the run store");
|
|
978
945
|
return;
|
|
979
946
|
}
|
|
980
|
-
const runId = safeDecode(
|
|
981
|
-
const target = safeDecode(
|
|
947
|
+
const runId = safeDecode(match.params.runId);
|
|
948
|
+
const target = safeDecode(match.params.sub);
|
|
982
949
|
if (runId === null || target === null) {
|
|
983
950
|
sendError(res, 400, "request.path_malformed", "malformed subagent path (invalid percent-encoding)");
|
|
984
951
|
return;
|
|
985
952
|
}
|
|
986
|
-
const gatedTail = await subagentReadGate(req, res, deps, runId);
|
|
953
|
+
const gatedTail = await subagentReadGate(req, res, deps, runId, match.search);
|
|
987
954
|
if (!gatedTail)
|
|
988
955
|
return;
|
|
989
956
|
const { run } = gatedTail;
|
|
@@ -1034,26 +1001,25 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1034
1001
|
}
|
|
1035
1002
|
return;
|
|
1036
1003
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const stopVerb = req.method === "POST";
|
|
1004
|
+
if (match.decl.id === "run-task-output" || match.decl.id === "run-task-stop") {
|
|
1005
|
+
const stopVerb = match.decl.id === "run-task-stop";
|
|
1040
1006
|
if (!deps.runStore || !deps.taskHandleOutput || !deps.taskHandleStop) {
|
|
1041
|
-
sendError(res, 501, "capability.run_store_required", "task-handle verbs require the run store
|
|
1007
|
+
sendError(res, 501, "capability.run_store_required", "task-handle verbs require the run store and the task registry");
|
|
1042
1008
|
return;
|
|
1043
1009
|
}
|
|
1044
1010
|
if (stopVerb && rateLimited(req, res))
|
|
1045
1011
|
return;
|
|
1046
|
-
const runId = safeDecode(
|
|
1047
|
-
const target = safeDecode(
|
|
1012
|
+
const runId = safeDecode(match.params.runId);
|
|
1013
|
+
const target = safeDecode(match.params.tid);
|
|
1048
1014
|
if (runId === null || target === null) {
|
|
1049
1015
|
sendError(res, 400, "request.path_malformed", "malformed task path (invalid percent-encoding)");
|
|
1050
1016
|
return;
|
|
1051
1017
|
}
|
|
1052
|
-
const gatedB = await subagentReadGate(req, res, deps, runId);
|
|
1018
|
+
const gatedB = await subagentReadGate(req, res, deps, runId, match.search);
|
|
1053
1019
|
if (!gatedB)
|
|
1054
1020
|
return;
|
|
1055
1021
|
const { run } = gatedB;
|
|
1056
|
-
const q = new
|
|
1022
|
+
const q = new URLSearchParams(match.search);
|
|
1057
1023
|
const access = { owner: runId, scope: run.owner ?? "default", ...(run.sessionId ? { sessionId: run.sessionId } : {}) };
|
|
1058
1024
|
if (q.get("filter") !== null) {
|
|
1059
1025
|
sendError(res, 400, "request.param_unsupported", "filter is not accepted on the wire — fetch the output and filter client-side (note: the wire serves the clipped projection)");
|
|
@@ -1106,9 +1072,8 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1106
1072
|
sendJson(res, 200, { taskId: runId, target, content: out.content, output: out.details, ...g14 });
|
|
1107
1073
|
return;
|
|
1108
1074
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
const verb = url.endsWith("/resume") ? "resume" : "steer";
|
|
1075
|
+
if (match.decl.id === "run-subagent-steer" || match.decl.id === "run-subagent-resume") {
|
|
1076
|
+
const verb = match.decl.id === "run-subagent-resume" ? "resume" : "steer";
|
|
1112
1077
|
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
|
|
1113
1078
|
return;
|
|
1114
1079
|
if (!deps.runStore) {
|
|
@@ -1120,8 +1085,8 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1120
1085
|
sendPrincipalRefusal(res, req, deps.config);
|
|
1121
1086
|
return;
|
|
1122
1087
|
}
|
|
1123
|
-
const runId = safeDecode(
|
|
1124
|
-
const target = safeDecode(
|
|
1088
|
+
const runId = safeDecode(match.params.runId);
|
|
1089
|
+
const target = safeDecode(match.params.sub);
|
|
1125
1090
|
if (runId === null || target === null) {
|
|
1126
1091
|
sendError(res, 400, "request.path_malformed", "malformed subagent path (invalid percent-encoding)");
|
|
1127
1092
|
return;
|
|
@@ -1204,8 +1169,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1204
1169
|
: `run is ${run.status} — its sub-agents are no longer addressable`);
|
|
1205
1170
|
return;
|
|
1206
1171
|
}
|
|
1207
|
-
|
|
1208
|
-
if (elicitMatch) {
|
|
1172
|
+
if (match.decl.id === "elicitation-respond") {
|
|
1209
1173
|
if (rateLimited(req, res))
|
|
1210
1174
|
return;
|
|
1211
1175
|
if (!deps.elicitation) {
|
|
@@ -1217,7 +1181,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1217
1181
|
sendPrincipalRefusal(res, req, deps.config);
|
|
1218
1182
|
return;
|
|
1219
1183
|
}
|
|
1220
|
-
const id =
|
|
1184
|
+
const id = match.params.id;
|
|
1221
1185
|
let body;
|
|
1222
1186
|
try {
|
|
1223
1187
|
body = await readJson(req);
|
|
@@ -1230,8 +1194,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1230
1194
|
sendJson(res, status, respBody);
|
|
1231
1195
|
return;
|
|
1232
1196
|
}
|
|
1233
|
-
|
|
1234
|
-
if (questionMatch) {
|
|
1197
|
+
if (match.decl.id === "question-respond") {
|
|
1235
1198
|
if (rateLimited(req, res))
|
|
1236
1199
|
return;
|
|
1237
1200
|
if (!deps.question) {
|
|
@@ -1243,7 +1206,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1243
1206
|
sendPrincipalRefusal(res, req, deps.config);
|
|
1244
1207
|
return;
|
|
1245
1208
|
}
|
|
1246
|
-
const id =
|
|
1209
|
+
const id = match.params.id;
|
|
1247
1210
|
let body;
|
|
1248
1211
|
try {
|
|
1249
1212
|
body = await readJson(req);
|
|
@@ -1256,8 +1219,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1256
1219
|
sendJson(res, status, respBody);
|
|
1257
1220
|
return;
|
|
1258
1221
|
}
|
|
1259
|
-
|
|
1260
|
-
if (approvalMatch) {
|
|
1222
|
+
if (match.decl.id === "tool-approval-respond") {
|
|
1261
1223
|
if (rateLimited(req, res))
|
|
1262
1224
|
return;
|
|
1263
1225
|
if (!deps.toolApproval) {
|
|
@@ -1269,7 +1231,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1269
1231
|
sendPrincipalRefusal(res, req, deps.config);
|
|
1270
1232
|
return;
|
|
1271
1233
|
}
|
|
1272
|
-
const id =
|
|
1234
|
+
const id = match.params.id;
|
|
1273
1235
|
let body;
|
|
1274
1236
|
try {
|
|
1275
1237
|
body = await readJson(req);
|
|
@@ -1286,8 +1248,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1286
1248
|
sendJson(res, status, respBody);
|
|
1287
1249
|
return;
|
|
1288
1250
|
}
|
|
1289
|
-
|
|
1290
|
-
if (askDecisionMatch) {
|
|
1251
|
+
if (match.decl.id === "task-ask-decision") {
|
|
1291
1252
|
if (rateLimited(req, res))
|
|
1292
1253
|
return;
|
|
1293
1254
|
const gate = resolveStreamApprovalGate({
|
|
@@ -1298,7 +1259,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1298
1259
|
});
|
|
1299
1260
|
const askStore = gate.active ? gate.askStore : undefined;
|
|
1300
1261
|
if (!askStore) {
|
|
1301
|
-
sendError(res, 501, "feature.approval_ask_disabled", "the durable stream-approval ask ledger is not enabled on this worker (needs STREAM_APPROVAL_ENABLED plus a DURABLE store backend — the local
|
|
1262
|
+
sendError(res, 501, "feature.approval_ask_disabled", "the durable stream-approval ask ledger is not enabled on this worker (needs STREAM_APPROVAL_ENABLED plus a DURABLE store backend — the local backend keeps asks in memory and would lose accepted decisions on restart)");
|
|
1302
1263
|
return;
|
|
1303
1264
|
}
|
|
1304
1265
|
const principal = gatedPrincipal(req, deps.config);
|
|
@@ -1321,8 +1282,8 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1321
1282
|
}
|
|
1322
1283
|
const body = parsedBody.data;
|
|
1323
1284
|
const hasUpdatedInput = typeof rawBody === "object" && rawBody !== null && "updatedInput" in rawBody;
|
|
1324
|
-
const taskId =
|
|
1325
|
-
const askId =
|
|
1285
|
+
const taskId = match.params.taskId;
|
|
1286
|
+
const askId = match.params.askId;
|
|
1326
1287
|
const askNotFound = () => sendError(res, 404, "not_found.ask", "no such approval ask under this task (never existed, belongs to another task, or is not yours)");
|
|
1327
1288
|
const row0 = await askStore.getAsk(askId);
|
|
1328
1289
|
if (!row0 || row0.taskId !== taskId) {
|
|
@@ -1531,7 +1492,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1531
1492
|
});
|
|
1532
1493
|
return;
|
|
1533
1494
|
}
|
|
1534
|
-
|
|
1495
|
+
return assertNever(match.decl.id);
|
|
1535
1496
|
}
|
|
1536
1497
|
export async function createDurableRun(req, res, ctx, prepared, idemKey) {
|
|
1537
1498
|
const { deps } = ctx;
|
|
@@ -1571,4 +1532,26 @@ export async function createDurableRun(req, res, ctx, prepared, idemKey) {
|
|
|
1571
1532
|
}), (r) => r.status === 202);
|
|
1572
1533
|
return resp;
|
|
1573
1534
|
}
|
|
1535
|
+
export const RUNS_ROUTES = [
|
|
1536
|
+
{ id: "runs-submit", path: "/v1/runs", billable: true, methods: ["POST"] },
|
|
1537
|
+
{ id: "run-steer", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/steer$/, label: "/v1/runs/:id/steer", methods: ["POST"] },
|
|
1538
|
+
{ id: "run-interrupt", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/interrupt$/, label: "/v1/runs/:id/interrupt", methods: ["POST"] },
|
|
1539
|
+
{ id: "run-detach", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/detach$/, label: "/v1/runs/:id/detach", methods: ["POST"] },
|
|
1540
|
+
{ id: "run-cancel", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/cancel$/, label: "/v1/runs/:id/cancel", methods: ["POST"] },
|
|
1541
|
+
{ id: "run-compact", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/compact$/, label: "/v1/runs/:id/compact", methods: ["POST"] },
|
|
1542
|
+
{ id: "run-events", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/events$/, label: "/v1/runs/:id/events", methods: ["GET"] },
|
|
1543
|
+
{ id: "run-detail", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)$/, label: "/v1/runs/:id", methods: ["GET"] },
|
|
1544
|
+
];
|
|
1545
|
+
export const RUN_VERBS_ROUTES = [
|
|
1546
|
+
{ id: "run-subagent-output", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/output$/, label: "/v1/runs/:id/subagents/:sub/*", methods: ["GET"] },
|
|
1547
|
+
{ id: "run-subagent-stream", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/stream$/, label: "/v1/runs/:id/subagents/:sub/*", methods: ["GET"] },
|
|
1548
|
+
{ id: "run-subagent-steer", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/steer$/, label: "/v1/runs/:id/subagents/:sub/*", methods: ["POST"] },
|
|
1549
|
+
{ id: "run-subagent-resume", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/subagents\/(?<sub>[^/]+)\/resume$/, label: "/v1/runs/:id/subagents/:sub/*", billable: true, methods: ["POST"] },
|
|
1550
|
+
{ id: "run-task-output", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/tasks\/(?<tid>[^/]+)\/output$/, label: "/v1/runs/:id/tasks/:tid/*", methods: ["GET"] },
|
|
1551
|
+
{ id: "run-task-stop", pattern: /^\/v1\/runs\/(?<runId>[^/]+)\/tasks\/(?<tid>[^/]+)\/stop$/, label: "/v1/runs/:id/tasks/:tid/*", methods: ["POST"] },
|
|
1552
|
+
{ id: "elicitation-respond", pattern: /^\/v1\/elicitations\/(?<id>[^/]+)\/respond$/, label: "/v1/elicitations/:id/respond", methods: ["POST"] },
|
|
1553
|
+
{ id: "question-respond", pattern: /^\/v1\/questions\/(?<id>[^/]+)\/respond$/, label: "/v1/questions/:id/respond", methods: ["POST"] },
|
|
1554
|
+
{ id: "tool-approval-respond", pattern: /^\/v1\/tool-approvals\/(?<id>[^/]+)\/respond$/, label: "/v1/tool-approvals/:id/respond", methods: ["POST"] },
|
|
1555
|
+
{ id: "task-ask-decision", pattern: /^\/v1\/tasks\/(?<taskId>[^/]+)\/asks\/(?<askId>[^/]+)\/decision$/, label: "/v1/tasks/:id/asks/:askId/decision", methods: ["POST"] },
|
|
1556
|
+
];
|
|
1574
1557
|
//# sourceMappingURL=runs.js.map
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
11
11
|
import { type SessionRulesRecord, type FileHistoryExport } from "@sema-agent/core";
|
|
12
12
|
import { type StagingHandle } from "../../session-sync-kernel.js";
|
|
13
|
-
import type { RouteCtx } from "../route-ctx.js";
|
|
13
|
+
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
14
14
|
export type PendingStaging = {
|
|
15
15
|
handle: StagingHandle;
|
|
16
16
|
realSessionId: string;
|
|
@@ -45,5 +45,44 @@ export interface SessionSyncLocal {
|
|
|
45
45
|
cleanupStaging(stagingId: string): void;
|
|
46
46
|
}
|
|
47
47
|
export declare function createSessionSyncLocal(): SessionSyncLocal;
|
|
48
|
-
|
|
48
|
+
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。 */
|
|
49
|
+
export declare function handleSessionSync(req: IncomingMessage, res: ServerResponse, match: RouteMatch<SessionSyncRouteId>, ctx: RouteCtx): Promise<void>;
|
|
50
|
+
export declare const SESSION_SYNC_ROUTES: readonly [{
|
|
51
|
+
readonly id: "session-sync-manifest";
|
|
52
|
+
readonly pattern: RegExp;
|
|
53
|
+
readonly label: "/v1/sessions/:id/sync/$verb";
|
|
54
|
+
readonly methods: readonly ["GET"];
|
|
55
|
+
}, {
|
|
56
|
+
readonly id: "session-sync-entries";
|
|
57
|
+
readonly pattern: RegExp;
|
|
58
|
+
readonly label: "/v1/sessions/:id/sync/$verb";
|
|
59
|
+
readonly methods: readonly ["GET"];
|
|
60
|
+
}, {
|
|
61
|
+
readonly id: "session-sync-plan";
|
|
62
|
+
readonly pattern: RegExp;
|
|
63
|
+
readonly label: "/v1/sessions/:id/sync/$verb";
|
|
64
|
+
readonly methods: readonly ["POST"];
|
|
65
|
+
}, {
|
|
66
|
+
readonly id: "session-sync-import";
|
|
67
|
+
readonly pattern: RegExp;
|
|
68
|
+
readonly label: "/v1/sessions/:id/sync/import";
|
|
69
|
+
readonly methods: readonly ["POST"];
|
|
70
|
+
}, {
|
|
71
|
+
readonly id: "session-sync-import-entries";
|
|
72
|
+
readonly pattern: RegExp;
|
|
73
|
+
readonly label: "/v1/sessions/:id/sync/import";
|
|
74
|
+
readonly methods: readonly ["POST"];
|
|
75
|
+
}, {
|
|
76
|
+
readonly id: "session-sync-blob-put";
|
|
77
|
+
readonly pattern: RegExp;
|
|
78
|
+
readonly label: "/v1/sessions/:id/sync/blobs";
|
|
79
|
+
readonly methods: readonly ["PUT"];
|
|
80
|
+
}, {
|
|
81
|
+
readonly id: "session-sync-history-blob";
|
|
82
|
+
readonly pattern: RegExp;
|
|
83
|
+
readonly label: "/v1/sessions/:id/sync/history/blobs/:hash";
|
|
84
|
+
readonly methods: readonly ["GET"];
|
|
85
|
+
}];
|
|
86
|
+
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
87
|
+
export type SessionSyncRouteId = RouteIdsOf<typeof SESSION_SYNC_ROUTES>;
|
|
49
88
|
//# sourceMappingURL=session-sync.d.ts.map
|