@sema-agent/server 7.124.1 → 7.125.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/MIGRATION.md +138 -2
- package/USAGE.md +46 -8
- package/dist/audit.d.ts +27 -5
- package/dist/audit.js +42 -8
- package/dist/boot/execution-env.d.ts +33 -1
- package/dist/boot/execution-env.js +31 -2
- package/dist/boot/resolve-spec.js +4 -3
- package/dist/boot/session-faces.d.ts +14 -1
- package/dist/boot/session-faces.js +6 -5
- package/dist/boot/stage-02-web-admin-pull.js +5 -1
- package/dist/boot/stage-05-execution-env.js +6 -2
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/web-admin.js +130 -140
- package/dist/capabilities/center-wiring.d.ts +2 -1
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capability-facts.d.ts +28 -3
- package/dist/capability-facts.js +20 -0
- package/dist/config-catalog.js +4 -1
- package/dist/config-d-migration.js +2 -1
- package/dist/config-lkg.d.ts +4 -1
- package/dist/config-lkg.js +16 -12
- package/dist/config-provider.d.ts +4 -1
- package/dist/config-provider.js +1 -0
- package/dist/config-types.d.ts +10 -3
- package/dist/config.js +44 -14
- package/dist/env-facts.d.ts +2 -2
- package/dist/env-facts.js +3 -2
- package/dist/fleet/fleet-bus.d.ts +24 -12
- package/dist/fleet/fleet-bus.js +4 -1
- package/dist/fleet/workspace-snapshot-residual.d.ts +10 -0
- package/dist/fleet/workspace-snapshot-residual.js +35 -0
- package/dist/hooks/hook-runner.d.ts +15 -27
- package/dist/hooks/hook-runner.js +19 -59
- package/dist/http/active-run-conflict.d.ts +3 -0
- package/dist/http/active-run-conflict.js +3 -0
- package/dist/http/admission.js +7 -7
- package/dist/http/approval-sweeps.js +2 -4
- package/dist/http/device-placement.js +1 -0
- package/dist/http/dispatch.js +1 -1
- package/dist/http/durable-run-leg.d.ts +20 -6
- package/dist/http/durable-run-leg.js +4 -4
- package/dist/http/idempotency.d.ts +83 -38
- package/dist/http/idempotency.js +38 -13
- package/dist/http/request-guards.d.ts +18 -0
- package/dist/http/request-guards.js +12 -3
- package/dist/http/resume-legs.js +7 -1
- package/dist/http/route-ctx.d.ts +3 -3
- package/dist/http/route-ctx.js +1 -1
- package/dist/http/routes/a2a-serve.js +27 -11
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/fleet.js +2 -0
- package/dist/http/routes/images.js +2 -2
- package/dist/http/routes/leader.d.ts +4 -0
- package/dist/http/routes/leader.js +13 -3
- package/dist/http/routes/observability.js +2 -0
- package/dist/http/routes/runs.d.ts +6 -2
- package/dist/http/routes/runs.js +71 -101
- package/dist/http/routes/session-sync.d.ts +3 -0
- package/dist/http/routes/session-sync.js +30 -1
- package/dist/http/routes/sessions-list.d.ts +11 -0
- package/dist/http/routes/sessions-list.js +6 -6
- package/dist/http/routes/sessions.d.ts +6 -1
- package/dist/http/routes/sessions.js +41 -10
- package/dist/http/routes/tasks.js +63 -21
- package/dist/http/run-read-face.d.ts +26 -0
- package/dist/http/run-read-face.js +67 -0
- package/dist/http/server.d.ts +4 -4
- package/dist/http/server.js +1 -2
- package/dist/http/submit-replay.d.ts +40 -0
- package/dist/http/submit-replay.js +22 -0
- package/dist/leader/endpoint.d.ts +10 -5
- package/dist/leader/endpoint.js +27 -5
- package/dist/observability/cost-quota.d.ts +51 -7
- package/dist/observability/cost-quota.js +35 -17
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/observability/run-terminal.d.ts +5 -9
- package/dist/observability/run-terminal.js +2 -7
- package/dist/plugins/approval-ask-store-sql.js +1 -1
- package/dist/plugins/caching-session-store.d.ts +7 -2
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/device-store-sql.js +1 -1
- package/dist/plugins/ensure-index.js +22 -0
- package/dist/plugins/file-run-store.d.ts +24 -14
- package/dist/plugins/file-run-store.js +52 -12
- package/dist/plugins/leader-run-store-sql.d.ts +1 -11
- package/dist/plugins/leader-run-store-sql.js +1 -5
- package/dist/plugins/local-session-store.d.ts +35 -6
- package/dist/plugins/local-session-store.js +87 -18
- package/dist/plugins/memory-run-store.d.ts +13 -12
- package/dist/plugins/memory-run-store.js +25 -0
- package/dist/plugins/one-time-migrations.js +1 -1
- package/dist/plugins/pg-cost-quota.d.ts +1 -1
- package/dist/plugins/pg-cost-quota.js +2 -5
- package/dist/plugins/pg-pool.js +3 -0
- package/dist/plugins/pg-session-storage.d.ts +11 -1
- package/dist/plugins/pg-session-storage.js +44 -8
- package/dist/plugins/remote-env-k8s.d.ts +127 -12
- package/dist/plugins/remote-env-k8s.js +270 -27
- package/dist/plugins/run-store-sql.d.ts +26 -12
- package/dist/plugins/run-store-sql.js +50 -25
- package/dist/plugins/schema-consistency.d.ts +1 -13
- package/dist/plugins/schema-consistency.js +1 -41
- package/dist/plugins/schema-indexes.js +1 -0
- package/dist/plugins/schema-live-columns.d.ts +20 -0
- package/dist/plugins/schema-live-columns.js +42 -0
- package/dist/plugins/store-contracts.d.ts +40 -2
- package/dist/plugins/tidb-cost-quota.d.ts +1 -1
- package/dist/plugins/tidb-cost-quota.js +2 -5
- package/dist/plugins/tidb-pool.js +14 -3
- package/dist/plugins/tidb-session-store.d.ts +22 -2
- package/dist/plugins/tidb-session-store.js +33 -6
- package/dist/plugins/write-behind-counter.d.ts +12 -5
- package/dist/plugins/write-behind-counter.js +14 -7
- package/dist/prompts-domain-validate.js +3 -2
- package/dist/rules-consent.d.ts +2 -2
- package/dist/rules-consent.js +2 -2
- package/dist/run-local.js +31 -23
- package/dist/runs.d.ts +9 -0
- package/dist/runs.js +8 -0
- package/dist/runtime-governance.d.ts +3 -3
- package/dist/runtime-governance.js +2 -4
- package/dist/security.d.ts +19 -2
- package/dist/session-row.d.ts +53 -0
- package/dist/session-row.js +47 -0
- package/dist/session-sync.d.ts +6 -0
- package/dist/session-sync.js +5 -1
- package/dist/session-titler.d.ts +9 -5
- package/dist/session-titler.js +3 -1
- package/dist/spec-fields.js +2 -2
- package/dist/task-cwd.d.ts +0 -20
- package/dist/task-cwd.js +2 -10
- package/dist/tool-approval.d.ts +5 -7
- package/dist/trace/redact.d.ts +23 -50
- package/dist/trace/redact.js +42 -9
- package/dist/trace/self-minted-wire.d.ts +25 -0
- package/dist/trace/self-minted-wire.js +9 -0
- package/dist/web-admin-client/apply-effective.d.ts +10 -12
- package/dist/web-admin-client/apply-effective.js +2 -3
- package/dist/web-admin-client/candidate-gate.d.ts +69 -0
- package/dist/web-admin-client/candidate-gate.js +27 -0
- package/dist/web-admin-client/http-client.d.ts +2 -1
- package/dist/web-admin-client/http-client.js +2 -1
- package/dist/web-admin-client/pull-state.d.ts +6 -4
- package/dist/web-admin-client/read-warnings.d.ts +29 -5
- package/dist/web-admin-client/read-warnings.js +24 -4
- package/dist/web-admin-client/stage-limits.js +4 -1
- package/dist/workspace-space.d.ts +41 -0
- package/dist/workspace-space.js +5 -0
- package/package.json +3 -3
package/dist/http/routes/runs.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { memoRedactedRead, memoRedactTerminalResult, redactErrorMessage, redactHead, redactLedgerEventForRead, redactTerminalResult } from "../../observability/run-terminal.js";
|
|
1
|
+
import { redactHead, redactLedgerEventForRead } from "../../observability/run-terminal.js";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
import { uuidv7, mintCheckpointToken, CheckpointError, policyCannotAnswer, validatePendingSteer, TASK_OUTPUT_REPLAY_KINDS, isSystemInjectionPriority } from "@sema-agent/core";
|
|
5
|
-
import {
|
|
4
|
+
import { isTerminalRunStatus, isParkedRunStatus, runProvenElsewhere } from "../../plugins/store-contracts.js";
|
|
6
5
|
import { LIVE_RUN_GATE_KINDS, TERMINAL_PARK_GATE_KINDS } from "../../plugins/checkpoint-store-sql.js";
|
|
7
6
|
import { redactSecrets } from "../../trace/redact.js";
|
|
8
|
-
import { HttpError,
|
|
7
|
+
import { HttpError, isUuidV7, encodeCheckpointScope } from "../../security.js";
|
|
9
8
|
import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from "../../orchestration/workflow-agent-steer.js";
|
|
10
9
|
import { defaultSubagentTailBus } from "../../fleet/subagent-tail-bus.js";
|
|
11
|
-
import {
|
|
12
|
-
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
13
|
-
import { createHash } from "node:crypto";
|
|
14
|
-
import { scopedIdempotencyKey } from "../idempotency.js";
|
|
10
|
+
import { idemCallerOf, idemKeyOf, inflightKeyOf } from "../idempotency.js";
|
|
15
11
|
import { streamSseLog } from "../sse-log.js";
|
|
16
12
|
import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
17
13
|
import { buildApprovalPreamble, buildApprovalPreambleSseFrames, readClosedWordSeat, storedCardDemandsAPerson } from "../../approval-card.js";
|
|
@@ -21,15 +17,15 @@ import { MAX_DECISION_NOTE_CHARS } from "../../approval-ask-machine.js";
|
|
|
21
17
|
import { causeOfAskRow } from "../../approval-deny-reasons.js";
|
|
22
18
|
import { normalizeRunEventType, screenedStoredEventData } from "../../trace/project.js";
|
|
23
19
|
import { sendJson, sendError, httpErrorCode, sseHeaders } from "../send.js";
|
|
24
|
-
import { readTurnActivityMs } from "../../turn-activity.js";
|
|
25
20
|
import { mintCancelledResult } from "../../run-cancel-context.js";
|
|
26
21
|
import { acceptedInputKindOf } from "../../user-input-ledger.js";
|
|
27
22
|
import { INJECTION_TIERS } from "../../trace/injection-tier.js";
|
|
28
|
-
import { buildCrossSliceUsage, readResourceWindow } from "../../resource-window.js";
|
|
29
23
|
import { headerStr, gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
30
24
|
import { ActorAssertionWire } from "@sema-agent/settings-schema";
|
|
31
25
|
import * as facts from "../../capability-facts.js";
|
|
32
26
|
import { assertNever } from "../route-ctx.js";
|
|
27
|
+
import { readRunFace } from "../run-read-face.js";
|
|
28
|
+
import { replayOfExistingRun } from "../submit-replay.js";
|
|
33
29
|
import { mintAndDriveDurableRun } from "../durable-run-leg.js";
|
|
34
30
|
import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
|
|
35
31
|
import { withPrincipal } from "../../observability/principal-context.js";
|
|
@@ -191,16 +187,27 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
191
187
|
}
|
|
192
188
|
const runStore = runs.runStore;
|
|
193
189
|
const rawIdem = headerStr(req.headers["idempotency-key"]);
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
190
|
+
const submitter = gatedPrincipal(req, deps.config);
|
|
191
|
+
const idemKey = rawIdem ? idemKeyOf(rawIdem, idemCallerOf(req, ctx), "run") : undefined;
|
|
192
|
+
let keyedBody;
|
|
193
|
+
if (idemKey) {
|
|
194
|
+
keyedBody = await readJson(req);
|
|
195
|
+
const clientTaskId = clientTaskIdOf(keyedBody);
|
|
196
|
+
if (clientTaskId === false) {
|
|
197
|
+
sendError(res, 400, "request.id_invalid", "body.taskId must be a uuidv7 string (caller-minted idempotency key)");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const inflight = idemCache.peek(inflightKeyOf(idemKey, "durable", clientTaskId));
|
|
201
|
+
const keyedRow = inflight ? undefined : await runStore.getRunByIdem(idemKey);
|
|
202
|
+
if (inflight || keyedRow) {
|
|
203
|
+
const resp = inflight ? await inflight : await replayOfExistingRun(keyedRow, { principal: submitter, ...(clientTaskId !== undefined ? { clientTaskId } : {}) }, "durable", deps, runStore);
|
|
204
|
+
sendJson(res, resp.status, resp.body);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
200
207
|
}
|
|
201
208
|
if (rateLimited(req, res) || quotaExceeded(req, res))
|
|
202
209
|
return;
|
|
203
|
-
const prepared = await prepareSpec(req, res);
|
|
210
|
+
const prepared = await prepareSpec(req, res, keyedBody);
|
|
204
211
|
if (!prepared)
|
|
205
212
|
return;
|
|
206
213
|
const resp = await createDurableRun(req, res, ctx, prepared, idemKey);
|
|
@@ -209,23 +216,38 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
209
216
|
sendJson(res, resp.status, resp.body);
|
|
210
217
|
return;
|
|
211
218
|
}
|
|
212
|
-
if (match.decl.id === "run-detail" || match.decl.id === "run-events") {
|
|
219
|
+
if (match.decl.id === "run-detail" || match.decl.id === "run-events" || match.decl.id === "runs-by-idem") {
|
|
213
220
|
const runs = facts.durableRuns(deps);
|
|
214
221
|
if (!runs.available) {
|
|
215
222
|
sendError(res, 501, "capability.run_store_required", "async runs require a durable run store (DB_BACKEND=mysql|pg|local)");
|
|
216
223
|
return;
|
|
217
224
|
}
|
|
218
225
|
const runStore = runs.runStore;
|
|
219
|
-
const taskId = match.params.taskId;
|
|
220
226
|
if (deps.config.requirePrincipal && !gatedPrincipal(req, deps.config)) {
|
|
221
227
|
sendPrincipalRefusal(res, req, deps.config);
|
|
222
228
|
return;
|
|
223
229
|
}
|
|
224
|
-
|
|
230
|
+
let run;
|
|
231
|
+
let notFound;
|
|
232
|
+
if (match.decl.id === "runs-by-idem") {
|
|
233
|
+
const rawKey = new URLSearchParams(match.search).get("idempotencyKey");
|
|
234
|
+
if (!rawKey) {
|
|
235
|
+
sendError(res, 400, "request.query_invalid", "idempotencyKey is required: pass the raw Idempotency-Key the submission was sent with");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
run = await runStore.getRunByIdem(idemKeyOf(rawKey, idemCallerOf(req, ctx), "run"));
|
|
239
|
+
notFound = RUN_NOT_FOUND_MESSAGE;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
const taskId = match.params.taskId;
|
|
243
|
+
run = await runStore.getRun(taskId);
|
|
244
|
+
notFound = runNotFoundMessage(taskId);
|
|
245
|
+
}
|
|
225
246
|
if (!run) {
|
|
226
|
-
sendError(res, 404, "not_found.run",
|
|
247
|
+
sendError(res, 404, "not_found.run", notFound);
|
|
227
248
|
return;
|
|
228
249
|
}
|
|
250
|
+
const taskId = run.taskId;
|
|
229
251
|
if (!runOwnerOk(req, res, run.owner))
|
|
230
252
|
return;
|
|
231
253
|
if (!runSessionAcceptOk(req, res, run, match.decl.id === "run-events" ? "run.events" : "run.poll"))
|
|
@@ -234,62 +256,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
234
256
|
await streamRunEvents(req, res, deps, runStore, taskId, deps.config.runStaleSec * 1000);
|
|
235
257
|
}
|
|
236
258
|
else {
|
|
237
|
-
|
|
238
|
-
const infraRates = deps.config.infraCostRates;
|
|
239
|
-
const needCost = Boolean(run.result?.stats && !stale && infraRates && hasInfraPricing(infraRates));
|
|
240
|
-
const needSuggestions = !stale && run.status === "completed";
|
|
241
|
-
const resourceWindow = run.status === "running" && !stale ? readResourceWindow(run.taskId) : undefined;
|
|
242
|
-
let events;
|
|
243
|
-
let eventsUnavailable = false;
|
|
244
|
-
if (needCost || needSuggestions || resourceWindow !== undefined) {
|
|
245
|
-
if (runStore.getEvents) {
|
|
246
|
-
events = await runStore.getEvents(taskId, 0).catch(() => {
|
|
247
|
-
eventsUnavailable = true;
|
|
248
|
-
return [];
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
eventsUnavailable = true;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
let supervisorCost;
|
|
256
|
-
if (needCost && events && run.result?.stats) {
|
|
257
|
-
const durMs = new Date(run.updatedAt).getTime() - new Date(run.createdAt).getTime();
|
|
258
|
-
const usage = infraUsageFromEvents(events, Number.isFinite(durMs) ? durMs : 0);
|
|
259
|
-
const st = run.result.stats;
|
|
260
|
-
supervisorCost = composeSupervisorCost(st.costBreakdown ?? null, st.costMicroUsd ?? 0, infraCost(usage, infraRates));
|
|
261
|
-
}
|
|
262
|
-
let suggestions;
|
|
263
|
-
if (needSuggestions && events) {
|
|
264
|
-
const sug = [...events].reverse().find((e) => e.type === "suggestions");
|
|
265
|
-
const arr = sug?.data?.suggestions;
|
|
266
|
-
if (Array.isArray(arr))
|
|
267
|
-
suggestions = arr.map((s) => String(s));
|
|
268
|
-
}
|
|
269
|
-
const lastActivityAt = run.status === "running" && !stale ? readTurnActivityMs(run.taskId) : undefined;
|
|
270
|
-
let crossSliceUsage;
|
|
271
|
-
if (resourceWindow !== undefined) {
|
|
272
|
-
if (eventsUnavailable || events === undefined)
|
|
273
|
-
recordFailOpen("server.runs.cross-slice-usage-unavailable");
|
|
274
|
-
else
|
|
275
|
-
crossSliceUsage = buildCrossSliceUsage(resourceWindow, events);
|
|
276
|
-
}
|
|
277
|
-
sendJson(res, 200, {
|
|
278
|
-
taskId: run.taskId,
|
|
279
|
-
sessionId: run.sessionId,
|
|
280
|
-
status: stale ? "failed" : run.status,
|
|
281
|
-
...(lastActivityAt !== undefined ? { msSinceLastActivity: Math.max(0, Date.now() - lastActivityAt) } : {}),
|
|
282
|
-
...(crossSliceUsage !== undefined ? { crossSliceUsage } : {}),
|
|
283
|
-
result: isTerminalRunStatus(run.status) ? memoRedactTerminalResult("run.result", run.result ?? undefined) : redactTerminalResult(run.result ?? undefined),
|
|
284
|
-
supervisorCost,
|
|
285
|
-
suggestions,
|
|
286
|
-
errorCode: stale ? RUN_HOLDER_DEATH_CODES.stalled : (persistedPlaneOf(run.result).errorCode ?? run.errorCode ?? undefined),
|
|
287
|
-
error: stale ? "run stalled (instance lost?)" : isTerminalRunStatus(run.status) ? memoRedactedRead("run.error", run.error ?? undefined, redactErrorMessage) : redactErrorMessage(run.error ?? undefined),
|
|
288
|
-
jobId: run.jobId ?? undefined,
|
|
289
|
-
source: run.source ?? undefined,
|
|
290
|
-
heldBy: run.instanceId ?? null,
|
|
291
|
-
cancelRequested: run.cancelRequested,
|
|
292
|
-
});
|
|
259
|
+
sendJson(res, 200, await readRunFace(deps, runStore, run));
|
|
293
260
|
}
|
|
294
261
|
return;
|
|
295
262
|
}
|
|
@@ -458,8 +425,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
458
425
|
const text = body.text;
|
|
459
426
|
const priority = body.priority;
|
|
460
427
|
const steerIdemRaw = headerStr(req.headers["idempotency-key"]);
|
|
461
|
-
const
|
|
462
|
-
const steerInputId = steerScoped ? `idem-${createHash("sha256").update(steerScoped).digest("hex").slice(0, 32)}` : undefined;
|
|
428
|
+
const steerInputId = steerIdemRaw ? `idem-${idemKeyOf(steerIdemRaw, idemCallerOf(req, ctx), "run").slice(0, 32)}` : undefined;
|
|
463
429
|
const messageId = steerInputId ?? uuidv7();
|
|
464
430
|
const trusted = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
465
431
|
const steerInput = () => ({
|
|
@@ -774,8 +740,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
774
740
|
text = interruptBody.text;
|
|
775
741
|
}
|
|
776
742
|
const interruptIdemRaw = headerStr(req.headers["idempotency-key"]);
|
|
777
|
-
const
|
|
778
|
-
const interruptInputId = interruptScoped ? `idem-${createHash("sha256").update(interruptScoped).digest("hex").slice(0, 32)}` : uuidv7();
|
|
743
|
+
const interruptInputId = interruptIdemRaw ? `idem-${idemKeyOf(interruptIdemRaw, idemCallerOf(req, ctx), "run").slice(0, 32)}` : uuidv7();
|
|
779
744
|
const interruptMessageId = interruptInputId;
|
|
780
745
|
const trusted = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
781
746
|
if (!interruptBare) {
|
|
@@ -1623,7 +1588,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1623
1588
|
}
|
|
1624
1589
|
export async function createDurableRun(req, res, ctx, prepared, idemKey) {
|
|
1625
1590
|
const { deps } = ctx;
|
|
1626
|
-
const { idemCache
|
|
1591
|
+
const { idemCache } = ctx.registry;
|
|
1627
1592
|
const { leaseDenied, usageWindowDenied } = ctx.helpers;
|
|
1628
1593
|
const source = ctx.req.source;
|
|
1629
1594
|
const runs = facts.durableRuns(deps);
|
|
@@ -1632,36 +1597,41 @@ export async function createDurableRun(req, res, ctx, prepared, idemKey) {
|
|
|
1632
1597
|
return null;
|
|
1633
1598
|
}
|
|
1634
1599
|
const runStore = runs.runStore;
|
|
1635
|
-
const
|
|
1636
|
-
if (
|
|
1600
|
+
const clientTaskId = clientTaskIdOf(prepared.body);
|
|
1601
|
+
if (clientTaskId === false) {
|
|
1637
1602
|
sendError(res, 400, "request.id_invalid", "body.taskId must be a uuidv7 string (caller-minted idempotency key)");
|
|
1638
1603
|
return null;
|
|
1639
1604
|
}
|
|
1640
|
-
const
|
|
1605
|
+
const submitter = gatedPrincipal(req, deps.config);
|
|
1606
|
+
const caller = { principal: submitter, ...(clientTaskId !== undefined ? { clientTaskId } : {}) };
|
|
1641
1607
|
if (clientTaskId) {
|
|
1642
1608
|
const existing = await runStore.getRun(clientTaskId);
|
|
1643
|
-
if (existing)
|
|
1644
|
-
|
|
1645
|
-
if (existing.owner !== null && existing.owner !== verified) {
|
|
1646
|
-
sendError(res, 409, "conflict.run_exists", "taskId already exists");
|
|
1647
|
-
return null;
|
|
1648
|
-
}
|
|
1649
|
-
return { status: 202, body: { taskId: existing.taskId, sessionId: existing.sessionId, status: existing.status } };
|
|
1650
|
-
}
|
|
1609
|
+
if (existing)
|
|
1610
|
+
return await replayOfExistingRun(existing, caller, "durable", deps, runStore);
|
|
1651
1611
|
}
|
|
1652
1612
|
if ((await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
|
|
1653
1613
|
return null;
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1614
|
+
return await idemCache.run(idemKey !== undefined ? inflightKeyOf(idemKey, "durable", clientTaskId) : undefined, async () => {
|
|
1615
|
+
const out = await mintAndDriveDurableRun({ deps, registry: ctx.registry, helpers: ctx.helpers, local: ctx.local, legs: ctx.legs }, runStore, {
|
|
1616
|
+
prepared,
|
|
1617
|
+
...(clientTaskId !== undefined ? { clientTaskId } : {}),
|
|
1618
|
+
...(idemKey !== undefined ? { idemKey } : {}),
|
|
1619
|
+
fleetScope: submitter ?? "default",
|
|
1620
|
+
elicitOwner: submitter ?? null,
|
|
1621
|
+
source,
|
|
1622
|
+
});
|
|
1623
|
+
return out.existing ? await replayOfExistingRun(out.existing, caller, "durable", deps, runStore) : out;
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
function clientTaskIdOf(body) {
|
|
1627
|
+
const raw = typeof body === "object" && body !== null ? body.taskId : undefined;
|
|
1628
|
+
if (raw === undefined)
|
|
1629
|
+
return undefined;
|
|
1630
|
+
return typeof raw === "string" && isUuidV7(raw) ? raw : false;
|
|
1662
1631
|
}
|
|
1663
1632
|
export const RUNS_ROUTES = [
|
|
1664
1633
|
{ id: "runs-submit", path: "/v1/runs", billable: "submit", methods: ["POST"] },
|
|
1634
|
+
{ id: "runs-by-idem", path: "/v1/runs", methods: ["GET"] },
|
|
1665
1635
|
{ id: "run-steer", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/steer$/, label: "/v1/runs/:id/steer", methods: ["POST"] },
|
|
1666
1636
|
{ id: "run-interrupt", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/interrupt$/, label: "/v1/runs/:id/interrupt", methods: ["POST"] },
|
|
1667
1637
|
{ id: "run-detach", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/detach$/, label: "/v1/runs/:id/detach", methods: ["POST"] },
|
|
@@ -11,6 +11,7 @@ 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
13
|
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
14
|
+
import { type SessionRowPatch } from "../../session-row.js";
|
|
14
15
|
export type PendingStaging = {
|
|
15
16
|
handle: StagingHandle;
|
|
16
17
|
realSessionId: string;
|
|
@@ -25,6 +26,8 @@ export type PendingStaging = {
|
|
|
25
26
|
entryId: string;
|
|
26
27
|
owner: string | null;
|
|
27
28
|
}>;
|
|
29
|
+
/** 会话行的两位可写位(Phase A 体里带了哪几键就重放哪几键;老客户端不带 ⇒ `{}`,目的端两位不动)。提交后经会话店唯一写口落。 */
|
|
30
|
+
row: SessionRowPatch;
|
|
28
31
|
/** 交叉复查 A-3(HIGH):Phase A's DECLARED entry-id set — Phase B verifies the streamed NDJSON matches it
|
|
29
32
|
* (a different tree under the same staging would otherwise commit while the parked snapshots/anchors
|
|
30
33
|
* still reference the DECLARED one). */
|
|
@@ -15,6 +15,7 @@ const SYNC_IMPORT_BATCH = 500;
|
|
|
15
15
|
import { SHA256_HEX_RE } from "../../digest-form.js";
|
|
16
16
|
import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
|
|
17
17
|
import { readSessionRulesRecords, describeSessionRulesRecordProblem, legacyToolNameProblemOf } from "../../session-policy-wire.js";
|
|
18
|
+
import { readSessionPinned, readSessionTitle } from "../../session-row.js";
|
|
18
19
|
export function createSessionSyncLocal() {
|
|
19
20
|
const pendingStagings = new Map();
|
|
20
21
|
const importLeases = new Map();
|
|
@@ -74,6 +75,14 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
74
75
|
}
|
|
75
76
|
return true;
|
|
76
77
|
};
|
|
78
|
+
const replaySessionRow = async (row, importedOwner) => {
|
|
79
|
+
if (Object.keys(row).length === 0)
|
|
80
|
+
return;
|
|
81
|
+
const face = await sessionStorage.sessionFaceOf(sessionId);
|
|
82
|
+
if (face === undefined || face.owner !== importedOwner)
|
|
83
|
+
return;
|
|
84
|
+
await sessionStorage.patchSessionMeta(sessionId, row, face);
|
|
85
|
+
};
|
|
77
86
|
if (match.decl.id === "session-sync-manifest") {
|
|
78
87
|
if (!pullGateOk())
|
|
79
88
|
return;
|
|
@@ -247,6 +256,23 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
247
256
|
sendError(res, 400, "request.field_invalid", "resolution must be 'overwrite-dst'");
|
|
248
257
|
return;
|
|
249
258
|
}
|
|
259
|
+
let row = {};
|
|
260
|
+
if ("title" in body) {
|
|
261
|
+
const read = readSessionTitle(body.title);
|
|
262
|
+
if ("problem" in read) {
|
|
263
|
+
sendError(res, 400, read.problem.code, read.problem.message);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
row = { ...row, title: read.title };
|
|
267
|
+
}
|
|
268
|
+
if ("pinned" in body) {
|
|
269
|
+
const read = readSessionPinned(body.pinned);
|
|
270
|
+
if ("problem" in read) {
|
|
271
|
+
sendError(res, 400, read.problem.code, read.problem.message);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
row = { ...row, pinned: read.pinned };
|
|
275
|
+
}
|
|
250
276
|
const resolution = body.resolution;
|
|
251
277
|
const srcLogDigest = typeof body.logDigest === "string" && body.logDigest.length > 0 ? body.logDigest : undefined;
|
|
252
278
|
const entryIds = body.entryIds;
|
|
@@ -305,6 +331,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
305
331
|
if (deps.resumeAnchorStore)
|
|
306
332
|
for (const a of anchors)
|
|
307
333
|
await deps.resumeAnchorStore.put(sessionId, a.eventId, a.entryId, importingP);
|
|
334
|
+
await replaySessionRow(row, owner);
|
|
308
335
|
sendJson(res, 200, {
|
|
309
336
|
relation: "identical",
|
|
310
337
|
basis: payloadVerified ? "entry-ids+digest" : "entry-ids",
|
|
@@ -368,7 +395,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
368
395
|
importLeases.set(sessionId, { at: Date.now(), stagingId });
|
|
369
396
|
pendingStagings.set(stagingId, {
|
|
370
397
|
handle, realSessionId: sessionId, owner: importingPrincipal, ...(resolution ? { resolution } : {}), operatorOk,
|
|
371
|
-
fileHistory, policy, anchors, declaredEntryIds: entryIds, openedBy: scope.gateOwner, openedFleetWide: scope.fleetWide,
|
|
398
|
+
fileHistory, policy, anchors, row, declaredEntryIds: entryIds, openedBy: scope.gateOwner, openedFleetWide: scope.fleetWide,
|
|
372
399
|
});
|
|
373
400
|
sendJson(res, 200, { stagingId, relation: relA.relation });
|
|
374
401
|
return;
|
|
@@ -494,6 +521,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
494
521
|
if (anchorStore)
|
|
495
522
|
for (const a of pending.anchors)
|
|
496
523
|
await anchorStore.put(sessionId, a.eventId, a.entryId, pending.owner);
|
|
524
|
+
await replaySessionRow(pending.row, pending.owner);
|
|
497
525
|
sessionStorage.evict?.(sessionId);
|
|
498
526
|
cleanupStaging(stagingId);
|
|
499
527
|
sendJson(res, 200, { relation: committed.relation });
|
|
@@ -540,6 +568,7 @@ function sendSessionSyncUnavailable(res, why) {
|
|
|
540
568
|
switch (why) {
|
|
541
569
|
case "store_backend_absent":
|
|
542
570
|
case "session_owner_face_absent":
|
|
571
|
+
case "session_row_faces_absent":
|
|
543
572
|
sendError(res, 501, "capability.session_store_required", "session sync requires a durable session store");
|
|
544
573
|
return;
|
|
545
574
|
case "session_export_absent":
|
|
@@ -5,11 +5,22 @@
|
|
|
5
5
|
* owner-scope 与 trace 列表同一权威模型(system/traceToken=fleet-wide,principal=钉住本人)。本域零可变状态。
|
|
6
6
|
*/
|
|
7
7
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
|
+
import { type SessionListItem } from "../../security.js";
|
|
9
|
+
import { sessionTempoOfRunStatus } from "../../plugins/store-contracts.js";
|
|
8
10
|
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
9
11
|
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
|
|
10
12
|
* 的语义原样 =「本域已应答」;走到函数尾才是「没匹配上」,置 `miss.fell` 交回装配器继续下一个域。
|
|
11
13
|
* 跨域的东西(deps / 助手 / 单域状态)一律在首行解构出来,于是下面每一行都逐字不变。 */
|
|
12
14
|
export declare function handleSessionsList(req: IncomingMessage, res: ServerResponse, match: RouteMatch<SessionsListRouteId>, ctx: RouteCtx): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* 会话列表行的 **wire 投影** —— `GET /v1/sessions` 的每一行与 `PATCH /v1/sessions/:id` 的 200 回执是**同一只**(店行 → wire 行只有这一处)。
|
|
17
|
+
* · 预览:店行读的是 run 行预览列的事实位(`""` = 有记录、剥完没有用户正文;NULL = 没记录);本面只说「有没有可显示的预览」⇒ 两者都是 `null`。
|
|
18
|
+
* · `tempo`:见上方 [ref] 注(由 `lastStatus` 经唯一一张表派生;omit-when-absent)。
|
|
19
|
+
* · 其余键(含 `title` / `pinned`)零投影透传 —— 店行是唯一铸点。
|
|
20
|
+
*/
|
|
21
|
+
export declare function projectSessionListRow(r: SessionListItem): SessionListItem & {
|
|
22
|
+
tempo?: ReturnType<typeof sessionTempoOfRunStatus>;
|
|
23
|
+
};
|
|
13
24
|
export declare const SESSIONS_LIST_ROUTES: readonly [{
|
|
14
25
|
readonly id: "sessions-list";
|
|
15
26
|
readonly path: "/v1/sessions";
|
|
@@ -125,12 +125,12 @@ async function handleSessionList(res, lister, query, forceOwner, includeUnowned
|
|
|
125
125
|
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
126
126
|
const last = page[page.length - 1];
|
|
127
127
|
const nextCursor = hasMore && last ? encodeSessionCursor({ lastActivityAt: last.lastActivityAt, sessionId: last.sessionId }, f) : undefined;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
128
|
+
sendJson(res, 200, { sessions: page.map(projectSessionListRow), ...(nextCursor ? { nextCursor } : {}) });
|
|
129
|
+
}
|
|
130
|
+
export function projectSessionListRow(r) {
|
|
131
|
+
const tempo = sessionTempoOfRunStatus(r.lastStatus);
|
|
132
|
+
const row = r.objectivePreview === "" ? { ...r, objectivePreview: null } : r;
|
|
133
|
+
return tempo === undefined ? row : { ...row, tempo };
|
|
134
134
|
}
|
|
135
135
|
export const SESSIONS_LIST_ROUTES = [
|
|
136
136
|
{ id: "sessions-list", path: "/v1/sessions", methods: ["GET"] },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* [ref] A9 域模块 —— 单会话面。
|
|
3
3
|
*
|
|
4
|
-
* 路由:`/v1/sessions/:id` 的 init / settings / mcp / head / events(SSE)/ fork / DELETE / policy / workspace,
|
|
4
|
+
* 路由:`/v1/sessions/:id` 的 init / settings / mcp / head / events(SSE)/ fork / PATCH(会话行可写位)/ DELETE / policy / workspace,
|
|
5
5
|
* 外加同段的 `deps.sessionAudit` 只读审计面。
|
|
6
6
|
*
|
|
7
7
|
* 单域独占状态(lens1 §C2 的 12 条里的 5 条)全部收进 `SessionsLocal`,每个 server 实例一份:
|
|
@@ -115,6 +115,11 @@ export declare const SESSIONS_ROUTES: readonly [{
|
|
|
115
115
|
readonly pattern: RegExp;
|
|
116
116
|
readonly label: "/v1/sessions/:id";
|
|
117
117
|
readonly methods: readonly ["GET"];
|
|
118
|
+
}, {
|
|
119
|
+
readonly id: "session-patch";
|
|
120
|
+
readonly pattern: RegExp;
|
|
121
|
+
readonly label: "/v1/sessions/:id";
|
|
122
|
+
readonly methods: readonly ["PATCH"];
|
|
118
123
|
}, {
|
|
119
124
|
readonly id: "session-delete";
|
|
120
125
|
readonly pattern: RegExp;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SessionError, materializeA2aTools, materializeMcpTools, sanitizePathComponent } from "@sema-agent/core";
|
|
2
2
|
import { a2aForScenario, mcpForScenario } from "../../web-admin-client/facade.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isUuidShape, legacyUnownedSessionOk } from "../../security.js";
|
|
4
4
|
import { windowMessages, truncateMessageBlobs, redactSessionViewForRead } from "../../audit.js";
|
|
5
5
|
import { redactSecrets } from "../../trace/redact.js";
|
|
6
6
|
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
@@ -13,6 +13,8 @@ import { gatedPrincipal, explicitOperatorOk, runOwnerReadable, sendPrincipalRefu
|
|
|
13
13
|
import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
|
|
14
14
|
import * as facts from "../../capability-facts.js";
|
|
15
15
|
import { rosterAgentId } from "./agents-roster.js";
|
|
16
|
+
import { projectSessionListRow } from "./sessions-list.js";
|
|
17
|
+
import { readSessionRowPatch } from "../../session-row.js";
|
|
16
18
|
const MCP_STATUS_TTL_MS = 5_000;
|
|
17
19
|
const A2A_STATUS_TTL_MS = 5_000;
|
|
18
20
|
const A2A_MATERIALIZE_TIMEOUT_MS = 10_000;
|
|
@@ -69,7 +71,7 @@ async function readLastLegMcp(deps, sessionId, principal) {
|
|
|
69
71
|
export async function handleSessions(req, res, match, ctx) {
|
|
70
72
|
const { deps } = ctx;
|
|
71
73
|
const { readJson, safeDecode, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite } = ctx.helpers;
|
|
72
|
-
const readOwnedSessionView = async (sessionAudit, sessionId) => {
|
|
74
|
+
const readOwnedSessionView = async (sessionAudit, sessionId, readOpts) => {
|
|
73
75
|
const ownerOf = deps.sessionStorage?.ownerOf?.bind(deps.sessionStorage);
|
|
74
76
|
if (ownerOf !== undefined) {
|
|
75
77
|
const owner = await ownerOf(sessionId);
|
|
@@ -82,7 +84,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
82
84
|
}
|
|
83
85
|
let audit;
|
|
84
86
|
try {
|
|
85
|
-
audit = await sessionAudit(sessionId);
|
|
87
|
+
audit = await sessionAudit(sessionId, readOpts);
|
|
86
88
|
}
|
|
87
89
|
catch (e) {
|
|
88
90
|
if (ownerOf !== undefined)
|
|
@@ -596,7 +598,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
596
598
|
sendError(res, 501, "capability.session_store_required", "the session audit view requires a durable session store (DB_BACKEND=mysql|pg|local)");
|
|
597
599
|
return;
|
|
598
600
|
}
|
|
599
|
-
const owned = await readOwnedSessionView(auditFact.sessionAudit, match.params.sessionId);
|
|
601
|
+
const owned = await readOwnedSessionView(auditFact.sessionAudit, match.params.sessionId, { reader: gatedPrincipal(req, deps.config) ?? null });
|
|
600
602
|
if (owned === undefined)
|
|
601
603
|
return;
|
|
602
604
|
if (!Array.isArray(owned.messages)) {
|
|
@@ -651,6 +653,34 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
651
653
|
return;
|
|
652
654
|
}
|
|
653
655
|
}
|
|
656
|
+
if (match.decl.id === "session-patch") {
|
|
657
|
+
const rowFact = facts.sessionPatch(deps);
|
|
658
|
+
if (!rowFact.available) {
|
|
659
|
+
sendError(res, 501, "capability.session_store_required", "renaming or pinning a session requires a durable session store (DB_BACKEND=mysql|pg|local)");
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
const store = rowFact.sessionStorage;
|
|
663
|
+
const sessionId = match.params.sessionId;
|
|
664
|
+
const face = await store.sessionFaceOf(sessionId);
|
|
665
|
+
if (face === undefined) {
|
|
666
|
+
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (!runOwnerOk(req, res, face.owner, "session"))
|
|
670
|
+
return;
|
|
671
|
+
const read = readSessionRowPatch(await readJson(req));
|
|
672
|
+
if ("problem" in read) {
|
|
673
|
+
sendError(res, 400, read.problem.code, read.problem.message);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const row = await store.patchSessionMeta(sessionId, read.patch, face);
|
|
677
|
+
if (row === undefined) {
|
|
678
|
+
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
sendJson(res, 200, projectSessionListRow(row));
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
654
684
|
{
|
|
655
685
|
if (match.decl.id === "session-fork") {
|
|
656
686
|
const forkFact = facts.sessionFork(deps);
|
|
@@ -666,8 +696,8 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
666
696
|
return;
|
|
667
697
|
}
|
|
668
698
|
const sourceId = safeDecode(match.params.sessionId);
|
|
669
|
-
if (sourceId === null || !
|
|
670
|
-
sendError(res, 400, "request.id_invalid", "invalid session id (must be a
|
|
699
|
+
if (sourceId === null || !isUuidShape(sourceId)) {
|
|
700
|
+
sendError(res, 400, "request.id_invalid", "invalid session id (must be a canonical uuid)");
|
|
671
701
|
return;
|
|
672
702
|
}
|
|
673
703
|
const owner = await ownerOf(sourceId);
|
|
@@ -704,8 +734,8 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
704
734
|
return;
|
|
705
735
|
}
|
|
706
736
|
const sessionId = safeDecode(match.params.sessionId);
|
|
707
|
-
if (sessionId === null || !
|
|
708
|
-
sendError(res, 400, "request.id_invalid", "invalid session id (must be a
|
|
737
|
+
if (sessionId === null || !isUuidShape(sessionId)) {
|
|
738
|
+
sendError(res, 400, "request.id_invalid", "invalid session id (must be a canonical uuid)");
|
|
709
739
|
return;
|
|
710
740
|
}
|
|
711
741
|
const owner = await ownerOf(sessionId);
|
|
@@ -750,8 +780,8 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
750
780
|
return;
|
|
751
781
|
}
|
|
752
782
|
const sessionId = safeDecode(match.params.sessionId);
|
|
753
|
-
if (sessionId === null || !
|
|
754
|
-
sendError(res, 400, "request.id_invalid", "invalid session id (must be a
|
|
783
|
+
if (sessionId === null || !isUuidShape(sessionId)) {
|
|
784
|
+
sendError(res, 400, "request.id_invalid", "invalid session id (must be a canonical uuid)");
|
|
755
785
|
return;
|
|
756
786
|
}
|
|
757
787
|
const owner = await ownerOf(sessionId);
|
|
@@ -849,6 +879,7 @@ export const SESSIONS_ROUTES = [
|
|
|
849
879
|
{ id: "session-memory-status", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/memory-status$/, label: "/v1/sessions/:id/memory-status", methods: ["GET"] },
|
|
850
880
|
{ id: "session-workspace", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/workspace(?:\/(?<key>[^/]+)(?:\/(?<sub>tree|file|archive))?)?$/, label: "/v1/sessions/:id/workspace", methods: ["GET"] },
|
|
851
881
|
{ id: "session-detail", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)$/, label: "/v1/sessions/:id", methods: ["GET"] },
|
|
882
|
+
{ id: "session-patch", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)$/, label: "/v1/sessions/:id", methods: ["PATCH"] },
|
|
852
883
|
{ id: "session-delete", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)$/, label: "/v1/sessions/:id", methods: ["DELETE"] },
|
|
853
884
|
];
|
|
854
885
|
//# sourceMappingURL=sessions.js.map
|