@sema-agent/client-core 0.112.1 → 0.113.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 +130 -0
- package/README.md +36 -23
- package/dist/adapt/arms.js +7 -1
- package/dist/adapt/textStream.d.ts +2 -0
- package/dist/adapt/textStream.js +27 -1
- package/dist/adapter/activeRunSelfHeal.d.ts +7 -5
- package/dist/adapter/activeRunSelfHeal.js +38 -66
- package/dist/adapter/downstream/eventToSdkMessage.js +4 -0
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +8 -0
- package/dist/adapter/downstream/terminalToSdkResult.js +36 -1
- package/dist/adapter/runStream.d.ts +2 -12
- package/dist/adapter/runStream.js +46 -19
- package/dist/agentSession/engineAgentRegistryRead.js +2 -1
- package/dist/agentSession/engineSessionBackgroundStop.js +2 -2
- package/dist/attachmentOutcome.d.ts +35 -0
- package/dist/attachmentOutcome.js +192 -0
- package/dist/attachmentPathPlan.d.ts +67 -0
- package/dist/attachmentPathPlan.js +203 -0
- package/dist/attachmentRoute.d.ts +9 -1
- package/dist/attachmentRoute.js +23 -2
- package/dist/declared/supportFloorDeclared.d.ts +1 -1
- package/dist/declared/supportFloorDeclared.js +1 -0
- package/dist/deviceRebind.js +2 -3
- package/dist/displayUntrusted.js +126 -19
- package/dist/engineAddress.d.ts +1 -1
- package/dist/engineAddress.js +9 -6
- package/dist/engineErrorCodes.d.ts +3 -0
- package/dist/engineErrorCodes.js +4 -0
- package/dist/engineNoticeCodes.d.ts +34 -4
- package/dist/engineNoticeCodes.js +133 -6
- package/dist/engineUnavailable.d.ts +4 -2
- package/dist/engineUnavailable.js +99 -17
- package/dist/fleet/fleetLedger.d.ts +9 -3
- package/dist/fleet/fleetLedger.js +112 -16
- package/dist/fleet/fleetProjection.d.ts +5 -0
- package/dist/fleet/fleetProjection.js +9 -1
- package/dist/gateVocabulary.d.ts +7 -0
- package/dist/gateVocabulary.js +40 -14
- package/dist/generated/engineFactTables.d.ts +4 -2
- package/dist/generated/engineFactTables.js +198 -2
- package/dist/generated/engineNoticeTables.js +56 -8
- package/dist/generated/settingsSchemaFns.d.ts +7 -0
- package/dist/generated/settingsSchemaFns.js +11 -0
- package/dist/generated/settingsSchemaHash.d.ts +1 -0
- package/dist/generated/settingsSchemaHash.js +58 -0
- package/dist/generated/settingsSchemaOwnKeyRead.js +6 -1
- package/dist/generated/settingsSchemaTables.d.ts +3 -2
- package/dist/generated/settingsSchemaTables.js +14 -0
- package/dist/generated/settingsSchemaTightenVerdict.d.ts +1 -0
- package/dist/generated/settingsSchemaTightenVerdict.js +125 -0
- package/dist/headlessPermissionModeWire.d.ts +7 -2
- package/dist/headlessPermissionModeWire.js +77 -8
- package/dist/hitl/approvalDecisionDoor.d.ts +24 -0
- package/dist/hitl/approvalDecisionDoor.js +77 -0
- package/dist/hitl/approvalDelivery.d.ts +6 -0
- package/dist/hitl/approvalDelivery.js +57 -7
- package/dist/hitl/approvalResolution.d.ts +6 -3
- package/dist/hitl/approvalResolution.js +24 -2
- package/dist/hitl/approvalsFeed.js +1 -1
- package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
- package/dist/hitl/decisionSend.d.ts +7 -4
- package/dist/hitl/decisionSend.js +90 -41
- package/dist/hitl/frameRouter.d.ts +3 -3
- package/dist/hitl/frameRouter.js +5 -0
- package/dist/hitl/hitlHostSurface.d.ts +3 -2
- package/dist/hitl/hitlHostSurface.js +5 -3
- package/dist/hitl/legacyApprovalShapes.d.ts +142 -0
- package/dist/hitl/legacyApprovalShapes.js +1 -0
- package/dist/hitl/livePendingAsk.d.ts +4 -4
- package/dist/hitl/livePendingAsk.js +5 -3
- package/dist/hitl/parkResolver.js +1 -1
- package/dist/hitl/persistedRulesWire.js +2 -2
- package/dist/hitl/planReviewCard.d.ts +2 -1
- package/dist/hitl/planReviewCard.js +64 -1
- package/dist/hitl/planReviewWire.d.ts +30 -0
- package/dist/hitl/planReviewWire.js +204 -65
- package/dist/hitl/sessionPolicyWire.js +16 -7
- package/dist/hitl/subagentSource.d.ts +1 -0
- package/dist/hitl/subagentSource.js +11 -0
- package/dist/hitl/toolApprovalWire.d.ts +34 -10
- package/dist/hitl/toolApprovalWire.js +182 -33
- package/dist/hookMatcherVerdict.d.ts +2 -1
- package/dist/hookMatcherVerdict.js +3 -2
- package/dist/hooksReaderGenerations.d.ts +8 -0
- package/dist/hooksReaderGenerations.js +49 -0
- package/dist/hooksWireCaps.js +43 -18
- package/dist/hostSettingsSource.d.ts +1 -1
- package/dist/hostSettingsSource.js +2 -2
- package/dist/hostTurnSettings.d.ts +2 -2
- package/dist/hostTurnSettings.js +87 -47
- package/dist/humanTurn.d.ts +2 -1
- package/dist/humanTurn.js +44 -13
- package/dist/humanTurnRead.d.ts +7 -0
- package/dist/humanTurnRead.js +23 -14
- package/dist/humanTurnSentences.js +3 -0
- package/dist/i18n/messages.en.d.ts +91 -5
- package/dist/i18n/messages.en.js +98 -12
- package/dist/i18n/messages.zh.js +108 -15
- package/dist/index.d.ts +29 -15
- package/dist/index.js +17 -9
- package/dist/liveModelCatalog.js +1 -0
- package/dist/liveQuestionStore.d.ts +2 -0
- package/dist/liveQuestionStore.js +7 -2
- package/dist/localConfig/kernel.d.ts +2 -1
- package/dist/localConfig/kernel.js +12 -18
- package/dist/localConfig/mcp/edit.js +25 -27
- package/dist/localConfig/mcp/servers.js +5 -8
- package/dist/localConfig/modelPool.d.ts +6 -1
- package/dist/localConfig/modelPool.js +104 -27
- package/dist/localConfig/modelPoolWords.d.ts +2 -0
- package/dist/localConfig/modelPoolWords.js +1 -0
- package/dist/localConfig/settingsWrite.js +1 -14
- package/dist/localConfig/skills.js +9 -16
- package/dist/localEngineSpawn.d.ts +29 -1
- package/dist/localEngineSpawn.js +62 -1
- package/dist/mcpLiveness.d.ts +1 -1
- package/dist/mcpProbeWire.js +2 -2
- package/dist/mcpReconnect.js +2 -2
- package/dist/model/catalogLoader.js +3 -17
- package/dist/model/onboardJudgement.d.ts +8 -0
- package/dist/model/onboardJudgement.js +8 -3
- package/dist/model/onboardSteps.d.ts +19 -0
- package/dist/model/onboardSteps.js +76 -0
- package/dist/notifications.js +6 -0
- package/dist/ownKey.d.ts +4 -0
- package/dist/ownKey.js +61 -0
- package/dist/pathShape.d.ts +14 -0
- package/dist/pathShape.js +44 -0
- package/dist/planReviewCardFacts.d.ts +3 -0
- package/dist/planReviewCardFacts.js +20 -3
- package/dist/pluginHooksWire.d.ts +20 -8
- package/dist/pluginHooksWire.js +204 -86
- package/dist/prepareRefusal.d.ts +2 -0
- package/dist/prepareRefusal.js +11 -1
- package/dist/request/taskRequest.js +3 -1
- package/dist/resultFrameKeys.d.ts +1 -0
- package/dist/resultFrameKeys.js +3 -0
- package/dist/resumeRefusalCopy.js +3 -1
- package/dist/retryStatus.d.ts +11 -1
- package/dist/retryStatus.js +15 -4
- package/dist/runTerminal.d.ts +1 -0
- package/dist/runTerminal.js +15 -1
- package/dist/sdkWireTransit.d.ts +1 -1
- package/dist/seatContract.d.ts +12 -3
- package/dist/seatContract.js +24 -13
- package/dist/sessionEngine.js +1 -1
- package/dist/sessionHooksRoster.js +8 -6
- package/dist/sessionIdSegment.d.ts +0 -1
- package/dist/sessionIdSegment.js +1 -3
- package/dist/sessionMemoryErase.js +2 -2
- package/dist/sessionMemoryStatus.js +2 -1
- package/dist/sessionOccupant.d.ts +29 -0
- package/dist/sessionOccupant.js +78 -0
- package/dist/settingsHooksSalvage.d.ts +4 -1
- package/dist/settingsHooksSalvage.js +22 -9
- package/dist/skillOverrideChain.d.ts +3 -0
- package/dist/skillOverrideChain.js +31 -4
- package/dist/skillsWireCaps.d.ts +16 -2
- package/dist/skillsWireCaps.js +47 -20
- package/dist/subagent/agentStateDetail.js +32 -8
- package/dist/subagent/engineCompactWire.js +48 -36
- package/dist/subagent/engineDelegatedPrompt.d.ts +3 -2
- package/dist/subagent/engineDelegatedPrompt.js +65 -28
- package/dist/subagentEditOutcomes.d.ts +9 -0
- package/dist/subagentEditOutcomes.js +43 -0
- package/dist/supportFloor.d.ts +2 -1
- package/dist/supportFloor.js +1 -1
- package/dist/toolResult.js +4 -2
- package/dist/toolRoster.d.ts +1 -0
- package/dist/toolRoster.js +1 -0
- package/dist/usageAnalytics.js +2 -1
- package/dist/userAttachmentEnvelope.d.ts +11 -0
- package/dist/userAttachmentEnvelope.js +7 -2
- package/dist/userAttachmentSeat.d.ts +7 -1
- package/dist/userAttachmentSeat.js +44 -7
- package/dist/wiringInstructionsAttribution.js +8 -2
- package/docs/INTEGRATION-CLIENTS.md +1588 -16
- package/package.json +5 -5
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const SETTINGS_HASH_CLOSURE = (UNINSPECTABLE, containerView, inspect, valueOfDescriptor, jsonShapeOf) => {
|
|
2
|
+
const MAX_HASH_DEPTH = 200;
|
|
3
|
+
function stableStringify(v, depth = 0) {
|
|
4
|
+
if (depth > MAX_HASH_DEPTH)
|
|
5
|
+
throw new Error(`stableStringify: value nested deeper than ${MAX_HASH_DEPTH} — refusing to hash (a config value is pathologically deep or cyclic)`);
|
|
6
|
+
if (v === undefined)
|
|
7
|
+
return "null";
|
|
8
|
+
const shape = jsonShapeOf(v);
|
|
9
|
+
switch (shape) {
|
|
10
|
+
case "null":
|
|
11
|
+
case "primitive":
|
|
12
|
+
return JSON.stringify(v);
|
|
13
|
+
case "uninspectable":
|
|
14
|
+
throw new TypeError(`stableStringify: ${UNINSPECTABLE} — refusing to hash`);
|
|
15
|
+
case "array": {
|
|
16
|
+
const view = readOrRefuse(() => containerView(v, true, "wide"));
|
|
17
|
+
const length = readOrRefuse(() => lengthOf(view.length));
|
|
18
|
+
const byIndex = new Map(view.members);
|
|
19
|
+
let out = "";
|
|
20
|
+
for (let i = 0; i < length; i++) {
|
|
21
|
+
const d = byIndex.get(String(i));
|
|
22
|
+
out += `${i === 0 ? "" : ","}${d === undefined ? "" : stableStringify(readOrRefuse(() => valueOfDescriptor(v, d)), depth + 1)}`;
|
|
23
|
+
}
|
|
24
|
+
return `[${out}]`;
|
|
25
|
+
}
|
|
26
|
+
case "plain":
|
|
27
|
+
case "non_plain_object": {
|
|
28
|
+
if (typeof v === "function")
|
|
29
|
+
return JSON.stringify(v);
|
|
30
|
+
const members = readOrRefuse(() => containerView(v, false, "wide")).members.slice().sort(([a], [b]) => (a < b ? -1 : 1));
|
|
31
|
+
let out = "";
|
|
32
|
+
for (const [key, d] of members) {
|
|
33
|
+
const value = readOrRefuse(() => valueOfDescriptor(v, d));
|
|
34
|
+
out += `${out === "" ? "" : ","}${JSON.stringify(key)}:${stableStringify(value, depth + 1)}`;
|
|
35
|
+
}
|
|
36
|
+
return `{${out}}`;
|
|
37
|
+
}
|
|
38
|
+
default: {
|
|
39
|
+
const unhandled = shape;
|
|
40
|
+
throw new Error(`stableStringify: unhandled shape ${String(unhandled)}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function readOrRefuse(read) {
|
|
45
|
+
const r = inspect(read);
|
|
46
|
+
if (!r.ok)
|
|
47
|
+
throw new TypeError(`stableStringify: ${r.sentence} — refusing to hash`);
|
|
48
|
+
return r.value;
|
|
49
|
+
}
|
|
50
|
+
function lengthOf(value) {
|
|
51
|
+
const n = +value;
|
|
52
|
+
const length = Number.isNaN(n) || n <= 0 ? 0 : Math.floor(n);
|
|
53
|
+
if (length > 2 ** 32 - 1)
|
|
54
|
+
throw new RangeError("Invalid array length");
|
|
55
|
+
return length;
|
|
56
|
+
}
|
|
57
|
+
return { stableStringify, MAX_HASH_DEPTH };
|
|
58
|
+
};
|
|
@@ -137,6 +137,11 @@ export const SETTINGS_OWN_KEY_READ_CLOSURE = (isJsonArray) => {
|
|
|
137
137
|
return Number.isInteger(n) && n >= 0 && n < 2 ** 32 - 1 && String(n) === key;
|
|
138
138
|
};
|
|
139
139
|
const NOT_PLAIN_DATA = "a value that is not plain data (a class instance, a Map, a Date, a function, an object with a hidden — non-enumerable — or an accessor own property … — only plain objects, arrays and primitives are)";
|
|
140
|
+
const COPY_PROTOTYPE = Object.freeze(Object.create(null, {
|
|
141
|
+
toString: { value: Object.freeze(function toString() { return "[object Object]"; }), enumerable: false },
|
|
142
|
+
valueOf: { value: Object.freeze(function valueOf() { return this; }), enumerable: false },
|
|
143
|
+
}));
|
|
144
|
+
const isolatedObject = () => Object.create(COPY_PROTOTYPE);
|
|
140
145
|
function appendOwn(list, value) {
|
|
141
146
|
Object.defineProperty(list, list.length, dataDescriptor(value));
|
|
142
147
|
}
|
|
@@ -168,5 +173,5 @@ export const SETTINGS_OWN_KEY_READ_CLOSURE = (isJsonArray) => {
|
|
|
168
173
|
});
|
|
169
174
|
return read.ok ? read.value : undefined;
|
|
170
175
|
}
|
|
171
|
-
return { captureStringArray, UNINSPECTABLE, dataDescriptor, inspect, ownFieldsOf, ownValueOf, readKey };
|
|
176
|
+
return { captureStringArray, UNINSPECTABLE, dataDescriptor, inspect, ownFieldsOf, ownValueOf, readKey, containerView, valueOfDescriptor, isAccessorDescriptor, isolatedObject };
|
|
172
177
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export declare const SETTINGS_ENGINE_OWNED_HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "PreCompact", "PostCompact", "Stop", "StopFailure", "UserPromptSubmit"];
|
|
1
|
+
export declare const SETTINGS_ENGINE_OWNED_HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "PreCompact", "PostCompact", "Stop", "StopFailure", "UserPromptSubmit", "PermissionDenied"];
|
|
2
|
+
export declare const SETTINGS_ENGINE_OWNED_HOOK_EVENT_SINCE: Readonly<Record<(typeof SETTINGS_ENGINE_OWNED_HOOK_EVENTS)[number], string>>;
|
|
2
3
|
export declare const SETTINGS_SECRET_LITERAL_RE: RegExp;
|
|
3
4
|
export declare const SETTINGS_PRIVATE_KEY_PEM_HEADER: RegExp;
|
|
4
5
|
export declare const SETTINGS_SHA256_DIGEST_RE: RegExp;
|
|
@@ -9,7 +10,7 @@ export declare const SETTINGS_BUDGET_NUMBER_KEYS: readonly ["maxBudgetUsd", "tpm
|
|
|
9
10
|
export declare const SETTINGS_BUDGET_STRING_KEYS: readonly ["mode", "budgetDuration", "enforcement"];
|
|
10
11
|
export declare const SETTINGS_BUDGET_PER_MODEL_NUMBER_KEYS: readonly ["maxBudgetUsd", "tpmLimit", "rpmLimit"];
|
|
11
12
|
export declare const SETTINGS_MODEL_API_WORDS: readonly ["openai-completions", "anthropic-messages"];
|
|
12
|
-
export declare const SETTINGS_TOOL_NAME_MATCHER_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostToolUseFailure"];
|
|
13
|
+
export declare const SETTINGS_TOOL_NAME_MATCHER_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PermissionDenied"];
|
|
13
14
|
export declare const SETTINGS_UI_LANGUAGE_VALUES: readonly ["en", "zh"];
|
|
14
15
|
export declare const SETTINGS_MCP_CONTENT_ORIGINS: readonly ["local", "execution", "external"];
|
|
15
16
|
export declare const SETTINGS_MIN_TRUST_WORDS: readonly ["local", "session", "project", "global", "managed", "task"];
|
|
@@ -8,7 +8,20 @@ export const SETTINGS_ENGINE_OWNED_HOOK_EVENTS = Object.freeze([
|
|
|
8
8
|
"Stop",
|
|
9
9
|
"StopFailure",
|
|
10
10
|
"UserPromptSubmit",
|
|
11
|
+
"PermissionDenied",
|
|
11
12
|
]);
|
|
13
|
+
export const SETTINGS_ENGINE_OWNED_HOOK_EVENT_SINCE = Object.freeze({
|
|
14
|
+
"PreToolUse": "1.0.0",
|
|
15
|
+
"PostToolUse": "1.0.0",
|
|
16
|
+
"PostToolUseFailure": "1.0.0",
|
|
17
|
+
"PostToolBatch": "1.0.0",
|
|
18
|
+
"PreCompact": "1.0.0",
|
|
19
|
+
"PostCompact": "1.0.0",
|
|
20
|
+
"Stop": "1.0.0",
|
|
21
|
+
"StopFailure": "1.0.0",
|
|
22
|
+
"UserPromptSubmit": "1.0.0",
|
|
23
|
+
"PermissionDenied": "26.0.0",
|
|
24
|
+
});
|
|
12
25
|
export const SETTINGS_SECRET_LITERAL_RE = Object.freeze(/^(sk-|gh[opus]_|github_pat_|xox[baprse]-|xapp-|bearer\s|basic\s|eyJ[A-Za-z0-9_-]{10,}\.|AKIA[0-9A-Z]{12,}|AIza[0-9A-Za-z_-]{20,}|-----BEGIN[A-Z0-9 ]*PRIVATE KEY)/i);
|
|
13
26
|
export const SETTINGS_PRIVATE_KEY_PEM_HEADER = Object.freeze(/-----BEGIN[A-Z0-9 ]*PRIVATE KEY/i);
|
|
14
27
|
export const SETTINGS_SHA256_DIGEST_RE = Object.freeze(/^sha256:[0-9a-f]{64}$/);
|
|
@@ -41,6 +54,7 @@ export const SETTINGS_TOOL_NAME_MATCHER_EVENTS = Object.freeze([
|
|
|
41
54
|
"PreToolUse",
|
|
42
55
|
"PostToolUse",
|
|
43
56
|
"PostToolUseFailure",
|
|
57
|
+
"PermissionDenied",
|
|
44
58
|
]);
|
|
45
59
|
export const SETTINGS_UI_LANGUAGE_VALUES = Object.freeze([
|
|
46
60
|
"en",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SETTINGS_TIGHTEN_CLOSURE: (MAX_HASH_DEPTH: number, stableStringify: (v: unknown, depth?: number) => string, captureStringArray: (v: unknown, classification: "strict" | "wide") => string[] | undefined, dataDescriptor: (value: unknown) => PropertyDescriptor, isAccessorDescriptor: (d: PropertyDescriptor) => boolean, isolatedObject: () => object, jsonShapeOf: (v: unknown) => "plain" | "non_plain_object" | "array" | "null" | "primitive" | "uninspectable") => any;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export const SETTINGS_TIGHTEN_CLOSURE = (MAX_HASH_DEPTH, stableStringify, captureStringArray, dataDescriptor, isAccessorDescriptor, isolatedObject, jsonShapeOf) => {
|
|
2
|
+
function orderVerdict(diff) {
|
|
3
|
+
return diff > 0 ? "stricter" : diff < 0 ? "looser" : "equal";
|
|
4
|
+
}
|
|
5
|
+
function stringSetOf(v) {
|
|
6
|
+
const list = captureStringArray(v, "strict");
|
|
7
|
+
return list === undefined ? undefined : new Set(list);
|
|
8
|
+
}
|
|
9
|
+
function setVerdict(existing, candidate, supersetIsStricter) {
|
|
10
|
+
const a = stringSetOf(existing);
|
|
11
|
+
const b = stringSetOf(candidate);
|
|
12
|
+
if (a === undefined || b === undefined)
|
|
13
|
+
return "unreadable";
|
|
14
|
+
const candidateCoversExisting = [...a].every((x) => b.has(x));
|
|
15
|
+
const existingCoversCandidate = [...b].every((x) => a.has(x));
|
|
16
|
+
if (candidateCoversExisting && existingCoversCandidate)
|
|
17
|
+
return "equal";
|
|
18
|
+
if (candidateCoversExisting)
|
|
19
|
+
return supersetIsStricter ? "stricter" : "looser";
|
|
20
|
+
if (existingCoversCandidate)
|
|
21
|
+
return supersetIsStricter ? "looser" : "stricter";
|
|
22
|
+
return "unordered";
|
|
23
|
+
}
|
|
24
|
+
function isNumericLowerValue(v) {
|
|
25
|
+
return typeof v === "number" && (Number.isFinite(v) || v === Number.POSITIVE_INFINITY);
|
|
26
|
+
}
|
|
27
|
+
const NOT_STRICT_JSON = { ok: false };
|
|
28
|
+
function strictJsonCopy(v, depth = 0) {
|
|
29
|
+
if (depth > MAX_HASH_DEPTH)
|
|
30
|
+
return NOT_STRICT_JSON;
|
|
31
|
+
const shape = jsonShapeOf(v);
|
|
32
|
+
switch (shape) {
|
|
33
|
+
case "null":
|
|
34
|
+
return { ok: true, value: v };
|
|
35
|
+
case "primitive":
|
|
36
|
+
if (typeof v === "string" || typeof v === "boolean")
|
|
37
|
+
return { ok: true, value: v };
|
|
38
|
+
return typeof v === "number" && Number.isFinite(v) ? { ok: true, value: v } : NOT_STRICT_JSON;
|
|
39
|
+
case "array": {
|
|
40
|
+
const array = v;
|
|
41
|
+
const length = array.length;
|
|
42
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0)
|
|
43
|
+
return NOT_STRICT_JSON;
|
|
44
|
+
const out = [];
|
|
45
|
+
for (let i = 0; i < length; i++) {
|
|
46
|
+
const member = strictJsonStored(Object.getOwnPropertyDescriptor(array, i), depth);
|
|
47
|
+
if (!member.ok)
|
|
48
|
+
return NOT_STRICT_JSON;
|
|
49
|
+
Object.defineProperty(out, i, dataDescriptor(member.value));
|
|
50
|
+
}
|
|
51
|
+
return { ok: true, value: out };
|
|
52
|
+
}
|
|
53
|
+
case "plain": {
|
|
54
|
+
const record = v;
|
|
55
|
+
const out = isolatedObject();
|
|
56
|
+
for (const key of Reflect.ownKeys(record)) {
|
|
57
|
+
if (typeof key === "symbol")
|
|
58
|
+
continue;
|
|
59
|
+
const desc = Object.getOwnPropertyDescriptor(record, key);
|
|
60
|
+
if (desc !== undefined && desc.enumerable !== true)
|
|
61
|
+
continue;
|
|
62
|
+
const member = strictJsonStored(desc, depth);
|
|
63
|
+
if (!member.ok)
|
|
64
|
+
return NOT_STRICT_JSON;
|
|
65
|
+
Object.defineProperty(out, key, dataDescriptor(member.value));
|
|
66
|
+
}
|
|
67
|
+
return { ok: true, value: out };
|
|
68
|
+
}
|
|
69
|
+
case "non_plain_object":
|
|
70
|
+
case "uninspectable":
|
|
71
|
+
return NOT_STRICT_JSON;
|
|
72
|
+
default: {
|
|
73
|
+
const unhandled = shape;
|
|
74
|
+
throw new Error(`strictJsonCopy: unhandled shape ${String(unhandled)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function strictJsonStored(desc, depth) {
|
|
79
|
+
if (desc === undefined || isAccessorDescriptor(desc))
|
|
80
|
+
return NOT_STRICT_JSON;
|
|
81
|
+
return strictJsonCopy(desc.value, depth + 1);
|
|
82
|
+
}
|
|
83
|
+
function noneVerdict(existing, candidate) {
|
|
84
|
+
try {
|
|
85
|
+
const a = strictJsonCopy(existing);
|
|
86
|
+
const b = strictJsonCopy(candidate);
|
|
87
|
+
if (!a.ok || !b.ok)
|
|
88
|
+
return "unreadable";
|
|
89
|
+
return stableStringify(a.value) === stableStringify(b.value) ? "equal" : "unordered";
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return "unreadable";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function tightenVerdict(rule, existing, candidate) {
|
|
96
|
+
switch (rule.rule) {
|
|
97
|
+
case "enumRank": {
|
|
98
|
+
const a = typeof existing === "string" ? rule.order.indexOf(existing) : -1;
|
|
99
|
+
const b = typeof candidate === "string" ? rule.order.indexOf(candidate) : -1;
|
|
100
|
+
return a < 0 || b < 0 ? "unreadable" : orderVerdict(b - a);
|
|
101
|
+
}
|
|
102
|
+
case "boolStricter":
|
|
103
|
+
if (typeof existing !== "boolean" || typeof candidate !== "boolean")
|
|
104
|
+
return "unreadable";
|
|
105
|
+
return existing === candidate ? "equal" : candidate === rule.strict ? "stricter" : "looser";
|
|
106
|
+
case "setUnion":
|
|
107
|
+
return setVerdict(existing, candidate, true);
|
|
108
|
+
case "setNarrower":
|
|
109
|
+
return setVerdict(existing, candidate, false);
|
|
110
|
+
case "numericLower":
|
|
111
|
+
if (!isNumericLowerValue(existing) || !isNumericLowerValue(candidate))
|
|
112
|
+
return "unreadable";
|
|
113
|
+
return existing === candidate ? "equal" : candidate < existing ? "stricter" : "looser";
|
|
114
|
+
case "none":
|
|
115
|
+
return noneVerdict(existing, candidate);
|
|
116
|
+
case "policyDenyWins":
|
|
117
|
+
throw new RangeError("tightenVerdict: policyDenyWins is the fold law between a category's members (deny beats allow), not an order on one value — compare a member through memberTighten(category, member)");
|
|
118
|
+
default: {
|
|
119
|
+
const unknown = rule;
|
|
120
|
+
throw new RangeError(`tightenVerdict: unknown rule '${String(unknown.rule)}' — not a TightenRule word`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { tightenVerdict };
|
|
125
|
+
};
|
|
@@ -9,12 +9,17 @@ export interface HeadlessPermissionFlags {
|
|
|
9
9
|
readonly missing?: true;
|
|
10
10
|
readonly dangerouslySkipPermissions?: boolean;
|
|
11
11
|
}
|
|
12
|
+
export interface HeadlessPermissionSettingsLayer {
|
|
13
|
+
readonly defaultMode?: string;
|
|
14
|
+
readonly autoEligible?: boolean;
|
|
15
|
+
readonly sessionMode?: string | null;
|
|
16
|
+
}
|
|
12
17
|
export type HeadlessPermissionResolution = {
|
|
13
18
|
fields: PermissionModeField;
|
|
14
|
-
source: 'flag' | 'env' | 'default';
|
|
19
|
+
source: 'flag' | 'env' | 'settings' | 'default';
|
|
15
20
|
warning?: string;
|
|
16
21
|
};
|
|
17
|
-
export declare function resolveHeadlessPermissionMode(flags: HeadlessPermissionFlags | undefined, env?: EnvLike, permissionModes?: PermissionModesReading): HeadlessPermissionResolution;
|
|
22
|
+
export declare function resolveHeadlessPermissionMode(flags: HeadlessPermissionFlags | undefined, env?: EnvLike, permissionModes?: PermissionModesReading, settings?: HeadlessPermissionSettingsLayer): HeadlessPermissionResolution;
|
|
18
23
|
export type HeadlessPermissionModeKnob = {
|
|
19
24
|
readonly kind: 'absent';
|
|
20
25
|
} | {
|
|
@@ -3,10 +3,13 @@ import { permissionModeAdmitted } from './permissionModesCapability.js';
|
|
|
3
3
|
import { hostEnv } from './hostEnv.js';
|
|
4
4
|
import { flagString, flagTrue } from './flagInput.js';
|
|
5
5
|
import { PERMISSION_MODE_WORDS } from './taskRequestWords.js';
|
|
6
|
-
import { readOwnDataValue } from './ownKey.js';
|
|
6
|
+
import { guardedRead, ownDataField, readOwnDataValue } from './ownKey.js';
|
|
7
|
+
import { SETTINGS_JSON_SHAPE_OF, SETTINGS_SAFETY_MERGE_SPEC, SETTINGS_TIGHTEN_VERDICT } from './generated/settingsSchemaFns.js';
|
|
8
|
+
import { quoteUntrustedForSentence } from './displayUntrusted.js';
|
|
7
9
|
export const HEADLESS_PERMISSION_MODE_ENV = 'SEMA_HEADLESS_PERMISSION_MODE';
|
|
8
10
|
export const HEADLESS_PERMISSION_WIRE_MODES = [...PERMISSION_MODE_WORDS];
|
|
9
11
|
const SHELL_INTERNAL_MODE_TO_WIRE = new Map([['manual', 'default']]);
|
|
12
|
+
const LAUNCH_FLAG_ONLY_MODE = 'bypassPermissions';
|
|
10
13
|
export function normalizeShellPermissionModeForWire(mode) {
|
|
11
14
|
if (mode === undefined)
|
|
12
15
|
return undefined;
|
|
@@ -21,7 +24,7 @@ function permissionModeOfFlags(flags) {
|
|
|
21
24
|
return undefined;
|
|
22
25
|
}
|
|
23
26
|
let warnedInvalidEnv = false;
|
|
24
|
-
export function resolveHeadlessPermissionMode(flags, env = hostEnv(), permissionModes) {
|
|
27
|
+
export function resolveHeadlessPermissionMode(flags, env = hostEnv(), permissionModes, settings) {
|
|
25
28
|
const flagMode = permissionModeOfFlags(flags);
|
|
26
29
|
if (flagMode !== undefined) {
|
|
27
30
|
const snap = flagMode === 'dontAsk' ? dontAskSnapshot(permissionModes) : undefined;
|
|
@@ -31,6 +34,9 @@ export function resolveHeadlessPermissionMode(flags, env = hostEnv(), permission
|
|
|
31
34
|
}
|
|
32
35
|
const knob = readHeadlessPermissionModeKnob(env);
|
|
33
36
|
if (knob.kind === 'absent') {
|
|
37
|
+
const fromSettings = settingsLayerField(settings, permissionModes);
|
|
38
|
+
if (fromSettings !== undefined)
|
|
39
|
+
return { fields: fromSettings, source: 'settings' };
|
|
34
40
|
return { fields: permissionModeField('default', { stampDefault: true }), source: 'default' };
|
|
35
41
|
}
|
|
36
42
|
if (knob.kind === 'word') {
|
|
@@ -46,16 +52,72 @@ export function resolveHeadlessPermissionMode(flags, env = hostEnv(), permission
|
|
|
46
52
|
}
|
|
47
53
|
return resolution;
|
|
48
54
|
}
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
source: '
|
|
52
|
-
|
|
55
|
+
const fromSettings = settingsLayerField(settings, permissionModes);
|
|
56
|
+
const resolution = fromSettings !== undefined
|
|
57
|
+
? { fields: fromSettings, source: 'settings' }
|
|
58
|
+
: { fields: permissionModeField('default', { stampDefault: true }), source: 'default' };
|
|
53
59
|
if (!warnedInvalidEnv) {
|
|
54
60
|
warnedInvalidEnv = true;
|
|
55
|
-
resolution.warning = knob.warning;
|
|
61
|
+
resolution.warning = fromSettings !== undefined ? invalidKnobSettingsWarning(knob.raw, fromSettings.permissionMode ?? 'default') : knob.warning;
|
|
56
62
|
}
|
|
57
63
|
return resolution;
|
|
58
64
|
}
|
|
65
|
+
function settingsLayerField(settings, permissionModes) {
|
|
66
|
+
const carrier = settingsCarrierOf(settings);
|
|
67
|
+
if (carrier === undefined)
|
|
68
|
+
return undefined;
|
|
69
|
+
const word = flagString(carrier, 'defaultMode');
|
|
70
|
+
if (word === undefined || word === LAUNCH_FLAG_ONLY_MODE || !HEADLESS_PERMISSION_WIRE_MODES.includes(word))
|
|
71
|
+
return undefined;
|
|
72
|
+
const rejectedAuto = word === 'auto' && !flagTrue(carrier, 'autoEligible');
|
|
73
|
+
const chosen = tightenToSession(rejectedAuto ? 'default' : word, readSessionMode(carrier));
|
|
74
|
+
if (chosen === undefined || (rejectedAuto && chosen === 'default'))
|
|
75
|
+
return undefined;
|
|
76
|
+
const snap = chosen === 'dontAsk' ? dontAskSnapshot(permissionModes) : undefined;
|
|
77
|
+
return permissionModeField(chosen, { stampDefault: true, permissionModes: snap });
|
|
78
|
+
}
|
|
79
|
+
function settingsCarrierOf(settings) {
|
|
80
|
+
const shape = SETTINGS_JSON_SHAPE_OF(settings);
|
|
81
|
+
switch (shape) {
|
|
82
|
+
case 'plain':
|
|
83
|
+
return settings;
|
|
84
|
+
case 'non_plain_object':
|
|
85
|
+
case 'array':
|
|
86
|
+
case 'null':
|
|
87
|
+
case 'primitive':
|
|
88
|
+
case 'uninspectable':
|
|
89
|
+
return undefined;
|
|
90
|
+
default: {
|
|
91
|
+
const unreachable = shape;
|
|
92
|
+
return unreachable;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function readSessionMode(carrier) {
|
|
97
|
+
try {
|
|
98
|
+
const v = ownDataField(carrier, 'sessionMode');
|
|
99
|
+
if (typeof v === 'string')
|
|
100
|
+
return Object.freeze({ kind: 'word', word: v });
|
|
101
|
+
if (v === null)
|
|
102
|
+
return Object.freeze({ kind: 'none' });
|
|
103
|
+
if (v !== undefined)
|
|
104
|
+
return Object.freeze({ kind: 'unreadable' });
|
|
105
|
+
return 'sessionMode' in carrier && !Object.hasOwn(carrier, 'sessionMode') ? Object.freeze({ kind: 'unreadable' }) : Object.freeze({ kind: 'none' });
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return Object.freeze({ kind: 'unreadable' });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function tightenToSession(word, session) {
|
|
112
|
+
if (session.kind === 'none')
|
|
113
|
+
return word;
|
|
114
|
+
return (session.kind === 'word' ? clampOnce(word, session.word) : undefined) ?? clampOnce(word, 'default');
|
|
115
|
+
}
|
|
116
|
+
function clampOnce(word, session) {
|
|
117
|
+
const rule = guardedRead(() => readOwnDataValue(readOwnDataValue(SETTINGS_SAFETY_MERGE_SPEC, 'permissionMode'), 'tighten'));
|
|
118
|
+
const verdict = rule === undefined ? undefined : guardedRead(() => SETTINGS_TIGHTEN_VERDICT(rule, word, session));
|
|
119
|
+
return verdict === 'stricter' ? session : verdict === 'equal' || verdict === 'looser' ? word : undefined;
|
|
120
|
+
}
|
|
59
121
|
export function readHeadlessPermissionModeKnob(env = hostEnv()) {
|
|
60
122
|
const raw = env[HEADLESS_PERMISSION_MODE_ENV]?.trim();
|
|
61
123
|
if (raw === undefined || raw === '')
|
|
@@ -65,9 +127,16 @@ export function readHeadlessPermissionModeKnob(env = hostEnv()) {
|
|
|
65
127
|
return Object.freeze({
|
|
66
128
|
kind: 'invalid',
|
|
67
129
|
raw,
|
|
68
|
-
warning: `sema: ${HEADLESS_PERMISSION_MODE_ENV}
|
|
130
|
+
warning: `sema: ${HEADLESS_PERMISSION_MODE_ENV}=${shownKnobValue(raw)} is not a recognized permission mode (expected one of: ${HEADLESS_PERMISSION_WIRE_MODES.join(', ')}) — ignoring it, this run keeps the default permission behavior`,
|
|
69
131
|
});
|
|
70
132
|
}
|
|
133
|
+
function invalidKnobSettingsWarning(raw, mode) {
|
|
134
|
+
return `sema: ${HEADLESS_PERMISSION_MODE_ENV}=${shownKnobValue(raw)} is not a recognized permission mode (expected one of: ${HEADLESS_PERMISSION_WIRE_MODES.join(', ')}) — ignoring it, this run uses "${mode}" from settings`;
|
|
135
|
+
}
|
|
136
|
+
const KNOB_VALUE_SHOWN_MAX = 64;
|
|
137
|
+
function shownKnobValue(raw) {
|
|
138
|
+
return quoteUntrustedForSentence(raw, KNOB_VALUE_SHOWN_MAX) ?? '""';
|
|
139
|
+
}
|
|
71
140
|
export function headlessDontAskDowngradeWarning(permissionModes) {
|
|
72
141
|
return `sema: ${HEADLESS_PERMISSION_MODE_ENV}="dontAsk" is only sent once this client knows the engine accepts that mode, and ${dontAskWithheldClause(permissionModes)} — this run uses "default"`;
|
|
73
142
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AskDecisionBody, PersistRuleSelection } from '@sema-agent/sdk';
|
|
2
|
+
export declare const ASK_DECISION_DOOR_SERVER_FROM = "7.152.0";
|
|
3
|
+
export declare const APPROVAL_DECISION_DOORS: readonly ["ask_decision", "respond"];
|
|
4
|
+
export type ApprovalDecisionDoor = (typeof APPROVAL_DECISION_DOORS)[number];
|
|
5
|
+
export interface ApprovalAskDecisionVerb {
|
|
6
|
+
decideAsk(taskId: string, askId: string, body: AskDoorDecisionBody, opts?: {
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}): Promise<{} | null | undefined>;
|
|
9
|
+
}
|
|
10
|
+
export interface ApprovalDecisionDoorReading {
|
|
11
|
+
readonly baseUrl?: string;
|
|
12
|
+
readonly serverVersion?: string;
|
|
13
|
+
readonly verbs?: ApprovalAskDecisionVerb;
|
|
14
|
+
}
|
|
15
|
+
export declare function approvalDecisionDoorSource(baseUrl?: string | undefined, verbs?: ApprovalAskDecisionVerb): ApprovalDecisionDoorReading;
|
|
16
|
+
export declare function approvalDecisionDoorOf(reading: ApprovalDecisionDoorReading | undefined): ApprovalDecisionDoor;
|
|
17
|
+
export declare function noteRespondDoorSwitch(reading: ApprovalDecisionDoorReading | undefined, logTag: string, approvalId: string): void;
|
|
18
|
+
export declare function approvalDecisionVerbOf(reading: ApprovalDecisionDoorReading | undefined): ApprovalAskDecisionVerb['decideAsk'] | undefined;
|
|
19
|
+
export declare const ROUTE_MISSING_WIRING_HINT = "approval answer got 404 not_found.route on the respond door and could not be retried on the ask decision door \u2014 pass approvalDecisionDoorSource(baseUrl, client.toolApprovals) as approvalDecisionDoor to sendHitlDecision deps and to the live-card lane options (and call invalidateEngineCaps(baseUrl) after an engine switch)";
|
|
20
|
+
export declare function hitlAskIdempotencyKey(askId: string, verdict: 'approve' | 'deny'): string;
|
|
21
|
+
export type AskDoorDecisionBody = AskDecisionBody & {
|
|
22
|
+
remember?: 'session';
|
|
23
|
+
persistRule?: PersistRuleSelection;
|
|
24
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { engineCapString, refreshEngineCapsOnce } from '../engineCapsCache.js';
|
|
2
|
+
import { engineWireTargetBaseUrl } from '../engineWireTarget.js';
|
|
3
|
+
import { hostLog } from '../host.js';
|
|
4
|
+
import { engineVersionTier } from '../supportFloor.js';
|
|
5
|
+
import { guardedRead, readOwnDataNonEmptyString, readOwnDataValue } from '../ownKey.js';
|
|
6
|
+
import { slotReadOf } from '../wireFailureShape.js';
|
|
7
|
+
export const ASK_DECISION_DOOR_SERVER_FROM = '7.152.0';
|
|
8
|
+
export const APPROVAL_DECISION_DOORS = Object.freeze(['ask_decision', 'respond']);
|
|
9
|
+
void true;
|
|
10
|
+
export function approvalDecisionDoorSource(baseUrl = engineWireTargetBaseUrl(), verbs) {
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
...(typeof baseUrl === 'string' && baseUrl !== '' ? { baseUrl } : {}),
|
|
13
|
+
...(verbs !== undefined ? { verbs } : {}),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export function approvalDecisionDoorOf(reading) {
|
|
17
|
+
try {
|
|
18
|
+
if (typeof reading !== 'object' || reading === null)
|
|
19
|
+
return 'respond';
|
|
20
|
+
return engineVersionTier(decisionMomentVersionOf(reading), ASK_DECISION_DOOR_SERVER_FROM) === 'engine' ? 'ask_decision' : 'respond';
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return 'respond';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function decisionMomentVersionOf(reading) {
|
|
27
|
+
const own = slotReadOf(reading, 'serverVersion');
|
|
28
|
+
if (own.presence === 'present')
|
|
29
|
+
return own.value;
|
|
30
|
+
if (own.presence === 'unreadable')
|
|
31
|
+
return undefined;
|
|
32
|
+
const baseUrl = readOwnDataNonEmptyString(reading, 'baseUrl');
|
|
33
|
+
return baseUrl !== undefined ? engineCapString(baseUrl, 'version') : undefined;
|
|
34
|
+
}
|
|
35
|
+
export function noteRespondDoorSwitch(reading, logTag, approvalId) {
|
|
36
|
+
try {
|
|
37
|
+
hostLog('debug', `${logTag}: respond answered not_found.route for ${approvalId} — the same decision goes once to the ask decision door`);
|
|
38
|
+
if (typeof reading !== 'object' || reading === null)
|
|
39
|
+
return;
|
|
40
|
+
const baseUrl = readOwnDataNonEmptyString(reading, 'baseUrl');
|
|
41
|
+
if (baseUrl !== undefined)
|
|
42
|
+
void refreshEngineCapsOnce(baseUrl, { withinMs: 0 });
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function approvalDecisionVerbOf(reading) {
|
|
48
|
+
if (typeof reading !== 'object' || reading === null)
|
|
49
|
+
return undefined;
|
|
50
|
+
const holder = guardedRead(() => readOwnDataValue(reading, 'verbs'));
|
|
51
|
+
if (typeof holder !== 'object' || holder === null)
|
|
52
|
+
return undefined;
|
|
53
|
+
try {
|
|
54
|
+
const fn = holder['decideAsk'];
|
|
55
|
+
if (typeof fn !== 'function')
|
|
56
|
+
return undefined;
|
|
57
|
+
return (taskId, askId, body, opts) => {
|
|
58
|
+
try {
|
|
59
|
+
return Promise.resolve(fn.call(holder, taskId, askId, body, opts));
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
return Promise.reject(e);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export const ROUTE_MISSING_WIRING_HINT = 'approval answer got 404 not_found.route on the respond door and could not be retried on the ask decision door — pass approvalDecisionDoorSource(baseUrl, client.toolApprovals) as approvalDecisionDoor to sendHitlDecision deps and to the live-card lane options (and call invalidateEngineCaps(baseUrl) after an engine switch)';
|
|
71
|
+
const IDEMPOTENCY_KEY_MAX = 255;
|
|
72
|
+
export function hitlAskIdempotencyKey(askId, verdict) {
|
|
73
|
+
const head = `sema-ask:${askId}`;
|
|
74
|
+
const suffix = `:${verdict}`;
|
|
75
|
+
return head.length + suffix.length <= IDEMPOTENCY_KEY_MAX ? `${head}${suffix}` : `${head.slice(0, IDEMPOTENCY_KEY_MAX - suffix.length)}${suffix}`;
|
|
76
|
+
}
|
|
77
|
+
void true;
|
|
@@ -56,6 +56,8 @@ export interface ApprovalParkingFacts {
|
|
|
56
56
|
readonly approvalId: string;
|
|
57
57
|
readonly retryAfterMs?: number;
|
|
58
58
|
}
|
|
59
|
+
export declare function retryAfterMsOf(e: {} | null | undefined): number | undefined;
|
|
60
|
+
export declare function askDoorParkingOfThrown(reading: TransportReading, approvalId: string): ApprovalParkingFacts | undefined;
|
|
59
61
|
export declare function approvalParkingRetryWait(view: ApprovalParkingFacts, tally: SendTally, signal: AbortSignal | undefined): number | undefined;
|
|
60
62
|
export type ApprovalDelivery = {
|
|
61
63
|
readonly kind: 'answered';
|
|
@@ -81,7 +83,11 @@ export interface ApprovalDeliveryInput<B extends {
|
|
|
81
83
|
readonly ladder: AttributionLadderState;
|
|
82
84
|
readonly onLadderStep?: (step: AttributionLadderStep) => void;
|
|
83
85
|
readonly parkingOf: (raw: {} | null | undefined) => ApprovalParkingFacts | undefined;
|
|
86
|
+
readonly parkingOfThrown?: (reading: TransportReading) => ApprovalParkingFacts | undefined;
|
|
84
87
|
}
|
|
88
|
+
export declare function respondRouteGoneOf(delivery: Extract<ApprovalDelivery, {
|
|
89
|
+
kind: 'thrown';
|
|
90
|
+
}>): boolean;
|
|
85
91
|
export declare function deliverApprovalDecision<B extends {
|
|
86
92
|
settledBy?: unknown;
|
|
87
93
|
decidedBy?: unknown;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { abortableSleep } from '../abortableSleep.js';
|
|
2
2
|
import { classifyTurnWireError } from '../wireErrorTriage.js';
|
|
3
|
-
import { ownReadingSlot, triageReadingOf } from '../engineWireFor.js';
|
|
4
|
-
import { isSlotCarrier, readThrownSlot, THROWN_SLOT_UNREADABLE } from '../wireFailureShape.js';
|
|
5
|
-
import {
|
|
3
|
+
import { effectiveCodeOf, ownReadingSlot, triageReadingOf } from '../engineWireFor.js';
|
|
4
|
+
import { isSlotCarrier, readErrorNumber, readThrownSlot, THROWN_SLOT_UNREADABLE } from '../wireFailureShape.js';
|
|
5
|
+
import { ASK_PARKING_RETRY, ROUTE_NOT_FOUND } from '../engineErrorCodes.js';
|
|
6
|
+
import { carrierReadOf, deliverWithAttributionLadder, isLocalSafetyStop, localCodeReadOf, topCauseReadOf } from './attributionLadder.js';
|
|
6
7
|
import { GATE_FAILURE_CODES, refusalCarrierOf, safetyStopHeadOf } from './hitlBridge.js';
|
|
7
8
|
const RETRY_DELAYS_MS = Object.freeze([500, 1_000, 2_000]);
|
|
8
9
|
export const RETRY_BUDGET_MS = 8_000;
|
|
@@ -139,6 +140,29 @@ function settledReadingOf(e, reading, heads) {
|
|
|
139
140
|
export function directFailureReadingOf(e, triaged, topHead) {
|
|
140
141
|
return settledReadingOf(e, triaged, localStopHeadsOf(e, triaged, topHead));
|
|
141
142
|
}
|
|
143
|
+
export function retryAfterMsOf(e) {
|
|
144
|
+
if (!isSlotCarrier(e))
|
|
145
|
+
return undefined;
|
|
146
|
+
const ms = readErrorNumber(e, 'retryAfterMs');
|
|
147
|
+
if (ms !== undefined && Number.isFinite(ms) && ms >= 0)
|
|
148
|
+
return ms;
|
|
149
|
+
const sec = readErrorNumber(e, 'retryAfterSec');
|
|
150
|
+
return sec !== undefined && Number.isFinite(sec) && sec >= 0 ? sec * 1000 : undefined;
|
|
151
|
+
}
|
|
152
|
+
export function askDoorParkingOfThrown(reading, approvalId) {
|
|
153
|
+
try {
|
|
154
|
+
if (ownReadingSlot(reading, 'safetyStop') === true)
|
|
155
|
+
return undefined;
|
|
156
|
+
const answered = ownReadingSlot(reading, 'answered');
|
|
157
|
+
if (answered === undefined || effectiveCodeOf(answered.code) !== ASK_PARKING_RETRY)
|
|
158
|
+
return undefined;
|
|
159
|
+
const retryAfterMs = retryAfterMsOf(answered.layer);
|
|
160
|
+
return Object.freeze({ approvalId, ...(retryAfterMs !== undefined ? { retryAfterMs } : {}) });
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
142
166
|
export function approvalParkingRetryWait(view, tally, signal) {
|
|
143
167
|
const w = ownReadingSlot(view, 'retryAfterMs');
|
|
144
168
|
if (typeof w !== 'number' || !Number.isFinite(w) || w < 0)
|
|
@@ -153,6 +177,15 @@ function thrownFacts(e, signal, tally) {
|
|
|
153
177
|
const reading = settledTransportReadingOf(e, signal, tally);
|
|
154
178
|
return { reading, transportExhausted: reading.cls === 'transport' && tally.retried, afterRetry: tally.retried };
|
|
155
179
|
}
|
|
180
|
+
export function respondRouteGoneOf(delivery) {
|
|
181
|
+
if (delivery.afterRetry)
|
|
182
|
+
return false;
|
|
183
|
+
const reading = delivery.reading;
|
|
184
|
+
const answered = ownReadingSlot(reading, 'answered');
|
|
185
|
+
if (answered === undefined || answered.status !== 404 || effectiveCodeOf(answered.code) !== ROUTE_NOT_FOUND)
|
|
186
|
+
return false;
|
|
187
|
+
return !isLocalSafetyStop(carrierReadOf(answered.layer, reading), reading);
|
|
188
|
+
}
|
|
156
189
|
export async function deliverApprovalDecision(input) {
|
|
157
190
|
const { signal, tally } = input;
|
|
158
191
|
let last = input.body;
|
|
@@ -164,7 +197,21 @@ export async function deliverApprovalDecision(input) {
|
|
|
164
197
|
}, signal, input.ladder, input.onLadderStep, { kind: 'delivered', readingOf: (thrown) => deliveryReadingOf(thrown, tally) }));
|
|
165
198
|
}
|
|
166
199
|
catch (e) {
|
|
167
|
-
|
|
200
|
+
const facts = thrownFacts(e, signal, tally);
|
|
201
|
+
const failed = { kind: 'thrown', error: e, ...facts };
|
|
202
|
+
const readThrown = input.parkingOfThrown;
|
|
203
|
+
if (readThrown === undefined)
|
|
204
|
+
return failed;
|
|
205
|
+
let parkedThrown;
|
|
206
|
+
try {
|
|
207
|
+
parkedThrown = readThrown(facts.reading);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
parkedThrown = undefined;
|
|
211
|
+
}
|
|
212
|
+
if (parkedThrown === undefined || parkedThrown.approvalId !== input.approvalId)
|
|
213
|
+
return failed;
|
|
214
|
+
return parkingResend(input, last, parkedThrown, failed);
|
|
168
215
|
}
|
|
169
216
|
let parking;
|
|
170
217
|
try {
|
|
@@ -175,14 +222,17 @@ export async function deliverApprovalDecision(input) {
|
|
|
175
222
|
}
|
|
176
223
|
if (parking === undefined || parking.approvalId !== input.approvalId)
|
|
177
224
|
return { kind: 'answered', raw };
|
|
225
|
+
return parkingResend(input, last, parking, { kind: 'answered', raw });
|
|
226
|
+
}
|
|
227
|
+
async function parkingResend(input, resent, parking, first) {
|
|
228
|
+
const { signal, tally } = input;
|
|
178
229
|
const wait = approvalParkingRetryWait(parking, tally, signal);
|
|
179
230
|
if (wait === undefined)
|
|
180
|
-
return
|
|
231
|
+
return first;
|
|
181
232
|
await sleepFor(wait, signal);
|
|
182
233
|
if (isAborted(signal) || budgetSpent(tally, 0))
|
|
183
|
-
return
|
|
234
|
+
return first;
|
|
184
235
|
tally.retried = false;
|
|
185
|
-
const resent = last;
|
|
186
236
|
try {
|
|
187
237
|
const raw2 = (await withTransportRetry((sig) => input.send(resent, sig), signal, tally, true));
|
|
188
238
|
return { kind: 'answered', raw: raw2, parkingRetried: true };
|