@sema-agent/server 7.54.0 → 7.55.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/dist/approval-ask-audit-store.d.ts +129 -0
- package/dist/approval-ask-audit-store.js +284 -0
- package/dist/approval-card.d.ts +18 -6
- package/dist/approval-card.js +2 -2
- package/dist/approval-reconciler.d.ts +2 -1
- package/dist/approval-reconciler.js +1 -1
- package/dist/boot/coordinators.d.ts +2 -0
- package/dist/boot/coordinators.js +18 -1
- package/dist/boot/reapers.d.ts +2 -1
- package/dist/boot/stores.js +8 -1
- package/dist/device-store.d.ts +66 -2
- package/dist/device-store.js +35 -0
- package/dist/device-ws-hub.d.ts +8 -0
- package/dist/device-ws-hub.js +6 -0
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/devices.d.ts +64 -0
- package/dist/http/routes/devices.js +173 -0
- package/dist/http/server.d.ts +14 -0
- package/dist/http/server.js +13 -2
- package/dist/main.js +3 -1
- package/dist/observability/fail-open.d.ts +7 -3
- package/dist/observability/fail-open.js +7 -3
- package/dist/plugins/approval-ask-store-memory.d.ts +11 -1
- package/dist/plugins/approval-ask-store-memory.js +20 -3
- package/dist/plugins/approval-ask-store-sql.d.ts +82 -0
- package/dist/plugins/approval-ask-store-sql.js +41 -10
- package/dist/plugins/device-store-sql.d.ts +38 -1
- package/dist/plugins/device-store-sql.js +82 -2
- package/dist/plugins/remote-env-device.js +8 -1
- package/dist/plugins/sql-errors.d.ts +12 -0
- package/dist/plugins/sql-errors.js +10 -0
- package/dist/runs.js +1 -0
- package/dist/tool-approval.d.ts +54 -23
- package/dist/tool-approval.js +218 -127
- package/dist/trace/ledger-events.d.ts +9 -0
- package/package.json +2 -2
package/dist/tool-approval.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createLogger } from "./observability/logger.js";
|
|
|
8
8
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
9
9
|
import { deriveAskId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
|
|
10
10
|
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readProbeCause, readRuleEvidence, readRuleOffersAbsence, } from "./approval-card.js";
|
|
11
|
+
import { projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
|
|
11
12
|
import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
|
|
12
13
|
const defaultLogger = createLogger();
|
|
13
14
|
export const APPROVAL_GATE_KINDS = ["human", "irreversible_ask"];
|
|
@@ -43,9 +44,12 @@ function durableRuleMaterial(material) {
|
|
|
43
44
|
return { ruleCommand: material.command, ruleScopeRoot: material.scopeRoot ?? null };
|
|
44
45
|
}
|
|
45
46
|
export const ASK_DECISION_CONSUMER_ABSENT = "absent";
|
|
47
|
+
const MAX_INFLIGHT_TERMINAL_CLAIMS = 2;
|
|
46
48
|
const DURABLE_CONVERGE_BACKOFF_MS = [50, 100, 200, 400, 800, 1600];
|
|
47
49
|
const DURABLE_CONVERGE_READ_TIMEOUT_MS = 2_000;
|
|
48
50
|
const DURABLE_CALL_TIMEOUT_MS = 5_000;
|
|
51
|
+
const TERMINAL_LADDER_WALL_MS = DURABLE_CALL_TIMEOUT_MS + DURABLE_CONVERGE_READ_TIMEOUT_MS + DURABLE_CONVERGE_BACKOFF_MS.reduce((a, b) => a + b, 0);
|
|
52
|
+
const TERMINAL_CLAIM_CAP_POLL_MS = 50;
|
|
49
53
|
function ambiguousCasFailOpenTag(intent) {
|
|
50
54
|
switch (intent) {
|
|
51
55
|
case "expire":
|
|
@@ -228,6 +232,30 @@ function settleArgsOf(outcome) {
|
|
|
228
232
|
export class ToolApprovalCoordinator {
|
|
229
233
|
als = new AsyncLocalStorage();
|
|
230
234
|
pending = new Map();
|
|
235
|
+
terminalClaimsInFlight = new Map();
|
|
236
|
+
admitTerminalClaim(askId) {
|
|
237
|
+
const n = this.terminalClaimsInFlight.get(askId) ?? 0;
|
|
238
|
+
if (n >= MAX_INFLIGHT_TERMINAL_CLAIMS)
|
|
239
|
+
return undefined;
|
|
240
|
+
this.terminalClaimsInFlight.set(askId, n + 1);
|
|
241
|
+
let released = false;
|
|
242
|
+
return () => {
|
|
243
|
+
if (released)
|
|
244
|
+
return;
|
|
245
|
+
released = true;
|
|
246
|
+
const cur = this.terminalClaimsInFlight.get(askId) ?? 0;
|
|
247
|
+
if (cur <= 1)
|
|
248
|
+
this.terminalClaimsInFlight.delete(askId);
|
|
249
|
+
else
|
|
250
|
+
this.terminalClaimsInFlight.set(askId, cur - 1);
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
terminalClaimsInFlightCount(askId) {
|
|
254
|
+
return this.terminalClaimsInFlight.get(askId) ?? 0;
|
|
255
|
+
}
|
|
256
|
+
terminalClaimsInFlightForTest(askId) {
|
|
257
|
+
return this.terminalClaimsInFlightCount(askId);
|
|
258
|
+
}
|
|
231
259
|
pendingByAskId = new Map();
|
|
232
260
|
allowAllSessions = new Set();
|
|
233
261
|
streams = new Map();
|
|
@@ -253,6 +281,7 @@ export class ToolApprovalCoordinator {
|
|
|
253
281
|
parkTombstoneTtlMs;
|
|
254
282
|
parkTombstonePerPrincipalMax;
|
|
255
283
|
parkedRedeem;
|
|
284
|
+
askAudit;
|
|
256
285
|
constructor(opts) {
|
|
257
286
|
this.ruleConsent = opts?.ruleConsent;
|
|
258
287
|
this.ruleScopeRootFor = opts?.ruleScopeRootFor;
|
|
@@ -268,6 +297,7 @@ export class ToolApprovalCoordinator {
|
|
|
268
297
|
this.parkTombstoneMax = opts?.parkTombstoneMax ?? DEFAULT_PARK_TOMBSTONE_MAX;
|
|
269
298
|
this.parkTombstoneTtlMs = opts?.parkTombstoneTtlMs ?? DEFAULT_PARK_TOMBSTONE_TTL_MS;
|
|
270
299
|
this.parkTombstonePerPrincipalMax = opts?.parkTombstonePerPrincipalMax ?? DEFAULT_PARK_TOMBSTONE_PER_PRINCIPAL_MAX;
|
|
300
|
+
this.askAudit = opts?.askAudit;
|
|
271
301
|
}
|
|
272
302
|
unattendedOutcome() {
|
|
273
303
|
return this.unattendedPolicy === "deny" ? false : "unavailable";
|
|
@@ -454,23 +484,30 @@ export class ToolApprovalCoordinator {
|
|
|
454
484
|
return false;
|
|
455
485
|
}
|
|
456
486
|
}
|
|
457
|
-
async
|
|
458
|
-
if (!this.askStore)
|
|
459
|
-
return { kind: "
|
|
487
|
+
async probeRowWhileYielding(askId, budgetMs) {
|
|
488
|
+
if (!this.askStore || budgetMs <= 0)
|
|
489
|
+
return { kind: "none" };
|
|
490
|
+
const release = this.admitTerminalClaim(askId);
|
|
491
|
+
if (release === undefined)
|
|
492
|
+
return { kind: "denied" };
|
|
493
|
+
const raw = this.askStore.getAsk(askId);
|
|
494
|
+
void raw.then(release, release);
|
|
460
495
|
let row;
|
|
461
496
|
try {
|
|
462
|
-
row = await this.withStoreDeadline(
|
|
497
|
+
row = await this.withStoreDeadline(raw, "getAsk(yield-probe)", Math.min(DURABLE_CONVERGE_READ_TIMEOUT_MS, budgetMs));
|
|
463
498
|
}
|
|
464
499
|
catch (err) {
|
|
465
|
-
this.noteStoreError(err,
|
|
466
|
-
return { kind: "
|
|
500
|
+
this.noteStoreError(err, "getAsk(yield-probe)");
|
|
501
|
+
return { kind: "none" };
|
|
467
502
|
}
|
|
468
|
-
if (row === null)
|
|
503
|
+
if (row === null || row.state === "STREAM_PENDING")
|
|
504
|
+
return { kind: "none" };
|
|
505
|
+
return { kind: "row", current: projectAskTerminalClaimTruth(row) };
|
|
506
|
+
}
|
|
507
|
+
classifyClaimLoss(askId, current, where) {
|
|
508
|
+
if (current.state === "absent")
|
|
469
509
|
return { kind: "absent" };
|
|
470
|
-
|
|
471
|
-
return this.editedDecisionInFlight(askId) ? { kind: "edit-in-flight" } : { kind: "pending" };
|
|
472
|
-
}
|
|
473
|
-
const replayed = settleArgsOf(this.guardRowDerivedApprove(askId, replayTerminalAskRow(row), where));
|
|
510
|
+
const replayed = settleArgsOf(this.guardRowDerivedApprove(askId, replayTerminalAskRow(current), where));
|
|
474
511
|
if (replayed === "unavailable")
|
|
475
512
|
return { kind: "parked" };
|
|
476
513
|
if (!replayed.allowed)
|
|
@@ -631,6 +668,7 @@ export class ToolApprovalCoordinator {
|
|
|
631
668
|
if (p.targetCtxs?.has(ctx))
|
|
632
669
|
p.onTargetGone?.(ctx);
|
|
633
670
|
}
|
|
671
|
+
this.askAudit?.noteLegClosed(ctx.taskId);
|
|
634
672
|
}
|
|
635
673
|
}));
|
|
636
674
|
}
|
|
@@ -923,111 +961,159 @@ export class ToolApprovalCoordinator {
|
|
|
923
961
|
releaseAdmission();
|
|
924
962
|
if (selfEntry)
|
|
925
963
|
selfEntry.settledOutcome = outcome;
|
|
964
|
+
this.askAudit?.recordSettle({
|
|
965
|
+
approvalId: wireApprovalId,
|
|
966
|
+
outcome,
|
|
967
|
+
...(windowRouteUnavailable ? { parkRouted: true } : {}),
|
|
968
|
+
...(hostSettledBy !== undefined ? { settledBy: hostSettledBy } : {}),
|
|
969
|
+
settledAtMs: Date.now(),
|
|
970
|
+
});
|
|
926
971
|
resolveAllowed({ allowed, ...(allowed && updatedInput !== undefined ? { updatedInput } : {}) });
|
|
927
972
|
};
|
|
928
|
-
const
|
|
929
|
-
if (
|
|
973
|
+
const broadcastClaimTruth = (read) => {
|
|
974
|
+
if (askId === undefined)
|
|
930
975
|
return;
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
976
|
+
switch (read.kind) {
|
|
977
|
+
case "approved":
|
|
978
|
+
this.notifyExternalDecision(askId, true, read.updatedInput);
|
|
934
979
|
return;
|
|
935
|
-
|
|
936
|
-
|
|
980
|
+
case "denied":
|
|
981
|
+
this.notifyExternalDecision(askId, false);
|
|
937
982
|
return;
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
continue;
|
|
948
|
-
case "parked":
|
|
949
|
-
windowRouteUnavailable = true;
|
|
950
|
-
settle(false, "expired");
|
|
951
|
-
return;
|
|
952
|
-
case "approved":
|
|
953
|
-
settle(true, "allowed", read.updatedInput);
|
|
954
|
-
return;
|
|
955
|
-
case "denied":
|
|
956
|
-
settle(false, "denied");
|
|
957
|
-
return;
|
|
958
|
-
default: {
|
|
959
|
-
const unreachable = read;
|
|
960
|
-
throw new Error(`convergeFromDurableState: unknown recovery read ${String(unreachable)}`);
|
|
961
|
-
}
|
|
983
|
+
case "parked":
|
|
984
|
+
this.settleSameAskIdParkRoute(askId);
|
|
985
|
+
return;
|
|
986
|
+
case "absent":
|
|
987
|
+
case "edit-in-flight":
|
|
988
|
+
return;
|
|
989
|
+
default: {
|
|
990
|
+
const unreachable = read;
|
|
991
|
+
throw new Error(`broadcastClaimTruth: unknown recovery read ${String(unreachable)}`);
|
|
962
992
|
}
|
|
963
993
|
}
|
|
964
|
-
|
|
965
|
-
|
|
994
|
+
};
|
|
995
|
+
const settleFromClaimLoss = (read) => {
|
|
996
|
+
switch (read.kind) {
|
|
997
|
+
case "approved":
|
|
998
|
+
settle(true, "allowed", read.updatedInput);
|
|
999
|
+
broadcastClaimTruth(read);
|
|
1000
|
+
return true;
|
|
1001
|
+
case "denied":
|
|
1002
|
+
settle(false, "denied");
|
|
1003
|
+
broadcastClaimTruth(read);
|
|
1004
|
+
return true;
|
|
1005
|
+
case "parked":
|
|
966
1006
|
windowRouteUnavailable = true;
|
|
967
1007
|
settle(false, "expired");
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1008
|
+
broadcastClaimTruth(read);
|
|
1009
|
+
return true;
|
|
1010
|
+
case "absent":
|
|
1011
|
+
case "edit-in-flight":
|
|
1012
|
+
return false;
|
|
1013
|
+
default: {
|
|
1014
|
+
const unreachable = read;
|
|
1015
|
+
throw new Error(`settleFromClaimLoss: unknown recovery read ${String(unreachable)}`);
|
|
971
1016
|
}
|
|
972
1017
|
}
|
|
973
1018
|
};
|
|
974
|
-
const
|
|
1019
|
+
const lateClaimLossSettle = (current, intent) => {
|
|
1020
|
+
if (askId === undefined)
|
|
1021
|
+
return;
|
|
1022
|
+
const read = this.classifyClaimLoss(askId, current, `late:${intent}`);
|
|
1023
|
+
if (done) {
|
|
1024
|
+
broadcastClaimTruth(read);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
settleFromClaimLoss(read);
|
|
1028
|
+
};
|
|
1029
|
+
const boundedClaim = (mk, label, timeoutMs, late) => {
|
|
1030
|
+
const release = this.admitTerminalClaim(askId ?? wireApprovalId);
|
|
1031
|
+
if (release === undefined)
|
|
1032
|
+
return undefined;
|
|
1033
|
+
const raw = mk();
|
|
1034
|
+
void raw.then(release, release);
|
|
1035
|
+
return this.withStoreDeadline(raw, label, timeoutMs, late);
|
|
1036
|
+
};
|
|
1037
|
+
const resolveLocalOutcome = async (arm) => {
|
|
975
1038
|
if (askId === undefined) {
|
|
976
1039
|
arm.settleByIntent();
|
|
977
1040
|
return;
|
|
978
1041
|
}
|
|
979
1042
|
const pinnedAskId = askId;
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1043
|
+
let lastLoss;
|
|
1044
|
+
const ladderStartedAt = Date.now();
|
|
1045
|
+
const remainingMs = () => ladderStartedAt + TERMINAL_LADDER_WALL_MS - Date.now();
|
|
1046
|
+
const nap = async (ms) => {
|
|
1047
|
+
const slice = Math.min(ms, remainingMs());
|
|
1048
|
+
if (slice > 0)
|
|
1049
|
+
await new Promise((r) => setTimeout(r, slice).unref?.());
|
|
1050
|
+
};
|
|
1051
|
+
let attempt = 0;
|
|
1052
|
+
let claimsIssued = 0;
|
|
1053
|
+
let rejects = 0;
|
|
1054
|
+
while (attempt <= DURABLE_CONVERGE_BACKOFF_MS.length && remainingMs() > 0) {
|
|
984
1055
|
if (done)
|
|
985
1056
|
return;
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
case "denied":
|
|
991
|
-
settle(false, "denied");
|
|
992
|
-
return;
|
|
993
|
-
case "parked":
|
|
994
|
-
windowRouteUnavailable = true;
|
|
995
|
-
settle(false, "expired");
|
|
1057
|
+
const backoffAfter = attempt < DURABLE_CONVERGE_BACKOFF_MS.length ? DURABLE_CONVERGE_BACKOFF_MS[attempt] : 0;
|
|
1058
|
+
if (attempt > 0 && this.editedDecisionInFlight(pinnedAskId)) {
|
|
1059
|
+
const probed = await this.probeRowWhileYielding(pinnedAskId, remainingMs());
|
|
1060
|
+
if (done)
|
|
996
1061
|
return;
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
result = await arm.cas();
|
|
1005
|
-
}
|
|
1006
|
-
catch (err) {
|
|
1007
|
-
this.noteStoreError(err, arm.storeLabel);
|
|
1008
|
-
arm.settleByIntent();
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
if (arm.won(result)) {
|
|
1012
|
-
arm.onWon(result);
|
|
1062
|
+
if (probed.kind === "denied") {
|
|
1063
|
+
await nap(TERMINAL_CLAIM_CAP_POLL_MS);
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
if (probed.kind === "row") {
|
|
1067
|
+
const read = this.classifyClaimLoss(pinnedAskId, probed.current, `yield-probe:${arm.intent}`);
|
|
1068
|
+
if (settleFromClaimLoss(read))
|
|
1013
1069
|
return;
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
return;
|
|
1070
|
+
if (read.kind === "edit-in-flight")
|
|
1071
|
+
lastLoss = probed.current;
|
|
1017
1072
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
return;
|
|
1021
|
-
case "edit-in-flight":
|
|
1022
|
-
case "unreadable":
|
|
1023
|
-
if (attempt < DURABLE_CONVERGE_BACKOFF_MS.length)
|
|
1024
|
-
await new Promise((r) => setTimeout(r, DURABLE_CONVERGE_BACKOFF_MS[attempt]).unref?.());
|
|
1073
|
+
else if (this.terminalClaimsInFlightCount(pinnedAskId) > 0) {
|
|
1074
|
+
await nap(TERMINAL_CLAIM_CAP_POLL_MS);
|
|
1025
1075
|
continue;
|
|
1026
|
-
default: {
|
|
1027
|
-
const unreachable = read;
|
|
1028
|
-
throw new Error(`recoverFromUnknownStore: unknown recovery read ${String(unreachable)}`);
|
|
1029
1076
|
}
|
|
1077
|
+
await nap(backoffAfter);
|
|
1078
|
+
attempt++;
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
const inFlight = arm.claim(Math.min(attempt === 0 ? DURABLE_CALL_TIMEOUT_MS : DURABLE_CONVERGE_READ_TIMEOUT_MS, remainingMs()));
|
|
1082
|
+
if (inFlight === undefined) {
|
|
1083
|
+
await nap(TERMINAL_CLAIM_CAP_POLL_MS);
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
claimsIssued++;
|
|
1087
|
+
let outcome;
|
|
1088
|
+
try {
|
|
1089
|
+
outcome = await inFlight;
|
|
1090
|
+
}
|
|
1091
|
+
catch (err) {
|
|
1092
|
+
this.noteStoreError(err, arm.storeLabel);
|
|
1093
|
+
rejects++;
|
|
1094
|
+
await nap(backoffAfter);
|
|
1095
|
+
attempt++;
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
if (outcome.claimed) {
|
|
1099
|
+
arm.onWon(outcome);
|
|
1100
|
+
return;
|
|
1030
1101
|
}
|
|
1102
|
+
arm.onLostTruth?.(outcome.current);
|
|
1103
|
+
const read = this.classifyClaimLoss(pinnedAskId, outcome.current, `claim:${arm.intent}`);
|
|
1104
|
+
if (done) {
|
|
1105
|
+
broadcastClaimTruth(read);
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
if (settleFromClaimLoss(read))
|
|
1109
|
+
return;
|
|
1110
|
+
if (read.kind === "absent") {
|
|
1111
|
+
arm.settleByIntent();
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
lastLoss = outcome.current;
|
|
1115
|
+
await nap(backoffAfter);
|
|
1116
|
+
attempt++;
|
|
1031
1117
|
}
|
|
1032
1118
|
if (done)
|
|
1033
1119
|
return;
|
|
@@ -1036,24 +1122,10 @@ export class ToolApprovalCoordinator {
|
|
|
1036
1122
|
settle(false, "expired");
|
|
1037
1123
|
return;
|
|
1038
1124
|
}
|
|
1039
|
-
|
|
1040
|
-
arm.settleByIntent();
|
|
1041
|
-
};
|
|
1042
|
-
const resolveLocalOutcome = async (arm) => {
|
|
1043
|
-
let result;
|
|
1044
|
-
try {
|
|
1045
|
-
result = await arm.cas();
|
|
1046
|
-
}
|
|
1047
|
-
catch (err) {
|
|
1048
|
-
this.noteStoreError(err, arm.storeLabel);
|
|
1049
|
-
await recoverFromUnknownStore(arm, err instanceof ApprovalStoreDeadlineError);
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
if (arm.won(result)) {
|
|
1053
|
-
arm.onWon(result);
|
|
1125
|
+
if (lastLoss !== undefined && settleFromClaimLoss(this.classifyClaimLoss(pinnedAskId, lastLoss, `claim-tail:${arm.intent}`)))
|
|
1054
1126
|
return;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1127
|
+
recordFailOpen(ambiguousCasFailOpenTag(arm.intent), `askId=${pinnedAskId} attempts=${attempt} claims=${claimsIssued} rejects=${rejects} elapsedMs=${Date.now() - ladderStartedAt}`);
|
|
1128
|
+
arm.settleByIntent();
|
|
1057
1129
|
};
|
|
1058
1130
|
const settleParkRouteIfUnsettled = (hostSettledBy) => {
|
|
1059
1131
|
if (done)
|
|
@@ -1072,22 +1144,22 @@ export class ToolApprovalCoordinator {
|
|
|
1072
1144
|
const pinnedBatchId = batchId;
|
|
1073
1145
|
await resolveLocalOutcome({
|
|
1074
1146
|
intent: "expire",
|
|
1075
|
-
storeLabel: "
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1147
|
+
storeLabel: "claimTerminal(window)",
|
|
1148
|
+
claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "expire"), "claimTerminal(window)", timeoutMs, (late) => {
|
|
1149
|
+
if (!late.claimed) {
|
|
1150
|
+
lateClaimLossSettle(late.current, "expire");
|
|
1079
1151
|
return;
|
|
1152
|
+
}
|
|
1080
1153
|
this.settleSameAskIdParkRoute(pinnedAskId);
|
|
1081
1154
|
this.settleVoidedSiblings(late.voidedSiblings);
|
|
1082
|
-
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, late.voidedSiblings, "superseded_by_park", Date.now()));
|
|
1155
|
+
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, late.voidedSiblings, "superseded_by_park", Date.now(), origin.taskId));
|
|
1083
1156
|
}),
|
|
1084
|
-
won: (res) => res.won,
|
|
1085
1157
|
onWon: (res) => {
|
|
1086
1158
|
windowRouteUnavailable = true;
|
|
1087
1159
|
settle(false, "expired");
|
|
1088
1160
|
this.settleSameAskIdParkRoute(pinnedAskId);
|
|
1089
1161
|
this.settleVoidedSiblings(res.voidedSiblings);
|
|
1090
|
-
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, res.voidedSiblings, "superseded_by_park", Date.now()));
|
|
1162
|
+
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, res.voidedSiblings, "superseded_by_park", Date.now(), origin.taskId));
|
|
1091
1163
|
},
|
|
1092
1164
|
settleByIntent: () => settleParkRouteIfUnsettled(hostSelfReport),
|
|
1093
1165
|
});
|
|
@@ -1116,24 +1188,31 @@ export class ToolApprovalCoordinator {
|
|
|
1116
1188
|
const store = this.askStore;
|
|
1117
1189
|
const pinnedAskId = askId;
|
|
1118
1190
|
const pinnedBatchId = batchId;
|
|
1191
|
+
const cancelLostTruth = (current) => {
|
|
1192
|
+
if (current.state !== "VOID")
|
|
1193
|
+
return;
|
|
1194
|
+
this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1195
|
+
};
|
|
1119
1196
|
await resolveLocalOutcome({
|
|
1120
1197
|
intent: "cancel",
|
|
1121
|
-
storeLabel: "
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1198
|
+
storeLabel: "claimTerminal(cancel)",
|
|
1199
|
+
claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "cancel"), "claimTerminal(cancel)", timeoutMs, (late) => {
|
|
1200
|
+
if (!late.claimed) {
|
|
1201
|
+
cancelLostTruth(late.current);
|
|
1202
|
+
lateClaimLossSettle(late.current, "cancel");
|
|
1125
1203
|
return;
|
|
1204
|
+
}
|
|
1126
1205
|
this.settleVoidedSiblings([pinnedAskId]);
|
|
1127
1206
|
this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1128
|
-
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now()));
|
|
1207
|
+
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
|
|
1129
1208
|
}),
|
|
1130
|
-
won: (won) => won,
|
|
1131
1209
|
onWon: () => {
|
|
1132
1210
|
settle(false, "expired");
|
|
1133
1211
|
this.settleVoidedSiblings([pinnedAskId]);
|
|
1134
1212
|
this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1135
|
-
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now()));
|
|
1213
|
+
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
|
|
1136
1214
|
},
|
|
1215
|
+
onLostTruth: cancelLostTruth,
|
|
1137
1216
|
settleByIntent: () => settle(false, "expired"),
|
|
1138
1217
|
});
|
|
1139
1218
|
};
|
|
@@ -1193,6 +1272,18 @@ export class ToolApprovalCoordinator {
|
|
|
1193
1272
|
}
|
|
1194
1273
|
siblings.add(pendingEntry);
|
|
1195
1274
|
}
|
|
1275
|
+
this.askAudit?.recordAsk({
|
|
1276
|
+
approvalId: wireApprovalId,
|
|
1277
|
+
taskId: origin.taskId,
|
|
1278
|
+
...(req.sourceTaskId !== undefined ? { sourceTaskId: req.sourceTaskId } : {}),
|
|
1279
|
+
...(origin.sessionId !== undefined ? { sessionId: origin.sessionId } : {}),
|
|
1280
|
+
owner: origin.owner,
|
|
1281
|
+
toolName: req.toolName,
|
|
1282
|
+
toolCallId: req.toolCallId,
|
|
1283
|
+
legTracked: !child && aliveCtxs.some((c) => c.taskId === origin.taskId),
|
|
1284
|
+
expiresAtMs: persistedExpiresAtMs,
|
|
1285
|
+
mintedAtMs: Date.now(),
|
|
1286
|
+
});
|
|
1196
1287
|
const cardFrame = askId !== undefined && cardForFrame !== undefined
|
|
1197
1288
|
? buildApprovalRequestFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, card: cardForFrame, expiresAtMs: persistedExpiresAtMs, nowMs: Date.now() })
|
|
1198
1289
|
: undefined;
|
|
@@ -1239,15 +1330,15 @@ export class ToolApprovalCoordinator {
|
|
|
1239
1330
|
};
|
|
1240
1331
|
await resolveLocalOutcome({
|
|
1241
1332
|
intent: "unreachable",
|
|
1242
|
-
storeLabel: "
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1333
|
+
storeLabel: "claimTerminal(emit-failed)",
|
|
1334
|
+
claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "expire"), "claimTerminal(emit-failed)", timeoutMs, (late) => {
|
|
1335
|
+
if (!late.claimed) {
|
|
1336
|
+
lateClaimLossSettle(late.current, "unreachable");
|
|
1246
1337
|
return;
|
|
1338
|
+
}
|
|
1247
1339
|
this.settleSameAskIdParkRoute(pinnedAskId);
|
|
1248
1340
|
this.settleVoidedSiblings(late.voidedSiblings);
|
|
1249
1341
|
}),
|
|
1250
|
-
won: (res) => res.won,
|
|
1251
1342
|
onWon: (res) => {
|
|
1252
1343
|
settleUnreachable();
|
|
1253
1344
|
this.settleSameAskIdParkRoute(pinnedAskId);
|
|
@@ -38,6 +38,15 @@ export type LedgerEventType = "reasoning" | "text" | "tool_start" | "tool_end" |
|
|
|
38
38
|
| "elicitation" | "elicitation_complete" | "question" | "question_complete" | "tool_approval" | "tool_approval_complete"
|
|
39
39
|
/** [ref] §4.3 新协议的呈卡帧(与上面两只 tool_approval 帧同一条投递面)。 */
|
|
40
40
|
| "approval_request"
|
|
41
|
+
/**
|
|
42
|
+
* [ref]([ref]):批级撤卡帧在 **bg/resume 两条 durable 腿**的账本落行(与呈卡帧同口
|
|
43
|
+
* `append(type, rest)`,seq 由腿自己的 ledger 链步分配)。此前成文「reaper/收敛器无 seq 可分配故
|
|
44
|
+
* live-only」只对**调用点**成立,不对 ctx 的投递口成立——帧经 `emitRevokeTo` 交给各 ctx 的
|
|
45
|
+
* `emitRevoke`,durable 腿的那只钩子自己就是账本写口。sync 腿维持 live-only(行为零变,
|
|
46
|
+
* `routes/tasks.ts` 装配点注)。重放语义:同一账本里卡帧(`approval_request`)在前、撤帧在后,
|
|
47
|
+
* 全量重读恒同序 ⇒ 幂等诚实;不投影成 trace 2.3 帧(`LEDGER_TYPES_NOT_PROJECTED` 登记)。
|
|
48
|
+
*/
|
|
49
|
+
| "approval_revoke"
|
|
41
50
|
/** 异步 workflow 完成的入箱 drain(`emitPendingWorkflowCompletions` 两个字面型之一)。 */
|
|
42
51
|
| "workflow_complete"
|
|
43
52
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.55.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",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"sharp": "^0.35.3"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@sema-agent/sdk": "
|
|
72
|
+
"@sema-agent/sdk": "8.1.0",
|
|
73
73
|
"@types/libsodium-wrappers": "^0.7.14",
|
|
74
74
|
"@types/node": "22.10.2",
|
|
75
75
|
"@types/pg": "^8.20.0",
|