@sema-agent/server 4.3.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +16 -16
- package/dist/approval-hmac.js +1 -1
- package/dist/approval.d.ts +17 -37
- package/dist/approval.js +26 -112
- package/dist/auth-bridge.d.ts +0 -1
- package/dist/auth-bridge.js +0 -1
- package/dist/auth-keys.d.ts +1 -1
- package/dist/auth-keys.js +1 -1
- package/dist/bench/s1/run-firm.js +1 -1
- package/dist/boot/config-center.d.ts +1 -1
- package/dist/boot/config-center.js +4 -3
- package/dist/boot/reapers.d.ts +1 -2
- package/dist/boot/reapers.js +4 -5
- package/dist/boot/resolve-spec.d.ts +1 -3
- package/dist/boot/resolve-spec.js +21 -35
- package/dist/boot/stores.js +1 -1
- package/dist/brain.js +3 -1
- package/dist/capabilities/center-prompts.d.ts +2 -3
- package/dist/capabilities/center-prompts.js +2 -3
- package/dist/capabilities/sandbox-file-send.d.ts +0 -2
- package/dist/capabilities/sandbox-file-send.js +2 -5
- package/dist/capabilities/scenarios.d.ts +2 -13
- package/dist/capabilities/scenarios.js +3 -22
- package/dist/capabilities/send-user-file-tool.js +3 -3
- package/dist/config-center/apply-effective.js +51 -13
- package/dist/config-center/types.d.ts +10 -3
- package/dist/config-provider.d.ts +1 -1
- package/dist/config-provider.js +6 -11
- package/dist/config-types.d.ts +15 -29
- package/dist/config.d.ts +28 -0
- package/dist/config.js +122 -68
- package/dist/fleet/fleet-bus.d.ts +1 -1
- package/dist/fleet/fleet-bus.js +25 -33
- package/dist/fleet-client.js +1 -1
- package/dist/http/active-run-conflict.d.ts +2 -0
- package/dist/http/active-run-conflict.js +1 -0
- package/dist/http/routes/approvals-assistant.d.ts +2 -1
- package/dist/http/routes/approvals-assistant.js +10 -96
- package/dist/http/routes/capabilities.js +6 -5
- package/dist/http/routes/fleet.js +7 -2
- package/dist/http/routes/images.js +10 -4
- package/dist/http/routes/runs.js +42 -65
- package/dist/http/routes/session-sync.d.ts +1 -1
- package/dist/http/routes/session-sync.js +2 -1
- package/dist/http/routes/tasks.js +7 -7
- package/dist/http/routes/trace-usage.js +1 -1
- package/dist/http/routes/workflows.js +3 -1
- package/dist/http/server.d.ts +1 -6
- package/dist/http/server.js +15 -10
- package/dist/http/verify-rounds.d.ts +2 -2
- package/dist/http/wire-types.d.ts +1 -1
- package/dist/images/bake-validate.d.ts +0 -22
- package/dist/images/bake-validate.js +0 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -5
- package/dist/main.js +9 -10
- package/dist/memory-scope.d.ts +1 -2
- package/dist/memory-scope.js +8 -4
- package/dist/orchestration/workflow-completion-inbox.d.ts +6 -4
- package/dist/orchestration/workflow-completion-inbox.js +6 -4
- package/dist/plugins/breaker-state-sql.d.ts +2 -3
- package/dist/plugins/file-run-store.d.ts +1 -1
- package/dist/plugins/file-run-store.js +1 -1
- package/dist/plugins/image-index-sql.d.ts +0 -1
- package/dist/plugins/image-index-sql.js +0 -1
- package/dist/plugins/local-checkpoint-store.d.ts +1 -1
- package/dist/plugins/local-checkpoint-store.js +1 -1
- package/dist/plugins/memory-engine-pg.js +2 -1
- package/dist/plugins/memory-engine-tidb.js +1 -1
- package/dist/plugins/memory-engine-vector-util.d.ts +2 -3
- package/dist/plugins/memory-engine-vector-util.js +2 -3
- package/dist/plugins/memory-run-store.d.ts +2 -2
- package/dist/plugins/memory-run-store.js +1 -1
- package/dist/plugins/memory-session-policy-store.d.ts +1 -1
- package/dist/plugins/memory-session-policy-store.js +1 -1
- package/dist/plugins/memory-sync-store-pg.js +1 -1
- package/dist/plugins/pg-pool.js +8 -10
- package/dist/plugins/pg-rate-limiter.d.ts +1 -4
- package/dist/plugins/pg-rate-limiter.js +0 -1
- package/dist/plugins/roster-store-sql.d.ts +1 -3
- package/dist/plugins/roster-store-sql.js +3 -5
- package/dist/plugins/store-backend.d.ts +7 -17
- package/dist/plugins/store-backend.js +14 -30
- package/dist/plugins/tidb-pool.js +6 -30
- package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
- package/dist/plugins/workflow-journal-store-sql.js +1 -1
- package/dist/principal-jwt.js +2 -2
- package/dist/prompts-domain-validate.d.ts +4 -3
- package/dist/prompts-domain-validate.js +7 -8
- package/dist/router/route-orchestration.js +3 -3
- package/dist/run-local.js +13 -10
- package/dist/runs.js +8 -6
- package/dist/runtime-caps-resolver.d.ts +3 -2
- package/dist/runtime-caps-resolver.js +4 -4
- package/dist/runtime-governance.d.ts +4 -7
- package/dist/runtime-governance.js +12 -17
- package/dist/sealed-key.d.ts +1 -1
- package/dist/sealed-key.js +1 -1
- package/dist/security.d.ts +0 -5
- package/dist/security.js +8 -12
- package/dist/session-sync.d.ts +0 -2
- package/dist/session-sync.js +10 -17
- package/dist/spec-fields.js +6 -6
- package/dist/task-settings.d.ts +0 -27
- package/dist/task-settings.js +42 -11
- package/dist/tool-approval.d.ts +3 -3
- package/dist/tool-approval.js +10 -10
- package/dist/trace/artifacts.d.ts +11 -0
- package/dist/trace/artifacts.js +8 -19
- package/dist/trace/ledger-sink.js +7 -1
- package/dist/trace/project.d.ts +17 -0
- package/dist/trace/project.js +58 -5
- package/package.json +3 -3
- package/dist/capabilities/oa-tools.d.ts +0 -15
- package/dist/capabilities/oa-tools.js +0 -54
- package/dist/plugins/approval-store-sql.d.ts +0 -116
- package/dist/plugins/approval-store-sql.js +0 -150
- package/dist/plugins/file-workflow-journal-store.d.ts +0 -12
- package/dist/plugins/file-workflow-journal-store.js +0 -12
- package/dist/plugins/pg-approval-store.d.ts +0 -9
- package/dist/plugins/pg-approval-store.js +0 -9
- package/dist/plugins/pg-breaker-state.d.ts +0 -8
- package/dist/plugins/pg-breaker-state.js +0 -8
- package/dist/plugins/pg-checkpoint-store.d.ts +0 -10
- package/dist/plugins/pg-checkpoint-store.js +0 -10
- package/dist/plugins/pg-file-snapshot-store.d.ts +0 -8
- package/dist/plugins/pg-file-snapshot-store.js +0 -8
- package/dist/plugins/pg-image-bake.d.ts +0 -12
- package/dist/plugins/pg-image-bake.js +0 -11
- package/dist/plugins/pg-image-index.d.ts +0 -12
- package/dist/plugins/pg-image-index.js +0 -11
- package/dist/plugins/pg-outcome-ledger.d.ts +0 -12
- package/dist/plugins/pg-outcome-ledger.js +0 -11
- package/dist/plugins/pg-resume-anchor-store.d.ts +0 -7
- package/dist/plugins/pg-resume-anchor-store.js +0 -7
- package/dist/plugins/pg-run-store.d.ts +0 -9
- package/dist/plugins/pg-run-store.js +0 -9
- package/dist/plugins/pg-session-policy-store.d.ts +0 -7
- package/dist/plugins/pg-session-policy-store.js +0 -7
- package/dist/plugins/pg-session-store.d.ts +0 -12
- package/dist/plugins/pg-session-store.js +0 -12
- package/dist/plugins/pg-tool-result-store.d.ts +0 -9
- package/dist/plugins/pg-tool-result-store.js +0 -9
- package/dist/plugins/pg-workflow-journal-store.d.ts +0 -9
- package/dist/plugins/pg-workflow-journal-store.js +0 -9
- package/dist/plugins/pg-workflow-run-store.d.ts +0 -9
- package/dist/plugins/pg-workflow-run-store.js +0 -9
- package/dist/plugins/tidb-approval-store.d.ts +0 -8
- package/dist/plugins/tidb-approval-store.js +0 -8
- package/dist/plugins/tidb-breaker-state.d.ts +0 -7
- package/dist/plugins/tidb-breaker-state.js +0 -7
- package/dist/plugins/tidb-checkpoint-store.d.ts +0 -9
- package/dist/plugins/tidb-checkpoint-store.js +0 -9
- package/dist/plugins/tidb-file-snapshot-store.d.ts +0 -8
- package/dist/plugins/tidb-file-snapshot-store.js +0 -8
- package/dist/plugins/tidb-image-bake.d.ts +0 -12
- package/dist/plugins/tidb-image-bake.js +0 -11
- package/dist/plugins/tidb-image-index.d.ts +0 -12
- package/dist/plugins/tidb-image-index.js +0 -11
- package/dist/plugins/tidb-outcome-ledger.d.ts +0 -12
- package/dist/plugins/tidb-outcome-ledger.js +0 -12
- package/dist/plugins/tidb-resume-anchor-store.d.ts +0 -7
- package/dist/plugins/tidb-resume-anchor-store.js +0 -7
- package/dist/plugins/tidb-run-store.d.ts +0 -10
- package/dist/plugins/tidb-run-store.js +0 -9
- package/dist/plugins/tidb-session-policy-store.d.ts +0 -7
- package/dist/plugins/tidb-session-policy-store.js +0 -7
- package/dist/plugins/tidb-tool-result-store.d.ts +0 -8
- package/dist/plugins/tidb-tool-result-store.js +0 -10
- package/dist/plugins/tidb-workflow-journal-store.d.ts +0 -9
- package/dist/plugins/tidb-workflow-journal-store.js +0 -9
- package/dist/plugins/tidb-workflow-run-store.d.ts +0 -10
- package/dist/plugins/tidb-workflow-run-store.js +0 -10
- package/dist/plugins/workflow-journal-limits.d.ts +0 -12
- package/dist/plugins/workflow-journal-limits.js +0 -12
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { principalFrom, decodeCheckpointScope } from "../../security.js";
|
|
2
|
+
import { verifyDirectDoorProof } from "../../principal-jwt.js";
|
|
3
|
+
import { MAX_APPROVAL_REASON_CHARS } from "../../approval-hmac.js";
|
|
3
4
|
import { redactedPreview } from "../../trace/redact.js";
|
|
4
5
|
import { fleetRunLabels } from "../../fleet/fleet-bus.js"; // [2069]④ §3 行展示名与 fleet 行同源(见用处的注)
|
|
5
6
|
import { sleep } from "../sse-log.js";
|
|
@@ -123,7 +124,7 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
|
|
|
123
124
|
sendError(res, 400, "request.route_unsupported", "DELETE needs /exemptions/:toolName");
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
126
|
-
const revoked = await deps.approvalExemptionStore.revoke(exSessionId,
|
|
127
|
+
const revoked = await deps.approvalExemptionStore.revoke(exSessionId, exToolName); // RAW(5.0.0):豁免键空间=CC canonical 名,live/存量行同名
|
|
127
128
|
sendJson(res, revoked ? 200 : 404, revoked ? { revoked: true } : { error: "not found", errorCode: "not_found.exemption" });
|
|
128
129
|
return;
|
|
129
130
|
}
|
|
@@ -445,7 +446,9 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
|
|
|
445
446
|
sendJson(res, out.status, out.body);
|
|
446
447
|
return;
|
|
447
448
|
}
|
|
448
|
-
|
|
449
|
+
// [2373]C-4(5.0.0):裸 `POST /v1/approvals/:id` 装饰臂删——/decide 是唯一决裁形(裸形曾与
|
|
450
|
+
// `GET /v1/approvals/:id` 撞 URL 空间,并把限流/计费/wire 注三处记账劈成两条)。
|
|
451
|
+
const m = /^\/v1\/approvals\/([^/]+)\/decide$/.exec(url);
|
|
449
452
|
if (m && req.method === "POST") {
|
|
450
453
|
if (rateLimited(req, res) || quotaExceeded(req, res))
|
|
451
454
|
return; // 🔴 复审 C2:lease admitted in driveResumeIntoRunLog (owner principal). resume hits TiDB — rate-limit (council)
|
|
@@ -622,7 +625,7 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
|
|
|
622
625
|
? async (overrideSessionId) => {
|
|
623
626
|
const grantSessionId = overrideSessionId ?? sessionId;
|
|
624
627
|
try {
|
|
625
|
-
const canonical =
|
|
628
|
+
const canonical = rememberToolName; // RAW(5.0.0):行 toolName 由 core 铸,恒 canonical
|
|
626
629
|
await deps.approvalExemptionStore.grant(grantSessionId, canonical, deciderPrincipal ?? null);
|
|
627
630
|
rememberApplied = true;
|
|
628
631
|
deps.logger?.info?.("approval_exemption_granted", { sessionId: grantSessionId, toolName: canonical, grantedBy: deciderPrincipal ?? null });
|
|
@@ -643,97 +646,8 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
|
|
|
643
646
|
sendError(res, 404, "not_found.route", "not found");
|
|
644
647
|
return;
|
|
645
648
|
}
|
|
646
|
-
//
|
|
647
|
-
|
|
648
|
-
const store = deps.approvalStore;
|
|
649
|
-
// 🔴 D-G belt (defense-in-depth): this LEGACY block authorizes /approvals via the trusted x-agent-principal
|
|
650
|
-
// header (no crypto). On a direct-door worker that path must NEVER be live — the boot invariant already forces
|
|
651
|
-
// DURABLE_APPROVAL (so checkpointStore owns /approvals above and we never reach here), but if a direct-door
|
|
652
|
-
// worker ever fell through to here, fail closed rather than expose the header-trusting path.
|
|
653
|
-
if (deps.config.directApprovalDoor) {
|
|
654
|
-
sendError(res, 503, "approval_door_unconfigured", "approvals are served by the direct door on this worker");
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
if (req.method === "GET" && url === "/v1/approvals") {
|
|
658
|
-
const principal = principalFrom(req, deps.config);
|
|
659
|
-
if (isOperator(principal, deps.config.operatorPrincipals)) {
|
|
660
|
-
// operator: may filter by any ?owner (or list all). scope is owner-sourced, so the operator's ?owner
|
|
661
|
-
// filter IS the scope filter; an unfiltered operator list is cross-tenant BY DESIGN (operator queue).
|
|
662
|
-
const owner = new URL(req.url ?? "", "http://x").searchParams.get("owner") ?? undefined;
|
|
663
|
-
sendJson(res, 200, { pending: owner ? await store.listPending(owner, owner) : await store.listPendingAll() });
|
|
664
|
-
}
|
|
665
|
-
else {
|
|
666
|
-
// non-operator: only ITS OWN pending (ignore any ?owner spoof) — never others' tool-call args. scope =
|
|
667
|
-
// the caller's principal (owner-sourced), so the single-DB read guard isolates it at the SQL layer too.
|
|
668
|
-
sendJson(res, 200, { pending: principal ? await store.listPending(principal, principal) : [] });
|
|
669
|
-
}
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
const m = /^\/v1\/approvals\/([^/]+)$/.exec(url);
|
|
673
|
-
if (m) {
|
|
674
|
-
const id = m[1];
|
|
675
|
-
if (req.method === "GET") {
|
|
676
|
-
// The row carries the full tool-call args (commands/paths/contents of a high-risk op). The list
|
|
677
|
-
// endpoint deliberately scopes non-operators to their OWN pending ("never others' tool-call args");
|
|
678
|
-
// a by-id GET must enforce the same boundary, else any token-holder who learns an id reads another
|
|
679
|
-
// tenant's args. Operators see all; everyone else only their own. 404 (not 403) to non-owners so
|
|
680
|
-
// existence isn't revealed — symmetric with runOwnerOk.
|
|
681
|
-
const principal = principalFrom(req, deps.config);
|
|
682
|
-
if (deps.config.requirePrincipal && !principal) {
|
|
683
|
-
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
const operator = isOperator(principal, deps.config.operatorPrincipals);
|
|
687
|
-
// Operators read across tenants (scope-agnostic getById); a non-operator reads ONLY its own scope so the
|
|
688
|
-
// single-DB read guard fires at the SQL layer (no cross-tenant args even if it learns an id).
|
|
689
|
-
const row = operator ? await store.getById(id) : principal != null ? await store.get(id, principal) : undefined;
|
|
690
|
-
if (!row || (!operator && row.owner !== null && row.owner !== principal)) {
|
|
691
|
-
sendError(res, 404, "not_found.approval", "approval not found");
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
sendJson(res, 200, row);
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
if (req.method === "POST") {
|
|
698
|
-
// Deciding (approve/deny) is OPERATOR-ONLY — a requester approving its own high-risk op would
|
|
699
|
-
// defeat the F4 gate. (Legacy/unset OPERATOR_PRINCIPALS → the service token is the boundary — but
|
|
700
|
-
// ONLY on a single-tenant deployment: under REQUIRE_PRINCIPAL the empty-list true-for-all would let
|
|
701
|
-
// ANY tenant decide ANOTHER tenant's approval [the durable /decide got
|
|
702
|
-
// this hardening earlier and this legacy leg never did] — multi-tenant demands an EXPLICIT
|
|
703
|
-
// operator list, same posture as the durable twin.)
|
|
704
|
-
const principal = principalFrom(req, deps.config);
|
|
705
|
-
const operatorOk = deps.config.requirePrincipal
|
|
706
|
-
? explicitOperatorOk(gatedPrincipal(req, deps.config), deps.config.operatorPrincipals)
|
|
707
|
-
: isOperator(principal, deps.config.operatorPrincipals);
|
|
708
|
-
if (!operatorOk) {
|
|
709
|
-
sendError(res, 403, "auth.operator_only", "only an operator may decide an approval (set OPERATOR_PRINCIPALS)");
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
const body = (await readJson(req));
|
|
713
|
-
const decision = body?.decision === "approve" ? "approved" : body?.decision === "deny" ? "denied" : undefined;
|
|
714
|
-
if (!decision) {
|
|
715
|
-
sendError(res, 400, "request.body_shape", "body must be { decision: 'approve' | 'deny', reason? }");
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
// The operator decides ACROSS tenants by design (the F4 operator queue), so we bind the decide CAS to the
|
|
719
|
-
// row's OWN scope (read scope-agnostically first) rather than the operator's principal — the scope<=>?
|
|
720
|
-
// clause then becomes an exact, consistent guard instead of a cross-tenant bypass. Unknown id ⇒ 409.
|
|
721
|
-
const target = await store.getById(id);
|
|
722
|
-
if (!target) {
|
|
723
|
-
sendError(res, 409, "conflict.approval_settled", "approval already decided or not found");
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
const applied = await store.decide(id, target.scope, decision, body.reason ?? null, principal ?? null);
|
|
727
|
-
if (applied)
|
|
728
|
-
sendJson(res, 200, { id, status: decision });
|
|
729
|
-
else
|
|
730
|
-
sendError(res, 409, "conflict.approval_settled", "approval already decided or not found");
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
sendError(res, 404, "not_found.route", "not found");
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
649
|
+
// [2354]/[2356] A-5(5.0.0):legacy F4 轮询腿(approvalStore list/by-id/decide,trusted-header 决裁)
|
|
650
|
+
// 整体退役。无 checkpointStore 的部署打 /v1/approvals 一律落到本域尾部的未匹配路径(404 诚实缺席)。
|
|
737
651
|
miss.fell = true;
|
|
738
652
|
}
|
|
739
653
|
/** Server-side poll cadence for the native-push approvals stream. The CLIENT subscribes ONCE and never polls;
|
|
@@ -40,8 +40,8 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
40
40
|
artifacts: Boolean(deps.runStore),
|
|
41
41
|
// TRUE when durable HITL can FIRE = checkpointStore present (DURABLE_APPROVAL=true): it mounts
|
|
42
42
|
// AskUserQuestion-suspend + the approval center + resumable checkpoints, independent of whether any
|
|
43
|
-
// TOOL is gated (APPROVAL_REQUIRE = the additive F4 layer).
|
|
44
|
-
//
|
|
43
|
+
// TOOL is gated (APPROVAL_REQUIRE = the additive F4 layer). ([2354] A-5:legacy 轮询门已退役,
|
|
44
|
+
// durable 是唯一门形——本谓词自然成为 approvals 面唯一的挂载条件。)
|
|
45
45
|
//
|
|
46
46
|
// 🔴 **辖域比名字宽**(2026-08-01,web [C33]③.3 踩出来的):这个谓词同时是 `/v1/assistant/*`
|
|
47
47
|
// 整条车道的挂载条件 —— `routes/approvals-assistant.ts` 的分支头逐字是
|
|
@@ -54,7 +54,8 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
54
54
|
approvals: Boolean(deps.checkpointStore),
|
|
55
55
|
sessions: Boolean(deps.sessionAudit),
|
|
56
56
|
// [1196] session 级 SSE 订阅面:与 /v1/sessions/:id/events 的 501 谓词同源(sessionWatch 只在
|
|
57
|
-
// sessionStorage 具备 getLeafId/ownerOf 时由 main.ts 构建)
|
|
57
|
+
// sessionStorage 具备 getLeafId/ownerOf 时由 main.ts 构建)。[2373]A-5:截至 2026-08-02 无消费端接线
|
|
58
|
+
// (web 未按此换轨)——本位是预备位;「web 据此换轨」是当年的预期,不是已核实的现状。
|
|
58
59
|
sessionEvents: Boolean(deps.sessionWatch && deps.sessionStorage?.getLeafId && deps.sessionStorage?.ownerOf),
|
|
59
60
|
// #13([1871] web asks,clay 排批 2026-07-27):fleet 面能力族——一个诚实对象或 false(无 fleetBus=
|
|
60
61
|
// 无 /v1/fleet/stream,路由 501 同谓词)。`resume:"snapshot"` 是刻意的语义广告而非搪塞:总线是
|
|
@@ -253,8 +254,8 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
253
254
|
// 缺席时 issue()/prepareDirectUpload() 一律 fail-loud,工具挂着也必失败)。"says yes ⟺ 调用真能
|
|
254
255
|
// 成功":只看 config.sendUserFile(签发面已配)会在工具缺席的形态(adb/local-docker lane、多租户
|
|
255
256
|
// host/ssh)假 yes(配置自查 agent 抓获 2026-07-14);只看 emitter 会在无公网端点时假 yes。
|
|
256
|
-
// s3PublicEndpoint
|
|
257
|
-
//
|
|
257
|
+
// s3PublicEndpoint=公网 base 的单一真源透出(server 透出,消费端不猜、不走 registry env 双源;
|
|
258
|
+
// 纯感知面)。[2373]A-5:截至 2026-08-02 无消费端读它——预备位,「web 渲染要用」是预期非现状。
|
|
258
259
|
sendUserFile: Boolean(deps.sendUserFile && deps.config.sendUserFile?.publicEndpoint),
|
|
259
260
|
s3PublicEndpoint: deps.config.sendUserFile?.publicEndpoint ?? null,
|
|
260
261
|
// SendUserFile 账本(多租户治理面):true ⇔ 签发面已配 + store backend 在(记账写点活 + GET
|
|
@@ -62,7 +62,9 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
|
|
|
62
62
|
// a new-generation shell can drop its frame-level own/foreign checks entirely (incl. the cross-shell
|
|
63
63
|
// restart edge) while keeping them against older servers whose meta lacks the field. Additive (version
|
|
64
64
|
// stays 1): older shells ignore unknown fields.
|
|
65
|
-
|
|
65
|
+
// `type: "meta"` 与 `event: meta` 同名双发(tasks/trace-usage/approvals 三流同款):部分代理只转发
|
|
66
|
+
// `data:` 行,按 data.type 分派的消费端否则认不出首帧。additive,老消费端忽略未知键。
|
|
67
|
+
res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, scoped: callerScope !== null, sessionScoped: callerSession !== null, bgNotifyFailClosed: true })}\n\n`);
|
|
66
68
|
const seenTasks = new Set();
|
|
67
69
|
const seenWf = new Set();
|
|
68
70
|
// #3 fleet per-session scope (clay 2026-06-30): a row is visible iff it matches the caller's principal scope AND
|
|
@@ -95,8 +97,11 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
|
|
|
95
97
|
}
|
|
96
98
|
return rest;
|
|
97
99
|
};
|
|
100
|
+
// [2373]C-12:data 里补 `type`(=event 名)双发——代理剥 `event:` 行/onmessage 忽略具名事件时,
|
|
101
|
+
// 按 data.type 分派的客户端在本流拿到的是 undefined(approvals 流注释里的真实约束,同理适用)。
|
|
102
|
+
// 单点注入:调用方已带 type 的帧(hook_notice/bg_notification)保持原值。
|
|
98
103
|
const send = (event, data) => { if (!res.writableEnded)
|
|
99
|
-
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`); };
|
|
104
|
+
res.write(`event: ${event}\ndata: ${JSON.stringify({ type: event, ...data })}\n\n`); };
|
|
100
105
|
// connect-time snapshot (filtered + scope-stripped) — the visible rows become this connection's "seen" set.
|
|
101
106
|
const snap = bus.snapshot();
|
|
102
107
|
const tasks = snap.tasks.filter(visT);
|
|
@@ -23,6 +23,12 @@ export function coarseStatusForState(state) {
|
|
|
23
23
|
return "failed";
|
|
24
24
|
return "running";
|
|
25
25
|
}
|
|
26
|
+
/** [2373]C-7:202 受理体的**唯一铸点**——此前四份逐字构造散在 attach/竞态重读/新建/dryRun 四臂,
|
|
27
|
+
* 下一个字段必然只加到其中两处(:213/:217 与 :193/:206 已有分叉风险)。status/state 双发保留
|
|
28
|
+
* (status=state 的粗化像,省客户端抄映射表)。 */
|
|
29
|
+
function bakeAcceptedBody(b) {
|
|
30
|
+
return { bakeId: b.bakeId, eventsUrl: `/v1/images/bakes/${b.bakeId}/events`, status: b.status, state: b.state };
|
|
31
|
+
}
|
|
26
32
|
/** build.sh's structured exit code → the center band-composer UI's terminal `errorCode` (§P2.14 #2). null for an
|
|
27
33
|
* uncategorized failure (then `error` free-text + `exitCode` drive the UI). 10=dup-tag, 124=timeout, 507=disk. */
|
|
28
34
|
export function errorCodeForExit(exitCode, cancelled) {
|
|
@@ -168,7 +174,7 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
168
174
|
// (a) an IDENTICAL-argv in-flight bake ⇒ attach (idempotent re-submit, 202) — no second build.
|
|
169
175
|
const sameArgv = await bakes.findActiveByArgv(argv);
|
|
170
176
|
if (sameArgv) {
|
|
171
|
-
sendJson(res, 202,
|
|
177
|
+
sendJson(res, 202, bakeAcceptedBody(sameArgv));
|
|
172
178
|
return;
|
|
173
179
|
}
|
|
174
180
|
// (b) M4: ATOMIC admission — the busy-check and the create are ONE DB statement (`INSERT … SELECT …
|
|
@@ -181,18 +187,18 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
181
187
|
if (!r.created) {
|
|
182
188
|
const sameNow = await bakes.findActiveByArgv(argv);
|
|
183
189
|
if (sameNow) {
|
|
184
|
-
sendJson(res, 202,
|
|
190
|
+
sendJson(res, 202, bakeAcceptedBody(sameNow));
|
|
185
191
|
return;
|
|
186
192
|
}
|
|
187
193
|
const busy = await bakes.findActiveAny();
|
|
188
194
|
sendError(res, 409, "conflict.bake_in_progress", "a bake is already in progress", { ...(busy ? { activeBakeId: busy.bakeId, eventsUrl: `/v1/images/bakes/${busy.bakeId}/events` } : {}) });
|
|
189
195
|
return;
|
|
190
196
|
}
|
|
191
|
-
sendJson(res, 202,
|
|
197
|
+
sendJson(res, 202, bakeAcceptedBody(r.bake));
|
|
192
198
|
return;
|
|
193
199
|
}
|
|
194
200
|
const { bake } = await bakes.createBake(createInput);
|
|
195
|
-
sendJson(res, 202,
|
|
201
|
+
sendJson(res, 202, bakeAcceptedBody(bake));
|
|
196
202
|
return;
|
|
197
203
|
}
|
|
198
204
|
// GET /v1/images/bakes/:bakeId/events — resumable SSE (the live "baking…" view), run-trace-SSE-shaped (§P2.2).
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -54,6 +54,29 @@ export const RUN_TASK_OUTPUT_RE = /^\/v1\/runs\/([^/]+)\/tasks\/([^/]+)\/output$
|
|
|
54
54
|
export const RUN_TASK_STOP_RE = /^\/v1\/runs\/([^/]+)\/tasks\/([^/]+)\/stop$/;
|
|
55
55
|
/** The task_run SSE — a thin wrapper over `streamSseLog` that PRESERVES the existing wire shape exactly
|
|
56
56
|
* (`data: {type, ...data}`, `event: failed` stale frame). Behavior-identical to the pre-refactor reader. */
|
|
57
|
+
/** [2373]C-5:subagent/task-handle 两读写面的**共享 authz 门**(principal→owner→session 三级,
|
|
58
|
+
* [1493] 模型)。此前两面逐字重写——[1493] 类修复要改两次的真风险。返回 run 行;`null`=已应答
|
|
59
|
+
* (401/404,honest 404 无存在性 oracle)。语义逐字保留:operator 双跳过(owner+session)、
|
|
60
|
+
* session-unbound run 仅 principal 门、fail-closed。 */
|
|
61
|
+
async function subagentReadGate(req, res, deps, runId) {
|
|
62
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
63
|
+
if (deps.config.requirePrincipal && principal === undefined) {
|
|
64
|
+
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const trusted = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
68
|
+
const run = await deps.runStore.getRun(runId);
|
|
69
|
+
if (!run || (!trusted && run.owner !== null && run.owner !== principal)) {
|
|
70
|
+
sendError(res, 404, "not_found.run", "run not found");
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const callerSession = new URL(req.url ?? "", "http://x").searchParams.get("session");
|
|
74
|
+
if (!trusted && run.sessionId && callerSession !== run.sessionId) {
|
|
75
|
+
sendError(res, 404, "not_found.run", "run not found");
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return { run, trusted };
|
|
79
|
+
}
|
|
57
80
|
async function streamRunEvents(req, res, runStore, taskId, staleMs) {
|
|
58
81
|
await streamSseLog(req, res, {
|
|
59
82
|
statusOf: async (id) => {
|
|
@@ -900,44 +923,18 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
900
923
|
sendError(res, 501, "capability.run_store_required", "subagent output reads require the run store");
|
|
901
924
|
return;
|
|
902
925
|
}
|
|
903
|
-
const principal = gatedPrincipal(req, deps.config);
|
|
904
|
-
if (deps.config.requirePrincipal && principal === undefined) {
|
|
905
|
-
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
926
|
const runId = safeDecode(subOutputMatch[1]);
|
|
909
927
|
const target = safeDecode(subOutputMatch[2]);
|
|
910
928
|
if (runId === null || target === null) {
|
|
911
929
|
sendError(res, 400, "request.path_malformed", "malformed subagent path (invalid percent-encoding)");
|
|
912
930
|
return;
|
|
913
931
|
}
|
|
914
|
-
const
|
|
915
|
-
|
|
916
|
-
if (!run || (!trusted && run.owner !== null && run.owner !== principal)) {
|
|
917
|
-
sendError(res, 404, "not_found.run", "run not found");
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
// [1493] SESSION-scoped content read. principal answers "is this your data at all" (whose); session
|
|
921
|
-
// answers "which conversation" (which). This verb returns CONVERSATION CONTENT (a child's final body), so
|
|
922
|
-
// the principal owner-gate above is necessary but NOT sufficient: under a SHARED principal (single-user
|
|
923
|
-
// multi-shell — the default local posture, and any tenant that shares an identity) it passes for every
|
|
924
|
-
// co-principal shell, so a SIBLING session could read another conversation's child (clay's dual-shell
|
|
925
|
-
// repro). The run belongs to a conversation (run.sessionId); a non-operator caller must PROVE it owns that
|
|
926
|
-
// conversation by asserting the matching `?session=`. Threat model is ACCIDENTAL cross-read within one
|
|
927
|
-
// user's own shells (no adversary — the shell honestly reports its own session), so an honest
|
|
928
|
-
// caller-asserted session is correct AND sufficient here (no unguessable capability needed). fable5 R1
|
|
929
|
-
// (deliberate boundary, do not "harden" this into a capability): yes, a co-principal caller COULD forge the
|
|
930
|
-
// target's session — but co-principal means the SAME authz identity, i.e. the same user reading their own
|
|
931
|
-
// data through another window; session is a content-ROUTING axis inside one principal, not an authz wall.
|
|
932
|
-
// Deployments with genuinely different humans MUST give them distinct principals (requirePrincipal) — the
|
|
933
|
-
// principal owner-gate above is the security boundary, and it runs first. An
|
|
934
|
-
// operator/trace caller bypasses (as it does the owner-gate). A run with no sessionId is not
|
|
935
|
-
// conversation-bound → principal gate only. Fail-CLOSED: a session-bound run with no/mismatched assertion → 404.
|
|
936
|
-
const callerSession = new URL(req.url ?? "", "http://x").searchParams.get("session");
|
|
937
|
-
if (!trusted && run.sessionId && callerSession !== run.sessionId) {
|
|
938
|
-
sendError(res, 404, "not_found.run", "run not found");
|
|
932
|
+
const gated = await subagentReadGate(req, res, deps, runId); // [2373]C-5 共享门(principal→owner→session)
|
|
933
|
+
if (!gated)
|
|
939
934
|
return;
|
|
940
|
-
}
|
|
935
|
+
const { run } = gated;
|
|
936
|
+
// [1493] SESSION 边界的完整论证(threat model/为何 caller-asserted session 足够/为何不做 capability)
|
|
937
|
+
// 已随共享门挪至 subagentReadGate 注;本面语义逐字不变。
|
|
941
938
|
const out = await deps.subagentTaskOutput(target, { owner: runId, scope: run.owner ?? "default", ...(run.sessionId ? { sessionId: run.sessionId } : {}) });
|
|
942
939
|
const details = out.details;
|
|
943
940
|
// codex R1 (type gate): pollTask is the GENERIC registry face — same-tuple b*/m*/w* handles (bash
|
|
@@ -968,29 +965,19 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
968
965
|
sendError(res, 501, "capability.run_store_required", "subagent streams require the run store");
|
|
969
966
|
return;
|
|
970
967
|
}
|
|
971
|
-
const principal = gatedPrincipal(req, deps.config);
|
|
972
|
-
if (deps.config.requirePrincipal && principal === undefined) {
|
|
973
|
-
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
974
|
-
return;
|
|
975
|
-
}
|
|
976
968
|
const runId = safeDecode(subStreamMatch[1]);
|
|
977
969
|
const target = safeDecode(subStreamMatch[2]);
|
|
978
970
|
if (runId === null || target === null) {
|
|
979
971
|
sendError(res, 400, "request.path_malformed", "malformed subagent path (invalid percent-encoding)");
|
|
980
972
|
return;
|
|
981
973
|
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
974
|
+
// [2373]C-5 共享门(principal→owner→session)——本腿此前逐字手写同三层([1493] 语义),与 output/stop
|
|
975
|
+
// 两腿并列;收编到同一函数后一次修复处处生效。判定顺序以共享门为准(与已收编的两腿现行为一致:
|
|
976
|
+
// 路径解码 400 先于 principal 401,门内三级次序不变)。
|
|
977
|
+
const gatedTail = await subagentReadGate(req, res, deps, runId);
|
|
978
|
+
if (!gatedTail)
|
|
986
979
|
return;
|
|
987
|
-
}
|
|
988
|
-
// [1493] session 门(output 面同注,出生即强制——新面零消费者无软拍需要)。
|
|
989
|
-
const callerSession = new URL(req.url ?? "", "http://x").searchParams.get("session");
|
|
990
|
-
if (!trusted && run.sessionId && callerSession !== run.sessionId) {
|
|
991
|
-
sendError(res, 404, "not_found.run", "run not found");
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
980
|
+
const { run } = gatedTail;
|
|
994
981
|
// 🔴 cli[1524] HIGH-2:**subscribe 先行,probe 在后**——原序(probe await→meta→subscribe)在
|
|
995
982
|
// probe 与 subscribe 之间留竞态窗:窗内子代 settle → task_settled 发布时无订阅者被丢(总线无
|
|
996
983
|
// replay/sticky)→ 订阅者只收 heartbeat 永挂。先建订阅,窗内 settle 自然落进队列;probe 判 404
|
|
@@ -1006,12 +993,16 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1006
993
|
return;
|
|
1007
994
|
}
|
|
1008
995
|
sseHeaders(res);
|
|
996
|
+
// [2373]C-11(5.0.0 BREAKING,提货单点名):meta 帧的 run 实体键改 `taskId`——同 verb 家族的
|
|
997
|
+
// output 面(:936/:1162)一直叫 taskId,meta 独叫 runId 是家族内第三键名;5.0.0 收一。
|
|
1009
998
|
// meta 声明:live 帧 replica-local + replay 指引;status=探测快照(终态=不再有帧,发 meta 即收尾,
|
|
1010
999
|
// cli 拿到「走 replay 面」的信号而不是空挂)。seq=stop-cycle 世代号(settle 铸;core 1.377 起
|
|
1011
1000
|
// **全域投影**——进程内 live 臂投当前复活周期号 [[1533] ask 兑现,[1548]①],durable 终态 serve
|
|
1012
1001
|
// 同键同义;老 core 只在 durable 臂在场,缺省容忍)。1=spawn 周期,≥2=复活周期(retained revive
|
|
1013
1002
|
// 或 tier-3 均 bump——codex 快审 #11 纠,勿窄读成仅 tier-3)。
|
|
1014
|
-
|
|
1003
|
+
// `type: "meta"` 与 `event: meta` 同名双发(tasks/trace-usage/approvals 三流同款):部分代理只转发
|
|
1004
|
+
// `data:` 行,按 data.type 分派的消费端否则认不出首帧。additive,老消费端忽略未知键。
|
|
1005
|
+
res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, taskId: runId, target, status: probeDetails.status ?? "running", ...(typeof probeDetails.seq === "number" ? { seq: probeDetails.seq } : {}), live: "replica-local", replayFace: "GET /v1/runs/:id/subagents/:handle/output" })}\n\n`);
|
|
1015
1006
|
// core 1.382([1561] 提货单③):"parked" 非活非终态(durably suspended pending approval)——跟
|
|
1016
1007
|
// running/pending 同归"还没完",tail 该继续等而不是收尾指去 replay 面(那是给真终态用的)。子代获批
|
|
1017
1008
|
// 恢复后会在**同一条**活流上继续产帧,提前收尾会让客户端误判"已经结束",错过恢复后的后续内容。
|
|
@@ -1068,31 +1059,17 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1068
1059
|
}
|
|
1069
1060
|
if (stopVerb && rateLimited(req, res))
|
|
1070
1061
|
return; // mutating, runs no model (parity with detach — rateLimited only, no quota/lease gate)
|
|
1071
|
-
const principal = gatedPrincipal(req, deps.config);
|
|
1072
|
-
if (deps.config.requirePrincipal && principal === undefined) {
|
|
1073
|
-
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
1074
|
-
return;
|
|
1075
|
-
}
|
|
1076
1062
|
const runId = safeDecode(taskVerbMatch[1]);
|
|
1077
1063
|
const target = safeDecode(taskVerbMatch[2]);
|
|
1078
1064
|
if (runId === null || target === null) {
|
|
1079
1065
|
sendError(res, 400, "request.path_malformed", "malformed task path (invalid percent-encoding)");
|
|
1080
1066
|
return;
|
|
1081
1067
|
}
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
if (!run || (!trusted && run.owner !== null && run.owner !== principal)) {
|
|
1085
|
-
sendError(res, 404, "not_found.run", "run not found");
|
|
1068
|
+
const gatedB = await subagentReadGate(req, res, deps, runId); // [2373]C-5 共享门([1493] 模型,论证见门注)
|
|
1069
|
+
if (!gatedB)
|
|
1086
1070
|
return;
|
|
1087
|
-
}
|
|
1088
|
-
// SESSION-scoped content/action face ([1493] model): enforced fail-closed — same boundary rationale as
|
|
1089
|
-
// subagentOutput (principal = whose data, session = which conversation; honest caller-asserted session is
|
|
1090
|
-
// correct AND sufficient inside one principal; operators bypass; a session-unbound run is principal-only).
|
|
1071
|
+
const { run } = gatedB;
|
|
1091
1072
|
const q = new URL(req.url ?? "", "http://x").searchParams;
|
|
1092
|
-
if (!trusted && run.sessionId && q.get("session") !== run.sessionId) {
|
|
1093
|
-
sendError(res, 404, "not_found.run", "run not found");
|
|
1094
|
-
return;
|
|
1095
|
-
}
|
|
1096
1073
|
const access = { owner: runId, scope: run.owner ?? "default", ...(run.sessionId ? { sessionId: run.sessionId } : {}) };
|
|
1097
1074
|
// ?filter= is REFUSED on BOTH verbs, not ignored (fail-loud 400; the face shipped unreleased, zero
|
|
1098
1075
|
// consumers — and codex R2: a GET-only refusal would leave the stop verb silently accepting it). The
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
11
11
|
import { type SessionRulesRecord } from "@sema-agent/core";
|
|
12
|
-
import { type StagingHandle } from "../../session-sync.js";
|
|
12
|
+
import { type StagingHandle } from "../../session-sync-kernel.js";
|
|
13
13
|
import type { RouteCtx } from "../route-ctx.js";
|
|
14
14
|
export type PendingStaging = {
|
|
15
15
|
handle: StagingHandle;
|
|
@@ -2,7 +2,8 @@ import { once } from "node:events";
|
|
|
2
2
|
import { randomBytes, createHash } from "node:crypto";
|
|
3
3
|
import { sessionLogDigest, sessionLogDigestsComparable, StreamingImportValidator, SessionError, SessionPolicyError } from "@sema-agent/core";
|
|
4
4
|
import { StringDecoder } from "node:string_decoder";
|
|
5
|
-
import { exportSessionManifest,
|
|
5
|
+
import { exportSessionManifest, fileSnapshotImportFace } from "../../session-sync.js";
|
|
6
|
+
import { classifySyncRelationshipByIds, SyncConflictError } from "../../session-sync-kernel.js";
|
|
6
7
|
import { isUuidShape } from "../../security.js";
|
|
7
8
|
import { sendJson, sendError, msg } from "../send.js";
|
|
8
9
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
@@ -6,7 +6,7 @@ import { defaultSubagentTailBus, projectTailFrame } from "../../fleet/subagent-t
|
|
|
6
6
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "../../orchestration/workflow-completion-inbox.js";
|
|
7
7
|
import { createLedgerSink } from "../../trace/ledger-sink.js";
|
|
8
8
|
import { redactSecrets } from "../../trace/redact.js";
|
|
9
|
-
import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, workspaceChangedEventData, appendModelUsageDelta, attachModelUsage } from "../../trace/project.js";
|
|
9
|
+
import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, appendModelUsageDelta, attachModelUsage } from "../../trace/project.js";
|
|
10
10
|
import { cascadeConfig, runMeta } from "../run-meta.js";
|
|
11
11
|
import { scopedIdempotencyKey } from "../idempotency.js";
|
|
12
12
|
import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
@@ -371,8 +371,9 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
371
371
|
// parentToolCallId-gated), so this sink is the ONLY path a tick reaches the top-run consumers.
|
|
372
372
|
// 1.78 (review HIGH): fan it out to the LIVE SSE too (shared whitelist+redact builder, carrying
|
|
373
373
|
// the E2 identity the shell binds concurrent ticks by) — previously it fed ONLY the fleet bus,
|
|
374
|
-
// so the conversation lane never saw a real tick and the in-loop task_progress branch below
|
|
375
|
-
// forward-compat
|
|
374
|
+
// so the conversation lane never saw a real tick and the in-loop task_progress branch below was
|
|
375
|
+
// forward-compat then — [2373]B-4:floor ^4.0.0 下它是热路径(腐注方向已翻转,勿按旧注当死支)。
|
|
376
|
+
// core swallow-guards a throwing sink.
|
|
376
377
|
const fwdInternals = {
|
|
377
378
|
onForwardEvent: (e) => {
|
|
378
379
|
fleetPub?.onForwardEvent(e);
|
|
@@ -652,10 +653,9 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
652
653
|
res.write(`data: ${JSON.stringify({ type: "workspace_changed", ...workspaceChangedEventData(ev) })}\n\n`);
|
|
653
654
|
}
|
|
654
655
|
else if (ev.type === "compaction_outcome") {
|
|
655
|
-
// core 2.5.0([2105]②
|
|
656
|
-
//
|
|
657
|
-
|
|
658
|
-
res.write(`data: ${JSON.stringify({ type: "compaction_outcome", outcome: e.outcome, trigger: e.trigger, ...(e.reason !== undefined ? { reason: redactSecrets(e.reason) } : {}), ...(e.eventId !== undefined ? { eventId: e.eventId } : {}), ...(e.parentToolCallId !== undefined ? { parentToolCallId: e.parentToolCallId } : {}) })}\n\n`);
|
|
656
|
+
// core 2.5.0([2105]②)压缩非 compacted 结局帧。[2373]C-2:内联挑键收进共享 builder,
|
|
657
|
+
// 与两条 durable 腿同源(它曾是唯一不走 builder 的内容帧=三方普查共同盲区)。
|
|
658
|
+
res.write(`data: ${JSON.stringify({ type: "compaction_outcome", ...compactionOutcomeEventData(ev) })}\n\n`);
|
|
659
659
|
}
|
|
660
660
|
else if (ev.type === "text_delta" || ev.type === "turn_end" || ev.type === "message_committed" || ev.type === "context_usage") {
|
|
661
661
|
// 裸 catch-all 白名单化收官(probe-wire-1 表征钉的根治半场):最后四个已知臂离开
|
|
@@ -242,7 +242,7 @@ async function streamTaskTrace(req, res, runStore, taskId, staleMs) {
|
|
|
242
242
|
sseHeaders(res);
|
|
243
243
|
// First event declares the stream contract: mode + a version (council — lets the relay detect a worker whose
|
|
244
244
|
// SSE shape changed, e.g. when P1 adds token-delta granularity). resumeFrom echoes the honored Last-Event-ID.
|
|
245
|
-
res.write(`event: meta\ndata: ${JSON.stringify({ version: 1, mode: "delta", resumeFrom: from })}\n\n`);
|
|
245
|
+
res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, mode: "delta", resumeFrom: from })}\n\n`); // [2373]C-12:data.type 双发与内容帧一致
|
|
246
246
|
let closed = false;
|
|
247
247
|
req.on("close", () => {
|
|
248
248
|
closed = true;
|
|
@@ -451,7 +451,9 @@ function summarizeWorkflowDetail(run) {
|
|
|
451
451
|
* stream (same shape as not-found), so the caller passes the SAME scope it owner-gated with. */
|
|
452
452
|
async function streamWorkflowRun(req, res, runId, scope) {
|
|
453
453
|
sseHeaders(res);
|
|
454
|
-
|
|
454
|
+
// `type: "meta"` 与 `event: meta` 同名双发(tasks/trace-usage/approvals 三流同款):部分代理只转发
|
|
455
|
+
// `data:` 行,按 data.type 分派的消费端否则认不出首帧。additive,老消费端忽略未知键。
|
|
456
|
+
res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, runId })}\n\n`);
|
|
455
457
|
let closed = false;
|
|
456
458
|
// subscribeWorkflow is a PUSH iterator: a parked `next()` on an idle-but-alive workflow does NOT observe a
|
|
457
459
|
// `closed` flag, so a disconnected client would leave the subscriber queue registered in core's channels and
|
package/dist/http/server.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { TaskRequestBody } from "./wire-types.js";
|
|
|
5
5
|
import type { ServiceConfig } from "../config-types.js";
|
|
6
6
|
import { type RestartSignal, type SessionMirrorRuling } from "../config-center/facade.js";
|
|
7
7
|
import { type OwnerAwareSessionStore } from "../security.js";
|
|
8
|
-
import type { RunStore,
|
|
8
|
+
import type { RunStore, ImageIndex, ImageBake, CheckpointStoreFull, ResumeAnchorStore, ApprovalExemptionStore, ServiceSessionPolicyStore, ServiceFileSnapshotStore, StoreBackend } from "../plugins/store-backend.js";
|
|
9
9
|
import { type MemorySyncRequest, type MemorySyncResponse } from "../memory-sync.js";
|
|
10
10
|
import type { TaskAttachmentStore } from "../plugins/task-attachment-store.js";
|
|
11
11
|
import type { LeaderEndpoint } from "../leader/endpoint.js";
|
|
@@ -25,8 +25,6 @@ import type { QuotaTracker } from "../observability/cost-quota.js";
|
|
|
25
25
|
import type { ModelUsageTracker, PromptManifestTracker } from "../budget.js";
|
|
26
26
|
import { scopedIdempotencyKey } from "./idempotency.js";
|
|
27
27
|
export { scopedIdempotencyKey };
|
|
28
|
-
import { clampVerifyRounds } from "./verify-rounds.js";
|
|
29
|
-
export { clampVerifyRounds };
|
|
30
28
|
import { streamApprovals, isQuestionAnswer } from "./routes/approvals-assistant.js";
|
|
31
29
|
export { streamApprovals, isQuestionAnswer };
|
|
32
30
|
import { cascadeConfig } from "./run-meta.js";
|
|
@@ -88,8 +86,6 @@ export interface ServiceStoreDeps {
|
|
|
88
86
|
/** E18 resume-at: the eventId→entryId anchor map (tidb/pg/local). Present whenever a backend is — resolveSpec
|
|
89
87
|
* resolves body.resumeAt(eventId)→entryId, the run lifecycle captures per-turn anchors, E21 purges them. */
|
|
90
88
|
resumeAnchorStore?: ResumeAnchorStore;
|
|
91
|
-
/** Human-in-the-loop approval store (F4 poll gate). Enables /v1/approvals when present. */
|
|
92
|
-
approvalStore?: ApprovalStore;
|
|
93
89
|
/** Per-session approval exemption store — enables decide body `remember:"session"` + the
|
|
94
90
|
* exemptions list/revoke surfaces on /v1/approvals. The ask-gate probe is wired separately (main.ts). */
|
|
95
91
|
approvalExemptionStore?: ApprovalExemptionStore;
|
|
@@ -439,7 +435,6 @@ export type FlatServiceDeps = ServiceCoreDeps & Partial<ServiceStoreDeps & Servi
|
|
|
439
435
|
export type ServiceDeps = FlatServiceDeps & ServiceDepGroups;
|
|
440
436
|
/** 分组入参 → 平铺视图(createHttpServer 的第一件事)。一组都没给 ⇒ 原样返回(存量调用零开销、零形变)。 */
|
|
441
437
|
export declare function flattenServiceDeps(deps: ServiceDeps): FlatServiceDeps;
|
|
442
|
-
export type { TaskRequestBody } from "./wire-types.js";
|
|
443
438
|
/** Personalization caps: server-pinned hard limits — a UI may be stricter, never looser. */
|
|
444
439
|
export declare const MAX_USER_SKILLS = 10;
|
|
445
440
|
/** = core `SKILL_CONTENT_MAX_CHARS`(1MB 加载门,core 1.293 起 invoke 全文零截断、超 1MB 整拒;常量在
|