@sema-agent/server 7.116.1 → 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 +195 -0
- package/USAGE.md +65 -41
- package/dist/approval-card.d.ts +82 -18
- package/dist/approval-card.js +46 -4
- package/dist/approval.d.ts +4 -12
- package/dist/approval.js +4 -26
- package/dist/boot/budget-tracing.js +2 -3
- package/dist/boot/config-center.d.ts +2 -2
- package/dist/boot/config-center.js +5 -7
- 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 +18 -16
- package/dist/boot/runner-deps.d.ts +5 -3
- package/dist/boot/runner-deps.js +6 -4
- package/dist/boot/stage-07-capability-layer.js +2 -2
- package/dist/brain.d.ts +17 -8
- package/dist/brain.js +10 -7
- 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-catalog.js +3 -2
- package/dist/config-center/apply-effective.d.ts +6 -16
- package/dist/config-center/apply-effective.js +0 -6
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/restart-signal.d.ts +1 -1
- package/dist/config-types.d.ts +12 -16
- package/dist/config.d.ts +32 -29
- package/dist/config.js +20 -27
- package/dist/deployment-governance.d.ts +13 -19
- package/dist/deployment-governance.js +7 -14
- package/dist/governance-overrides.d.ts +20 -0
- package/dist/governance-overrides.js +8 -0
- package/dist/host-decision.d.ts +83 -25
- package/dist/host-decision.js +31 -1
- package/dist/http/admission.js +13 -7
- package/dist/http/approval-sweeps.js +16 -13
- package/dist/http/checkpoint-ctx.d.ts +83 -24
- package/dist/http/checkpoint-ctx.js +68 -8
- 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 +18 -18
- package/dist/http/retired-skill-manifest.d.ts +1 -1
- package/dist/http/route-ctx.d.ts +3 -3
- package/dist/http/routes/approvals-assistant.js +21 -24
- package/dist/http/routes/capabilities.js +5 -3
- 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 +53 -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-select.d.ts +16 -5
- package/dist/model-select.js +7 -3
- package/dist/model-unreachable-notice.d.ts +1 -1
- package/dist/namespaced-rule-name.d.ts +45 -39
- 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 +15 -3
- package/dist/observability/fail-open.js +15 -3
- package/dist/observability/permission-rule-events.d.ts +3 -0
- package/dist/observability/permission-rule-events.js +4 -0
- package/dist/observability/run-terminal.js +21 -1
- 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 +47 -20
- package/dist/outbound-dispatcher.js +198 -23
- package/dist/parked-decide.d.ts +43 -48
- package/dist/parked-decide.js +54 -16
- package/dist/permission-rule-list.d.ts +20 -31
- 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 +25 -0
- package/dist/plugins/one-time-migrations.js +192 -6
- 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/router/route-orchestration.d.ts +2 -23
- package/dist/rule-entry-list.d.ts +88 -0
- package/dist/rule-entry-list.js +102 -0
- package/dist/rules-consent.js +3 -0
- package/dist/run-local.d.ts +4 -2
- package/dist/run-local.js +4 -3
- package/dist/runs.d.ts +1 -1
- package/dist/runs.js +1 -0
- package/dist/runtime-governance.d.ts +5 -21
- package/dist/runtime-governance.js +4 -8
- package/dist/session-policy-wire.d.ts +13 -12
- package/dist/session-policy-wire.js +11 -6
- package/dist/spec-fields.d.ts +13 -24
- package/dist/spec-fields.js +13 -17
- package/dist/standing-answer-grant.d.ts +14 -2
- package/dist/standing-answer-grant.js +8 -1
- package/dist/task-settings.d.ts +18 -18
- package/dist/task-settings.js +63 -32
- package/dist/tool-approval.d.ts +77 -145
- package/dist/tool-approval.js +80 -62
- package/dist/trace/core-keyset-guard.d.ts +6 -6
- package/dist/trace/engine-notice-wire.d.ts +4 -6
- package/dist/trace/engine-notice-wire.js +14 -1
- 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 +103 -7
- package/scripts/drain-check.mysql.sql +59 -8
- package/scripts/drain-check.pg.sql +66 -10
package/dist/tool-approval.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { contentKindOf } from "./approval-content-kind.js";
|
|
2
|
+
import { liveSettledBy, policyCannotAllow, readDecidedBy, DECIDED_BY_OUTSIDE_CLOSED_SET, POLICY_CANNOT_ALLOW_PERSON_ASK, SETTLED_BY_RETIRED } from "./host-decision.js";
|
|
2
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
-
import { uuidv7, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, parseRuleText, pathConfinableWriteToolNames } from "@sema-agent/core";
|
|
4
|
+
import { uuidv7, classifyAsk, foldArbitrationRow, screenAskClassMembers, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, parseRuleText, pathConfinableWriteToolNames } from "@sema-agent/core";
|
|
4
5
|
import { precheckCardRuleText } from "./rules-consent.js";
|
|
5
6
|
import { redactDeep, redactSecrets, redactSecretsWithArms } from "./trace/redact.js";
|
|
6
7
|
import { hasBidiControls } from "./text-bidi.js";
|
|
@@ -8,12 +9,12 @@ import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerpri
|
|
|
8
9
|
import { createLogger } from "./observability/logger.js";
|
|
9
10
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
10
11
|
import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
|
|
11
|
-
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readRuleEvidence, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readClosedWordSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
12
|
-
import {
|
|
12
|
+
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readRuleEvidence, readAllowRuleShadowed, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readClosedWordSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
13
|
+
import { POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
|
|
13
14
|
import { settleByPolicy } from "./approval-policy-settlement.js";
|
|
14
15
|
import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
|
|
15
16
|
import { governanceOriginOf } from "./runtime-governance.js";
|
|
16
|
-
import { answerGrantsBeyondCall } from "./standing-answer-grant.js";
|
|
17
|
+
import { answerGrantsBeyondCall, grantBindingOf } from "./standing-answer-grant.js";
|
|
17
18
|
const defaultLogger = createLogger();
|
|
18
19
|
export const APPROVAL_GATE_KINDS = ["human", "irreversible_ask"];
|
|
19
20
|
export function isApprovalGateKind(k) {
|
|
@@ -81,11 +82,37 @@ const FS_WRITE_TOOLS = pathConfinableWriteToolNames();
|
|
|
81
82
|
function sessionAllowCategory(toolName) {
|
|
82
83
|
return FS_WRITE_TOOLS.has(toolName) ? "fs-write" : `tool:${toolName}`;
|
|
83
84
|
}
|
|
84
|
-
function sessionAllowKey(owner, sessionId, category) {
|
|
85
|
-
return JSON.stringify([owner, sessionId, category]);
|
|
85
|
+
function sessionAllowKey(owner, sessionId, category, binding) {
|
|
86
|
+
return JSON.stringify([owner, sessionId, category, binding.kind === "bound" ? binding.digest : null]);
|
|
87
|
+
}
|
|
88
|
+
function askStandingAnswerFacts(req, governanceForced) {
|
|
89
|
+
return {
|
|
90
|
+
governanceForced,
|
|
91
|
+
requiresRealApproval: req.requiresRealApproval === true,
|
|
92
|
+
mandated: req.mandated === true,
|
|
93
|
+
grantBinding: grantBindingOf(req),
|
|
94
|
+
classes: screenAskClassMembers(req.askClass, req.classes)?.classes,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function policyAskStandingAnswerFacts(req, ask) {
|
|
98
|
+
return {
|
|
99
|
+
governanceForced: false,
|
|
100
|
+
requiresRealApproval: ask.requiresRealApproval === true,
|
|
101
|
+
mandated: ask.mandated === true,
|
|
102
|
+
grantBinding: grantBindingOf(req),
|
|
103
|
+
classes: [...classifyAsk(ask, { contentQuestion: false, markedUnresolvable: false, org: undefined, tightened: undefined, mandateMarks: {} }).classes],
|
|
104
|
+
};
|
|
86
105
|
}
|
|
87
106
|
export function standingAnswerMayRetire(facts) {
|
|
88
|
-
return !facts.governanceForced &&
|
|
107
|
+
return (!facts.governanceForced &&
|
|
108
|
+
!facts.requiresRealApproval &&
|
|
109
|
+
!facts.mandated &&
|
|
110
|
+
facts.grantBinding.kind !== "unreadable" &&
|
|
111
|
+
facts.classes !== undefined &&
|
|
112
|
+
foldArbitrationRow(new Set(facts.classes)).ruleMayClear);
|
|
113
|
+
}
|
|
114
|
+
export function standingAnswerDurablyRecordable(facts) {
|
|
115
|
+
return standingAnswerMayRetire(facts) && facts.grantBinding.kind === "none";
|
|
89
116
|
}
|
|
90
117
|
export function parkRowStandingAnswerFacts(pending, gate, governance) {
|
|
91
118
|
const riskDescriptor = "riskDescriptor" in gate ? gate.riskDescriptor : undefined;
|
|
@@ -93,7 +120,8 @@ export function parkRowStandingAnswerFacts(pending, gate, governance) {
|
|
|
93
120
|
governanceForced: governanceOriginOf({ riskDescriptor }, governance) === true,
|
|
94
121
|
requiresRealApproval: readParkRowTrueSeat(pending, "requiresRealApproval"),
|
|
95
122
|
mandated: readParkRowTrueSeat(pending, "mandated") || mandatedFromSeat(readRuleOffersAbsenceSeat(pending)),
|
|
96
|
-
|
|
123
|
+
grantBinding: grantBindingOf(pending),
|
|
124
|
+
classes: screenAskClassMembers(pending.askClass, pending.classes)?.classes,
|
|
97
125
|
};
|
|
98
126
|
}
|
|
99
127
|
function pendingEntryStandingAnswerFacts(entry) {
|
|
@@ -101,7 +129,8 @@ function pendingEntryStandingAnswerFacts(entry) {
|
|
|
101
129
|
governanceForced: entry.governanceForced === true,
|
|
102
130
|
requiresRealApproval: entry.requiresRealApproval === true,
|
|
103
131
|
mandated: entry.mandated === true,
|
|
104
|
-
|
|
132
|
+
grantBinding: entry.grantBinding,
|
|
133
|
+
classes: entry.classes,
|
|
105
134
|
};
|
|
106
135
|
}
|
|
107
136
|
function mandatedFromSeat(seat) {
|
|
@@ -118,25 +147,6 @@ export const PERSIST_RULE_TEXT_ERROR = `persistRule.rule must be a non-empty str
|
|
|
118
147
|
export const PERSIST_RULE_EDITED_FLAG_ERROR = "persistRule.edited must be a boolean when present";
|
|
119
148
|
export const PERSIST_RULE_BATCH_INDEX_ERROR = "persistRule.batchOfferIndex must be a non-negative integer when present";
|
|
120
149
|
export const PERSIST_RULE_BATCH_EXCLUSIVE_ERROR = "persistRule.batchOfferIndex is mutually exclusive with persistRule.rule / persistRule.edited — send exactly one arm";
|
|
121
|
-
export const SETTLED_BY_MUST_BE_STRING = "settledBy must be a string — the settlement source is one word from the engine's closed set, and this layer judges only the SHAPE (which word it is, the engine decides)";
|
|
122
|
-
export function respondSettledBy(parsed) {
|
|
123
|
-
return parsed.settledBy;
|
|
124
|
-
}
|
|
125
|
-
export const SETTLED_BY_DENY_ONLY = 'settledBy is only meaningful with decision "deny" — only a person approves an action that EXECUTES, so a self-reported settlement source on an allow is a contradiction the engine refuses; the refusal would arrive too late to undo this door\'s own side effects (a session allow-all grant, a persisted rule, a DECIDED row that replays as a plain approval), so it is refused here, before the decision';
|
|
126
|
-
export const SETTLED_BY_TOO_WIDE = `settledBy must be 1-${MAX_SETTLED_BY_CHARS} characters — the settlement source is one word from the engine's closed set and lands in a fixed-width column on the decision row`;
|
|
127
|
-
export function judgeSettledByBody(settledBy, decision, directDoorActive) {
|
|
128
|
-
if (settledBy === undefined)
|
|
129
|
-
return { kind: "absent" };
|
|
130
|
-
if (directDoorActive)
|
|
131
|
-
return { kind: "not_in_proof" };
|
|
132
|
-
if (decision !== "deny")
|
|
133
|
-
return { kind: "deny_only" };
|
|
134
|
-
if (typeof settledBy !== "string")
|
|
135
|
-
return { kind: "not_string" };
|
|
136
|
-
if (!settledByFitsColumn(settledBy))
|
|
137
|
-
return { kind: "too_wide" };
|
|
138
|
-
return { kind: "accepted", settledBy };
|
|
139
|
-
}
|
|
140
150
|
export const READ_ROOT_GRANT_ALLOW_ONLY = 'readRootGrant is only meaningful with decision "allow" — it is the card\'s "Yes, and add directory" answer (allow this call and add the card\'s readRootCandidate.dir to this run\'s read directories)';
|
|
141
151
|
export const READ_ROOT_GRANT_SHAPE = `readRootGrant must be the card's readRootCandidate.dir sent back byte for byte — a non-empty string of at most ${READ_ROOT_CANDIDATE_DIR_MAX} characters`;
|
|
142
152
|
export const PERSIST_RULE_SCOPE_REFUSED = "this endpoint does not accept a rule scope — the server mints it from where the approval happened (send neither `scope` nor `persistRule.scope`)";
|
|
@@ -183,23 +193,19 @@ export function parseToolApprovalResponse(body) {
|
|
|
183
193
|
}
|
|
184
194
|
note = rawNote;
|
|
185
195
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
case "
|
|
192
|
-
return { ok: false, error:
|
|
193
|
-
case "too_wide":
|
|
194
|
-
return { ok: false, error: SETTLED_BY_TOO_WIDE };
|
|
195
|
-
case "absent":
|
|
196
|
-
break;
|
|
196
|
+
if (body.settledBy !== undefined)
|
|
197
|
+
return { ok: false, error: SETTLED_BY_RETIRED };
|
|
198
|
+
const decidedByReading = readDecidedBy(body.decidedBy, false);
|
|
199
|
+
let decidedBy;
|
|
200
|
+
switch (decidedByReading.kind) {
|
|
201
|
+
case "outside_closed_set":
|
|
202
|
+
return { ok: false, error: DECIDED_BY_OUTSIDE_CLOSED_SET };
|
|
197
203
|
case "accepted":
|
|
198
|
-
|
|
204
|
+
decidedBy = decidedByReading.decidedBy;
|
|
199
205
|
break;
|
|
200
206
|
default: {
|
|
201
|
-
const unreachable =
|
|
202
|
-
throw new Error(`unhandled
|
|
207
|
+
const unreachable = decidedByReading;
|
|
208
|
+
throw new Error(`unhandled decidedBy reading: ${JSON.stringify(unreachable)}`);
|
|
203
209
|
}
|
|
204
210
|
}
|
|
205
211
|
const rawGrant = body.readRootGrant;
|
|
@@ -214,7 +220,7 @@ export function parseToolApprovalResponse(body) {
|
|
|
214
220
|
...(d !== "deny" && u !== undefined ? { updatedInput: u } : {}),
|
|
215
221
|
...(persistRule !== undefined ? { persistRule } : {}),
|
|
216
222
|
...(note !== undefined ? { note } : {}),
|
|
217
|
-
|
|
223
|
+
decidedBy,
|
|
218
224
|
...(readRootGrant !== undefined ? { readRootGrant } : {}),
|
|
219
225
|
};
|
|
220
226
|
}
|
|
@@ -310,9 +316,10 @@ function replayTerminalAskRow(row) {
|
|
|
310
316
|
return false;
|
|
311
317
|
}
|
|
312
318
|
export function outcomeOfDecidedRow(row) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
319
|
+
const allow = row.decision === "approve";
|
|
320
|
+
const source = row.settledBy !== null ? { settledBy: row.settledBy } : {};
|
|
321
|
+
const edit = allow && row.updatedInput !== undefined ? { updatedInput: row.updatedInput } : {};
|
|
322
|
+
return row.settledBy === null && !("updatedInput" in edit) ? allow : { allow, ...edit, ...source };
|
|
316
323
|
}
|
|
317
324
|
function settleArgsOfDecided(outcome) {
|
|
318
325
|
if (typeof outcome === "boolean")
|
|
@@ -751,13 +758,14 @@ export class ToolApprovalCoordinator {
|
|
|
751
758
|
return (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(origin.owner, origin.sessionId, origin.taskId))).isMarked(req.toolCallId);
|
|
752
759
|
}
|
|
753
760
|
async standingGrantSettles(origin, req, governanceForced) {
|
|
754
|
-
|
|
761
|
+
const facts = askStandingAnswerFacts(req, governanceForced);
|
|
762
|
+
if (!standingAnswerMayRetire(facts))
|
|
755
763
|
return false;
|
|
756
764
|
const sessionId = origin.sessionId;
|
|
757
765
|
if (!sessionId)
|
|
758
766
|
return false;
|
|
759
|
-
const live = this.allowAllSessions.has(sessionAllowKey(origin.owner, sessionId, sessionAllowCategory(req.toolName)));
|
|
760
|
-
const durable = !live && (await this.durableGrantHas(sessionId, req.toolName));
|
|
767
|
+
const live = this.allowAllSessions.has(sessionAllowKey(origin.owner, sessionId, sessionAllowCategory(req.toolName), facts.grantBinding));
|
|
768
|
+
const durable = !live && !answerGrantsBeyondCall(req) && (await this.durableGrantHas(sessionId, req.toolName));
|
|
761
769
|
if (!live && !durable)
|
|
762
770
|
return false;
|
|
763
771
|
if (!(await this.sessionGrantUncontestedByRow(origin, req)))
|
|
@@ -1071,6 +1079,7 @@ export class ToolApprovalCoordinator {
|
|
|
1071
1079
|
let effectiveGovernanceForced = governanceForced;
|
|
1072
1080
|
let ruleLaneMaterial = this.buildRuleLaneMaterial(req, origin.owner, governanceForced, origin.sessionId);
|
|
1073
1081
|
const mandateWord = readClosedWordSeat(req, "mandate");
|
|
1082
|
+
const noRulingCause = readClosedWordSeat(req, "classifierNoRuling");
|
|
1074
1083
|
const originWord = typeof req.origin === "string" && req.origin.length > 0 ? req.origin : undefined;
|
|
1075
1084
|
const askClassPair = readAskClassPair(req);
|
|
1076
1085
|
const frame = {
|
|
@@ -1098,13 +1107,13 @@ export class ToolApprovalCoordinator {
|
|
|
1098
1107
|
...(governanceForced ? { governanceForced: true } : {}),
|
|
1099
1108
|
...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
1100
1109
|
...(req.mandated === true ? { mandated: true } : {}),
|
|
1101
|
-
...(standingAnswerMayRetire(
|
|
1110
|
+
...(standingAnswerMayRetire(askStandingAnswerFacts(req, governanceForced)) ? {} : { rememberRefused: true }),
|
|
1111
|
+
...(typeof req.boundGrantDigest === "string" ? { boundGrantDigest: req.boundGrantDigest, ...(req.boundGrants !== undefined ? { boundGrants: req.boundGrants } : {}) } : {}),
|
|
1112
|
+
...(noRulingCause !== undefined ? { classifierNoRuling: noRulingCause } : {}),
|
|
1102
1113
|
...(mandateWord !== undefined ? { mandate: mandateWord } : {}),
|
|
1103
1114
|
...(originWord !== undefined ? { origin: originWord } : {}),
|
|
1104
1115
|
...(askClassPair ?? {}),
|
|
1105
|
-
...(
|
|
1106
|
-
? { persistedRuleShadowed: redactSecrets(req.persistedRuleShadowed).slice(0, MAX_RULE_TEXT_CHARS) }
|
|
1107
|
-
: {}),
|
|
1116
|
+
...((d) => (d !== undefined ? { allowRuleShadowed: d } : {}))(readAllowRuleShadowed(req)),
|
|
1108
1117
|
...(ruleLaneMaterial !== undefined ? { ruleOffers: ruleLaneMaterial.offers } : {}),
|
|
1109
1118
|
...((a) => (a !== undefined ? { ruleOffersAbsence: a } : {}))(readRuleOffersAbsence(req)),
|
|
1110
1119
|
...((f) => (f !== undefined ? { denialLimitFallback: f } : {}))(readDenialLimitFallback(req)),
|
|
@@ -1206,10 +1215,10 @@ export class ToolApprovalCoordinator {
|
|
|
1206
1215
|
frame.governanceForced = true;
|
|
1207
1216
|
else
|
|
1208
1217
|
delete frame.governanceForced;
|
|
1209
|
-
if (
|
|
1210
|
-
frame.
|
|
1218
|
+
if (cardForFrame.allowRuleShadowed !== undefined)
|
|
1219
|
+
frame.allowRuleShadowed = cardForFrame.allowRuleShadowed;
|
|
1211
1220
|
else
|
|
1212
|
-
delete frame.
|
|
1221
|
+
delete frame.allowRuleShadowed;
|
|
1213
1222
|
if (cardForFrame.inputHasBidi === true)
|
|
1214
1223
|
frame.inputHasBidi = true;
|
|
1215
1224
|
else
|
|
@@ -1308,11 +1317,12 @@ export class ToolApprovalCoordinator {
|
|
|
1308
1317
|
return unattended === "unavailable" ? unattended : shapeDeny(unattended);
|
|
1309
1318
|
}
|
|
1310
1319
|
if (settled.allowed) {
|
|
1311
|
-
if (settled.updatedInput === undefined && settled.readRootGrant === undefined)
|
|
1320
|
+
if (hostSettled === undefined && settled.updatedInput === undefined && settled.readRootGrant === undefined)
|
|
1312
1321
|
return true;
|
|
1313
1322
|
return {
|
|
1314
1323
|
allow: true,
|
|
1315
1324
|
...(settled.updatedInput !== undefined ? { updatedInput: settled.updatedInput } : {}),
|
|
1325
|
+
...(hostSettled !== undefined ? { settledBy: hostSettled } : {}),
|
|
1316
1326
|
...(settled.readRootGrant !== undefined ? { readRootGrant: settled.readRootGrant } : {}),
|
|
1317
1327
|
};
|
|
1318
1328
|
}
|
|
@@ -1625,11 +1635,14 @@ export class ToolApprovalCoordinator {
|
|
|
1625
1635
|
return shapeOutcome(await allowedP);
|
|
1626
1636
|
}
|
|
1627
1637
|
}
|
|
1638
|
+
const entryGrantBinding = grantBindingOf(req);
|
|
1639
|
+
const entryClasses = askClassPair?.classes;
|
|
1628
1640
|
const rememberRefused = !standingAnswerMayRetire(pendingEntryStandingAnswerFacts({
|
|
1629
1641
|
...(effectiveGovernanceForced ? { governanceForced: true } : {}),
|
|
1630
1642
|
...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
1631
1643
|
...(req.mandated === true ? { mandated: true } : {}),
|
|
1632
|
-
|
|
1644
|
+
grantBinding: entryGrantBinding,
|
|
1645
|
+
classes: entryClasses,
|
|
1633
1646
|
}));
|
|
1634
1647
|
if (rememberRefused)
|
|
1635
1648
|
frame.rememberRefused = true;
|
|
@@ -1650,6 +1663,8 @@ export class ToolApprovalCoordinator {
|
|
|
1650
1663
|
...(origin.sessionId ? { sessionId: origin.sessionId } : {}),
|
|
1651
1664
|
frame,
|
|
1652
1665
|
toolName: req.toolName,
|
|
1666
|
+
grantBinding: entryGrantBinding,
|
|
1667
|
+
...(entryClasses !== undefined ? { classes: entryClasses } : {}),
|
|
1653
1668
|
registeredAtMs: Date.now(),
|
|
1654
1669
|
expiresAtMs: persistedExpiresAtMs,
|
|
1655
1670
|
...(ruleLaneMaterial !== undefined ? { ruleLane: ruleLaneMaterial } : {}),
|
|
@@ -1787,6 +1802,9 @@ export class ToolApprovalCoordinator {
|
|
|
1787
1802
|
const preRule = this.precheckEditedRuleBeforeDecision(entry, parsed);
|
|
1788
1803
|
if (preRule !== undefined)
|
|
1789
1804
|
return preRule;
|
|
1805
|
+
if (policyCannotAllow(parsed.decidedBy, parsed.value !== "deny", entry.requiresRealApproval === true)) {
|
|
1806
|
+
return { status: 400, body: { error: POLICY_CANNOT_ALLOW_PERSON_ASK, errorCode: "request.body_shape" } };
|
|
1807
|
+
}
|
|
1790
1808
|
const ruleEligibility = { eligible: true };
|
|
1791
1809
|
const settled = this.askStore && entry.askId !== undefined && entry.batchId !== undefined
|
|
1792
1810
|
? this.respondWithCas(this.askStore, entry.askId, entry.batchId, id, entry, parsed, ruleEligibility)
|
|
@@ -1947,7 +1965,7 @@ export class ToolApprovalCoordinator {
|
|
|
1947
1965
|
gateBoundInputHash: row.gateBoundInputHash,
|
|
1948
1966
|
decision,
|
|
1949
1967
|
...(parsed.note !== undefined ? { note: parsed.note } : {}),
|
|
1950
|
-
|
|
1968
|
+
decidedBy: parsed.decidedBy,
|
|
1951
1969
|
...(parsed.updatedInput !== undefined && parsed.value !== "deny" ? { updatedInput: parsed.updatedInput } : {}),
|
|
1952
1970
|
...(principal !== undefined ? { principal } : {}),
|
|
1953
1971
|
...(httpReq !== undefined ? { httpReq } : {}),
|
|
@@ -2185,7 +2203,7 @@ export class ToolApprovalCoordinator {
|
|
|
2185
2203
|
decision: parsed.value === "deny" ? "deny" : "approve",
|
|
2186
2204
|
...(parsed.note !== undefined ? { decisionNote: parsed.note } : {}),
|
|
2187
2205
|
...(parsed.value !== "deny" && parsed.updatedInput !== undefined ? { updatedInput: parsed.updatedInput } : {}),
|
|
2188
|
-
...(
|
|
2206
|
+
...((sb) => (sb !== undefined ? { settledBy: sb } : {}))(liveSettledBy(parsed.decidedBy)),
|
|
2189
2207
|
}), "decideAsk", DURABLE_CALL_TIMEOUT_MS, (late) => {
|
|
2190
2208
|
if (late.ok)
|
|
2191
2209
|
this.notifyExternalDecision(askId, outcomeOfDecidedRow(late.row));
|
|
@@ -2246,10 +2264,10 @@ export class ToolApprovalCoordinator {
|
|
|
2246
2264
|
if (oldest !== undefined)
|
|
2247
2265
|
this.allowAllSessions.delete(oldest);
|
|
2248
2266
|
}
|
|
2249
|
-
this.allowAllSessions.add(sessionAllowKey(entry.owner, entry.sessionId, sessionAllowCategory(entry.toolName)));
|
|
2267
|
+
this.allowAllSessions.add(sessionAllowKey(entry.owner, entry.sessionId, sessionAllowCategory(entry.toolName), entry.grantBinding));
|
|
2250
2268
|
}
|
|
2251
2269
|
const allowed = parsed.value !== "deny";
|
|
2252
|
-
const settleArgs = opts?.settleOutcome !== undefined ? settleArgsOfDecided(opts.settleOutcome) : { allowed, updatedInput: parsed.updatedInput, settledBy:
|
|
2270
|
+
const settleArgs = opts?.settleOutcome !== undefined ? settleArgsOfDecided(opts.settleOutcome) : { allowed, updatedInput: parsed.updatedInput, settledBy: liveSettledBy(parsed.decidedBy) };
|
|
2253
2271
|
const readRootGrant = opts?.settleOutcome === undefined || opts.settleOutcomeIsThisRequest === true ? parsed.readRootGrant : undefined;
|
|
2254
2272
|
entry.settle(settleArgs.allowed, settleArgs.allowed ? "allowed" : "denied", settleArgs.updatedInput, settleArgs.settledBy, settleArgs.allowed ? undefined : parsed.note, readRootGrant);
|
|
2255
2273
|
const updatedInputDelivered = opts?.updatedInputDelivered ?? true;
|
|
@@ -43,7 +43,7 @@ type _GuardBgNotifReverse = AssertAllKeysHandled<Exclude<BgNotifProjected | BgNo
|
|
|
43
43
|
type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
|
|
44
44
|
type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
|
|
45
45
|
type _GuardRosterReverse = AssertAllKeysHandled<Exclude<RosterProjected, keyof RosterEntry>>;
|
|
46
|
-
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "
|
|
46
|
+
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "allowRuleShadowed" | "probeCause" | "probeReason" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "readRootCandidate" | "mandated" | "mandate" | "askClass" | "classes" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling";
|
|
47
47
|
type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "hasBidiControls" | "previewWithheld" | "execCwd" | "authoredBy";
|
|
48
48
|
type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
|
|
49
49
|
type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded, keyof AskRequest>>;
|
|
@@ -129,14 +129,14 @@ type WiringGovernanceHandled = "audience" | "lockedConfig" | "compliance" | "mem
|
|
|
129
129
|
type _GuardWiringGovernance = AssertAllKeysHandled<Exclude<keyof WiringSection<"governance">, WiringGovernanceHandled>>;
|
|
130
130
|
type _GuardWiringGovernanceReverse = AssertAllKeysHandled<Exclude<WiringGovernanceHandled, keyof WiringSection<"governance">>>;
|
|
131
131
|
type PermissionTraceKind = Extract<TraceEvent["kind"], `permission.${string}`>;
|
|
132
|
-
export type PermissionTraceProjected = "permission.persisted_rule_allowed" | "permission.rule_store_unreadable" | "permission.read_only_allowed" | "permission.skill_allowed";
|
|
132
|
+
export type PermissionTraceProjected = "permission.persisted_rule_allowed" | "permission.rule_store_unreadable" | "permission.read_only_allowed" | "permission.skill_allowed" | "permission.allowed_tools_allowed";
|
|
133
133
|
type PermissionTraceDropped = "permission.sandbox_admitted" | "permission.rule_sync_resurrected" | "permission.rule_sync_dropped" | "permission.org_snapshot_unavailable";
|
|
134
134
|
type _GuardPermissionTrace = AssertAllKeysHandled<Exclude<PermissionTraceKind, PermissionTraceProjected | PermissionTraceDropped>>;
|
|
135
135
|
type _GuardPermissionTraceReverse = AssertAllKeysHandled<Exclude<PermissionTraceProjected | PermissionTraceDropped, PermissionTraceKind>>;
|
|
136
136
|
/** `T` 非 `never` 时收敛到 `never`(空集 ⇒ 残留 `"EMPTY"` ⇒ tsc 红)。 */
|
|
137
137
|
type AssertNonEmpty<T> = [T] extends [never] ? "EMPTY" : never;
|
|
138
138
|
type _GuardPermissionTraceNonEmpty = AssertAllKeysHandled<AssertNonEmpty<PermissionTraceKind>>;
|
|
139
|
-
type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "mandated" | "mandate";
|
|
139
|
+
type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "mandated" | "mandate" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling";
|
|
140
140
|
type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld" | "authoredBy";
|
|
141
141
|
type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
|
|
142
142
|
type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
|
|
@@ -144,11 +144,11 @@ type ParkArmKeys = keyof Extract<PendingAction, {
|
|
|
144
144
|
kind: "tool_approval";
|
|
145
145
|
}>;
|
|
146
146
|
type ParkArmQueueProjected = "toolName" | "toolCallId" | "boundInputHash" | "ruleOffers" | "hasBidiControls" | keyof import("../approval-card.js").ParkRowFacts;
|
|
147
|
-
type ParkArmQueueExcluded = "kind" | "args" | "batchToolCallIds" | "completedCallIds" | "capabilityId" | "contractId" | "shapeDigest" | "implementationRevision" | "rosterDigest" | "family" | "pathTarget" | "names" | "preview" | "previewWithheld" | "execCwd" | "denialLimitFallback" | "authoredBy" | "boundGrantDigest";
|
|
147
|
+
type ParkArmQueueExcluded = "kind" | "args" | "batchToolCallIds" | "completedCallIds" | "capabilityId" | "contractId" | "shapeDigest" | "implementationRevision" | "rosterDigest" | "family" | "pathTarget" | "names" | "preview" | "previewWithheld" | "execCwd" | "denialLimitFallback" | "authoredBy" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling";
|
|
148
148
|
type _GuardParkArmQueue = AssertAllKeysHandled<Exclude<ParkArmKeys, ParkArmQueueProjected | ParkArmQueueExcluded>>;
|
|
149
149
|
type _GuardParkArmQueueReverse = AssertAllKeysHandled<Exclude<ParkArmQueueProjected | ParkArmQueueExcluded, ParkArmKeys>>;
|
|
150
150
|
type _NoDoubleBookParkArmQueue = AssertAllKeysHandled<Extract<ParkArmQueueProjected, ParkArmQueueExcluded>>;
|
|
151
|
-
type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "permissionMode" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools" | "fileHistory" | "userInvokedSkills";
|
|
151
|
+
type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "approvalReach" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "permissionMode" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools" | "fileHistory" | "userInvokedSkills" | "allowedTools";
|
|
152
152
|
type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence" | "outputStyle";
|
|
153
153
|
type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
|
|
154
154
|
type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
|
|
@@ -199,7 +199,7 @@ type WorkflowRunStripped = "sourceTaskId" | "originatingSessionId" | "effectiveA
|
|
|
199
199
|
type _GuardWorkflowRun = AssertAllKeysHandled<Exclude<keyof WorkflowRun, WorkflowRunForwarded | WorkflowRunStripped>>;
|
|
200
200
|
type _GuardWorkflowRunReverse = AssertAllKeysHandled<Exclude<WorkflowRunForwarded | WorkflowRunStripped, keyof WorkflowRun>>;
|
|
201
201
|
type ParkRowKeys = keyof NonNullable<WorkflowRun["parks"]>[number];
|
|
202
|
-
type ParkRowProjected = "callKey" | "sessionId" | "originRunId" | "originUnconfirmed";
|
|
202
|
+
type ParkRowProjected = "callKey" | "sessionId" | "originRunId" | "originUnconfirmed" | "parkedBy" | "generation";
|
|
203
203
|
type ParkRowWithheld = "token";
|
|
204
204
|
type _GuardParkRow = AssertAllKeysHandled<Exclude<ParkRowKeys, ParkRowProjected | ParkRowWithheld>>;
|
|
205
205
|
type _NoDoubleBookParkRow = AssertAllKeysHandled<Extract<ParkRowProjected, ParkRowWithheld>>;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* 同文件 G1 e2e 的全码扇出夹具、以及契约文档附录 D.3 那张表(含**小节标题里的码数** —— 那道门本批
|
|
17
17
|
* 刚立,立完就在下一次加码时自己咬住了)。
|
|
18
18
|
*/
|
|
19
|
-
export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed", "session.records_skipped", "server.model_unreachable", "classifier.unavailable", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "task.tool_turn_incomplete"];
|
|
19
|
+
export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed", "session.records_skipped", "server.model_unreachable", "classifier.unavailable", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.allowed_tool_not_granted", "task.tool_turn_incomplete"];
|
|
20
20
|
export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
|
|
21
21
|
/** 白名单谓词(单点):路由与门都读这一个,不许第二处手抄码串。 */
|
|
22
22
|
export declare function isEngineNoticeWireCode(code: string): code is EngineNoticeWireCode;
|
|
@@ -63,12 +63,10 @@ export interface EngineNoticeWireRow {
|
|
|
63
63
|
*/
|
|
64
64
|
export declare function sanitizeNoticeDetail(detail: Record<string, unknown> | undefined): Record<string, unknown>;
|
|
65
65
|
/**
|
|
66
|
-
* `
|
|
67
|
-
*
|
|
68
|
-
* 与 `trace/project.ts` 的 `*EventData` 族同纪律(白名单挑键、禁 `{...notice}`),但**不住在那个文件里**:
|
|
69
|
-
* 那一族的入参全是 core `TaskEvent` 的臂(有 `wire-whitelist-exhaustiveness` 的臂集编译门看着),
|
|
70
|
-
* 而 `EngineNotice` 不是 `TaskEvent` —— 混进去会让那道门的语义变成"两个不同来源的联合",判别力反而降。
|
|
66
|
+
* 通告 `detail` 里的**规则条目原串**过呈现口(`presentRuleEntry`:剥 C0 / 双向控制符、超长截断)—— 会话流行(经 {@link engineNoticeEventData})
|
|
67
|
+
* 与运维日志行(`boot/runner-deps.ts` 的转发席)同调这一只。其余字段、其余码原样(多行文本不受影响)。
|
|
71
68
|
*/
|
|
69
|
+
export declare function presentNoticeDetail(code: string, detail: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
|
|
72
70
|
export declare function engineNoticeEventData(notice: {
|
|
73
71
|
code: string;
|
|
74
72
|
message: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { redactSecrets } from "./redact.js";
|
|
2
|
+
import { presentRuleEntry } from "../rule-entry-list.js";
|
|
2
3
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
3
4
|
export const ENGINE_NOTICE_WIRE_CODES = [
|
|
4
5
|
"memory.session_polluted",
|
|
@@ -33,6 +34,7 @@ export const ENGINE_NOTICE_WIRE_CODES = [
|
|
|
33
34
|
"permission.ask_released_by_mode",
|
|
34
35
|
"permission.read_deny_unapplied",
|
|
35
36
|
"permission.rule_store_unavailable",
|
|
37
|
+
"permission.allowed_tool_not_granted",
|
|
36
38
|
"task.tool_turn_incomplete",
|
|
37
39
|
];
|
|
38
40
|
const WIRE_CODES = new Set(ENGINE_NOTICE_WIRE_CODES);
|
|
@@ -90,11 +92,22 @@ function sanitizeValue(v, depth) {
|
|
|
90
92
|
}
|
|
91
93
|
return undefined;
|
|
92
94
|
}
|
|
95
|
+
const RULE_ENTRY_DETAIL_ARRAYS = { "permission.allowed_tool_not_granted": "entries" };
|
|
96
|
+
export function presentNoticeDetail(code, detail) {
|
|
97
|
+
const key = Object.hasOwn(RULE_ENTRY_DETAIL_ARRAYS, code) ? RULE_ENTRY_DETAIL_ARRAYS[code] : undefined;
|
|
98
|
+
if (key === undefined || detail === undefined)
|
|
99
|
+
return detail;
|
|
100
|
+
const list = detail[key];
|
|
101
|
+
if (!Array.isArray(list))
|
|
102
|
+
return detail;
|
|
103
|
+
const presented = list.map((item) => item !== null && typeof item === "object" && typeof item.entry === "string" ? { ...item, entry: presentRuleEntry(item.entry) } : item);
|
|
104
|
+
return { ...detail, [key]: presented };
|
|
105
|
+
}
|
|
93
106
|
export function engineNoticeEventData(notice, sessionId, nowMs) {
|
|
94
107
|
return {
|
|
95
108
|
code: notice.code,
|
|
96
109
|
message: redactSecrets(String(notice.message)).slice(0, NOTICE_TEXT_MAX),
|
|
97
|
-
detail: sanitizeNoticeDetail(notice.detail),
|
|
110
|
+
detail: sanitizeNoticeDetail(presentNoticeDetail(notice.code, notice.detail)),
|
|
98
111
|
sessionId,
|
|
99
112
|
ts: nowMs,
|
|
100
113
|
};
|
|
@@ -19,9 +19,9 @@ export declare const SELF_MINTED_WIRE: {
|
|
|
19
19
|
};
|
|
20
20
|
};
|
|
21
21
|
/**
|
|
22
|
-
* S-928(7.110.0):`engine_notice` `task.user_steer_undrained` 上**本仓铸**的那一臂。词表属主是 core(`
|
|
22
|
+
* S-928(7.110.0):`engine_notice` `task.user_steer_undrained` 上**本仓铸**的那一臂。词表属主是 core(`USER_INPUT_UNDRAINED_CAUSES`,两族共用;
|
|
23
23
|
* `leg_end` 由引擎铸、逐字过境,不在本表);本表登记的是「这个值自哪一版起由本服务出现在 wire 上」。`satisfies Record<CrashedCause, …>`:
|
|
24
|
-
* core 删掉 `crashed` ⇒ `CrashedCause` 成 never ⇒
|
|
24
|
+
* core 删掉 `crashed` ⇒ `CrashedCause` 成 never ⇒ 本行编译红。码按受理时的车道选(`user-input-ledger.ts` `userInputLaneOf`):本行 = steer 车道。
|
|
25
25
|
*/
|
|
26
26
|
readonly userSteerUndrainedServerCause: {
|
|
27
27
|
readonly at: "engine_notice task.user_steer_undrained detail.cause(本服务铸的臂;下一条同会话 run 开通告口那一拍)";
|
|
@@ -32,6 +32,16 @@ export declare const SELF_MINTED_WIRE: {
|
|
|
32
32
|
};
|
|
33
33
|
};
|
|
34
34
|
};
|
|
35
|
+
/** S-1122(7.118.0,core 7.44.0 #1362):同一臂在 follow-up 车道(`later` 档)上的那一族。7.110.0–7.117.x 的这类丢失报在 steer 一族里。 */
|
|
36
|
+
readonly userFollowupUndrainedServerCause: {
|
|
37
|
+
readonly at: "engine_notice task.user_followup_undrained detail.cause(本服务铸的臂;`later` 档受理的崩溃丢失,下一条同会话 run 开通告口那一拍)";
|
|
38
|
+
readonly members: {
|
|
39
|
+
crashed: {
|
|
40
|
+
attr: string;
|
|
41
|
+
since: string;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
35
45
|
/**
|
|
36
46
|
* S-961(7.111.0):`wiring_manifest.instructions.selected[]` 的成员值 = 指令文件链的成员(相对 cwd 的拼写)。属主是 `project-memory.ts`
|
|
37
47
|
* 的 `INSTRUCTION_FILE_CHAIN`([ref] 序:CLAUDE 族先、AGENTS 族后,首中即选)—— 链加 / 删一员 ⇒ 本行 `satisfies` 编译红,登记跟着动。
|
|
@@ -95,6 +105,10 @@ export declare const SELF_MINTED_WIRE: {
|
|
|
95
105
|
readonly attr: 409;
|
|
96
106
|
readonly since: "7.101.0";
|
|
97
107
|
};
|
|
108
|
+
readonly "memory.scope_home_not_directory": {
|
|
109
|
+
readonly attr: 409;
|
|
110
|
+
readonly since: "7.118.0";
|
|
111
|
+
};
|
|
98
112
|
readonly "memory.erasure_evidence_unavailable": {
|
|
99
113
|
readonly attr: 409;
|
|
100
114
|
readonly since: "7.108.0";
|
|
@@ -12,6 +12,12 @@ export const SELF_MINTED_WIRE = {
|
|
|
12
12
|
crashed: { attr: "-", since: "7.110.0" },
|
|
13
13
|
},
|
|
14
14
|
},
|
|
15
|
+
userFollowupUndrainedServerCause: {
|
|
16
|
+
at: "engine_notice task.user_followup_undrained detail.cause(本服务铸的臂;`later` 档受理的崩溃丢失,下一条同会话 run 开通告口那一拍)",
|
|
17
|
+
members: {
|
|
18
|
+
crashed: { attr: "-", since: "7.118.0" },
|
|
19
|
+
},
|
|
20
|
+
},
|
|
15
21
|
wiringInstructionsSelected: {
|
|
16
22
|
at: "wiring_manifest instructions.selected[](本腿 loader 选中的链成员;段缺席 = 本腿无陈述)",
|
|
17
23
|
members: {
|
|
@@ -34,6 +40,7 @@ export const SELF_MINTED_WIRE = {
|
|
|
34
40
|
members: {
|
|
35
41
|
"config.memory_erasure_request": { attr: 400, since: "7.52.0" },
|
|
36
42
|
"memory.target_outside_store": { attr: 409, since: "7.101.0" },
|
|
43
|
+
"memory.scope_home_not_directory": { attr: 409, since: "7.118.0" },
|
|
37
44
|
"memory.erasure_evidence_unavailable": { attr: 409, since: "7.108.0" },
|
|
38
45
|
"memory.erasure_selector_mismatch": { attr: 409, since: "7.108.0" },
|
|
39
46
|
"memory.erasure_census_incomplete": { attr: 409, since: "7.108.0" },
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `POST /v1/runs/:id/steer` 对本副本的活 run 回 **200 `delivery:"applied"`**:输入只进了 core `TaskStream` 的**进程内**队列,
|
|
7
7
|
* 本服务此前不落任何字节。「你的输入丢了」今天唯一的告知是 core 在**腿终局**铸的 `task.user_steer_undrained`(`cause:"leg_end"`)——
|
|
8
8
|
* 进程被 SIGKILL / 崩溃时那个终局席跑不到 ⇒ **零告知**:壳手里只有一张 200 回执,重启后主请求与转录里都没有它。
|
|
9
|
-
* core
|
|
9
|
+
* core 给了词:`detail.cause` 闭集 {@link USER_INPUT_UNDRAINED_CAUSES} = `leg_end` / `crashed`,`crashed` 由**服务层**在
|
|
10
10
|
* 该会话下一条 run 之前铸。本文件 = 那个服务层。
|
|
11
11
|
*
|
|
12
12
|
* ## 对象(一只账本,一类输入,一句规则)
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
* (core `WorkflowTaskHandle.sessionScoped`),活得比发起它的 run 腿长,没有一条 run 行约束它,而 workflow run 行上没有持有者列 ⇒ 判不出写者死没死(另票);
|
|
23
23
|
* 子代 resume(`…/subagents/:target/resume`)不往在飞的队列里投,起的是一条新的子代腿;follow-up 在本仓**没有**独立的受理口(`src/http/routes` 零
|
|
24
24
|
* follow-up 入口)。steer 的 `later` 档在引擎里走 follow-up 车道(引擎在腿收场时按车道分账:没投出去的 `later` 记进 `task.user_followup_undrained`),
|
|
25
|
-
* 但它仍是一次 `TaskStream.steer` 受理、消费帧同为 `human_input{delivery:"applied"}`(两车道同一只消费口)⇒
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* 但它仍是一次 `TaskStream.steer` 受理、消费帧同为 `human_input{delivery:"applied"}`(两车道同一只消费口)⇒ 同进本账,消费即结清。
|
|
26
|
+
* **族 = 受理时它排进的引擎车道**({@link UserInputKind}:`steer` / `follow_up`,由档位经 {@link userInputLaneOf} 一只函数得出),崩溃告知的码按族选
|
|
27
|
+
* (core 7.44.0 #1362:两族同一只成因词表 `USER_INPUT_UNDRAINED_CAUSES`,服务层的 `crashed` 臂按车道选码,与引擎自己的 `leg_end` 臂同一种分账)。
|
|
28
28
|
*
|
|
29
29
|
* ## 五形行(append-only;行序 = 语义序;File 孪生的盘上形 = 下面的 {@link UserInputLedgerRecord},SQL 孪生 = 一行一受理 + CAS 列 + 删行,同义)
|
|
30
30
|
* · `accepted` —— 受理点(`routes/runs.ts` 的 live 腿,**调 `TaskStream.steer` 之前**、回 200 之前)写;写不进 ⇒ 503
|
|
@@ -85,17 +85,28 @@
|
|
|
85
85
|
* 不开旋钮(没有「关掉告知」的正当需求)· 不改 steer 的 200 体(回执仍是「已交付」,账本是它的影子)· 不重放丢失的输入(core 明写
|
|
86
86
|
* NOT redelivered;本账只告知,不替人决定重发)· 不存输入正文(行里只有身份与时刻)。
|
|
87
87
|
*/
|
|
88
|
-
import { type EngineNoticeCode, type TaskEvent, type
|
|
88
|
+
import { type EngineNoticeCode, type SystemInjectionPriority, type TaskEvent, type UserInputUndrainedCause } from "@sema-agent/core";
|
|
89
89
|
import { type RunRecord } from "./plugins/store-contracts.js";
|
|
90
90
|
import { type EngineNoticeRouter } from "./trace/engine-notice-wire.js";
|
|
91
91
|
/** local 数据根下的子目录(File 孪生;与 `approval-ask-audit/` 同层同形)。 */
|
|
92
92
|
export declare const USER_INPUT_LEDGER_DIR = "user-input-ledger";
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
/**
|
|
94
|
+
* 账本收的输入族(闭集)= 受理时它排进的**引擎车道**:`steer`(`now` / `next` 档)/ `follow_up`(`later` 档)。崩溃告知的码按族选
|
|
95
|
+
* ({@link UNDRAINED_NOTICE_OF}),读面行的 `kind` 就是它。`follow_up` 自 7.118.0 起写(之前的版本受理口一律写 `steer`,`later` 档的旧行照它当时的族读)。
|
|
96
|
+
*/
|
|
97
|
+
export type UserInputKind = "steer" | "follow_up";
|
|
98
|
+
/** 读盘词表(`satisfies Record<…, true>` ⇒ 闭集加词而本表没跟 = 编译红;判定走 `Object.hasOwn`)。≤7.117.x 的读腿不认 `follow_up`(回滚须知在 MIGRATION)。 */
|
|
96
99
|
export declare const USER_INPUT_KIND_WORDS: {
|
|
97
100
|
readonly steer: true;
|
|
101
|
+
readonly follow_up: true;
|
|
98
102
|
};
|
|
103
|
+
/**
|
|
104
|
+
* 一次受理排进哪条引擎车道 —— 三个受理口(run steer 的 live 腿 / interrupt 带 text 形 / 子代 steer)同调这一只。core 原句(`TaskStream.steer` 注):
|
|
105
|
+
* 「a `"later"` steer rides the followUp lane, so its stranded account is `task.user_followup_undrained`」;`now` / `next` 走 steer 车道。
|
|
106
|
+
* 档位型取 core 包根的 `SystemInjectionPriority`(`TaskStream.steer` 的 `priority`;缺席 = 引擎缺省 `next`),本仓不复述:core 加一档 ⇒ 下面的
|
|
107
|
+
* 穷举 `never` 臂编译红,由人裁新档排进哪条车道。
|
|
108
|
+
*/
|
|
109
|
+
export declare function userInputLaneOf(tier: SystemInjectionPriority | undefined): UserInputKind;
|
|
99
110
|
/** `settled` 的两种收场(闭集,见顶注)。 */
|
|
100
111
|
export type SettleHow = "leg_end" | "not_applied" | "consumed";
|
|
101
112
|
/** 读盘词表(File 孪生的折叠守卫 / SQL 孪生的 `settle_how` 列读腿同读这一张;`consumed` 自 7.113.0 起,≤7.112.x 的读腿不认它 —— 回滚须知在 MIGRATION)。 */
|
|
@@ -105,7 +116,7 @@ export declare const SETTLE_HOW_WORDS: {
|
|
|
105
116
|
readonly consumed: true;
|
|
106
117
|
};
|
|
107
118
|
/** 本服务铸的那一臂(core 删掉这个词 ⇒ `Extract` 得 `never` ⇒ 下面的常量编译红)。 */
|
|
108
|
-
export type CrashedCause = Extract<
|
|
119
|
+
export type CrashedCause = Extract<UserInputUndrainedCause, "crashed">;
|
|
109
120
|
export declare const CRASHED_CAUSE: CrashedCause;
|
|
110
121
|
/** 盘上 / 行上的 cause 词判:必须是 core 闭集的成员,**且**铸者是服务层(词表外 / 引擎臂 ⇒ 不是本账能写的行)。 */
|
|
111
122
|
export declare function isServingLayerUndrainedCause(v: unknown): v is CrashedCause;
|
|
@@ -328,11 +339,12 @@ export interface UserInputLedger {
|
|
|
328
339
|
readSession(sessionId: string): Promise<UndrainedUserInputRow[] | undefined>;
|
|
329
340
|
}
|
|
330
341
|
/**
|
|
331
|
-
* 一条旧 run
|
|
342
|
+
* 一条旧 run 一族的 `crashed` 通告(纯数据)。`detail` 键集 = core 契约 `{ steer | followUp, cause, taskId?, sessionId?, runId? }` 的子集(`runId` 缺席,理由见顶注)。
|
|
332
343
|
* `message` 只是 fallback 展示(core 同族句的形,只换成因);消费端按 `code` + `detail.cause` 分支。
|
|
333
344
|
*/
|
|
334
|
-
export declare function
|
|
335
|
-
|
|
345
|
+
export declare function buildCrashedUndrainedNotice(input: {
|
|
346
|
+
family: UserInputKind;
|
|
347
|
+
count: number;
|
|
336
348
|
taskId: string;
|
|
337
349
|
sessionId: string;
|
|
338
350
|
}): {
|