@sema-agent/client-core 0.95.0 → 0.96.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 +180 -0
- package/README.md +49 -25
- package/dist/adapt/arms.js +7 -1
- package/dist/adapt/toolCards.d.ts +1 -0
- package/dist/adapt/toolCards.js +16 -3
- package/dist/adapt/wireShapes.js +2 -16
- package/dist/adapter/activeRunSelfHeal.d.ts +4 -0
- package/dist/adapter/activeRunSelfHeal.js +30 -4
- package/dist/adapter/downstream/eventToSdkMessage.js +11 -3
- package/dist/adapter/downstream/terminalToSdkResult.js +8 -9
- package/dist/adapter/runStream.js +30 -47
- package/dist/agentSession/engineAgentRegistryRead.js +5 -4
- package/dist/agentSession/engineSessionBackgroundStop.js +22 -15
- package/dist/agentsWireCaps.js +13 -2
- package/dist/callerCwdCapability.d.ts +0 -4
- package/dist/callerCwdCapability.js +0 -6
- package/dist/centerWiringCapability.d.ts +0 -4
- package/dist/centerWiringCapability.js +0 -6
- package/dist/classifierVerdictWire.js +4 -16
- package/dist/clientSlice.js +1 -1
- package/dist/cloudConfigWireCaps.d.ts +3 -3
- package/dist/cloudConfigWireCaps.js +65 -45
- package/dist/coreValuePorts.d.ts +11 -7
- package/dist/coreValuePorts.js +5 -8
- package/dist/credentialProvenance.d.ts +28 -0
- package/dist/credentialProvenance.js +76 -0
- package/dist/decideFailureNote.js +3 -1
- package/dist/decideReceipt.js +7 -10
- package/dist/declared/supportFloorDeclared.js +3 -3
- package/dist/displayUntrusted.d.ts +7 -3
- package/dist/displayUntrusted.js +356 -60
- package/dist/engineCapsCache.d.ts +6 -0
- package/dist/engineCapsCache.js +226 -32
- package/dist/engineDirectory.d.ts +79 -0
- package/dist/engineDirectory.js +186 -0
- package/dist/engineErrorCodes.d.ts +14 -1
- package/dist/engineErrorCodes.js +37 -15
- package/dist/engineNoticeCodes.d.ts +17 -0
- package/dist/engineNoticeCodes.js +57 -3
- package/dist/engineWordRenames.d.ts +8 -0
- package/dist/engineWordRenames.js +13 -0
- package/dist/fileHistoryCaptureCapability.d.ts +14 -0
- package/dist/fileHistoryCaptureCapability.js +70 -0
- package/dist/fleet/bgSubagentRow.d.ts +15 -0
- package/dist/fleet/bgSubagentRow.js +67 -0
- package/dist/fleet/fleetLedger.d.ts +1 -0
- package/dist/fleet/fleetLedger.js +103 -89
- package/dist/fleet/fleetProjection.d.ts +1 -0
- package/dist/fleet/fleetProjection.js +10 -3
- package/dist/fleetTaskDesc.js +4 -17
- package/dist/gateOutcome.d.ts +12 -1
- package/dist/gateOutcome.js +58 -1
- package/dist/gateVocabulary.d.ts +7 -2
- package/dist/gateVocabulary.js +5 -1
- package/dist/generated/engineAskFaceTables.js +1 -1
- package/dist/generated/engineFactTables.d.ts +5 -3
- package/dist/generated/engineFactTables.js +17 -4
- package/dist/generated/engineNoticeTables.js +12 -3
- package/dist/generated/settingsSchemaFns.d.ts +3 -0
- package/dist/generated/settingsSchemaFns.js +53 -0
- package/dist/generated/toolNameTables.d.ts +1 -0
- package/dist/generated/toolNameTables.js +1 -0
- package/dist/hitl/approvalEndingCause.d.ts +2 -0
- package/dist/hitl/approvalEndingCause.js +31 -21
- package/dist/hitl/askParkRowRouting.js +1 -1
- package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
- package/dist/hitl/automaticAnswerVerdict.js +13 -8
- package/dist/hitl/decisionConflict.js +0 -2
- package/dist/hitl/decisionRefusal.d.ts +8 -0
- package/dist/hitl/decisionRefusal.js +48 -14
- package/dist/hitl/frameRouter.d.ts +0 -1
- package/dist/hitl/frameRouter.js +6 -14
- package/dist/hitl/hitlBridge.d.ts +6 -2
- package/dist/hitl/hitlBridge.js +28 -7
- package/dist/hitl/localAllowRule.d.ts +1 -0
- package/dist/hitl/localAllowRule.js +2 -2
- package/dist/hitl/noAllowCause.d.ts +2 -1
- package/dist/hitl/noAllowCause.js +3 -2
- package/dist/hitl/parkResolver.d.ts +1 -0
- package/dist/hitl/parkResolver.js +8 -54
- package/dist/hitl/parkRowBirthWait.d.ts +1 -0
- package/dist/hitl/parkRowBirthWait.js +2 -2
- package/dist/hitl/planReviewWire.d.ts +1 -0
- package/dist/hitl/planReviewWire.js +98 -20
- package/dist/hitl/replayedApproval.d.ts +1 -0
- package/dist/hitl/replayedApproval.js +14 -0
- package/dist/hitl/sessionPolicyDeliverable.js +8 -6
- package/dist/hitl/standingRows.d.ts +18 -0
- package/dist/hitl/standingRows.js +127 -0
- package/dist/hitl/toolApprovalWire.d.ts +15 -3
- package/dist/hitl/toolApprovalWire.js +238 -25
- package/dist/hookMatcherVerdict.d.ts +1 -9
- package/dist/hookMatcherVerdict.js +21 -61
- package/dist/hooksWireCaps.js +2 -2
- package/dist/hostCallbackGuard.d.ts +1 -0
- package/dist/hostCallbackGuard.js +6 -0
- package/dist/index.d.ts +21 -9
- package/dist/index.js +17 -8
- package/dist/mcpProbeCapability.d.ts +0 -4
- package/dist/mcpProbeCapability.js +0 -6
- package/dist/memoryComplianceCapability.d.ts +0 -4
- package/dist/memoryComplianceCapability.js +0 -6
- package/dist/memoryOriginCapability.d.ts +0 -4
- package/dist/memoryOriginCapability.js +0 -6
- package/dist/memorySpecWire.js +2 -2
- package/dist/modelIdentityResolvability.d.ts +2 -2
- package/dist/modelIdentityResolvability.js +12 -3
- package/dist/noticePresentation.d.ts +68 -0
- package/dist/noticePresentation.js +247 -0
- package/dist/outputStyleSeatCapability.d.ts +0 -1
- package/dist/outputStyleSeatCapability.js +0 -3
- package/dist/ownKey.d.ts +2 -0
- package/dist/ownKey.js +18 -0
- package/dist/peerFrames.js +13 -9
- package/dist/permissionModesCapability.d.ts +0 -4
- package/dist/permissionModesCapability.js +0 -6
- package/dist/permissionRuleIssue.js +0 -3
- package/dist/permissionsDeclaration.d.ts +2 -0
- package/dist/permissionsDeclaration.js +56 -41
- package/dist/printPermissionOutcome.d.ts +12 -0
- package/dist/printPermissionOutcome.js +50 -0
- package/dist/printToolResultFrame.d.ts +1 -0
- package/dist/printToolResultFrame.js +5 -9
- package/dist/promptBlockedCopy.d.ts +5 -0
- package/dist/promptBlockedCopy.js +35 -0
- package/dist/protocolSegment.d.ts +9 -6
- package/dist/protocolSegment.js +31 -21
- package/dist/providerRejection.d.ts +4 -0
- package/dist/providerRejection.js +75 -14
- package/dist/replayBody.d.ts +12 -0
- package/dist/replayBody.js +25 -0
- package/dist/resumeRefusalCopy.js +5 -21
- package/dist/retryStatus.d.ts +9 -1
- package/dist/retryStatus.js +9 -2
- package/dist/rewindArchiveCapability.d.ts +3 -13
- package/dist/rewindArchiveCapability.js +7 -58
- package/dist/rewindArchiveReading.d.ts +15 -0
- package/dist/rewindArchiveReading.js +56 -0
- package/dist/ruleCompileVerdict.d.ts +9 -1
- package/dist/ruleCompileVerdict.js +29 -4
- package/dist/runTerminal.d.ts +15 -1
- package/dist/runTerminal.js +56 -8
- package/dist/sandboxWire.js +13 -0
- package/dist/sealedKeyCapability.d.ts +0 -4
- package/dist/sealedKeyCapability.js +0 -6
- package/dist/seam.js +4 -2
- package/dist/seatContract.d.ts +73 -11
- package/dist/seatContract.js +195 -14
- package/dist/sessionReadRedactionCapability.d.ts +0 -4
- package/dist/sessionReadRedactionCapability.js +0 -6
- package/dist/sessionSyncFileHistory.d.ts +4 -0
- package/dist/sessionSyncFileHistory.js +66 -0
- package/dist/sessionSyncFileHistoryCapability.d.ts +0 -1
- package/dist/sessionSyncFileHistoryCapability.js +0 -3
- package/dist/skillToolListsCapability.d.ts +0 -1
- package/dist/skillToolListsCapability.js +0 -3
- package/dist/steerPriorityCapability.d.ts +0 -1
- package/dist/steerPriorityCapability.js +0 -3
- package/dist/storePostureCapability.d.ts +0 -4
- package/dist/storePostureCapability.js +0 -6
- package/dist/subagent/engineSubagentSteer.js +7 -5
- package/dist/subagentContentStore.js +4 -15
- package/dist/supportFloor.d.ts +1 -0
- package/dist/supportFloor.js +13 -9
- package/dist/systemReminderTag.d.ts +1 -0
- package/dist/systemReminderTag.js +8 -0
- package/dist/toolHistoryMismatch.d.ts +0 -3
- package/dist/toolHistoryMismatch.js +3 -7
- package/dist/toolResult.d.ts +1 -1
- package/dist/toolResult.js +2 -1
- package/dist/toolResultContent.d.ts +2 -0
- package/dist/toolResultContent.js +63 -0
- package/dist/usageAnalytics.js +40 -16
- package/dist/userInvokedSkillsCapability.d.ts +0 -1
- package/dist/userInvokedSkillsCapability.js +0 -3
- package/dist/wireErrorTriage.d.ts +2 -0
- package/dist/wireErrorTriage.js +34 -5
- package/dist/wireFailureShape.d.ts +19 -2
- package/dist/wireFailureShape.js +55 -50
- package/docs/INTEGRATION-CLIENTS.md +1262 -17
- package/package.json +5 -5
|
@@ -14,7 +14,7 @@ export const CORE_ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
14
14
|
"config.artifact_host_invalid",
|
|
15
15
|
"config.task_root_not_canonical",
|
|
16
16
|
"config.write_protection_target_view_absent",
|
|
17
|
-
"config.
|
|
17
|
+
"config.path_view_unresolved",
|
|
18
18
|
"config.execution_env_capability_invalid",
|
|
19
19
|
"config.tool_card_undeclared",
|
|
20
20
|
"config.tool_face_undeclared",
|
|
@@ -66,6 +66,9 @@ export const CORE_ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
66
66
|
"route.base_url_changed_key_unchanged",
|
|
67
67
|
"route.response_model_mismatch",
|
|
68
68
|
"route.tool_call_id_folded",
|
|
69
|
+
"route.finish_reason_unknown",
|
|
70
|
+
"route.stream_end_ignored",
|
|
71
|
+
"route.extra_body_reserved_dropped",
|
|
69
72
|
"context.tool_turn_repaired",
|
|
70
73
|
"task.user_steer_undrained",
|
|
71
74
|
"task.user_followup_undrained",
|
|
@@ -103,7 +106,7 @@ export const CORE_NOTICE_AUDIENCE = Object.freeze({
|
|
|
103
106
|
"config.artifact_host_invalid": "operator",
|
|
104
107
|
"config.task_root_not_canonical": "operator",
|
|
105
108
|
"config.write_protection_target_view_absent": "operator",
|
|
106
|
-
"config.
|
|
109
|
+
"config.path_view_unresolved": "operator",
|
|
107
110
|
"config.execution_env_capability_invalid": "operator",
|
|
108
111
|
"config.tool_card_undeclared": "operator",
|
|
109
112
|
"config.tool_face_undeclared": "operator",
|
|
@@ -155,6 +158,9 @@ export const CORE_NOTICE_AUDIENCE = Object.freeze({
|
|
|
155
158
|
"route.base_url_changed_key_unchanged": "operator",
|
|
156
159
|
"route.response_model_mismatch": "operator",
|
|
157
160
|
"route.tool_call_id_folded": "operator",
|
|
161
|
+
"route.finish_reason_unknown": "user",
|
|
162
|
+
"route.stream_end_ignored": "user",
|
|
163
|
+
"route.extra_body_reserved_dropped": "operator",
|
|
158
164
|
"context.tool_turn_repaired": "operator",
|
|
159
165
|
"task.user_steer_undrained": "user",
|
|
160
166
|
"task.user_followup_undrained": "user",
|
|
@@ -198,7 +204,7 @@ export const CORE_NOTICE_MINTER = Object.freeze({
|
|
|
198
204
|
"config.artifact_host_invalid": "engine",
|
|
199
205
|
"config.task_root_not_canonical": "engine",
|
|
200
206
|
"config.write_protection_target_view_absent": "engine",
|
|
201
|
-
"config.
|
|
207
|
+
"config.path_view_unresolved": "engine",
|
|
202
208
|
"config.execution_env_capability_invalid": "engine",
|
|
203
209
|
"config.tool_card_undeclared": "engine",
|
|
204
210
|
"config.tool_face_undeclared": "engine",
|
|
@@ -250,6 +256,9 @@ export const CORE_NOTICE_MINTER = Object.freeze({
|
|
|
250
256
|
"route.base_url_changed_key_unchanged": "engine",
|
|
251
257
|
"route.response_model_mismatch": "engine",
|
|
252
258
|
"route.tool_call_id_folded": "engine",
|
|
259
|
+
"route.finish_reason_unknown": "engine",
|
|
260
|
+
"route.stream_end_ignored": "engine",
|
|
261
|
+
"route.extra_body_reserved_dropped": "engine",
|
|
253
262
|
"context.tool_turn_repaired": "engine",
|
|
254
263
|
"task.user_steer_undrained": "engine",
|
|
255
264
|
"task.user_followup_undrained": "engine",
|
|
@@ -5,6 +5,9 @@ export type SettingsAtMention = {
|
|
|
5
5
|
readonly mode: 'only';
|
|
6
6
|
readonly models: readonly string[];
|
|
7
7
|
};
|
|
8
|
+
export type SettingsHookMatcherForm = "literal" | "regex" | "invalid";
|
|
8
9
|
export declare const SETTINGS_IS_LOOPBACK_AUTH_HOST: (host: string) => boolean;
|
|
9
10
|
export declare const SETTINGS_IS_HIERARCHICAL_URL: (raw: string) => boolean;
|
|
10
11
|
export declare const SETTINGS_NARROW_AT_MENTION: (atMention: SettingsAtMention, available: ReadonlySet<string>) => SettingsAtMention;
|
|
12
|
+
export declare const SETTINGS_HOOK_MATCHER_SELECTS: (matcher: string | undefined, name: string) => boolean | 'invalid';
|
|
13
|
+
export declare const SETTINGS_HOOK_MATCHER_FORM: (matcher: string | undefined) => SettingsHookMatcherForm;
|
|
@@ -20,3 +20,56 @@ export const SETTINGS_NARROW_AT_MENTION = function narrowAtMention(atMention, av
|
|
|
20
20
|
return atMention;
|
|
21
21
|
return kept.length > 0 ? { mode: "only", models: kept } : { mode: "none" };
|
|
22
22
|
};
|
|
23
|
+
const HOOK_MATCHER_NAME_LIST = /^[a-zA-Z0-9_|, -]+$/;
|
|
24
|
+
const readHookMatcher = function readHookMatcher(matcher) {
|
|
25
|
+
if (matcher === undefined || matcher === "" || matcher === "*")
|
|
26
|
+
return { form: "all" };
|
|
27
|
+
if (HOOK_MATCHER_NAME_LIST.test(matcher)) {
|
|
28
|
+
return {
|
|
29
|
+
form: "names",
|
|
30
|
+
names: matcher
|
|
31
|
+
.split(/[|,]/)
|
|
32
|
+
.map((part) => part.trim())
|
|
33
|
+
.filter((part) => part !== ""),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return { form: "regex", pattern: new RegExp(matcher) };
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { form: "invalid" };
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export const SETTINGS_HOOK_MATCHER_SELECTS = function hookMatcherSelects(matcher, name) {
|
|
44
|
+
const r = readHookMatcher(matcher);
|
|
45
|
+
switch (r.form) {
|
|
46
|
+
case "all":
|
|
47
|
+
return true;
|
|
48
|
+
case "names":
|
|
49
|
+
return r.names.includes(name);
|
|
50
|
+
case "regex":
|
|
51
|
+
return r.pattern.test(name);
|
|
52
|
+
case "invalid":
|
|
53
|
+
return "invalid";
|
|
54
|
+
default: {
|
|
55
|
+
const unhandled = r;
|
|
56
|
+
throw new Error(`hookMatcherSelects: unhandled matcher reading ${JSON.stringify(unhandled)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
export const SETTINGS_HOOK_MATCHER_FORM = function hookMatcherForm(matcher) {
|
|
61
|
+
const r = readHookMatcher(matcher);
|
|
62
|
+
switch (r.form) {
|
|
63
|
+
case "all":
|
|
64
|
+
case "names":
|
|
65
|
+
return "literal";
|
|
66
|
+
case "regex":
|
|
67
|
+
return "regex";
|
|
68
|
+
case "invalid":
|
|
69
|
+
return "invalid";
|
|
70
|
+
default: {
|
|
71
|
+
const unhandled = r;
|
|
72
|
+
throw new Error(`hookMatcherForm: unhandled matcher reading ${JSON.stringify(unhandled)}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
@@ -2,4 +2,6 @@ import type { ApprovalSettleCause, AskRowEndingCause } from '@sema-agent/sdk';
|
|
|
2
2
|
export declare const ASK_ROW_ENDING_CAUSE_WORDS: readonly ["routing_failure_fail_closed", "run_cancelled", "run_exited", "batch_aborted", "batch_sibling_withdrawn", "batch_bound_elsewhere", "adhoc_leg_no_durable_domain", "orphan_ttl_exceeded", "crashed_before_park", "shutdown_before_park"];
|
|
3
3
|
export declare function askRowEndingCauseFor(state: 'VOID' | 'DENIED' | undefined, word: unknown): AskRowEndingCause | undefined;
|
|
4
4
|
export declare function approvalCompleteCauseOf(frame: unknown): ApprovalSettleCause | undefined;
|
|
5
|
+
export declare function notPendingCauseReading(err: object): 'ended' | 'other_replica' | 'none';
|
|
6
|
+
export declare function refusalCauseWordOf(err: object): string | undefined;
|
|
5
7
|
export declare function notPendingCauseSaysEnded(err: object): boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ASK_GONE_DENIED_WORD } from '../engineErrorCodes.js';
|
|
2
2
|
import { SDK_ASK_ROW_ENDING_CAUSES } from '../generated/sdkFactTables.js';
|
|
3
|
+
import { readThrownExtra } from '../wireFailureShape.js';
|
|
3
4
|
export const ASK_ROW_ENDING_CAUSE_WORDS = SDK_ASK_ROW_ENDING_CAUSES;
|
|
4
5
|
const _askRowCausePin = true;
|
|
5
6
|
void _askRowCausePin;
|
|
@@ -25,37 +26,46 @@ export function askRowEndingCauseFor(state, word) {
|
|
|
25
26
|
return word !== DENIED_GROUP_WORD ? word : undefined;
|
|
26
27
|
return undefined;
|
|
27
28
|
}
|
|
29
|
+
function ownDataSlot(o, key) {
|
|
30
|
+
try {
|
|
31
|
+
const slot = Object.getOwnPropertyDescriptor(o, key);
|
|
32
|
+
return slot !== undefined && Object.hasOwn(slot, 'value') ? slot.value : undefined;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
28
38
|
export function approvalCompleteCauseOf(frame) {
|
|
29
39
|
try {
|
|
30
40
|
if (typeof frame !== 'object' || frame === null)
|
|
31
41
|
return undefined;
|
|
32
|
-
|
|
33
|
-
if (typeSlot === undefined || !Object.hasOwn(typeSlot, 'value') || typeSlot.value !== 'tool_approval_complete')
|
|
42
|
+
if (ownDataSlot(frame, 'type') !== 'tool_approval_complete')
|
|
34
43
|
return undefined;
|
|
35
|
-
const
|
|
36
|
-
if (causeSlot === undefined || !Object.hasOwn(causeSlot, 'value'))
|
|
37
|
-
return undefined;
|
|
38
|
-
const word = causeSlot.value;
|
|
44
|
+
const word = ownDataSlot(frame, 'cause');
|
|
39
45
|
return isApprovalSettleCause(word) ? word : undefined;
|
|
40
46
|
}
|
|
41
47
|
catch {
|
|
42
48
|
return undefined;
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
|
-
const
|
|
51
|
+
const NOT_PENDING_OTHER_REPLICA_WORDS = Object.freeze({
|
|
52
|
+
unknown_or_other_replica: true,
|
|
53
|
+
});
|
|
54
|
+
export function notPendingCauseReading(err) {
|
|
55
|
+
const word = refusalCauseWordOf(err);
|
|
56
|
+
if (word === undefined)
|
|
57
|
+
return 'none';
|
|
58
|
+
if (isAskRowEndingCause(word))
|
|
59
|
+
return 'ended';
|
|
60
|
+
return Object.hasOwn(NOT_PENDING_OTHER_REPLICA_WORDS, word) ? 'other_replica' : 'none';
|
|
61
|
+
}
|
|
62
|
+
export function refusalCauseWordOf(err) {
|
|
63
|
+
const reading = readThrownExtra(err);
|
|
64
|
+
if (reading.state !== 'object')
|
|
65
|
+
return undefined;
|
|
66
|
+
const word = ownDataSlot(reading.value, 'cause');
|
|
67
|
+
return typeof word === 'string' && word !== '' ? word : undefined;
|
|
68
|
+
}
|
|
46
69
|
export function notPendingCauseSaysEnded(err) {
|
|
47
|
-
|
|
48
|
-
const extraSlot = Object.getOwnPropertyDescriptor(err, 'extra');
|
|
49
|
-
const extra = extraSlot !== undefined && Object.hasOwn(extraSlot, 'value') ? extraSlot.value : undefined;
|
|
50
|
-
if (typeof extra !== 'object' || extra === null || Array.isArray(extra))
|
|
51
|
-
return false;
|
|
52
|
-
const causeSlot = Object.getOwnPropertyDescriptor(extra, 'cause');
|
|
53
|
-
if (causeSlot === undefined || !Object.hasOwn(causeSlot, 'value'))
|
|
54
|
-
return false;
|
|
55
|
-
const word = causeSlot.value;
|
|
56
|
-
return isAskRowEndingCause(word) || (typeof word === 'string' && LEGACY_NOT_PENDING_ENDED_WORDS.has(word));
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
70
|
+
return notPendingCauseReading(err) === 'ended';
|
|
61
71
|
}
|
|
@@ -50,7 +50,7 @@ export function askParkRowArm(row) {
|
|
|
50
50
|
const rowHasToolName = typeof row.toolName === 'string' && row.toolName.trim().length > 0;
|
|
51
51
|
return askQuestionsFromPending(row) !== null || !rowHasToolName ? 'question' : 'tool-gate';
|
|
52
52
|
}
|
|
53
|
-
const GENERIC_DECISION_GATE_KINDS = ['human'
|
|
53
|
+
const GENERIC_DECISION_GATE_KINDS = ['human'];
|
|
54
54
|
export function askParkRowStillPending(rows, identity) {
|
|
55
55
|
return rows.some(r => identity.gatedCallId !== undefined
|
|
56
56
|
? (r.toolCallId ?? r.boundCallId ?? undefined) === identity.gatedCallId
|
|
@@ -19,7 +19,7 @@ export type AutomaticAnswerVerdict = {
|
|
|
19
19
|
readonly kind: 'unknown';
|
|
20
20
|
readonly why: AutomaticAnswerUnknownWhy;
|
|
21
21
|
};
|
|
22
|
-
export declare const MODE_ARBITRATION: Readonly<Record<"hook" | "write_protection" | "mode" | "rule_unreadable" | "
|
|
22
|
+
export declare const MODE_ARBITRATION: Readonly<Record<"hook" | "write_protection" | "mode" | "rule_unreadable" | "target_unresolved" | "content_question" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "unverifiable_shell_text" | "transcript_write" | "org_rule" | "governance_unavailable" | "ask_rule" | "denial_limit_fallback" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", CoreModeArbitrationRow>>;
|
|
23
23
|
export declare function foldModeArbitrationRow(classes: Iterable<string>, askFace?: AutomaticAnswerAskFace): CoreModeArbitrationRow;
|
|
24
24
|
export declare function automaticAnswerVerdict(card: object | null | undefined, lane: AutomaticAnswerLane, facts?: AutomaticAnswerFacts | null): AutomaticAnswerVerdict;
|
|
25
25
|
export declare function mandatedApprovalDetailOf(card: object | null | undefined): string;
|
|
@@ -3,6 +3,7 @@ import { CORE_RELEASABLE_ASK_CLASSES_BY_FACE, CORE_SHELL_FACE_TOOL_NAMES } from
|
|
|
3
3
|
import { mandatedApprovalDetail } from '../gateVocabulary.js';
|
|
4
4
|
import { readAskOrigin, readOwnNonEmptyString } from '../ownKey.js';
|
|
5
5
|
import { readAskClassPairState } from './askClassPair.js';
|
|
6
|
+
import { currentEngineWord } from '../engineWordRenames.js';
|
|
6
7
|
import { approvalAllowRuleShadowed } from './allowRuleShadow.js';
|
|
7
8
|
import { approvalIsMandated, readApprovalMandate } from './toolApprovalWire.js';
|
|
8
9
|
export const MODE_ARBITRATION = CORE_MODE_ARBITRATION;
|
|
@@ -15,9 +16,10 @@ const ORIGIN_IMPLIES_CLASS = Object.freeze({
|
|
|
15
16
|
ask_rule: 'ask_rule',
|
|
16
17
|
denial_limit_fallback: 'denial_limit_fallback',
|
|
17
18
|
});
|
|
18
|
-
function mandateWordClass(
|
|
19
|
-
if (
|
|
19
|
+
function mandateWordClass(raw) {
|
|
20
|
+
if (raw === 'operator_always')
|
|
20
21
|
return 'shell_floor';
|
|
22
|
+
const word = currentEngineWord(raw);
|
|
21
23
|
return Object.hasOwn(TABLE, word) ? word : undefined;
|
|
22
24
|
}
|
|
23
25
|
const ORDINARY_ROW = CORE_MODE_ARBITRATION.ordinary;
|
|
@@ -41,7 +43,8 @@ export function foldModeArbitrationRow(classes, askFace) {
|
|
|
41
43
|
let sandboxAdmission = true;
|
|
42
44
|
let ruleMayClear = true;
|
|
43
45
|
let hookAskFloor = false;
|
|
44
|
-
for (const
|
|
46
|
+
for (const raw of classes) {
|
|
47
|
+
const c = typeof raw === 'string' ? currentEngineWord(raw) : raw;
|
|
45
48
|
if (!Object.hasOwn(TABLE, c))
|
|
46
49
|
continue;
|
|
47
50
|
const row = TABLE[c];
|
|
@@ -130,11 +133,12 @@ export function automaticAnswerVerdict(card, lane, facts) {
|
|
|
130
133
|
return UNKNOWN.classes_malformed;
|
|
131
134
|
if (pair.state === 'absent')
|
|
132
135
|
return UNKNOWN.classes_absent;
|
|
133
|
-
const
|
|
136
|
+
const classes = pair.classes.map(currentEngineWord);
|
|
137
|
+
const folded = foldModeArbitrationRow(classes, face);
|
|
134
138
|
let anyKnown = false;
|
|
135
139
|
let firstBlocking;
|
|
136
140
|
let anyUnknown = false;
|
|
137
|
-
for (const c of
|
|
141
|
+
for (const c of classes) {
|
|
138
142
|
if (!Object.hasOwn(TABLE, c)) {
|
|
139
143
|
anyUnknown = true;
|
|
140
144
|
continue;
|
|
@@ -154,7 +158,7 @@ export function automaticAnswerVerdict(card, lane, facts) {
|
|
|
154
158
|
return mustAsk('settings_ask_rule');
|
|
155
159
|
if (matched !== false)
|
|
156
160
|
return UNKNOWN.host_not_reported;
|
|
157
|
-
const releaseReason = CORE_MODE_RELEASE_REASONS.find((r) =>
|
|
161
|
+
const releaseReason = CORE_MODE_RELEASE_REASONS.find((r) => classes.includes(r));
|
|
158
162
|
return releaseReason !== undefined ? Object.freeze({ kind: 'may_answer', releaseReason }) : MAY_ANSWER;
|
|
159
163
|
}
|
|
160
164
|
catch {
|
|
@@ -168,9 +172,10 @@ export function mandatedApprovalDetailOf(card) {
|
|
|
168
172
|
const pair = readAskClassPairState(card);
|
|
169
173
|
if (pair.state !== 'pair')
|
|
170
174
|
return mandatedApprovalDetail();
|
|
171
|
-
|
|
175
|
+
const classes = pair.classes.map(currentEngineWord);
|
|
176
|
+
if (classes.some((c) => !Object.hasOwn(TABLE, c)))
|
|
172
177
|
return mandatedApprovalDetail();
|
|
173
|
-
const row = foldModeArbitrationRow(
|
|
178
|
+
const row = foldModeArbitrationRow(classes);
|
|
174
179
|
if (row.classifierEligible || row.sandboxAdmission)
|
|
175
180
|
return mandatedApprovalDetail();
|
|
176
181
|
return MANDATED_NO_AUTOMATIC_CHECK_DETAIL;
|
|
@@ -34,8 +34,6 @@ export function decisionConflictOf(err) {
|
|
|
34
34
|
}
|
|
35
35
|
if (code === RESUME_OUTCOME_INVALID)
|
|
36
36
|
return field === ANSWER_FIELD ? 'answer_invalid' : undefined;
|
|
37
|
-
if (field === ANSWER_FIELD)
|
|
38
|
-
return 'answer_invalid';
|
|
39
37
|
if (field === undefined)
|
|
40
38
|
return 'row_integrity';
|
|
41
39
|
return BINDING_SEAT_FIELDS.has(field) ? 'binding_mismatch' : undefined;
|
|
@@ -2,6 +2,14 @@ import { type DecisionRefusalConflict } from './decisionConflict.js';
|
|
|
2
2
|
export type DecisionRefusalStanding = 'settled-elsewhere' | 'ambiguous' | 'refused-pending';
|
|
3
3
|
export type { DecisionRefusalConflict } from './decisionConflict.js';
|
|
4
4
|
export declare function decisionRefusalSaysAskGone(err: unknown): boolean;
|
|
5
|
+
export type DecisionRefusalAskReading = 'settled' | 'not_settled' | 'unreadable';
|
|
6
|
+
interface DecisionRefusalAskFacts {
|
|
7
|
+
readonly reading: DecisionRefusalAskReading;
|
|
8
|
+
readonly gateMoved: boolean;
|
|
9
|
+
readonly wordingHint: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function readDecisionRefusalAsk(failure: unknown): DecisionRefusalAskFacts;
|
|
12
|
+
export declare function decisionRefusalAskReading(failure: unknown): DecisionRefusalAskReading;
|
|
5
13
|
export interface DecisionRefusalClassified {
|
|
6
14
|
readonly standing: DecisionRefusalStanding;
|
|
7
15
|
readonly status: number;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ApprovalStaleError, AskDecidedError, ParkedResumeStartupError } from '@sema-agent/sdk';
|
|
2
|
+
import { ASK_GONE, ASK_GONE_PARKED, isDecideAlreadyResolvedCode, isDecideParkMovedCode, refusalRetriableReading, refusalSaysStillPending } from '../engineErrorCodes.js';
|
|
3
|
+
import { notPendingCauseReading } from './approvalEndingCause.js';
|
|
4
|
+
import { GATE_FAILURE_CODES } from './hitlBridge.js';
|
|
5
|
+
import { isAlreadyResolvedGateReason, isCodeClassifiedGateFailure } from './parkResolver.js';
|
|
3
6
|
import { decisionConflictOf } from './decisionConflict.js';
|
|
4
7
|
import { readLivePendingRows } from './livePendingAsk.js';
|
|
5
8
|
import { readToolApprovalRespondRefusal } from './toolApprovalWire.js';
|
|
@@ -7,20 +10,50 @@ const ASK_DECIDED_CODE = 'conflict.ask_decided';
|
|
|
7
10
|
const TRANSIENT_STATUSES = new Set([408, 425, 429, 502, 503, 504]);
|
|
8
11
|
const DISPLAYABLE_CODE = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
9
12
|
export function decisionRefusalSaysAskGone(err) {
|
|
13
|
+
return readDecisionRefusalAsk(err).reading === 'settled';
|
|
14
|
+
}
|
|
15
|
+
const facts = (reading, gateMoved = false, wordingHint = false) => ({ reading, gateMoved, wordingHint });
|
|
16
|
+
export function readDecisionRefusalAsk(failure) {
|
|
17
|
+
if (typeof failure !== 'object' || failure === null)
|
|
18
|
+
return facts('not_settled');
|
|
19
|
+
let flattened, localCode, name, errorCode, status, retriable, text;
|
|
10
20
|
try {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
const o = failure;
|
|
22
|
+
if (Object.hasOwn(failure, 'unreadable') && failure.unreadable === true)
|
|
23
|
+
return facts('unreadable');
|
|
24
|
+
flattened = Object.hasOwn(failure, 'kind') && o.kind === 'failed';
|
|
25
|
+
const closed = (v) => (typeof v === 'string' && GATE_FAILURE_CODES.includes(v) ? v : undefined);
|
|
26
|
+
localCode = flattened ? (closed(o.code) ?? closed(o.safetyCode)) : closed(o.code);
|
|
27
|
+
name = flattened ? undefined : o.name;
|
|
28
|
+
errorCode = o.errorCode;
|
|
29
|
+
status = o.status;
|
|
30
|
+
retriable = refusalRetriableReading(failure);
|
|
31
|
+
text = flattened ? o.reason : String(failure);
|
|
20
32
|
}
|
|
21
33
|
catch {
|
|
22
|
-
return
|
|
34
|
+
return facts('unreadable');
|
|
23
35
|
}
|
|
36
|
+
if (retriable === undefined)
|
|
37
|
+
return facts('unreadable');
|
|
38
|
+
if (localCode !== undefined)
|
|
39
|
+
return facts(localCode === 'no_pending' ? 'settled' : 'not_settled');
|
|
40
|
+
if (name === 'HitlSafetyError')
|
|
41
|
+
return facts('not_settled');
|
|
42
|
+
const code = typeof errorCode === 'string' && errorCode.length > 0 ? errorCode : undefined;
|
|
43
|
+
const st = typeof status === 'number' && Number.isInteger(status) && status >= 400 && status <= 599 ? status : undefined;
|
|
44
|
+
if (code === ASK_DECIDED_CODE || code === ASK_GONE || (st === 410 && code !== ASK_GONE_PARKED))
|
|
45
|
+
return facts('settled');
|
|
46
|
+
if (st === 404 && notPendingCauseReading(failure) === 'ended')
|
|
47
|
+
return facts('settled');
|
|
48
|
+
if (code === undefined && name === AskDecidedError.name)
|
|
49
|
+
return facts('settled');
|
|
50
|
+
const gateMoved = isDecideAlreadyResolvedCode(code) || isDecideParkMovedCode(code) || code === ASK_GONE_PARKED || (code === undefined && name === ApprovalStaleError.name);
|
|
51
|
+
if (gateMoved || st !== undefined || name === ParkedResumeStartupError.name || isCodeClassifiedGateFailure(code, retriable))
|
|
52
|
+
return facts('not_settled', gateMoved);
|
|
53
|
+
return facts('not_settled', false, isAlreadyResolvedGateReason(text));
|
|
54
|
+
}
|
|
55
|
+
export function decisionRefusalAskReading(failure) {
|
|
56
|
+
return readDecisionRefusalAsk(failure).reading;
|
|
24
57
|
}
|
|
25
58
|
export function classifyDecisionRefusal(err) {
|
|
26
59
|
const r = readToolApprovalRespondRefusal(err);
|
|
@@ -29,12 +62,13 @@ export function classifyDecisionRefusal(err) {
|
|
|
29
62
|
return undefined;
|
|
30
63
|
const code = r?.errorCode;
|
|
31
64
|
const errorCode = code !== undefined && DISPLAYABLE_CODE.test(code) ? code : undefined;
|
|
32
|
-
const standing =
|
|
65
|
+
const standing = readDecisionRefusalAsk(err).reading === 'settled'
|
|
33
66
|
? 'settled-elsewhere'
|
|
34
67
|
: status >= 500 && status !== 501
|
|
35
68
|
? 'ambiguous'
|
|
36
69
|
: 'refused-pending';
|
|
37
|
-
const transient = TRANSIENT_STATUSES.has(status) ||
|
|
70
|
+
const transient = TRANSIENT_STATUSES.has(status) ||
|
|
71
|
+
(typeof err === 'object' && err !== null && (refusalSaysStillPending(err) || (status === 404 && notPendingCauseReading(err) === 'other_replica')));
|
|
38
72
|
const conflict = status === 409 ? decisionConflictOf(err) : undefined;
|
|
39
73
|
return Object.freeze({
|
|
40
74
|
standing,
|
|
@@ -17,7 +17,6 @@ export interface PolicyDenyReasonFacts {
|
|
|
17
17
|
export declare function policyDenyReason(facts: PolicyDenyReasonFacts): string;
|
|
18
18
|
export declare const HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE = "[Request interrupted by user for tool use]";
|
|
19
19
|
export declare const ENGINE_ABORT_TOOL_RESULT = "Operation aborted";
|
|
20
|
-
export declare function toolEndOutputText(output: unknown): string | undefined;
|
|
21
20
|
export interface ParkReattachNotice {
|
|
22
21
|
attempt: number;
|
|
23
22
|
max: number;
|
package/dist/hitl/frameRouter.js
CHANGED
|
@@ -2,6 +2,7 @@ import { hostLog } from '../host.js';
|
|
|
2
2
|
import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
|
|
3
3
|
import { isToolApprovalGate, toolNameIsFsWrite, toolNameIsShellExec, isToolApprovalFrame, isFromSubagent, surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
|
|
4
4
|
import { classifierDenyFromToolEnd } from '../classifierVerdictWire.js';
|
|
5
|
+
import { toolEndOutputText } from '../toolResultContent.js';
|
|
5
6
|
import { GATE_PARKED_ERROR_CODE } from '../engineErrorCodes.js';
|
|
6
7
|
import { readRunTerminal, runTerminalCode, runTerminalGateKind, runTerminalGateToolName } from '../runTerminal.js';
|
|
7
8
|
import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
|
|
@@ -23,7 +24,7 @@ export function policyDenyReason(facts) {
|
|
|
23
24
|
const one = typeof facts.toolName === 'string' && SENTENCE_TOOL_NAME.test(facts.toolName) ? `this one "${facts.toolName}" call` : 'this one call';
|
|
24
25
|
const flag = typeof facts.allowRuleFlag === 'string' && SENTENCE_TOOL_NAME.test(facts.allowRuleFlag.replace(/^--?/, '')) ? ` (or ${facts.allowRuleFlag})` : '';
|
|
25
26
|
const budgetKnob = typeof facts.budgetSetting === 'string' && SENTENCE_TOOL_NAME.test(facts.budgetSetting) ? facts.budgetSetting : undefined;
|
|
26
|
-
const cause = facts.cause === 'no_allow_rule' || facts.cause === 'ask_rule' || facts.cause === 'budget' ? facts.cause : 'other';
|
|
27
|
+
const cause = facts.cause === 'no_allow_rule' || facts.cause === 'ask_rule' || facts.cause === 'engine_ask_rule' || facts.cause === 'budget' ? facts.cause : 'other';
|
|
27
28
|
if (facts.lane === 'dont_ask') {
|
|
28
29
|
const mode = 'denied by the dontAsk permission mode';
|
|
29
30
|
switch (cause) {
|
|
@@ -31,6 +32,8 @@ export function policyDenyReason(facts) {
|
|
|
31
32
|
return `${mode}: ${one} needed approval, no permission rule allowed it, and this mode refuses anything that would ask — calls that need no approval still run; an allow rule in settings${flag} can approve calls like this ahead of time`;
|
|
32
33
|
case 'ask_rule':
|
|
33
34
|
return `${mode}: an ask rule matched ${one}, and this mode refuses anything that would ask (an ask rule outranks allow rules) — narrow or remove that ask rule, or use another permission mode to answer it`;
|
|
35
|
+
case 'engine_ask_rule':
|
|
36
|
+
return `${mode}: a rule on the engine side, not one of the user's permission rules, asks about ${one}, and this mode refuses anything that would ask — the user's settings cannot approve it; use another permission mode to answer it`;
|
|
34
37
|
case 'budget':
|
|
35
38
|
return `${mode}: ${one} needed approval, and this session's automatic-approval budget is used up, so saved rules no longer approve calls${budgetKnob !== undefined ? ` — raise ${budgetKnob} before launch to change the cap` : ''}`;
|
|
36
39
|
default:
|
|
@@ -44,6 +47,8 @@ export function policyDenyReason(facts) {
|
|
|
44
47
|
return `${lane}: ${one} needed a person's approval, no permission rule answered it, and ${tail}; a matching allow rule in settings${flag} can approve calls like this ahead of time`;
|
|
45
48
|
case 'ask_rule':
|
|
46
49
|
return `${lane}: an ask rule matched ${one}, so it needed a person's answer, and ${tail}; an ask rule outranks allow rules, so narrow or remove that ask rule, or run the task interactively to answer it`;
|
|
50
|
+
case 'engine_ask_rule':
|
|
51
|
+
return `${lane}: a rule on the engine side, not one of the user's permission rules, asks about ${one}, so it needed a person's answer, and ${tail}; the user's settings cannot approve it - run the task interactively to answer it`;
|
|
47
52
|
case 'budget':
|
|
48
53
|
return `${lane}: ${one} needed a person's approval, and this session's automatic-approval budget is used up, so saved rules no longer approve calls; ${tail}${budgetKnob !== undefined ? `; raise ${budgetKnob} before launch to change the cap` : ''}`;
|
|
49
54
|
default:
|
|
@@ -73,19 +78,6 @@ function denyStampedEnd(ev, attribution) {
|
|
|
73
78
|
export const HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE = '[Request interrupted by user for tool use]';
|
|
74
79
|
const RUN_CANCELLED_ERROR_CODE = RUN_CANCELLED_CODE;
|
|
75
80
|
export const ENGINE_ABORT_TOOL_RESULT = 'Operation aborted';
|
|
76
|
-
export function toolEndOutputText(output) {
|
|
77
|
-
if (typeof output === 'string')
|
|
78
|
-
return output;
|
|
79
|
-
if (!Array.isArray(output))
|
|
80
|
-
return undefined;
|
|
81
|
-
const parts = [];
|
|
82
|
-
for (const b of output) {
|
|
83
|
-
const bb = b;
|
|
84
|
-
if (bb?.type === 'text' && typeof bb.text === 'string')
|
|
85
|
-
parts.push(bb.text);
|
|
86
|
-
}
|
|
87
|
-
return parts.length > 0 ? parts.join('') : undefined;
|
|
88
|
-
}
|
|
89
81
|
function isAskToolLoose(name) {
|
|
90
82
|
return typeof name === 'string' && name.replace(/[\s_-]+/g, '').toLowerCase() === 'askuserquestion';
|
|
91
83
|
}
|
|
@@ -46,7 +46,7 @@ export interface PlanReviewOutcome {
|
|
|
46
46
|
export declare const PLAN_REVIEW_MODE_AFTER_WORDS: readonly ["default", "acceptEdits"];
|
|
47
47
|
export type PlanReviewModeAfter = (typeof PLAN_REVIEW_MODE_AFTER_WORDS)[number];
|
|
48
48
|
export declare function isPlanReviewModeAfter(v: string | undefined): v is PlanReviewModeAfter;
|
|
49
|
-
export declare const GATE_FAILURE_CODES: readonly ["no_pending", "binding_mismatch", "wrong_gate", "bad_plan_edit", "bad_plan_mode", "empty_answer"];
|
|
49
|
+
export declare const GATE_FAILURE_CODES: readonly ["no_pending", "binding_mismatch", "wrong_gate", "bad_plan_edit", "bad_plan_mode", "empty_answer", "binding_missing"];
|
|
50
50
|
export type HitlSafetyCode = (typeof GATE_FAILURE_CODES)[number];
|
|
51
51
|
export type HitlFailureStage = 'fetch' | 'input' | 'card' | 'decide' | 'orchestration';
|
|
52
52
|
export declare const NO_APPROVAL_CARD_PORT_REASON = "no approval card port installed (print/non-REPL mode, or host forgot installApprovalCardPort)";
|
|
@@ -76,6 +76,10 @@ export type WireErrorCodeReading = {
|
|
|
76
76
|
readonly state: 'unreadable';
|
|
77
77
|
};
|
|
78
78
|
export declare function readWireErrorCodeReading(e: unknown): WireErrorCodeReading;
|
|
79
|
+
export declare function readWireCodes(e: unknown): {
|
|
80
|
+
readonly errorCode?: string;
|
|
81
|
+
readonly blockedBy?: string;
|
|
82
|
+
};
|
|
79
83
|
export declare function __setDecideTimeoutRetryBudgetForTests(ms?: number): void;
|
|
80
84
|
export declare class DecideTransportRetryExhaustedError extends Error {
|
|
81
85
|
readonly attempts: number;
|
|
@@ -116,7 +120,7 @@ export type FindPendingOutcome = {
|
|
|
116
120
|
} | {
|
|
117
121
|
ok: false;
|
|
118
122
|
reason: string;
|
|
119
|
-
code?: 'no_pending';
|
|
123
|
+
code?: 'no_pending' | 'wrong_gate';
|
|
120
124
|
};
|
|
121
125
|
export declare function findPendingForTask(client: HitlClientLike, taskId: string, matches: (toolName: string | undefined) => boolean, opts?: {
|
|
122
126
|
signal?: AbortSignal;
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -4,8 +4,8 @@ import { hostLog } from '../host.js';
|
|
|
4
4
|
import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
|
|
5
5
|
import { abortableSleep } from '../abortableSleep.js';
|
|
6
6
|
import { readOwnDataValue } from '../ownKey.js';
|
|
7
|
-
import { THROWN_SLOT_UNREADABLE, readThrownSlot, thrownIsInstanceOf, wireFailureTextsOf } from '../wireFailureShape.js';
|
|
8
|
-
import {
|
|
7
|
+
import { THROWN_SLOT_UNREADABLE, readBlockedBy, readThrownSlot, thrownIsInstanceOf, wireFailureTextsOf } from '../wireFailureShape.js';
|
|
8
|
+
import { RESUME_POLICY_FOLD_CODE } from '../engineErrorCodes.js';
|
|
9
9
|
import { suspendedReopenOf } from './suspendedReopen.js';
|
|
10
10
|
export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
|
|
11
11
|
const MAX_DENY_REASON_CHARS = 4096;
|
|
@@ -29,14 +29,15 @@ export const PLAN_REVIEW_MODE_AFTER_WORDS = Object.freeze(['default', 'acceptEdi
|
|
|
29
29
|
export function isPlanReviewModeAfter(v) {
|
|
30
30
|
return v !== undefined && PLAN_REVIEW_MODE_AFTER_WORDS.includes(v);
|
|
31
31
|
}
|
|
32
|
-
export const GATE_FAILURE_CODES = [
|
|
32
|
+
export const GATE_FAILURE_CODES = Object.freeze([
|
|
33
33
|
'no_pending',
|
|
34
34
|
'binding_mismatch',
|
|
35
35
|
'wrong_gate',
|
|
36
36
|
'bad_plan_edit',
|
|
37
37
|
'bad_plan_mode',
|
|
38
38
|
'empty_answer',
|
|
39
|
-
|
|
39
|
+
'binding_missing',
|
|
40
|
+
]);
|
|
40
41
|
export const NO_APPROVAL_CARD_PORT_REASON = 'no approval card port installed (print/non-REPL mode, or host forgot installApprovalCardPort)';
|
|
41
42
|
export const APPROVAL_CARD_PORT_KEY_MISMATCH_REASON = 'no approval card port installed for the default session key, but a non-default sessionKey has one — likely a keyed-install/default-read mismatch (installApprovalCardPortFor vs the zero-arg default-key read), not a missing install';
|
|
42
43
|
export const NO_QUESTION_OVERLAY_REASON = 'no question overlay mounted (print/non-REPL mode)';
|
|
@@ -92,6 +93,15 @@ export function readWireErrorCodeReading(e) {
|
|
|
92
93
|
return { state: 'unreadable' };
|
|
93
94
|
return typeof raw === 'string' && raw.length > 0 ? { state: 'code', code: raw } : { state: 'absent' };
|
|
94
95
|
}
|
|
96
|
+
export function readWireCodes(e) {
|
|
97
|
+
const reading = readWireErrorCodeReading(e);
|
|
98
|
+
if (reading.state !== 'code')
|
|
99
|
+
return {};
|
|
100
|
+
if (reading.code !== RESUME_POLICY_FOLD_CODE)
|
|
101
|
+
return { errorCode: reading.code };
|
|
102
|
+
const folded = readBlockedBy(e);
|
|
103
|
+
return folded.kind === 'named' ? { errorCode: reading.code, blockedBy: folded.code } : { errorCode: reading.code };
|
|
104
|
+
}
|
|
95
105
|
const DECIDE_TRANSPORT_RETRY_BACKOFF_MS = 750;
|
|
96
106
|
const DECIDE_RETRY_BACKOFF_MAX_MS = 5_000;
|
|
97
107
|
const DECIDE_ATTEMPT_TIMEOUT_CAP_MS = 60_000;
|
|
@@ -224,10 +234,21 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
|
|
|
224
234
|
hostLog('debug', `hitlBridge: approvals.list failed: ${t.log}`);
|
|
225
235
|
return { ok: false, reason: `approvals.list failed: ${t.reason}` };
|
|
226
236
|
}
|
|
227
|
-
const
|
|
237
|
+
const readings = rows.map((r) => ({ r, foreign: askParkForeignGateKind(r) }));
|
|
238
|
+
const decidable = readings.filter((x) => x.foreign === null).map((x) => x.r);
|
|
228
239
|
const identityRow = callId !== undefined
|
|
229
240
|
? decidable.find((r) => r.taskId === taskId && (r.boundCallId === callId || r.toolCallId === callId))
|
|
230
241
|
: undefined;
|
|
242
|
+
const identityForeign = callId !== undefined && identityRow === undefined
|
|
243
|
+
? readings.find((x) => x.r.taskId === taskId && (x.r.boundCallId === callId || x.r.toolCallId === callId) && x.foreign !== null)
|
|
244
|
+
: undefined;
|
|
245
|
+
if (identityForeign !== undefined) {
|
|
246
|
+
return {
|
|
247
|
+
ok: false,
|
|
248
|
+
reason: `the pending row this park names (call ${callId}) is parked on a '${String(identityForeign.foreign)}' gate — not an approval this leg decides; refusing to decide a different row`,
|
|
249
|
+
code: 'wrong_gate',
|
|
250
|
+
};
|
|
251
|
+
}
|
|
231
252
|
const identityConflicts = identityRow !== undefined && !identityAccepts(identityRow);
|
|
232
253
|
const pool = identityConflicts ? decidable.filter((r) => identityAccepts(r)) : decidable;
|
|
233
254
|
const pending = (identityConflicts ? undefined : identityRow) ??
|
|
@@ -286,7 +307,7 @@ export class HitlBridge {
|
|
|
286
307
|
if (p.boundCallId === undefined || p.boundInputHash === undefined) {
|
|
287
308
|
throw new HitlSafetyError(`pending checkpoint ${p.id ?? '<no-id>'} is missing D-1 binding fields ` +
|
|
288
309
|
`(boundCallId=${String(p.boundCallId)}, boundInputHash=${String(p.boundInputHash)}) — ` +
|
|
289
|
-
'pre-D-1 workers are no longer supported (server 5.x requires the verbatim two-tuple echo)', '
|
|
310
|
+
'pre-D-1 workers are no longer supported (server 5.x requires the verbatim two-tuple echo)', 'binding_missing');
|
|
290
311
|
}
|
|
291
312
|
return { boundCallId: p.boundCallId, boundInputHash: p.boundInputHash };
|
|
292
313
|
}
|
|
@@ -500,7 +521,7 @@ function bindingMismatchReading(e) {
|
|
|
500
521
|
const name = readThrownSlot(e, 'name');
|
|
501
522
|
const family = (code.state === 'code' && code.code === 'approval_binding_mismatch') || name === 'ApprovalBindingMismatchError';
|
|
502
523
|
if (family)
|
|
503
|
-
return
|
|
524
|
+
return 'mismatch';
|
|
504
525
|
if (code.state === 'unreadable')
|
|
505
526
|
return 'unreadable';
|
|
506
527
|
return 'not';
|
|
@@ -6,6 +6,7 @@ export interface LocalAllowRuleDeps {
|
|
|
6
6
|
bashRuleContentHasDangerousBarePrefix(ruleContent: string): boolean;
|
|
7
7
|
isDangerousBashPermission(toolName: string, ruleContent: string): boolean;
|
|
8
8
|
isDangerousPowerShellPermission(toolName: string, ruleContent: string): boolean;
|
|
9
|
+
onFailure?(log: string): void;
|
|
9
10
|
}
|
|
10
11
|
export type LocalAllowRuleParse = {
|
|
11
12
|
ok: true;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { reasonOfThrownWithSink } from '../hostCallbackGuard.js';
|
|
2
2
|
const BASH_TOOL_NAME_FOR_RULES = 'Bash';
|
|
3
3
|
export function parseLocalAllowRule(rule, expectedToolName, deps) {
|
|
4
4
|
if (typeof rule !== 'string' || rule === '') {
|
|
@@ -9,7 +9,7 @@ export function parseLocalAllowRule(rule, expectedToolName, deps) {
|
|
|
9
9
|
value = deps.parseRule(rule);
|
|
10
10
|
}
|
|
11
11
|
catch (e) {
|
|
12
|
-
return { ok: false, error: `rule text did not parse (${
|
|
12
|
+
return { ok: false, error: `rule text did not parse (${reasonOfThrownWithSink(e, (log) => deps.onFailure?.(log))})` };
|
|
13
13
|
}
|
|
14
14
|
if (typeof value.toolName !== 'string' || value.toolName === '') {
|
|
15
15
|
return { ok: false, error: 'rule text carries no tool name' };
|