@sema-agent/server 7.71.0 → 7.73.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 +1 -1
- package/USAGE.md +12 -1
- package/dist/approval-card.d.ts +16 -17
- package/dist/approval-card.js +0 -9
- package/dist/boot/config-center.d.ts +6 -1
- package/dist/boot/config-center.js +3 -1
- package/dist/boot/memory-boundary.d.ts +27 -5
- package/dist/boot/memory-boundary.js +6 -6
- package/dist/boot/memory-consolidation.js +2 -1
- package/dist/boot/runner-deps.d.ts +32 -16
- package/dist/boot/runner-deps.js +3 -1
- package/dist/boot/stores.js +6 -4
- package/dist/boot/unpriced-ceiling-verdict.d.ts +15 -0
- package/dist/boot/unpriced-ceiling-verdict.js +39 -0
- package/dist/brain.js +2 -9
- package/dist/budget.d.ts +21 -2
- package/dist/budget.js +3 -3
- package/dist/capabilities/tool-defer.d.ts +18 -12
- package/dist/config-catalog.js +12 -12
- package/dist/config-center/apply-effective.js +2 -4
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/restart-signal.js +2 -2
- package/dist/config-center/skills-mcp.js +15 -0
- package/dist/config-types.d.ts +83 -23
- package/dist/config-types.js +19 -1
- package/dist/config.d.ts +58 -0
- package/dist/config.js +76 -22
- package/dist/http/active-run-conflict.d.ts +3 -2
- package/dist/http/durable-run-leg.d.ts +64 -0
- package/dist/http/durable-run-leg.js +63 -0
- package/dist/http/route-ctx.d.ts +14 -0
- package/dist/http/route-ctx.js +3 -0
- package/dist/http/routes/approvals-assistant.js +3 -3
- package/dist/http/routes/capabilities.js +2 -2
- package/dist/http/routes/runs.js +23 -68
- package/dist/http/routes/workflows.js +1 -0
- package/dist/http/server.js +70 -24
- package/dist/main.js +4 -1
- package/dist/memory-bundle-engine.d.ts +4 -3
- package/dist/memory-bundle-engine.js +2 -1
- package/dist/memory-incident-sink.d.ts +37 -0
- package/dist/memory-incident-sink.js +13 -0
- package/dist/memory-operator-faces.d.ts +9 -3
- package/dist/memory-operator-faces.js +4 -3
- package/dist/memory-scope.d.ts +37 -1
- package/dist/memory-scope.js +12 -2
- package/dist/observability/fail-open.d.ts +1 -1
- package/dist/observability/fail-open.js +1 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/tool-trace.d.ts +18 -1
- package/dist/observability/tool-trace.js +4 -3
- package/dist/parked-decide.js +3 -2
- package/dist/plugins/e2b-orphan-reclaim.d.ts +4 -4
- package/dist/plugins/e2b-orphan-reclaim.js +18 -20
- package/dist/plugins/file-run-store.js +1 -0
- package/dist/plugins/memory-run-store.js +1 -0
- package/dist/plugins/run-store-sql.d.ts +5 -2
- package/dist/plugins/run-store-sql.js +5 -3
- package/dist/plugins/store-contracts.d.ts +41 -0
- package/dist/plugins/store-contracts.js +3 -2
- package/dist/run-cancel-context.js +1 -1
- package/dist/run-local.js +19 -8
- package/dist/runs.d.ts +16 -2
- package/dist/runs.js +2 -1
- package/dist/runtime-caps-resolver.js +5 -3
- package/dist/spec-fields.d.ts +4 -2
- package/dist/task-a2a.js +3 -2
- package/dist/task-mcp.js +3 -2
- package/dist/tool-approval.d.ts +26 -21
- package/dist/tool-approval.js +2 -2
- package/dist/trace/core-keyset-guard.d.ts +17 -5
- package/dist/trace/project.d.ts +55 -0
- package/dist/trace/project.js +45 -3
- package/dist/usage-analytics.d.ts +20 -2
- package/dist/usage-analytics.js +9 -1
- package/dist/wire-name-bound.d.ts +14 -0
- package/dist/wire-name-bound.js +13 -0
- package/package.json +2 -2
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **一条 durable run 的「建行 + 驱动」单一属主**(S-218 提取;此前整段住在 `routes/runs.ts` 的
|
|
3
|
+
* `createDurableRun` 里,只服务 HTTP/A2A 两条提交门)。
|
|
4
|
+
*
|
|
5
|
+
* 🔴 为什么提成模块:`/decide` 的 workflow 第三条车道在**宿主不在 park 上**时,同样要在宿主会话上起
|
|
6
|
+
* 一跑新 turn 把决定交给宿主的模型(S-218)。这条链上挂着 `createRun` 的会话 claim(以及它唯一诚实的
|
|
7
|
+
* 409 出路)、checkpoint ctx 落库、fleet 行发布、resume/rewind 两族锚点捕获、审批 ALS、通告投递……
|
|
8
|
+
* 手抄第二份 = 把这十来件事复制成两份会漂的实现,而漂开的那天没有任何东西会说话(`createDurableRun`
|
|
9
|
+
* 当年从路由体里提取出来时,头注写的就是同一条判词)。方向:`routes/runs.ts` 与 `http/server.ts`
|
|
10
|
+
* 都 import 本模块,本模块谁都不 import —— 与 `http/run-meta.ts` 同一条「两个域共用一件 ⇒ 单独成文件」。
|
|
11
|
+
*
|
|
12
|
+
* 边界(本腿**不**含的东西,留给各自的门):`body.taskId` 幂等重放、`Idempotency-Key` 内存去重、
|
|
13
|
+
* lease / 治理窗 pre-admission、属主门。它们是**提交面**的判据(读请求、写响应),而本腿只吃已经备好的
|
|
14
|
+
* `PreparedTaskSubmission` 并交回一份收据 —— 这正是它能被一条没有 `ServerResponse` 的内部车道复用的原因。
|
|
15
|
+
*/
|
|
16
|
+
import { type RunInternals } from "@sema-agent/core";
|
|
17
|
+
import type { RunStore } from "../plugins/store-backend.js";
|
|
18
|
+
import type { PreparedTaskSubmission, RouteCtxBase } from "./route-ctx.js";
|
|
19
|
+
/**
|
|
20
|
+
* 🔴 **TRUSTED run internals** —— 由**部署方**(不是模型、不是请求体、不是工具参数)铸给这一跑的
|
|
21
|
+
* core 内部席位。今天唯一成员 `workflowParkedResume` = 人对一条 parked workflow 子代的决定
|
|
22
|
+
* (语义属主 = core `RunInternals.workflowParkedResume` 的 d.ts 顶注:「A host that decided a workflow
|
|
23
|
+
* child's parked checkpoint launches the run that re-invokes `Workflow({resumeFromRunId})` with the
|
|
24
|
+
* decision here」)。
|
|
25
|
+
*
|
|
26
|
+
* 为什么单独命名而不是直接摊 `RunInternals`:`RunInternals` 里绝大多数席位是**执行腿自己**铸的
|
|
27
|
+
* (onForwardEvent / onTaskNotification / onActivity …),把整只类型开给调用方等于邀请第二个写者;
|
|
28
|
+
* 本类型只开「部署方可以决定的那一格」,新增一格时**必须**在这里显式点名,编译期看得见。
|
|
29
|
+
* 缺席 ⇒ 对两条腿都是完整 no-op。
|
|
30
|
+
*/
|
|
31
|
+
export type TrustedRunInternals = Pick<RunInternals, "workflowParkedResume">;
|
|
32
|
+
export interface DurableRunLegArgs {
|
|
33
|
+
/** `prepareSpec` 的产物,或内部车道按同一条 `resolveSpec` 装配出来的等价形。 */
|
|
34
|
+
prepared: PreparedTaskSubmission;
|
|
35
|
+
/** 调用方自带的 taskId(`POST /v1/runs` 的 `body.taskId` 幂等键);缺席 ⇒ 本腿铸 uuidv7。 */
|
|
36
|
+
clientTaskId?: string;
|
|
37
|
+
/** 🔒 这条 run 的 **fleet 行 scope** —— 必须与 `GET /v1/fleet/stream` 过滤用的是同一个身份,且绝不是
|
|
38
|
+
* 自报的 `x-agent-principal` 头(自报头会把行打到受害者的 scope 上,或打到 `default` 而真属主的流把它
|
|
39
|
+
* 过滤掉)。提交门传 `gatedPrincipal(req) ?? "default"`;`/decide` 的第三条车道传 resume 家族的
|
|
40
|
+
* `resumeFleetScope(req, auth)`(同一条 wire 上两条腿因此同答)。 */
|
|
41
|
+
fleetScope: string;
|
|
42
|
+
/** 🔒 这条 run 的 **elicit / question 应答方**(E23:与 respond 门读的是同一个已验身份,不是自报头)。
|
|
43
|
+
* 提交门传 `gatedPrincipal(req) ?? null` = 提交者本人;`/decide` 的第三条车道传**卡属主**(= 原提交者,
|
|
44
|
+
* 不是代为按下按钮的 operator —— 与 resume 腿那条成文口径逐字同源)。`null` = 匿名/开放部署。 */
|
|
45
|
+
elicitOwner: string | null;
|
|
46
|
+
/** run 行元数据的 `source` 列(调用方的系统身份;内部车道传自己的车道名)。 */
|
|
47
|
+
source: string | null;
|
|
48
|
+
/** 见 {@link TrustedRunInternals}。缺席 ⇒ 与本位存在之前逐字相同。 */
|
|
49
|
+
trustedInternals?: TrustedRunInternals;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 建行 + 驱动。返回 `202` = 新行已建并在跑(体带 `taskId`/`sessionId`);`409` = 会话被别的 run 占着,
|
|
53
|
+
* **没有**建行(体 = `buildActiveRunConflict` 的既有形,含 `errorCode: "conflict.session_active_run"`)。
|
|
54
|
+
* 协议渲染归调用方(HTTP `sendJson` / A2A 协议对象 / `/decide` 的受理体)。
|
|
55
|
+
*/
|
|
56
|
+
export declare function mintAndDriveDurableRun(ctx: RouteCtxBase, runStore: RunStore, args: DurableRunLegArgs): Promise<{
|
|
57
|
+
status: number;
|
|
58
|
+
body: {
|
|
59
|
+
taskId: string;
|
|
60
|
+
sessionId: string;
|
|
61
|
+
status: string;
|
|
62
|
+
} | object;
|
|
63
|
+
}>;
|
|
64
|
+
//# sourceMappingURL=durable-run-leg.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { uuidv7 } from "@sema-agent/core";
|
|
2
|
+
import { runInBackground } from "../runs.js";
|
|
3
|
+
import { fleetRunPublisher, fleetRunLabels } from "../fleet/fleet-bus.js";
|
|
4
|
+
import { cascadeConfig, runMeta } from "./run-meta.js";
|
|
5
|
+
import { resolveStreamApprovalGate } from "../tool-approval.js";
|
|
6
|
+
import { buildActiveRunConflict } from "./active-run-conflict.js";
|
|
7
|
+
import { clearTurnActivity, readTurnActivityMs } from "../turn-activity.js";
|
|
8
|
+
import { recordResourceWindow } from "../resource-window.js";
|
|
9
|
+
export async function mintAndDriveDurableRun(ctx, runStore, args) {
|
|
10
|
+
const { deps } = ctx;
|
|
11
|
+
const { inflightRuns, preemptableRuns, steerableRuns } = ctx.registry;
|
|
12
|
+
const sessionId = args.prepared.spec.sessionId ?? uuidv7();
|
|
13
|
+
const taskId = args.clientTaskId ?? uuidv7();
|
|
14
|
+
const created = await runStore.createRun(taskId, sessionId, args.prepared.auth?.principal ?? null, deps.instanceId ?? "default", runMeta(args.prepared, args.source));
|
|
15
|
+
if (created.ok)
|
|
16
|
+
clearTurnActivity(taskId);
|
|
17
|
+
if (created.ok)
|
|
18
|
+
recordResourceWindow(taskId, args.prepared.spec.resourceSuspend);
|
|
19
|
+
if (created.ok)
|
|
20
|
+
deps.sessionTitler?.maybeTitle(sessionId, args.prepared.spec.objective, typeof args.prepared.spec.model === "string" ? args.prepared.spec.model : args.prepared.spec.model?.id);
|
|
21
|
+
if (!created.ok) {
|
|
22
|
+
if (args.clientTaskId && created.activeTaskId === args.clientTaskId) {
|
|
23
|
+
return { status: 202, body: { taskId: args.clientTaskId, sessionId, status: "running" } };
|
|
24
|
+
}
|
|
25
|
+
return { status: 409, body: await buildActiveRunConflict({ runStore, checkpointStore: deps.checkpointStore, governance: deps.config, runStaleSec: deps.config.runStaleSec, turnActivity: readTurnActivityMs }, sessionId, created.activeTaskId) };
|
|
26
|
+
}
|
|
27
|
+
if (deps.checkpointStore) {
|
|
28
|
+
await deps.checkpointStore.putCtx(sessionId, { body: args.prepared.body, memoryScope: args.prepared.auth?.memoryScope });
|
|
29
|
+
}
|
|
30
|
+
const anchorOwner = args.prepared.auth?.principal ?? null;
|
|
31
|
+
const getLeafId = deps.sessionStorage?.getLeafId?.bind(deps.sessionStorage);
|
|
32
|
+
const captureTurnAnchor = deps.resumeAnchorStore && getLeafId
|
|
33
|
+
? async (eventId) => {
|
|
34
|
+
const leaf = await getLeafId(sessionId);
|
|
35
|
+
if (leaf)
|
|
36
|
+
await deps.resumeAnchorStore.put(sessionId, eventId, leaf, anchorOwner);
|
|
37
|
+
}
|
|
38
|
+
: undefined;
|
|
39
|
+
const captureUserMessageAnchor = deps.resumeAnchorStore
|
|
40
|
+
? async (entryId) => { await deps.resumeAnchorStore.put(sessionId, taskId, entryId, anchorOwner); }
|
|
41
|
+
: undefined;
|
|
42
|
+
const fleetPub = fleetRunPublisher(deps.fleetBus, {
|
|
43
|
+
runId: taskId,
|
|
44
|
+
scope: args.fleetScope,
|
|
45
|
+
rootTaskId: taskId,
|
|
46
|
+
...fleetRunLabels(args.prepared.spec.objective),
|
|
47
|
+
});
|
|
48
|
+
void runInBackground(args.prepared.runner, { ...args.prepared.spec, sessionId }, runStore, taskId, deps.metrics, args.prepared.auth?.principal, args.prepared.verify, args.prepared.cascade ? cascadeConfig(deps.config.cascadeLadder, args.prepared.spec.limits?.maxCostUsd) : undefined, deps.instrumentDegenerate, deps.planCacheProbe, deps.config.traceThinking, inflightRuns, preemptableRuns, steerableRuns, deps.modelUsage, deps.elicitation, args.elicitOwner, captureTurnAnchor, fleetPub, captureUserMessageAnchor, deps.question, deps.workflowCompletionInbox, deps.subagentSteerRegistry, (m, x) => deps.logger?.info?.(m, x), deps.sendUserFile, deps.promptManifests, deps.toolApproval
|
|
49
|
+
? {
|
|
50
|
+
coordinator: deps.toolApproval,
|
|
51
|
+
streamApprovalOn: resolveStreamApprovalGate({
|
|
52
|
+
toolApprovalEnabled: true,
|
|
53
|
+
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
54
|
+
backend: deps.backend,
|
|
55
|
+
parkFacility: deps.checkpointStore !== undefined,
|
|
56
|
+
}).active,
|
|
57
|
+
windowMs: deps.config.streamApproval?.windowMs,
|
|
58
|
+
windowMarginMs: deps.config.streamAskWindowMarginMs,
|
|
59
|
+
}
|
|
60
|
+
: undefined, deps.checkpointStore, args.prepared.engineNotices, args.trustedInternals);
|
|
61
|
+
return { status: 202, body: { taskId, sessionId, status: "running" } };
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=durable-run-leg.js.map
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -438,6 +438,20 @@ export interface RouteMiss {
|
|
|
438
438
|
* 两臂逐字节同形,指路价值不丢。钉=audit-fixes-http「[ref]」格(两臂全文比对);文案变更走
|
|
439
439
|
* api-error-text-freeze 基线流程。
|
|
440
440
|
*/
|
|
441
|
+
/**
|
|
442
|
+
* **「决定/续跑已受理」那一枚 200 收据的单一铸点**(S-218)。
|
|
443
|
+
*
|
|
444
|
+
* 两条腿投同一份体:①resume 家族的受理点(`driveResumeLeg`,宿主停在 park 上时);②`/decide` 的
|
|
445
|
+
* workflow 第三条车道在宿主**不在** park 上时新铸的那一跑。契约上这两条腿说的是同一句话 ——
|
|
446
|
+
* 「决定已被受理,拿 `taskId` 去 poll `GET /v1/runs/:id` / `/events` 跟终态」——所以消费端**一条分支
|
|
447
|
+
* 都不该加**;写成两份字面量,漂开的那天没有任何东西会说话。
|
|
448
|
+
*
|
|
449
|
+
* `bindingEnforced` 的语义不因铸点而变(「这次决定按绑定坐标受理,不是盲目放行」),故两腿同带。
|
|
450
|
+
*/
|
|
451
|
+
export declare function acceptedResumingReceipt(taskId: string, sessionId: string): {
|
|
452
|
+
status: number;
|
|
453
|
+
body: object;
|
|
454
|
+
};
|
|
441
455
|
export declare const RUN_NOT_FOUND_MESSAGE = "run not found \u2014 the id belongs to no run in this deployment's run store (a run from another server process, or an in-memory store that did not survive a restart, is not visible here)";
|
|
442
456
|
/** [ref] 前缀=域判别子既成契约:a\* 与 wa\* 形 id 给 TaskOutput/journal 指路;其余给 {@link RUN_NOT_FOUND_MESSAGE}。 */
|
|
443
457
|
export declare function runNotFoundMessage(id: string): string;
|
package/dist/http/route-ctx.js
CHANGED
|
@@ -17,6 +17,9 @@ export function sendResumeOutcome(res, out, logger, extraBody) {
|
|
|
17
17
|
res.setHeader("retry-after", String(Math.ceil(wait)));
|
|
18
18
|
sendJson(res, out.status, extraBody === undefined ? out.body : { ...out.body, ...extraBody });
|
|
19
19
|
}
|
|
20
|
+
export function acceptedResumingReceipt(taskId, sessionId) {
|
|
21
|
+
return { status: 200, body: { taskId, sessionId, status: "resuming", bindingEnforced: true } };
|
|
22
|
+
}
|
|
20
23
|
export const RUN_NOT_FOUND_MESSAGE = "run not found — the id belongs to no run in this deployment's run store (a run from another server process, or an in-memory store that did not survive a restart, is not visible here)";
|
|
21
24
|
export function runNotFoundMessage(id) {
|
|
22
25
|
const aStar = /^(?:a|wa)[0-9a-f]{4,}$/i.test(id);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { screenedStoredGate } from "../../trace/project.js";
|
|
2
2
|
import { personDecision } from "../../host-decision.js";
|
|
3
3
|
import { sameExecutionRecord } from "@sema-agent/core";
|
|
4
4
|
import { DECIDED_REPLAY_SCAN_CAP, approvalPayloadFingerprint } from "../../plugins/checkpoint-store-sql.js";
|
|
@@ -87,7 +87,7 @@ async function decideIdempotentReplay(cs, args) {
|
|
|
87
87
|
return undefined;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
return
|
|
90
|
+
return screenedStoredGate(first);
|
|
91
91
|
})();
|
|
92
92
|
return {
|
|
93
93
|
status: 200,
|
|
@@ -200,7 +200,7 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
|
|
|
200
200
|
...(s.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
201
201
|
...(s.denialLimitFallback !== undefined ? { denialLimitFallback: s.denialLimitFallback } : {}),
|
|
202
202
|
...(s.origin !== undefined ? { origin: s.origin } : {}),
|
|
203
|
-
...(s.
|
|
203
|
+
...(s.ruleStoreUnreadable !== undefined ? { ruleStoreUnreadable: s.ruleStoreUnreadable } : {}),
|
|
204
204
|
objective: ctx?.body?.objective ?? null,
|
|
205
205
|
input,
|
|
206
206
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { expandTiers, reasoningEffortLevelsOf, routePairingStatus } from "@sema-agent/core";
|
|
2
2
|
import { isModelAllowlisted } from "../../model-select.js";
|
|
3
3
|
import { resolveModelApiKey } from "../../key-resolver.js";
|
|
4
4
|
import { isSealedKeyPoison } from "../../sealed-key.js";
|
|
@@ -194,7 +194,7 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
194
194
|
vision: Array.isArray(m.input) ? m.input.includes("image") : false,
|
|
195
195
|
...(m.contextWindow ? { contextWindow: m.contextWindow } : {}),
|
|
196
196
|
...(m.maxTokens ? { maxOutputTokens: m.maxTokens } : {}),
|
|
197
|
-
...(
|
|
197
|
+
...((levels) => (levels !== undefined ? { supportedEffortLevels: [...levels] } : {}))(reasoningEffortLevelsOf(m)),
|
|
198
198
|
...(m.compat !== undefined ? { compat: m.compat } : {}),
|
|
199
199
|
...(atAllowlist.length > 0 ? { atMentionable: isModelAllowlisted(name, atCatalog, atAllowlist) } : {}),
|
|
200
200
|
routePairing: routePairingOf(deps, name, m),
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -6,13 +6,10 @@ import { isTerminalRunStatus, isParkedRunStatus } from "../../plugins/store-cont
|
|
|
6
6
|
import { LIVE_RUN_GATE_KINDS, TERMINAL_PARK_GATE_KINDS } from "../../plugins/checkpoint-store-sql.js";
|
|
7
7
|
import { redactSecrets } from "../../trace/redact.js";
|
|
8
8
|
import { HttpError, verifiedPrincipal, isUuidV7, encodeCheckpointScope } from "../../security.js";
|
|
9
|
-
import { runInBackground } from "../../runs.js";
|
|
10
9
|
import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from "../../orchestration/workflow-agent-steer.js";
|
|
11
10
|
import { defaultSubagentTailBus } from "../../fleet/subagent-tail-bus.js";
|
|
12
|
-
import { fleetRunPublisher, fleetRunLabels } from "../../fleet/fleet-bus.js";
|
|
13
11
|
import { composeSupervisorCost, infraCost, infraUsageFromEvents, hasInfraPricing } from "../../observability/cost-taxonomy.js";
|
|
14
12
|
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
15
|
-
import { cascadeConfig, runMeta } from "../run-meta.js";
|
|
16
13
|
import { createHash } from "node:crypto";
|
|
17
14
|
import { scopedIdempotencyKey } from "../idempotency.js";
|
|
18
15
|
import { streamSseLog } from "../sse-log.js";
|
|
@@ -20,14 +17,14 @@ import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
|
20
17
|
import { buildApprovalPreamble, buildApprovalPreambleSseFrames } from "../../approval-card.js";
|
|
21
18
|
import { ASK_DECISION_CONSUMER_ABSENT, resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
22
19
|
import { MAX_DECISION_NOTE_CHARS } from "../../approval-ask-machine.js";
|
|
23
|
-
import { normalizeRunEventType } from "../../trace/project.js";
|
|
20
|
+
import { normalizeRunEventType, screenedStoredEventData } from "../../trace/project.js";
|
|
24
21
|
import { sendJson, sendError, httpErrorCode, sseHeaders } from "../send.js";
|
|
25
|
-
import {
|
|
26
|
-
import { clearTurnActivity, readTurnActivityMs } from "../../turn-activity.js";
|
|
22
|
+
import { readTurnActivityMs } from "../../turn-activity.js";
|
|
27
23
|
import { mintCancelledResult } from "../../run-cancel-context.js";
|
|
28
|
-
import { buildCrossSliceUsage, readResourceWindow
|
|
24
|
+
import { buildCrossSliceUsage, readResourceWindow } from "../../resource-window.js";
|
|
29
25
|
import { headerStr, gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
30
26
|
import { ActorAssertionWire } from "@sema-agent/settings-schema";
|
|
27
|
+
import { mintAndDriveDurableRun } from "../durable-run-leg.js";
|
|
31
28
|
import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
|
|
32
29
|
import { withPrincipal } from "../../observability/principal-context.js";
|
|
33
30
|
export const RUN_ID_RE = /^\/v1\/runs\/([^/]+)(\/events)?$/;
|
|
@@ -140,9 +137,10 @@ async function streamRunEvents(req, res, deps, runStore, taskId, staleMs) {
|
|
|
140
137
|
retainedFrom: (id) => runStore.retainedFrom(id),
|
|
141
138
|
formatEvent: (ev) => {
|
|
142
139
|
const type = normalizeRunEventType(ev.type);
|
|
143
|
-
const
|
|
140
|
+
const redacted = type === "done" && ev.data && typeof ev.data === "object" && ev.data.result && typeof ev.data.result === "object"
|
|
144
141
|
? { ...ev.data, result: memoRedactTerminalResult("ev.done.result", ev.data.result) }
|
|
145
142
|
: type === "failed" ? memoRedactedRead("ev.failed", ev.data, (d) => redactLedgerEventData(type, d)) : redactLedgerEventData(type, ev.data);
|
|
143
|
+
const data = screenedStoredEventData(type, redacted);
|
|
146
144
|
return { id: ev.seq, event: type, data: { type, ...(data ?? {}) } };
|
|
147
145
|
},
|
|
148
146
|
staleFrame: () => ({ event: "failed", data: { type: "failed", errorMessage: "run stalled (instance lost?)" } }),
|
|
@@ -265,6 +263,8 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
265
263
|
error: stale ? "run stalled (instance lost?)" : isTerminalRunStatus(run.status) ? memoRedactedRead("run.error", run.error ?? undefined, redactErrorMessage) : redactErrorMessage(run.error ?? undefined),
|
|
266
264
|
jobId: run.jobId ?? undefined,
|
|
267
265
|
source: run.source ?? undefined,
|
|
266
|
+
heldBy: run.instanceId ?? null,
|
|
267
|
+
cancelRequested: run.cancelRequested,
|
|
268
268
|
});
|
|
269
269
|
}
|
|
270
270
|
return;
|
|
@@ -305,7 +305,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
305
305
|
sendError(res, 409, "conflict.approval_settled", "pending approval was settled concurrently (decided or expired) — re-check the run and retry cancel if it is still active", { taskId, status: st });
|
|
306
306
|
}
|
|
307
307
|
else {
|
|
308
|
-
sendJson(res, 202, { taskId, status: st ?? "failed", note: "run already terminal — cancel is a no-op" });
|
|
308
|
+
sendJson(res, 202, { taskId, status: st ?? "failed", cancelRequested: now?.cancelRequested ?? false, note: "run already terminal — cancel is a no-op" });
|
|
309
309
|
}
|
|
310
310
|
return;
|
|
311
311
|
}
|
|
@@ -319,7 +319,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
319
319
|
sendError(res, 409, "conflict.approval_settled", "pending approval was settled concurrently (decided or expired) — re-check the run and retry cancel if it is still active", { taskId, status: st });
|
|
320
320
|
}
|
|
321
321
|
else {
|
|
322
|
-
sendJson(res, 202, { taskId, status: st ?? "failed", note: "run already terminal — cancel is a no-op" });
|
|
322
|
+
sendJson(res, 202, { taskId, status: st ?? "failed", cancelRequested: now?.cancelRequested ?? false, note: "run already terminal — cancel is a no-op" });
|
|
323
323
|
}
|
|
324
324
|
return;
|
|
325
325
|
}
|
|
@@ -355,10 +355,10 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
355
355
|
}
|
|
356
356
|
const finalRow = await rs.getRun(taskId).catch(() => undefined);
|
|
357
357
|
if (finalRow && finalRow.errorCode !== "cancelled" && isTerminalRunStatus(finalRow.status)) {
|
|
358
|
-
sendJson(res, 202, { taskId, status: finalRow.status, errorCode: finalRow.errorCode ?? null, note: `session unlocked; the run was terminalized concurrently (${finalRow.errorCode ?? finalRow.status}) before this cancel's write — reporting the actual ledger state` });
|
|
358
|
+
sendJson(res, 202, { taskId, status: finalRow.status, errorCode: finalRow.errorCode ?? null, cancelRequested: finalRow.cancelRequested, note: `session unlocked; the run was terminalized concurrently (${finalRow.errorCode ?? finalRow.status}) before this cancel's write — reporting the actual ledger state` });
|
|
359
359
|
return;
|
|
360
360
|
}
|
|
361
|
-
sendJson(res, 202, { taskId, status: "failed", errorCode: "cancelled", note });
|
|
361
|
+
sendJson(res, 202, { taskId, status: "failed", errorCode: "cancelled", cancelRequested: false, note });
|
|
362
362
|
};
|
|
363
363
|
if (run.status === "running") {
|
|
364
364
|
let flagged = await deps.runStore.requestCancel(taskId, run.owner);
|
|
@@ -374,23 +374,23 @@ async function handleRunsBody(req, res, url, ctx, miss) {
|
|
|
374
374
|
continue;
|
|
375
375
|
}
|
|
376
376
|
if (now?.status === "running") {
|
|
377
|
-
sendError(res, 409, "conflict.
|
|
377
|
+
sendError(res, 409, "conflict.run_not_running", "the cancel flag could not be set: this run was not in 'running' state at the moment of each attempt (it keeps flipping between running and a park) — retry cancel", { taskId, status: now.status });
|
|
378
378
|
return;
|
|
379
379
|
}
|
|
380
|
-
sendJson(res, 202, { taskId, status: now?.status ?? "failed", note: "run already terminal — cancel is a no-op" });
|
|
380
|
+
sendJson(res, 202, { taskId, status: now?.status ?? "failed", cancelRequested: now?.cancelRequested ?? false, note: "run already terminal — cancel is a no-op" });
|
|
381
381
|
return;
|
|
382
382
|
}
|
|
383
383
|
if (inflightRuns.has(taskId)) {
|
|
384
384
|
cancelledViaVerb.add(taskId);
|
|
385
385
|
inflightRuns.get(taskId).abort();
|
|
386
386
|
}
|
|
387
|
-
sendJson(res, 202, { taskId, status: "cancelling" });
|
|
387
|
+
sendJson(res, 202, { taskId, status: "cancelling", cancelRequested: true });
|
|
388
388
|
}
|
|
389
389
|
else if (isParkedRunStatus(run.status)) {
|
|
390
390
|
await cancelSuspended();
|
|
391
391
|
}
|
|
392
392
|
else {
|
|
393
|
-
sendJson(res, 202, { taskId, status: run.status, note: "run already terminal — cancel is a no-op" });
|
|
393
|
+
sendJson(res, 202, { taskId, status: run.status, cancelRequested: run.cancelRequested, note: "run already terminal — cancel is a no-op" });
|
|
394
394
|
}
|
|
395
395
|
return;
|
|
396
396
|
}
|
|
@@ -1559,58 +1559,13 @@ export async function createDurableRun(req, res, ctx, prepared, idemKey) {
|
|
|
1559
1559
|
}
|
|
1560
1560
|
if ((await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
|
|
1561
1561
|
return null;
|
|
1562
|
-
const resp = await idemCache.run(idemKey, async () => {
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
recordResourceWindow(taskId, prepared.spec.resourceSuspend);
|
|
1570
|
-
if (created.ok)
|
|
1571
|
-
deps.sessionTitler?.maybeTitle(sessionId, prepared.spec.objective, typeof prepared.spec.model === "string" ? prepared.spec.model : prepared.spec.model?.id);
|
|
1572
|
-
if (!created.ok) {
|
|
1573
|
-
if (clientTaskId && created.activeTaskId === clientTaskId) {
|
|
1574
|
-
return { status: 202, body: { taskId: clientTaskId, sessionId, status: "running" } };
|
|
1575
|
-
}
|
|
1576
|
-
return { status: 409, body: await buildActiveRunConflict({ runStore, checkpointStore: deps.checkpointStore, governance: deps.config, runStaleSec: deps.config.runStaleSec, turnActivity: readTurnActivityMs }, sessionId, created.activeTaskId) };
|
|
1577
|
-
}
|
|
1578
|
-
if (deps.checkpointStore) {
|
|
1579
|
-
await deps.checkpointStore.putCtx(sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope });
|
|
1580
|
-
}
|
|
1581
|
-
const anchorOwner = prepared.auth?.principal ?? null;
|
|
1582
|
-
const getLeafId = deps.sessionStorage?.getLeafId?.bind(deps.sessionStorage);
|
|
1583
|
-
const captureTurnAnchor = deps.resumeAnchorStore && getLeafId
|
|
1584
|
-
? async (eventId) => {
|
|
1585
|
-
const leaf = await getLeafId(sessionId);
|
|
1586
|
-
if (leaf)
|
|
1587
|
-
await deps.resumeAnchorStore.put(sessionId, eventId, leaf, anchorOwner);
|
|
1588
|
-
}
|
|
1589
|
-
: undefined;
|
|
1590
|
-
const captureUserMessageAnchor = deps.resumeAnchorStore
|
|
1591
|
-
? async (entryId) => { await deps.resumeAnchorStore.put(sessionId, taskId, entryId, anchorOwner); }
|
|
1592
|
-
: undefined;
|
|
1593
|
-
const fleetPub = fleetRunPublisher(deps.fleetBus, {
|
|
1594
|
-
runId: taskId,
|
|
1595
|
-
scope: gatedPrincipal(req, deps.config) ?? "default",
|
|
1596
|
-
rootTaskId: taskId,
|
|
1597
|
-
...fleetRunLabels(prepared.spec.objective),
|
|
1598
|
-
});
|
|
1599
|
-
void runInBackground(prepared.runner, { ...prepared.spec, sessionId }, runStore, taskId, deps.metrics, prepared.auth?.principal, prepared.verify, prepared.cascade ? cascadeConfig(deps.config.cascadeLadder, prepared.spec.limits?.maxCostUsd) : undefined, deps.instrumentDegenerate, deps.planCacheProbe, deps.config.traceThinking, inflightRuns, preemptableRuns, steerableRuns, deps.modelUsage, deps.elicitation, gatedPrincipal(req, deps.config) ?? null, captureTurnAnchor, fleetPub, captureUserMessageAnchor, deps.question, deps.workflowCompletionInbox, deps.subagentSteerRegistry, (m, x) => deps.logger?.info?.(m, x), deps.sendUserFile, deps.promptManifests, deps.toolApproval
|
|
1600
|
-
? {
|
|
1601
|
-
coordinator: deps.toolApproval,
|
|
1602
|
-
streamApprovalOn: resolveStreamApprovalGate({
|
|
1603
|
-
toolApprovalEnabled: true,
|
|
1604
|
-
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
1605
|
-
backend: deps.backend,
|
|
1606
|
-
parkFacility: deps.checkpointStore !== undefined,
|
|
1607
|
-
}).active,
|
|
1608
|
-
windowMs: deps.config.streamApproval?.windowMs,
|
|
1609
|
-
windowMarginMs: deps.config.streamAskWindowMarginMs,
|
|
1610
|
-
}
|
|
1611
|
-
: undefined, deps.checkpointStore, prepared.engineNotices);
|
|
1612
|
-
return { status: 202, body: { taskId, sessionId, status: "running" } };
|
|
1613
|
-
}, (r) => r.status === 202);
|
|
1562
|
+
const resp = await idemCache.run(idemKey, async () => await mintAndDriveDurableRun({ deps, registry: ctx.registry, helpers: ctx.helpers, local: ctx.local, legs: ctx.legs }, runStore, {
|
|
1563
|
+
prepared,
|
|
1564
|
+
...(clientTaskId !== undefined ? { clientTaskId } : {}),
|
|
1565
|
+
fleetScope: gatedPrincipal(req, deps.config) ?? "default",
|
|
1566
|
+
elicitOwner: gatedPrincipal(req, deps.config) ?? null,
|
|
1567
|
+
source,
|
|
1568
|
+
}), (r) => r.status === 202);
|
|
1614
1569
|
return resp;
|
|
1615
1570
|
}
|
|
1616
1571
|
//# sourceMappingURL=runs.js.map
|
|
@@ -338,6 +338,7 @@ function summarizeWorkflowDetail(run) {
|
|
|
338
338
|
durationMs: durationOf(run),
|
|
339
339
|
rev: run.rev,
|
|
340
340
|
...(run.error !== undefined ? { error: redactErrorMessage(run.error) ?? "" } : {}),
|
|
341
|
+
...(run.errorCode !== undefined ? { errorCode: run.errorCode } : {}),
|
|
341
342
|
...(run.result !== undefined ? { result: redactSecrets(run.result) } : {}),
|
|
342
343
|
phases,
|
|
343
344
|
...(unphasedView ? { unphased: unphasedView } : {}),
|
package/dist/http/server.js
CHANGED
|
@@ -43,6 +43,8 @@ import { IdempotencyCache, scopedIdempotencyKey } from "./idempotency.js";
|
|
|
43
43
|
export { scopedIdempotencyKey };
|
|
44
44
|
import { streamSseLog } from "./sse-log.js";
|
|
45
45
|
import { verifyRoundsFromBody } from "./verify-rounds.js";
|
|
46
|
+
import { acceptedResumingReceipt } from "./route-ctx.js";
|
|
47
|
+
import { mintAndDriveDurableRun } from "./durable-run-leg.js";
|
|
46
48
|
import { RUN_NOT_FOUND_MESSAGE } from "./route-ctx.js";
|
|
47
49
|
import { handleCapabilities } from "./routes/capabilities.js";
|
|
48
50
|
import { handleObservability } from "./routes/observability.js";
|
|
@@ -1218,22 +1220,14 @@ export function createHttpServer(rawDeps) {
|
|
|
1218
1220
|
const plan = planWorkflowParkedDecide(origin, parkedReq);
|
|
1219
1221
|
if (!plan.ok)
|
|
1220
1222
|
return plan.deny;
|
|
1223
|
+
const parkedResume = { workflowParkedResume: [{ runId: plan.runId, token, outcome: plan.outcome }] };
|
|
1221
1224
|
const hostToken = await cs
|
|
1222
1225
|
.findPendingTokenBySession(plan.hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" })
|
|
1223
1226
|
.catch(() => undefined);
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
error: "the workflow's originating session is not parked awaiting this run — the deployment can only deliver the decision on the host's next turn; retry once the host session parks, or resume the workflow run yourself",
|
|
1229
|
-
errorCode: "decide.workflow_host_not_parked",
|
|
1230
|
-
runId: plan.runId,
|
|
1231
|
-
},
|
|
1232
|
-
};
|
|
1233
|
-
}
|
|
1234
|
-
return await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, [
|
|
1235
|
-
{ runId: plan.runId, token, outcome: plan.outcome },
|
|
1236
|
-
]);
|
|
1227
|
+
const admittedPayer = decodeCheckpointScope(cp.scope);
|
|
1228
|
+
return hostToken
|
|
1229
|
+
? await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, parkedResume.workflowParkedResume, admittedPayer)
|
|
1230
|
+
: await startFreshHostTurn(plan.hostSessionId, plan.message, admittedPayer, req, parkedResume, plan.runId);
|
|
1237
1231
|
}
|
|
1238
1232
|
}
|
|
1239
1233
|
let ctx;
|
|
@@ -1286,9 +1280,11 @@ export function createHttpServer(rawDeps) {
|
|
|
1286
1280
|
noteLateSettleIfAccepted(driven);
|
|
1287
1281
|
return driven;
|
|
1288
1282
|
}
|
|
1283
|
+
const ONE_SHOT_HISTORY_KEYS = ["resumeAt", "restoreFiles", "acceptPartialRestore", "rewindFilesTo", "rewindFiles"];
|
|
1284
|
+
const ONE_SHOT_HISTORY_STRIP = Object.fromEntries(ONE_SHOT_HISTORY_KEYS.map((k) => [k, undefined]));
|
|
1289
1285
|
const resolveResumeSpec = async (body, req, auth) => {
|
|
1290
1286
|
const engineNotices = [];
|
|
1291
|
-
const spec = await deps.resolveSpec({ ...body,
|
|
1287
|
+
const spec = await deps.resolveSpec({ ...body, ...ONE_SHOT_HISTORY_STRIP }, req, auth, { leg: "resume", onNotice: (n) => engineNotices.push(n) });
|
|
1292
1288
|
return { spec, engineNotices };
|
|
1293
1289
|
};
|
|
1294
1290
|
async function driveResumeIntoRunLog(args) {
|
|
@@ -1638,7 +1634,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1638
1634
|
steerableRuns.set(taskId, stream);
|
|
1639
1635
|
if (!taskId || !rs)
|
|
1640
1636
|
return await stream.result();
|
|
1641
|
-
onAccepted?.(
|
|
1637
|
+
onAccepted?.(acceptedResumingReceipt(taskId, sessionId));
|
|
1642
1638
|
for await (const ev of stream) {
|
|
1643
1639
|
recordTurnActivity(taskId);
|
|
1644
1640
|
fleetPub?.onEvent(ev);
|
|
@@ -1832,7 +1828,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1832
1828
|
(e.code === "checkpoint.unsupported_version" && checkpointRowRedeemableElsewhere(e.detail?.reason));
|
|
1833
1829
|
if (claimedRow && taskId && deps.runStore) {
|
|
1834
1830
|
if (retriable && freshRunClaimed) {
|
|
1835
|
-
const c = { taskId, sessionId, terminal: { kind: "failed", code: e.code, message: e.message }, stats: { turns: 0, tokens: 0 } };
|
|
1831
|
+
const c = { taskId, sessionId, terminal: { kind: "failed", code: e.code, message: e.message }, stats: { turns: 0, tokens: 0, usageMissing: true } };
|
|
1836
1832
|
await deps.runStore.setTerminal(taskId, "failed", c, e.message).catch((err) => {
|
|
1837
1833
|
deps.logger?.warn?.("wake_synthetic_run_release_failed", { taskId, sessionId, errorCode: e.code, error: err instanceof Error ? err.message : String(err), note: "the synthetic run row for this refused fresh-run resume could not be settled — its task_active claim is still held and the next attempt on this session will 409 conflict.session_active_run until reapStale collects it (runStaleSec)" });
|
|
1838
1834
|
});
|
|
@@ -1987,7 +1983,51 @@ export function createHttpServer(rawDeps) {
|
|
|
1987
1983
|
const verifyRounds = verifyRoundsFromBody(ctx.body);
|
|
1988
1984
|
return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, verifyRounds, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
|
|
1989
1985
|
}
|
|
1990
|
-
async function
|
|
1986
|
+
async function startFreshHostTurn(sessionId, objective, principal, req, trustedInternals, runId) {
|
|
1987
|
+
const cs = deps.checkpointStore;
|
|
1988
|
+
if (!deps.runStore) {
|
|
1989
|
+
return { status: 501, body: { error: "delivering a workflow decision on the host's next turn requires a durable run store (DB_BACKEND=mysql|pg|local)", errorCode: "capability.run_store_required" } };
|
|
1990
|
+
}
|
|
1991
|
+
const ctx = await cs.getCtx(sessionId).catch(() => null);
|
|
1992
|
+
if (!ctx) {
|
|
1993
|
+
return {
|
|
1994
|
+
status: 409,
|
|
1995
|
+
body: {
|
|
1996
|
+
error: "the workflow's originating session no longer has the rebuild inputs for a new turn: a session's resume context is garbage-collected once it is stale AND that session itself has no pending checkpoint and no active run (REAP_RUN_STALE_SEC, default 120s) — a workflow CHILD's park does not hold the HOST session's row open. Raise REAP_RUN_STALE_SEC to cover how long approvals may sit before someone decides them, or resume the workflow run yourself with the runId below",
|
|
1997
|
+
errorCode: "conflict.resume_context_unavailable",
|
|
1998
|
+
runId,
|
|
1999
|
+
},
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
const windowDenial = await usageWindowDenialFor(principal || GLOBAL_USAGE_KEY);
|
|
2003
|
+
if (windowDenial)
|
|
2004
|
+
return { status: windowDenial.status, body: windowDenial.body };
|
|
2005
|
+
const auth = { sessionId, principal, memoryScope: ctx.memoryScope };
|
|
2006
|
+
let spec;
|
|
2007
|
+
let engineNotices = [];
|
|
2008
|
+
try {
|
|
2009
|
+
({ spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth));
|
|
2010
|
+
}
|
|
2011
|
+
catch (e) {
|
|
2012
|
+
if (e instanceof HttpError) {
|
|
2013
|
+
return { status: 409, body: { error: `resume blocked by an admission policy change: ${e.message}`, errorCode: "resume_blocked_by_policy", ...(e.code ? { blockedBy: e.code } : {}), ...(e.extra ?? {}) } };
|
|
2014
|
+
}
|
|
2015
|
+
throw e;
|
|
2016
|
+
}
|
|
2017
|
+
const hostSpec = { ...spec, objective, sessionId };
|
|
2018
|
+
const out = await mintAndDriveDurableRun(routeCtxBase, deps.runStore, {
|
|
2019
|
+
prepared: { spec: hostSpec, runner: runnerFor(hostSpec), auth, body: ctx.body, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
|
|
2020
|
+
fleetScope: resumeFleetScope(req, auth),
|
|
2021
|
+
elicitOwner: principal ?? null,
|
|
2022
|
+
source: "workflow_parked_decide",
|
|
2023
|
+
trustedInternals,
|
|
2024
|
+
});
|
|
2025
|
+
if (out.status !== 202)
|
|
2026
|
+
return { status: out.status, body: out.body };
|
|
2027
|
+
const minted = out.body;
|
|
2028
|
+
return acceptedResumingReceipt(minted.taskId, minted.sessionId);
|
|
2029
|
+
}
|
|
2030
|
+
async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowParkedResume, ownerLeaseAdmittedFor) {
|
|
1991
2031
|
const cs = deps.checkpointStore;
|
|
1992
2032
|
const wakeBinding = { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" };
|
|
1993
2033
|
const token = (await cs.findPendingTokenBySession(sessionId, undefined, wakeBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
|
|
@@ -2058,6 +2098,7 @@ export function createHttpServer(rawDeps) {
|
|
|
2058
2098
|
...(acceptEarly === true ? { acceptEarly: true } : {}),
|
|
2059
2099
|
...(req !== undefined ? { callerInitiated: true } : {}),
|
|
2060
2100
|
...(workflowParkedResume !== undefined ? { workflowParkedResume } : {}),
|
|
2101
|
+
...(ownerLeaseAdmittedFor !== undefined && auth.principal === ownerLeaseAdmittedFor ? { ownerLeaseAdmitted: true } : {}),
|
|
2061
2102
|
});
|
|
2062
2103
|
}
|
|
2063
2104
|
async function resumePlanReview(sessionId, decision, editedPlan, reason, req, log, acceptEarly, decider) {
|
|
@@ -2222,27 +2263,32 @@ export function createHttpServer(rawDeps) {
|
|
|
2222
2263
|
sendError(res, 429, "quota_exhausted", "budget exhausted (quota lease)", { retryAfterSec: adm.retryAfterSec, ...(adm.detail ?? {}) });
|
|
2223
2264
|
return true;
|
|
2224
2265
|
}
|
|
2225
|
-
async function
|
|
2266
|
+
async function usageWindowDenialFor(key) {
|
|
2226
2267
|
const store = deps.usageWindowStore;
|
|
2227
2268
|
const windows = deps.config.usageWindows;
|
|
2228
2269
|
if (!store || !windows || windows.length === 0)
|
|
2229
|
-
return
|
|
2230
|
-
const key = gatedPrincipal(req, deps.config) || GLOBAL_USAGE_KEY;
|
|
2270
|
+
return undefined;
|
|
2231
2271
|
let retryAfterMs;
|
|
2232
2272
|
try {
|
|
2233
2273
|
retryAfterMs = usageRetryAfterMs(await store.read(key, windows, Date.now()));
|
|
2234
2274
|
}
|
|
2235
2275
|
catch (e) {
|
|
2236
2276
|
deps.logger?.warn?.("usage_window_admission_read_failed", { error: e instanceof Error ? e.message : String(e) });
|
|
2237
|
-
return
|
|
2277
|
+
return undefined;
|
|
2238
2278
|
}
|
|
2239
2279
|
if (retryAfterMs === undefined)
|
|
2240
|
-
return
|
|
2280
|
+
return undefined;
|
|
2241
2281
|
deps.metrics?.inc("usage_window_rejected_total");
|
|
2242
2282
|
deps.logger?.info?.("usage_window_exhausted", { hasPrincipal: key !== GLOBAL_USAGE_KEY, retryAfterMs });
|
|
2243
2283
|
const retryAfterSec = Math.max(1, Math.ceil(retryAfterMs / 1000));
|
|
2244
|
-
|
|
2245
|
-
|
|
2284
|
+
return { status: 429, body: { error: "usage window exhausted (deployment token governance)", errorCode: "usage.window_exhausted", retryAfterSec }, retryAfterSec };
|
|
2285
|
+
}
|
|
2286
|
+
async function usageWindowDenied(req, res) {
|
|
2287
|
+
const denial = await usageWindowDenialFor(gatedPrincipal(req, deps.config) || GLOBAL_USAGE_KEY);
|
|
2288
|
+
if (!denial)
|
|
2289
|
+
return false;
|
|
2290
|
+
res.setHeader("retry-after", String(denial.retryAfterSec));
|
|
2291
|
+
sendError(res, denial.status, denial.body.errorCode, denial.body.error, { retryAfterSec: denial.retryAfterSec });
|
|
2246
2292
|
return true;
|
|
2247
2293
|
}
|
|
2248
2294
|
function rateLimited(req, res) {
|
package/dist/main.js
CHANGED
|
@@ -8,6 +8,7 @@ import { selectEnvironmentTool } from "./capabilities/select-environment-tool.js
|
|
|
8
8
|
import { subagentSendUserFileExtraTools } from "./capabilities/send-user-file-tool.js";
|
|
9
9
|
import { brainSummary } from "./brain.js";
|
|
10
10
|
import { loadConfig, logConfigDiagnostics, resolveBindHost, splitLocalRoots, parseCapEnv, parseNumOrFailNonNegative } from "./config.js";
|
|
11
|
+
import { createUnpricedCeilingVerdict } from "./boot/unpriced-ceiling-verdict.js";
|
|
11
12
|
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
12
13
|
import { cardRuleScopeRoot } from "./task-cwd.js";
|
|
13
14
|
import { isPricingConfigured } from "./budget.js";
|
|
@@ -129,6 +130,8 @@ async function main() {
|
|
|
129
130
|
}
|
|
130
131
|
const configCenter = await createConfigCenterRuntime({ config, logger, metrics, localRoot });
|
|
131
132
|
await configCenter.applyLocalRemoteExec();
|
|
133
|
+
const ceilingVerdict = createUnpricedCeilingVerdict({ config, logger });
|
|
134
|
+
ceilingVerdict.judge();
|
|
132
135
|
const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts, } = await openStores({ config, logger, metrics, localRoot });
|
|
133
136
|
const instanceId = uuidv7();
|
|
134
137
|
const sessionCaptureRecords = backend?.sessionCaptureRecords();
|
|
@@ -561,7 +564,7 @@ async function main() {
|
|
|
561
564
|
}
|
|
562
565
|
}
|
|
563
566
|
};
|
|
564
|
-
configCenter.startRefreshLoop({ runnerTierFrozen, pricing, limitSync, swapRunnerDeps });
|
|
567
|
+
configCenter.startRefreshLoop({ runnerTierFrozen, pricing, limitSync, swapRunnerDeps, ceilingVerdict });
|
|
565
568
|
configCenter.initKeyResolver();
|
|
566
569
|
const { ownerAware, sessionAudit, sessionWatchRegistry, purgeSession, instrumentDegenerate, planCacheProbe } = createSessionFaces({
|
|
567
570
|
deviceStore: deviceLane?.store,
|
|
@@ -53,9 +53,10 @@ export interface MemoryBundleFaces {
|
|
|
53
53
|
/**
|
|
54
54
|
* 在**已装配的记忆后端**上建 bundle 两面。命名照 CLAUDE.md 工厂律:返回的是带行为的活对象 ⇒ `create*`。
|
|
55
55
|
*
|
|
56
|
-
* `memoryDir` = `
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* `memoryDir` = 引擎的**挂载平面**(`memoryMountRootOf(backend, root)` 单铸点;全仓自构引擎同一条规则,
|
|
57
|
+
* 由 `test/memory-layout-plane.test.ts` 的归一门咬住)。见文件头:这两条路径**不读它**(机器钉在
|
|
58
|
+
* `test/memory-bundle-engine.test.ts`:给一个不存在的 `memoryDir` 也照样工作),传对的那一只是为了
|
|
59
|
+
* 万一 core 日后真用上时,它拿到的是本部署真正的挂载平面,而不是高一层的配置根。
|
|
59
60
|
*/
|
|
60
61
|
export declare function createMemoryBundleFaces(store: {
|
|
61
62
|
backend: MemoryBackend;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { MemoryEngine } from "@sema-agent/core";
|
|
2
|
+
import { memoryMountRootOf } from "./memory-scope.js";
|
|
2
3
|
export function createMemoryBundleFaces(store, opts) {
|
|
3
4
|
const b = store.backend;
|
|
4
5
|
if (typeof b.exportSnapshotOf !== "function" || typeof b.importBundleCommit !== "function")
|
|
5
6
|
return undefined;
|
|
6
|
-
const engine = new MemoryEngine({ backend: store.backend, memoryDir: store.root, ...(opts?.captureRecordStore !== undefined ? { captureRecordStore: opts.captureRecordStore } : {}) });
|
|
7
|
+
const engine = new MemoryEngine({ backend: store.backend, memoryDir: memoryMountRootOf(store.backend, store.root), ...(opts?.captureRecordStore !== undefined ? { captureRecordStore: opts.captureRecordStore } : {}) });
|
|
7
8
|
return {
|
|
8
9
|
exportScopes: (scopes) => engine.exportMemoryScopes(scopes),
|
|
9
10
|
importBundle: (bundle, opts) => engine.importMemoryBundle(bundle, opts),
|