@sema-agent/server 7.47.0 → 7.48.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/USAGE.md +10 -0
- package/dist/boot/stores.js +13 -0
- package/dist/config.d.ts +4 -1
- package/dist/config.js +15 -1
- package/dist/fleet/fleet-bus.d.ts +93 -6
- package/dist/fleet/fleet-bus.js +40 -1
- package/dist/hooks/hook-runner.d.ts +10 -10
- package/dist/hooks/hook-runner.js +142 -23
- package/dist/http/route-ctx.d.ts +24 -0
- package/dist/http/route-ctx.js +8 -0
- package/dist/http/routes/approvals-assistant.js +4 -11
- package/dist/http/routes/capabilities.js +1 -1
- package/dist/http/routes/fleet.js +2 -2
- package/dist/http/routes/notify-wake.js +2 -3
- package/dist/http/routes/runs.js +26 -2
- package/dist/http/routes/tasks.js +5 -0
- package/dist/http/server.js +4 -0
- package/dist/observability/fail-open.d.ts +8 -0
- package/dist/observability/fail-open.js +8 -0
- package/dist/plugins/approval-ask-store-memory.js +2 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +50 -1
- package/dist/plugins/approval-ask-store-sql.js +40 -5
- package/dist/plugins/permission-rule-store-sql.d.ts +33 -0
- package/dist/plugins/permission-rule-store-sql.js +1 -8
- package/dist/plugins/sql-errors.d.ts +18 -0
- package/dist/plugins/sql-errors.js +10 -0
- package/dist/resource-window.d.ts +111 -0
- package/dist/resource-window.js +80 -0
- package/dist/rules-consent.d.ts +29 -1
- package/dist/rules-consent.js +22 -1
- package/dist/tool-approval.d.ts +47 -1
- package/dist/tool-approval.js +131 -11
- package/package.json +1 -1
package/dist/rules-consent.js
CHANGED
|
@@ -101,6 +101,27 @@ function refusedMembersOf(members) {
|
|
|
101
101
|
}
|
|
102
102
|
return refused;
|
|
103
103
|
}
|
|
104
|
+
const anchorLogger = createLogger();
|
|
105
|
+
function anchorBatchMembers(members, displayed) {
|
|
106
|
+
const seatOf = new Map();
|
|
107
|
+
displayed.rules.forEach((r, i) => seatOf.set(r.rule, i));
|
|
108
|
+
const anchored = [];
|
|
109
|
+
for (const m of members) {
|
|
110
|
+
const memberIndex = seatOf.get(m.rule);
|
|
111
|
+
if (memberIndex === undefined) {
|
|
112
|
+
anchorLogger.warn("redeemed batch member is not a member of the offer the human confirmed — the receipt ships without per-rule anchors (core/dist skew?)", { rule: m.rule });
|
|
113
|
+
return { kind: "batch", rules: members.map((x) => x.rule) };
|
|
114
|
+
}
|
|
115
|
+
anchored.push({ memberIndex, rule: m.rule });
|
|
116
|
+
}
|
|
117
|
+
const distinctSeats = new Set(anchored.map((a) => a.memberIndex)).size;
|
|
118
|
+
if (anchored.length !== displayed.rules.length || distinctSeats !== anchored.length) {
|
|
119
|
+
anchorLogger.warn("redeemed batch does not line up one-to-one with the offer the human confirmed — the receipt ships without per-rule anchors (core/dist skew?)", { landed: anchored.length, offered: displayed.rules.length, distinctSeats });
|
|
120
|
+
return { kind: "batch", rules: members.map((x) => x.rule) };
|
|
121
|
+
}
|
|
122
|
+
anchored.sort((a, b) => a.memberIndex - b.memberIndex);
|
|
123
|
+
return { kind: "batch", rules: anchored.map((a) => a.rule), members: anchored };
|
|
124
|
+
}
|
|
104
125
|
export function createRuleConsentLane(stores, opts) {
|
|
105
126
|
const deps = { provider: stores.provider, approvals: stores.approvals, cardEdits: true };
|
|
106
127
|
const ticketTtlMs = opts?.ticketTtlMs ?? RULE_IMPORT_TICKET_TTL_MS;
|
|
@@ -158,7 +179,7 @@ export function createRuleConsentLane(stores, opts) {
|
|
|
158
179
|
if (refused.length > 0) {
|
|
159
180
|
return { ok: false, reason: "redeem-refused", detail: `partial batch: ${refused.length} member(s) refused (${refused.map((x) => x.reason).join("; ")})` };
|
|
160
181
|
}
|
|
161
|
-
return { ok: true,
|
|
182
|
+
return { ok: true, ...anchorBatchMembers(result.members, redemption.offer), rev: result.rev };
|
|
162
183
|
}
|
|
163
184
|
let ticket;
|
|
164
185
|
if (redemption.kind === "offered") {
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -348,7 +348,20 @@ export type RuleRefusalReason = Extract<CardRulePersisted, {
|
|
|
348
348
|
* `rule_lane_unavailable` 刻意分词**:那个说的是「这台部署压根不供规则」(壳可以从此不渲这一格),
|
|
349
349
|
* 这个说的是「规则车道好好的,只是**这一只** ask 归 operator 管」—— 折成同一个词会让壳把一台正常
|
|
350
350
|
* 部署整条车道判死。 */
|
|
351
|
-
| "rule_governance_forced"
|
|
351
|
+
| "rule_governance_forced"
|
|
352
|
+
/**
|
|
353
|
+
* #363:**这一行**没有可铸规则的素材 —— durable(PARKED)迟到决议腿专属。
|
|
354
|
+
*
|
|
355
|
+
* 成因闭集(三条,处置相同):①这只 ask 从没进过规则车道(治理档 / 无属主 / 引擎没铸候选 / args 里
|
|
356
|
+
* 读不出命令原字节)⇒ `rule_command` 列 NULL;②行由**加这两列之前**的构建落下;③行上的卡读不回来
|
|
357
|
+
* (信封 schema 判假)⇒ 候选表与工具名都取不到。
|
|
358
|
+
*
|
|
359
|
+
* 🔴 **与 `rule_lane_unavailable` 刻意分词**(理由与上面那条 `rule_governance_forced` 逐字同源):
|
|
360
|
+
* 那个词的语义是「这台部署压根不供规则」,壳据它可以**从此不渲这一格**;而这里的真相是「车道好好的,
|
|
361
|
+
* 只是**这一行**上没有素材」—— 折成同一个词,一次 park 迟到答就能让壳把一台正常部署的整条车道判死。
|
|
362
|
+
* 静默忽略更不行(安全/审批轴禁静默 fail-open):人勾了「不再询问」而什么都没发生,他不会再勾第二次。
|
|
363
|
+
*/
|
|
364
|
+
| "rule_material_absent";
|
|
352
365
|
/**
|
|
353
366
|
* #340([4763])—— `persistRule` 的**两个显式臂**在 wire 上的解析产物。
|
|
354
367
|
*
|
|
@@ -1013,6 +1026,39 @@ export declare class ToolApprovalCoordinator {
|
|
|
1013
1026
|
* 非 2xx 原样透出(gate 被别人收走 ⇒ 409 等),绝不粉饰成受理。
|
|
1014
1027
|
*/
|
|
1015
1028
|
private redeemParkedAsk;
|
|
1029
|
+
/**
|
|
1030
|
+
* #363 —— **durable 回决腿的规则位**:PARKED 行的迟到决议携 `persistRule` 时,用**行上**的素材兑付。
|
|
1031
|
+
*
|
|
1032
|
+
* 回 `undefined` = 这次请求没带 `persistRule`(一个键都不加,老壳的回执逐字节不变);否则回一个
|
|
1033
|
+
* `{rulePersisted, ruleRefusal?, …echo}` 片段,由调用点铺进 200 体 —— 与 live 腿 `withFlag` 的键集
|
|
1034
|
+
* **同形**(壳的解析器一套判据吃两条腿)。
|
|
1035
|
+
*
|
|
1036
|
+
* ## 素材从哪来(这是本方法存在的全部理由)
|
|
1037
|
+
* live 腿的等式左边是**进程内条目** `PendingApproval.ruleLane`,而 park 路由把条目连同素材一起散场
|
|
1038
|
+
* 了(#329 的墓碑只桥身份,不携素材)。本腿因此从**行**取,一件一件对着 live 腿的入参:
|
|
1039
|
+
* · `command` = `rule_command` 列(**#363 新列**;缺席即本方法唯一的新拒因,见下);
|
|
1040
|
+
* · `scope` = `rule_scope_root` 列 ⇒ `{kind:"project", root}`,缺席则**不铸 scope**(core 落 global,
|
|
1041
|
+
* = live 腿 `material.scopeRoot` 缺席时逐字同形 —— 绝不在坐标系不明时编一个 root);
|
|
1042
|
+
* · 候选表 / `toolName` / 治理位 = 行上的 `card_json`(呈卡帧的同一份;**人看见的就是它**)。
|
|
1043
|
+
* ⚠️ 卡上的 `toolName` 带 200 字符的投影帽(`approval-card.ts` 的 `MAX_IDENT`),理论上可被截。
|
|
1044
|
+
* **那一支结构性 fail-closed、不会静默存错规则**:规则文本由 `toolName` + 命令派生,截过的名字
|
|
1045
|
+
* 让 core 重铸出**另一条**规则 ⇒ 候选臂的文本等式当场不成立(`unknown-candidate`),编辑臂则被
|
|
1046
|
+
* core 自己的覆盖门拒。现网不可达(canonical / `mcp__srv__tool` 都远短于 200),记在这里免得
|
|
1047
|
+
* 下一个人以为是漏了;
|
|
1048
|
+
* · `toolCallId` / `boundInputHash` = 行上的同名列(与 live 腿铸素材时读的是同一个 `req` 的同两件)。
|
|
1049
|
+
*
|
|
1050
|
+
* ## 三道门与 live 腿逐条同源(顺序也同,理由逐字见 `persistRuleAfterDecision`)
|
|
1051
|
+
* 治理档先判(否则一只治理 ask 会拿到「这台部署不供规则」这个更宽的词)→ 车道/属主 → 素材 →
|
|
1052
|
+
* `updatedInput`(ctrl+g 编辑放行 ⇒ `rule_input_edited`:生效的是编辑后的调用,而候选是从**原始**
|
|
1053
|
+
* 命令铸的)→ 候选臂的文本等式。
|
|
1054
|
+
*
|
|
1055
|
+
* ## 与 live 腿**刻意不同**的一处:没有裁决前的自由文本预检(#345 的那道 400)
|
|
1056
|
+
* 那道门的语义是「拒的时候这张卡还在,改好再来」;而本腿走到这里时赎回腿的 CAS **已经赢了**
|
|
1057
|
+
* (checkpoint 已被消费),卡不可能还在 —— 400 回去只会把一次**已经生效**的放行谎报成失败。
|
|
1058
|
+
* 所以编辑臂在本腿上照旧落「裁决受理 + `rulePersisted:false` + `edit-rejected`」那条老路(= #345
|
|
1059
|
+
* 之前 live 腿的行为),如实登记为本腿的残余。
|
|
1060
|
+
*/
|
|
1061
|
+
private persistRuleFromRow;
|
|
1016
1062
|
/** #329:回执/单飞胜者的**回放形** —— 与首次受理同键集,多一位 `idempotent`,`decision` 恒是**受理时**
|
|
1017
1063
|
* 那一向(反向重试不改判,与 DECIDED 臂同一条纪律)。 */
|
|
1018
1064
|
private replayRedeemReceipt;
|
package/dist/tool-approval.js
CHANGED
|
@@ -24,6 +24,17 @@ const DEFAULT_PARK_TOMBSTONE_MAX = 2000;
|
|
|
24
24
|
const DEFAULT_PARK_TOMBSTONE_TTL_MS = 60 * 60_000;
|
|
25
25
|
const DEFAULT_PARK_TOMBSTONE_PER_PRINCIPAL_MAX = 128;
|
|
26
26
|
const PARKING_RETRY_AFTER_MS = 250;
|
|
27
|
+
function durableRuleMaterial(material) {
|
|
28
|
+
if (material === undefined)
|
|
29
|
+
return { ruleCommand: null, ruleScopeRoot: null };
|
|
30
|
+
if (redactSecrets(material.command, { observe: false }) !== material.command) {
|
|
31
|
+
defaultLogger.warn("approval rule material withheld: the adjudicated command carries credential-shaped bytes", {
|
|
32
|
+
commandChars: material.command.length,
|
|
33
|
+
});
|
|
34
|
+
return { ruleCommand: null, ruleScopeRoot: null };
|
|
35
|
+
}
|
|
36
|
+
return { ruleCommand: material.command, ruleScopeRoot: material.scopeRoot ?? null };
|
|
37
|
+
}
|
|
27
38
|
const DURABLE_CONVERGE_BACKOFF_MS = [50, 100, 200, 400, 800, 1600];
|
|
28
39
|
const DURABLE_CONVERGE_READ_TIMEOUT_MS = 2_000;
|
|
29
40
|
const DURABLE_CALL_TIMEOUT_MS = 5_000;
|
|
@@ -673,6 +684,7 @@ export class ToolApprovalCoordinator {
|
|
|
673
684
|
parentToolCallId: req.delegation?.parentToolCallId ?? null,
|
|
674
685
|
cardJson: buildApprovalCardEnvelope(id, card),
|
|
675
686
|
boundInputHash: readBoundInputHash(req),
|
|
687
|
+
...durableRuleMaterial(ruleLaneMaterial),
|
|
676
688
|
schemaVersion: 1,
|
|
677
689
|
expiresAtMs,
|
|
678
690
|
createdAtMs: attemptCreatedAtMs,
|
|
@@ -1166,7 +1178,7 @@ export class ToolApprovalCoordinator {
|
|
|
1166
1178
|
return this.notPending("unknown_or_other_replica");
|
|
1167
1179
|
}
|
|
1168
1180
|
if (tomb.redeemed !== undefined)
|
|
1169
|
-
return this.replayRedeemReceipt(id, tomb
|
|
1181
|
+
return this.replayRedeemReceipt(id, tomb, row, tomb.redeemed, parsed);
|
|
1170
1182
|
switch (row.state) {
|
|
1171
1183
|
case "DECIDED":
|
|
1172
1184
|
return this.replayDecidedAsk(id, row, parsed);
|
|
@@ -1237,18 +1249,19 @@ export class ToolApprovalCoordinator {
|
|
|
1237
1249
|
return this.notPending();
|
|
1238
1250
|
}
|
|
1239
1251
|
if (tomb.redeemed !== undefined)
|
|
1240
|
-
return this.replayRedeemReceipt(id, row
|
|
1252
|
+
return this.replayRedeemReceipt(id, tomb, row, tomb.redeemed, parsed);
|
|
1241
1253
|
if (tomb.inflight !== undefined) {
|
|
1242
1254
|
const shared = await tomb.inflight;
|
|
1243
1255
|
if (shared.status < 200 || shared.status >= 300)
|
|
1244
1256
|
return shared;
|
|
1245
|
-
return this.replayRedeemReceipt(id, row
|
|
1257
|
+
return this.replayRedeemReceipt(id, tomb, row, { decision: shared.decision, body: shared.body, ...(shared.rule?.terminal === true ? { rule: shared.rule.body } : {}) }, parsed);
|
|
1246
1258
|
}
|
|
1247
1259
|
const decision = parsed.value === "deny" ? "deny" : "approve";
|
|
1248
1260
|
const gateToken = row.gateToken;
|
|
1249
1261
|
const flight = (async () => {
|
|
1262
|
+
let res;
|
|
1250
1263
|
try {
|
|
1251
|
-
|
|
1264
|
+
res = await redeem({
|
|
1252
1265
|
askId: row.askId,
|
|
1253
1266
|
gateToken,
|
|
1254
1267
|
gateBoundCallId: row.gateBoundCallId,
|
|
@@ -1259,12 +1272,15 @@ export class ToolApprovalCoordinator {
|
|
|
1259
1272
|
...(principal !== undefined ? { principal } : {}),
|
|
1260
1273
|
...(httpReq !== undefined ? { httpReq } : {}),
|
|
1261
1274
|
});
|
|
1262
|
-
return { ...res, decision };
|
|
1263
1275
|
}
|
|
1264
1276
|
catch (err) {
|
|
1265
1277
|
defaultLogger.error("tool approval parked redemption threw", { askId: row.askId, error: err instanceof Error ? err.message : String(err) });
|
|
1266
1278
|
return { status: 500, body: { error: "the parked approval could not be redeemed right now — retry", errorCode: "internal.error" }, decision };
|
|
1267
1279
|
}
|
|
1280
|
+
if (res.status < 200 || res.status >= 300)
|
|
1281
|
+
return { ...res, decision };
|
|
1282
|
+
const rule = await this.persistRuleFromRow(row, parsed);
|
|
1283
|
+
return { ...res, decision, ...(rule !== undefined ? { rule } : {}) };
|
|
1268
1284
|
})();
|
|
1269
1285
|
tomb.inflight = flight;
|
|
1270
1286
|
let out;
|
|
@@ -1276,7 +1292,8 @@ export class ToolApprovalCoordinator {
|
|
|
1276
1292
|
}
|
|
1277
1293
|
if (out.status < 200 || out.status >= 300)
|
|
1278
1294
|
return { status: out.status, body: out.body };
|
|
1279
|
-
|
|
1295
|
+
const rule = out.rule;
|
|
1296
|
+
tomb.redeemed = { decision, body: out.body, ...(rule?.terminal === true ? { rule: rule.body } : {}) };
|
|
1280
1297
|
return {
|
|
1281
1298
|
status: out.status,
|
|
1282
1299
|
body: {
|
|
@@ -1285,22 +1302,118 @@ export class ToolApprovalCoordinator {
|
|
|
1285
1302
|
askId: row.askId,
|
|
1286
1303
|
accepted: "parked_redeemed",
|
|
1287
1304
|
...(parsed.value === "allow_session" ? { rememberApplied: false } : {}),
|
|
1288
|
-
...
|
|
1305
|
+
...(rule?.body ?? {}),
|
|
1289
1306
|
},
|
|
1290
1307
|
};
|
|
1291
1308
|
}
|
|
1292
|
-
|
|
1309
|
+
async persistRuleFromRow(row, parsed) {
|
|
1310
|
+
const persistRule = parsed.persistRule;
|
|
1311
|
+
if (persistRule === undefined)
|
|
1312
|
+
return undefined;
|
|
1313
|
+
const flag = (rulePersisted, ruleRefusal, echo) => ({
|
|
1314
|
+
body: { rulePersisted, ...(ruleRefusal !== undefined ? { ruleRefusal } : {}), ...(echo ?? {}) },
|
|
1315
|
+
terminal: true,
|
|
1316
|
+
});
|
|
1317
|
+
const lane = this.ruleConsent;
|
|
1318
|
+
if (lane === undefined || row.owner === null)
|
|
1319
|
+
return flag(false, "rule_lane_unavailable");
|
|
1320
|
+
const owner = row.owner;
|
|
1321
|
+
const envelope = ApprovalCardEnvelopeSchema.safeParse(row.cardJson);
|
|
1322
|
+
if (!envelope.success)
|
|
1323
|
+
return flag(false, "rule_material_absent");
|
|
1324
|
+
const card = envelope.data.card;
|
|
1325
|
+
if (card.governanceForced === true)
|
|
1326
|
+
return flag(false, "rule_governance_forced");
|
|
1327
|
+
const command = row.ruleCommand;
|
|
1328
|
+
if (command === null)
|
|
1329
|
+
return flag(false, "rule_material_absent");
|
|
1330
|
+
if (parsed.updatedInput !== undefined)
|
|
1331
|
+
return flag(false, "rule_input_edited");
|
|
1332
|
+
const offers = card.ruleOffers ?? [];
|
|
1333
|
+
const scope = row.ruleScopeRoot !== null ? { kind: "project", root: row.ruleScopeRoot } : undefined;
|
|
1334
|
+
const common = {
|
|
1335
|
+
principal: owner,
|
|
1336
|
+
toolName: card.toolName,
|
|
1337
|
+
command,
|
|
1338
|
+
...(row.toolCallId !== "" ? { toolCallId: row.toolCallId } : {}),
|
|
1339
|
+
...(row.boundInputHash !== null ? { boundInputHash: row.boundInputHash } : {}),
|
|
1340
|
+
...(scope !== undefined ? { scope } : {}),
|
|
1341
|
+
};
|
|
1342
|
+
try {
|
|
1343
|
+
if (persistRule.kind === "batch") {
|
|
1344
|
+
const offer = offers[persistRule.batchOfferIndex];
|
|
1345
|
+
if (offer === undefined || offer.kind !== "batch")
|
|
1346
|
+
return flag(false, "rule_not_offered");
|
|
1347
|
+
const persisted = await lane.persistCardRule({ ...common, redemption: { kind: "batch", offer } });
|
|
1348
|
+
if (!persisted.ok) {
|
|
1349
|
+
defaultLogger.warn("permission rule batch was not persisted after a parked late decision", {
|
|
1350
|
+
askId: row.askId,
|
|
1351
|
+
reason: persisted.reason,
|
|
1352
|
+
...(persisted.detail !== undefined ? { detail: redactSecrets(persisted.detail) } : {}),
|
|
1353
|
+
});
|
|
1354
|
+
return flag(false, persisted.reason);
|
|
1355
|
+
}
|
|
1356
|
+
return flag(true, undefined, persisted.kind === "batch"
|
|
1357
|
+
? {
|
|
1358
|
+
persistedRules: persisted.rules,
|
|
1359
|
+
...(persisted.members !== undefined
|
|
1360
|
+
? { persistedRuleAnchors: persisted.members.map((m) => ({ offerIndex: persistRule.batchOfferIndex, memberIndex: m.memberIndex, rule: m.rule })) }
|
|
1361
|
+
: {}),
|
|
1362
|
+
}
|
|
1363
|
+
: {});
|
|
1364
|
+
}
|
|
1365
|
+
const ruleText = persistRule.rule;
|
|
1366
|
+
if (!persistRule.edited && !offers.some((o) => o.kind === "single" && o.rule === ruleText))
|
|
1367
|
+
return flag(false, "rule_not_offered");
|
|
1368
|
+
const persisted = await lane.persistCardRule({
|
|
1369
|
+
...common,
|
|
1370
|
+
redemption: persistRule.edited ? { kind: "edited", text: ruleText } : { kind: "offered", ruleText },
|
|
1371
|
+
});
|
|
1372
|
+
if (!persisted.ok) {
|
|
1373
|
+
defaultLogger.warn("permission rule was not persisted after a parked late decision", {
|
|
1374
|
+
askId: row.askId,
|
|
1375
|
+
reason: persisted.reason,
|
|
1376
|
+
...(persisted.detail !== undefined ? { detail: redactSecrets(persisted.detail) } : {}),
|
|
1377
|
+
});
|
|
1378
|
+
return flag(false, persisted.reason);
|
|
1379
|
+
}
|
|
1380
|
+
return flag(true, undefined, persistRule.edited && persisted.kind === "single" ? { persistedRule: persisted.rule } : {});
|
|
1381
|
+
}
|
|
1382
|
+
catch (err) {
|
|
1383
|
+
this.noteStoreError(err, "persistCardRule(parked-late)");
|
|
1384
|
+
return { ...flag(false, "rule_store_error"), terminal: false };
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
async replayRedeemReceipt(id, tomb, row, receipt, parsed) {
|
|
1388
|
+
let rule = receipt.rule;
|
|
1389
|
+
if (rule === undefined && parsed.persistRule !== undefined) {
|
|
1390
|
+
const flight = tomb.ruleInflight ?? this.persistRuleFromRow(row, parsed);
|
|
1391
|
+
const isOwner = tomb.ruleInflight === undefined;
|
|
1392
|
+
if (isOwner)
|
|
1393
|
+
tomb.ruleInflight = flight;
|
|
1394
|
+
let fresh;
|
|
1395
|
+
try {
|
|
1396
|
+
fresh = await flight;
|
|
1397
|
+
}
|
|
1398
|
+
finally {
|
|
1399
|
+
if (isOwner)
|
|
1400
|
+
tomb.ruleInflight = undefined;
|
|
1401
|
+
}
|
|
1402
|
+
rule = fresh?.body;
|
|
1403
|
+
if (fresh?.terminal === true && tomb.redeemed !== undefined)
|
|
1404
|
+
tomb.redeemed.rule = fresh.body;
|
|
1405
|
+
}
|
|
1293
1406
|
return {
|
|
1294
1407
|
status: 200,
|
|
1295
1408
|
body: {
|
|
1296
1409
|
...receipt.body,
|
|
1297
1410
|
approvalId: id,
|
|
1298
|
-
askId,
|
|
1411
|
+
askId: row.askId,
|
|
1299
1412
|
accepted: "parked_redeemed",
|
|
1300
1413
|
idempotent: true,
|
|
1301
1414
|
decision: receipt.decision === "approve" ? "allow" : "deny",
|
|
1302
1415
|
...(parsed.value === "allow_session" ? { rememberApplied: false } : {}),
|
|
1303
|
-
...
|
|
1416
|
+
...(rule ?? {}),
|
|
1304
1417
|
},
|
|
1305
1418
|
};
|
|
1306
1419
|
}
|
|
@@ -1341,7 +1454,14 @@ export class ToolApprovalCoordinator {
|
|
|
1341
1454
|
defaultLogger.warn("permission rule batch was not persisted after an approval", { reason: persisted.reason, ...(persisted.detail !== undefined ? { detail: redactSecrets(persisted.detail) } : {}) });
|
|
1342
1455
|
return withFlag(false, persisted.reason);
|
|
1343
1456
|
}
|
|
1344
|
-
return withFlag(true, undefined, persisted.kind === "batch"
|
|
1457
|
+
return withFlag(true, undefined, persisted.kind === "batch"
|
|
1458
|
+
? {
|
|
1459
|
+
persistedRules: persisted.rules,
|
|
1460
|
+
...(persisted.members !== undefined
|
|
1461
|
+
? { persistedRuleAnchors: persisted.members.map((m) => ({ offerIndex: persistRule.batchOfferIndex, memberIndex: m.memberIndex, rule: m.rule })) }
|
|
1462
|
+
: {}),
|
|
1463
|
+
}
|
|
1464
|
+
: {});
|
|
1345
1465
|
}
|
|
1346
1466
|
catch (err) {
|
|
1347
1467
|
this.noteStoreError(err, "persistCardRule(batch)");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.48.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|