@sema-agent/server 7.125.1 → 7.127.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 +229 -2
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +346 -145
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/chart/templates/networkpolicy-sandbox.yaml +57 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +32 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +3 -2
- package/deploy/sema-up/chart/templates/server.yaml +2 -1
- package/deploy/sema-up/chart/values.yaml +22 -0
- package/deploy/sema-up/cluster-up.sh +10 -10
- package/deploy/sema-up/kube-up.sh +20 -13
- package/deploy/sema-up/lib/dataroot.sh +1 -1
- package/deploy/sema-up/lib/envf.sh +5 -5
- package/deploy/sema-up/lib/flagshape.sh +5 -5
- package/deploy/sema-up/lib/lanes.py +1 -1
- package/deploy/sema-up/lib/node-addrs.sh +42 -0
- package/deploy/sema-up/lib/parse-machines.py +2 -2
- package/deploy/sema-up/lib/shell-access.sh +1 -1
- package/deploy/sema-up/preflight.sh +4 -4
- package/deploy/sema-up/sema-up.sh +33 -33
- package/deploy/sema-up/smoke.sh +3 -3
- package/deploy/sema-up/stack/compose.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +26 -3
- package/dist/approval-card.d.ts +20 -4
- package/dist/approval-content-kind.d.ts +38 -22
- package/dist/approval-content-kind.js +20 -4
- package/dist/audit.js +3 -0
- package/dist/bench/s1/arms.d.ts +9 -37
- package/dist/bench/s1/arms.js +4 -61
- package/dist/bench/s1/live-deps.d.ts +4 -10
- package/dist/bench/s1/live-deps.js +1 -222
- package/dist/bench/s1/oracle.d.ts +1 -1
- package/dist/bench/s1/reviewer.d.ts +1 -1
- package/dist/bench/s1/row.d.ts +5 -7
- package/dist/bench/s1/row.js +0 -2
- package/dist/bench/s1/run-firm.d.ts +2 -2
- package/dist/bench/s1/run-firm.js +1 -4
- package/dist/bench/s1/runner-ctx.d.ts +17 -29
- package/dist/bench/s1/runner-ctx.js +3 -7
- package/dist/bench/s1/tasks.d.ts +9 -46
- package/dist/bench/s1/tasks.js +4 -63
- package/dist/boot/execution-env.d.ts +38 -26
- package/dist/boot/execution-env.js +87 -41
- package/dist/boot/leader.d.ts +2 -1
- package/dist/boot/memory-boundary.d.ts +1 -1
- package/dist/boot/memory-consolidation.d.ts +37 -3
- package/dist/boot/memory-consolidation.js +18 -2
- package/dist/boot/parked-revive-gate.d.ts +0 -3
- package/dist/boot/parked-revive-gate.js +2 -2
- package/dist/boot/resolve-spec.d.ts +0 -2
- package/dist/boot/resolve-spec.js +12 -17
- package/dist/boot/retention-lane.d.ts +12 -1
- package/dist/boot/retention-lane.js +2 -0
- package/dist/boot/runner-deps.d.ts +7 -6
- package/dist/boot/runner-deps.js +5 -16
- package/dist/boot/session-faces.d.ts +7 -2
- package/dist/boot/session-faces.js +37 -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 +2 -2
- package/dist/boot/stage-05-execution-env.js +7 -4
- package/dist/boot/stage-06-runners.d.ts +2 -3
- package/dist/boot/stage-06-runners.js +13 -9
- package/dist/boot/stage-07-capability-layer.d.ts +2 -3
- package/dist/boot/stage-07-capability-layer.js +3 -4
- package/dist/boot/stage-08-reapers.d.ts +2 -3
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -3
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.d.ts +2 -3
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +4 -3
- package/dist/boot/web-admin.d.ts +5 -0
- package/dist/boot/web-admin.js +28 -14
- package/dist/brain.d.ts +2 -2
- package/dist/brain.js +1 -1
- package/dist/capabilities/builtin-tools.d.ts +2 -1
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/hands-lane.d.ts +9 -6
- package/dist/capabilities/hands-lane.js +2 -2
- package/dist/capabilities/memory-notice.d.ts +27 -22
- package/dist/capabilities/memory-notice.js +15 -17
- package/dist/capabilities/scenarios.d.ts +9 -14
- package/dist/capabilities/scenarios.js +5 -50
- package/dist/capability-facts.d.ts +22 -11
- package/dist/capability-facts.js +13 -6
- package/dist/config-catalog.js +14 -5
- package/dist/config-provider.d.ts +5 -1
- package/dist/config-provider.js +23 -4
- package/dist/config-types.d.ts +19 -2
- package/dist/config.js +41 -6
- package/dist/deployment-governance.d.ts +19 -21
- package/dist/deployment-governance.js +7 -8
- package/dist/env-name-allowlist-knobs.d.ts +5 -5
- package/dist/env-name-allowlist-knobs.js +1 -0
- package/dist/execution-lane-caps.d.ts +12 -0
- package/dist/execution-lane-caps.js +18 -4
- package/dist/fleet/subagent-tail-bus.js +15 -5
- package/dist/hooks/hook-runner.d.ts +84 -30
- package/dist/hooks/hook-runner.js +74 -41
- package/dist/http/admission.js +17 -10
- package/dist/http/durable-run-leg.d.ts +5 -4
- package/dist/http/durable-run-leg.js +2 -4
- package/dist/http/idempotency.d.ts +28 -6
- package/dist/http/idempotency.js +10 -2
- package/dist/http/principal-gate.d.ts +14 -0
- package/dist/http/principal-gate.js +7 -0
- package/dist/http/request-guards.d.ts +2 -1
- package/dist/http/request-guards.js +14 -14
- package/dist/http/resume-legs.d.ts +2 -2
- package/dist/http/resume-legs.js +20 -10
- package/dist/http/route-ctx.d.ts +15 -7
- package/dist/http/routes/a2a-serve.js +11 -7
- package/dist/http/routes/approvals-assistant.js +18 -3
- package/dist/http/routes/background.d.ts +1 -1
- package/dist/http/routes/background.js +1 -1
- package/dist/http/routes/capabilities.js +10 -2
- package/dist/http/routes/mcp-reconnect.js +1 -1
- package/dist/http/routes/runs.d.ts +4 -3
- package/dist/http/routes/runs.js +35 -14
- package/dist/http/routes/session-sync.js +14 -11
- package/dist/http/routes/sessions.js +9 -9
- package/dist/http/routes/side-query.js +6 -19
- package/dist/http/routes/tasks.js +29 -19
- package/dist/http/routes/workflows.js +8 -0
- package/dist/http/server.d.ts +4 -0
- package/dist/http/server.js +3 -5
- package/dist/http/submit-replay.d.ts +15 -8
- package/dist/http/submit-replay.js +6 -0
- package/dist/http/wire-types.d.ts +7 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal-run-env.d.ts +30 -0
- package/dist/internal-run-env.js +11 -0
- package/dist/leader/wire.js +3 -2
- package/dist/lsp/manager.js +1 -1
- package/dist/memory-scope.d.ts +19 -7
- package/dist/memory-scope.js +25 -8
- package/dist/observability/fail-open.d.ts +31 -3
- package/dist/observability/fail-open.js +31 -3
- package/dist/observability/metrics.d.ts +0 -1
- package/dist/observability/metrics.js +1 -4
- package/dist/observability/run-terminal.js +72 -4
- package/dist/permission-rule-vocab.d.ts +3 -2
- package/dist/permission-rule-vocab.js +1 -1
- package/dist/plan-cache-probe.d.ts +3 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
- package/dist/plugins/caching-session-store.d.ts +42 -22
- package/dist/plugins/caching-session-store.js +19 -9
- package/dist/plugins/checkpoint-store-sql.d.ts +42 -28
- package/dist/plugins/checkpoint-store-sql.js +57 -11
- package/dist/plugins/file-run-store.d.ts +25 -21
- package/dist/plugins/file-run-store.js +70 -34
- package/dist/plugins/k8s-exec-protocol.js +3 -0
- package/dist/plugins/local-checkpoint-store.d.ts +8 -5
- package/dist/plugins/local-checkpoint-store.js +5 -15
- package/dist/plugins/local-session-store.d.ts +66 -7
- package/dist/plugins/local-session-store.js +168 -101
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +1 -1
- package/dist/plugins/memory-run-store.d.ts +19 -3
- package/dist/plugins/memory-run-store.js +38 -3
- package/dist/plugins/one-time-migrations.d.ts +165 -39
- package/dist/plugins/one-time-migrations.js +51 -144
- package/dist/plugins/pg-pool.js +11 -1
- package/dist/plugins/pg-session-storage.d.ts +2 -0
- package/dist/plugins/pg-session-storage.js +9 -5
- package/dist/plugins/remote-env-adb.d.ts +3 -0
- package/dist/plugins/remote-env-adb.js +1 -0
- package/dist/plugins/remote-env-device.d.ts +3 -0
- package/dist/plugins/remote-env-device.js +1 -0
- package/dist/plugins/remote-env-e2b.d.ts +4 -0
- package/dist/plugins/remote-env-e2b.js +2 -1
- package/dist/plugins/remote-env-host.d.ts +23 -1
- package/dist/plugins/remote-env-host.js +20 -2
- package/dist/plugins/remote-env-k8s.d.ts +264 -41
- package/dist/plugins/remote-env-k8s.js +551 -82
- package/dist/plugins/remote-env-local-docker.d.ts +4 -0
- package/dist/plugins/remote-env-local-docker.js +2 -1
- package/dist/plugins/remote-env-ssh.d.ts +3 -0
- package/dist/plugins/remote-env-ssh.js +1 -0
- package/dist/plugins/retention-store-sql.d.ts +4 -1
- package/dist/plugins/retention-store-sql.js +11 -9
- package/dist/plugins/run-store-sql.d.ts +44 -6
- package/dist/plugins/run-store-sql.js +92 -27
- package/dist/plugins/session-capture-record-store-sql.d.ts +2 -3
- package/dist/plugins/session-store.d.ts +4 -3
- package/dist/plugins/session-store.js +1 -1
- package/dist/plugins/sql-driver.d.ts +2 -1
- package/dist/plugins/store-backend.d.ts +12 -5
- package/dist/plugins/store-backend.js +13 -4
- package/dist/plugins/store-contracts.d.ts +20 -3
- package/dist/plugins/store-open-wait.js +3 -0
- package/dist/plugins/tidb-pool.js +15 -3
- package/dist/plugins/tidb-session-storage.js +3 -2
- package/dist/plugins/tidb-session-store.d.ts +2 -0
- package/dist/plugins/tidb-session-store.js +6 -2
- package/dist/request-key-closure.d.ts +32 -7
- package/dist/request-key-closure.js +25 -9
- package/dist/router/route-orchestration.d.ts +3 -11
- package/dist/router/route-orchestration.js +0 -4
- package/dist/rule-entry-list.js +0 -4
- package/dist/rules-consent.js +3 -0
- package/dist/run-acceptance.d.ts +51 -0
- package/dist/run-acceptance.js +27 -0
- package/dist/run-local.d.ts +5 -4
- package/dist/run-local.js +23 -17
- package/dist/runs.d.ts +7 -11
- package/dist/runs.js +6 -16
- package/dist/sandbox-egress-policy.d.ts +61 -0
- package/dist/sandbox-egress-policy.js +235 -0
- package/dist/security.d.ts +7 -0
- package/dist/security.js +5 -0
- package/dist/server-secret-env.d.ts +10 -4
- package/dist/server-secret-env.js +4 -1
- package/dist/session-error-wire.d.ts +2 -2
- package/dist/session-incarnation.d.ts +46 -2
- package/dist/session-incarnation.js +14 -3
- package/dist/session-sync-kernel.d.ts +10 -3
- package/dist/session-titler.d.ts +5 -3
- package/dist/spec-fields.d.ts +24 -8
- package/dist/spec-fields.js +21 -2
- package/dist/task-workflow.d.ts +6 -4
- package/dist/task-workflow.js +3 -2
- package/dist/tool-approval.d.ts +78 -6
- package/dist/tool-approval.js +7 -3
- package/dist/trace/core-keyset-guard.d.ts +6 -5
- package/dist/trace/engine-notice-wire.d.ts +26 -1
- package/dist/trace/engine-notice-wire.js +7 -2
- package/dist/trace/ledger-events.d.ts +9 -3
- package/dist/trace/ledger-sink.d.ts +7 -0
- package/dist/trace/ledger-sink.js +14 -4
- package/dist/trace/project.d.ts +57 -11
- package/dist/trace/project.js +54 -8
- package/dist/trace/self-minted-wire.d.ts +94 -2
- package/dist/trace/self-minted-wire.js +28 -0
- package/dist/trace/tool-output-media.d.ts +26 -0
- package/dist/trace/tool-output-media.js +57 -0
- package/dist/trace/wire-projection-faces.d.ts +7 -1
- package/dist/trace/wire-projection-faces.js +2 -1
- package/dist/web-admin-client/apply-effective.d.ts +13 -7
- package/dist/web-admin-client/apply-effective.js +11 -26
- package/dist/web-admin-client/apply-ledger.d.ts +2 -2
- package/dist/web-admin-client/candidate-gate.d.ts +39 -4
- package/dist/web-admin-client/candidate-gate.js +9 -2
- package/dist/web-admin-client/facade.d.ts +2 -2
- package/dist/web-admin-client/http-client.d.ts +3 -1
- package/dist/web-admin-client/http-client.js +4 -3
- package/dist/web-admin-client/read-warnings.d.ts +33 -5
- package/dist/web-admin-client/read-warnings.js +41 -6
- package/dist/web-admin-client/types.d.ts +11 -5
- package/dist/workspace-cache.d.ts +45 -0
- package/dist/workspace-cache.js +30 -0
- package/dist/workspace-space-commit.d.ts +116 -0
- package/dist/workspace-space-commit.js +117 -0
- package/dist/workspace-space.d.ts +75 -9
- package/dist/workspace-space.js +52 -1
- package/package.json +4 -4
- package/dist/boot/session-incarnation-seat.d.ts +0 -41
- package/dist/boot/session-incarnation-seat.js +0 -69
- package/dist/capabilities/code-review-council.d.ts +0 -49
- package/dist/capabilities/code-review-council.js +0 -111
- package/dist/capabilities/collab-wire.d.ts +0 -36
- package/dist/capabilities/collab-wire.js +0 -2
- package/dist/capabilities/collab-workflows.d.ts +0 -47
- package/dist/capabilities/collab-workflows.js +0 -90
- package/dist/capabilities/prompts/code-review.d.ts +0 -25
- package/dist/capabilities/prompts/code-review.js +0 -39
- package/dist/capabilities/prompts/team.d.ts +0 -10
- package/dist/capabilities/prompts/team.js +0 -6
- package/dist/capabilities/team.d.ts +0 -113
- package/dist/capabilities/team.js +0 -70
|
@@ -9,7 +9,7 @@ import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.
|
|
|
9
9
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
|
|
10
10
|
import { readWritableSessionPermissionRules, describeSessionPermissionRulesProblem } from "../../session-policy-wire.js";
|
|
11
11
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders } from "../send.js";
|
|
12
|
-
import { gatedPrincipal, explicitOperatorOk,
|
|
12
|
+
import { gatedPrincipal, explicitOperatorOk, sessionOwnerReadable, sendPrincipalRefusal, sendUnauthorized } from "../principal-gate.js";
|
|
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";
|
|
@@ -54,7 +54,7 @@ async function readLastLegMcp(deps, sessionId, principal) {
|
|
|
54
54
|
const latest = await runStore.latestRunForSession(sessionId);
|
|
55
55
|
if (latest === undefined)
|
|
56
56
|
return undefined;
|
|
57
|
-
if (!
|
|
57
|
+
if (!sessionOwnerReadable(principal, latest.owner, deps.config) && !explicitOperatorOk(principal, deps.config.operatorPrincipals))
|
|
58
58
|
return undefined;
|
|
59
59
|
const events = await runStore.getEvents(latest.taskId, 0);
|
|
60
60
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
@@ -70,7 +70,7 @@ async function readLastLegMcp(deps, sessionId, principal) {
|
|
|
70
70
|
}
|
|
71
71
|
export async function handleSessions(req, res, match, ctx) {
|
|
72
72
|
const { deps } = ctx;
|
|
73
|
-
const { readJson, safeDecode,
|
|
73
|
+
const { readJson, safeDecode, sessionOwnerOk, sessionOwnerScopeForWrite } = ctx.helpers;
|
|
74
74
|
const readOwnedSessionView = async (sessionAudit, sessionId, readOpts) => {
|
|
75
75
|
const ownerOf = deps.sessionStorage?.ownerOf?.bind(deps.sessionStorage);
|
|
76
76
|
if (ownerOf !== undefined) {
|
|
@@ -79,7 +79,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
79
79
|
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
80
80
|
return undefined;
|
|
81
81
|
}
|
|
82
|
-
if (!
|
|
82
|
+
if (!sessionOwnerOk(req, res, owner))
|
|
83
83
|
return undefined;
|
|
84
84
|
}
|
|
85
85
|
let audit;
|
|
@@ -95,7 +95,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
95
95
|
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
96
96
|
return undefined;
|
|
97
97
|
}
|
|
98
|
-
if (!
|
|
98
|
+
if (!sessionOwnerOk(req, res, audit.owner))
|
|
99
99
|
return undefined;
|
|
100
100
|
return audit;
|
|
101
101
|
};
|
|
@@ -265,7 +265,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
265
265
|
return;
|
|
266
266
|
}
|
|
267
267
|
owner = head.owner;
|
|
268
|
-
if (!
|
|
268
|
+
if (!sessionOwnerOk(req, res, owner ?? null))
|
|
269
269
|
return;
|
|
270
270
|
leafId = head.leafId;
|
|
271
271
|
}
|
|
@@ -275,7 +275,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
275
275
|
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
276
276
|
return;
|
|
277
277
|
}
|
|
278
|
-
if (!
|
|
278
|
+
if (!sessionOwnerOk(req, res, owner ?? null))
|
|
279
279
|
return;
|
|
280
280
|
leafId = await getLeafId(headSession);
|
|
281
281
|
}
|
|
@@ -359,7 +359,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
359
359
|
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
360
360
|
return;
|
|
361
361
|
}
|
|
362
|
-
if (!
|
|
362
|
+
if (!sessionOwnerOk(req, res, owner ?? null)) {
|
|
363
363
|
cleanup();
|
|
364
364
|
return;
|
|
365
365
|
}
|
|
@@ -666,7 +666,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
666
666
|
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
667
667
|
return;
|
|
668
668
|
}
|
|
669
|
-
if (!
|
|
669
|
+
if (!sessionOwnerOk(req, res, face.owner))
|
|
670
670
|
return;
|
|
671
671
|
const read = readSessionRowPatch(await readJson(req));
|
|
672
672
|
if ("problem" in read) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expandTiers, isThinkingLevel } from "@sema-agent/core";
|
|
1
|
+
import { MODEL_ROLES, expandTiers, isThinkingLevel } from "@sema-agent/core";
|
|
2
2
|
import { HttpError } from "../../security.js";
|
|
3
3
|
import { isAtMentionable, matchCatalogModel, refusedCatalogEntry, selectableCatalog } from "../../model-select.js";
|
|
4
4
|
import { modelUnavailableMessage } from "../../model-entry-refusal.js";
|
|
@@ -12,22 +12,8 @@ export function isCorePreflightValidationError(message) {
|
|
|
12
12
|
return CORE_PREFLIGHT_VALIDATION_PREFIXES.some((prefix) => message.startsWith(prefix));
|
|
13
13
|
}
|
|
14
14
|
export const CORE_PREFLIGHT_VALIDATION_ANCHORS = CORE_PREFLIGHT_VALIDATION_PREFIXES;
|
|
15
|
-
const MODEL_ROLE_TABLE = {
|
|
16
|
-
default: true,
|
|
17
|
-
summarize: true,
|
|
18
|
-
subagent: true,
|
|
19
|
-
team: true,
|
|
20
|
-
synthesize: true,
|
|
21
|
-
advisor: true,
|
|
22
|
-
verifier: true,
|
|
23
|
-
classifier: true,
|
|
24
|
-
consolidate: true,
|
|
25
|
-
};
|
|
26
15
|
function isModelRole(value) {
|
|
27
|
-
return typeof value === "string" &&
|
|
28
|
-
}
|
|
29
|
-
function passthroughModelRole(value) {
|
|
30
|
-
return value;
|
|
16
|
+
return typeof value === "string" && MODEL_ROLES.includes(value);
|
|
31
17
|
}
|
|
32
18
|
function shapeSideQueryEcho(result) {
|
|
33
19
|
if (result.responseModel === undefined || admitProviderEcho(result.responseModel, "side-query.responseModel"))
|
|
@@ -76,8 +62,9 @@ export async function handleSideQuery(req, res, match, ctx) {
|
|
|
76
62
|
sendError(res, 400, "request.field_invalid", "invalid thinking level");
|
|
77
63
|
return;
|
|
78
64
|
}
|
|
79
|
-
if (
|
|
80
|
-
|
|
65
|
+
if (body.modelRole !== undefined && !isModelRole(body.modelRole)) {
|
|
66
|
+
const shown = (typeof body.modelRole === "string" ? JSON.stringify(body.modelRole.slice(0, 80)) : `of type ${body.modelRole === null ? "null" : Array.isArray(body.modelRole) ? "array" : typeof body.modelRole}`);
|
|
67
|
+
sendError(res, 400, "request.field_invalid", `invalid modelRole ${shown} — must be a string, one of ${MODEL_ROLES.join(" / ")} (the engine's role vocabulary); a value outside it is refused before any model call`);
|
|
81
68
|
return;
|
|
82
69
|
}
|
|
83
70
|
if (typeof body.model === "string" && body.model.length > 0) {
|
|
@@ -98,7 +85,7 @@ export async function handleSideQuery(req, res, match, ctx) {
|
|
|
98
85
|
const spec = {
|
|
99
86
|
messages: body.messages,
|
|
100
87
|
...(typeof body.model === "string" ? { model: body.model } : {}),
|
|
101
|
-
...(
|
|
88
|
+
...(isModelRole(body.modelRole) ? { modelRole: body.modelRole } : {}),
|
|
102
89
|
...(isThinkingLevel(body.thinking) ? { thinking: body.thinking } : {}),
|
|
103
90
|
...(typeof body.systemPrompt === "string" ? { systemPrompt: body.systemPrompt } : {}),
|
|
104
91
|
...(Array.isArray(body.tools) ? { tools: body.tools } : {}),
|
|
@@ -13,9 +13,10 @@ import { clearTurnActivity, readTurnActivityMs, recordTurnActivity } from "../..
|
|
|
13
13
|
import { buildCancelledLedgerRow, createRunAbort, mintCancelledResult } from "../../run-cancel-context.js";
|
|
14
14
|
import { recordResourceWindow } from "../../resource-window.js";
|
|
15
15
|
import { redactSecrets } from "../../trace/redact.js";
|
|
16
|
-
import {
|
|
16
|
+
import { liveToolEndForBacklog } from "../../trace/tool-output-media.js";
|
|
17
|
+
import { contextUsageEventData, turnEndEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestFaceEventData, toolRosterDeltaEventData, toolProgressEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, textEndEventData, liveIdentityFields } from "../../trace/project.js";
|
|
17
18
|
import { cascadeConfig, runMeta } from "../run-meta.js";
|
|
18
|
-
import { idemCallerOf,
|
|
19
|
+
import { idemCallerOf, inflightKeyOf, submissionIdemOf } from "../idempotency.js";
|
|
19
20
|
import { replayOfExistingRun } from "../submit-replay.js";
|
|
20
21
|
import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
21
22
|
import { collectSsePreamble } from "../sse-log.js";
|
|
@@ -37,7 +38,8 @@ const DISCONNECT_PARK_GRACE_MS = 10_000;
|
|
|
37
38
|
function sseData(res, payload) {
|
|
38
39
|
const anchor = payload.eventId;
|
|
39
40
|
const idLine = typeof anchor === "string" && anchor.length > 0 ? `id: ${anchor}\n` : "";
|
|
40
|
-
res.
|
|
41
|
+
const frame = payload.type === "tool_end" ? liveToolEndForBacklog(payload, res.writableLength) : payload;
|
|
42
|
+
res.write(`${idLine}data: ${JSON.stringify(frame)}\n\n`);
|
|
41
43
|
}
|
|
42
44
|
async function mintSubmitExitResult(result, checkpoints) {
|
|
43
45
|
return redactTerminalResult(sealResultForExit(await graftParkToolCallId(result, checkpoints)));
|
|
@@ -74,7 +76,7 @@ const MULTI_ATTEMPT_NO_EVENT_CHANNEL_NOTICE = "verify / cascade ran with their n
|
|
|
74
76
|
export async function handleTasks(req, res, match, ctx) {
|
|
75
77
|
const { deps } = ctx;
|
|
76
78
|
const { idemCache, inflightRuns, steerableRuns, counters } = ctx.registry;
|
|
77
|
-
const { rateLimited, quotaExceeded, leaseDenied, usageWindowDenied } = ctx.helpers;
|
|
79
|
+
const { readJson, rateLimited, quotaExceeded, leaseDenied, usageWindowDenied } = ctx.helpers;
|
|
78
80
|
const { prepareSpec, finalizeTaskResult } = ctx.legs;
|
|
79
81
|
const reqState = ctx.req;
|
|
80
82
|
const source = ctx.req.source;
|
|
@@ -97,12 +99,13 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
97
99
|
{
|
|
98
100
|
const rawIdem = headerStr(req.headers["idempotency-key"]);
|
|
99
101
|
const submitter = gatedPrincipal(req, deps.config);
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
+
const keyedBody = rawIdem ? await readJson(req) : undefined;
|
|
103
|
+
const idem = rawIdem ? submissionIdemOf(rawIdem, idemCallerOf(req, ctx), keyedBody) : undefined;
|
|
104
|
+
const inflightKey = idem ? inflightKeyOf(idem, "sync") : undefined;
|
|
102
105
|
const cached = inflightKey ? idemCache.peek(inflightKey) : undefined;
|
|
103
|
-
const keyedRow =
|
|
106
|
+
const keyedRow = idem && !cached && deps.runStore ? await deps.runStore.getRunByIdem(idem.key) : undefined;
|
|
104
107
|
if (keyedRow && deps.runStore) {
|
|
105
|
-
const replay = await replayOfExistingRun(keyedRow, { principal: submitter }, "sync", deps, deps.runStore);
|
|
108
|
+
const replay = await replayOfExistingRun(keyedRow, { principal: submitter, idem }, "sync", deps, deps.runStore);
|
|
106
109
|
if (liveStream && replay.status === 200) {
|
|
107
110
|
sseHeaders(res, { "x-task-id": keyedRow.taskId });
|
|
108
111
|
writeMetaFrame(res, keyedRow.taskId, keyedRow.sessionId);
|
|
@@ -141,7 +144,7 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
141
144
|
}
|
|
142
145
|
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
|
|
143
146
|
return;
|
|
144
|
-
const prepared = await prepareSpec(req, res,
|
|
147
|
+
const prepared = await prepareSpec(req, res, keyedBody, undefined, { liveStream });
|
|
145
148
|
if (!prepared)
|
|
146
149
|
return;
|
|
147
150
|
const principal = prepared.auth?.principal;
|
|
@@ -257,15 +260,13 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
257
260
|
};
|
|
258
261
|
if (deps.runStore && prepared.spec.sessionId) {
|
|
259
262
|
const tid = earlyDurableTid ?? uuidv7();
|
|
260
|
-
const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", { ...runMeta(prepared, source),
|
|
263
|
+
const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", { ...runMeta(prepared, source), idem: idem ?? null });
|
|
261
264
|
if (created.ok)
|
|
262
265
|
clearTurnActivity(tid);
|
|
263
266
|
if (created.ok)
|
|
264
267
|
recordResourceWindow(tid, prepared.spec.resourceSuspend);
|
|
265
|
-
if (created.ok)
|
|
266
|
-
deps.sessionTitler?.maybeTitle(prepared.spec.sessionId, prepared.spec.objective, typeof prepared.spec.model === "string" ? prepared.spec.model : prepared.spec.model?.id);
|
|
267
268
|
if (created.existing) {
|
|
268
|
-
const replay = await replayOfExistingRun(created.existing, { principal: submitter }, "sync", deps, deps.runStore);
|
|
269
|
+
const replay = await replayOfExistingRun(created.existing, { principal: submitter, idem }, "sync", deps, deps.runStore);
|
|
269
270
|
sseData(res, { type: "done", result: doneResultOfOutcome(replay), replay: true });
|
|
270
271
|
return replay;
|
|
271
272
|
}
|
|
@@ -389,6 +390,9 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
389
390
|
if (t === "task_progress") {
|
|
390
391
|
sseData(res, { type: "task_progress", ...taskProgressEventData(e) });
|
|
391
392
|
}
|
|
393
|
+
else if (t === "task_started") {
|
|
394
|
+
sseData(res, { type: "task_started", ...taskStartedEventData(e) });
|
|
395
|
+
}
|
|
392
396
|
else if (t === "tool_start") {
|
|
393
397
|
const tsv = e;
|
|
394
398
|
sseData(res, { type: "tool_start", ...toolStartEventData(tsv) });
|
|
@@ -457,6 +461,7 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
457
461
|
userMsgEntryId = ev.entryId;
|
|
458
462
|
const causeAtDone = ev.type === "done" ? ac.cause() : undefined;
|
|
459
463
|
await deps.userInputLedger?.observeLegEvent(liveStream, ev);
|
|
464
|
+
prepared.acceptance.observe(ev);
|
|
460
465
|
if (ev.type === "done") {
|
|
461
466
|
finalResult = await mintSubmitExitResult(ev.result, deps.checkpointStore);
|
|
462
467
|
if (durableTaskId && finalResult)
|
|
@@ -464,7 +469,7 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
464
469
|
if (causeAtDone !== undefined && finalResult !== undefined) {
|
|
465
470
|
finalResult = mintCancelledResult({ taskId: durableTaskId ?? "", sessionId: prepared.spec.sessionId ?? "", startedAtMs: streamLegStartedAt, message: "cancelled by user", cause: causeAtDone, engineResult: finalResult });
|
|
466
471
|
}
|
|
467
|
-
finalizeTaskResult(ev.result, principal, prepared.spec.objective
|
|
472
|
+
finalizeTaskResult(ev.result, principal, prepared.spec.objective);
|
|
468
473
|
noteRunOutcome(finalResult);
|
|
469
474
|
putRewindAnchor();
|
|
470
475
|
settleFleet(outwardPlane(finalResult.terminal).status, fleetRunResiduals(finalResult));
|
|
@@ -516,6 +521,9 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
516
521
|
else if (ev.type === "task_progress") {
|
|
517
522
|
sseData(res, { type: "task_progress", ...taskProgressEventData(ev) });
|
|
518
523
|
}
|
|
524
|
+
else if (ev.type === "task_started") {
|
|
525
|
+
sseData(res, { type: "task_started", ...taskStartedEventData(ev) });
|
|
526
|
+
}
|
|
519
527
|
else if (ev.type === "compacted") {
|
|
520
528
|
sseData(res, { type: "compacted", ...compactedEventData(ev) });
|
|
521
529
|
}
|
|
@@ -830,19 +838,18 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
830
838
|
let durableHeartbeat;
|
|
831
839
|
if (deps.runStore && prepared.spec.sessionId) {
|
|
832
840
|
const tid = uuidv7();
|
|
833
|
-
const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", { ...runMeta(prepared, source),
|
|
841
|
+
const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", { ...runMeta(prepared, source), idem: idem ?? null });
|
|
834
842
|
if (created.ok)
|
|
835
843
|
clearTurnActivity(tid);
|
|
836
844
|
if (created.ok)
|
|
837
845
|
recordResourceWindow(tid, prepared.spec.resourceSuspend);
|
|
838
846
|
if (created.existing)
|
|
839
|
-
return await replayOfExistingRun(created.existing, { principal: submitter }, "sync", deps, deps.runStore);
|
|
847
|
+
return await replayOfExistingRun(created.existing, { principal: submitter, idem }, "sync", deps, deps.runStore);
|
|
840
848
|
if (!created.ok)
|
|
841
849
|
return { status: 409, body: await buildActiveRunConflict({ runStore: deps.runStore, checkpointStore: deps.checkpointStore, governance: deps.config, runStaleSec: deps.config.runStaleSec, turnActivity: readTurnActivityMs }, prepared.spec.sessionId, created.activeTaskId) };
|
|
842
850
|
const placementReject = await commitPlacement(deps, deps.runStore, tid, prepared.bindingIntent);
|
|
843
851
|
if (placementReject)
|
|
844
852
|
return { status: placementReject.status, body: devicePlacementErrorBody(placementReject) };
|
|
845
|
-
deps.sessionTitler?.maybeTitle(prepared.spec.sessionId, prepared.spec.objective, typeof prepared.spec.model === "string" ? prepared.spec.model : prepared.spec.model?.id);
|
|
846
853
|
durableTaskId = tid;
|
|
847
854
|
}
|
|
848
855
|
commitSessionInheritEnv(deps, prepared.spec.sessionId, prepared.body);
|
|
@@ -885,8 +892,10 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
885
892
|
const cascadeLeg = () => runCascade(prepared.runner, specWithSignal, cascadeConfig(deps.config.cascadeLadder, prepared.spec.limits?.maxCostUsd));
|
|
886
893
|
const plainLeg = async () => {
|
|
887
894
|
const stream = prepared.runner.runTaskStream(specWithSignal);
|
|
888
|
-
for await (const ev of stream)
|
|
895
|
+
for await (const ev of stream) {
|
|
889
896
|
observeSyncLegEvent(ev);
|
|
897
|
+
prepared.acceptance.observe(ev);
|
|
898
|
+
}
|
|
890
899
|
return stream.result();
|
|
891
900
|
};
|
|
892
901
|
result = await instructionLeg.run(prepared.spec.sessionId, () => withPrincipal(principal, () => prepared.verify ? verifyLeg(prepared.verify) : prepared.cascade ? cascadeLeg() : plainLeg()));
|
|
@@ -933,7 +942,8 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
933
942
|
}
|
|
934
943
|
if (durableTaskId && deps.runStore)
|
|
935
944
|
await deps.runStore.setTerminal(durableTaskId, syncPlane.status, exitResult, syncPlane.errorMessage ?? null);
|
|
936
|
-
|
|
945
|
+
prepared.acceptance.settle(result);
|
|
946
|
+
finalizeTaskResult(result, principal, prepared.spec.objective);
|
|
937
947
|
noteRunOutcome(result);
|
|
938
948
|
return { status: 200, body: withSyncSubmitKeys(buildSyncSubmitBody(exitResult)) };
|
|
939
949
|
};
|
|
@@ -13,6 +13,7 @@ import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
|
13
13
|
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
14
14
|
import { assertNever } from "../route-ctx.js";
|
|
15
15
|
import * as facts from "../../capability-facts.js";
|
|
16
|
+
import { steerBodyKeyIssue } from "../../request-key-closure.js";
|
|
16
17
|
export async function handleWorkflows(req, res, match, ctx) {
|
|
17
18
|
const { deps } = ctx;
|
|
18
19
|
const { runSessionAcceptOk } = ctx.helpers;
|
|
@@ -188,6 +189,13 @@ export async function handleWorkflowAgentSteer(req, res, match, ctx) {
|
|
|
188
189
|
sendError(res, 400, "request.body_shape", "body must be { content: string (non-empty) }");
|
|
189
190
|
return;
|
|
190
191
|
}
|
|
192
|
+
{
|
|
193
|
+
const keyIssue = steerBodyKeyIssue(body, "content");
|
|
194
|
+
if (keyIssue) {
|
|
195
|
+
sendError(res, 400, "request.body_shape", keyIssue.message, { unknownKeys: keyIssue.unknownKeys, unsupportedKeys: keyIssue.unsupportedKeys });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
191
199
|
if (body.content.length > STEER_IN_MAX_REQUEST_CHARS) {
|
|
192
200
|
sendError(res, 413, "steer.content_too_large", `content exceeds ${STEER_IN_MAX_REQUEST_CHARS} characters (got ${body.content.length}); note only the first ${STEER_IN_MAX_CHARS} are delivered anyway`);
|
|
193
201
|
return;
|
package/dist/http/server.d.ts
CHANGED
|
@@ -718,6 +718,10 @@ export interface ServiceDeploymentDeps {
|
|
|
718
718
|
* (`boot/stage-10-http-server.ts` → `modelRefusals: () => deployment.modelRefusalsState?.()`)—— 一处铸、两出口。缺席 / 返回 undefined =
|
|
719
719
|
* 没有可报的拒或没有配置管道 ⇒ `/health` 形状逐字不变(additive)。 */
|
|
720
720
|
modelRefusalsState?: () => import("@sema-agent/settings-schema").ModelRefusalsSignal | undefined;
|
|
721
|
+
/** S-1457:`GET /v1/models.candidateRejected` 的取值口 —— 最近一次配置候选被判官整拒的结论(`boot/stage-10-http-server.ts` →
|
|
722
|
+
* `candidateRejectionState: () => webAdmin.candidateRejectionState()`;座 = web-admin 的 `candidateRejection`,`config_candidate_rejected` 日志行的去重键读同一份)。
|
|
723
|
+
* present-iff 当前生效配置是因候选被拒而回落的那一份;缺席 / 返回 undefined = 没有被拒的候选或没有配置管道 ⇒ `GET /v1/models` 不出该键(additive)。 */
|
|
724
|
+
candidateRejectionState?: () => import("../web-admin-client/candidate-gate.js").CandidateRejection | undefined;
|
|
721
725
|
/** `/health.configHandoff`(只上 `/health`):在场 ⇔ 部署声明了耐久接力 ∧ 应用着的那一代没被确认(web-admin 接力账的 `configHandoffState()`;合成律
|
|
722
726
|
* `fleet/engine-signal.ts` 的 `buildConfigHandoff`)。缺席 / 返回 undefined = 有接力、没有中心或本地腿 ⇒ 键缺席。 */
|
|
723
727
|
configHandoffState?: () => import("../fleet/engine-signal.js").ConfigHandoff | undefined;
|
package/dist/http/server.js
CHANGED
|
@@ -5,7 +5,6 @@ import { validatePendingSteer } from "@sema-agent/core";
|
|
|
5
5
|
import {} from "../web-admin-client/facade.js";
|
|
6
6
|
import { HttpError } from "../security.js";
|
|
7
7
|
import {} from "../memory-sync.js";
|
|
8
|
-
import { evictIfConflict } from "../runs.js";
|
|
9
8
|
import {} from "../orchestration/workflow-agent-steer.js";
|
|
10
9
|
import {} from "../orchestration/workflow-completion-inbox.js";
|
|
11
10
|
import {} from "../fleet/fleet-bus.js";
|
|
@@ -113,9 +112,9 @@ export function createHttpServer(rawDeps) {
|
|
|
113
112
|
}
|
|
114
113
|
};
|
|
115
114
|
}
|
|
116
|
-
const { quotaExceeded, ownerCostQuotaDenied, ownerFleetLeaseDenied, ownerLeaseDenied, ownerQuotaDenied, leaseDenied, usageWindowDenialFor, usageWindowDenied, rateLimited, safeDecode, isFleetWide, sessionOwnerScope, sessionOwnerScopeForWrite, runOwnerOk, runSessionAcceptOk, readJson, readRawBody } = createRequestGuards({ deps });
|
|
115
|
+
const { quotaExceeded, ownerCostQuotaDenied, ownerFleetLeaseDenied, ownerLeaseDenied, ownerQuotaDenied, leaseDenied, usageWindowDenialFor, usageWindowDenied, rateLimited, safeDecode, isFleetWide, sessionOwnerScope, sessionOwnerScopeForWrite, runOwnerOk, sessionOwnerOk, runSessionAcceptOk, readJson, readRawBody } = createRequestGuards({ deps });
|
|
117
116
|
const { prepareSpec } = createAdmission({ deps, readJson, runnerFor });
|
|
118
|
-
const { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, mayDiscloseRowError, resumePreempted, startFreshHostTurn, resumeWake } = createResumeLegs({ deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor });
|
|
117
|
+
const { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, mayDiscloseRowError, resumePreempted, startFreshHostTurn, resumeWake } = createResumeLegs({ deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor });
|
|
119
118
|
const { denyExpiredApprovals, redeemParkedAsk } = createApprovalSweeps({ deps, resumeCheckpoint, mayDiscloseRowError });
|
|
120
119
|
const { handle } = createDispatch({ deps, noteActivity, routeCtxBase, counters });
|
|
121
120
|
const server = http.createServer((req, res) => {
|
|
@@ -162,10 +161,9 @@ export function createHttpServer(rawDeps) {
|
|
|
162
161
|
deps.metrics?.inc("cascade_total", { outcome: cascadeOutcome });
|
|
163
162
|
deps.instrumentDegenerate?.(result);
|
|
164
163
|
}
|
|
165
|
-
function finalizeTaskResult(result, principal, objective
|
|
164
|
+
function finalizeTaskResult(result, principal, objective) {
|
|
166
165
|
recordTaskResult(result);
|
|
167
166
|
deps.planCacheProbe?.record(principal, objective, result.terminal.kind === "completed");
|
|
168
|
-
evictIfConflict(deps.runner, sessionId, result);
|
|
169
167
|
}
|
|
170
168
|
const isRunLiveOnThisReplica = (taskId) => inflightRuns.has(taskId) || steerableRuns.has(taskId);
|
|
171
169
|
return Object.assign(server, { denyExpiredApprovals, isRunLiveOnThisReplica, redeemParkedAsk });
|
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
* 提交幂等的**唯一回执函数** —— 一把提交钥匙命中了一条既有 run 行之后,这次提交拿到什么。
|
|
3
3
|
*
|
|
4
4
|
* ## 律(一条)
|
|
5
|
-
* 幂等 = 资源行上的唯一键。`task_run` 上有两把:`idem_key`(`Idempotency-Key`,唯一铸法
|
|
6
|
-
* 与 `task_id`(调用方自铸的 `body.taskId`;A2A 由 messageId 派生)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* 幂等 = 同一调用方、同一键、**同一请求**,执法点 = 资源行上的唯一键。`task_run` 上有两把:`idem_key`(`Idempotency-Key`,唯一铸法
|
|
6
|
+
* `idemKeyOf(raw, caller, "run")`)与 `task_id`(调用方自铸的 `body.taskId`;A2A 由 messageId 派生);`idem_key` 旁边记着绑定它的那次
|
|
7
|
+
* 请求的体指纹(`idem_body_fingerprint`,`RunIdemBinding`)。哪一把命中、在哪条提交腿上命中、是请求进门时读行命中还是建行撞唯一键时
|
|
8
|
+
* 观察到,一律经**本函数**作答 —— 提交面不再有第二个判官(进程内缓存只做在飞合流,且只合同一请求,见 `http/idempotency.ts`)。
|
|
9
9
|
*
|
|
10
|
-
* ## 判定(
|
|
10
|
+
* ## 判定(三条,按序;别的一律不放)
|
|
11
11
|
* · 这次提交自带 taskId 而命中的行**不是**那一条(`Idempotency-Key` 指向别的行)⇒ 冲突;
|
|
12
|
-
* · 调用方读不到这一行(属主门 = `runOwnerReadable`,「这个调用方读得到这一行 run 吗」的唯一谓词)⇒
|
|
13
|
-
*
|
|
12
|
+
* · 调用方读不到这一行(属主门 = `runOwnerReadable`,「这个调用方读得到这一行 run 吗」的唯一谓词)⇒ 冲突;
|
|
13
|
+
* 冲突 ⇒ `409 conflict.run_exists`,体里**不回显**那条行的任何标识(不给跨租户预言机)。先于下一条判:读不到的行不比体。
|
|
14
|
+
* · 这次提交带 `Idempotency-Key`(`caller.idem` 在场)、那一行绑的**就是这把键**,而它记着的请求体指纹与这次不同 ⇒
|
|
15
|
+
* `409 conflict.idempotency_payload_mismatch`(同形 `{ error, errorCode }`,不回显键与那一行);**不**回放首发、不新建、那一行不动。
|
|
16
|
+
* 行上没有指纹(列之前绑的键)⇒ 这一只绑上(同键先到者绑,`RunStore.bindIdemBodyFingerprint`)并放行。指纹属于绑定它的那把键:
|
|
17
|
+
* 按 taskId 命中而键是另一把(或行是无键提交建的)⇒ 不比、不写;只凭 `body.taskId` 的提交不带绑定,不比体。
|
|
14
18
|
*
|
|
15
19
|
* ## 回执两形(按提交腿)
|
|
16
20
|
* · `durable`(`POST /v1/runs`、A2A `message/send`)—— `202 { taskId, sessionId, status }`(这条腿首答的既有形);
|
|
@@ -18,7 +22,7 @@
|
|
|
18
22
|
* (读面 = `GET /v1/runs/:id` 的 200 体,同一只投影口 `readRunFace`)。同步腿的首答是引擎结果,而重放时手里只有行;
|
|
19
23
|
* `replay: true` 是这一形的判别位。
|
|
20
24
|
*/
|
|
21
|
-
import type { RunRecord } from "../plugins/store-contracts.js";
|
|
25
|
+
import type { RunIdemBinding, RunRecord } from "../plugins/store-contracts.js";
|
|
22
26
|
import type { RunStore } from "../plugins/store-backend.js";
|
|
23
27
|
import type { ServiceConfig } from "../config-types.js";
|
|
24
28
|
import type { SubmitOutcome } from "./idempotency.js";
|
|
@@ -30,6 +34,9 @@ export interface ReplayCaller {
|
|
|
30
34
|
principal: string | undefined;
|
|
31
35
|
/** 这次提交自带的 taskId(`body.taskId`;A2A 派生值)。缺席 = 只凭 `Idempotency-Key` 命中。 */
|
|
32
36
|
clientTaskId?: string;
|
|
37
|
+
/** 这次提交的账本绑定(键 + 请求体指纹;`RunIdemBinding`)。`undefined` = 这次提交没带 `Idempotency-Key`(只凭 `body.taskId`)⇒ 不比体。
|
|
38
|
+
* 必填位(值可为 `undefined`):每个调用点都得当面说出它有没有绑定,漏传是编译错误,不是静默不比。 */
|
|
39
|
+
idem: RunIdemBinding | undefined;
|
|
33
40
|
}
|
|
34
41
|
/** 一份提交回执是不是 {@link replayOfExistingRun} 铸的**冲突**体(判别位 = 机器码;铸点就在下面,码与它同文件)—— 把回执渲成别的协议形的
|
|
35
42
|
* 调用方(A2A)按它分句,不另抄码。 */
|
|
@@ -8,6 +8,12 @@ export async function replayOfExistingRun(row, caller, face, deps, runStore) {
|
|
|
8
8
|
if (!sameRun || !runOwnerReadable(caller.principal, row.owner, deps.config)) {
|
|
9
9
|
return { status: 409, body: { error: "a run already exists for this submission's taskId or Idempotency-Key", errorCode: "conflict.run_exists" } };
|
|
10
10
|
}
|
|
11
|
+
if (caller.idem !== undefined) {
|
|
12
|
+
const held = await runStore.bindIdemBodyFingerprint(row.taskId, caller.idem);
|
|
13
|
+
if (held !== null && held !== caller.idem.bodyFingerprint) {
|
|
14
|
+
return { status: 409, body: { error: "this Idempotency-Key was already used for a different request body — a retry must resend the same body; send a different request with a new Idempotency-Key", errorCode: "conflict.idempotency_payload_mismatch" } };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
11
17
|
switch (face) {
|
|
12
18
|
case "durable":
|
|
13
19
|
return { status: 202, body: { taskId: row.taskId, sessionId: row.sessionId, status: row.status } };
|
|
@@ -13,7 +13,7 @@ export interface TaskRequestBody {
|
|
|
13
13
|
objective: string;
|
|
14
14
|
sessionId?: string;
|
|
15
15
|
/** Which deployment SCENARIO assembles this task's tools/prompt/skills (the keys of the worker's scenario
|
|
16
|
-
* table — e.g. "default"/"code-review"/"scan"
|
|
16
|
+
* table — e.g. "default"/"code"/"code-review"/"scan"). An INTENT bounded by the deployment's own table AND,
|
|
17
17
|
* on a governed worker, by the principal's center-resolved allowlist: an unknown name is a typed 400
|
|
18
18
|
* `scenario_unknown`, a known-but-not-allowed one a typed 400 `scenario_not_allowed`; absent ⇒ the ruling's
|
|
19
19
|
* assigned scenario, else `DEFAULT_SCENARIO` (consumed at `boot/resolve-spec.ts` via `gateScenarioRequest`). A name
|
|
@@ -94,14 +94,6 @@ export interface TaskRequestBody {
|
|
|
94
94
|
* a rung fails the gate (default = it didn't complete). Default off; mutually exclusive with `verify`;
|
|
95
95
|
* not on /v1/tasks/stream. ⚠️ each rung is a cold re-run — use for read-only / idempotent tasks. */
|
|
96
96
|
cascade?: boolean;
|
|
97
|
-
/** Multi-lens review council (the `code-review` scenario's expensive tier) — `council:true` fans the review out
|
|
98
|
-
* to N parallel lenses + an arbiter instead of the lead reviewing in-line; `debate:true` additionally runs the
|
|
99
|
-
* L2 peer-debate rounds. Read on TWO seams and both matter: the scenario builder mounts `run_council`
|
|
100
|
-
* (`capabilities/scenarios.ts` `codeReview`), and `boot/resolve-spec.ts` treats either flag as an EXPLICIT team
|
|
101
|
-
* declaration — it widens the task's wall-clock tenancy budget (`resolveTaskLimits`) and suppresses the value
|
|
102
|
-
* router's auto-escalation (`explicitTeam`). Only literal `true` counts on both seams. */
|
|
103
|
-
council?: boolean;
|
|
104
|
-
debate?: boolean;
|
|
105
97
|
/** Work-view correlation id (optional): groups the runs of one logical task across both client doors
|
|
106
98
|
* (MCP façade / portal) so a fragmented set of runs reads as one task. Opaque to the engine — only
|
|
107
99
|
* persisted on the run row + filterable via `GET /v1/tasks?jobId=`. ≤64 chars (matches the column). */
|
|
@@ -317,6 +309,12 @@ export interface TaskRequestBody {
|
|
|
317
309
|
/** [ref]② (core 1.314 工具面控制批): roster 真卸载清单(schema 不上 wire,manifest 收窄)。wire 工具名;
|
|
318
310
|
* tighten-only(core 继承不变量 union 进子 spawn)。数组-of-string fail-loud at submit。 */
|
|
319
311
|
excludeTools?: string[];
|
|
312
|
+
/** S-1448(core 7.49.0 #1623,[ref]):引擎的**可用性选择器** —— 这一 run 只用列出的工具(wire 名、别名、`Tool(content)` 整只、
|
|
313
|
+
* `mcp__srv` / `mcp__srv__*` / 工具段通配;`"*"` = 本层不设名单;`[]` = 什么都不给)。宿主**原样透传**进 `TaskSpec.allowTools`:
|
|
314
|
+
* 不算补集、不过滤、不与 `settings.permissions.allow`(免问)混词;没有任何工具答应的名字由引擎开跑前拒(`config.tool_face_control`,同步腿 400)。
|
|
315
|
+
* `excludeTools` 是同一只运算的减项(deny 赢)。值形:字符串数组(受理门 400 `request.field_invalid`,与 `excludeTools` 同一只门);
|
|
316
|
+
* 装着的引擎不认该座 ⇒ 400 `request.field_invalid` 点名所需引擎版本(不静默丢)。无租户门(收窄)。 */
|
|
317
|
+
allowTools?: string[];
|
|
320
318
|
/** [ref]② (core 1.314): [ref] 延迟披露清单(占位上 wire,ToolSearch 激活)——cli「Workflow 默认开
|
|
321
319
|
* 不暴露」的承载位。同 excludeTools 的校验/继承姿势。 */
|
|
322
320
|
deferTools?: string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { composeExecutionLane, assertExecutionLaneInvariants, type ExecutionLane
|
|
|
3
3
|
export { createBrain } from "./brain.js";
|
|
4
4
|
export { createSessionStore } from "./plugins/session-store.js";
|
|
5
5
|
export { createStoreBackend, type StoreBackend, type SessionCaptureRecordSource } from "./plugins/store-backend.js";
|
|
6
|
+
export { RetiredSchemaPresentError } from "./plugins/one-time-migrations.js";
|
|
6
7
|
export { CachingSessionStore, type CachingOptions } from "./plugins/caching-session-store.js";
|
|
7
8
|
export type { RunRecord, RunEvent } from "./plugins/store-contracts.js";
|
|
8
9
|
export { runInBackground } from "./runs.js";
|
|
@@ -15,7 +16,6 @@ export { Metrics, createMetrics, type Labels } from "./observability/metrics.js"
|
|
|
15
16
|
export { RateLimiter, type RateDecision } from "./observability/rate-limit.js";
|
|
16
17
|
export { GiteaClient, GitHubClient, createRepoClient, GIT_API_KINDS, isGitApiKind, repoToolsFor, parseRepo, type GitApiKind, type RepoCoords, type RepoReadClient } from "./capabilities/repo-tools.js";
|
|
17
18
|
export { nowTool } from "./capabilities/builtin-tools.js";
|
|
18
|
-
export { createCouncilTool } from "./capabilities/code-review-council.js";
|
|
19
19
|
export { loadSkills, skillsForScenario, type LoadedSkill } from "./capabilities/skills.js";
|
|
20
20
|
export { buildScenarios, selectScenario, type Scenario, type ScenarioBundle, type ScenarioContext, type ScenarioDeps, type ScenarioRequest, } from "./capabilities/scenarios.js";
|
|
21
21
|
export { createTaskListLane, taskListKeyFor, MAX_TASK_LIST_SESSIONS, type TaskListLane, type TaskListLaneBackend, type TaskListLaneOpts } from "./boot/task-list-lane.js";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { composeExecutionLane, assertExecutionLaneInvariants } from "./boot/exec
|
|
|
3
3
|
export { createBrain } from "./brain.js";
|
|
4
4
|
export { createSessionStore } from "./plugins/session-store.js";
|
|
5
5
|
export { createStoreBackend } from "./plugins/store-backend.js";
|
|
6
|
+
export { RetiredSchemaPresentError } from "./plugins/one-time-migrations.js";
|
|
6
7
|
export { CachingSessionStore } from "./plugins/caching-session-store.js";
|
|
7
8
|
export { runInBackground } from "./runs.js";
|
|
8
9
|
export { createHardenedVmRunner, runInHardenedVm } from "./orchestration/hardened-vm-runner.js";
|
|
@@ -13,7 +14,6 @@ export { Metrics, createMetrics } from "./observability/metrics.js";
|
|
|
13
14
|
export { RateLimiter } from "./observability/rate-limit.js";
|
|
14
15
|
export { GiteaClient, GitHubClient, createRepoClient, GIT_API_KINDS, isGitApiKind, repoToolsFor, parseRepo } from "./capabilities/repo-tools.js";
|
|
15
16
|
export { nowTool } from "./capabilities/builtin-tools.js";
|
|
16
|
-
export { createCouncilTool } from "./capabilities/code-review-council.js";
|
|
17
17
|
export { loadSkills, skillsForScenario } from "./capabilities/skills.js";
|
|
18
18
|
export { buildScenarios, selectScenario, } from "./capabilities/scenarios.js";
|
|
19
19
|
export { createTaskListLane, taskListKeyFor, MAX_TASK_LIST_SESSIONS } from "./boot/task-list-lane.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **服务自己为自己起的 run**(agent 钩子子代理 / leader 的集成沙盒与 grader 沙盒)的执行环境上下文 —— 它们不属于任何用户会话:
|
|
3
|
+
* 会话 id 是服务现铸的(`uuidv7()` / `leader-integ-<ts>`),会话店里没有这一行,也不该有。
|
|
4
|
+
*
|
|
5
|
+
* ── 为什么要在这里表态 ───────────────────────────────────────────────────────────────────────────
|
|
6
|
+
* k8s 车道的工作区空间按**会话住客**分区(`boot/execution-env.ts` `createK8sRunEnvFactory`):建环境时按 id 读会话店的住客,读不到(= 没有这只会话)
|
|
7
|
+
* 就报一行 `workspace_space_unavailable`、开着缓存卷时再计一次 fail-open。对用户的 run 这是真信号(会话被删了);对服务自己的 run 它是**恒真的噪声**
|
|
8
|
+
* —— 每只碰了手的内部 env 一行、计数被内部 runner 抬高。
|
|
9
|
+
*
|
|
10
|
+
* ── 怎么表态:不是新开关,是同一条法 ───────────────────────────────────────────────────────────────
|
|
11
|
+
* 工厂里已有的判别 `placementRootSessionId ≠ sessionId` 读作「这条 run 不拥有自己的空间」(委派子代 / 后台代理与父并发,共用一个空间 = 两个写者,
|
|
12
|
+
* 按法 off:不读住客、不出该行、不挂缓存卷,停驻车道照旧)。内部 run 同样没有自己的空间 —— 它的放置根不是某个用户会话,而是**服务自己**
|
|
13
|
+
* ({@link INTERNAL_RUN_PLACEMENT_ROOT})。所以内部 run 只需如实报它的放置根,工厂那一行判别原样把它读成「无空间」;本模块不加任何第二个判据。
|
|
14
|
+
* 放置根的另一个读者是 device 车道(按根会话找绑定):服务自己的根上没有任何绑定,与修前(根 = 它自己的现铸会话 id,同样没有绑定)同答 —— 拒。
|
|
15
|
+
*/
|
|
16
|
+
import type { ExecutionEnvFactory, ExecutionEnvFactoryContext } from "@sema-agent/core";
|
|
17
|
+
/** 内部 run 的放置根:不是用户会话 id —— 提交口的会话 id 形律只收规范 uuid(`security.ts` `sessionIdShapeIssue`),这个词不是 uuid 形,调用方登记不出它。 */
|
|
18
|
+
export declare const INTERNAL_RUN_PLACEMENT_ROOT = "service:internal-run";
|
|
19
|
+
/** 服务为内部 run 直接调工厂时递的上下文(leader 的集成沙盒 / grader 沙盒):自己的现铸会话 id,放置根 = 服务自己。 */
|
|
20
|
+
export declare function buildInternalRunCtx(sessionId: string): ExecutionEnvFactoryContext;
|
|
21
|
+
/**
|
|
22
|
+
* 内部 Runner(agent 钩子子代理)的工厂:把 core 递来的上下文的放置根改报为服务自己 —— core 对这类 run 只知道「根 run,根 = 自己」,不知道它是服务
|
|
23
|
+
* 为自己起的。其余字段(`sessionId` / `taskId` …)原样过境。
|
|
24
|
+
*/
|
|
25
|
+
export declare function createInternalRunEnvFactory(factory: ExecutionEnvFactory): ExecutionEnvFactory;
|
|
26
|
+
/** 内部 Runner 的 deps:`executionEnvFactory` 在场就按 {@link createInternalRunEnvFactory} 换装,其余键原样(缺席仍缺席)。 */
|
|
27
|
+
export declare function withInternalRunEnvs<D extends {
|
|
28
|
+
readonly executionEnvFactory?: ExecutionEnvFactory;
|
|
29
|
+
}>(deps: D): D;
|
|
30
|
+
//# sourceMappingURL=internal-run-env.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const INTERNAL_RUN_PLACEMENT_ROOT = "service:internal-run";
|
|
2
|
+
export function buildInternalRunCtx(sessionId) {
|
|
3
|
+
return { sessionId, placementRootSessionId: INTERNAL_RUN_PLACEMENT_ROOT };
|
|
4
|
+
}
|
|
5
|
+
export function createInternalRunEnvFactory(factory) {
|
|
6
|
+
return (ctx) => factory({ ...ctx, placementRootSessionId: INTERNAL_RUN_PLACEMENT_ROOT });
|
|
7
|
+
}
|
|
8
|
+
export function withInternalRunEnvs(deps) {
|
|
9
|
+
return deps.executionEnvFactory === undefined ? deps : { ...deps, executionEnvFactory: createInternalRunEnvFactory(deps.executionEnvFactory) };
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=internal-run-env.js.map
|
package/dist/leader/wire.js
CHANGED
|
@@ -19,6 +19,7 @@ import { runLeaderTask, withLeaderLeg } from "./leader.js";
|
|
|
19
19
|
import { attachRepairLoopDeps } from "./repair-wire.js";
|
|
20
20
|
import { routePlanWithFallback, validateSubtasks } from "./planner.js";
|
|
21
21
|
import { createEngineNoticeSeat } from "../boot/runner-deps.js";
|
|
22
|
+
import { buildInternalRunCtx } from "../internal-run-env.js";
|
|
22
23
|
import { currentPrincipal } from "../observability/principal-context.js";
|
|
23
24
|
import { redactHead } from "../observability/run-terminal.js";
|
|
24
25
|
const REPAIR_LOOP_WIRED = true;
|
|
@@ -379,7 +380,7 @@ export function createLeaderRunner(cfg) {
|
|
|
379
380
|
const provisionIntegrationSandbox = async () => {
|
|
380
381
|
if (cfg.envFactory) {
|
|
381
382
|
const integSessionId = `leader-integ-${Date.now()}`;
|
|
382
|
-
const env = await cfg.envFactory(
|
|
383
|
+
const env = await cfg.envFactory(buildInternalRunCtx(integSessionId));
|
|
383
384
|
const reap = createEnvReaper(env, "leader integration sandbox");
|
|
384
385
|
try {
|
|
385
386
|
await sh(env)(body.seedCmd);
|
|
@@ -431,7 +432,7 @@ export function createLeaderRunner(cfg) {
|
|
|
431
432
|
let env;
|
|
432
433
|
if (cfg.envFactory) {
|
|
433
434
|
const graderSessionId = `leader-grader-${Date.now()}`;
|
|
434
|
-
env = await cfg.envFactory(
|
|
435
|
+
env = await cfg.envFactory(buildInternalRunCtx(graderSessionId));
|
|
435
436
|
}
|
|
436
437
|
else {
|
|
437
438
|
if (!cfg.e2bApiKey)
|
package/dist/lsp/manager.js
CHANGED
|
@@ -79,7 +79,7 @@ export class E2bLspManager {
|
|
|
79
79
|
return undefined;
|
|
80
80
|
}
|
|
81
81
|
if (heal)
|
|
82
|
-
await session.warmOpen(heal.carry).catch(() => ({ opened: 0, failed: heal.carry.length }));
|
|
82
|
+
await session.warmOpen(heal.carry).catch(() => ({ opened: 0, failed: heal.carry.length, deferred: 0 }));
|
|
83
83
|
if (sessions.get(language) !== job) {
|
|
84
84
|
await session.close().catch(() => undefined);
|
|
85
85
|
return undefined;
|