@sema-agent/client-core 0.90.0 → 0.92.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 +365 -0
- package/README.md +48 -32
- package/dist/adapt/arms.js +13 -12
- package/dist/adapt/panelTasks.d.ts +0 -2
- package/dist/adapt/panelTasks.js +6 -26
- package/dist/adapt.js +2 -2
- package/dist/adapter/activeRunSelfHeal.d.ts +0 -2
- package/dist/adapter/activeRunSelfHeal.js +5 -27
- package/dist/adapter/downstream/eventToSdkMessage.js +4 -2
- package/dist/adapter/downstream/terminalToSdkResult.js +16 -7
- package/dist/adapter/downstream/wiringManifestView.d.ts +6 -0
- package/dist/adapter/downstream/wiringManifestView.js +16 -0
- package/dist/adapter/runStream.js +39 -20
- package/dist/askSurvivesStandingAnswer.d.ts +13 -0
- package/dist/askSurvivesStandingAnswer.js +53 -0
- package/dist/attachmentSeatCapability.d.ts +11 -0
- package/dist/attachmentSeatCapability.js +27 -0
- package/dist/attachmentsWireCaps.d.ts +9 -0
- package/dist/attachmentsWireCaps.js +74 -21
- package/dist/autoModeUnavailable.js +3 -2
- package/dist/cloudConfigWireCaps.js +5 -4
- package/dist/compensations.js +13 -17
- package/dist/controlRouter.d.ts +32 -25
- package/dist/controlRouter.js +166 -63
- package/dist/coreValuePorts.d.ts +4 -2
- package/dist/coreValuePorts.js +5 -0
- package/dist/decideFailureNote.js +7 -3
- package/dist/decideReceipt.d.ts +9 -0
- package/dist/decideReceipt.js +62 -1
- package/dist/disableSkillsWire.d.ts +1 -0
- package/dist/disableSkillsWire.js +8 -1
- package/dist/displayUntrusted.js +155 -5
- package/dist/effectiveFacts.d.ts +14 -4
- package/dist/effectiveFacts.js +45 -5
- package/dist/engineAgentAbsence.js +2 -1
- package/dist/engineCapReader.js +16 -10
- package/dist/engineCapsCache.d.ts +5 -1
- package/dist/engineCapsCache.js +69 -8
- package/dist/engineClosedSets.d.ts +2 -0
- package/dist/engineClosedSets.js +3 -0
- package/dist/engineErrorCodes.d.ts +17 -0
- package/dist/engineErrorCodes.js +47 -0
- package/dist/engineNoticeCodes.d.ts +14 -0
- package/dist/engineNoticeCodes.js +71 -0
- package/dist/executionLaneCapability.js +2 -0
- package/dist/gateOutcome.js +2 -17
- package/dist/gateVocabulary.d.ts +2 -4
- package/dist/gateVocabulary.js +3 -11
- package/dist/generated/engineFactTables.d.ts +27 -0
- package/dist/generated/engineFactTables.js +128 -0
- package/dist/generated/engineNoticeTables.js +3 -0
- package/dist/generated/settingsSchemaTables.d.ts +8 -0
- package/dist/generated/settingsSchemaTables.js +40 -0
- package/dist/hitl/allowRuleShadow.d.ts +21 -0
- package/dist/hitl/allowRuleShadow.js +167 -0
- package/dist/hitl/askClassPair.d.ts +14 -0
- package/dist/hitl/askClassPair.js +42 -0
- package/dist/hitl/askGateWire.d.ts +1 -1
- package/dist/hitl/askGateWire.js +2 -1
- package/dist/hitl/askSurvivesPosture.d.ts +5 -0
- package/dist/hitl/askSurvivesPosture.js +17 -0
- package/dist/hitl/automaticAnswerVerdict.d.ts +21 -0
- package/dist/hitl/automaticAnswerVerdict.js +143 -0
- package/dist/hitl/crashConverged.d.ts +8 -0
- package/dist/hitl/crashConverged.js +58 -1
- package/dist/hitl/crashConvergedNotice.d.ts +4 -0
- package/dist/hitl/crashConvergedNotice.js +57 -0
- package/dist/hitl/frameRouter.d.ts +2 -0
- package/dist/hitl/frameRouter.js +66 -14
- package/dist/hitl/gateLedger.d.ts +6 -0
- package/dist/hitl/gateLedger.js +34 -5
- package/dist/hitl/hitlBridge.d.ts +7 -4
- package/dist/hitl/hitlHostSurface.d.ts +2 -0
- package/dist/hitl/hitlHostSurface.js +7 -0
- package/dist/hitl/livePendingAsk.js +24 -5
- package/dist/hitl/parkResolver.d.ts +7 -2
- package/dist/hitl/parkResolver.js +21 -7
- package/dist/hitl/persistedRulesWire.d.ts +4 -1
- package/dist/hitl/persistedRulesWire.js +9 -2
- package/dist/hitl/planReviewWire.d.ts +1 -1
- package/dist/hitl/planReviewWire.js +19 -5
- package/dist/hitl/replayedApproval.d.ts +21 -0
- package/dist/hitl/replayedApproval.js +89 -0
- package/dist/hitl/sessionPolicyDeliverable.js +20 -2
- package/dist/hitl/sessionPolicyWire.d.ts +13 -0
- package/dist/hitl/sessionPolicyWire.js +75 -2
- package/dist/hitl/suspendedReopen.js +2 -2
- package/dist/hitl/toolApprovalWire.d.ts +9 -1
- package/dist/hitl/toolApprovalWire.js +40 -40
- package/dist/hookMatcherVerdict.d.ts +29 -0
- package/dist/hookMatcherVerdict.js +266 -0
- package/dist/humanTurn.d.ts +91 -0
- package/dist/humanTurn.js +252 -0
- package/dist/humanTurnRead.d.ts +105 -0
- package/dist/humanTurnRead.js +331 -0
- package/dist/index.d.ts +38 -11
- package/dist/index.js +27 -8
- package/dist/inheritEnvWire.js +4 -5
- package/dist/liveModelCatalog.d.ts +2 -0
- package/dist/liveModelCatalog.js +51 -3
- package/dist/mcpLiveness.js +2 -1
- package/dist/memoryVerbsWire.d.ts +1 -1
- package/dist/memoryVerbsWire.js +3 -0
- package/dist/model/catalog.js +2 -1
- package/dist/model/providerPresets.d.ts +3 -1
- package/dist/model/tierVocabulary.d.ts +2 -1
- package/dist/model/tierVocabulary.js +3 -8
- package/dist/peerFrames.js +2 -2
- package/dist/permissionRuleIssue.d.ts +1 -1
- package/dist/permissionRuleIssue.js +31 -9
- package/dist/permissionsAllowGrantCapability.d.ts +2 -1
- package/dist/permissionsAllowGrantCapability.js +11 -2
- package/dist/permissionsDeclaration.d.ts +29 -0
- package/dist/permissionsDeclaration.js +155 -66
- package/dist/pluginHooksWire.js +2 -11
- package/dist/promptDelivery.d.ts +11 -3
- package/dist/promptDelivery.js +164 -29
- package/dist/promptProfileWireCaps.d.ts +2 -1
- package/dist/protocolSegment.d.ts +9 -0
- package/dist/protocolSegment.js +28 -0
- package/dist/providerRejection.d.ts +1 -0
- package/dist/providerRejection.js +20 -0
- package/dist/readDenyTiers.d.ts +10 -0
- package/dist/readDenyTiers.js +29 -0
- package/dist/request/taskRequest.js +3 -17
- package/dist/resumeRefusalCopy.d.ts +11 -0
- package/dist/resumeRefusalCopy.js +61 -4
- package/dist/rewindArchiveCapability.js +4 -4
- package/dist/rewindWireCaps.d.ts +2 -1
- package/dist/rewindWireCaps.js +19 -2
- package/dist/ruleCompileVerdict.d.ts +2 -0
- package/dist/ruleCompileVerdict.js +13 -0
- package/dist/ruleRemovalConsequence.d.ts +2 -1
- package/dist/runCancelContext.d.ts +12 -0
- package/dist/runCancelContext.js +83 -1
- package/dist/seam.d.ts +2 -0
- package/dist/seam.js +1 -1
- package/dist/seatContract.d.ts +2 -1
- package/dist/sessionMemoryStatus.d.ts +2 -4
- package/dist/sessionSyncFileHistoryCapability.d.ts +18 -0
- package/dist/sessionSyncFileHistoryCapability.js +50 -0
- package/dist/skillToolListsCapability.d.ts +18 -0
- package/dist/skillToolListsCapability.js +50 -0
- package/dist/sqlEngineCapability.js +1 -8
- package/dist/steerPriorityCapability.d.ts +18 -0
- package/dist/steerPriorityCapability.js +80 -0
- package/dist/steerReceipt.d.ts +9 -0
- package/dist/steerReceipt.js +25 -0
- package/dist/subagent/engineSubagentSteer.js +2 -9
- package/dist/supportFloor.js +2 -2
- package/dist/taskRequestWords.d.ts +7 -1
- package/dist/taskRequestWords.js +18 -8
- package/dist/toolHistoryBadTurn.d.ts +1 -0
- package/dist/toolHistoryBadTurn.js +89 -0
- package/dist/toolHistoryMismatch.d.ts +2 -1
- package/dist/toolHistoryMismatch.js +19 -6
- package/dist/toolResult.d.ts +8 -0
- package/dist/toolResult.js +47 -3
- package/dist/types/engineState.d.ts +2 -12
- package/dist/userAttachmentEnvelope.d.ts +61 -0
- package/dist/userAttachmentEnvelope.js +204 -0
- package/dist/userInvokedSkillsCapability.d.ts +18 -0
- package/dist/userInvokedSkillsCapability.js +50 -0
- package/dist/wireFailureShape.d.ts +3 -0
- package/dist/wireFailureShape.js +8 -0
- package/dist/wireRefusalCopy.d.ts +6 -0
- package/dist/wireRefusalCopy.js +26 -3
- package/dist/workflowsJournalCapability.d.ts +18 -0
- package/dist/workflowsJournalCapability.js +50 -0
- package/dist/writeProtectionCapability.d.ts +2 -4
- package/docs/INTEGRATION-CLIENTS.md +1489 -16
- package/package.json +3 -3
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const ABSENT = Object.freeze({ state: 'absent' });
|
|
2
|
+
const MALFORMED = Object.freeze({ state: 'malformed' });
|
|
3
|
+
export function readAskClassPairState(v) {
|
|
4
|
+
if (typeof v !== 'object' || v === null)
|
|
5
|
+
return ABSENT;
|
|
6
|
+
const hasAskClass = Object.hasOwn(v, 'askClass');
|
|
7
|
+
const hasClasses = Object.hasOwn(v, 'classes');
|
|
8
|
+
if (!hasAskClass && !hasClasses)
|
|
9
|
+
return ABSENT;
|
|
10
|
+
if (!hasAskClass || !hasClasses)
|
|
11
|
+
return MALFORMED;
|
|
12
|
+
const askClass = v.askClass;
|
|
13
|
+
if (typeof askClass !== 'string' || askClass === '')
|
|
14
|
+
return MALFORMED;
|
|
15
|
+
const raw = v.classes;
|
|
16
|
+
if (!Array.isArray(raw))
|
|
17
|
+
return MALFORMED;
|
|
18
|
+
const length = raw.length;
|
|
19
|
+
if (typeof length !== 'number' || !Number.isSafeInteger(length) || length <= 0)
|
|
20
|
+
return MALFORMED;
|
|
21
|
+
const classes = [];
|
|
22
|
+
const seen = new Set();
|
|
23
|
+
for (let i = 0; i < length; i++) {
|
|
24
|
+
const d = Object.getOwnPropertyDescriptor(raw, i);
|
|
25
|
+
if (d === undefined || !('value' in d))
|
|
26
|
+
return MALFORMED;
|
|
27
|
+
const c = d.value;
|
|
28
|
+
if (typeof c !== 'string' || c === '' || seen.has(c))
|
|
29
|
+
return MALFORMED;
|
|
30
|
+
seen.add(c);
|
|
31
|
+
classes.push(c);
|
|
32
|
+
}
|
|
33
|
+
if (raw.length !== length)
|
|
34
|
+
return MALFORMED;
|
|
35
|
+
if (classes[0] !== askClass)
|
|
36
|
+
return MALFORMED;
|
|
37
|
+
return Object.freeze({ state: 'pair', askClass, classes: Object.freeze(classes) });
|
|
38
|
+
}
|
|
39
|
+
export function readAskClassPair(v) {
|
|
40
|
+
const r = readAskClassPairState(v);
|
|
41
|
+
return r.state === 'pair' ? { askClass: r.askClass, classes: r.classes } : undefined;
|
|
42
|
+
}
|
|
@@ -8,7 +8,7 @@ export type { AskAnsweredOutput } from './gateLedger.js';
|
|
|
8
8
|
export { SEMA_COLLATERAL_ABORT_KEY } from './gateLedger.js';
|
|
9
9
|
export { SEMA_DENIAL_KIND_KEY } from './gateLedger.js';
|
|
10
10
|
export type { GateDenyAttribution } from './gateLedger.js';
|
|
11
|
-
export { toAnsweredOutput, isAlreadyResolvedGateReason, isGateStandingErrorCode, isCodeClassifiedGateFailure, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
|
|
11
|
+
export { toAnsweredOutput, isAlreadyResolvedFailure, isAlreadyResolvedGateReason, isGateStandingErrorCode, isCodeClassifiedGateFailure, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
|
|
12
12
|
export type { HopBudget, HopRound } from './parkResolver.js';
|
|
13
13
|
export type { GateOutcome, GateFailureCode } from './parkResolver.js';
|
|
14
14
|
export declare function bridgeAskUserQuestionGates(source: AsyncGenerator<AgentEvent>, deps: AskGateWireDeps, opts?: {
|
package/dist/hitl/askGateWire.js
CHANGED
|
@@ -6,7 +6,7 @@ export { HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE } from './frameRouter.js';
|
|
|
6
6
|
export { HITL_POLICY_DENY_MESSAGE } from './frameRouter.js';
|
|
7
7
|
export { SEMA_COLLATERAL_ABORT_KEY } from './gateLedger.js';
|
|
8
8
|
export { SEMA_DENIAL_KIND_KEY } from './gateLedger.js';
|
|
9
|
-
export { toAnsweredOutput, isAlreadyResolvedGateReason, isGateStandingErrorCode, isCodeClassifiedGateFailure, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
|
|
9
|
+
export { toAnsweredOutput, isAlreadyResolvedFailure, isAlreadyResolvedGateReason, isGateStandingErrorCode, isCodeClassifiedGateFailure, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
|
|
10
10
|
export async function* bridgeAskUserQuestionGates(source, deps, opts) {
|
|
11
11
|
const led = createGateLedger();
|
|
12
12
|
const taskId = { current: opts?.taskId };
|
|
@@ -116,6 +116,7 @@ export async function* bridgeAskUserQuestionGates(source, deps, opts) {
|
|
|
116
116
|
led.forgetRunScopedState();
|
|
117
117
|
}
|
|
118
118
|
const seq = handoff !== undefined ? undefined : led.lastSeq();
|
|
119
|
+
led.beginStreamSegment();
|
|
119
120
|
stream = deps.runsEvents(taskId.current, {
|
|
120
121
|
...(seq !== undefined ? { lastEventId: seq } : {}),
|
|
121
122
|
...(opts?.signal ? { signal: opts.signal } : {}),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AutomaticAnswerRoad, type AutomaticAnswerUnknownWhy } from './automaticAnswerVerdict.js';
|
|
1
2
|
export interface AskPostureFacts {
|
|
2
3
|
readonly settingsAskRuleMatched?: boolean;
|
|
3
4
|
}
|
|
@@ -5,10 +6,14 @@ export type AskPostureSurvivalRoad = 'origin' | 'settings_ask_rule';
|
|
|
5
6
|
export type AskPostureVerdict = {
|
|
6
7
|
readonly kind: 'must_ask';
|
|
7
8
|
readonly by: AskPostureSurvivalRoad;
|
|
9
|
+
readonly road?: AutomaticAnswerRoad;
|
|
10
|
+
readonly askClass?: string;
|
|
8
11
|
} | {
|
|
9
12
|
readonly kind: 'posture_may_answer';
|
|
13
|
+
readonly releaseReason?: string;
|
|
10
14
|
} | {
|
|
11
15
|
readonly kind: 'unknown';
|
|
16
|
+
readonly why?: AutomaticAnswerUnknownWhy;
|
|
12
17
|
};
|
|
13
18
|
export declare function askSurvivesPosture(req: {
|
|
14
19
|
readonly origin?: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readOwnNonEmptyString } from '../ownKey.js';
|
|
2
|
+
import { automaticAnswerVerdict } from './automaticAnswerVerdict.js';
|
|
2
3
|
const ORIGIN_SURVIVES_POSTURE = Object.freeze({
|
|
3
4
|
content_question: true,
|
|
4
5
|
ancestor_marked: false,
|
|
@@ -17,6 +18,22 @@ const MUST_ASK_BY_SETTINGS = Object.freeze({ kind: 'must_ask', by: 'settings_ask
|
|
|
17
18
|
const POSTURE_MAY_ANSWER = Object.freeze({ kind: 'posture_may_answer' });
|
|
18
19
|
const UNKNOWN = Object.freeze({ kind: 'unknown' });
|
|
19
20
|
export function askSurvivesPosture(req, facts) {
|
|
21
|
+
const old = legacyPostureVerdict(req, facts);
|
|
22
|
+
const neu = automaticAnswerVerdict(req, 'mode_posture', facts ?? undefined);
|
|
23
|
+
if (old.kind === 'must_ask' || neu.kind === 'must_ask') {
|
|
24
|
+
const by = old.kind === 'must_ask' ? old.by : neu.kind === 'must_ask' && neu.road === 'settings_ask_rule' ? 'settings_ask_rule' : 'origin';
|
|
25
|
+
const road = neu.kind === 'must_ask' ? neu.road : by === 'settings_ask_rule' ? 'settings_ask_rule' : 'origin';
|
|
26
|
+
const askClass = neu.kind === 'must_ask' ? neu.askClass : undefined;
|
|
27
|
+
return Object.freeze({ kind: 'must_ask', by, road, ...(askClass !== undefined ? { askClass } : {}) });
|
|
28
|
+
}
|
|
29
|
+
if (old.kind === 'unknown' || neu.kind === 'unknown') {
|
|
30
|
+
return neu.kind === 'unknown' ? Object.freeze({ kind: 'unknown', why: neu.why }) : UNKNOWN;
|
|
31
|
+
}
|
|
32
|
+
return neu.kind === 'may_answer' && neu.releaseReason !== undefined
|
|
33
|
+
? Object.freeze({ kind: 'posture_may_answer', releaseReason: neu.releaseReason })
|
|
34
|
+
: POSTURE_MAY_ANSWER;
|
|
35
|
+
}
|
|
36
|
+
function legacyPostureVerdict(req, facts) {
|
|
20
37
|
const origin = readOwnNonEmptyString(req, 'origin');
|
|
21
38
|
const rawMatched = typeof facts === 'object' && facts !== null ? facts.settingsAskRuleMatched : undefined;
|
|
22
39
|
const matched = typeof rawMatched === 'boolean' ? rawMatched : undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type CoreModeArbitrationRow } from '../generated/engineFactTables.js';
|
|
2
|
+
export type AutomaticAnswerLane = 'mode_posture' | 'standing_rule';
|
|
3
|
+
export interface AutomaticAnswerFacts {
|
|
4
|
+
readonly settingsAskRuleMatched?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export type AutomaticAnswerRoad = 'origin' | 'requires_real_approval' | 'governance' | 'mandate_word' | 'mandated' | 'allow_rule_shadow' | 'ask_class' | 'settings_ask_rule';
|
|
7
|
+
export type AutomaticAnswerUnknownWhy = 'card_unreadable' | 'lane_not_recognised' | 'classes_malformed' | 'classes_absent' | 'class_not_in_table' | 'host_not_reported';
|
|
8
|
+
export type AutomaticAnswerVerdict = {
|
|
9
|
+
readonly kind: 'must_ask';
|
|
10
|
+
readonly road: AutomaticAnswerRoad;
|
|
11
|
+
readonly askClass?: string;
|
|
12
|
+
readonly origin?: string;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'may_answer';
|
|
15
|
+
readonly releaseReason?: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: 'unknown';
|
|
18
|
+
readonly why: AutomaticAnswerUnknownWhy;
|
|
19
|
+
};
|
|
20
|
+
export declare function foldModeArbitrationRow(classes: Iterable<string>): CoreModeArbitrationRow;
|
|
21
|
+
export declare function automaticAnswerVerdict(card: object | null | undefined, lane: AutomaticAnswerLane, facts?: AutomaticAnswerFacts | null): AutomaticAnswerVerdict;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { CORE_MODE_ARBITRATION, CORE_MODE_RELEASE_REASONS } from '../generated/engineFactTables.js';
|
|
2
|
+
import { readOwnNonEmptyString } from '../ownKey.js';
|
|
3
|
+
import { readAskClassPairState } from './askClassPair.js';
|
|
4
|
+
import { approvalAllowRuleShadowed } from './allowRuleShadow.js';
|
|
5
|
+
import { approvalIsMandated, readApprovalMandate } from './toolApprovalWire.js';
|
|
6
|
+
const TABLE = CORE_MODE_ARBITRATION;
|
|
7
|
+
const ORIGIN_IMPLIES_CLASS = Object.freeze({
|
|
8
|
+
content_question: 'content_question',
|
|
9
|
+
org_unavailable: 'governance_unavailable',
|
|
10
|
+
org_rule: 'org_rule',
|
|
11
|
+
hook: 'hook',
|
|
12
|
+
ask_rule: 'ask_rule',
|
|
13
|
+
denial_limit_fallback: 'denial_limit_fallback',
|
|
14
|
+
});
|
|
15
|
+
const MANDATE_WORD_IMPLIES_CLASS = Object.freeze({
|
|
16
|
+
operator_always: 'shell_floor',
|
|
17
|
+
write_protection_unresolved: 'write_protection_unresolved',
|
|
18
|
+
});
|
|
19
|
+
const ORDINARY_ROW = CORE_MODE_ARBITRATION.ordinary;
|
|
20
|
+
export function foldModeArbitrationRow(classes) {
|
|
21
|
+
let any = false;
|
|
22
|
+
let bypassImmune = false;
|
|
23
|
+
let classifierEligible = true;
|
|
24
|
+
let sandboxAdmission = true;
|
|
25
|
+
let ruleMayClear = true;
|
|
26
|
+
let hookAskFloor = false;
|
|
27
|
+
for (const c of classes) {
|
|
28
|
+
if (!Object.hasOwn(TABLE, c))
|
|
29
|
+
continue;
|
|
30
|
+
const row = TABLE[c];
|
|
31
|
+
any = true;
|
|
32
|
+
bypassImmune ||= row.bypassImmune;
|
|
33
|
+
classifierEligible &&= row.classifierEligible;
|
|
34
|
+
sandboxAdmission &&= row.sandboxAdmission;
|
|
35
|
+
ruleMayClear &&= row.ruleMayClear;
|
|
36
|
+
hookAskFloor ||= row.hookAskFloor;
|
|
37
|
+
}
|
|
38
|
+
if (!any)
|
|
39
|
+
return ORDINARY_ROW;
|
|
40
|
+
return Object.freeze({ bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear, hookAskFloor });
|
|
41
|
+
}
|
|
42
|
+
function laneBlocks(lane, row) {
|
|
43
|
+
return lane === 'mode_posture' ? row.bypassImmune : !row.ruleMayClear;
|
|
44
|
+
}
|
|
45
|
+
function mustAsk(road, extra) {
|
|
46
|
+
return Object.freeze({ kind: 'must_ask', road, ...(extra?.askClass !== undefined ? { askClass: extra.askClass } : {}), ...(extra?.origin !== undefined ? { origin: extra.origin } : {}) });
|
|
47
|
+
}
|
|
48
|
+
const UNKNOWN = Object.freeze({
|
|
49
|
+
card_unreadable: Object.freeze({ kind: 'unknown', why: 'card_unreadable' }),
|
|
50
|
+
lane_not_recognised: Object.freeze({ kind: 'unknown', why: 'lane_not_recognised' }),
|
|
51
|
+
classes_malformed: Object.freeze({ kind: 'unknown', why: 'classes_malformed' }),
|
|
52
|
+
classes_absent: Object.freeze({ kind: 'unknown', why: 'classes_absent' }),
|
|
53
|
+
class_not_in_table: Object.freeze({ kind: 'unknown', why: 'class_not_in_table' }),
|
|
54
|
+
host_not_reported: Object.freeze({ kind: 'unknown', why: 'host_not_reported' }),
|
|
55
|
+
});
|
|
56
|
+
const MAY_ANSWER = Object.freeze({ kind: 'may_answer' });
|
|
57
|
+
function evidenceView(card, ...keys) {
|
|
58
|
+
const out = {};
|
|
59
|
+
for (const k of keys) {
|
|
60
|
+
const v = card[k];
|
|
61
|
+
if (v !== undefined)
|
|
62
|
+
out[k] = v;
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
function readHostAskRuleMatched(facts) {
|
|
67
|
+
if (typeof facts !== 'object' || facts === null)
|
|
68
|
+
return undefined;
|
|
69
|
+
try {
|
|
70
|
+
const d = Object.getOwnPropertyDescriptor(facts, 'settingsAskRuleMatched');
|
|
71
|
+
return d !== undefined && 'value' in d && typeof d.value === 'boolean' ? d.value : undefined;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function automaticAnswerVerdict(card, lane, facts) {
|
|
78
|
+
if (lane !== 'mode_posture' && lane !== 'standing_rule')
|
|
79
|
+
return UNKNOWN.lane_not_recognised;
|
|
80
|
+
try {
|
|
81
|
+
if (typeof card !== 'object' || card === null)
|
|
82
|
+
return UNKNOWN.card_unreadable;
|
|
83
|
+
const origin = readOwnNonEmptyString(evidenceView(card, 'origin'), 'origin');
|
|
84
|
+
if (origin !== undefined && Object.hasOwn(ORIGIN_IMPLIES_CLASS, origin)) {
|
|
85
|
+
const cls = ORIGIN_IMPLIES_CLASS[origin];
|
|
86
|
+
if (laneBlocks(lane, TABLE[cls]))
|
|
87
|
+
return mustAsk('origin', { origin, askClass: cls });
|
|
88
|
+
}
|
|
89
|
+
if (lane === 'standing_rule' && evidenceView(card, 'requiresRealApproval').requiresRealApproval === true)
|
|
90
|
+
return mustAsk('requires_real_approval');
|
|
91
|
+
if (evidenceView(card, 'governanceForced').governanceForced === true)
|
|
92
|
+
return mustAsk('governance');
|
|
93
|
+
if (lane === 'mode_posture') {
|
|
94
|
+
const word = readApprovalMandate(evidenceView(card, 'mandate'));
|
|
95
|
+
if (word !== undefined && Object.hasOwn(MANDATE_WORD_IMPLIES_CLASS, word)) {
|
|
96
|
+
const cls = MANDATE_WORD_IMPLIES_CLASS[word];
|
|
97
|
+
if (laneBlocks(lane, TABLE[cls]))
|
|
98
|
+
return mustAsk('mandate_word', { askClass: cls });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const mandateView = evidenceView(card, 'mandated', 'ruleOffersAbsence', 'mandate');
|
|
103
|
+
if (approvalIsMandated(mandateView))
|
|
104
|
+
return mustAsk('mandated');
|
|
105
|
+
if (approvalAllowRuleShadowed(card) || readOwnNonEmptyString(mandateView, 'ruleOffersAbsence') === 'shadowed')
|
|
106
|
+
return mustAsk('allow_rule_shadow');
|
|
107
|
+
}
|
|
108
|
+
const pair = readAskClassPairState(card);
|
|
109
|
+
if (pair.state === 'malformed')
|
|
110
|
+
return UNKNOWN.classes_malformed;
|
|
111
|
+
if (pair.state === 'absent')
|
|
112
|
+
return UNKNOWN.classes_absent;
|
|
113
|
+
const folded = foldModeArbitrationRow(pair.classes);
|
|
114
|
+
let anyKnown = false;
|
|
115
|
+
let firstBlocking;
|
|
116
|
+
let anyUnknown = false;
|
|
117
|
+
for (const c of pair.classes) {
|
|
118
|
+
if (!Object.hasOwn(TABLE, c)) {
|
|
119
|
+
anyUnknown = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
anyKnown = true;
|
|
123
|
+
if (firstBlocking === undefined && laneBlocks(lane, TABLE[c]))
|
|
124
|
+
firstBlocking = c;
|
|
125
|
+
}
|
|
126
|
+
if (anyKnown && laneBlocks(lane, folded))
|
|
127
|
+
return mustAsk('ask_class', firstBlocking !== undefined ? { askClass: firstBlocking } : undefined);
|
|
128
|
+
if (anyUnknown)
|
|
129
|
+
return UNKNOWN.class_not_in_table;
|
|
130
|
+
if (lane === 'standing_rule')
|
|
131
|
+
return MAY_ANSWER;
|
|
132
|
+
const matched = readHostAskRuleMatched(facts);
|
|
133
|
+
if (matched === true)
|
|
134
|
+
return mustAsk('settings_ask_rule');
|
|
135
|
+
if (matched !== false)
|
|
136
|
+
return UNKNOWN.host_not_reported;
|
|
137
|
+
const releaseReason = CORE_MODE_RELEASE_REASONS.find((r) => pair.classes.includes(r));
|
|
138
|
+
return releaseReason !== undefined ? Object.freeze({ kind: 'may_answer', releaseReason }) : MAY_ANSWER;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return UNKNOWN.card_unreadable;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -16,6 +16,7 @@ export interface CrashConvergedRow {
|
|
|
16
16
|
readonly resumeSafe: boolean;
|
|
17
17
|
readonly needsHumanReason?: 'approved_then_interrupted' | 'pending_unsafe' | 'unstable_row' | 'contradictory';
|
|
18
18
|
}
|
|
19
|
+
export declare const isCrashConvergedCause: (v: unknown) => v is CrashConvergedRow["cause"];
|
|
19
20
|
export interface ApprovalsListEnvelope {
|
|
20
21
|
pending: PendingCheckpoint[];
|
|
21
22
|
livePending?: unknown[];
|
|
@@ -28,3 +29,10 @@ export interface CrashConvergedProjection {
|
|
|
28
29
|
readonly dropped: number;
|
|
29
30
|
}
|
|
30
31
|
export declare function projectCrashConverged(env: unknown): CrashConvergedProjection | undefined;
|
|
32
|
+
export interface CrashConvergedNoticeFacts {
|
|
33
|
+
readonly total: number;
|
|
34
|
+
readonly dropped: number;
|
|
35
|
+
readonly needsHumanReasons: readonly (string | undefined)[];
|
|
36
|
+
readonly causes: readonly CrashConvergedRow['cause'][] | undefined;
|
|
37
|
+
}
|
|
38
|
+
export declare function readCrashConvergedNoticeFacts(projection: CrashConvergedProjection | undefined): CrashConvergedNoticeFacts | undefined;
|
|
@@ -2,7 +2,7 @@ const CRASH_CONVERGED_CAUSE_WORDS = Object.freeze({
|
|
|
2
2
|
crashed_before_park: true,
|
|
3
3
|
shutdown_before_park: true,
|
|
4
4
|
});
|
|
5
|
-
const isCrashConvergedCause = (v) => typeof v === 'string' && Object.hasOwn(CRASH_CONVERGED_CAUSE_WORDS, v);
|
|
5
|
+
export const isCrashConvergedCause = (v) => typeof v === 'string' && Object.hasOwn(CRASH_CONVERGED_CAUSE_WORDS, v);
|
|
6
6
|
const isString = (v) => typeof v === 'string';
|
|
7
7
|
const isNumber = (v) => typeof v === 'number';
|
|
8
8
|
const nonEmptyString = (v) => typeof v === 'string' && v.length > 0;
|
|
@@ -127,3 +127,60 @@ export function projectCrashConverged(env) {
|
|
|
127
127
|
}
|
|
128
128
|
return { total: resumeSafe.length + needsHuman.length, resumeSafe, needsHuman, dropped };
|
|
129
129
|
}
|
|
130
|
+
const isCount = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
|
|
131
|
+
function bucketRowsOf(bucket) {
|
|
132
|
+
if (!Array.isArray(bucket))
|
|
133
|
+
return undefined;
|
|
134
|
+
const len = ownDataValue(bucket, 'length');
|
|
135
|
+
if (!isCount(len) || len > MAX_CRASH_CONVERGED_ROWS)
|
|
136
|
+
return undefined;
|
|
137
|
+
const rows = [];
|
|
138
|
+
for (let i = 0; i < len; i++)
|
|
139
|
+
rows.push(ownDataValue(bucket, String(i)));
|
|
140
|
+
return rows;
|
|
141
|
+
}
|
|
142
|
+
function reasonTextOf(row) {
|
|
143
|
+
try {
|
|
144
|
+
if (typeof row !== 'object' || row === null)
|
|
145
|
+
return undefined;
|
|
146
|
+
const v = ownDataValue(row, 'needsHumanReason');
|
|
147
|
+
return typeof v === 'string' ? v : undefined;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function causeWordsOf(rows) {
|
|
154
|
+
const seen = [];
|
|
155
|
+
try {
|
|
156
|
+
for (const row of rows) {
|
|
157
|
+
if (typeof row !== 'object' || row === null)
|
|
158
|
+
return undefined;
|
|
159
|
+
const c = ownDataValue(row, 'cause');
|
|
160
|
+
if (isCrashConvergedCause(c) && !seen.includes(c))
|
|
161
|
+
seen.push(c);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
return seen;
|
|
168
|
+
}
|
|
169
|
+
export function readCrashConvergedNoticeFacts(projection) {
|
|
170
|
+
try {
|
|
171
|
+
if (typeof projection !== 'object' || projection === null)
|
|
172
|
+
return undefined;
|
|
173
|
+
const total = ownDataValue(projection, 'total');
|
|
174
|
+
const dropped = ownDataValue(projection, 'dropped');
|
|
175
|
+
if (!isCount(total) || !isCount(dropped) || total > MAX_CRASH_CONVERGED_ROWS)
|
|
176
|
+
return undefined;
|
|
177
|
+
const safe = bucketRowsOf(ownDataValue(projection, 'resumeSafe'));
|
|
178
|
+
const human = bucketRowsOf(ownDataValue(projection, 'needsHuman'));
|
|
179
|
+
if (safe === undefined || human === undefined || safe.length + human.length !== total)
|
|
180
|
+
return undefined;
|
|
181
|
+
return { total, dropped, needsHumanReasons: human.map(reasonTextOf), causes: causeWordsOf([...safe, ...human]) };
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CrashConvergedProjection } from './crashConverged.js';
|
|
2
|
+
export declare const NEEDS_HUMAN_REASON_ORDER: readonly ["approved_then_interrupted", "pending_unsafe", "contradictory", "unstable_row"];
|
|
3
|
+
export declare function crashConvergedNeedsHumanSentence(reason: string | undefined, n: number, total: number): string | undefined;
|
|
4
|
+
export declare function crashConvergedNotice(projection: CrashConvergedProjection | undefined): string | undefined;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readCrashConvergedNoticeFacts } from './crashConverged.js';
|
|
2
|
+
export const NEEDS_HUMAN_REASON_ORDER = Object.freeze(['approved_then_interrupted', 'pending_unsafe', 'contradictory', 'unstable_row']);
|
|
3
|
+
const _orderCoversReasons = true;
|
|
4
|
+
const _orderInsideReasons = true;
|
|
5
|
+
const _orderHasNoDuplicate = true;
|
|
6
|
+
void _orderCoversReasons;
|
|
7
|
+
void _orderInsideReasons;
|
|
8
|
+
void _orderHasNoDuplicate;
|
|
9
|
+
const RECORDED = 'based on what was recorded';
|
|
10
|
+
const NO_AUTO_RERUN = 'sema has not re-run anything';
|
|
11
|
+
const plural = (n, one, many) => (n === 1 ? one : many);
|
|
12
|
+
const SENTENCE = Object.freeze({
|
|
13
|
+
approved_then_interrupted: (n, total) => `${n} of ${total} had already been approved and may have been running when the previous engine run ended, so a side effect may have partly landed`,
|
|
14
|
+
pending_unsafe: (n, total) => `${n} of ${total} had not started, ${RECORDED}, but the engine did not mark ${plural(n, 'it', 'them')} as safe to re-run`,
|
|
15
|
+
contradictory: (n, total) => `${n} of ${total} ${plural(n, 'has a contradictory record', 'have contradictory records')}`,
|
|
16
|
+
unstable_row: (n, total) => `${n} of ${total} ${plural(n, 'has a record that read differently each time it was read', 'have records that read differently each time they were read')}`,
|
|
17
|
+
});
|
|
18
|
+
const fallbackSentence = (n, total) => `${n} of ${total} had already been approved, or cannot be accounted for, so a side effect may have partly landed`;
|
|
19
|
+
const isNeedsHumanReason = (v) => typeof v === 'string' && Object.hasOwn(SENTENCE, v);
|
|
20
|
+
export function crashConvergedNeedsHumanSentence(reason, n, total) {
|
|
21
|
+
if (!isNeedsHumanReason(reason))
|
|
22
|
+
return undefined;
|
|
23
|
+
if (typeof n !== 'number' || !Number.isSafeInteger(n) || n < 1)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (typeof total !== 'number' || !Number.isSafeInteger(total) || total < n)
|
|
26
|
+
return undefined;
|
|
27
|
+
return SENTENCE[reason](n, total);
|
|
28
|
+
}
|
|
29
|
+
function needsHumanClauses(reasons, total) {
|
|
30
|
+
const clauses = [];
|
|
31
|
+
for (const reason of NEEDS_HUMAN_REASON_ORDER) {
|
|
32
|
+
const n = reasons.filter((r) => r === reason).length;
|
|
33
|
+
if (n > 0)
|
|
34
|
+
clauses.push(SENTENCE[reason](n, total));
|
|
35
|
+
}
|
|
36
|
+
const fallback = reasons.filter((r) => !isNeedsHumanReason(r)).length;
|
|
37
|
+
if (fallback > 0)
|
|
38
|
+
clauses.push(fallbackSentence(fallback, total));
|
|
39
|
+
return `${clauses.join('; ')} — check ${plural(reasons.length, 'it', 'them')} by hand before re-running`;
|
|
40
|
+
}
|
|
41
|
+
export function crashConvergedNotice(projection) {
|
|
42
|
+
const facts = readCrashConvergedNoticeFacts(projection);
|
|
43
|
+
if (facts === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
const { total, dropped, needsHumanReasons, causes } = facts;
|
|
46
|
+
if (total === 0 && dropped === 0)
|
|
47
|
+
return undefined;
|
|
48
|
+
if (total === 0) {
|
|
49
|
+
return `the previous session left ${dropped} crash-converged approval ${plural(dropped, 'record', 'records')} that this version could not read. ${NO_AUTO_RERUN}.`;
|
|
50
|
+
}
|
|
51
|
+
const recordedAs = causes !== undefined && causes.length > 0 ? ` (recorded as ${causes.join(', ')})` : '';
|
|
52
|
+
const head = `the previous engine run ended with ${total} ${plural(total, 'approval', 'approvals')} still waiting${recordedAs}, ` +
|
|
53
|
+
`and the engine converged ${plural(total, 'it', 'them')} to denied`;
|
|
54
|
+
const body = needsHumanReasons.length > 0 ? needsHumanClauses(needsHumanReasons, total) : `${RECORDED}, ${plural(total, 'it', 'they')} can be re-run`;
|
|
55
|
+
const droppedClause = dropped > 0 ? ` (${dropped} further ${plural(dropped, 'record', 'records')} could not be read.)` : '';
|
|
56
|
+
return `${head}; ${body}. ${NO_AUTO_RERUN}.${droppedClause}`;
|
|
57
|
+
}
|
|
@@ -2,6 +2,7 @@ import type { AgentEvent } from '@sema-agent/sdk';
|
|
|
2
2
|
import type { DecideOperationObserver, HitlClientLike } from './hitlBridge.js';
|
|
3
3
|
import { type RespondToolApprovalFn, type ToolApprovalFrame, type ToolApprovalFrameLaneOpts, type ToolApprovalFrameOutcome } from './toolApprovalWire.js';
|
|
4
4
|
import type { GateLedger } from './gateLedger.js';
|
|
5
|
+
import { type ReplayedApprovalDisposition } from './replayedApproval.js';
|
|
5
6
|
export declare const HITL_REJECT_MESSAGE = "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.";
|
|
6
7
|
export declare const HITL_POLICY_DENY_MESSAGE: string;
|
|
7
8
|
export declare const HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE = "[Request interrupted by user for tool use]";
|
|
@@ -23,6 +24,7 @@ export interface AskGateWireDeps {
|
|
|
23
24
|
approvalLane?: ToolApprovalFrameLaneOpts;
|
|
24
25
|
onToolApprovalOutcome?: (frame: ToolApprovalFrame, outcome: ToolApprovalFrameOutcome) => void | Promise<void>;
|
|
25
26
|
onDecideOperation?: DecideOperationObserver;
|
|
27
|
+
onReplayedApproval?: (frame: ToolApprovalFrame, disposition: ReplayedApprovalDisposition) => void | Promise<void>;
|
|
26
28
|
}
|
|
27
29
|
type GateKind = 'ask' | 'fs';
|
|
28
30
|
export interface GatePark {
|
package/dist/hitl/frameRouter.js
CHANGED
|
@@ -3,8 +3,10 @@ import { isToolApprovalGate, toolNameIsFsWrite, toolNameIsShellExec, isToolAppro
|
|
|
3
3
|
import { classifierDenyFromToolEnd } from '../classifierVerdictWire.js';
|
|
4
4
|
import { GATE_PARKED_ERROR_CODE } from '../engineErrorCodes.js';
|
|
5
5
|
import { readRunTerminal, runTerminalCode, runTerminalGateKind, runTerminalGateToolName } from '../runTerminal.js';
|
|
6
|
-
import { surfaceForCurrentSession } from './hitlHostSurface.js';
|
|
7
|
-
import { SEMA_DENIAL_KIND_KEY } from './gateLedger.js';
|
|
6
|
+
import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
|
|
7
|
+
import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
|
|
8
|
+
import { replayedApprovalDisposition } from './replayedApproval.js';
|
|
9
|
+
import { settleWithinDeadline } from '../abortableSleep.js';
|
|
8
10
|
import { ccToolDenialKindForSettledBy } from '../gateVocabulary.js';
|
|
9
11
|
import { REJECT_MESSAGE } from '../adapt/wireShapes.js';
|
|
10
12
|
import { RUN_CANCELLED_CODE } from '../runCancelContext.js';
|
|
@@ -233,22 +235,36 @@ async function routeToolApprovalFrame(ev, ctx) {
|
|
|
233
235
|
hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} but no respond wire — engine self-settles (TTL deny)`);
|
|
234
236
|
return { kind: 'skip' };
|
|
235
237
|
}
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
238
|
+
if (hasLedgerPositionKey(ev) && !led.preambleApprovals().has(ev.approvalId)) {
|
|
239
|
+
const disposition = await judgeLedgerApprovalFrame(ev, ctx);
|
|
240
|
+
if (disposition.kind !== 'live') {
|
|
241
|
+
hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} read back from the run event log is ${disposition.kind}` +
|
|
242
|
+
`${disposition.kind === 'unknown' ? ` (${disposition.why})` : disposition.cause !== undefined ? ` (${disposition.cause})` : ''}` +
|
|
243
|
+
' — not raised as a question, no decision sent');
|
|
244
|
+
if (disposition.kind === 'unknown' && ctx.signal?.aborted !== true)
|
|
245
|
+
surfaceReplayedApprovalListUnreadable();
|
|
246
|
+
if (deps.onReplayedApproval !== undefined) {
|
|
247
|
+
try {
|
|
248
|
+
const out = deps.onReplayedApproval(ev, disposition);
|
|
249
|
+
if (out && typeof out.then === 'function') {
|
|
250
|
+
void Promise.resolve(out).catch((e) => {
|
|
251
|
+
hostLog('debug', `liveHitlAskWire: onReplayedApproval rejected: ${String(e).slice(0, 160)}`);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch (e) {
|
|
256
|
+
hostLog('debug', `liveHitlAskWire: onReplayedApproval threw: ${String(e).slice(0, 160)}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return { kind: 'skip' };
|
|
245
260
|
}
|
|
246
|
-
return { kind: 'skip' };
|
|
247
261
|
}
|
|
248
262
|
const fromSubagent = isFromSubagent(ev);
|
|
249
263
|
hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} tool=${String(ev.toolName)}${fromSubagent ? ` from-subagent=${String(ev.sourceTaskId ?? ev.sourceAgentName ?? 'explicit')}` : ''}`);
|
|
250
|
-
const
|
|
251
|
-
const
|
|
264
|
+
const frameCallId = !fromSubagent && typeof ev.toolCallId === 'string' && ev.toolCallId !== '' ? ev.toolCallId : undefined;
|
|
265
|
+
const gatedCallId = fromSubagent ? undefined : frameCallId ?? led.lastPendingFsCall();
|
|
266
|
+
const argsCallId = frameCallId === undefined ? gatedCallId : typeof ev.toolName === 'string' && toolNameIsFsWrite(ev.toolName) ? frameCallId : undefined;
|
|
267
|
+
const outcome = await surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, argsCallId), ctx.signal, deps.approvalLane);
|
|
252
268
|
const { decision } = outcome;
|
|
253
269
|
if (deps.onToolApprovalOutcome !== undefined) {
|
|
254
270
|
try {
|
|
@@ -275,6 +291,30 @@ async function routeToolApprovalFrame(ev, ctx) {
|
|
|
275
291
|
}
|
|
276
292
|
return { kind: 'skip' };
|
|
277
293
|
}
|
|
294
|
+
const REPLAYED_APPROVAL_LIST_DEADLINE_MS = 3000;
|
|
295
|
+
async function judgeLedgerApprovalFrame(ev, ctx) {
|
|
296
|
+
const outer = ctx.signal;
|
|
297
|
+
const got = await settleWithinDeadline(REPLAYED_APPROVAL_LIST_DEADLINE_MS, (deadline) => {
|
|
298
|
+
const ac = new AbortController();
|
|
299
|
+
const release = () => {
|
|
300
|
+
deadline.removeEventListener('abort', forward);
|
|
301
|
+
outer?.removeEventListener('abort', forward);
|
|
302
|
+
};
|
|
303
|
+
const forward = () => {
|
|
304
|
+
ac.abort();
|
|
305
|
+
release();
|
|
306
|
+
};
|
|
307
|
+
deadline.addEventListener('abort', forward, { once: true });
|
|
308
|
+
outer?.addEventListener('abort', forward, { once: true });
|
|
309
|
+
if (outer?.aborted === true)
|
|
310
|
+
forward();
|
|
311
|
+
return Promise.resolve()
|
|
312
|
+
.then(() => ctx.deps.client.approvals.list({ signal: ac.signal }))
|
|
313
|
+
.finally(release);
|
|
314
|
+
});
|
|
315
|
+
const approvals = got.kind === 'value' ? { read: 'ok', body: got.value } : { read: 'failed' };
|
|
316
|
+
return replayedApprovalDisposition(ev, { preamble: ctx.led.preambleApprovals(), approvals });
|
|
317
|
+
}
|
|
278
318
|
function routeToolStart(ev, callId, led) {
|
|
279
319
|
led.noteStart(callId, ev);
|
|
280
320
|
if (!led.markStarted(callId))
|
|
@@ -355,5 +395,17 @@ export async function routeFrame(ev, ctx) {
|
|
|
355
395
|
if (ev.type === 'failed') {
|
|
356
396
|
return { kind: 'end', events: [...flushHeldWithInterruptRewrite(led, { terminal: ev, signal: ctx.signal }), ev] };
|
|
357
397
|
}
|
|
398
|
+
if (ev.type === 'approval_request') {
|
|
399
|
+
try {
|
|
400
|
+
if (!hasLedgerPositionKey(ev)) {
|
|
401
|
+
const id = ev.approvalId;
|
|
402
|
+
if (typeof id === 'string' && id !== '')
|
|
403
|
+
led.notePreambleApproval(id);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
catch (e) {
|
|
407
|
+
hostLog('debug', `liveHitlAskWire: approval_request frame unreadable — not counted as preamble evidence: ${String(e).slice(0, 120)}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
358
410
|
return { kind: 'yield', events: [ev] };
|
|
359
411
|
}
|
|
@@ -13,10 +13,16 @@ export interface AskAnsweredOutput {
|
|
|
13
13
|
notes?: string;
|
|
14
14
|
}>;
|
|
15
15
|
}
|
|
16
|
+
export declare function durableSeqOf(ev: object): string | undefined;
|
|
17
|
+
export declare function hasLedgerPositionKey(ev: object): boolean;
|
|
18
|
+
export declare const PREAMBLE_APPROVALS_MAX_ENTRIES = 4096;
|
|
16
19
|
export interface GateLedger {
|
|
17
20
|
noteSeq(ev: AgentEvent): void;
|
|
18
21
|
lastSeq(): string | undefined;
|
|
19
22
|
forgetRunScopedState(): void;
|
|
23
|
+
notePreambleApproval(approvalId: string): void;
|
|
24
|
+
preambleApprovals(): ReadonlySet<string>;
|
|
25
|
+
beginStreamSegment(): void;
|
|
20
26
|
markStarted(callId: string): boolean;
|
|
21
27
|
noteGatedStart(callId: string, ev: AgentEvent): void;
|
|
22
28
|
gatedStartArgs(): ReadonlyMap<string, unknown>;
|
package/dist/hitl/gateLedger.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
export const SEMA_COLLATERAL_ABORT_KEY = '_sema_collateral_abort';
|
|
2
2
|
export const SEMA_DENIAL_KIND_KEY = '_sema_denial_kind';
|
|
3
|
+
export function durableSeqOf(ev) {
|
|
4
|
+
const rawSeq = ev.id ?? ev.seq;
|
|
5
|
+
if (typeof rawSeq === 'string' && rawSeq.length > 0)
|
|
6
|
+
return rawSeq;
|
|
7
|
+
if (typeof rawSeq === 'number' && Number.isFinite(rawSeq))
|
|
8
|
+
return String(rawSeq);
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
export function hasLedgerPositionKey(ev) {
|
|
12
|
+
const { id, seq } = ev;
|
|
13
|
+
return (id !== undefined && id !== null) || (seq !== undefined && seq !== null);
|
|
14
|
+
}
|
|
15
|
+
export const PREAMBLE_APPROVALS_MAX_ENTRIES = 4096;
|
|
3
16
|
export const START_ARGS_MAX_ENTRIES = 256;
|
|
4
17
|
function normalizeDenyAttribution(a) {
|
|
5
18
|
if (a === undefined)
|
|
@@ -74,13 +87,12 @@ export function createGateLedger() {
|
|
|
74
87
|
let denyStampNextFsEnd = false;
|
|
75
88
|
let denyStampAttribution;
|
|
76
89
|
let seq;
|
|
90
|
+
const preambleApprovalIds = new Set();
|
|
77
91
|
return {
|
|
78
92
|
noteSeq(ev) {
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
81
|
-
seq =
|
|
82
|
-
else if (typeof rawSeq === 'number' && Number.isFinite(rawSeq))
|
|
83
|
-
seq = String(rawSeq);
|
|
93
|
+
const s = durableSeqOf(ev);
|
|
94
|
+
if (s !== undefined)
|
|
95
|
+
seq = s;
|
|
84
96
|
},
|
|
85
97
|
lastSeq() {
|
|
86
98
|
return seq;
|
|
@@ -91,6 +103,23 @@ export function createGateLedger() {
|
|
|
91
103
|
gatedStartArgsByCall.clear();
|
|
92
104
|
startArgsByCall.clear();
|
|
93
105
|
},
|
|
106
|
+
notePreambleApproval(approvalId) {
|
|
107
|
+
if (typeof approvalId !== 'string' || approvalId === '')
|
|
108
|
+
return;
|
|
109
|
+
preambleApprovalIds.delete(approvalId);
|
|
110
|
+
preambleApprovalIds.add(approvalId);
|
|
111
|
+
if (preambleApprovalIds.size > PREAMBLE_APPROVALS_MAX_ENTRIES) {
|
|
112
|
+
const oldest = preambleApprovalIds.values().next().value;
|
|
113
|
+
if (oldest !== undefined)
|
|
114
|
+
preambleApprovalIds.delete(oldest);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
preambleApprovals() {
|
|
118
|
+
return preambleApprovalIds;
|
|
119
|
+
},
|
|
120
|
+
beginStreamSegment() {
|
|
121
|
+
preambleApprovalIds.clear();
|
|
122
|
+
},
|
|
94
123
|
markStarted(callId) {
|
|
95
124
|
if (startedCalls.has(callId))
|
|
96
125
|
return false;
|