@sema-agent/server 7.56.0 → 7.57.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/README.md +3 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +101 -9
- package/dist/approval-ask-audit-store.d.ts +100 -1
- package/dist/approval-ask-audit-store.js +103 -2
- package/dist/approval-card.d.ts +148 -13
- package/dist/approval-card.js +82 -13
- package/dist/approval.d.ts +31 -0
- package/dist/approval.js +18 -0
- package/dist/auto-mode-face.d.ts +111 -0
- package/dist/auto-mode-face.js +99 -0
- package/dist/bench/s1/arms.js +5 -5
- package/dist/bench/s1/live-deps.js +11 -11
- package/dist/bench/s1/run-firm.js +3 -0
- package/dist/bench/s1/runner-ctx.d.ts +4 -0
- package/dist/bench/s1/runner-ctx.js +7 -0
- package/dist/boot/config-center.js +23 -1
- package/dist/boot/coordinators.js +13 -1
- package/dist/boot/parked-revive-gate.d.ts +7 -2
- package/dist/boot/parked-revive-gate.js +12 -1
- package/dist/boot/resolve-spec.d.ts +3 -0
- package/dist/boot/resolve-spec.js +3 -1
- package/dist/boot/runner-deps.d.ts +12 -0
- package/dist/boot/runner-deps.js +28 -3
- package/dist/boot/runtime-caps.d.ts +19 -9
- package/dist/boot/runtime-caps.js +49 -21
- package/dist/boot/session-shell-gate-registry.d.ts +39 -0
- package/dist/boot/session-shell-gate-registry.js +21 -0
- package/dist/config-catalog.js +14 -5
- package/dist/config-types.d.ts +41 -10
- package/dist/config.d.ts +21 -0
- package/dist/config.js +40 -5
- package/dist/http/route-ctx.d.ts +75 -0
- package/dist/http/routes/a2a-serve.js +5 -3
- package/dist/http/routes/approvals-assistant.js +19 -6
- package/dist/http/routes/capabilities.js +21 -4
- package/dist/http/routes/memory-origin.d.ts +2 -2
- package/dist/http/routes/rules.js +3 -2
- package/dist/http/routes/runs.d.ts +0 -14
- package/dist/http/routes/runs.js +20 -8
- package/dist/http/routes/tasks.js +34 -8
- package/dist/http/routes/workflows.js +19 -4
- package/dist/http/server.d.ts +11 -1
- package/dist/http/server.js +215 -43
- package/dist/http/wire-types.d.ts +9 -4
- package/dist/leader/diffout.js +2 -1
- package/dist/leader/endpoint.js +45 -12
- package/dist/leader/fanout.js +6 -5
- package/dist/leader/leader.js +17 -14
- package/dist/leader/merge.js +17 -12
- package/dist/leader/planner.js +3 -2
- package/dist/leader/repair-oracle.js +6 -4
- package/dist/leader/repair-wire.js +5 -3
- package/dist/leader/wire.d.ts +30 -1
- package/dist/leader/wire.js +36 -19
- package/dist/main.js +43 -7
- package/dist/observability/err-text.d.ts +6 -0
- package/dist/observability/err-text.js +10 -0
- package/dist/observability/fail-open.d.ts +40 -0
- package/dist/observability/fail-open.js +19 -0
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal-log.d.ts +120 -0
- package/dist/observability/run-terminal-log.js +360 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
- package/dist/orchestration/workflow-completion-inbox.js +8 -0
- package/dist/permission-rule-vocab.d.ts +40 -0
- package/dist/permission-rule-vocab.js +17 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
- package/dist/plugins/checkpoint-store-sql.js +19 -3
- package/dist/plugins/file-run-store.d.ts +3 -34
- package/dist/plugins/file-run-store.js +19 -0
- package/dist/plugins/local-checkpoint-store.d.ts +10 -0
- package/dist/plugins/local-checkpoint-store.js +3 -0
- package/dist/plugins/memory-run-store.d.ts +3 -15
- package/dist/plugins/memory-run-store.js +19 -0
- package/dist/plugins/permission-rule-store-file.js +8 -3
- package/dist/plugins/permission-rule-store-sql.js +16 -10
- package/dist/plugins/remote-scratchpad.js +3 -2
- package/dist/plugins/run-store-sql.d.ts +3 -42
- package/dist/plugins/run-store-sql.js +30 -1
- package/dist/plugins/store-backend.d.ts +1 -1
- package/dist/plugins/store-contracts.d.ts +66 -1
- package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
- package/dist/plugins/workflow-run-store-sql.js +10 -2
- package/dist/rules-consent.js +20 -13
- package/dist/run-cancel-context.d.ts +13 -0
- package/dist/run-cancel-context.js +15 -0
- package/dist/run-local.js +2 -2
- package/dist/runs.d.ts +4 -1
- package/dist/runs.js +41 -10
- package/dist/runtime-caps-resolver.d.ts +133 -17
- package/dist/runtime-caps-resolver.js +41 -3
- package/dist/task-settings.d.ts +57 -3
- package/dist/task-settings.js +78 -5
- package/dist/task-workflow.d.ts +32 -2
- package/dist/task-workflow.js +8 -3
- package/dist/tool-approval.d.ts +26 -3
- package/dist/tool-approval.js +96 -13
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/ledger-sink.d.ts +13 -4
- package/dist/trace/ledger-sink.js +14 -6
- package/dist/trace/project.d.ts +13 -0
- package/dist/trace/project.js +10 -1
- package/dist/trace/redact.d.ts +22 -11
- package/dist/trace/redact.js +655 -20
- package/dist/trace/sema-provenance.d.ts +26 -0
- package/dist/trace/sema-provenance.js +11 -0
- package/dist/turn-activity.d.ts +32 -2
- package/dist/turn-activity.js +29 -4
- package/package.json +2 -2
|
@@ -107,6 +107,7 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
|
|
|
107
107
|
: (principal ?? "__none__");
|
|
108
108
|
sendJson(res, 200, {
|
|
109
109
|
pending: (await cs.listPending(scope)).map((r) => projectPendingForWire(r, deps.config)),
|
|
110
|
+
serverNowMs: Date.now(),
|
|
110
111
|
...(deps.toolApproval ? { livePending: deps.toolApproval.listLivePending(scope) } : {}),
|
|
111
112
|
...(deps.approvalAskAudit ? { crashConverged: deps.approvalAskAudit.listCrashConverged(scope) } : {}),
|
|
112
113
|
});
|
|
@@ -520,9 +521,20 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
|
|
|
520
521
|
const APPROVALS_STREAM_POLL_MS = 3000;
|
|
521
522
|
function redactPendingDisclosures(row) {
|
|
522
523
|
const rd = row.riskDescriptor;
|
|
523
|
-
if (rd === null || rd === undefined
|
|
524
|
+
if (rd === null || rd === undefined)
|
|
524
525
|
return row;
|
|
525
|
-
|
|
526
|
+
const shadowedRule = typeof rd.shadowedRule === "string" ? redactSecrets(rd.shadowedRule) : undefined;
|
|
527
|
+
const summary = typeof rd.summary === "string" ? redactSecrets(rd.summary) : undefined;
|
|
528
|
+
if (shadowedRule === undefined && summary === undefined)
|
|
529
|
+
return row;
|
|
530
|
+
return {
|
|
531
|
+
...row,
|
|
532
|
+
riskDescriptor: {
|
|
533
|
+
...rd,
|
|
534
|
+
...(shadowedRule !== undefined ? { shadowedRule } : {}),
|
|
535
|
+
...(summary !== undefined ? { summary } : {}),
|
|
536
|
+
},
|
|
537
|
+
};
|
|
526
538
|
}
|
|
527
539
|
function projectPendingForWire(row, governance) {
|
|
528
540
|
const redacted = redactPendingDisclosures(row);
|
|
@@ -553,19 +565,20 @@ export async function streamApprovals(req, res, cs, scope, governance, pollMs =
|
|
|
553
565
|
}
|
|
554
566
|
const cur = new Map(pending.map((p) => [keyOf(p), p]));
|
|
555
567
|
const curFrames = new Map([...cur].map(([k, p]) => [k, JSON.stringify({ type: "pending", ...p })]));
|
|
568
|
+
const pendingFrame = (k) => JSON.stringify({ type: "pending", ...cur.get(k), serverNowMs: Date.now() });
|
|
556
569
|
if (first) {
|
|
557
|
-
for (const
|
|
558
|
-
res.write(`event: pending\ndata: ${
|
|
570
|
+
for (const k of curFrames.keys())
|
|
571
|
+
res.write(`event: pending\ndata: ${pendingFrame(k)}\n\n`);
|
|
559
572
|
res.write(`event: synced\ndata: ${JSON.stringify({ type: "synced", count: pending.length })}\n\n`);
|
|
560
573
|
first = false;
|
|
561
574
|
}
|
|
562
575
|
else {
|
|
563
576
|
for (const [k, frame] of curFrames)
|
|
564
577
|
if (prevFrames.get(k) !== frame)
|
|
565
|
-
res.write(`event: pending\ndata: ${
|
|
578
|
+
res.write(`event: pending\ndata: ${pendingFrame(k)}\n\n`);
|
|
566
579
|
for (const [k, p] of prev)
|
|
567
580
|
if (!cur.has(k))
|
|
568
|
-
res.write(`event: resolved\ndata: ${JSON.stringify({ type: "resolved", sessionId: p.sessionId, toolCallId: p.toolCallId })}\n\n`);
|
|
581
|
+
res.write(`event: resolved\ndata: ${JSON.stringify({ type: "resolved", sessionId: p.sessionId, toolCallId: p.toolCallId, serverNowMs: Date.now() })}\n\n`);
|
|
569
582
|
}
|
|
570
583
|
prev = cur;
|
|
571
584
|
prevFrames = curFrames;
|
|
@@ -9,8 +9,10 @@ import { mcpInjectionHonored } from "../../task-mcp.js";
|
|
|
9
9
|
import { sendJson, sendError } from "../send.js";
|
|
10
10
|
import { resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
11
11
|
import { projectMemoryEngineCapability } from "../../memory-posture.js";
|
|
12
|
-
import { workflowModelAllowlistFor } from "../../task-workflow.js";
|
|
12
|
+
import { workflowModelAllowlistFor, selfOrchestrationDenial } from "../../task-workflow.js";
|
|
13
13
|
import { centerEntitlementSourceWired } from "../../runtime-caps-resolver.js";
|
|
14
|
+
import { judgeAutoModeArming } from "../../auto-mode-face.js";
|
|
15
|
+
import { coercePermissionModeStrict } from "../../task-settings.js";
|
|
14
16
|
import { gatedPrincipal } from "../principal-gate.js";
|
|
15
17
|
function routePairingOf(deps, name, m) {
|
|
16
18
|
if (isSealedKeyPoison(deps.config.modelApiKeys?.[name]))
|
|
@@ -43,6 +45,18 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
47
|
if (req.method === "GET" && url === "/v1/capabilities") {
|
|
48
|
+
const workflowsEngineCan = deps.workflowsCapable ?? Boolean(deps.workflowRunStore);
|
|
49
|
+
const workflowsDenial = selfOrchestrationDenial({ selfOrchestrationEnabled: deps.config.selfOrchestrationEnabled, requirePrincipal: deps.config.requirePrincipal }, centerEntitlementSourceWired(deps.config));
|
|
50
|
+
const requestedModeRaw = new URL(req.url ?? "", "http://x").searchParams.get("permissionMode");
|
|
51
|
+
const requestedMode = requestedModeRaw === null ? undefined : coercePermissionModeStrict(requestedModeRaw);
|
|
52
|
+
if (requestedModeRaw !== null && requestedMode === undefined) {
|
|
53
|
+
sendError(res, 400, "request.field_invalid", 'permissionMode must be one of "default" | "acceptEdits" | "plan" | "bypassPermissions" | "auto"');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const entitlementSource = centerEntitlementSourceWired(deps.config);
|
|
57
|
+
const { fault: autoModeFault, ...autoMode } = await judgeAutoModeArming(deps.autoModeFace, { requestedMode, principal: gatedPrincipal(req, deps.config), entitlementSource, localDeny: deps.config.permissionsDisableAutoMode });
|
|
58
|
+
if (autoModeFault !== undefined)
|
|
59
|
+
deps.logger?.warn("auto_mode_face_resolver_fault", { fault: autoModeFault });
|
|
46
60
|
sendJson(res, 200, {
|
|
47
61
|
...deps.capabilities,
|
|
48
62
|
asyncRuns: Boolean(deps.runStore),
|
|
@@ -92,8 +106,10 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
92
106
|
modeShellGateTranslation: true,
|
|
93
107
|
permissionModeAuto: {
|
|
94
108
|
accepted: true,
|
|
95
|
-
classifierSeat: true,
|
|
96
|
-
entitlementSource
|
|
109
|
+
classifierSeat: deps.autoModeFace?.seatMounted === true,
|
|
110
|
+
entitlementSource,
|
|
111
|
+
intentArming: deps.autoModeFace?.engineArmsOnIntent === true,
|
|
112
|
+
...autoMode,
|
|
97
113
|
},
|
|
98
114
|
readFace: deps.config.readFace ?? null,
|
|
99
115
|
appendSystemPrompt: true,
|
|
@@ -141,8 +157,9 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
141
157
|
sendUserFile: Boolean(deps.sendUserFile && deps.config.sendUserFile?.publicEndpoint),
|
|
142
158
|
s3PublicEndpoint: deps.config.sendUserFile?.publicEndpoint ?? null,
|
|
143
159
|
sendUserFileLedger: Boolean(deps.sendFileLedger),
|
|
144
|
-
workflows:
|
|
160
|
+
workflows: workflowsEngineCan && workflowsDenial === null,
|
|
145
161
|
workflowsList: Boolean(deps.workflowRunStore),
|
|
162
|
+
workflowsGate: { engineCan: workflowsEngineCan, denial: workflowsDenial },
|
|
146
163
|
workflowModels: (() => {
|
|
147
164
|
const allow = deps.workflowModelAllowlist
|
|
148
165
|
?? workflowModelAllowlistFor({
|
|
@@ -99,7 +99,7 @@ export declare function buildClearanceRowWire(r: OriginClearanceRow): {
|
|
|
99
99
|
detail?: string | undefined;
|
|
100
100
|
eventId: string;
|
|
101
101
|
at: number;
|
|
102
|
-
to: "
|
|
102
|
+
to: "done" | "failed";
|
|
103
103
|
requestId: string;
|
|
104
104
|
}[];
|
|
105
105
|
custodyBytes: number;
|
|
@@ -107,7 +107,7 @@ export declare function buildClearanceRowWire(r: OriginClearanceRow): {
|
|
|
107
107
|
requestId: string;
|
|
108
108
|
reason: string;
|
|
109
109
|
at: number;
|
|
110
|
-
status: "pending" | "
|
|
110
|
+
status: "pending" | "done" | "failed";
|
|
111
111
|
originUnknownKeys?: string[] | undefined;
|
|
112
112
|
clearanceId: string;
|
|
113
113
|
entryId: string;
|
|
@@ -6,6 +6,7 @@ import { RULE_IMPORT_RETRY_AFTER_SEC, MAX_RULE_SCOPE_CHARS, parseRuleScope, seri
|
|
|
6
6
|
import { CONTROL_AND_BIDI_CHARS } from "../../text-bidi.js";
|
|
7
7
|
import { HttpError } from "../../security.js";
|
|
8
8
|
import { MAX_RULE_TEXT_CHARS } from "@sema-agent/core";
|
|
9
|
+
import { PERSISTED_RULE_MATCHES, PERSISTED_RULE_TOOLS } from "../../permission-rule-vocab.js";
|
|
9
10
|
const logger = createLogger();
|
|
10
11
|
export const RULES_CC_IMPORT_PREPARE_PATH = "/v1/rules/cc-import/prepare";
|
|
11
12
|
export const RULES_CC_IMPORT_REDEEM_PATH = "/v1/rules/cc-import/redeem";
|
|
@@ -45,8 +46,8 @@ const LocalImportRowSchema = z
|
|
|
45
46
|
.object({
|
|
46
47
|
rule: z.string().min(1).max(MAX_RULE_TEXT_CHARS),
|
|
47
48
|
scope: z.string().min(1).max(MAX_RULE_SCOPE_CHARS),
|
|
48
|
-
tool: z.
|
|
49
|
-
match: z.enum(
|
|
49
|
+
tool: z.enum(PERSISTED_RULE_TOOLS),
|
|
50
|
+
match: z.enum(PERSISTED_RULE_MATCHES),
|
|
50
51
|
command: z.string().min(1).max(MAX_RULE_TEXT_CHARS),
|
|
51
52
|
adds: z.array(LocalImportAddSchema).max(64),
|
|
52
53
|
})
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* [ref] A9 域模块 —— 异步 run 面与它的全部动词。
|
|
3
|
-
*
|
|
4
|
-
* 路由:`POST /v1/runs`(异步提交)· `GET /v1/runs/:id[/events]`(轮询 / 可恢复 SSE)· 运行期动词
|
|
5
|
-
* `cancel` / `steer` / `compact` / `detach` · 子代理面 `subagents/:target/{steer,resume,output,stream}` ·
|
|
6
|
-
* 通用任务句柄 `tasks/:handle/{output,stop}` · 三条 HITL 应答口 `elicitations|questions|tool-approvals/:id/respond`。
|
|
7
|
-
*
|
|
8
|
-
* 两段搬运:提交+run 级动词一段,子代理/任务句柄/HITL 应答一段——它们在 `handle()` 里原本被 SVC-5 的
|
|
9
|
-
* workflow steer 隔开,拆分保持各自位置(本文件里的先后只是同域归并)。`POST /v1/runs` 与 tasks 域共用
|
|
10
|
-
* `ctx.legs.prepareSpec`(同一条读体/校验/建 spec 的前段)。
|
|
11
|
-
*
|
|
12
|
-
* 可变状态全部来自跨域 registry(`idemCache` 幂等、`inflightRuns`/`preemptableRuns` 中止句柄、
|
|
13
|
-
* `cancelledViaVerb` first-writer-wins 判别、`steerableRuns` 活流句柄、`wakeParkMints` park 铸造闩)。
|
|
14
|
-
*/
|
|
15
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
16
2
|
import { ActorAssertionWire } from "@sema-agent/settings-schema";
|
|
17
3
|
import type { RouteCtx, PreparedTaskSubmission } from "../route-ctx.js";
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { memoRedactedRead, memoRedactTerminalResult, redactErrorMessage, redactHead, redactLedgerEventData, redactTerminalResult } from "../../observability/run-terminal-log.js";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { uuidv7, mintCheckpointToken, CheckpointError, validatePendingSteer } from "@sema-agent/core";
|
|
3
4
|
import { isTerminalRunStatus, isParkedRunStatus } from "../../plugins/store-contracts.js";
|
|
@@ -22,10 +23,12 @@ import { normalizeRunEventType } from "../../trace/project.js";
|
|
|
22
23
|
import { sendJson, sendError, httpErrorCode, sseHeaders } from "../send.js";
|
|
23
24
|
import { buildActiveRunConflict } from "../active-run-conflict.js";
|
|
24
25
|
import { clearTurnActivity, readTurnActivityMs } from "../../turn-activity.js";
|
|
26
|
+
import { buildCancelContext } from "../../run-cancel-context.js";
|
|
25
27
|
import { buildCrossSliceUsage, readResourceWindow, recordResourceWindow } from "../../resource-window.js";
|
|
26
28
|
import { headerStr, gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
27
29
|
import { ActorAssertionWire } from "@sema-agent/settings-schema";
|
|
28
30
|
import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
|
|
31
|
+
import { withPrincipal } from "../../observability/principal-context.js";
|
|
29
32
|
export const RUN_ID_RE = /^\/v1\/runs\/([^/]+)(\/events)?$/;
|
|
30
33
|
export const RUN_CANCEL_RE = /^\/v1\/runs\/([^/]+)\/cancel$/;
|
|
31
34
|
export const RUN_STEER_RE = /^\/v1\/runs\/([^/]+)\/steer$/;
|
|
@@ -136,7 +139,10 @@ async function streamRunEvents(req, res, deps, runStore, taskId, staleMs) {
|
|
|
136
139
|
retainedFrom: (id) => runStore.retainedFrom(id),
|
|
137
140
|
formatEvent: (ev) => {
|
|
138
141
|
const type = normalizeRunEventType(ev.type);
|
|
139
|
-
|
|
142
|
+
const data = type === "done" && ev.data && typeof ev.data === "object" && ev.data.result && typeof ev.data.result === "object"
|
|
143
|
+
? { ...ev.data, result: memoRedactTerminalResult("ev.done.result", ev.data.result) }
|
|
144
|
+
: type === "failed" ? memoRedactedRead("ev.failed", ev.data, (d) => redactLedgerEventData(type, d)) : redactLedgerEventData(type, ev.data);
|
|
145
|
+
return { id: ev.seq, event: type, data: { type, ...(data ?? {}) } };
|
|
140
146
|
},
|
|
141
147
|
staleFrame: () => ({ event: "failed", data: { type: "failed", errorMessage: "run stalled (instance lost?)" } }),
|
|
142
148
|
}, taskId, staleMs);
|
|
@@ -251,11 +257,11 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
251
257
|
status: stale ? "failed" : run.status,
|
|
252
258
|
...(lastActivityAt !== undefined ? { msSinceLastActivity: Math.max(0, Date.now() - lastActivityAt) } : {}),
|
|
253
259
|
...(crossSliceUsage !== undefined ? { crossSliceUsage } : {}),
|
|
254
|
-
result: run.result ?? undefined,
|
|
260
|
+
result: isTerminalRunStatus(run.status) ? memoRedactTerminalResult("run.result", run.result ?? undefined) : redactTerminalResult(run.result ?? undefined),
|
|
255
261
|
supervisorCost,
|
|
256
262
|
suggestions,
|
|
257
263
|
errorCode: stale ? undefined : (run.result?.errorCode ?? undefined),
|
|
258
|
-
error: stale ? "run stalled (instance lost?)" : run.error ?? undefined,
|
|
264
|
+
error: stale ? "run stalled (instance lost?)" : isTerminalRunStatus(run.status) ? memoRedactedRead("run.error", run.error ?? undefined, redactErrorMessage) : redactErrorMessage(run.error ?? undefined),
|
|
259
265
|
jobId: run.jobId ?? undefined,
|
|
260
266
|
source: run.source ?? undefined,
|
|
261
267
|
});
|
|
@@ -324,7 +330,8 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
324
330
|
}
|
|
325
331
|
const err = note;
|
|
326
332
|
try {
|
|
327
|
-
const
|
|
333
|
+
const cancelContext = buildCancelContext(taskId, Date.parse(run.createdAt));
|
|
334
|
+
const result = { taskId, sessionId: run.sessionId, status: "failed", errorCode: "cancelled", errorMessage: err, stats: { turns: 0, tokens: 0 }, ...(cancelContext ? { cancelContext } : {}) };
|
|
328
335
|
let lastErr;
|
|
329
336
|
for (let attempt = 0;; attempt++) {
|
|
330
337
|
try {
|
|
@@ -870,7 +877,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
870
877
|
deps.logger?.[level]?.("manual_compact_outcome", { taskId, outcome, note: outcome === "compacted" ? "a compacted{trigger:'manual'} event rode the run stream" : `no event (see compaction.${outcome === "mooted" ? "mooted" : outcome} trace / task terminal events)` });
|
|
871
878
|
}, (e) => {
|
|
872
879
|
if (e.code !== "steering.not_running") {
|
|
873
|
-
deps.logger?.warn?.("manual_compact_failed", { taskId, err: e instanceof Error ? e.message : String(e) });
|
|
880
|
+
deps.logger?.warn?.("manual_compact_failed", { taskId, err: redactHead(e instanceof Error ? e.message : String(e), 2000) });
|
|
874
881
|
}
|
|
875
882
|
else {
|
|
876
883
|
deps.logger?.info?.("manual_compact_not_running", { taskId, note: "stream settled between lookup and compact() — benign unless systematic" });
|
|
@@ -934,7 +941,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
934
941
|
}
|
|
935
942
|
async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
936
943
|
const { deps } = ctx;
|
|
937
|
-
const { readJson, rateLimited, quotaExceeded, leaseDenied, safeDecode, runSessionAcceptOk } = ctx.helpers;
|
|
944
|
+
const { readJson, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, safeDecode, runSessionAcceptOk } = ctx.helpers;
|
|
938
945
|
const subOutputMatch = req.method === "GET" ? RUN_SUBAGENT_OUTPUT_RE.exec(url) : null;
|
|
939
946
|
if (subOutputMatch) {
|
|
940
947
|
if (!deps.runStore || !deps.subagentTaskOutput) {
|
|
@@ -1159,7 +1166,12 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1159
1166
|
send409("resume.retain_off", "the parent run did not retain sub-agent sessions (set retainSubagentSessions on the run to enable revival)");
|
|
1160
1167
|
return;
|
|
1161
1168
|
}
|
|
1162
|
-
|
|
1169
|
+
if (ownerQuotaDenied(res, run.owner ?? undefined))
|
|
1170
|
+
return;
|
|
1171
|
+
if (await ownerLeaseDenied(res, run.owner ?? undefined))
|
|
1172
|
+
return;
|
|
1173
|
+
const resumeFn = handle.resume;
|
|
1174
|
+
const marker = await withPrincipal(run.owner ?? undefined, () => resumeFn(redacted));
|
|
1163
1175
|
sendJson(res, 200, { taskId: runId, target, status: "running", delivery: "applied", marker, note: `Message queued for delivery to ${handle.agentName ?? target}; it will continue in the background.` });
|
|
1164
1176
|
return;
|
|
1165
1177
|
}
|
|
@@ -1593,7 +1605,7 @@ export async function createDurableRun(req, res, ctx, prepared, idemKey) {
|
|
|
1593
1605
|
backend: deps.backend,
|
|
1594
1606
|
parkFacility: deps.checkpointStore !== undefined,
|
|
1595
1607
|
}).active,
|
|
1596
|
-
windowMs: deps.config.streamApproval?.windowMs
|
|
1608
|
+
windowMs: deps.config.streamApproval?.windowMs,
|
|
1597
1609
|
windowMarginMs: deps.config.streamAskWindowMarginMs,
|
|
1598
1610
|
}
|
|
1599
1611
|
: undefined, deps.checkpointStore);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { redactErrorMessage, redactHead, redactTerminalResult } from "../../observability/run-terminal-log.js";
|
|
1
2
|
import { runWithVerification, runCascade, uuidv7 } from "@sema-agent/core";
|
|
2
3
|
import { graftParkToolCallId, markChildrenStoppedByUserOnAbort, resumeAtHttpStatus, stripCheckpointToken, HEARTBEAT_MS } from "../../runs.js";
|
|
3
4
|
import { withPrincipal } from "../../observability/principal-context.js";
|
|
@@ -7,6 +8,7 @@ import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotific
|
|
|
7
8
|
import { createLedgerSink } from "../../trace/ledger-sink.js";
|
|
8
9
|
import { registerEngineNoticeLeg } from "../../trace/engine-notice-wire.js";
|
|
9
10
|
import { clearTurnActivity, readTurnActivityMs, recordTurnActivity } from "../../turn-activity.js";
|
|
11
|
+
import { buildCancelContext } from "../../run-cancel-context.js";
|
|
10
12
|
import { recordResourceWindow } from "../../resource-window.js";
|
|
11
13
|
import { redactSecrets } from "../../trace/redact.js";
|
|
12
14
|
import { contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, wiringManifestOperatorEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage } from "../../trace/project.js";
|
|
@@ -47,6 +49,13 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
47
49
|
const { prepareSpec, finalizeTaskResult } = ctx.legs;
|
|
48
50
|
const reqState = ctx.req;
|
|
49
51
|
const source = ctx.req.source;
|
|
52
|
+
const noteRunOutcome = (r) => {
|
|
53
|
+
if (!r?.status)
|
|
54
|
+
return;
|
|
55
|
+
reqState.runStatus = r.status;
|
|
56
|
+
if (r.errorCode)
|
|
57
|
+
reqState.runErrorCode = r.errorCode;
|
|
58
|
+
};
|
|
50
59
|
if (req.method === "POST" && (url === "/v1/tasks" || url === "/v1/tasks/stream")) {
|
|
51
60
|
const rawIdem = headerStr(req.headers["idempotency-key"]);
|
|
52
61
|
const idemKey = rawIdem ? scopedIdempotencyKey(rawIdem, source, gatedPrincipal(req, deps.config)) : undefined;
|
|
@@ -88,6 +97,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
88
97
|
return;
|
|
89
98
|
}
|
|
90
99
|
const earlyDurableTid = deps.runStore && prepared.spec.sessionId ? uuidv7() : undefined;
|
|
100
|
+
const streamLegStartedAt = Date.now();
|
|
91
101
|
const detachOnDisconnect = headerStr(req.headers["x-detach-on-disconnect"]) === "true";
|
|
92
102
|
if (detachOnDisconnect && earlyDurableTid === undefined) {
|
|
93
103
|
sendError(res, 400, "request.precondition_unmet", "x-detach-on-disconnect requires a durable run (a run store + sessionId) — without one the detached result would be unqueryable");
|
|
@@ -178,6 +188,19 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
178
188
|
let runRowSettled = false;
|
|
179
189
|
let durableTaskId;
|
|
180
190
|
let durableHeartbeat;
|
|
191
|
+
const mintCancelledResult = (errorMessage) => {
|
|
192
|
+
const ctx = durableTaskId === undefined ? undefined : buildCancelContext(durableTaskId, streamLegStartedAt);
|
|
193
|
+
return {
|
|
194
|
+
taskId: durableTaskId ?? "",
|
|
195
|
+
sessionId: prepared.spec.sessionId ?? "",
|
|
196
|
+
status: "failed",
|
|
197
|
+
result: "",
|
|
198
|
+
errorCode: "cancelled",
|
|
199
|
+
errorMessage,
|
|
200
|
+
stats: { turns: 0, tokens: 0 },
|
|
201
|
+
...(ctx ? { cancelContext: ctx } : {}),
|
|
202
|
+
};
|
|
203
|
+
};
|
|
181
204
|
let userMsgEntryId;
|
|
182
205
|
let anchorPut = false;
|
|
183
206
|
const putRewindAnchor = () => {
|
|
@@ -211,7 +234,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
211
234
|
if (detachOnDisconnect && durableTaskId && deps.runStore) {
|
|
212
235
|
const rs = deps.runStore;
|
|
213
236
|
const tid = durableTaskId;
|
|
214
|
-
ledgerSink = createLedgerSink({ appendEvent: (seq, type, data) => rs.appendEvent(tid, seq, type, data), persistThinking: deps.config.traceThinking, onActivity: () => recordTurnActivity(tid) });
|
|
237
|
+
ledgerSink = createLedgerSink({ appendEvent: (seq, type, data) => rs.appendEvent(tid, seq, type, data), persistThinking: deps.config.traceThinking, onActivity: (kind, statusData) => recordTurnActivity(tid, kind, statusData) });
|
|
215
238
|
usageKey = durableTaskId;
|
|
216
239
|
if (usageKey)
|
|
217
240
|
deps.modelUsage?.register(usageKey);
|
|
@@ -380,13 +403,14 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
380
403
|
if (ev.type === "message_committed" && ev.role === "user")
|
|
381
404
|
userMsgEntryId = ev.entryId;
|
|
382
405
|
if (ev.type === "done") {
|
|
383
|
-
finalResult = stripCheckpointToken(await graftParkToolCallId(ev.result, deps.checkpointStore));
|
|
406
|
+
finalResult = redactTerminalResult(stripCheckpointToken(await graftParkToolCallId(ev.result, deps.checkpointStore)));
|
|
384
407
|
if (durableTaskId && finalResult)
|
|
385
408
|
finalResult = { ...finalResult, taskId: durableTaskId };
|
|
386
409
|
if (durableTaskId && cancelledViaVerb.has(durableTaskId) && finalResult?.status === "failed" && !finalResult.errorCode) {
|
|
387
410
|
finalResult = { ...finalResult, errorCode: "cancelled" };
|
|
388
411
|
}
|
|
389
412
|
finalizeTaskResult(ev.result, principal, prepared.spec.objective, prepared.spec.sessionId);
|
|
413
|
+
noteRunOutcome(finalResult);
|
|
390
414
|
putRewindAnchor();
|
|
391
415
|
settleFleet(finalResult?.status ?? "completed", fleetRunResiduals(finalResult));
|
|
392
416
|
if (ledgerSink && finalResult) {
|
|
@@ -530,7 +554,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
530
554
|
const legWalltimeMs = typeof prepared.spec.limits?.maxWalltimeMs === "number" ? prepared.spec.limits.maxWalltimeMs : undefined;
|
|
531
555
|
const approvalLeg = resolveApprovalLeg({
|
|
532
556
|
streamApprovalOn,
|
|
533
|
-
windowMs: deps.config.streamApproval?.windowMs
|
|
557
|
+
windowMs: deps.config.streamApproval?.windowMs,
|
|
534
558
|
windowMarginMs: deps.config.streamAskWindowMarginMs,
|
|
535
559
|
...(legWalltimeMs !== undefined ? { legWalltimeMs } : {}),
|
|
536
560
|
nowMonotonicMs: performance.now(),
|
|
@@ -618,13 +642,14 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
618
642
|
}
|
|
619
643
|
else {
|
|
620
644
|
const dcErr = "stream client disconnected before completion (run aborted)";
|
|
621
|
-
const dc =
|
|
645
|
+
const dc = mintCancelledResult(dcErr);
|
|
622
646
|
if (ledgerSink && !ledgerTerminal) {
|
|
623
647
|
ledgerTerminal = true;
|
|
624
648
|
await ledgerSink.flush().catch(() => undefined);
|
|
625
649
|
await ledgerSink.append("failed", { errorMessage: dcErr, errorCode: "cancelled" }).catch(() => undefined);
|
|
626
650
|
}
|
|
627
651
|
await deps.runStore.setTerminal(durableTaskId, "failed", dc, dcErr);
|
|
652
|
+
noteRunOutcome(dc);
|
|
628
653
|
settleFleet("failed");
|
|
629
654
|
}
|
|
630
655
|
}
|
|
@@ -668,7 +693,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
668
693
|
}
|
|
669
694
|
else if (ac.signal.aborted) {
|
|
670
695
|
const err = "stream aborted before completion (run cancelled)";
|
|
671
|
-
const c =
|
|
696
|
+
const c = mintCancelledResult(err);
|
|
672
697
|
if (ledgerSink && !ledgerTerminal) {
|
|
673
698
|
ledgerTerminal = true;
|
|
674
699
|
await ledgerSink.flush().catch(() => undefined);
|
|
@@ -699,7 +724,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
699
724
|
resp = await idemCache.run(idemKey, runStreamSubmitLeg, (r) => r.body?.status === "completed");
|
|
700
725
|
}
|
|
701
726
|
catch (e) {
|
|
702
|
-
deps.logger?.error("stream_error", { closed, err: e instanceof Error ? e.message : String(e) });
|
|
727
|
+
deps.logger?.error("stream_error", { closed, err: redactHead(e instanceof Error ? e.message : String(e), 2000) });
|
|
703
728
|
if (!closed)
|
|
704
729
|
throw e;
|
|
705
730
|
}
|
|
@@ -790,7 +815,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
790
815
|
const retryAfterSec = result.errorCode === "memory.admission_required" && result.retryAfterMs !== undefined
|
|
791
816
|
? Math.max(1, Math.ceil(result.retryAfterMs / 1000))
|
|
792
817
|
: undefined;
|
|
793
|
-
return { status: resumeAtStatus, body: { errorCode: result.errorCode, error: result.errorMessage ?? "resume-at failed", ...(retryAfterSec !== undefined ? { retryAfterSec } : {}) } };
|
|
818
|
+
return { status: resumeAtStatus, body: { errorCode: result.errorCode, error: redactErrorMessage(result.errorMessage) ?? "resume-at failed", ...(retryAfterSec !== undefined ? { retryAfterSec } : {}) } };
|
|
794
819
|
}
|
|
795
820
|
if (result.status === "suspended" && deps.checkpointStore && deps.runStore && prepared.spec.sessionId) {
|
|
796
821
|
if (durableTaskId)
|
|
@@ -805,7 +830,8 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
805
830
|
if (durableTaskId && deps.runStore)
|
|
806
831
|
await deps.runStore.setTerminal(durableTaskId, result.status, stripCheckpointToken(result), result.errorMessage ?? null);
|
|
807
832
|
finalizeTaskResult(result, principal, prepared.spec.objective, prepared.spec.sessionId);
|
|
808
|
-
|
|
833
|
+
noteRunOutcome(result);
|
|
834
|
+
return { status: 200, body: withDiscoveryNotice(redactTerminalResult(stripCheckpointToken(result))) };
|
|
809
835
|
};
|
|
810
836
|
const resp = await idemCache.run(idemKey, runSyncSubmitLeg, (r) => r.status < 400);
|
|
811
837
|
sendSubmitResult(res, resp);
|
|
@@ -2,6 +2,10 @@ import { z } from "zod";
|
|
|
2
2
|
import { getWorkflowRun, subscribeWorkflow, deriveAgentDisplayStatus, callKeyOrdinal } from "@sema-agent/core";
|
|
3
3
|
import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from "../../orchestration/workflow-agent-steer.js";
|
|
4
4
|
import { redactSecrets } from "../../trace/redact.js";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { redactErrorMessage, redactHead } from "../../observability/run-terminal-log.js";
|
|
7
|
+
const JOURNAL_PROJECTION_MEMO_CAP = 1024;
|
|
8
|
+
const journalProjectionMemo = new Map();
|
|
5
9
|
import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
6
10
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
7
11
|
const WORKFLOWS_RE = /^\/v1\/workflows(?:\/([^/]+)(\/stream|\/journal)?)?$/;
|
|
@@ -68,16 +72,27 @@ async function handleWorkflowsReadBody(req, res, url, ctx, miss) {
|
|
|
68
72
|
const jLimit = Math.min(50, Math.max(1, Number(jq.get("limit") ?? 20) || 20));
|
|
69
73
|
const jOffset = Math.max(0, Number(jq.get("offset") ?? 0) || 0);
|
|
70
74
|
const MAX_ROW_BYTES = 64 * 1024;
|
|
71
|
-
const firstLine = (t) => (t ?
|
|
75
|
+
const firstLine = (t) => (t ? redactHead(t.split("\n")[0], 300) : undefined);
|
|
72
76
|
const MAX_RESULT_CHARS = 2000;
|
|
73
|
-
const projectResult = (callKey, r) =>
|
|
77
|
+
const projectResult = (callKey, r) => {
|
|
78
|
+
const memoKey = `${wfId}\u0000${callKey}\u0000${createHash("sha256").update(JSON.stringify(r)).digest("hex")}`;
|
|
79
|
+
const hit = journalProjectionMemo.get(memoKey);
|
|
80
|
+
if (hit)
|
|
81
|
+
return hit;
|
|
82
|
+
const out = projectResultUncached(callKey, r);
|
|
83
|
+
if (journalProjectionMemo.size >= JOURNAL_PROJECTION_MEMO_CAP)
|
|
84
|
+
journalProjectionMemo.delete(journalProjectionMemo.keys().next().value);
|
|
85
|
+
journalProjectionMemo.set(memoKey, out);
|
|
86
|
+
return out;
|
|
87
|
+
};
|
|
88
|
+
const projectResultUncached = (callKey, r) => ({
|
|
74
89
|
callKey,
|
|
75
90
|
ordinal: callKeyOrdinal(callKey),
|
|
76
91
|
status: r.status,
|
|
77
92
|
...(r.errorMessage ? { error: firstLine(r.errorMessage) } : {}),
|
|
78
93
|
...(r.result
|
|
79
94
|
? {
|
|
80
|
-
result:
|
|
95
|
+
result: redactHead(r.result, MAX_RESULT_CHARS),
|
|
81
96
|
...(r.result.length > MAX_RESULT_CHARS ? { resultTruncated: true, resultChars: r.result.length } : {}),
|
|
82
97
|
}
|
|
83
98
|
: {}),
|
|
@@ -310,7 +325,7 @@ function summarizeWorkflowDetail(run) {
|
|
|
310
325
|
createdAt: run.createdAt,
|
|
311
326
|
durationMs: durationOf(run),
|
|
312
327
|
rev: run.rev,
|
|
313
|
-
error: run.error,
|
|
328
|
+
...(run.error !== undefined ? { error: redactErrorMessage(run.error) ?? "" } : {}),
|
|
314
329
|
...(run.result !== undefined ? { result: redactSecrets(run.result) } : {}),
|
|
315
330
|
phases,
|
|
316
331
|
...(unphasedView ? { unphased: unphasedView } : {}),
|
package/dist/http/server.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { type FleetEventBus } from "../fleet/fleet-bus.js";
|
|
|
18
18
|
import type { ElicitationCoordinator } from "../elicitation.js";
|
|
19
19
|
import type { QuestionCoordinator } from "../question.js";
|
|
20
20
|
import { type ParkedAskRedeem, type ToolApprovalCoordinator } from "../tool-approval.js";
|
|
21
|
-
import type { ApprovalAskAuditReadFace } from "../approval-ask-audit-store.js";
|
|
21
|
+
import type { ApprovalAskAuditReadFace, ApprovalLateSettleSink } from "../approval-ask-audit-store.js";
|
|
22
22
|
import type { PlanCacheProbe } from "../plan-cache-probe.js";
|
|
23
23
|
import type { Logger } from "../observability/logger.js";
|
|
24
24
|
import type { Metrics } from "../observability/metrics.js";
|
|
@@ -210,6 +210,11 @@ export interface ServiceCoordinatorDeps {
|
|
|
210
210
|
* SQL 车道恒缺席(那里有真 ask 店,[ref])—— 键缺席 = 本部署无此面,`[]` = 有面且无崩溃残账,
|
|
211
211
|
* 两义与 `livePending` 同律可判别。 */
|
|
212
212
|
approvalAskAudit?: ApprovalAskAuditReadFace;
|
|
213
|
+
/** [ref] 迟到人工结算的**写面**(与 {@link approvalAskAudit} 同一个店实例,两把窄口:读面喂
|
|
214
|
+
* `GET /v1/approvals` 的 `crashConverged`,写面只有 `recordLateSettle` 一个动词 —— 铸造/决议/腿闭
|
|
215
|
+
* 三点的属主是协调器,路由层拿到它们只会长出第二个写者)。在场判据与读面逐字同源(local 车道 ∧
|
|
216
|
+
* 审批面开着);缺席 ⇒ 两条 decide 腿**不写、不抛**,行为与修前逐字相同(SQL 车道恒缺席)。 */
|
|
217
|
+
approvalLateSettle?: ApprovalLateSettleSink;
|
|
213
218
|
/** SVC-5 ([ref] CORE-5 #6): the process-local registry of STEERABLE workflow-agent handles
|
|
214
219
|
* (`ctx.agentStream`) live on THIS replica. Enables POST /v1/workflows/:id/agents/:label/steer. See
|
|
215
220
|
* workflow-agent-steer.ts for the handle-visibility seam status (the registry is real + ready; core does not
|
|
@@ -539,6 +544,11 @@ export interface ServiceGovernanceDeps {
|
|
|
539
544
|
* pre-admission 读窗用(主力 /v1/runs 是 202 异步,core 进场拒发生在 202 之后永远变不成 429;
|
|
540
545
|
* 这里是异步车道唯一的 429 出路)。enforcement 真源在 core(RunnerDeps 同店),此门是快速座。 */
|
|
541
546
|
usageWindowStore?: import("@sema-agent/core").UsageWindowStore;
|
|
547
|
+
/** [ref] ④([ref]/[ref],core [ref] 合修):auto 模式自查读面的三件装配事实(席位在不在 / 合成后的
|
|
548
|
+
* per-principal caps resolver / 分类器配置路由)。`/v1/capabilities.permissionModeAuto.{armed,reason,model}`
|
|
549
|
+
* 只消费它,不自算 —— 席位与 resolver 都必须是递给 `RunnerDeps` 的**同一只**(main.ts 装配),否则又是
|
|
550
|
+
* 「诊断说亮、位说暗」。缺席(桩/一次性 CLI)⇒ 读面如实答 `deployment_incapable`。 */
|
|
551
|
+
autoModeFace?: import("../auto-mode-face.js").AutoModeFaceDeps;
|
|
542
552
|
}
|
|
543
553
|
/** **部署自述面**:只被 /health、/v1/capabilities 与提交前置门读的部署事实(多为 live getter,
|
|
544
554
|
* 不是快照——/health 必须回答「此刻」)。 */
|