@sema-agent/client-core 0.79.0 → 0.80.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/CHANGELOG.md +40 -0
- package/README.md +12 -9
- package/dist/adapter/downstream/eventToSdkMessage.js +2 -4
- package/dist/adapter/downstream/terminalToSdkResult.js +5 -4
- package/dist/adapter/runStream.js +25 -8
- package/dist/gateOutcome.d.ts +4 -0
- package/dist/gateOutcome.js +36 -0
- package/dist/gateVocabulary.d.ts +1 -0
- package/dist/gateVocabulary.js +7 -0
- package/dist/hitl/frameRouter.js +2 -5
- package/dist/hitl/hitlBridge.d.ts +6 -4
- package/dist/hitl/hitlBridge.js +1 -0
- package/dist/hitl/toolApprovalWire.d.ts +6 -0
- package/dist/hitl/toolApprovalWire.js +116 -38
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcpEngineLeg.d.ts +23 -0
- package/dist/mcpEngineLeg.js +135 -0
- package/dist/mcpLiveness.d.ts +1 -0
- package/dist/mcpLiveness.js +2 -1
- package/dist/request/taskRequest.js +1 -1
- package/dist/ruleRemovalConsequence.d.ts +3 -0
- package/dist/ruleRemovalConsequence.js +15 -0
- package/dist/sdkRegistryTransit.d.ts +6 -0
- package/dist/sdkRegistryTransit.js +5 -0
- package/dist/wireRefusalCopy.d.ts +9 -0
- package/dist/wireRefusalCopy.js +68 -0
- package/docs/INTEGRATION-CLIENTS.md +162 -9
- package/package.json +8 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type GateCurrentPending, type HitlFailureStage, type HitlSafetyCode, type HitlClientLike } from './hitlBridge.js';
|
|
2
2
|
import { type RuleStoreUnreadableKind } from '../gateVocabulary.js';
|
|
3
|
+
import { type DenyAttributionRefusalDetail } from '../wireRefusalCopy.js';
|
|
3
4
|
import { type DecideReceiptView } from '../decideReceipt.js';
|
|
4
5
|
import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, DENIAL_LIMIT_KINDS } from '@sema-agent/sdk';
|
|
5
6
|
import type { RuleSuggestion, ToolApprovalRespondAck, PersistedRuleAnchor as SdkPersistedRuleAnchor, RuleOfferMatch as SdkRuleOfferMatch, RuleOfferBatchMember as SdkRuleOfferBatchMember, RuleOfferUncoveredDetail as SdkRuleOfferUncoveredDetail, RuleOfferUncoveredReason as SdkRuleOfferUncoveredReason, AskOrigin as SdkAskOrigin, RuleOffersAbsence as SdkRuleOffersAbsence, DenialLimitKind as SdkDenialLimitKind, DenialLimitFallback as SdkDenialLimitFallback } from '@sema-agent/sdk';
|
|
@@ -19,6 +20,7 @@ export type FsApprovalOutcome = {
|
|
|
19
20
|
denied?: boolean | undefined;
|
|
20
21
|
denySettledBy?: ApprovalDenySettledBy | undefined;
|
|
21
22
|
denyReason?: string | undefined;
|
|
23
|
+
denyAttributionDropped?: true;
|
|
22
24
|
receipt?: DecideReceiptView | undefined;
|
|
23
25
|
} | {
|
|
24
26
|
kind: 'aborted';
|
|
@@ -36,6 +38,7 @@ export type FsApprovalOutcome = {
|
|
|
36
38
|
safetyCode?: HitlSafetyCode | undefined;
|
|
37
39
|
retryExhausted?: true;
|
|
38
40
|
currentPending?: GateCurrentPending | undefined;
|
|
41
|
+
denyAttributionRefusal?: DenyAttributionRefusalDetail | undefined;
|
|
39
42
|
};
|
|
40
43
|
export interface FsApprovalWireDeps {
|
|
41
44
|
client: HitlClientLike;
|
|
@@ -202,6 +205,7 @@ export interface RespondToolApprovalOpts {
|
|
|
202
205
|
persistRuleEdited?: true;
|
|
203
206
|
persistRuleBatchOfferIndex?: number;
|
|
204
207
|
note?: string;
|
|
208
|
+
settledBy?: 'policy';
|
|
205
209
|
}
|
|
206
210
|
export type RespondToolApprovalFn = (approvalId: string, decision: ToolApprovalRespondDecision, opts?: RespondToolApprovalOpts) => Promise<ToolApprovalRespondAck | void>;
|
|
207
211
|
export type ToolApprovalRespondAckView = ToolApprovalRespondAck;
|
|
@@ -215,6 +219,8 @@ export interface ToolApprovalFrameOutcome {
|
|
|
215
219
|
retracted?: true;
|
|
216
220
|
denySettledBy?: ApprovalDenySettledBy;
|
|
217
221
|
denyReason?: string;
|
|
222
|
+
denyAttributionRefusal?: DenyAttributionRefusalDetail;
|
|
223
|
+
denyAttributionDropped?: true;
|
|
218
224
|
}
|
|
219
225
|
interface ToolApprovalRespondRefusalFields {
|
|
220
226
|
status?: number;
|
|
@@ -6,6 +6,7 @@ import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
|
|
|
6
6
|
import { observeCancelByDeny, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceEditRefusedOnBlindAsk, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
|
|
7
7
|
import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
|
|
8
8
|
import { isRuleStoreUnreadableKind } from '../gateVocabulary.js';
|
|
9
|
+
import { denyAttributionRefusalFromError } from '../wireRefusalCopy.js';
|
|
9
10
|
import { readDecideReceipt } from '../decideReceipt.js';
|
|
10
11
|
import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, DENIAL_LIMIT_KINDS, } from '@sema-agent/sdk';
|
|
11
12
|
export function isFsApprovalGate(gate) {
|
|
@@ -205,18 +206,44 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
205
206
|
...(safetyCode !== undefined ? { safetyCode } : {}),
|
|
206
207
|
};
|
|
207
208
|
}
|
|
208
|
-
case 'deny':
|
|
209
|
+
case 'deny': {
|
|
210
|
+
const denySettledByForWire = readApprovalDenySettledBy(card);
|
|
211
|
+
const denyReasonSnapshot = typeof card.reason === 'string' ? card.reason : undefined;
|
|
212
|
+
let lastSendCarriedAttribution = false;
|
|
209
213
|
try {
|
|
210
|
-
const
|
|
214
|
+
const denyBody = {
|
|
215
|
+
decision: 'deny',
|
|
216
|
+
reason: denyReasonForWire(denyReasonSnapshot, `task ${taskId}`) ?? DEFAULT_DENY_REASON,
|
|
217
|
+
...(denySettledByForWire === 'policy' ? { settledBy: 'policy' } : {}),
|
|
218
|
+
};
|
|
219
|
+
let attributionDropped = false;
|
|
220
|
+
let raw;
|
|
221
|
+
lastSendCarriedAttribution = denySettledByForWire === 'policy';
|
|
222
|
+
try {
|
|
223
|
+
raw = await bridge.decideTool(denyBody, gatedCallId, signal ? { signal } : undefined, pending);
|
|
224
|
+
}
|
|
225
|
+
catch (first) {
|
|
226
|
+
if (!shouldResendWithoutAttribution(first, lastSendCarriedAttribution, signal))
|
|
227
|
+
throw first;
|
|
228
|
+
const { settledBy: _droppedKey, ...withoutAttribution } = denyBody;
|
|
229
|
+
void _droppedKey;
|
|
230
|
+
lastSendCarriedAttribution = false;
|
|
231
|
+
hostLog('debug', `liveToolApprovalWire: decide(deny) for task ${taskId} was refused because the settlement note is not part of ` +
|
|
232
|
+
'what this deployment signs — re-sending the SAME decision exactly once with that one key removed ' +
|
|
233
|
+
'(the refusal happened before the approval was judged, so nothing was consumed and this is a re-delivery, not a second act)');
|
|
234
|
+
raw = await bridge.decideTool(withoutAttribution, gatedCallId, signal ? { signal } : undefined, pending);
|
|
235
|
+
attributionDropped = true;
|
|
236
|
+
}
|
|
211
237
|
const receipt = readDecideReceipt(raw);
|
|
212
|
-
const denySettledBy =
|
|
213
|
-
const denyReason =
|
|
238
|
+
const denySettledBy = denySettledByForWire;
|
|
239
|
+
const denyReason = denyReasonSnapshot !== undefined && denyReasonSnapshot.trim() !== '' ? denyReasonSnapshot : undefined;
|
|
214
240
|
return {
|
|
215
241
|
kind: 'decided',
|
|
216
242
|
gatedCallId,
|
|
217
243
|
denied: true,
|
|
218
244
|
...(denySettledBy !== undefined ? { denySettledBy } : {}),
|
|
219
245
|
...(denyReason !== undefined ? { denyReason } : {}),
|
|
246
|
+
...(attributionDropped ? { denyAttributionDropped: true } : {}),
|
|
220
247
|
...(receipt !== undefined ? { receipt } : {}),
|
|
221
248
|
};
|
|
222
249
|
}
|
|
@@ -224,6 +251,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
224
251
|
const currentPending = readDecideCurrentPending(e);
|
|
225
252
|
const wireCode = readWireErrorCode(e);
|
|
226
253
|
const safetyCode = e instanceof HitlSafetyError ? e.code : undefined;
|
|
254
|
+
const attributionRefusal = denyAttributionRefusalFromError(e, lastSendCarriedAttribution);
|
|
227
255
|
return {
|
|
228
256
|
kind: 'failed',
|
|
229
257
|
stage: 'decide',
|
|
@@ -233,8 +261,10 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
233
261
|
...(currentPending !== undefined ? { currentPending } : {}),
|
|
234
262
|
...(wireCode !== undefined ? { errorCode: wireCode } : {}),
|
|
235
263
|
...(safetyCode !== undefined ? { safetyCode } : {}),
|
|
264
|
+
...(attributionRefusal !== null ? { denyAttributionRefusal: attributionRefusal } : {}),
|
|
236
265
|
};
|
|
237
266
|
}
|
|
267
|
+
}
|
|
238
268
|
}
|
|
239
269
|
}
|
|
240
270
|
function readReadRootCandidate(v) {
|
|
@@ -392,6 +422,13 @@ function readPersistedRuleAnchors(v) {
|
|
|
392
422
|
function isNonNegativeSafeInt(v) {
|
|
393
423
|
return typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
|
|
394
424
|
}
|
|
425
|
+
function shouldResendWithoutAttribution(e, sentSettledBy, signal) {
|
|
426
|
+
if (!sentSettledBy)
|
|
427
|
+
return false;
|
|
428
|
+
if (signal?.aborted === true)
|
|
429
|
+
return false;
|
|
430
|
+
return denyAttributionRefusalFromError(e, true)?.kind === 'not_in_proof';
|
|
431
|
+
}
|
|
395
432
|
export function readToolApprovalRespondRefusal(err) {
|
|
396
433
|
const pick = (key) => {
|
|
397
434
|
try {
|
|
@@ -447,11 +484,20 @@ export function isToolApprovalFrame(ev) {
|
|
|
447
484
|
typeof e.approvalId === 'string' &&
|
|
448
485
|
e.approvalId.length > 0);
|
|
449
486
|
}
|
|
450
|
-
const
|
|
487
|
+
const FS_WRITE_GATE_ASK_PREFIX = 'approve write to "';
|
|
488
|
+
const FS_WRITE_GATE_ASK_PATH_CLOSE = '"?';
|
|
451
489
|
function pathFromGateMessage(message) {
|
|
452
490
|
if (typeof message !== 'string')
|
|
453
491
|
return undefined;
|
|
454
|
-
|
|
492
|
+
if (!message.startsWith(FS_WRITE_GATE_ASK_PREFIX))
|
|
493
|
+
return undefined;
|
|
494
|
+
const body = message.slice(FS_WRITE_GATE_ASK_PREFIX.length);
|
|
495
|
+
const close = body.indexOf(FS_WRITE_GATE_ASK_PATH_CLOSE);
|
|
496
|
+
if (close <= 0)
|
|
497
|
+
return undefined;
|
|
498
|
+
if (body.indexOf(FS_WRITE_GATE_ASK_PATH_CLOSE, close + 1) !== -1)
|
|
499
|
+
return undefined;
|
|
500
|
+
return body.slice(0, close);
|
|
455
501
|
}
|
|
456
502
|
function isNonNegativeFinite(v) {
|
|
457
503
|
return typeof v === 'number' && Number.isFinite(v) && v >= 0;
|
|
@@ -638,6 +684,8 @@ function subagentBadgeFor(frame) {
|
|
|
638
684
|
}
|
|
639
685
|
const MAX_RESPOND_NOTE_CHARS = 2048;
|
|
640
686
|
export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamArgs, signal, lane) {
|
|
687
|
+
const approvalId = frame.approvalId;
|
|
688
|
+
const frameToolCallId = typeof frame.toolCallId === 'string' && frame.toolCallId !== '' ? frame.toolCallId : undefined;
|
|
641
689
|
const toolName = typeof frame.toolName === 'string' ? frame.toolName : 'Write';
|
|
642
690
|
let args = streamArgs !== undefined && streamArgs !== null
|
|
643
691
|
? streamArgs
|
|
@@ -648,7 +696,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
648
696
|
if (typeof args !== 'object' || args === null) {
|
|
649
697
|
if (frame.argsOmitted === true) {
|
|
650
698
|
wireNote = 'tool arguments exceeded the wire cap and were omitted — the diff below is reconstructed from the gate message, not the full payload';
|
|
651
|
-
hostLog('debug', `liveToolApprovalWire: frame ${
|
|
699
|
+
hostLog('debug', `liveToolApprovalWire: frame ${approvalId} args omitted (>16KiB wire cap) — card falls back to message-derived path`);
|
|
652
700
|
}
|
|
653
701
|
if (lane?.argsUnavailable === true) {
|
|
654
702
|
wireNote =
|
|
@@ -664,9 +712,9 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
664
712
|
const card = await surfaceApprovalCard({
|
|
665
713
|
toolName,
|
|
666
714
|
args: args,
|
|
667
|
-
callKey: liveFrameCallKey(
|
|
668
|
-
...(
|
|
669
|
-
? { toolCallId:
|
|
715
|
+
callKey: liveFrameCallKey(approvalId),
|
|
716
|
+
...(frameToolCallId !== undefined
|
|
717
|
+
? { toolCallId: frameToolCallId }
|
|
670
718
|
: {}),
|
|
671
719
|
...(signal ? { signal } : {}),
|
|
672
720
|
...(isFromSubagent(frame) ? { workerBadge: subagentBadgeFor(frame) } : {}),
|
|
@@ -704,32 +752,36 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
704
752
|
...(typeof frame.origin === 'string' && frame.origin !== '' ? { origin: frame.origin } : {}),
|
|
705
753
|
});
|
|
706
754
|
if (lane?.argsUnavailable === true && card.kind === 'allow' && card.updatedInput !== undefined) {
|
|
707
|
-
hostLog('error', `liveToolApprovalWire: ${
|
|
755
|
+
hostLog('error', `liveToolApprovalWire: ${approvalId} card returned an edited approval on an args-unavailable ask — nothing sent (the ask stays pending)`);
|
|
708
756
|
surfaceEditRefusedOnBlindAsk();
|
|
709
757
|
return { decision: 'unresolved', editRefused: true };
|
|
710
758
|
}
|
|
711
759
|
if (card.kind === RETRACTED_CARD_DECISION_KIND) {
|
|
712
|
-
hostLog('debug', `liveToolApprovalWire: ${
|
|
760
|
+
hostLog('debug', `liveToolApprovalWire: ${approvalId} card retracted without a decision${card.reason ? ` (${card.reason})` : ''} — nothing sent`);
|
|
713
761
|
return { decision: 'unresolved', retracted: true };
|
|
714
762
|
}
|
|
715
763
|
const decision = card.kind === 'allow' ? (card.allowSession ? 'allow_session' : 'allow') : 'deny';
|
|
764
|
+
const denySettledByForWire = card.kind === 'deny' ? readApprovalDenySettledBy(card) : undefined;
|
|
765
|
+
const sentSettledBy = decision === 'deny' && denySettledByForWire === 'policy';
|
|
766
|
+
let lastSendCarriedAttribution = sentSettledBy;
|
|
767
|
+
const denyReasonSnapshot = card.kind === 'deny' && typeof card.reason === 'string' ? card.reason : undefined;
|
|
716
768
|
if (card.kind === 'failed') {
|
|
717
|
-
hostLog('debug', `liveToolApprovalWire: approval card unavailable (${card.reason}) — fail-closed deny for ${
|
|
769
|
+
hostLog('debug', `liveToolApprovalWire: approval card unavailable (${card.reason}) — fail-closed deny for ${approvalId}`);
|
|
718
770
|
}
|
|
719
771
|
let note;
|
|
720
|
-
if (
|
|
772
|
+
if (denyReasonSnapshot !== undefined && denyReasonSnapshot.trim() !== '') {
|
|
721
773
|
if (lane?.approvalDecisionNoteCapable !== true) {
|
|
722
|
-
hostLog('debug', `liveToolApprovalWire: ${
|
|
774
|
+
hostLog('debug', `liveToolApprovalWire: ${approvalId} card supplied a deny reason (len=${denyReasonSnapshot.length}) but the ` +
|
|
723
775
|
'approvalDecisionNote capability is not confirmed for this engine — note not sent (an unknown key would be ' +
|
|
724
776
|
'silently swallowed by an older server while still acking 200)');
|
|
725
777
|
}
|
|
726
|
-
else if (
|
|
727
|
-
hostLog('debug', `liveToolApprovalWire: ${
|
|
728
|
-
`(len=${
|
|
778
|
+
else if (denyReasonSnapshot.length > MAX_RESPOND_NOTE_CHARS) {
|
|
779
|
+
hostLog('debug', `liveToolApprovalWire: ${approvalId} deny reason exceeds the decision_note cap ` +
|
|
780
|
+
`(len=${denyReasonSnapshot.length} > ${MAX_RESPOND_NOTE_CHARS}) — note not sent at all (the server rejects the WHOLE ` +
|
|
729
781
|
'respond with 400 over an oversize note, and a silently halved audit reason is worse than none)');
|
|
730
782
|
}
|
|
731
783
|
else {
|
|
732
|
-
note =
|
|
784
|
+
note = denyReasonSnapshot;
|
|
733
785
|
}
|
|
734
786
|
}
|
|
735
787
|
try {
|
|
@@ -742,7 +794,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
742
794
|
const wantsTextArm = card.kind === 'allow' && typeof card.persistRule === 'string' && card.persistRule !== '';
|
|
743
795
|
const wantsBatchArm = card.kind === 'allow' && card.persistRuleBatchOfferIndex !== undefined;
|
|
744
796
|
if (wantsTextArm && wantsBatchArm) {
|
|
745
|
-
hostLog('error', `liveToolApprovalWire: DROPPING the whole persistRule arm for ${
|
|
797
|
+
hostLog('error', `liveToolApprovalWire: DROPPING the whole persistRule arm for ${approvalId} — the card returned BOTH a rule ` +
|
|
746
798
|
'text and a batchOfferIndex; the server rejects that combination with a 400 that would take the decision down ' +
|
|
747
799
|
'with it, and picking one arm here would be deciding on the user\'s behalf (the decision itself still goes through)');
|
|
748
800
|
ruleArmDroppedForCheck = true;
|
|
@@ -760,14 +812,14 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
760
812
|
batchArmTarget = target;
|
|
761
813
|
}
|
|
762
814
|
else {
|
|
763
|
-
hostLog('debug', `liveToolApprovalWire: DROPPING persistRuleBatchOfferIndex(${idx}) for ${
|
|
815
|
+
hostLog('debug', `liveToolApprovalWire: DROPPING persistRuleBatchOfferIndex(${idx}) for ${approvalId} — the ` +
|
|
764
816
|
'respondBatchRuleOffers capability is not confirmed for this engine, so the batch arm is not sent at all ' +
|
|
765
817
|
'(the decision itself still goes through unchanged; the offer stays on the card for local/display use)');
|
|
766
818
|
ruleArmDroppedForCaps = true;
|
|
767
819
|
}
|
|
768
820
|
}
|
|
769
821
|
else {
|
|
770
|
-
hostLog('error', `liveToolApprovalWire: DROPPING persistRuleBatchOfferIndex(${String(idx)}) for ${
|
|
822
|
+
hostLog('error', `liveToolApprovalWire: DROPPING persistRuleBatchOfferIndex(${String(idx)}) for ${approvalId} — that wire ` +
|
|
771
823
|
'index is not a batch offer on this frame (out of range, a single offer, or not a non-negative integer); ' +
|
|
772
824
|
'never redeeming an index the engine did not offer as a batch');
|
|
773
825
|
ruleArmDroppedForCheck = true;
|
|
@@ -781,7 +833,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
781
833
|
persistRuleEdited = true;
|
|
782
834
|
}
|
|
783
835
|
else {
|
|
784
|
-
hostLog('debug', `liveToolApprovalWire: DROPPING edited persistRule for ${
|
|
836
|
+
hostLog('debug', `liveToolApprovalWire: DROPPING edited persistRule for ${approvalId} (len=${card.persistRule.length}) — the ` +
|
|
785
837
|
'respondFreeFormRules capability is not confirmed for this engine, so the free-form arm is not sent at all ' +
|
|
786
838
|
'(the decision itself still goes through unchanged)');
|
|
787
839
|
ruleArmDroppedForCaps = true;
|
|
@@ -791,22 +843,40 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
791
843
|
persistRule = card.persistRule;
|
|
792
844
|
}
|
|
793
845
|
else {
|
|
794
|
-
hostLog('error', `liveToolApprovalWire: DROPPING persistRule for ${
|
|
846
|
+
hostLog('error', `liveToolApprovalWire: DROPPING persistRule for ${approvalId} — the card returned a rule that is not among the frame's SINGLE offers (a batch member's text is not a selectable candidate either: a conjunction batch is redeemed by index, all-or-nothing) — never sending un-offered text to the rule store`);
|
|
795
847
|
ruleArmDroppedForCheck = true;
|
|
796
848
|
}
|
|
797
849
|
}
|
|
798
|
-
const
|
|
850
|
+
const respondOpts = {
|
|
799
851
|
...(signal && !signal.aborted ? { signal } : {}),
|
|
800
852
|
...(card.kind === 'allow' && card.updatedInput !== undefined ? { updatedInput: card.updatedInput } : {}),
|
|
801
853
|
...(persistRule !== undefined ? { persistRule } : {}),
|
|
802
854
|
...(persistRuleEdited !== undefined ? { persistRuleEdited } : {}),
|
|
803
855
|
...(persistRuleBatchOfferIndex !== undefined ? { persistRuleBatchOfferIndex } : {}),
|
|
804
856
|
...(note !== undefined ? { note } : {}),
|
|
805
|
-
|
|
857
|
+
...(sentSettledBy ? { settledBy: 'policy' } : {}),
|
|
858
|
+
};
|
|
859
|
+
let attributionDropped = false;
|
|
860
|
+
let raw;
|
|
861
|
+
try {
|
|
862
|
+
raw = await respond(approvalId, decision, respondOpts);
|
|
863
|
+
}
|
|
864
|
+
catch (first) {
|
|
865
|
+
if (!shouldResendWithoutAttribution(first, lastSendCarriedAttribution, signal))
|
|
866
|
+
throw first;
|
|
867
|
+
const { settledBy: _droppedKey, ...withoutAttribution } = respondOpts;
|
|
868
|
+
void _droppedKey;
|
|
869
|
+
lastSendCarriedAttribution = false;
|
|
870
|
+
hostLog('debug', `liveToolApprovalWire: respond(deny) for ${approvalId} was refused because the settlement note is not part of ` +
|
|
871
|
+
'what this deployment signs — re-sending the SAME decision exactly once with that one key removed ' +
|
|
872
|
+
'(the refusal happened before the approval was judged, so nothing was consumed and this is a re-delivery, not a second act)');
|
|
873
|
+
raw = await respond(approvalId, decision, withoutAttribution);
|
|
874
|
+
attributionDropped = true;
|
|
875
|
+
}
|
|
806
876
|
const parsed = readToolApprovalRespondAck(raw);
|
|
807
877
|
let ack = parsed;
|
|
808
|
-
if (parsed !== undefined && (parsed.approvalId !==
|
|
809
|
-
hostLog('error', `liveToolApprovalWire: DISCARDING respond ack for ${
|
|
878
|
+
if (parsed !== undefined && (parsed.approvalId !== approvalId || parsed.decision !== decision)) {
|
|
879
|
+
hostLog('error', `liveToolApprovalWire: DISCARDING respond ack for ${approvalId} — it does not correlate ` +
|
|
810
880
|
`(ack.approvalId=${parsed.approvalId} ack.decision=${parsed.decision}, sent decision=${decision}); ` +
|
|
811
881
|
'treating as "no ack" (unknown) — never surfacing a safety notice off an unrelated receipt');
|
|
812
882
|
ack = undefined;
|
|
@@ -837,7 +907,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
837
907
|
strayEchoes.push('persistedRuleAnchors');
|
|
838
908
|
if (strayEchoes.length > 0) {
|
|
839
909
|
hostLog('error', `liveToolApprovalWire: DISCARDING persisted-rule echo(es) [${strayEchoes.join(', ')}] on the ack for ` +
|
|
840
|
-
`${
|
|
910
|
+
`${approvalId} — they do not correlate with the persistence arm actually sent ` +
|
|
841
911
|
`(editedArm=${String(sentEditedArm)} batchArmMembers=${String(expectedBatchCount ?? 'none')} ` +
|
|
842
912
|
`sentOfferIndex=${String(persistRuleBatchOfferIndex ?? 'none')} ` +
|
|
843
913
|
`echoedRules=${String(ack.persistedRules?.length ?? 'none')} ` +
|
|
@@ -860,31 +930,39 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
860
930
|
if (ruleArmDroppedForCheck)
|
|
861
931
|
surfaceRuleArmRejected();
|
|
862
932
|
if (decision === 'allow_session' && ack?.rememberApplied === false) {
|
|
863
|
-
hostLog('debug', `liveToolApprovalWire: ${
|
|
933
|
+
hostLog('debug', `liveToolApprovalWire: ${approvalId} allow_session ack rememberApplied=false — grant not stored, surfacing honest notice`);
|
|
864
934
|
surfaceRememberNotApplied();
|
|
865
935
|
}
|
|
866
|
-
if (
|
|
867
|
-
hostLog('debug', `liveToolApprovalWire: ${
|
|
936
|
+
if (respondOpts.updatedInput !== undefined && ack?.updatedInputForwarded === false) {
|
|
937
|
+
hostLog('debug', `liveToolApprovalWire: ${approvalId} edited args were NOT forwarded (ack.updatedInputForwarded=false) — the tool runs on the ORIGINAL input`);
|
|
868
938
|
surfaceEditNotForwarded();
|
|
869
939
|
}
|
|
870
940
|
if (note !== undefined && ack !== undefined && ack.noteRecorded !== true) {
|
|
871
|
-
hostLog('debug', `liveToolApprovalWire: ${
|
|
941
|
+
hostLog('debug', `liveToolApprovalWire: ${approvalId} decision note was sent but not persisted ` +
|
|
872
942
|
`(ack.noteRecorded=${String(ack.noteRecorded)}) — decision stood; the audit note did not land on the ask row`);
|
|
873
943
|
}
|
|
874
|
-
const denyAttribution =
|
|
944
|
+
const denyAttribution = decision === 'deny'
|
|
875
945
|
? {
|
|
876
|
-
...(
|
|
877
|
-
...(
|
|
946
|
+
...(attributionDropped ? { denyAttributionDropped: true } : {}),
|
|
947
|
+
...(denySettledByForWire !== undefined ? { denySettledBy: denySettledByForWire } : {}),
|
|
948
|
+
...(denyReasonSnapshot !== undefined && denyReasonSnapshot.trim() !== ''
|
|
949
|
+
? { denyReason: denyReasonSnapshot }
|
|
950
|
+
: {}),
|
|
878
951
|
}
|
|
879
952
|
: {};
|
|
880
953
|
return ack !== undefined ? { decision, ack, ...denyAttribution } : { decision, ...denyAttribution };
|
|
881
954
|
}
|
|
882
955
|
catch (e) {
|
|
883
956
|
const respondRefusal = readToolApprovalRespondRefusal(e);
|
|
884
|
-
|
|
957
|
+
const attributionRefusal = denyAttributionRefusalFromError(e, lastSendCarriedAttribution);
|
|
958
|
+
hostLog('debug', `liveToolApprovalWire: respond(${decision}) failed for ${approvalId} ` +
|
|
885
959
|
`(status=${respondRefusal?.status ?? 'none'} errorCode=${logSafeErrorCode(respondRefusal?.errorCode)} ` +
|
|
886
960
|
`messageLen=${respondRefusal?.message?.length ?? 0}) — engine self-settles (TTL/abort); ` +
|
|
887
961
|
'the refusal text is handed back on outcome.respondRefusal for the host to surface');
|
|
888
|
-
return
|
|
962
|
+
return {
|
|
963
|
+
decision: 'unresolved',
|
|
964
|
+
...(respondRefusal !== undefined ? { respondRefusal } : {}),
|
|
965
|
+
...(attributionRefusal !== null ? { denyAttributionRefusal: attributionRefusal } : {}),
|
|
966
|
+
};
|
|
889
967
|
}
|
|
890
968
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export * from './argvFlagValue.js';
|
|
|
40
40
|
export * from './readFacePosture.js';
|
|
41
41
|
export * from './mcpPanel.js';
|
|
42
42
|
export * from './mcpLiveness.js';
|
|
43
|
+
export * from './mcpEngineLeg.js';
|
|
43
44
|
export * from './mcpProbeCapability.js';
|
|
44
45
|
export * from './mcpProbeWire.js';
|
|
45
46
|
export * from './effectiveFacts.js';
|
|
@@ -174,3 +175,4 @@ export * from './env/localeGeo.js';
|
|
|
174
175
|
export * from './env/localeTag.js';
|
|
175
176
|
export * from './env/uiLanguage.js';
|
|
176
177
|
export type { PendingCheckpoint, RuleSuggestion } from '@sema-agent/sdk';
|
|
178
|
+
export * from './ruleRemovalConsequence.js';
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,7 @@ export * from './argvFlagValue.js';
|
|
|
40
40
|
export * from './readFacePosture.js';
|
|
41
41
|
export * from './mcpPanel.js';
|
|
42
42
|
export * from './mcpLiveness.js';
|
|
43
|
+
export * from './mcpEngineLeg.js';
|
|
43
44
|
export * from './mcpProbeCapability.js';
|
|
44
45
|
export * from './mcpProbeWire.js';
|
|
45
46
|
export * from './effectiveFacts.js';
|
|
@@ -171,3 +172,4 @@ export * from './websearch/searchProviderPresets.js';
|
|
|
171
172
|
export * from './env/localeGeo.js';
|
|
172
173
|
export * from './env/localeTag.js';
|
|
173
174
|
export * from './env/uiLanguage.js';
|
|
175
|
+
export * from './ruleRemovalConsequence.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { WiringManifestMcpEntryView } from './adapter/downstream/eventToSdkMessage.js';
|
|
2
|
+
export type McpEngineLegLiveness = {
|
|
3
|
+
kind: 'unobserved';
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'not-listed';
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'ambiguous';
|
|
8
|
+
rows: number;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'unreadable';
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'absent';
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'observed';
|
|
15
|
+
state: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function mcpEngineLegLivenessOf(serverName: string, rows: readonly WiringManifestMcpEntryView[] | null | undefined): McpEngineLegLiveness;
|
|
18
|
+
export interface McpDetailLegNoteInput {
|
|
19
|
+
localClientFailed: boolean;
|
|
20
|
+
engineHostedToolCount: number | undefined;
|
|
21
|
+
liveness: McpEngineLegLiveness;
|
|
22
|
+
}
|
|
23
|
+
export declare function mcpDetailLegNote(input: McpDetailLegNoteInput): string | undefined;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { isMcpLivenessState } from './mcpLiveness.js';
|
|
2
|
+
void true;
|
|
3
|
+
void true;
|
|
4
|
+
void true;
|
|
5
|
+
const MCP_LEG_ROSTER_SCAN_LIMIT = 4096;
|
|
6
|
+
const ABSENT_CELL = Symbol('mcpEngineLeg.absent');
|
|
7
|
+
const UNREADABLE_CELL = Symbol('mcpEngineLeg.unreadable');
|
|
8
|
+
const readKey = (o, k) => {
|
|
9
|
+
if (o === null || typeof o !== 'object')
|
|
10
|
+
return ABSENT_CELL;
|
|
11
|
+
try {
|
|
12
|
+
if (!Object.hasOwn(o, k))
|
|
13
|
+
return ABSENT_CELL;
|
|
14
|
+
return o[k];
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return UNREADABLE_CELL;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export function mcpEngineLegLivenessOf(serverName, rows) {
|
|
21
|
+
let isArray = false;
|
|
22
|
+
try {
|
|
23
|
+
isArray = Array.isArray(rows);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { kind: 'unobserved' };
|
|
27
|
+
}
|
|
28
|
+
if (!isArray)
|
|
29
|
+
return { kind: 'unobserved' };
|
|
30
|
+
const len = readKey(rows, 'length');
|
|
31
|
+
if (typeof len !== 'number' || !Number.isInteger(len) || len < 0)
|
|
32
|
+
return { kind: 'unobserved' };
|
|
33
|
+
if (len > MCP_LEG_ROSTER_SCAN_LIMIT)
|
|
34
|
+
return { kind: 'unobserved' };
|
|
35
|
+
let first;
|
|
36
|
+
let matches = 0;
|
|
37
|
+
let incomplete = false;
|
|
38
|
+
for (let i = 0; i < len; i++) {
|
|
39
|
+
const r = readKey(rows, i);
|
|
40
|
+
if (r === UNREADABLE_CELL) {
|
|
41
|
+
incomplete = true;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (r === null || typeof r !== 'object')
|
|
45
|
+
continue;
|
|
46
|
+
const nm = readKey(r, 'name');
|
|
47
|
+
if (nm === UNREADABLE_CELL) {
|
|
48
|
+
incomplete = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (nm !== serverName)
|
|
52
|
+
continue;
|
|
53
|
+
matches++;
|
|
54
|
+
if (matches === 1)
|
|
55
|
+
first = r;
|
|
56
|
+
}
|
|
57
|
+
if (incomplete)
|
|
58
|
+
return { kind: 'unobserved' };
|
|
59
|
+
if (matches >= 2)
|
|
60
|
+
return { kind: 'ambiguous', rows: matches };
|
|
61
|
+
if (matches === 0)
|
|
62
|
+
return { kind: 'not-listed' };
|
|
63
|
+
const flag = readKey(first, 'livenessUnreadable');
|
|
64
|
+
if (flag === UNREADABLE_CELL || flag === true)
|
|
65
|
+
return { kind: 'unreadable' };
|
|
66
|
+
const cell = readKey(first, 'liveness');
|
|
67
|
+
if (cell === ABSENT_CELL)
|
|
68
|
+
return { kind: 'absent' };
|
|
69
|
+
if (cell === UNREADABLE_CELL || cell === null || typeof cell !== 'object')
|
|
70
|
+
return { kind: 'unreadable' };
|
|
71
|
+
const state = readKey(cell, 'state');
|
|
72
|
+
if (typeof state !== 'string' || state === '')
|
|
73
|
+
return { kind: 'unreadable' };
|
|
74
|
+
return { kind: 'observed', state };
|
|
75
|
+
}
|
|
76
|
+
const rosterReadingOf = (v) => {
|
|
77
|
+
if (v === UNREADABLE_CELL)
|
|
78
|
+
return 'unreadable';
|
|
79
|
+
if (v === ABSENT_CELL || v === undefined)
|
|
80
|
+
return 'silent';
|
|
81
|
+
if (typeof v !== 'number' || !Number.isInteger(v) || v < 0)
|
|
82
|
+
return 'unreadable';
|
|
83
|
+
return v > 0 ? 'listed' : 'zero';
|
|
84
|
+
};
|
|
85
|
+
const NOTE_ENGINE_REACHES = "This client's own connection is down; the engine still reaches it.";
|
|
86
|
+
const NOTE_ENGINE_UNREACHABLE = "This client's own connection is down, and the engine could not reach it when it last looked.";
|
|
87
|
+
const NOTE_ENGINE_CANNOT_TELL = "This client's own connection is down; the engine could not tell whether it reaches this server.";
|
|
88
|
+
const NOTE_ENGINE_WORD_UNRECOGNISED = "This client's own connection is down; the engine reported a liveness state this client does not recognise.";
|
|
89
|
+
const NOTE_LIVENESS_UNREADABLE = "This client's own connection is down; the engine sent a liveness record for this server that this client could not read.";
|
|
90
|
+
const NOTE_ROSTER_LISTS_TOOLS = "This client's own connection is down; the engine listed this server's tools for the last run.";
|
|
91
|
+
const NOTE_ENGINE_SILENT = "This status is this client's own connection. This client has neither a liveness reading nor a tool count from the engine for this server.";
|
|
92
|
+
const NOTE_ROSTER_ZERO = "This status is this client's own connection. The engine reported no tools from this server either.";
|
|
93
|
+
const NOTE_ROSTER_UNREADABLE = "This status is this client's own connection. The engine's tool roster for this server could not be read.";
|
|
94
|
+
export function mcpDetailLegNote(input) {
|
|
95
|
+
const raw = input;
|
|
96
|
+
if (raw === null || typeof raw !== 'object')
|
|
97
|
+
return undefined;
|
|
98
|
+
if (readKey(raw, 'localClientFailed') !== true)
|
|
99
|
+
return undefined;
|
|
100
|
+
const lv = readKey(raw, 'liveness');
|
|
101
|
+
const kind = lv !== null && typeof lv === 'object' ? readKey(lv, 'kind') : undefined;
|
|
102
|
+
if (lv !== null && typeof lv === 'object' && kind === 'observed') {
|
|
103
|
+
const state = readKey(lv, 'state');
|
|
104
|
+
if (isMcpLivenessState(state)) {
|
|
105
|
+
switch (state) {
|
|
106
|
+
case 'reachable':
|
|
107
|
+
return NOTE_ENGINE_REACHES;
|
|
108
|
+
case 'unreachable':
|
|
109
|
+
return NOTE_ENGINE_UNREACHABLE;
|
|
110
|
+
case 'unknown':
|
|
111
|
+
return NOTE_ENGINE_CANNOT_TELL;
|
|
112
|
+
default: {
|
|
113
|
+
const exhaustive = state;
|
|
114
|
+
void exhaustive;
|
|
115
|
+
return NOTE_ENGINE_WORD_UNRECOGNISED;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (typeof state === 'string' && state !== '')
|
|
120
|
+
return NOTE_ENGINE_WORD_UNRECOGNISED;
|
|
121
|
+
return NOTE_LIVENESS_UNREADABLE;
|
|
122
|
+
}
|
|
123
|
+
if (kind === 'unreadable')
|
|
124
|
+
return NOTE_LIVENESS_UNREADABLE;
|
|
125
|
+
switch (rosterReadingOf(readKey(raw, 'engineHostedToolCount'))) {
|
|
126
|
+
case 'listed':
|
|
127
|
+
return NOTE_ROSTER_LISTS_TOOLS;
|
|
128
|
+
case 'zero':
|
|
129
|
+
return NOTE_ROSTER_ZERO;
|
|
130
|
+
case 'unreadable':
|
|
131
|
+
return NOTE_ROSTER_UNREADABLE;
|
|
132
|
+
default:
|
|
133
|
+
return NOTE_ENGINE_SILENT;
|
|
134
|
+
}
|
|
135
|
+
}
|
package/dist/mcpLiveness.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const MCP_LIVENESS_STATES: readonly ["reachable", "unreachable", "unknown"];
|
|
2
2
|
export type McpLivenessState = (typeof MCP_LIVENESS_STATES)[number];
|
|
3
|
+
export declare const isMcpLivenessState: (v: unknown) => v is McpLivenessState;
|
|
3
4
|
export interface McpLivenessView {
|
|
4
5
|
state: McpLivenessState;
|
|
5
6
|
observedAt: number;
|
package/dist/mcpLiveness.js
CHANGED
|
@@ -2,7 +2,8 @@ export const MCP_LIVENESS_STATES = Object.freeze(['reachable', 'unreachable', 'u
|
|
|
2
2
|
void MCP_LIVENESS_STATES;
|
|
3
3
|
void true;
|
|
4
4
|
void true;
|
|
5
|
-
const
|
|
5
|
+
export const isMcpLivenessState = (v) => typeof v === 'string' && MCP_LIVENESS_STATES.includes(v);
|
|
6
|
+
const isLivenessState = isMcpLivenessState;
|
|
6
7
|
const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
7
8
|
export function readMcpLiveness(row) {
|
|
8
9
|
if (!isRecord(row))
|
|
@@ -27,7 +27,7 @@ export const REQUEST_FIELD_MATRIX = [
|
|
|
27
27
|
{ field: 'limits', lanes: ['print', 'utility'], live: true, why: 'P2-3-b:`-p` 的预算护栏(--max-* flag 族),交互 REPL 由人随时 Esc', whyUtilityLane: '有座:side-channel 必须带预算护栏(墙钟 / 输出 token / 轮数)—— 它同样没有人看着,而且没有后续回合可以中断' },
|
|
28
28
|
{ field: 'interactiveTools', lanes: ['print', 'utility'], live: true, why: '[909]B 件3:无人值守 stamp false,从 roster 源头灭掉 AskUserQuestion/plan 门。交互车道 stamp false 等于自废武功', whyUtilityLane: '有座:side-channel 绝不该弹交互问答 / plan 门(没有人在看这条提交)。它与 excludeAllTools 是**两条独立**的收紧声明、在这条车道上**并存**(本车道强制带卸载声明,见 `refuseUtilityWithoutToolUnload`;这一位是它旁边那条「连问都别问」的声明,不是它的降级替身)' },
|
|
29
29
|
{ field: 'oneShot', lanes: ['print', 'utility'], live: true, why: 'SDK TaskRequest.oneShot(server ≥7.12.0 消费):`-p` 提交是一次性的——没有后续回合接住异步后台通知,core 据此把「结束回合等通知」指引换成 block-wait;交互车道有后续回合,stamp true 就是谎报。老 worker 静默忽略(开集提交体),故发送侧不设能力位前置门(oneShotWireCaps 头注)', whyUtilityLane: '有座:side-channel 就是一次性提交 —— 没有后续回合接住异步后台通知,引擎据此把「结束回合等通知」换成 block-wait' },
|
|
30
|
-
{ field: 'outputSchema', lanes: ['print', 'utility'], live: true, why: '结构化输出约束(sdk `TaskRequest.outputSchema`:最终答案必须匹配的 JSON Schema)。上游只验形状与序列化尺寸(坏形 400 响亮拒)、深层合法性归引擎 ⇒
|
|
30
|
+
{ field: 'outputSchema', lanes: ['print', 'utility'], live: true, why: '结构化输出约束(sdk `TaskRequest.outputSchema`:最终答案必须匹配的 JSON Schema)。上游只验形状与序列化尺寸(坏形 400 响亮拒)、深层合法性归引擎 ⇒ 本层原样透传、不预铸第二判官。来源只可能在无人值守车道(schema 由壳的无人值守入口给;哪个壳今天真的接了那条入口是壳侧的事,本表只说座位);交互面今天没有入口 ⇒ 缺席是**没有来源**,不是漏。⚠️ 本行只管**上行**这一位:结果一侧的结构化产出落不落 CC 形 result 帧是下行投影的事,不在本表', whyUtilityLane: '有座:side-channel 的典型形正是「要一个可机读的小答案」(分类 / 摘要 / 抽取),结构化输出约束是它最直接的位,而 schema 由壳自己给 ⇒ 有来源' },
|
|
31
31
|
{ field: 'outputRetries', lanes: ['print', 'utility'], live: true, why: '结构化输出的重试轮数(sdk `TaskRequest.outputRetries`;与 `outputSchema` 是**成对旋钮** —— 没有 schema 时引擎忽略它)。🔴 座位集与 `outputSchema` 那一行**逐字相同**:一半有座一半没座 = 端调得动重试轮数却调不动它所服务的约束(同座孪生分开定座,正是「主轴改了、孪生漏了」那一族)。上游接受域很窄(有限、≥1、向下取整、封顶 10),**表外一律整键省略**并回落引擎缺省 ⇒ 本层对坏值**响亮拒**(见 `refuseBadOutputRetries`),但**不预夹** [1,10]:夹是上游的事,本层不铸第二判官', whyUtilityLane: '有座:与 outputSchema 同座孪生 —— side-channel 要机读小答案时,重试轮数是同一个旋钮的另一半' },
|
|
32
32
|
{ field: 'maxCostUsd', lanes: ['print', 'utility'], live: true, why: '花费上限声明(sdk `TaskRequest.maxCostUsd`,请求体**顶层**位;上游向下夹到部署上限 —— 只许要得更少)。与 `limits` 同一条理由只在无人值守车道:交互面由人随时中断。🔴 坏值(非数 / 非有限 / 非正)在本层**响亮拒**:上游对这一位的坏值是安静忽略并回落部署上限,不是 4xx(见 `refuseBadMaxCostUsd`)', whyUtilityLane: '有座:小预算是本车道的定义之一(与 limits 同一条理由);上游向下夹到部署上限,只许要得更少' },
|
|
33
33
|
{ field: 'maxTokens', lanes: ['print', 'utility'], live: true, why: '整任务 token 预算上限声明(sdk `TaskRequest.maxTokens`,请求体**顶层**位,与 `maxCostUsd` 同属上游那一族「调用方要求的上限」:向下夹到部署上限 —— 只许要得更少)。🔴 座位集与 `maxCostUsd` 那一行**逐字相同**:同一条预算轴的两种计价单位,分开定座就是让其中一条静默漂。🔴 坏值(非数 / 非有限 / 非正 / 非整)在本层**响亮拒**:上游对这一位的坏值是安静按「调用方没给」处理并回落部署上限,不是 4xx —— 部署没设上限时这一次运行就没有 token 闸,而声明人不会知道(见 `refuseBadMaxTokens`)', whyUtilityLane: '有座:与 maxCostUsd 同轴孪生 —— 小预算是本车道的定义之一,两种计价单位都该能声明' },
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function removalConsequenceLineForBehavior(behavior) {
|
|
2
|
+
switch (behavior) {
|
|
3
|
+
case 'allow':
|
|
4
|
+
return 'Commands it covers will be asked about again.';
|
|
5
|
+
case 'deny':
|
|
6
|
+
return 'Commands it covers will no longer be refused by this rule — removing it widens what can run; it does not tighten anything.';
|
|
7
|
+
case 'ask':
|
|
8
|
+
return 'Commands it covers will no longer be held for approval by this rule.';
|
|
9
|
+
default:
|
|
10
|
+
return 'Whatever this rule does for the commands it covers will stop applying.';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function ruleRemovalBehaviorOf(raw) {
|
|
14
|
+
return raw === 'allow' || raw === 'deny' || raw === 'ask' ? raw : undefined;
|
|
15
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { RegistryClient } from '@sema-agent/sdk/registry';
|
|
2
|
+
export { getMeConfig, getScopeConfigDraft, getEffective } from '@sema-agent/sdk/registry';
|
|
3
|
+
export { probeRegistryHealth, postFeedback } from '@sema-agent/sdk/registry';
|
|
4
|
+
export { REGISTRY_AUTH_PATHS, skillContentAddress } from '@sema-agent/sdk/registry';
|
|
5
|
+
export { RegistryApiError, OAuthFlowError } from '@sema-agent/sdk/registry';
|
|
6
|
+
export type { RegistryTokens, TokenProvider, } from '@sema-agent/sdk/registry';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { RegistryClient } from '@sema-agent/sdk/registry';
|
|
2
|
+
export { getMeConfig, getScopeConfigDraft, getEffective } from '@sema-agent/sdk/registry';
|
|
3
|
+
export { probeRegistryHealth, postFeedback } from '@sema-agent/sdk/registry';
|
|
4
|
+
export { REGISTRY_AUTH_PATHS, skillContentAddress } from '@sema-agent/sdk/registry';
|
|
5
|
+
export { RegistryApiError, OAuthFlowError } from '@sema-agent/sdk/registry';
|