@sema-agent/server 7.144.0 → 7.146.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/MIGRATION.md +621 -0
- package/README.md +3 -0
- package/README.zh-CN.md +1 -0
- package/USAGE.md +233 -54
- package/deploy/facts/CHANGES.json +25 -0
- package/deploy/facts/README.md +57 -0
- package/deploy/facts/lanes.json +54 -0
- package/deploy/facts/lanes.schema.json +57 -0
- package/deploy/facts/sandbox-rbac.json +113 -0
- package/deploy/facts/sandbox-rbac.schema.json +74 -0
- package/deploy/facts/sealed-key.json +9 -0
- package/deploy/facts/sealed-key.schema.json +52 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +19 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +4 -0
- package/deploy/sema-up/chart/templates/server.yaml +4 -0
- package/deploy/sema-up/chart/values.yaml +7 -0
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +30 -14
- package/dist/adoption/import-limits.d.ts +6 -0
- package/dist/adoption/import-limits.js +7 -0
- package/dist/adoption/import.d.ts +62 -0
- package/dist/adoption/import.js +107 -0
- package/dist/adoption/wire.d.ts +52 -0
- package/dist/adoption/wire.js +9 -0
- package/dist/approval-ask-machine.d.ts +2 -2
- package/dist/approval-card.d.ts +3 -12
- package/dist/approval-card.js +0 -4
- package/dist/approval-gate-currency.d.ts +1 -1
- package/dist/approval-gate-currency.js +1 -1
- package/dist/approval-reconciler.d.ts +1 -1
- package/dist/audit.d.ts +2 -21
- package/dist/audit.js +0 -9
- package/dist/boot/deployment-mods.d.ts +11 -1
- package/dist/boot/deployment-mods.js +15 -2
- package/dist/boot/device-lane.js +2 -0
- package/dist/boot/engine-lease.d.ts +193 -21
- package/dist/boot/engine-lease.js +146 -20
- package/dist/boot/execution-env.d.ts +4 -0
- package/dist/boot/execution-env.js +12 -0
- package/dist/boot/exit-boundary.d.ts +50 -0
- package/dist/boot/exit-boundary.js +32 -0
- package/dist/boot/exit-last.d.ts +20 -4
- package/dist/boot/exit-last.js +21 -4
- package/dist/boot/governance-seams.js +1 -0
- package/dist/boot/memory-consolidation.d.ts +29 -17
- package/dist/boot/memory-consolidation.js +26 -15
- package/dist/boot/reapers.d.ts +12 -4
- package/dist/boot/reapers.js +39 -15
- package/dist/boot/resolve-spec.d.ts +2 -0
- package/dist/boot/resolve-spec.js +38 -15
- package/dist/boot/runner-deps.js +1 -1
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +6 -4
- package/dist/boot/shutdown.js +27 -9
- package/dist/boot/stage-01-config.js +1 -1
- package/dist/boot/stage-03-store-backend.d.ts +1 -0
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-05-execution-env.d.ts +3 -0
- package/dist/boot/stage-05-execution-env.js +3 -3
- package/dist/boot/stage-06-runners.d.ts +3 -0
- package/dist/boot/stage-06-runners.js +11 -10
- package/dist/boot/stage-07-capability-layer.d.ts +3 -0
- package/dist/boot/stage-07-capability-layer.js +7 -2
- package/dist/boot/stage-08-reapers.d.ts +3 -0
- package/dist/boot/stage-08-reapers.js +3 -3
- package/dist/boot/stage-09-leader.d.ts +4 -1
- package/dist/boot/stage-10-http-server.d.ts +4 -1
- package/dist/boot/stage-10-http-server.js +4 -3
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +35 -19
- package/dist/boot/workflow-orchestration.d.ts +1 -1
- package/dist/boot/workflow-orchestration.js +2 -1
- package/dist/capabilities/composite-members.d.ts +3 -1
- package/dist/capabilities/composite-members.js +3 -1
- package/dist/capabilities/scenarios.d.ts +2 -2
- package/dist/capability-facts.d.ts +18 -1
- package/dist/capability-facts.js +25 -7
- package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
- package/dist/config-catalog.js +13 -4
- package/dist/config-d-migration.d.ts +3 -2
- package/dist/config-provider.d.ts +2 -2
- package/dist/config-types.d.ts +43 -4
- package/dist/config.d.ts +6 -1
- package/dist/config.js +67 -11
- package/dist/deleted-session-ids.d.ts +19 -0
- package/dist/deleted-session-ids.js +26 -0
- package/dist/deploy-facts.d.ts +155 -0
- package/dist/deploy-facts.js +94 -0
- package/dist/device-ws-hub.d.ts +48 -3
- package/dist/device-ws-hub.js +48 -2
- package/dist/error-code-family.d.ts +35 -0
- package/dist/error-code-family.js +26 -0
- package/dist/fleet/drain-entry.d.ts +39 -3
- package/dist/fleet/drain-entry.js +6 -0
- package/dist/fleet/fleet-bus.d.ts +15 -4
- package/dist/fleet/fleet-bus.js +17 -3
- package/dist/hooks/branch-transcript.d.ts +1 -1
- package/dist/hooks/branch-transcript.js +1 -0
- package/dist/hooks/classic-stations.d.ts +25 -8
- package/dist/hooks/classic-stations.js +29 -3
- package/dist/hooks/hook-runner.d.ts +33 -16
- package/dist/hooks/hook-runner.js +166 -73
- package/dist/hooks/hook-source.d.ts +14 -2
- package/dist/hooks/hook-source.js +8 -0
- package/dist/http/active-run-conflict.d.ts +1 -1
- package/dist/http/active-run-conflict.js +1 -1
- package/dist/http/admission.js +13 -2
- package/dist/http/checkpoint-ctx.d.ts +60 -71
- package/dist/http/checkpoint-ctx.js +35 -40
- package/dist/http/dispatch.js +18 -2
- package/dist/http/durable-run-leg.d.ts +5 -0
- package/dist/http/durable-run-leg.js +8 -6
- package/dist/http/execution-context.d.ts +4 -0
- package/dist/http/execution-context.js +9 -0
- package/dist/http/health-self-report.d.ts +6 -2
- package/dist/http/health-self-report.js +3 -2
- package/dist/http/memory-compliance-failure-reply.d.ts +1 -1
- package/dist/http/memory-compliance-failure-reply.js +2 -15
- package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
- package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
- package/dist/http/principal-gate.d.ts +35 -12
- package/dist/http/principal-gate.js +16 -12
- package/dist/http/request-guards.d.ts +1 -1
- package/dist/http/request-guards.js +5 -5
- package/dist/http/resume-legs.d.ts +22 -0
- package/dist/http/resume-legs.js +31 -20
- package/dist/http/route-ctx.d.ts +26 -10
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/adoption.d.ts +11 -0
- package/dist/http/routes/adoption.js +80 -6
- package/dist/http/routes/agents-roster.js +1 -0
- package/dist/http/routes/approvals-assistant.d.ts +20 -1
- package/dist/http/routes/approvals-assistant.js +29 -7
- package/dist/http/routes/background.js +1 -0
- package/dist/http/routes/capabilities.js +4 -1
- package/dist/http/routes/diagnostics.d.ts +9 -14
- package/dist/http/routes/diagnostics.js +10 -20
- package/dist/http/routes/engine.d.ts +31 -0
- package/dist/http/routes/engine.js +81 -0
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-consolidation.js +3 -5
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/memory-origin.js +2 -4
- package/dist/http/routes/memory-policy.js +5 -1
- package/dist/http/routes/observability.js +3 -6
- package/dist/http/routes/rules.d.ts +1 -1
- package/dist/http/routes/rules.js +5 -2
- package/dist/http/routes/runs.js +7 -3
- package/dist/http/routes/session-sync.js +24 -25
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/sessions.js +4 -0
- package/dist/http/routes/tasks.js +35 -14
- package/dist/http/routes/trace-usage.js +1 -1
- package/dist/http/run-concurrency-cap.d.ts +86 -0
- package/dist/http/run-concurrency-cap.js +54 -0
- package/dist/http/run-meta.d.ts +14 -7
- package/dist/http/run-meta.js +5 -2
- package/dist/http/server.d.ts +27 -16
- package/dist/http/server.js +21 -3
- package/dist/http/stored-body-replay.d.ts +9 -6
- package/dist/http/stored-body-replay.js +19 -5
- package/dist/http/submit-ctx.d.ts +12 -0
- package/dist/http/submit-ctx.js +23 -0
- package/dist/http/wire-types.d.ts +8 -3
- package/dist/json-body-limit.d.ts +8 -0
- package/dist/json-body-limit.js +2 -0
- package/dist/leader/diffout.d.ts +1 -1
- package/dist/local-data-root.d.ts +2 -2
- package/dist/memory-operator-faces.d.ts +9 -4
- package/dist/memory-scope-stores.d.ts +9 -7
- package/dist/memory-scope-stores.js +3 -14
- package/dist/memory-scope.d.ts +36 -0
- package/dist/memory-scope.js +32 -1
- package/dist/net-address.d.ts +53 -0
- package/dist/net-address.js +111 -0
- package/dist/observability/fail-open.d.ts +28 -12
- package/dist/observability/fail-open.js +28 -12
- package/dist/orchestration/workflow-notify-journal.js +2 -3
- package/dist/org-delivery-gate.js +1 -1
- package/dist/peer-directory.d.ts +6 -12
- package/dist/peer-directory.js +5 -1
- package/dist/plugins/approval-ask-store-file.d.ts +16 -2
- package/dist/plugins/approval-ask-store-file.js +21 -1
- package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
- package/dist/plugins/approval-ask-store-memory.js +11 -8
- package/dist/plugins/caching-session-store.d.ts +6 -1
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/checkpoint-store-face.d.ts +426 -0
- package/dist/plugins/checkpoint-store-face.js +152 -0
- package/dist/plugins/file-run-store.d.ts +13 -7
- package/dist/plugins/file-run-store.js +12 -13
- package/dist/plugins/fork-routing-session-store.d.ts +6 -2
- package/dist/plugins/fork-routing-session-store.js +2 -2
- package/dist/plugins/imported-layer-root-retirement.d.ts +10 -8
- package/dist/plugins/local-checkpoint-store.d.ts +16 -5
- package/dist/plugins/local-checkpoint-store.js +25 -6
- package/dist/plugins/local-session-store.d.ts +80 -21
- package/dist/plugins/local-session-store.js +79 -22
- package/dist/plugins/memory-engine-pg.d.ts +29 -6
- package/dist/plugins/memory-engine-pg.js +96 -214
- package/dist/plugins/memory-engine-sql-apply.d.ts +121 -0
- package/dist/plugins/memory-engine-sql-apply.js +358 -0
- package/dist/plugins/memory-engine-tidb.d.ts +35 -9
- package/dist/plugins/memory-engine-tidb.js +97 -227
- package/dist/plugins/memory-key-guards.d.ts +1 -1
- package/dist/plugins/memory-origin-law.d.ts +3 -3
- package/dist/plugins/memory-run-store.d.ts +2 -0
- package/dist/plugins/memory-run-store.js +6 -0
- package/dist/plugins/one-time-migrations.d.ts +32 -56
- package/dist/plugins/one-time-migrations.js +27 -119
- package/dist/plugins/permission-rule-store-file.d.ts +17 -3
- package/dist/plugins/permission-rule-store-file.js +8 -1
- package/dist/plugins/pg-pool.js +1 -1
- package/dist/plugins/pg-session-storage.d.ts +17 -4
- package/dist/plugins/pg-session-storage.js +8 -4
- package/dist/plugins/run-store-sql.d.ts +4 -0
- package/dist/plugins/run-store-sql.js +5 -2
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/session-fork.d.ts +31 -9
- package/dist/plugins/session-fork.js +3 -2
- package/dist/plugins/session-placement.d.ts +4 -2
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/sql-json-column.d.ts +5 -1
- package/dist/plugins/store-backend.d.ts +11 -8
- package/dist/plugins/store-backend.js +8 -7
- package/dist/plugins/store-contracts.d.ts +1 -11
- package/dist/plugins/store-contracts.js +0 -9
- package/dist/plugins/store-sweep.d.ts +63 -0
- package/dist/plugins/store-sweep.js +48 -0
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/plugins/tidb-session-store.d.ts +16 -3
- package/dist/plugins/tidb-session-store.js +8 -4
- package/dist/prompt-screen.d.ts +18 -0
- package/dist/prompt-screen.js +5 -0
- package/dist/rules-consent.d.ts +56 -51
- package/dist/rules-consent.js +86 -14
- package/dist/run-acceptance.d.ts +40 -10
- package/dist/run-acceptance.js +38 -5
- package/dist/run-cancel-context.d.ts +4 -1
- package/dist/run-cancel-context.js +2 -1
- package/dist/runs.js +2 -2
- package/dist/sandbox-egress-policy.js +6 -11
- package/dist/sandbox-rbac-rules.d.ts +88 -0
- package/dist/sandbox-rbac-rules.js +80 -0
- package/dist/sealed-key-layout.d.ts +17 -0
- package/dist/sealed-key-layout.js +4 -0
- package/dist/sealed-key.js +5 -5
- package/dist/security.d.ts +48 -9
- package/dist/security.js +9 -2
- package/dist/session-agent.d.ts +39 -0
- package/dist/session-agent.js +16 -0
- package/dist/session-error-wire.d.ts +4 -4
- package/dist/session-notify-door.d.ts +5 -3
- package/dist/session-resident-background.d.ts +57 -0
- package/dist/session-resident-background.js +20 -0
- package/dist/session-sync-kernel.d.ts +53 -14
- package/dist/session-sync-kernel.js +70 -1
- package/dist/session-sync.d.ts +5 -3
- package/dist/session-workspace.d.ts +1 -0
- package/dist/session-workspace.js +1 -0
- package/dist/sighup-idle.d.ts +9 -7
- package/dist/sighup-idle.js +4 -5
- package/dist/spec-fields.d.ts +16 -0
- package/dist/spec-fields.js +7 -0
- package/dist/stored-request-env-credentials-m38.d.ts +3 -0
- package/dist/stored-request-env-credentials-m38.js +8 -0
- package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
- package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +33 -429
- package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +70 -209
- package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
- package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
- package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
- package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
- package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
- package/dist/task-settings.d.ts +59 -4
- package/dist/task-settings.js +50 -4
- package/dist/tool-approval.d.ts +2 -2
- package/dist/trace/core-keyset-guard.d.ts +8 -7
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.d.ts +6 -0
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/model-segment.d.ts +3 -2
- package/dist/trace/model-segment.js +16 -8
- package/dist/trace/project.js +9 -0
- package/dist/trace/redact.d.ts +79 -4
- package/dist/trace/redact.js +523 -123
- package/dist/trace/self-minted-wire.d.ts +391 -1
- package/dist/trace/self-minted-wire.js +145 -0
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/usage-analytics.d.ts +4 -0
- package/package.json +12 -4
- package/scripts/drain-check-local.mjs +8 -29
- package/scripts/drain-check.mysql.sql +3 -24
- package/scripts/drain-check.pg.sql +3 -24
- package/skills/update-config.md +1 -1
- package/wire-changes/server@7.145.0.json +146 -0
- package/wire-changes/server@7.146.0.json +3221 -0
- package/dist/approval-batch-census.d.ts +0 -27
- package/dist/approval-batch-census.js +0 -27
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { ControlPlaneCorruptError } from "@sema-agent/core";
|
|
3
2
|
import { sendJson, sendError } from "../send.js";
|
|
4
3
|
import { sentMemoryStoreRefusal } from "../memory-store-refusal-reply.js";
|
|
4
|
+
import { sentControlPlaneCorrupt } from "../memory-control-plane-corrupt-reply.js";
|
|
5
5
|
import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
|
|
6
6
|
import { readLayerSeat, sentMemoryLayerRefusal } from "../memory-layer-reply.js";
|
|
7
7
|
import { gatedPrincipal, explicitOperatorOk, sendOperatorPrincipalsRequired, sendPrincipalRefusal } from "../principal-gate.js";
|
|
@@ -59,10 +59,8 @@ function sentClassifiedFailure(res, ctx, face, err) {
|
|
|
59
59
|
}
|
|
60
60
|
if (face !== "clearances" && sentMemoryStoreRefusal(res, err, ctx.deps.logger))
|
|
61
61
|
return true;
|
|
62
|
-
if (err
|
|
63
|
-
sendError(res, 500, "internal.memory_control_plane_corrupt", err.message);
|
|
62
|
+
if (sentControlPlaneCorrupt(res, err, { kind: "operator" }))
|
|
64
63
|
return true;
|
|
65
|
-
}
|
|
66
64
|
ctx.deps.logger?.warn?.(`memory_origin_${face}_failed`, { err: String(err) });
|
|
67
65
|
return false;
|
|
68
66
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatUserScope } from "@sema-agent/core";
|
|
1
|
+
import { formatUserScope, isReplicableScope } from "@sema-agent/core";
|
|
2
2
|
import { parseMemorySyncRequest } from "../../memory-sync.js";
|
|
3
3
|
import { memorySyncIdentityRule } from "../../memory-sync-identity.js";
|
|
4
4
|
import * as facts from "../../capability-facts.js";
|
|
@@ -160,6 +160,10 @@ export async function handleMemoryPolicy(req, res, match, ctx) {
|
|
|
160
160
|
sendError(res, 400, "memory_sync_invalid_scope", "missing :scope");
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
163
|
+
if (!isReplicableScope(scope)) {
|
|
164
|
+
sendError(res, 400, "memory_sync_invalid_scope", "a userlocal: memory layer never leaves the store it is written in — it is not synced");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
163
167
|
if (sendScopeGateRefusal(res, await gateMemoryScope(deps, principal, scope, "write")))
|
|
164
168
|
return;
|
|
165
169
|
const body = await readJson(req);
|
|
@@ -12,12 +12,9 @@ export async function handleObservability(req, res, match, ctx) {
|
|
|
12
12
|
sendOperatorPrincipalsRequired(res);
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
-
if (deps.config.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
sendError(res, 403, "auth.operator_only", "outcomes view is operator-only on a multi-tenant worker");
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
15
|
+
if (!explicitOperatorOk(gatedPrincipal(req, deps.config), deps.config)) {
|
|
16
|
+
sendError(res, 403, "auth.operator_only", "the outcomes view is operator-only");
|
|
17
|
+
return;
|
|
21
18
|
}
|
|
22
19
|
if (!fact.available) {
|
|
23
20
|
switch (fact.why) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* POST /v1/rules → 写一条**收紧**规则(deny/ask)进 durable 分区,单步、无票仪式
|
|
5
5
|
* POST /v1/rules/cc-import/prepare → 预览 + 一张**一次性、principal 绑定、有期、载荷绑定**的票
|
|
6
|
-
* POST /v1/rules/cc-import/redeem → 原子消费票 → confirm → 批量兑付进店
|
|
6
|
+
* POST /v1/rules/cc-import/redeem → 原子消费票 → confirm → 批量兑付进店 → 回执 `{ members, rev }`(与 local-import 口同一张,{@link sendImportReceipt})
|
|
7
7
|
* GET /v1/rules → 列出自己名下**活着**的规则(keyset 分页,游标绑 rev)
|
|
8
8
|
* DELETE /v1/rules → 按 (rule, scope) 内容撤销,**恒经** core `removePersistedRule`
|
|
9
9
|
*
|
|
@@ -159,7 +159,7 @@ export async function handleRules(req, res, match, ctx) {
|
|
|
159
159
|
}
|
|
160
160
|
const redeemed = await lane.redeemLocalImport(principal, parsed.data.ticket);
|
|
161
161
|
if (redeemed.ok) {
|
|
162
|
-
|
|
162
|
+
sendImportReceipt(res, redeemed.outcome);
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
165
165
|
logger.info("local rule import ticket refused", { reason: redeemed.reason, detail: redeemed.detail, retryable: redeemed.reason !== "ticket-unusable" && redeemed.reason !== "in-flight" && redeemed.retryable === true });
|
|
@@ -218,7 +218,10 @@ export async function handleRules(req, res, match, ctx) {
|
|
|
218
218
|
sendError(res, 404, "not_found.rule_ticket", "no such import ticket");
|
|
219
219
|
return;
|
|
220
220
|
}
|
|
221
|
-
|
|
221
|
+
sendImportReceipt(res, redeemed.outcome);
|
|
222
|
+
}
|
|
223
|
+
function sendImportReceipt(res, outcome) {
|
|
224
|
+
sendJson(res, 200, { members: outcome.members, rev: outcome.rev });
|
|
222
225
|
}
|
|
223
226
|
function resolveTargetPrincipal(requested, caller, config) {
|
|
224
227
|
if (requested === undefined || requested === caller)
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
import { uuidv7, mintCheckpointToken, CheckpointError, policyCannotAnswer, validatePendingSteer, TASK_OUTPUT_REPLAY_KINDS, isSystemInjectionPriority } from "@sema-agent/core";
|
|
5
5
|
import { isTerminalRunStatus, isParkedRunStatus, runProvenElsewhere } from "../../plugins/store-contracts.js";
|
|
6
6
|
import { askOwnerAdmits, askRowCarriesOwner } from "../../plugins/approval-ask-store-sql.js";
|
|
7
|
-
import { LIVE_RUN_GATE_KINDS, TERMINAL_PARK_GATE_KINDS } from "../../plugins/checkpoint-store-
|
|
7
|
+
import { LIVE_RUN_GATE_KINDS, TERMINAL_PARK_GATE_KINDS } from "../../plugins/checkpoint-store-face.js";
|
|
8
8
|
import { redactSecrets } from "../../trace/redact.js";
|
|
9
9
|
import { isUuidV7, encodeCheckpointScope } from "../../security.js";
|
|
10
10
|
import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from "../../orchestration/workflow-agent-steer.js";
|
|
@@ -1578,7 +1578,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1578
1578
|
export async function createDurableRun(req, res, ctx, prepared, idem) {
|
|
1579
1579
|
const { deps } = ctx;
|
|
1580
1580
|
const { idemCache } = ctx.registry;
|
|
1581
|
-
const { leaseDenied, usageWindowDenied } = ctx.helpers;
|
|
1581
|
+
const { leaseDenied, usageWindowDenied, admitRun } = ctx.helpers;
|
|
1582
1582
|
const source = ctx.req.source;
|
|
1583
1583
|
const runs = facts.durableRuns(deps);
|
|
1584
1584
|
if (!runs.available) {
|
|
@@ -1600,6 +1600,9 @@ export async function createDurableRun(req, res, ctx, prepared, idem) {
|
|
|
1600
1600
|
}
|
|
1601
1601
|
if ((await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
|
|
1602
1602
|
return null;
|
|
1603
|
+
const runSlot = admitRun(req, res);
|
|
1604
|
+
if (runSlot === undefined)
|
|
1605
|
+
return null;
|
|
1603
1606
|
return await idemCache.run(idem !== undefined ? inflightKeyOf(idem, "durable", clientTaskId) : undefined, async () => {
|
|
1604
1607
|
const out = await mintAndDriveDurableRun({ deps, registry: ctx.registry, helpers: ctx.helpers, local: ctx.local, legs: ctx.legs }, runStore, {
|
|
1605
1608
|
prepared,
|
|
@@ -1609,9 +1612,10 @@ export async function createDurableRun(req, res, ctx, prepared, idem) {
|
|
|
1609
1612
|
elicitOwner: submitter ?? null,
|
|
1610
1613
|
source,
|
|
1611
1614
|
identity: runIdentityOf(req, deps.config, prepared.auth?.principal ?? null),
|
|
1615
|
+
runSlot,
|
|
1612
1616
|
});
|
|
1613
1617
|
return out.existing ? await replayOfExistingRun(out.existing, caller, "durable", deps, runStore) : out;
|
|
1614
|
-
});
|
|
1618
|
+
}).finally(() => runSlot.release());
|
|
1615
1619
|
}
|
|
1616
1620
|
function clientTaskIdOf(body) {
|
|
1617
1621
|
const raw = typeof body === "object" && body !== null ? body.taskId : undefined;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { once } from "node:events";
|
|
2
|
-
import {
|
|
3
|
-
import { sessionLogDigest, sessionLogDigestsComparable, StreamingImportValidator, validateFileHistoryExport } from "@sema-agent/core";
|
|
2
|
+
import { sessionLogDigest, sessionLogDigestsComparable, validateFileHistoryExport } from "@sema-agent/core";
|
|
4
3
|
import { StringDecoder } from "node:string_decoder";
|
|
5
4
|
import { FileHistoryUnreadableError, blobGetterOf, exportSessionManifest, exportedFileHistory, fileHistoryImportFace, replayFileHistory, replayPolicyRecords, wipeSessionAttendants } from "../../session-sync.js";
|
|
6
5
|
import * as facts from "../../capability-facts.js";
|
|
7
|
-
import { classifySyncRelationshipByIds, readImportAnchors, SyncConflictError } from "../../session-sync-kernel.js";
|
|
6
|
+
import { classifySyncRelationshipByIds, createStagedLogFeed, MalformedImportEntryError, newStagingToken, readImportAnchors, SyncConflictError } from "../../session-sync-kernel.js";
|
|
8
7
|
import { isUuidShape, legacyUnownedSessionOk, sessionOwnerCellOf } from "../../security.js";
|
|
9
8
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, logRequestError, msg } from "../send.js";
|
|
9
|
+
import { StampedSessionArchiveError } from "../../session-error-wire.js";
|
|
10
|
+
import { readSessionAgent } from "../../session-agent.js";
|
|
10
11
|
import { buildDisplaySafeUrl } from "../../trace/redact.js";
|
|
11
12
|
import { storeFaultClassOf } from "../../plugins/store-fault.js";
|
|
12
13
|
import { fleetViewOf, gatedPrincipal, explicitOperatorOk, sendUnauthorized, sessionCallerOf } from "../principal-gate.js";
|
|
13
14
|
const SYNC_BLOB_MAX_BYTES = 64 * 1024 * 1024;
|
|
14
15
|
const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
|
|
15
|
-
const SYNC_IMPORT_BATCH = 500;
|
|
16
16
|
import { SHA256_HEX_RE } from "../../digest-form.js";
|
|
17
17
|
import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
|
|
18
18
|
import { readSessionRulesRecords, describeSessionRulesRecordProblem } from "../../session-policy-wire.js";
|
|
@@ -359,6 +359,16 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
359
359
|
const dstIds = (await sessionStorage.listEntryIds?.(sessionId)) ?? null;
|
|
360
360
|
const relA = classifySyncRelationshipByIds(entryIds, dstIds);
|
|
361
361
|
if ((relA.relation === "fork" || relA.relation === "stale") && resolution !== "overwrite-dst") {
|
|
362
|
+
if (sessionStorage.assertArchiveAnonymous !== undefined && (await readSessionAgent(sessionStorage, sessionId)) === undefined) {
|
|
363
|
+
try {
|
|
364
|
+
await sessionStorage.assertArchiveAnonymous(sessionId);
|
|
365
|
+
}
|
|
366
|
+
catch (e) {
|
|
367
|
+
if (e instanceof StampedSessionArchiveError && sendSessionError(res, e, "import"))
|
|
368
|
+
return;
|
|
369
|
+
throw e;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
362
372
|
sendError(res, 409, "conflict", "sync conflict", { relation: relA });
|
|
363
373
|
return;
|
|
364
374
|
}
|
|
@@ -489,7 +499,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
489
499
|
}
|
|
490
500
|
const importingPrincipal = scope.fleetWide ? (owner ?? null) : scope.gateOwner;
|
|
491
501
|
const operatorOk = explicitOperatorOk(gatedPrincipal(req, deps.config), deps.config);
|
|
492
|
-
const token =
|
|
502
|
+
const token = newStagingToken();
|
|
493
503
|
const handle = sessionStorage.beginImportStaging(sessionId, token);
|
|
494
504
|
const stagingId = handle.stagingId;
|
|
495
505
|
importLeases.set(sessionId, { at: Date.now(), stagingId });
|
|
@@ -515,13 +525,8 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
515
525
|
sendError(res, 404, "not_found.staging", "staging not found");
|
|
516
526
|
return;
|
|
517
527
|
}
|
|
518
|
-
const
|
|
528
|
+
const feed = createStagedLogFeed(pending.handle);
|
|
519
529
|
let lineBuf = "";
|
|
520
|
-
let pendingBatch = [];
|
|
521
|
-
const flush = async () => { if (pendingBatch.length) {
|
|
522
|
-
await pending.handle.appendBatch(pendingBatch);
|
|
523
|
-
pendingBatch = [];
|
|
524
|
-
} };
|
|
525
530
|
class PrecheckError extends Error {
|
|
526
531
|
status;
|
|
527
532
|
constructor(status, msg) {
|
|
@@ -530,12 +535,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
530
535
|
}
|
|
531
536
|
}
|
|
532
537
|
const streamedIds = new Set();
|
|
533
|
-
const onEntry = (e) => {
|
|
534
|
-
if (!e || typeof e !== "object" || typeof e.id !== "string")
|
|
535
|
-
throw new PrecheckError(422, "malformed entry (missing id)");
|
|
536
|
-
streamedIds.add(e.id);
|
|
537
|
-
validator.step(e);
|
|
538
|
-
};
|
|
538
|
+
const onEntry = async (e) => { streamedIds.add(await feed.push(e)); };
|
|
539
539
|
const utf8 = new StringDecoder("utf8");
|
|
540
540
|
try {
|
|
541
541
|
for await (const chunk of req) {
|
|
@@ -556,10 +556,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
556
556
|
catch {
|
|
557
557
|
throw new PrecheckError(400, "malformed NDJSON line");
|
|
558
558
|
}
|
|
559
|
-
onEntry(e);
|
|
560
|
-
pendingBatch.push(e);
|
|
561
|
-
if (pendingBatch.length >= SYNC_IMPORT_BATCH)
|
|
562
|
-
await flush();
|
|
559
|
+
await onEntry(e);
|
|
563
560
|
}
|
|
564
561
|
}
|
|
565
562
|
lineBuf += utf8.end();
|
|
@@ -572,11 +569,9 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
572
569
|
catch {
|
|
573
570
|
throw new PrecheckError(400, "malformed NDJSON line");
|
|
574
571
|
}
|
|
575
|
-
onEntry(e);
|
|
576
|
-
pendingBatch.push(e);
|
|
572
|
+
await onEntry(e);
|
|
577
573
|
}
|
|
578
|
-
await
|
|
579
|
-
validator.finish();
|
|
574
|
+
await feed.finish();
|
|
580
575
|
if (streamedIds.size !== pending.declaredEntryIds.length || !pending.declaredEntryIds.every((id) => streamedIds.has(id))) {
|
|
581
576
|
throw new PrecheckError(422, "streamed entries do not match the Phase-A declared entryIds");
|
|
582
577
|
}
|
|
@@ -588,6 +583,10 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
588
583
|
sendError(res, e.status, "invalid_entries", e.message);
|
|
589
584
|
return;
|
|
590
585
|
}
|
|
586
|
+
if (e instanceof MalformedImportEntryError) {
|
|
587
|
+
sendError(res, 422, "invalid_entries", e.message);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
591
590
|
if (sendSessionError(res, e, "import"))
|
|
592
591
|
return;
|
|
593
592
|
throw e;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* [ref] A9 域模块 —— 会话列表 / 发件链接列表。
|
|
3
3
|
*
|
|
4
4
|
* 路由:`GET /v1/sessions`(CC /resume picker,keyset 分页)、`GET /v1/sendfile-links`。
|
|
5
|
-
* owner-scope 与 trace 列表同一权威模型(`fleetViewOf` 读档:service
|
|
5
|
+
* owner-scope 与 trace 列表同一权威模型(`fleetViewOf` 读档:service 凭据不报主体〔部署声明 `SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT=false` 时不算〕/ 只读 trace 凭据 / dev-open = fleet-wide;带验过的主体 = 钉住那位主体)。本域零可变状态。
|
|
6
6
|
*/
|
|
7
7
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
8
|
import { type SessionListItem } from "../../security.js";
|
|
@@ -8,6 +8,7 @@ import { recordFailOpen } from "../../observability/fail-open.js";
|
|
|
8
8
|
import { MCP_DIAL_DEADLINE_MS, raceDeadline } from "../mcp-dial-deadline.js";
|
|
9
9
|
import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.js";
|
|
10
10
|
import { readLayerSeat, sentMemoryLayerRefusal } from "../memory-layer-reply.js";
|
|
11
|
+
import { DEPLOYMENT_SHAPE_PREDICATES } from "../../deployment-shape.js";
|
|
11
12
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
|
|
12
13
|
import { readWritableSessionPermissionRules } from "../../session-policy-wire.js";
|
|
13
14
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders, httpErrorBody } from "../send.js";
|
|
@@ -615,6 +616,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
615
616
|
memoryStatusCache.delete(cacheKey); });
|
|
616
617
|
}
|
|
617
618
|
const s = await pending;
|
|
619
|
+
const workerPathsDisclosed = DEPLOYMENT_SHAPE_PREDICATES.callerOwnsWorkerDisk.holds(deps.config);
|
|
618
620
|
sendJson(res, 200, {
|
|
619
621
|
sessionId,
|
|
620
622
|
autoConsolidationArmed: deps.memoryAutoConsolidationArmed,
|
|
@@ -624,6 +626,8 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
624
626
|
...(s.foldedCount !== undefined ? { foldedCount: s.foldedCount } : {}),
|
|
625
627
|
...(s.optOutSource !== undefined ? { optOutSource: s.optOutSource } : {}),
|
|
626
628
|
...(s.lastCaptureAt !== undefined ? { lastCaptureAt: s.lastCaptureAt } : {}),
|
|
629
|
+
...(s.writeHome !== undefined && workerPathsDisclosed ? { writeHome: s.writeHome } : {}),
|
|
630
|
+
...(s.planes !== undefined && workerPathsDisclosed ? { planes: s.planes.map((row) => ({ plane: row.plane, ...(row.status.writeHome !== undefined ? { writeHome: row.status.writeHome } : {}) })) } : {}),
|
|
627
631
|
});
|
|
628
632
|
}
|
|
629
633
|
catch (err) {
|
|
@@ -5,7 +5,7 @@ import { graftParkToolCallId, markChildrenStoppedByUserOnAbort, syncRefusalHttpS
|
|
|
5
5
|
import { atHostBoundary, sealResultForExit, outwardPlane } from "../../terminal.js";
|
|
6
6
|
import { voidVerifierPauseCard } from "../../verifier-pause-card.js";
|
|
7
7
|
import { withPrincipal } from "../../observability/principal-context.js";
|
|
8
|
-
import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals } from "../../fleet/fleet-bus.js";
|
|
8
|
+
import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, nameFleetRowOnDelivery } from "../../fleet/fleet-bus.js";
|
|
9
9
|
import { defaultSubagentTailBus } from "../../fleet/subagent-tail-bus.js";
|
|
10
10
|
import { createLiveModelText, modelStreamOfTask } from "../../trace/model-segment.js";
|
|
11
11
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../../orchestration/workflow-completion-inbox.js";
|
|
@@ -17,7 +17,7 @@ import { recordResourceWindow } from "../../resource-window.js";
|
|
|
17
17
|
import { redactSecrets } from "../../trace/redact.js";
|
|
18
18
|
import { writeLiveFrame, writeLiveHeartbeat } from "../live-frame.js";
|
|
19
19
|
import { contextUsageEventData, turnEndEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, compactionStartedEventData, workspaceChangedEventData, wiringManifestFaceEventData, toolRosterDeltaEventData, toolProgressEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, liveIdentityFields } from "../../trace/project.js";
|
|
20
|
-
import { cascadeConfig, runMeta } from "../run-meta.js";
|
|
20
|
+
import { cascadeConfig, fillRunPreviewOnDelivery, runMeta } from "../run-meta.js";
|
|
21
21
|
import { idemCallerOf, inflightKeyOf, submissionIdemOf } from "../idempotency.js";
|
|
22
22
|
import { replayOfExistingRun } from "../submit-replay.js";
|
|
23
23
|
import { httpErrorCode, sendJson, sendError, sseHeaders } from "../send.js";
|
|
@@ -33,8 +33,7 @@ import { failOpenTagForDroppedFrame, recordFailOpen } from "../../observability/
|
|
|
33
33
|
import { buildSyncSubmitBody } from "../sync-submit-body.js";
|
|
34
34
|
import { assertNever } from "../route-ctx.js";
|
|
35
35
|
import * as facts from "../../capability-facts.js";
|
|
36
|
-
import {
|
|
37
|
-
import { serviceVersion } from "../../service-version.js";
|
|
36
|
+
import { persistSubmissionCtx } from "../submit-ctx.js";
|
|
38
37
|
import { withModelReachScope } from "../../model-reach-scope.js";
|
|
39
38
|
import { createInstructionSelectionLeg } from "../../instruction-selection.js";
|
|
40
39
|
import { fieldConflictFacts } from "../../field-conflict.js";
|
|
@@ -77,7 +76,7 @@ const MULTI_ATTEMPT_NO_EVENT_CHANNEL_NOTICE = "verify / cascade ran with their n
|
|
|
77
76
|
export async function handleTasks(req, res, match, ctx) {
|
|
78
77
|
const { deps } = ctx;
|
|
79
78
|
const { idemCache, inflightRuns, steerableRuns, counters } = ctx.registry;
|
|
80
|
-
const { readJson, rateLimited, quotaExceeded, leaseDenied, usageWindowDenied } = ctx.helpers;
|
|
79
|
+
const { readJson, rateLimited, quotaExceeded, leaseDenied, usageWindowDenied, admitRun } = ctx.helpers;
|
|
81
80
|
const { prepareSpec, finalizeTaskResult } = ctx.legs;
|
|
82
81
|
const reqState = ctx.req;
|
|
83
82
|
const source = ctx.req.source;
|
|
@@ -145,10 +144,14 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
145
144
|
}
|
|
146
145
|
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
|
|
147
146
|
return;
|
|
147
|
+
let runSlot = undefined;
|
|
148
148
|
const prepared = await prepareSpec(req, res, keyedBody, undefined, { liveStream });
|
|
149
149
|
if (!prepared)
|
|
150
150
|
return;
|
|
151
151
|
try {
|
|
152
|
+
runSlot = admitRun(req, res);
|
|
153
|
+
if (runSlot === undefined)
|
|
154
|
+
return;
|
|
152
155
|
const declaredFamilies = declaredApprovalFrameFamilies(prepared.body.settings);
|
|
153
156
|
const approvalLegInput = (streamApprovalOn) => {
|
|
154
157
|
const legWalltimeMs = typeof prepared.spec.limits?.maxWalltimeMs === "number" ? prepared.spec.limits.maxWalltimeMs : undefined;
|
|
@@ -185,7 +188,7 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
185
188
|
sseHeaders(res, earlyDurableTid ? { "x-task-id": earlyDurableTid } : undefined);
|
|
186
189
|
if (earlyDurableTid)
|
|
187
190
|
writeMetaFrame(res, earlyDurableTid, prepared.spec.sessionId, prepared.warnings);
|
|
188
|
-
const ac = createRunAbort();
|
|
191
|
+
const ac = createRunAbort(prepared.auth?.principal ?? null);
|
|
189
192
|
let closed = false;
|
|
190
193
|
let detachLogged = false;
|
|
191
194
|
let disconnectParkGrace;
|
|
@@ -326,9 +329,10 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
326
329
|
return placementReject;
|
|
327
330
|
}
|
|
328
331
|
durableTaskId = tid;
|
|
332
|
+
fillRunPreviewOnDelivery(prepared.acceptance, deps.runStore, tid);
|
|
329
333
|
}
|
|
330
334
|
if (deps.checkpointStore) {
|
|
331
|
-
const ctxRefusal = await
|
|
335
|
+
const ctxRefusal = await persistSubmissionCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared);
|
|
332
336
|
if (ctxRefusal) {
|
|
333
337
|
writeLiveFrame(res, { type: "done", result: doneResultOfOutcome(ctxRefusal) });
|
|
334
338
|
return ctxRefusal;
|
|
@@ -355,14 +359,17 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
355
359
|
}, HEARTBEAT_MS);
|
|
356
360
|
durableHeartbeat.unref?.();
|
|
357
361
|
inflightRuns.set(tid, ac);
|
|
362
|
+
runSlot?.release();
|
|
358
363
|
void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c && !ac.signal.aborted)
|
|
359
364
|
abortFromVerb(); }).catch(() => undefined);
|
|
360
365
|
}
|
|
361
366
|
let fleetPub;
|
|
362
367
|
try {
|
|
363
368
|
fleetPub = durableTaskId
|
|
364
|
-
? fleetRunPublisher(deps.fleetBus, { runId: durableTaskId, scope: gatedPrincipal(req, deps.config) ?? "default", rootTaskId: durableTaskId, sessionId: prepared.spec.sessionId, ...fleetRunLabels(prepared.
|
|
369
|
+
? fleetRunPublisher(deps.fleetBus, { runId: durableTaskId, scope: gatedPrincipal(req, deps.config) ?? "default", rootTaskId: durableTaskId, sessionId: prepared.spec.sessionId, ...fleetRunLabels(prepared.acceptance.objectiveAtSubmit) })
|
|
365
370
|
: undefined;
|
|
371
|
+
if (fleetPub)
|
|
372
|
+
nameFleetRowOnDelivery(prepared.acceptance, fleetPub);
|
|
366
373
|
}
|
|
367
374
|
catch (e) {
|
|
368
375
|
if (durableHeartbeat)
|
|
@@ -499,7 +506,7 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
499
506
|
userMsgEntryId = promptEntryId;
|
|
500
507
|
const causeAtDone = ev.type === "done" ? ac.cause() : undefined;
|
|
501
508
|
await deps.userInputLedger?.observeLegEvent(liveStream, ev);
|
|
502
|
-
prepared.acceptance.observe(ev);
|
|
509
|
+
await prepared.acceptance.observe(ev);
|
|
503
510
|
await liveWorkspace.observe(ev);
|
|
504
511
|
if (ev.type === "done") {
|
|
505
512
|
finalResult = await mintSubmitExitResult(ev.result, deps.checkpointStore);
|
|
@@ -847,7 +854,10 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
847
854
|
};
|
|
848
855
|
counters.uncountedBillableInflight++;
|
|
849
856
|
try {
|
|
850
|
-
|
|
857
|
+
const settling = idemCache.run(inflightKey, async () => withRunReceipt(await runStreamSubmitLeg()));
|
|
858
|
+
if (!ranLive)
|
|
859
|
+
runSlot?.release();
|
|
860
|
+
resp = await settling;
|
|
851
861
|
}
|
|
852
862
|
catch (e) {
|
|
853
863
|
deps.logger?.error("stream_error", { closed, err: redactHead(e instanceof Error ? e.message : String(e), 2000) });
|
|
@@ -894,14 +904,15 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
894
904
|
if (placementReject)
|
|
895
905
|
return placementReject;
|
|
896
906
|
durableTaskId = tid;
|
|
907
|
+
fillRunPreviewOnDelivery(prepared.acceptance, deps.runStore, tid);
|
|
897
908
|
}
|
|
898
909
|
if (deps.checkpointStore) {
|
|
899
|
-
const ctxRefusal = await
|
|
910
|
+
const ctxRefusal = await persistSubmissionCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared);
|
|
900
911
|
if (ctxRefusal)
|
|
901
912
|
return ctxRefusal;
|
|
902
913
|
}
|
|
903
914
|
commitSessionInheritEnv(deps, prepared.spec.sessionId, prepared.body);
|
|
904
|
-
const syncCancelCtrl = createRunAbort();
|
|
915
|
+
const syncCancelCtrl = createRunAbort(principal ?? null);
|
|
905
916
|
markChildrenStoppedByUserOnAbort(syncCancelCtrl.signal, durableTaskId ?? prepared.spec.sessionId, prepared.auth?.principal);
|
|
906
917
|
if (durableTaskId && deps.runStore) {
|
|
907
918
|
const rs = deps.runStore;
|
|
@@ -917,11 +928,13 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
917
928
|
}, HEARTBEAT_MS);
|
|
918
929
|
durableHeartbeat.unref?.();
|
|
919
930
|
inflightRuns.set(tid, syncCancelCtrl);
|
|
931
|
+
runSlot?.release();
|
|
920
932
|
void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c && !syncCancelCtrl.signal.aborted)
|
|
921
933
|
abortFromVerb(); }).catch(() => undefined);
|
|
922
934
|
}
|
|
923
935
|
else if (durableTaskId) {
|
|
924
936
|
inflightRuns.set(durableTaskId, syncCancelCtrl);
|
|
937
|
+
runSlot?.release();
|
|
925
938
|
}
|
|
926
939
|
const specWithSignal = { ...prepared.spec, signal: syncCancelCtrl.signal, ...(durableTaskId ? { taskId: durableTaskId } : {}) };
|
|
927
940
|
const observeSyncLegEvent = (ev) => {
|
|
@@ -940,7 +953,7 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
940
953
|
const syncWorkspace = createSessionWorkspaceRecorder(sessionWorkspaceStoreOf(deps.sessionStorage), prepared.legFacts?.sessionWitness);
|
|
941
954
|
for await (const ev of stream) {
|
|
942
955
|
observeSyncLegEvent(ev);
|
|
943
|
-
prepared.acceptance.observe(ev);
|
|
956
|
+
await prepared.acceptance.observe(ev);
|
|
944
957
|
await syncWorkspace.observe(ev);
|
|
945
958
|
}
|
|
946
959
|
return stream.result();
|
|
@@ -998,12 +1011,20 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
998
1011
|
noteRunOutcome(result);
|
|
999
1012
|
return { status: 200, body: withSyncSubmitKeys(buildSyncSubmitBody(exitResult)) };
|
|
1000
1013
|
};
|
|
1001
|
-
|
|
1014
|
+
let ranSyncLeg = false;
|
|
1015
|
+
const settling = idemCache.run(inflightKey, async () => {
|
|
1016
|
+
ranSyncLeg = true;
|
|
1017
|
+
return withRunReceipt(await runSyncSubmitLeg());
|
|
1018
|
+
});
|
|
1019
|
+
if (!ranSyncLeg)
|
|
1020
|
+
runSlot?.release();
|
|
1021
|
+
const resp = await settling;
|
|
1002
1022
|
sendSubmitResult(res, resp);
|
|
1003
1023
|
}
|
|
1004
1024
|
return;
|
|
1005
1025
|
}
|
|
1006
1026
|
finally {
|
|
1027
|
+
runSlot?.release();
|
|
1007
1028
|
prepared.peerLease.release();
|
|
1008
1029
|
}
|
|
1009
1030
|
}
|
|
@@ -102,7 +102,7 @@ export async function handleTraceUsage(req, res, match, ctx) {
|
|
|
102
102
|
};
|
|
103
103
|
const now = Date.now();
|
|
104
104
|
const from = parseT(q.get("from"), now - 7 * 86_400_000);
|
|
105
|
-
const to = parseT(q.get("to"), now);
|
|
105
|
+
const to = parseT(q.get("to"), now + 1);
|
|
106
106
|
if (Number.isNaN(from) || Number.isNaN(to) || from >= to) {
|
|
107
107
|
sendError(res, 400, "request.query_invalid", "invalid from/to (ISO-8601 or epoch-ms; from < to)");
|
|
108
108
|
return;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 在飞 run 并发帽 —— `RUN_MAX_CONCURRENT`(本副本全局)/ `RUN_MAX_CONCURRENT_PER_PRINCIPAL`(同一已验提交主体),缺省 0 = 关。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么有这一只
|
|
5
|
+
* 共享一台 GPU / 一只小模型网关的部署,调用方自己限并发不可靠(多个调用方、重试风暴、脚本一把梭)。`RATE_LIMIT_RPM` 是速率桶(每分钟几条),
|
|
6
|
+
* 不是「此刻有几条在跑」;一条 run 可以跑几十分钟,速率桶放行的新提交会在同一台机器上叠起来。本帽数的就是后一个量。
|
|
7
|
+
*
|
|
8
|
+
* ## 数什么
|
|
9
|
+
* **同一份**在飞集:`inflightRuns ∪ steerableRuns`({@link liveRunIdsOf} —— `drainState.inflight` 读的也是它;`isRunLiveOnThisReplica`(`http/server.ts`)是同一并集的逐 id 判,
|
|
10
|
+
* 另写成两只 `has` 的析取,不经本函数;计数只有这一处写法,不另造第二份)。按主体数时读 `inflightRuns` 值上的 `owner`({@link RunAbort.owner} —— 句柄与腿同寿,不另起旁表);
|
|
11
|
+
* 只在 `steerableRuns` 里的键没有属主可读,只计入全局(今天每条可 steer 的腿都同时登记在 `inflightRuns`,这是形式上的保守)。
|
|
12
|
+
* 数的是**本副本**:副本之间不共享,多副本部署的整体并发 = 各副本的帽之和。
|
|
13
|
+
*
|
|
14
|
+
* ## 判点(哪几扇门判、哪几扇不判)
|
|
15
|
+
* 判 = **新提交要起一条 run** 的三个提交门(`routes/runs.ts` 的 `createDurableRun` —— `POST /v1/runs` 与 A2A `message/send` 共用的建行腿、
|
|
16
|
+
* `routes/tasks.ts` 的 `POST /v1/tasks` 与 `POST /v1/tasks/stream`),且排在**幂等重放之后**(同键 / 自带 taskId 的重试拿回自己的回执,不吃新 429)。
|
|
17
|
+
* 不判 = 已有 run 的延续与作答:续跑、唤醒、审批 / 问答作答、`/decide` —— 它们不是新提交,在飞时照样占一格(被数;没有 run 行的老形续跑不登记进在飞集,不被数),
|
|
18
|
+
* 但拒它们会把一张已经在等人的卡卡死。所以帽闸的是**新提交**,不是在飞总数的硬上限:一条停驻的 run 续跑回来时,在飞数可以越过帽。
|
|
19
|
+
* leader 口的后台 run 不在 `inflightRuns` 里(drain 靠 `uncountedBillableInflight` 看见它),本帽不数它也不判它。
|
|
20
|
+
* 位置的两个如实:三个口的帽都排在受理漏斗(`prepareSpec`,可能为新会话做登记)**之后**、建行与 SSE 头之前 —— `POST /v1/runs` 与 A2A 口因自带 `taskId` 的重放在漏斗之后才答、帽必须排在它后面
|
|
21
|
+
* (与 lease / 治理窗的 429 同位),`POST /v1/tasks` 两口与它们同位;拒时漏斗已做的会话登记不撤。同键的并发重复提交若赶在首发还在受理(尚未落行、尚未进在飞合流)的窗里到达,按新提交判,
|
|
22
|
+
* 帽满时与其它准入门(速率 / 配额)同形可能拿到一次 429 + `Retry-After`,按它重投即拿回首发的回执。
|
|
23
|
+
*
|
|
24
|
+
* ## 席位(为什么放行不是「读一下数就完了」)
|
|
25
|
+
* 判完帽到这条 run 登记进 `inflightRuns` 之间隔着若干次 await(建行、执行放置、续跑输入落库 …)。并发一阵提交若都只「读数」,会全部在登记之前读到同一个数、
|
|
26
|
+
* 一起放行 —— 越帽。所以放行即**占一枚席位**({@link RunSlot}),在飞数 = 在飞集 + 未放的席位。席位在这条 run 登记进在飞集的那一拍放掉:
|
|
27
|
+
* 三个提交门同形:建行腿 `mintAndDriveDurableRun` 在 `runInBackground`(返回前同步登记)返回后就地放(`runSlot` 入参),`POST /v1/tasks` / `/v1/tasks/stream` 两腿在各自的
|
|
28
|
+
* `inflightRuns.set` 之后就地放;提交门的 `finally` 总再放一次兜底(早退、被别的门拒、抛 —— `release` 幂等)。登记到放席位之间没有 await,别的提交读不到
|
|
29
|
+
* 「既在集里又占着席位」的中间态,所以不重复计。
|
|
30
|
+
* 两个尾巴:合流进别人在飞建行的重复提交(同键赛过 `peek`)自己不建行 —— `POST /v1/tasks` 一族席位当场放回;`POST /v1/runs`(与 A2A 提交)的席位到这一次提交的整段结清(`.finally`)才放,
|
|
31
|
+
* 合流的那一次要等共享铸件结清,帽边上可多答一次 429;没有 run store 的部署上同步 / 流式腿根本不登记进在飞集
|
|
32
|
+
* (drain 靠 `uncountedBillableInflight` 数它们),它们的席位就活到请求结束 —— 帽照样数得到它们。
|
|
33
|
+
*
|
|
34
|
+
* ## 拒的形
|
|
35
|
+
* 429 `limit.rate_exceeded`(沿用现有限流码,不新铸一枚:消费端的分支同「稍后再来」)+ 体里机读 `reason`(闭集 {@link RATE_EXCEEDED_REASONS},今天一词)
|
|
36
|
+
* + `retryAfterSec`(固定 {@link RUN_CONCURRENCY_RETRY_AFTER_SEC});头 `Retry-After` 与体里同值,同其它限流 / 配额门的发送形(`res.setHeader("retry-after")` + `sendError`)。
|
|
37
|
+
* 拒在建行、认领会话与 SSE 头之前(受理漏斗已做的会话登记见上面「位置的两个如实」)。
|
|
38
|
+
*
|
|
39
|
+
* ## 谁需要 / 谁受伤 / 补偿
|
|
40
|
+
* 需要:共享一台 GPU 或一只小模型网关的家用 / 小团队部署。受伤:超帽者拿 429(新行为,缺省关)。补偿:缺省 0 = 关(升级零变化)+ `Retry-After` 给退避。
|
|
41
|
+
* 无主体请求(无头 service 凭据、`REQUIRE_PRINCIPAL=false` 的单用户部署)只受全局帽 —— 按主体帽对它不判(没有主体可分账)。
|
|
42
|
+
*/
|
|
43
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
44
|
+
import type { RunAbort } from "../run-cancel-context.js";
|
|
45
|
+
import { type RefusalBody } from "./refusal-body.js";
|
|
46
|
+
import type { FlatServiceDeps } from "./server.js";
|
|
47
|
+
/**
|
|
48
|
+
* 本副本在飞 run 的 id 集 —— `inflightRuns`(durable 腿)∪ `steerableRuns`(活流)的**唯一**并集写法:
|
|
49
|
+
* `drainState.inflight`(排空等多久)、`isRunLiveOnThisReplica`(这条 run 是不是在本副本跑)与并发帽读的是同一份。
|
|
50
|
+
* 参数取 `ReadonlyMap`:读法不改登记簿。
|
|
51
|
+
*/
|
|
52
|
+
export declare function liveRunIdsOf(inflightRuns: ReadonlyMap<string, unknown>, steerableRuns: ReadonlyMap<string, unknown>): Set<string>;
|
|
53
|
+
/** 并发帽拒的机读 `reason` 词。 */
|
|
54
|
+
export declare const RUN_CONCURRENCY_CAP_REASON: "run_concurrency_cap";
|
|
55
|
+
/**
|
|
56
|
+
* 429 `limit.rate_exceeded` 体上机读 `reason` 的**闭集**(穷举读它;加一种拒因 ⇒ 这里加一词,`switch` 的消费端编译红)。
|
|
57
|
+
* 其余 `limit.rate_exceeded` 的产地(速率桶、赎回准入、MCP 探活、烘焙提交)不带 `reason` —— 读法是「有 `reason` 才分」。
|
|
58
|
+
*/
|
|
59
|
+
export declare const RATE_EXCEEDED_REASONS: readonly ["run_concurrency_cap"];
|
|
60
|
+
export type RateExceededReason = (typeof RATE_EXCEEDED_REASONS)[number];
|
|
61
|
+
/** 退避提示:固定 5 秒(是提示不是承诺 —— 在飞的 run 可能再跑很久;帽满只说「稍后再来」)。 */
|
|
62
|
+
export declare const RUN_CONCURRENCY_RETRY_AFTER_SEC = 5;
|
|
63
|
+
/** 一枚席位:放行与登记进在飞集之间占着的那一格。`release` 幂等(登记点放一次,提交门的 `finally` 再放一次兜底)。 */
|
|
64
|
+
export interface RunSlot {
|
|
65
|
+
release(): void;
|
|
66
|
+
}
|
|
67
|
+
/** 并发帽拒的 `{status, body, retryAfterSec}`(体经全仓唯一组句器;状态与退避供发送口用)。 */
|
|
68
|
+
export declare function runConcurrencyRefusalOf(): {
|
|
69
|
+
readonly status: 429;
|
|
70
|
+
readonly body: RefusalBody;
|
|
71
|
+
readonly retryAfterSec: number;
|
|
72
|
+
};
|
|
73
|
+
export interface RunConcurrencyGate {
|
|
74
|
+
/**
|
|
75
|
+
* 提交门的并发帽准入:放行 ⇒ 交回席位(调用点登记那一拍 / `finally` 放);超帽 ⇒ 已应答 429(+ `Retry-After`),交回 `undefined`,调用点 `return`。
|
|
76
|
+
* 位序:在幂等重放、`rateLimited` / `quotaExceeded` / `leaseDenied` / `usageWindowDenied` 之后,任何建行 / 认领 / SSE 头之前。
|
|
77
|
+
*/
|
|
78
|
+
admitRun(req: IncomingMessage, res: ServerResponse): RunSlot | undefined;
|
|
79
|
+
}
|
|
80
|
+
/** 装配口:`deps` 只读 `config`(两旋钮,每次判时现读)与 `logger`;在飞集由 server 装配层交来(两张登记簿本身,不拷)。 */
|
|
81
|
+
export declare function createRunConcurrencyGate(ctx: {
|
|
82
|
+
deps: Pick<FlatServiceDeps, "config" | "logger">;
|
|
83
|
+
inflightRuns: ReadonlyMap<string, RunAbort>;
|
|
84
|
+
steerableRuns: ReadonlyMap<string, unknown>;
|
|
85
|
+
}): RunConcurrencyGate;
|
|
86
|
+
//# sourceMappingURL=run-concurrency-cap.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { gatedPrincipal } from "./principal-gate.js";
|
|
2
|
+
import { refusalBody } from "./refusal-body.js";
|
|
3
|
+
import { sendError } from "./send.js";
|
|
4
|
+
export function liveRunIdsOf(inflightRuns, steerableRuns) {
|
|
5
|
+
return new Set([...inflightRuns.keys(), ...steerableRuns.keys()]);
|
|
6
|
+
}
|
|
7
|
+
export const RUN_CONCURRENCY_CAP_REASON = "run_concurrency_cap";
|
|
8
|
+
export const RATE_EXCEEDED_REASONS = [RUN_CONCURRENCY_CAP_REASON];
|
|
9
|
+
export const RUN_CONCURRENCY_RETRY_AFTER_SEC = 5;
|
|
10
|
+
const UNTRACKED_SLOT = { release: () => undefined };
|
|
11
|
+
export function runConcurrencyRefusalOf() {
|
|
12
|
+
return {
|
|
13
|
+
status: 429,
|
|
14
|
+
body: refusalBody({
|
|
15
|
+
errorCode: "limit.rate_exceeded",
|
|
16
|
+
message: "too many runs are in flight on this server right now — nothing was started; retry after the Retry-After interval",
|
|
17
|
+
facts: { reason: RUN_CONCURRENCY_CAP_REASON, retryAfterSec: RUN_CONCURRENCY_RETRY_AFTER_SEC },
|
|
18
|
+
}),
|
|
19
|
+
retryAfterSec: RUN_CONCURRENCY_RETRY_AFTER_SEC,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function createRunConcurrencyGate(ctx) {
|
|
23
|
+
const { deps, inflightRuns, steerableRuns } = ctx;
|
|
24
|
+
const seats = new Set();
|
|
25
|
+
function admitRun(req, res) {
|
|
26
|
+
const { runMaxConcurrent: globalCap, runMaxConcurrentPerPrincipal: ownCap } = deps.config;
|
|
27
|
+
if (globalCap === 0 && ownCap === 0)
|
|
28
|
+
return UNTRACKED_SLOT;
|
|
29
|
+
const owner = gatedPrincipal(req, deps.config);
|
|
30
|
+
const live = liveRunIdsOf(inflightRuns, steerableRuns);
|
|
31
|
+
const inFlight = live.size + seats.size;
|
|
32
|
+
let scope;
|
|
33
|
+
if (globalCap > 0 && inFlight >= globalCap)
|
|
34
|
+
scope = "global";
|
|
35
|
+
else if (ownCap > 0 && owner !== undefined) {
|
|
36
|
+
const holders = [...[...live].map((id) => inflightRuns.get(id)?.owner), ...[...seats].map((seat) => seat.owner)];
|
|
37
|
+
if (holders.filter((o) => o === owner).length >= ownCap)
|
|
38
|
+
scope = "principal";
|
|
39
|
+
}
|
|
40
|
+
if (scope !== undefined) {
|
|
41
|
+
deps.logger?.info?.("run_concurrency_cap_refused", { scope, inFlight, cap: scope === "global" ? globalCap : ownCap, hasPrincipal: owner !== undefined });
|
|
42
|
+
const refusal = runConcurrencyRefusalOf();
|
|
43
|
+
const { error, errorCode, ...extra } = refusal.body;
|
|
44
|
+
res.setHeader("retry-after", String(refusal.retryAfterSec));
|
|
45
|
+
sendError(res, refusal.status, errorCode, error, extra);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const seat = { owner };
|
|
49
|
+
seats.add(seat);
|
|
50
|
+
return { release: () => void seats.delete(seat) };
|
|
51
|
+
}
|
|
52
|
+
return { admitRun };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=run-concurrency-cap.js.map
|