@sema-agent/server 7.117.0 → 7.118.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +97 -0
- package/USAGE.md +37 -18
- package/dist/approval-card.d.ts +40 -10
- package/dist/approval-card.js +11 -1
- package/dist/approval.d.ts +4 -12
- package/dist/approval.js +4 -26
- package/dist/boot/parked-revive-gate.d.ts +1 -7
- package/dist/boot/parked-revive-gate.js +2 -12
- package/dist/boot/resolve-spec.js +8 -13
- package/dist/boot/runner-deps.js +2 -2
- package/dist/boot/stage-07-capability-layer.js +2 -2
- package/dist/capabilities/hands-lane.d.ts +25 -0
- package/dist/capabilities/hands-lane.js +1 -1
- package/dist/capabilities/mcp-probe.d.ts +2 -2
- package/dist/config.d.ts +14 -18
- package/dist/config.js +13 -24
- package/dist/deployment-governance.d.ts +2 -7
- package/dist/deployment-governance.js +3 -5
- package/dist/host-decision.d.ts +83 -25
- package/dist/host-decision.js +31 -1
- package/dist/http/admission.js +7 -1
- package/dist/http/approval-sweeps.js +1 -1
- package/dist/http/mcp-dial-deadline.d.ts +35 -0
- package/dist/http/mcp-dial-deadline.js +21 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +11 -5
- package/dist/http/memory-compliance-failure-reply.js +17 -8
- package/dist/http/memory-store-refusal-reply.d.ts +14 -11
- package/dist/http/memory-store-refusal-reply.js +16 -5
- package/dist/http/resume-legs.js +7 -3
- package/dist/http/routes/approvals-assistant.js +21 -24
- package/dist/http/routes/capabilities.js +2 -0
- package/dist/http/routes/mcp-reconnect.d.ts +17 -0
- package/dist/http/routes/mcp-reconnect.js +6 -1
- package/dist/http/routes/memory-bundle.js +1 -1
- package/dist/http/routes/memory-compliance.js +1 -1
- package/dist/http/routes/memory-origin.js +1 -1
- package/dist/http/routes/runs.js +41 -33
- package/dist/http/routes/sessions.d.ts +1 -1
- package/dist/http/routes/sessions.js +3 -6
- package/dist/http/routes/workflows.js +2 -0
- package/dist/http/wire-types.d.ts +6 -1
- package/dist/memory-store-refusal.d.ts +42 -17
- package/dist/memory-store-refusal.js +25 -6
- package/dist/model-unreachable-notice.d.ts +1 -1
- package/dist/namespaced-rule-name.d.ts +45 -41
- package/dist/namespaced-rule-name.js +36 -22
- package/dist/observability/corrupt-read-seat.d.ts +4 -0
- package/dist/observability/corrupt-read-seat.js +1 -0
- package/dist/observability/fail-open.d.ts +14 -2
- package/dist/observability/fail-open.js +14 -2
- package/dist/operator-ask.d.ts +4 -1
- package/dist/orchestration/workflow-agent-session-index.d.ts +82 -59
- package/dist/orchestration/workflow-agent-session-index.js +80 -14
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -0
- package/dist/orchestration/workflow-completion-inbox.js +39 -17
- package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
- package/dist/orchestration/workflow-notify-journal.js +7 -2
- package/dist/outbound-dispatcher.d.ts +29 -20
- package/dist/outbound-dispatcher.js +105 -16
- package/dist/parked-decide.d.ts +43 -48
- package/dist/parked-decide.js +54 -16
- package/dist/permission-rule-vocab.d.ts +4 -3
- package/dist/permission-rule-vocab.js +1 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +17 -20
- package/dist/plugins/approval-ask-store-sql.js +0 -3
- package/dist/plugins/one-time-migrations.d.ts +5 -0
- package/dist/plugins/one-time-migrations.js +110 -4
- package/dist/plugins/pg-pool.js +1 -1
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/plugins/web-search.d.ts +20 -1
- package/dist/plugins/web-search.js +39 -10
- package/dist/plugins/workflow-run-store-sql.d.ts +7 -6
- package/dist/plugins/workflow-run-store-sql.js +5 -7
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +1 -1
- package/dist/rule-entry-list.d.ts +35 -46
- package/dist/rule-entry-list.js +17 -25
- package/dist/rules-consent.js +3 -0
- package/dist/run-local.js +1 -0
- package/dist/runs.d.ts +1 -1
- package/dist/session-policy-wire.d.ts +13 -12
- package/dist/session-policy-wire.js +11 -6
- package/dist/spec-fields.d.ts +7 -9
- package/dist/spec-fields.js +4 -7
- package/dist/standing-answer-grant.d.ts +14 -2
- package/dist/standing-answer-grant.js +8 -1
- package/dist/task-settings.js +8 -4
- package/dist/tool-approval.d.ts +53 -112
- package/dist/tool-approval.js +75 -55
- package/dist/trace/core-keyset-guard.d.ts +5 -5
- package/dist/trace/self-minted-wire.d.ts +16 -2
- package/dist/trace/self-minted-wire.js +7 -0
- package/dist/user-input-ledger.d.ts +24 -12
- package/dist/user-input-ledger.js +33 -14
- package/package.json +2 -2
- package/scripts/drain-check-local.mjs +74 -3
- package/scripts/drain-check.mysql.sql +43 -17
- package/scripts/drain-check.pg.sql +46 -20
|
@@ -36,6 +36,23 @@
|
|
|
36
36
|
* (`isParkedRunStatus` / `isTerminalRunStatus`)与 `runProvenElsewhere`(手抄已收编成一只谓词,S-430 起同住 `plugins/store-contracts.ts`)。
|
|
37
37
|
* 显式 operator 可重拨任何租户的会话(监督动作,与 notify / steer 同臂;**不是** bare `isOperator`)。
|
|
38
38
|
*
|
|
39
|
+
* ── 应答期限(S-431)────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
* 修前这一口对 `live.reconnectMcpServer(server)` 是裸 `await`:server 侧没有期限,上界只来自 core 自己的启动期钟
|
|
41
|
+
* (env `MCP_TIMEOUT`,未设落 SDK 缺省 60s;拨号之前还有一次不在那条钟里的旧连接 `close()`),一台拨不通 / 半开的远端能把这一口
|
|
42
|
+
* HTTP 请求挂很久。现在与 `GET /v1/sessions/:id/mcp` 面板读**同一只**数 `MCP_DIAL_DEADLINE_MS`(`../mcp-dial-deadline.ts`),
|
|
43
|
+
* 到点答 **503 `state.mcp_reconnect_unsettled`**。
|
|
44
|
+
* 🔴 **只界应答,不取消重拨**:core 的 `TaskStream.reconnectMcpServer(server)` 不收 `AbortSignal`(d.ts 逐字:单参),
|
|
45
|
+
* 本面没有取消把手可递,也不伪造一个。到点之后重拨**继续跑**,落定时照常把名册写上这条 run(成功)或撤走(拨号失败)—— 所以
|
|
46
|
+
* 503 说的是「这一口没等到结果」,**不是**「失败」,文案与契约 §G.7c 都按这个读法写。重发**不作保证**:core 的在途守卫
|
|
47
|
+
* (`redialsInFlight`,`dist/core/mcp.js`)只罩**拨号本身**(关旧 + 连 + 列),拨号一落定就放开,而 `redialMcpServer`
|
|
48
|
+
* (`dist/core/runner/mcp-redial.js`)随后还要 `await commitMcpRosterChanges`(重建工具面)—— 那一段里守卫已不在,
|
|
49
|
+
* 此刻再发会开第二次「先关后拨」。所以文案只说「拨号还在跑时 core 会拒第二次」,不说「重发一定得 refused」,也不把重发
|
|
50
|
+
* 推荐成探针(对抗复审 [medium],验真后采纳;守卫延到名册提交之后是 core 的活,本仓不自造第二份在途判)。
|
|
51
|
+
* 为什么是 503 而不是 200 + 第四个 `outcome` 词:`outcome` 三词是 core 结果的投影,这里**没有结果**,编一个词就是替
|
|
52
|
+
* core 说话(与 `unsupported` 那一行「没有 core 结果就不铸 `prefix`」同一条纪律);503 是 `state.*` 族的唯一状态
|
|
53
|
+
* (附录 A 判据 3),与 `state.mcp_probe_incomplete` 同族同档,但**不复用**它的码 —— 那一码的语义钉在 probe 面上。
|
|
54
|
+
* 本面**不**带 `Retry-After`:重发不是无代价的重试(见上),一个会被通用重试臂自动执行的头在一次交易动词上是错的方向。
|
|
55
|
+
*
|
|
39
56
|
* ── 计费/门 ─────────────────────────────────────────────────────────────────────────────────────
|
|
40
57
|
* 零模型工作(不进 billable;不吃 drain / roster-pending);mutating ⇒ `rateLimited`。能力位 `mcpReconnect`
|
|
41
58
|
* = 持久 run 账面 ∧ 会话属主面(两道 501 的同一批 deps 推导出来,「说 yes 但 501」结构上不可能)。
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { isParkedRunStatus, isTerminalRunStatus, runProvenElsewhere } from "../../plugins/store-contracts.js";
|
|
3
3
|
import { redactSecrets } from "../../trace/redact.js";
|
|
4
|
+
import { DeadlineExceeded, MCP_DIAL_DEADLINE_MS, raceDeadline } from "../mcp-dial-deadline.js";
|
|
4
5
|
import { sendJson, sendError } from "../send.js";
|
|
5
6
|
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
6
7
|
import {} from "../route-ctx.js";
|
|
@@ -99,11 +100,15 @@ export async function handleMcpReconnect(req, res, match, ctx) {
|
|
|
99
100
|
const live = activeTaskId === undefined ? undefined : ctx.registry.steerableRuns.get(activeTaskId);
|
|
100
101
|
if (live) {
|
|
101
102
|
try {
|
|
102
|
-
const result = await live.reconnectMcpServer(server);
|
|
103
|
+
const result = await raceDeadline(live.reconnectMcpServer(server), MCP_DIAL_DEADLINE_MS);
|
|
103
104
|
sendJson(res, 200, { taskId: activeTaskId, sessionId, ...projectResult(result) });
|
|
104
105
|
return;
|
|
105
106
|
}
|
|
106
107
|
catch (e) {
|
|
108
|
+
if (e instanceof DeadlineExceeded) {
|
|
109
|
+
sendError(res, 503, "state.mcp_reconnect_unsettled", "the MCP re-dial did not settle within the time this endpoint waits, so there is no outcome to report — the re-dial itself was NOT cancelled (the engine takes no cancellation for it) and may still finish, so do not assume it failed; do not resend blindly either: the engine refuses a second re-dial of the same server only while the dial itself is still running, and a request sent after that point starts another close-then-dial on a server that may now be healthy, so check the server's state before re-sending");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
107
112
|
const code = e.code;
|
|
108
113
|
const msg = e instanceof Error ? e.message : String(e);
|
|
109
114
|
switch (code) {
|
|
@@ -37,7 +37,7 @@ function sentClassifiedFailure(res, ctx, face, err) {
|
|
|
37
37
|
const code = codeOf(err);
|
|
38
38
|
if (code !== undefined && sentCoreCode(res, code, err instanceof Error ? err.message : String(err)))
|
|
39
39
|
return true;
|
|
40
|
-
if (sentMemoryStoreRefusal(res, err))
|
|
40
|
+
if (sentMemoryStoreRefusal(res, err, ctx.deps.logger))
|
|
41
41
|
return true;
|
|
42
42
|
ctx.deps.logger?.warn?.(`memory_bundle_${face}_failed`, { err: String(err) });
|
|
43
43
|
return false;
|
|
@@ -12,7 +12,7 @@ const EraseRequestSchema = z.object({
|
|
|
12
12
|
allowUnevidenced: z.boolean().optional(),
|
|
13
13
|
});
|
|
14
14
|
function sentClassifiedFailure(res, ctx, face, err) {
|
|
15
|
-
if (sentMemoryComplianceFailure(res, err, { kind: "operator" }))
|
|
15
|
+
if (sentMemoryComplianceFailure(res, err, { kind: "operator" }, ctx.deps.logger))
|
|
16
16
|
return true;
|
|
17
17
|
ctx.deps.logger?.warn?.(`memory_compliance_${face}_failed`, { err: String(err) });
|
|
18
18
|
return false;
|
|
@@ -54,7 +54,7 @@ function sentClassifiedFailure(res, ctx, face, err) {
|
|
|
54
54
|
if (coreCode !== undefined && sentClearCode(res, coreCode, err instanceof Error ? err.message : String(err)))
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
57
|
-
if (face !== "clearances" && sentMemoryStoreRefusal(res, err))
|
|
57
|
+
if (face !== "clearances" && sentMemoryStoreRefusal(res, err, ctx.deps.logger))
|
|
58
58
|
return true;
|
|
59
59
|
if (err instanceof ControlPlaneCorruptError) {
|
|
60
60
|
sendError(res, 500, "internal.memory_control_plane_corrupt", err.message);
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { persistedPlaneOf } from "../../terminal.js";
|
|
2
2
|
import { memoRedactedRead, memoRedactTerminalResult, redactErrorMessage, redactHead, redactLedgerEventForRead, redactTerminalResult } from "../../observability/run-terminal.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { uuidv7, mintCheckpointToken, CheckpointError, validatePendingSteer, TASK_OUTPUT_REPLAY_KINDS } from "@sema-agent/core";
|
|
4
|
+
import { uuidv7, mintCheckpointToken, CheckpointError, validatePendingSteer, TASK_OUTPUT_REPLAY_KINDS, isSystemInjectionPriority } from "@sema-agent/core";
|
|
5
5
|
import { RUN_HOLDER_DEATH_CODES, isTerminalRunStatus, isParkedRunStatus, runProvenElsewhere } from "../../plugins/store-contracts.js";
|
|
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";
|
|
@@ -14,13 +14,16 @@ import { createHash } from "node:crypto";
|
|
|
14
14
|
import { scopedIdempotencyKey } from "../idempotency.js";
|
|
15
15
|
import { streamSseLog } from "../sse-log.js";
|
|
16
16
|
import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
17
|
-
import { buildApprovalPreamble, buildApprovalPreambleSseFrames } from "../../approval-card.js";
|
|
18
|
-
import {
|
|
17
|
+
import { buildApprovalPreamble, buildApprovalPreambleSseFrames, readClosedWordSeat, storedCardDemandsAPerson } from "../../approval-card.js";
|
|
18
|
+
import { liveSettledBy, policyCannotAllow, readDecidedBy, DECIDED_BY_NOT_IN_PROOF, DECIDED_BY_OUTSIDE_CLOSED_SET, POLICY_CANNOT_ALLOW_PERSON_ASK } from "../../host-decision.js";
|
|
19
|
+
import { ASK_DECISION_CONSUMER_ABSENT, outcomeOfDecidedRow, resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
19
20
|
import { MAX_DECISION_NOTE_CHARS } from "../../approval-ask-machine.js";
|
|
20
21
|
import { normalizeRunEventType, screenedStoredEventData } from "../../trace/project.js";
|
|
21
22
|
import { sendJson, sendError, httpErrorCode, sseHeaders } from "../send.js";
|
|
22
23
|
import { readTurnActivityMs } from "../../turn-activity.js";
|
|
23
24
|
import { mintCancelledResult } from "../../run-cancel-context.js";
|
|
25
|
+
import { userInputLaneOf } from "../../user-input-ledger.js";
|
|
26
|
+
import { INJECTION_TIERS } from "../../trace/injection-tier.js";
|
|
24
27
|
import { buildCrossSliceUsage, readResourceWindow } from "../../resource-window.js";
|
|
25
28
|
import { headerStr, gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
26
29
|
import { ActorAssertionWire } from "@sema-agent/settings-schema";
|
|
@@ -31,6 +34,9 @@ import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
|
|
|
31
34
|
import { withPrincipal } from "../../observability/principal-context.js";
|
|
32
35
|
import { readResumeCtx } from "../resume-ctx-read.js";
|
|
33
36
|
import { storedBodyReplayAnswer, storedBodyReplayLogFields, storedBodyReplayRefusal } from "../checkpoint-ctx.js";
|
|
37
|
+
const STEER_PRIORITY_WORDS = INJECTION_TIERS.map((tier) => `'${tier}'`);
|
|
38
|
+
const STEER_PRIORITY_UNION = STEER_PRIORITY_WORDS.join(" | ");
|
|
39
|
+
const STEER_PRIORITY_CHOICES = STEER_PRIORITY_WORDS.length <= 2 ? STEER_PRIORITY_WORDS.join(" or ") : `${STEER_PRIORITY_WORDS.slice(0, -1).join(", ")}, or ${STEER_PRIORITY_WORDS[STEER_PRIORITY_WORDS.length - 1]}`;
|
|
34
40
|
const ASK_PARKING_RETRY_AFTER_SEC = 2;
|
|
35
41
|
const ASK_ACTOR_LABEL_MAX = 256;
|
|
36
42
|
const AskDecisionActorSchema = ActorAssertionWire.strip();
|
|
@@ -46,7 +52,7 @@ const AskDecisionBodySchema = z
|
|
|
46
52
|
.refine((k) => k.trim() === k, { message: "idempotency key must not have leading/trailing whitespace" })
|
|
47
53
|
.optional(),
|
|
48
54
|
actor: z.object({ label: z.string().max(ASK_ACTOR_LABEL_MAX) }).strict().optional(),
|
|
49
|
-
|
|
55
|
+
decidedBy: z.unknown().optional(),
|
|
50
56
|
})
|
|
51
57
|
.strict();
|
|
52
58
|
const ASK_DECISION_CONFIRM_TIMEOUT_MS = 2_000;
|
|
@@ -136,11 +142,16 @@ async function streamRunEvents(req, res, deps, runStore, taskId, staleMs) {
|
|
|
136
142
|
staleFrame: () => ({ event: "failed", data: { type: "failed", errorMessage: "run stalled (instance lost?)" } }),
|
|
137
143
|
}, taskId, staleMs);
|
|
138
144
|
}
|
|
145
|
+
function sendSteerReplayRefusal(res, refusal) {
|
|
146
|
+
const detail = refusal?.detail;
|
|
147
|
+
const differs = detail !== null && typeof detail === "object" ? readClosedWordSeat(detail, "differs") : undefined;
|
|
148
|
+
sendError(res, 409, "steering.duplicate_input_id", refusal instanceof Error ? refusal.message : "this steering input id was already accepted with different values", differs !== undefined ? { detail: { differs } } : undefined);
|
|
149
|
+
}
|
|
139
150
|
async function acceptLiveUserInput(deps, res, at) {
|
|
140
151
|
if (!deps.userInputLedger)
|
|
141
152
|
return undefined;
|
|
142
153
|
try {
|
|
143
|
-
return await deps.userInputLedger.accept({ input:
|
|
154
|
+
return await deps.userInputLedger.accept({ input: userInputLaneOf(at.tier), messageId: at.messageId, sessionId: at.sessionId, taskId: at.taskId, leg: at.leg });
|
|
144
155
|
}
|
|
145
156
|
catch (e) {
|
|
146
157
|
deps.logger?.warn?.("user_input_ledger_accept_failed", { taskId: at.taskId, sessionId: at.sessionId, err: e instanceof Error ? e.message : String(e) });
|
|
@@ -429,15 +440,15 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
429
440
|
return;
|
|
430
441
|
}
|
|
431
442
|
if (typeof body.text !== "string" || body.text.length === 0) {
|
|
432
|
-
sendError(res, 400, "request.body_shape",
|
|
443
|
+
sendError(res, 400, "request.body_shape", `body must be { text: string (non-empty), mode?: 'all' | 'one-at-a-time', priority?: ${STEER_PRIORITY_UNION} }`);
|
|
433
444
|
return;
|
|
434
445
|
}
|
|
435
446
|
if (body.mode !== undefined && body.mode !== "all" && body.mode !== "one-at-a-time") {
|
|
436
447
|
sendError(res, 400, "request.field_invalid", "mode must be 'all' or 'one-at-a-time' when present");
|
|
437
448
|
return;
|
|
438
449
|
}
|
|
439
|
-
if (body.priority !== undefined && body.priority
|
|
440
|
-
sendError(res, 400, "request.field_invalid",
|
|
450
|
+
if (body.priority !== undefined && !isSystemInjectionPriority(body.priority)) {
|
|
451
|
+
sendError(res, 400, "request.field_invalid", `priority must be ${STEER_PRIORITY_CHOICES} when present`);
|
|
441
452
|
return;
|
|
442
453
|
}
|
|
443
454
|
const text = body.text;
|
|
@@ -495,19 +506,19 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
495
506
|
return "queue-full";
|
|
496
507
|
}
|
|
497
508
|
if (e instanceof CheckpointError && e.code === "steering.duplicate_input_id") {
|
|
498
|
-
parkDuplicateKey = e
|
|
509
|
+
parkDuplicateKey = e;
|
|
499
510
|
return "duplicate-key";
|
|
500
511
|
}
|
|
501
512
|
throw e;
|
|
502
513
|
}
|
|
503
514
|
};
|
|
504
515
|
const sendQueueFull = () => sendError(res, 409, "steering.queue_full", parkQueueFull ?? "the parked steering queue on this checkpoint is full");
|
|
505
|
-
const sendDuplicateKey = () =>
|
|
516
|
+
const sendDuplicateKey = () => sendSteerReplayRefusal(res, parkDuplicateKey);
|
|
506
517
|
const sendParked = (status) => sendJson(res, 202, { taskId, status, delivery: "queued", messageId, ...(priority ? { priority } : {}), note: "steer parked on the checkpoint — injected when the run resumes" });
|
|
507
518
|
const sendNotRunning = (error) => sendError(res, 409, "steering.not_running", error);
|
|
508
519
|
const live = steerableRuns.get(taskId);
|
|
509
520
|
if (live) {
|
|
510
|
-
const acceptance = await acceptLiveUserInput(deps, res, { messageId, sessionId: run.sessionId, taskId, leg: live });
|
|
521
|
+
const acceptance = await acceptLiveUserInput(deps, res, { messageId, sessionId: run.sessionId, taskId, leg: live, tier: priority });
|
|
511
522
|
if (acceptance === "refused")
|
|
512
523
|
return;
|
|
513
524
|
try {
|
|
@@ -526,7 +537,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
526
537
|
return;
|
|
527
538
|
}
|
|
528
539
|
if (code === "steering.duplicate_input_id") {
|
|
529
|
-
parkDuplicateKey = e
|
|
540
|
+
parkDuplicateKey = e;
|
|
530
541
|
sendDuplicateKey();
|
|
531
542
|
return;
|
|
532
543
|
}
|
|
@@ -601,7 +612,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
601
612
|
return;
|
|
602
613
|
}
|
|
603
614
|
if (e instanceof CheckpointError && e.code === "steering.duplicate_input_id") {
|
|
604
|
-
parkDuplicateKey = e
|
|
615
|
+
parkDuplicateKey = e;
|
|
605
616
|
mintResult.duplicateKey = true;
|
|
606
617
|
return;
|
|
607
618
|
}
|
|
@@ -831,7 +842,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
831
842
|
});
|
|
832
843
|
return;
|
|
833
844
|
}
|
|
834
|
-
const acceptance = await acceptLiveUserInput(deps, res, { messageId: interruptMessageId, sessionId: run.sessionId, taskId, leg: liveStream });
|
|
845
|
+
const acceptance = await acceptLiveUserInput(deps, res, { messageId: interruptMessageId, sessionId: run.sessionId, taskId, leg: liveStream, tier: "now" });
|
|
835
846
|
if (acceptance === "refused")
|
|
836
847
|
return;
|
|
837
848
|
try {
|
|
@@ -848,7 +859,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
848
859
|
return;
|
|
849
860
|
}
|
|
850
861
|
if (code === "steering.duplicate_input_id") {
|
|
851
|
-
|
|
862
|
+
sendSteerReplayRefusal(res, e);
|
|
852
863
|
return;
|
|
853
864
|
}
|
|
854
865
|
if (code !== "steering.not_running")
|
|
@@ -1221,7 +1232,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1221
1232
|
return;
|
|
1222
1233
|
}
|
|
1223
1234
|
const messageId = uuidv7();
|
|
1224
|
-
const acceptance = await acceptLiveUserInput(deps, res, { messageId, sessionId: run.sessionId, taskId: runId, leg: handle });
|
|
1235
|
+
const acceptance = await acceptLiveUserInput(deps, res, { messageId, sessionId: run.sessionId, taskId: runId, leg: handle, tier: undefined });
|
|
1225
1236
|
if (acceptance === "refused")
|
|
1226
1237
|
return;
|
|
1227
1238
|
const marker = await relayAcceptedInput(deps, acceptance, () => handle.steer(redacted));
|
|
@@ -1361,33 +1372,26 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1361
1372
|
}
|
|
1362
1373
|
const parsedBody = AskDecisionBodySchema.safeParse(rawBody);
|
|
1363
1374
|
if (!parsedBody.success) {
|
|
1364
|
-
sendError(res, 400, "request.body_shape", "invalid ask decision body — expected decision=approve|deny plus optional updatedInput / note / idempotencyKey / actor.label /
|
|
1375
|
+
sendError(res, 400, "request.body_shape", "invalid ask decision body — expected decision=approve|deny plus optional updatedInput / note / idempotencyKey / actor.label / decidedBy");
|
|
1365
1376
|
return;
|
|
1366
1377
|
}
|
|
1367
1378
|
const body = parsedBody.data;
|
|
1368
|
-
const
|
|
1369
|
-
switch (
|
|
1379
|
+
const decidedByReading = readDecidedBy(body.decidedBy, deps.config.directDoorActive);
|
|
1380
|
+
switch (decidedByReading.kind) {
|
|
1370
1381
|
case "not_in_proof":
|
|
1371
|
-
sendError(res, 400, "
|
|
1372
|
-
return;
|
|
1373
|
-
case "deny_only":
|
|
1374
|
-
sendError(res, 400, "request.field_conflict", SETTLED_BY_DENY_ONLY);
|
|
1375
|
-
return;
|
|
1376
|
-
case "not_string":
|
|
1377
|
-
sendError(res, 400, "request.field_invalid", SETTLED_BY_MUST_BE_STRING);
|
|
1382
|
+
sendError(res, 400, "decided_by_not_in_proof", DECIDED_BY_NOT_IN_PROOF);
|
|
1378
1383
|
return;
|
|
1379
|
-
case "
|
|
1380
|
-
sendError(res, 400, "request.field_invalid",
|
|
1384
|
+
case "outside_closed_set":
|
|
1385
|
+
sendError(res, 400, "request.field_invalid", DECIDED_BY_OUTSIDE_CLOSED_SET);
|
|
1381
1386
|
return;
|
|
1382
|
-
case "absent":
|
|
1383
1387
|
case "accepted":
|
|
1384
1388
|
break;
|
|
1385
1389
|
default: {
|
|
1386
|
-
const unreachable =
|
|
1387
|
-
throw new Error(`unhandled
|
|
1390
|
+
const unreachable = decidedByReading;
|
|
1391
|
+
throw new Error(`unhandled decidedBy reading: ${JSON.stringify(unreachable)}`);
|
|
1388
1392
|
}
|
|
1389
1393
|
}
|
|
1390
|
-
const
|
|
1394
|
+
const decidedBy = decidedByReading.decidedBy;
|
|
1391
1395
|
const hasUpdatedInput = typeof rawBody === "object" && rawBody !== null && "updatedInput" in rawBody;
|
|
1392
1396
|
const taskId = match.params.taskId;
|
|
1393
1397
|
const askId = match.params.askId;
|
|
@@ -1513,6 +1517,10 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1513
1517
|
sendTerminal(row0);
|
|
1514
1518
|
return;
|
|
1515
1519
|
}
|
|
1520
|
+
if (policyCannotAllow(decidedBy, body.decision === "approve", storedCardDemandsAPerson(row0.cardJson))) {
|
|
1521
|
+
sendError(res, 400, "request.field_conflict", POLICY_CANNOT_ALLOW_PERSON_ASK);
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1516
1524
|
let outcome;
|
|
1517
1525
|
const releaseEditMark = body.decision === "approve" && hasUpdatedInput ? deps.toolApproval?.markEditedDecisionInFlight(askId) : undefined;
|
|
1518
1526
|
try {
|
|
@@ -1521,7 +1529,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1521
1529
|
decisionActor: actor,
|
|
1522
1530
|
decisionNote: body.note ?? null,
|
|
1523
1531
|
idempotencyKey: body.idempotencyKey ?? null,
|
|
1524
|
-
...(
|
|
1532
|
+
...((sb) => (sb !== undefined ? { settledBy: sb } : {}))(liveSettledBy(decidedBy)),
|
|
1525
1533
|
...(body.decision === "approve" && hasUpdatedInput ? { updatedInput: body.updatedInput } : {}),
|
|
1526
1534
|
});
|
|
1527
1535
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 单域独占状态(lens1 §C2 的 12 条里的 5 条)全部收进 `SessionsLocal`,每个 server 实例一份:
|
|
8
8
|
* SSE 连接坑计数 `sseConnections`、owner 查探许可 `sseOwnerLookups`、`/mcp` 面板的短 TTL single-flight
|
|
9
|
-
* 缓存 `mcpStatusCache`
|
|
9
|
+
* 缓存 `mcpStatusCache` 与它的时限常量(应答期限 `MCP_DIAL_DEADLINE_MS` 与 `POST …/mcp/reconnect` 共读,见 `../mcp-dial-deadline.ts`)。
|
|
10
10
|
*
|
|
11
11
|
* ⚠️ 搬运例外(仅此一处,语义等价):两个原为闭包 `let` 的计数器进了 local 盒子,于是 6 个使用点写成
|
|
12
12
|
* `sse.sseConnections` / `sse.sseOwnerLookups`——`let` 无法跨模块共享自增。判据/时机/配对释放逐字未动。
|
|
@@ -4,6 +4,7 @@ import { isUuidV7, isUuidShape, legacyUnownedSessionOk } from "../../security.js
|
|
|
4
4
|
import { windowMessages, truncateMessageBlobs } from "../../audit.js";
|
|
5
5
|
import { redactSecrets } from "../../trace/redact.js";
|
|
6
6
|
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
7
|
+
import { MCP_DIAL_DEADLINE_MS, raceDeadline } from "../mcp-dial-deadline.js";
|
|
7
8
|
import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.js";
|
|
8
9
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
|
|
9
10
|
import { readWritableSessionPermissionRules, describeSessionPermissionRulesProblem } from "../../session-policy-wire.js";
|
|
@@ -13,7 +14,6 @@ import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
|
|
|
13
14
|
import * as facts from "../../capability-facts.js";
|
|
14
15
|
import { rosterAgentId } from "./agents-roster.js";
|
|
15
16
|
const MCP_STATUS_TTL_MS = 5_000;
|
|
16
|
-
const MCP_MATERIALIZE_TIMEOUT_MS = 10_000;
|
|
17
17
|
const A2A_STATUS_TTL_MS = 5_000;
|
|
18
18
|
const A2A_MATERIALIZE_TIMEOUT_MS = 10_000;
|
|
19
19
|
const MEMORY_STATUS_TTL_MS = 5_000;
|
|
@@ -154,10 +154,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
154
154
|
mcpStatusCache.clear();
|
|
155
155
|
const panel = (async () => {
|
|
156
156
|
const matP = materializeMcpTools(specs, principal, undefined, undefined, undefined, deps.mcpRevocations);
|
|
157
|
-
|
|
158
|
-
const mat = await Promise.race([matP.catch(() => null), new Promise((r) => { timer = setTimeout(() => r(null), MCP_MATERIALIZE_TIMEOUT_MS); })]);
|
|
159
|
-
if (timer)
|
|
160
|
-
clearTimeout(timer);
|
|
157
|
+
const mat = await raceDeadline(matP, MCP_DIAL_DEADLINE_MS).catch(() => null);
|
|
161
158
|
if (!mat) {
|
|
162
159
|
void matP.then((m) => m?.dispose().catch(() => undefined)).catch(() => undefined);
|
|
163
160
|
return { servers: [], asOf: new Date().toISOString(), degraded: true };
|
|
@@ -530,7 +527,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
530
527
|
}
|
|
531
528
|
catch (err) {
|
|
532
529
|
const withheld = (coreMessage) => deps.logger?.warn?.("session_memory_erase_refused", { sessionId, err: coreMessage });
|
|
533
|
-
if (sentMemoryComplianceFailure(res, err, { kind: "session_owner", withheld }))
|
|
530
|
+
if (sentMemoryComplianceFailure(res, err, { kind: "session_owner", withheld }, deps.logger))
|
|
534
531
|
return;
|
|
535
532
|
deps.logger?.warn?.("session_memory_erase_failed", { sessionId, err: String(err) });
|
|
536
533
|
sendError(res, 500, "internal.error", "session memory erasure failed");
|
|
@@ -254,6 +254,8 @@ function projectParkRow(p) {
|
|
|
254
254
|
sessionId: p.sessionId,
|
|
255
255
|
originRunId: p.originRunId,
|
|
256
256
|
...(p.originUnconfirmed === true ? { originUnconfirmed: true } : {}),
|
|
257
|
+
...(p.parkedBy !== undefined ? { parkedBy: p.parkedBy } : {}),
|
|
258
|
+
...(p.generation !== undefined ? { generation: p.generation } : {}),
|
|
257
259
|
};
|
|
258
260
|
}
|
|
259
261
|
function summarizeWorkflowDetail(run) {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* cut: ONLY TaskRequestBody (its field types are inline or core imports — no other server.ts-local satellites);
|
|
7
7
|
* sibling request/response shapes can migrate here later on the same pattern.
|
|
8
8
|
*/
|
|
9
|
-
import type { A2aServerSpec, AgentDefinition, McpServerSpec, SkillSpec } from "@sema-agent/core";
|
|
9
|
+
import type { A2aServerSpec, AgentDefinition, ApprovalReach, McpServerSpec, SkillSpec } from "@sema-agent/core";
|
|
10
10
|
import type { SettingsPermissionMode } from "../task-settings.js";
|
|
11
11
|
import type { wiringManifestFaceEventData } from "../trace/project.js";
|
|
12
12
|
import type { buildSyncSubmitBody } from "./sync-submit-body.js";
|
|
@@ -466,6 +466,11 @@ export interface TaskRequestBody {
|
|
|
466
466
|
* typeof read. Declared per [ref] 件1 ([ref] boundary-must-schema): the submit chain reads this key, and an
|
|
467
467
|
* undeclared read is a bare cast the exported type cannot audit (embedders could not even write it). */
|
|
468
468
|
interactiveTools?: boolean;
|
|
469
|
+
/** core 7.43.0 `TaskSpec.approvalReach`: the run declares that NOBODY on it can be asked to approve (the only word: `"none"`; closed set and
|
|
470
|
+
* predicate are core's `APPROVAL_REACH` / `isApprovalReach`). Passed to the spec untranslated (boot/resolve-spec.ts) and to the per-leg
|
|
471
|
+
* interactive-posture judgment (an interactive posture beside it is refused by the engine); absent ⇒ key omitted (byte-identical); an
|
|
472
|
+
* unreadable value is a 400 on submit (admission.ts). Not checkpointed by the engine ⇒ resume legs re-supply it from the persisted body. */
|
|
473
|
+
approvalReach?: ApprovalReach;
|
|
469
474
|
/** The caller's per-request settings stamp (the client-side `SemaSettings` shape). The KEY SET is deliberately
|
|
470
475
|
* NOT mirrored here — a hand-written second shape on this interface would be the classic mirror-drift form.
|
|
471
476
|
* Per-key ownership is split, not single: `parseTaskSettings` (src/task-settings.ts) defensively owns the
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* **店形拒**(store-form refusal)在本仓的唯一读法属主(纯叶:零 http / 零 boot 依赖)—— core 一只铸点(`layout.ts` `storeFormRefusal`)的**两码一族**:
|
|
3
|
+
* · `memory.target_outside_store`(core 7.31.1 #1094):记忆 store 里的一个目录经软链通到了 store **外**(或 store 根在 backend 打开 / 引擎首次
|
|
4
|
+
* 挂载之后被改指)⇒ 引擎对落点在外的删 / 写 / 重放 / 自身写一律**不做**;
|
|
5
|
+
* · `memory.scope_home_not_directory`(core 7.44.0 #1368):一只 scope home(`<memoryDir>/<label>`)不是真目录 —— 被换成软链 / 普通文件 / 特殊文件
|
|
6
|
+
* (`detail.form` ∈ `symlink` | `file` | `other`,`lstat` 判、不跟链)。修前 core 对这一形抛不带码的 `ControlPlaneCorruptError`(与真控制面损坏同类同形),
|
|
7
|
+
* 平铺布局(条目直接住在 home 里)于是答 500,而同一换法在嵌套布局下一层答 409 —— 现在两布局同答这一码。
|
|
8
|
+
* 两码的形相同:普通 `Error` + `.code` + `.detail`(`{ path }`;home 那一码多 `form`),**句子以码领头、不含任何宿主路径**(core 7.44.0 起,
|
|
9
|
+
* 路径只在 `detail.path`)。core 的读法:operator 要处置的盘形(去掉那只链 / 放回真目录),**不是**可重试的 IO 故障,也**不是**对调用方的裁决;
|
|
10
|
+
* 盘形修好之前重发恒拒。本仓据此一条规则:两码同族、409、码原样;路径只进运维行(HTTP 映射一处,见 `http/memory-store-refusal-reply.ts`)。
|
|
8
11
|
*
|
|
9
12
|
* ## 两条到达车道,两个读者(本模块各给一只,别处不许再写第二份判据)
|
|
10
13
|
* ① **抛出面**(抹除 / 出处 / 外源列表 / 清标 / bundle 导出导入 —— 凡触 store 树的 backend 面):`.code` 就是码
|
|
11
|
-
* ⇒ {@link
|
|
14
|
+
* ⇒ {@link storeFormRefusalOf}。HTTP 映射在 `http/memory-store-refusal-reply.ts`(409,operator 面一处字面量)与
|
|
12
15
|
* `http/memory-compliance-failure-reply.ts`(合规面两受众的那一只;会话所有者受众换固定句,S-930)。
|
|
13
16
|
* ② **报告面**(harvest `incident`):core 契约写明 report 车道上码**只在文本里** —— `sidecar_corrupt` 的 detail 恰两种形
|
|
14
|
-
* `memory commit refused:
|
|
15
|
-
*
|
|
17
|
+
* `memory commit refused: <码>: …` / `memory control plane refused: <码>: …`(core `docs/wire-consumption/7.31.1.md` 的 harvest 行逐字;
|
|
18
|
+
* 7.44.0 安装树 `engine.js` 两处 incident 铸点同形,home 那一码经同一只 `writeFailureText` 到这里)⇒ {@link storeFormRefusalInIncident} 只认**这两个 lead 紧跟码**
|
|
16
19
|
* 的位置:lead 与码都是 core 铸的字面,路径与引用数据全在码之后。🔴 不做全文子串找(codex 一审 C2:一条真控制面损坏若在
|
|
17
20
|
* 它引用的坏数据里恰好带着这串字,子串找会把它标成目录链 —— 两者运维动作相反)。不读散文、不猜语义;结构化码位
|
|
18
21
|
* (`HarvestReport.incident` / `.warnings` 带码)是向 core 索取的欠账。
|
|
@@ -20,19 +23,41 @@
|
|
|
20
23
|
* 那几行的前缀是**条目路径**(模型可写)—— 本仓今天不读 warnings 车道(既有面,不是本批回归),也不在模型可写的文本上
|
|
21
24
|
* 找码;这一半候 core 的结构化码位(登记在案,codex 一审 C3)。
|
|
22
25
|
*
|
|
23
|
-
* ##
|
|
24
|
-
* · journal 重放目标目录成链 ⇒ `ControlPlaneCorruptError`(无 `.code`,KL-1094-b)⇒ 路由 typed 500 `internal.memory_control_plane_corrupt
|
|
26
|
+
* ## 不是这一族的三形(core 同表「different surface」,本仓**不**自拼码,如实走各自既有臂)
|
|
27
|
+
* · journal 重放目标目录成链 ⇒ `ControlPlaneCorruptError`(无 `.code`,KL-1094-b)⇒ 路由 typed 500 `internal.memory_control_plane_corrupt`
|
|
28
|
+
* (core 7.44.0 起 scope home 非目录**不再**走这一类 —— 它有了自己的码,见上;`ControlPlaneCorruptError` 只剩真控制面损坏);
|
|
25
29
|
* · 引擎原地写撞硬链(nlink>1)⇒ 文本 `has N names (a hard link)`、无码(KL-1094-g / #1105)⇒ 走该写点既有的失败车道(harvest
|
|
26
30
|
* warnings / 挂载失败),抛出面够不着;
|
|
27
31
|
* · 解析故障(`EACCES` / `ELOOP` / `ENOTDIR`)⇒ 以 errno 本身抛 ⇒ 路由未分类 500 + 运维 warn(`String(err)` 带 core 补救句)。
|
|
32
|
+
*
|
|
33
|
+
* ## 运维行也在这里铸(两受众同一只,{@link logStoreFormRefusal})
|
|
34
|
+
* 路径不上 wire,只进这一条运维 warn —— 它是**日志行**不是 wire 错误体,所以铸点跟读法属主住在一起(不住 `http/`:`http/` 全树门
|
|
35
|
+
* `test/error-code-key-gate.test.ts` 咬的是错误体里的 `code:` 键位,日志字段名不该借 wire 层的目录去过它)。
|
|
28
36
|
*/
|
|
29
|
-
|
|
37
|
+
import type { Logger } from "./observability/logger.js";
|
|
38
|
+
/** core 的两码(`dist/core/memory-engine/layout.js` `TARGET_OUTSIDE_STORE` / `SCOPE_HOME_NOT_DIRECTORY`;core 不在包根导出它们,这两个常量是它们在本仓的唯一写点)。 */
|
|
30
39
|
export declare const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
export
|
|
40
|
+
export declare const SCOPE_HOME_NOT_DIRECTORY = "memory.scope_home_not_directory";
|
|
41
|
+
/** 店形拒族(闭集;core 加第三码 ⇒ 它落未分类 500 + 运维 warn,由人裁归不归这一族)。 */
|
|
42
|
+
export declare const STORE_FORM_REFUSAL_CODES: readonly ["memory.target_outside_store", "memory.scope_home_not_directory"];
|
|
43
|
+
export type StoreFormRefusalCode = (typeof STORE_FORM_REFUSAL_CODES)[number];
|
|
44
|
+
/** 一次店形拒的读数(纯数据)。`path` / `form` 取自 core 的 `detail`(读不出 ⇒ `undefined`,不猜、不从句子里抠)。 */
|
|
45
|
+
export interface StoreFormRefusal {
|
|
46
|
+
readonly code: StoreFormRefusalCode;
|
|
47
|
+
/** core 原句(以码领头;7.44.0 起不含宿主路径)。 */
|
|
48
|
+
readonly message: string;
|
|
49
|
+
/** `detail.path`:落点 / 被改指的根 / 不是目录的 scope home —— **只给运维行**。 */
|
|
50
|
+
readonly path: string | undefined;
|
|
51
|
+
/** `detail.form`(只在 home 那一码上):原样字符串,不镜像 core 的闭集(本仓只把它写进运维行,不按它分支)。 */
|
|
52
|
+
readonly form: string | undefined;
|
|
53
|
+
}
|
|
54
|
+
/** 抛出面:`err` 是店形拒 ⇒ 读数;否则 `undefined`。形 = core 的 `Error` + `.code` + `.detail`。 */
|
|
55
|
+
export declare function storeFormRefusalOf(err: unknown): StoreFormRefusal | undefined;
|
|
56
|
+
/** 两受众共用的运维行(一处铸点):码 + core `detail` 里的宿主路径与 home 形。`logger` 缺席(只有不接 logger 的测试装配)⇒ 不落。 */
|
|
57
|
+
export declare function logStoreFormRefusal(logger: Pick<Logger, "warn"> | undefined, refusal: StoreFormRefusal): void;
|
|
58
|
+
/** 报告面:harvest `incident` 是这一族拒 ⇒ 码;否则 `undefined`(真控制面损坏的 `sidecar_corrupt` 走这一臂 —— 不被说成店形拒)。 */
|
|
59
|
+
export declare function storeFormRefusalInIncident(incident: {
|
|
35
60
|
readonly kind: string;
|
|
36
61
|
readonly detail: string;
|
|
37
|
-
}):
|
|
62
|
+
}): StoreFormRefusalCode | undefined;
|
|
38
63
|
//# sourceMappingURL=memory-store-refusal.d.ts.map
|
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
export const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
|
|
2
|
-
export
|
|
2
|
+
export const SCOPE_HOME_NOT_DIRECTORY = "memory.scope_home_not_directory";
|
|
3
|
+
export const STORE_FORM_REFUSAL_CODES = [TARGET_OUTSIDE_STORE, SCOPE_HOME_NOT_DIRECTORY];
|
|
4
|
+
const isStoreFormRefusalCode = (code) => typeof code === "string" && STORE_FORM_REFUSAL_CODES.includes(code);
|
|
5
|
+
export function storeFormRefusalOf(err) {
|
|
3
6
|
if (!(err instanceof Error))
|
|
4
7
|
return undefined;
|
|
5
|
-
const { code } = err;
|
|
6
|
-
|
|
8
|
+
const { code, detail } = err;
|
|
9
|
+
if (!isStoreFormRefusalCode(code))
|
|
10
|
+
return undefined;
|
|
11
|
+
const d = typeof detail === "object" && detail !== null ? detail : {};
|
|
12
|
+
return { code, message: err.message, path: typeof d.path === "string" ? d.path : undefined, form: typeof d.form === "string" ? d.form : undefined };
|
|
13
|
+
}
|
|
14
|
+
export function logStoreFormRefusal(logger, refusal) {
|
|
15
|
+
logger?.warn("memory_store_form_refused", {
|
|
16
|
+
code: refusal.code,
|
|
17
|
+
...(refusal.path !== undefined ? { path: refusal.path } : {}),
|
|
18
|
+
...(refusal.form !== undefined ? { form: refusal.form } : {}),
|
|
19
|
+
});
|
|
7
20
|
}
|
|
8
|
-
const
|
|
9
|
-
export function
|
|
21
|
+
const INCIDENT_LEADS = ["memory commit refused: ", "memory control plane refused: "];
|
|
22
|
+
export function storeFormRefusalInIncident(incident) {
|
|
10
23
|
if (incident.kind !== "sidecar_corrupt")
|
|
11
24
|
return undefined;
|
|
12
|
-
|
|
25
|
+
for (const lead of INCIDENT_LEADS) {
|
|
26
|
+
if (!incident.detail.startsWith(lead))
|
|
27
|
+
continue;
|
|
28
|
+
const rest = incident.detail.slice(lead.length);
|
|
29
|
+
return STORE_FORM_REFUSAL_CODES.find((code) => rest.startsWith(`${code}: `));
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
13
32
|
}
|
|
14
33
|
//# sourceMappingURL=memory-store-refusal.js.map
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* 重试 / 子代对同一个网关再失败,不再说一遍)。受众 user;detail = `{ code, targetHost, proxyInUse?, remedy, sessionId }` ——
|
|
24
24
|
* `proxyInUse` 只在这一跳走了代理时在场,且只是**变量名**(代理地址是基础设施细节,不上租户 wire,与 `OutboundProxyError` 的
|
|
25
25
|
* F3 裁定同一条)。`targetHost`(模型端点的 `host:port`)对 user 受众是**新披露的部署事实**(F7 裁定:它是部署自己声明的模型端点,
|
|
26
|
-
* 不是基础设施秘密,可以上;代理地址仍不上)——
|
|
26
|
+
* 不是基础设施秘密,可以上;代理地址仍不上)—— 不要读成「终局句里本就有」:代理形下终局句只带码与 `via outbound proxy (from <变量>)`、不含任何主机地址,
|
|
27
27
|
* 辅助调用形下 run 可 completed、租户面本无主机句。
|
|
28
28
|
*/
|
|
29
29
|
import type { Brain } from "@sema-agent/core";
|