@sema-agent/client-core 0.98.0 → 0.100.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 +376 -0
- package/README.md +61 -35
- package/dist/adapt/arms.js +120 -55
- package/dist/adapt/panelTasks.d.ts +3 -2
- package/dist/adapt/panelTasks.js +34 -11
- package/dist/adapt/textStream.d.ts +1 -1
- package/dist/adapt/textStream.js +51 -28
- package/dist/adapter/activeRunSelfHeal.d.ts +11 -3
- package/dist/adapter/activeRunSelfHeal.js +210 -294
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +25 -6
- package/dist/adapter/downstream/eventToSdkMessage.js +176 -12
- package/dist/adapter/downstream/terminalToSdkResult.js +10 -0
- package/dist/adapter/downstream/wiringManifestView.d.ts +3 -1
- package/dist/adapter/downstream/wiringManifestView.js +1 -0
- package/dist/adapter/runStream.js +82 -40
- package/dist/adapter/types.d.ts +2 -0
- package/dist/adapter/types.js +2 -1
- package/dist/agentSession/engineSessionBackgroundStop.js +16 -8
- package/dist/agentsWireCaps.d.ts +7 -3
- package/dist/agentsWireCaps.js +133 -39
- package/dist/cloudConfigWireCaps.d.ts +10 -1
- package/dist/cloudConfigWireCaps.js +181 -65
- package/dist/compactionStatus.d.ts +34 -0
- package/dist/compactionStatus.js +196 -0
- package/dist/controlRouter.d.ts +4 -2
- package/dist/controlRouter.js +39 -51
- package/dist/coreValuePorts.d.ts +26 -1
- package/dist/coreValuePorts.js +10 -0
- package/dist/corruptSessionCopy.d.ts +2 -1
- package/dist/corruptSessionCopy.js +3 -3
- package/dist/decideFailureNote.js +3 -3
- package/dist/declared/supportFloorDeclared.js +1 -1
- package/dist/detachWire.js +3 -2
- package/dist/devicesFace.d.ts +34 -0
- package/dist/devicesFace.js +90 -0
- package/dist/displayUntrusted.d.ts +1 -0
- package/dist/displayUntrusted.js +153 -5
- package/dist/engineAgentPanelStore.d.ts +14 -4
- package/dist/engineAgentPanelStore.js +14 -0
- package/dist/engineCapReader.d.ts +2 -1
- package/dist/engineCapReader.js +7 -5
- package/dist/engineCapsCache.d.ts +8 -0
- package/dist/engineCapsCache.js +59 -6
- package/dist/engineCapsChange.d.ts +2 -0
- package/dist/engineCapsChange.js +9 -0
- package/dist/engineErrorCodes.d.ts +11 -2
- package/dist/engineErrorCodes.js +11 -12
- package/dist/engineRowRef.js +4 -0
- package/dist/engineSessionMessages.d.ts +1 -0
- package/dist/engineSessionMessages.js +7 -1
- package/dist/engineTargetSeat.d.ts +27 -0
- package/dist/engineTargetSeat.js +199 -0
- package/dist/engineToolDirectory.d.ts +3 -0
- package/dist/engineToolDirectory.js +4 -2
- package/dist/engineWireFor.js +3 -8
- package/dist/env/uiLanguage.d.ts +2 -1
- package/dist/env/uiLanguage.js +3 -2
- package/dist/eventLeg.d.ts +7 -5
- package/dist/eventLeg.js +68 -49
- package/dist/fileHistoryCaptureCapability.d.ts +2 -4
- package/dist/fileHistoryCaptureCapability.js +12 -22
- package/dist/fileHistoryLegNotice.d.ts +2 -1
- package/dist/fileHistoryLegNotice.js +3 -3
- package/dist/frameBinding.d.ts +3 -0
- package/dist/frameBinding.js +12 -0
- package/dist/gateOutcome.js +16 -2
- package/dist/gateVocabulary.d.ts +8 -7
- package/dist/gateVocabulary.js +71 -73
- package/dist/generated/engineFactTables.d.ts +18 -1
- package/dist/generated/engineFactTables.js +56 -0
- package/dist/generated/engineNoticeTables.js +12 -3
- package/dist/generated/settingsSchemaFns.d.ts +18 -0
- package/dist/generated/settingsSchemaFns.js +70 -0
- package/dist/generated/settingsSchemaHooksClassifier.d.ts +2 -0
- package/dist/generated/settingsSchemaHooksClassifier.js +254 -0
- package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -0
- package/dist/generated/settingsSchemaMcpRequestEntry.js +162 -0
- package/dist/generated/settingsSchemaTables.d.ts +2 -0
- package/dist/generated/settingsSchemaTables.js +9 -0
- package/dist/generated/settingsSchemaTypes.d.ts +133 -0
- package/dist/generated/settingsSchemaTypes.js +1 -0
- package/dist/hitl/allowRuleShadow.d.ts +2 -1
- package/dist/hitl/allowRuleShadow.js +18 -15
- package/dist/hitl/approvalCardReasons.d.ts +3 -2
- package/dist/hitl/approvalCardReasons.js +29 -30
- package/dist/hitl/approvalHolds.d.ts +17 -0
- package/dist/hitl/approvalHolds.js +114 -29
- package/dist/hitl/askClassPair.d.ts +2 -2
- package/dist/hitl/askClassPair.js +16 -0
- package/dist/hitl/askGateWire.js +2 -1
- package/dist/hitl/askSettlementLine.d.ts +14 -4
- package/dist/hitl/askSettlementLine.js +67 -47
- package/dist/hitl/attributionLadder.d.ts +23 -0
- package/dist/hitl/attributionLadder.js +67 -0
- package/dist/hitl/automaticAnswerVerdict.d.ts +2 -1
- package/dist/hitl/automaticAnswerVerdict.js +8 -8
- package/dist/hitl/crashConvergedNotice.d.ts +5 -4
- package/dist/hitl/crashConvergedNotice.js +28 -41
- package/dist/hitl/decisionLedger.d.ts +55 -0
- package/dist/hitl/decisionLedger.js +156 -0
- package/dist/hitl/decisionRefusal.d.ts +1 -0
- package/dist/hitl/decisionRefusal.js +1 -1
- package/dist/hitl/decisionSend.d.ts +113 -0
- package/dist/hitl/decisionSend.js +579 -0
- package/dist/hitl/frameRouter.js +7 -8
- package/dist/hitl/hitlBridge.d.ts +1 -0
- package/dist/hitl/hitlBridge.js +21 -9
- package/dist/hitl/persistedRulesWire.d.ts +6 -5
- package/dist/hitl/persistedRulesWire.js +55 -73
- package/dist/hitl/planReviewWire.d.ts +5 -0
- package/dist/hitl/planReviewWire.js +53 -76
- package/dist/hitl/sessionPolicyDeliverable.d.ts +2 -0
- package/dist/hitl/sessionPolicyDeliverable.js +49 -9
- package/dist/hitl/sessionPolicyWire.d.ts +2 -1
- package/dist/hitl/sessionPolicyWire.js +23 -24
- package/dist/hitl/suspendedReopen.d.ts +2 -1
- package/dist/hitl/suspendedReopen.js +7 -10
- package/dist/hitl/toolApprovalWire.d.ts +17 -5
- package/dist/hitl/toolApprovalWire.js +120 -147
- package/dist/hooksWireCaps.js +145 -56
- package/dist/host.d.ts +21 -0
- package/dist/hostTurnSettings.d.ts +110 -0
- package/dist/hostTurnSettings.js +692 -0
- package/dist/humanTurn.d.ts +15 -1
- package/dist/humanTurn.js +100 -12
- package/dist/humanTurnRead.d.ts +2 -0
- package/dist/humanTurnRead.js +22 -1
- package/dist/i18n/messages.en.d.ts +713 -0
- package/dist/i18n/messages.en.js +713 -0
- package/dist/i18n/messages.zh.js +854 -1
- package/dist/index.d.ts +45 -13
- package/dist/index.js +27 -9
- package/dist/inheritEnvCapability.d.ts +4 -2
- package/dist/inheritEnvCapability.js +12 -5
- package/dist/inheritEnvWire.d.ts +3 -2
- package/dist/inheritEnvWire.js +14 -16
- package/dist/injectionLaneNotice.d.ts +5 -0
- package/dist/injectionLaneNotice.js +46 -0
- package/dist/interactiveHalt.js +17 -6
- package/dist/liveModelCatalog.d.ts +2 -1
- package/dist/liveModelCatalog.js +14 -8
- package/dist/mcpPanel.d.ts +10 -1
- package/dist/mcpPanel.js +42 -13
- package/dist/mcpProbeCapability.d.ts +2 -1
- package/dist/mcpProbeCapability.js +7 -11
- package/dist/mcpProbeWire.d.ts +3 -1
- package/dist/mcpProbeWire.js +5 -0
- package/dist/mcpReconnect.js +2 -2
- package/dist/mcpToolToggles.d.ts +6 -0
- package/dist/mcpToolToggles.js +46 -0
- package/dist/mcpWireCaps.d.ts +43 -3
- package/dist/mcpWireCaps.js +194 -59
- package/dist/mcpWireTransport.d.ts +4 -0
- package/dist/mcpWireTransport.js +18 -0
- package/dist/memoryEntriesWire.js +5 -10
- package/dist/model/onboardJudgement.d.ts +93 -0
- package/dist/model/onboardJudgement.js +415 -0
- package/dist/model/tierVocabulary.d.ts +2 -0
- package/dist/model/tierVocabulary.js +17 -5
- package/dist/modelCapabilityProbe.d.ts +65 -0
- package/dist/modelCapabilityProbe.js +601 -0
- package/dist/modelIdentityResolvability.d.ts +5 -0
- package/dist/modelIdentityResolvability.js +49 -22
- package/dist/ownKey.d.ts +33 -0
- package/dist/ownKey.js +109 -0
- package/dist/panelReduce.d.ts +11 -1
- package/dist/panelReduce.js +95 -16
- package/dist/panelStateHolder.d.ts +1 -0
- package/dist/panelStateHolder.js +10 -1
- package/dist/permissionRuleIssue.d.ts +2 -1
- package/dist/permissionRuleIssue.js +23 -20
- package/dist/permissionRulesWriteCapability.d.ts +2 -1
- package/dist/permissionRulesWriteCapability.js +8 -6
- package/dist/pluginHooksWire.d.ts +46 -3
- package/dist/pluginHooksWire.js +220 -12
- package/dist/prepareRefusal.d.ts +8 -0
- package/dist/prepareRefusal.js +72 -0
- package/dist/printModelStatus.d.ts +63 -0
- package/dist/printModelStatus.js +155 -0
- package/dist/request/taskRequest.d.ts +16 -10
- package/dist/request/taskRequest.js +195 -58
- package/dist/resumeRefusalCopy.d.ts +14 -9
- package/dist/resumeRefusalCopy.js +89 -144
- package/dist/retryStatus.d.ts +55 -0
- package/dist/retryStatus.js +155 -19
- package/dist/ruleRefusalSentence.d.ts +4 -0
- package/dist/ruleRefusalSentence.js +58 -0
- package/dist/sandboxWire.d.ts +2 -1
- package/dist/sandboxWire.js +55 -21
- package/dist/scenarioRefusal.d.ts +10 -0
- package/dist/scenarioRefusal.js +111 -0
- package/dist/seam.d.ts +48 -9
- package/dist/seam.js +54 -5
- package/dist/seatContract.d.ts +272 -14
- package/dist/seatContract.js +711 -23
- package/dist/selfOrchestrationDenial.js +7 -19
- package/dist/sessionIdSegment.d.ts +6 -0
- package/dist/sessionIdSegment.js +24 -0
- package/dist/sessionMemoryStatus.js +6 -11
- package/dist/sessionPolicyCapability.d.ts +2 -1
- package/dist/sessionPolicyCapability.js +8 -6
- package/dist/settingsCredentialKeyPaths.d.ts +8 -0
- package/dist/settingsCredentialKeyPaths.js +219 -0
- package/dist/settingsHooksSalvage.d.ts +44 -0
- package/dist/settingsHooksSalvage.js +211 -0
- package/dist/skillToolListsCapability.d.ts +2 -1
- package/dist/skillToolListsCapability.js +8 -6
- package/dist/skillsWireCaps.d.ts +0 -3
- package/dist/skillsWireCaps.js +1 -2
- package/dist/sseIdleTriage.js +3 -2
- package/dist/subagent/engineSubagentResume.js +15 -9
- package/dist/subagentContentStore.d.ts +4 -0
- package/dist/subagentContentStore.js +157 -27
- package/dist/toolHistoryMismatch.js +3 -2
- package/dist/toolRoster.d.ts +3 -2
- package/dist/toolRoster.js +21 -20
- package/dist/toolsFlagNames.d.ts +8 -0
- package/dist/toolsFlagNames.js +110 -0
- package/dist/turnFailurePresentation.d.ts +12 -1
- package/dist/turnFailurePresentation.js +220 -15
- package/dist/usageAnalytics.js +6 -11
- package/dist/userAttachmentEnvelope.d.ts +1 -0
- package/dist/userAttachmentEnvelope.js +37 -7
- package/dist/userAttachmentSeat.d.ts +21 -0
- package/dist/userAttachmentSeat.js +75 -0
- package/dist/userInvokedSkillsCapability.d.ts +11 -1
- package/dist/userInvokedSkillsCapability.js +31 -7
- package/dist/wireErrorTriage.d.ts +7 -0
- package/dist/wireErrorTriage.js +23 -1
- package/dist/wireFailureShape.js +2 -1
- package/dist/wireRefusalCopy.d.ts +62 -16
- package/dist/wireRefusalCopy.js +307 -148
- package/dist/workflowsJournalCapability.d.ts +2 -1
- package/dist/workflowsJournalCapability.js +8 -6
- package/docs/INTEGRATION-CLIENTS.md +951 -42
- package/package.json +5 -5
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
import { CAPABILITY_SELF_ORCHESTRATION_REQUIRED } from './engineErrorCodes.js';
|
|
2
|
+
import { readThrownSlot, THROWN_SLOT_UNREADABLE } from './wireFailureShape.js';
|
|
2
3
|
const ENTITLEMENT_RESOLVER_ABSENT = 'entitlement_resolver_absent';
|
|
3
4
|
export const SELF_ORCHESTRATION_RETRY_WITHOUT = Object.freeze(['selfOrchestration', 'settings.ultracode']);
|
|
4
5
|
export function classifySelfOrchestrationRefusal(e) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const rawStatus = o?.status;
|
|
10
|
-
const rawCode = o?.errorCode;
|
|
11
|
-
status = typeof rawStatus === 'number' ? rawStatus : undefined;
|
|
12
|
-
code = typeof rawCode === 'string' ? rawCode : undefined;
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
6
|
+
const rawStatus = readThrownSlot(e, 'status', 'data');
|
|
7
|
+
const rawCode = readThrownSlot(e, 'errorCode', 'data');
|
|
8
|
+
const status = typeof rawStatus === 'number' ? rawStatus : undefined;
|
|
9
|
+
const code = typeof rawCode === 'string' ? rawCode : undefined;
|
|
17
10
|
if (status !== 501)
|
|
18
11
|
return null;
|
|
19
12
|
if (code !== CAPABILITY_SELF_ORCHESTRATION_REQUIRED)
|
|
20
13
|
return null;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
denial = e?.denial;
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
denial = undefined;
|
|
27
|
-
}
|
|
14
|
+
const rawDenial = readThrownSlot(e, 'denial', 'data');
|
|
15
|
+
const denial = rawDenial === THROWN_SLOT_UNREADABLE ? undefined : rawDenial;
|
|
28
16
|
return {
|
|
29
17
|
kind: 'self-orchestration-denied',
|
|
30
18
|
retryWithout: SELF_ORCHESTRATION_RETRY_WITHOUT,
|
|
@@ -1 +1,7 @@
|
|
|
1
1
|
export declare function isSessionIdPathSegment(id: unknown): id is string;
|
|
2
|
+
export type SessionIdRefusalWhy = 'not_string' | 'bad_shape' | 'bad_version' | 'bad_variant';
|
|
3
|
+
export interface SessionIdRefusal {
|
|
4
|
+
readonly why: SessionIdRefusalWhy;
|
|
5
|
+
}
|
|
6
|
+
export declare function canonicalSessionIdOf(raw: {} | null | undefined): string | null;
|
|
7
|
+
export declare function sessionIdRefusalOf(raw: {} | null | undefined): SessionIdRefusal | null;
|
package/dist/sessionIdSegment.js
CHANGED
|
@@ -9,3 +9,27 @@ export function isSessionIdPathSegment(id) {
|
|
|
9
9
|
return false;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
const UUID_ANY_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
13
|
+
function sessionIdReadingOf(raw) {
|
|
14
|
+
if (typeof raw !== 'string')
|
|
15
|
+
return { id: null, why: 'not_string' };
|
|
16
|
+
const folded = raw.replace(/[A-F]/g, (c) => c.toLowerCase());
|
|
17
|
+
if (UUID_ANY_RE.test(folded))
|
|
18
|
+
return { id: folded, why: null };
|
|
19
|
+
const version = folded.charAt(14);
|
|
20
|
+
const variant = folded.charAt(19);
|
|
21
|
+
const hex = '0123456789abcdef';
|
|
22
|
+
if (folded.length !== 36 || !hex.includes(version) || !hex.includes(variant))
|
|
23
|
+
return { id: null, why: 'bad_shape' };
|
|
24
|
+
const probe = `${folded.slice(0, 14)}4${folded.slice(15, 19)}8${folded.slice(20)}`;
|
|
25
|
+
if (!UUID_ANY_RE.test(probe))
|
|
26
|
+
return { id: null, why: 'bad_shape' };
|
|
27
|
+
return { id: null, why: '12345678'.includes(version) ? 'bad_variant' : 'bad_version' };
|
|
28
|
+
}
|
|
29
|
+
export function canonicalSessionIdOf(raw) {
|
|
30
|
+
return sessionIdReadingOf(raw).id;
|
|
31
|
+
}
|
|
32
|
+
export function sessionIdRefusalOf(raw) {
|
|
33
|
+
const r = sessionIdReadingOf(raw);
|
|
34
|
+
return r.why === null ? null : Object.freeze({ why: r.why });
|
|
35
|
+
}
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import { isSessionIdPathSegment } from './sessionIdSegment.js';
|
|
2
|
+
import { readThrownSlot, THROWN_SLOT_UNREADABLE } from './wireFailureShape.js';
|
|
2
3
|
export function classifyMemoryStatusFailure(e) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const o = e;
|
|
7
|
-
const rawCode = o?.errorCode;
|
|
8
|
-
const rawStatus = o?.status;
|
|
9
|
-
code = typeof rawCode === 'string' ? rawCode : undefined;
|
|
10
|
-
status = typeof rawStatus === 'number' ? rawStatus : undefined;
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
4
|
+
const rawCode = readThrownSlot(e, 'errorCode', 'data');
|
|
5
|
+
const rawStatus = readThrownSlot(e, 'status', 'data');
|
|
6
|
+
if (rawCode === THROWN_SLOT_UNREADABLE || rawStatus === THROWN_SLOT_UNREADABLE)
|
|
13
7
|
return { kind: 'failed', error: e };
|
|
14
|
-
|
|
8
|
+
const code = typeof rawCode === 'string' ? rawCode : undefined;
|
|
9
|
+
const status = typeof rawStatus === 'number' ? rawStatus : undefined;
|
|
15
10
|
if (code !== undefined) {
|
|
16
11
|
if (code.startsWith('capability.'))
|
|
17
12
|
return { kind: 'unsupported', reason: 'capability' };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UiLanguage } from './env/uiLanguage.js';
|
|
1
2
|
export type SessionPolicyReading = {
|
|
2
3
|
kind: 'unobserved';
|
|
3
4
|
} | {
|
|
@@ -12,4 +13,4 @@ export type SessionPolicyReading = {
|
|
|
12
13
|
export declare function projectSessionPolicyCapability(caps: unknown): SessionPolicyReading | undefined;
|
|
13
14
|
export declare function observedSessionPolicy(baseUrl?: string | undefined): SessionPolicyReading;
|
|
14
15
|
export declare function sessionPolicyFaceAvailable(reading: SessionPolicyReading): 'yes' | 'no' | 'unknown';
|
|
15
|
-
export declare function sessionPolicyDoctorDetail(reading: SessionPolicyReading): string;
|
|
16
|
+
export declare function sessionPolicyDoctorDetail(reading: SessionPolicyReading, lang?: UiLanguage): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { engineWireTarget } from './engineWireTarget.js';
|
|
2
2
|
import { createEngineCapReader, unreadableReadingDetail } from './engineCapReader.js';
|
|
3
|
+
import { msg, renderMessage } from './i18n/translate.js';
|
|
3
4
|
import { ownDataField } from './ownKey.js';
|
|
4
5
|
export function projectSessionPolicyCapability(caps) {
|
|
5
6
|
try {
|
|
@@ -31,17 +32,18 @@ export function sessionPolicyFaceAvailable(reading) {
|
|
|
31
32
|
return 'no';
|
|
32
33
|
return 'unknown';
|
|
33
34
|
}
|
|
34
|
-
export function sessionPolicyDoctorDetail(reading) {
|
|
35
|
+
export function sessionPolicyDoctorDetail(reading, lang) {
|
|
36
|
+
const subject = msg('notice.capability.subject.sessionPolicy');
|
|
35
37
|
switch (reading.kind) {
|
|
36
38
|
case 'unreadable':
|
|
37
|
-
return unreadableReadingDetail(
|
|
39
|
+
return unreadableReadingDetail(renderMessage(subject, lang), lang);
|
|
38
40
|
case 'unobserved':
|
|
39
|
-
return
|
|
41
|
+
return renderMessage(msg('notice.capability.unobserved', { subject, key: 'sessionPolicy' }), lang);
|
|
40
42
|
case 'not_reported':
|
|
41
|
-
return '
|
|
43
|
+
return renderMessage(msg('notice.capability.sessionPolicy.notReported'), lang);
|
|
42
44
|
case 'absent':
|
|
43
|
-
return '
|
|
45
|
+
return renderMessage(msg('notice.capability.sessionPolicy.absent'), lang);
|
|
44
46
|
case 'present':
|
|
45
|
-
return '
|
|
47
|
+
return renderMessage(msg('notice.capability.sessionPolicy.present'), lang);
|
|
46
48
|
}
|
|
47
49
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface CredentialKeyFinding {
|
|
2
|
+
readonly path: string;
|
|
3
|
+
readonly numericByNameOnly?: true;
|
|
4
|
+
}
|
|
5
|
+
export declare function credentialNameWords(name: string): string[];
|
|
6
|
+
export declare function isCredentialEnvName(name: string): boolean;
|
|
7
|
+
export declare function isCredentialConfigKey(name: string): boolean;
|
|
8
|
+
export declare function settingsCredentialKeyPaths(doc: object | null | undefined): CredentialKeyFinding[];
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { looksLikeKnownSecret, queryCarriesCredential } from './displayUntrusted.js';
|
|
2
|
+
const CREDENTIAL_WORDS = new Set([
|
|
3
|
+
'key', 'keys', 'apikey', 'token', 'tokens', 'secret', 'secrets', 'password', 'passwd', 'pass', 'pwd', 'pin', 'otp',
|
|
4
|
+
'auth', 'authorization', 'credential', 'credentials', 'cookie', 'cookies', 'session', 'private', 'cert', 'signature', 'salt',
|
|
5
|
+
]);
|
|
6
|
+
const CREDENTIAL_STEMS = ['PASSWORD', 'PASSWD', 'SECRET', 'TOKEN', 'APIKEY', 'CREDENTIAL', 'COOKIE', 'PRIVATE'];
|
|
7
|
+
const CREDENTIAL_SUFFIXES = ['PASS', 'PWD', 'KEY', 'AUTH'];
|
|
8
|
+
const SECRET_CONFIG_NAME = /(?:^|[-_.])(?:token|key|secret|password|authorization|credential|cookie)s?$/i;
|
|
9
|
+
export function credentialNameWords(name) {
|
|
10
|
+
if (typeof name !== 'string')
|
|
11
|
+
return [];
|
|
12
|
+
return name
|
|
13
|
+
.replace(/([A-Z])(?=[A-Z][a-z])/g, '$1_')
|
|
14
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
15
|
+
.split(/[_\-.\s]+/)
|
|
16
|
+
.filter((w) => w.length > 0)
|
|
17
|
+
.map((w) => w.toLowerCase());
|
|
18
|
+
}
|
|
19
|
+
export function isCredentialEnvName(name) {
|
|
20
|
+
if (typeof name !== 'string')
|
|
21
|
+
return false;
|
|
22
|
+
if (credentialNameWords(name).some((w) => CREDENTIAL_WORDS.has(w)))
|
|
23
|
+
return true;
|
|
24
|
+
const upper = name.toUpperCase();
|
|
25
|
+
return CREDENTIAL_STEMS.some((stem) => upper.includes(stem)) || CREDENTIAL_SUFFIXES.some((suffix) => upper.endsWith(suffix));
|
|
26
|
+
}
|
|
27
|
+
export function isCredentialConfigKey(name) {
|
|
28
|
+
if (typeof name !== 'string')
|
|
29
|
+
return false;
|
|
30
|
+
const words = credentialNameWords(name);
|
|
31
|
+
const last = words[words.length - 1];
|
|
32
|
+
return SECRET_CONFIG_NAME.test(name) || (last !== undefined && CREDENTIAL_WORDS.has(last));
|
|
33
|
+
}
|
|
34
|
+
const DECIMAL_INTEGER_RE = /^\s*\d+\s*$/;
|
|
35
|
+
function stringCarriesCredential(value) {
|
|
36
|
+
if (looksLikeKnownSecret(value))
|
|
37
|
+
return true;
|
|
38
|
+
const text = value.trim();
|
|
39
|
+
if (!text.includes('://'))
|
|
40
|
+
return false;
|
|
41
|
+
let url;
|
|
42
|
+
try {
|
|
43
|
+
url = new URL(text);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (url.username !== '' || url.password !== '')
|
|
49
|
+
return true;
|
|
50
|
+
return url.search.length > 1 && queryCarriesCredential(url.search.slice(1));
|
|
51
|
+
}
|
|
52
|
+
const ENV_ASSIGNMENT_RE = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s;
|
|
53
|
+
const LONG_OPTION_RE = /^--?([A-Za-z][A-Za-z0-9_-]*)(?:=(.*))?$/s;
|
|
54
|
+
const HEADER_FORM_RE = /^\s*([A-Za-z][A-Za-z0-9_-]*)\s*:\s*\S/;
|
|
55
|
+
function argvFindings(args) {
|
|
56
|
+
const hits = new Map();
|
|
57
|
+
const add = (i, numericByNameOnly) => {
|
|
58
|
+
hits.set(i, (hits.get(i) ?? true) && numericByNameOnly);
|
|
59
|
+
};
|
|
60
|
+
const valueCarries = (value) => {
|
|
61
|
+
const header = HEADER_FORM_RE.exec(value);
|
|
62
|
+
return stringCarriesCredential(value) || (header !== null && isCredentialConfigKey(header[1] ?? ''));
|
|
63
|
+
};
|
|
64
|
+
args.forEach((arg, i) => {
|
|
65
|
+
if (valueCarries(arg))
|
|
66
|
+
add(i, false);
|
|
67
|
+
const assignment = ENV_ASSIGNMENT_RE.exec(arg);
|
|
68
|
+
if (assignment !== null && assignment[2] !== '') {
|
|
69
|
+
const assigned = assignment[2] ?? '';
|
|
70
|
+
if (valueCarries(assigned))
|
|
71
|
+
add(i, false);
|
|
72
|
+
if (isCredentialEnvName(assignment[1] ?? ''))
|
|
73
|
+
add(i, DECIMAL_INTEGER_RE.test(assigned));
|
|
74
|
+
}
|
|
75
|
+
const option = LONG_OPTION_RE.exec(arg);
|
|
76
|
+
if (option === null)
|
|
77
|
+
return;
|
|
78
|
+
const inline = option[2];
|
|
79
|
+
if (inline !== undefined && inline !== '' && valueCarries(inline))
|
|
80
|
+
add(i, false);
|
|
81
|
+
const optionName = option[1] ?? '';
|
|
82
|
+
if (optionName.length <= 1 || !isCredentialEnvName(optionName))
|
|
83
|
+
return;
|
|
84
|
+
if (inline !== undefined) {
|
|
85
|
+
if (inline !== '')
|
|
86
|
+
add(i, DECIMAL_INTEGER_RE.test(inline));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const next = args[i + 1];
|
|
90
|
+
if (next !== undefined && next !== '')
|
|
91
|
+
add(i + 1, DECIMAL_INTEGER_RE.test(next));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return hits;
|
|
95
|
+
}
|
|
96
|
+
function isPresent(value) {
|
|
97
|
+
return value !== undefined && value !== null && value !== '' && value !== false;
|
|
98
|
+
}
|
|
99
|
+
function arrayKindOf(node) {
|
|
100
|
+
try {
|
|
101
|
+
return Array.isArray(node);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function ownKeysOf(node) {
|
|
108
|
+
try {
|
|
109
|
+
return Object.keys(node);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function readSlot(node, key) {
|
|
116
|
+
try {
|
|
117
|
+
return { ok: true, value: node[key] };
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return { ok: false };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export function settingsCredentialKeyPaths(doc) {
|
|
124
|
+
const found = [];
|
|
125
|
+
if (doc === null || doc === undefined || typeof doc !== 'object')
|
|
126
|
+
return found;
|
|
127
|
+
const finding = (path, numericByNameOnly) => (numericByNameOnly ? { path, numericByNameOnly: true } : { path });
|
|
128
|
+
const ancestors = new WeakSet();
|
|
129
|
+
const stack = [{ node: doc, path: '', inEnv: false }];
|
|
130
|
+
while (stack.length > 0) {
|
|
131
|
+
const frame = stack.pop();
|
|
132
|
+
if (frame === undefined)
|
|
133
|
+
continue;
|
|
134
|
+
const { node, path, inEnv } = frame;
|
|
135
|
+
if (frame.exit === true) {
|
|
136
|
+
ancestors.delete(node);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (ancestors.has(node))
|
|
140
|
+
continue;
|
|
141
|
+
ancestors.add(node);
|
|
142
|
+
stack.push({ node, path, inEnv, exit: true });
|
|
143
|
+
const isArray = arrayKindOf(node);
|
|
144
|
+
if (isArray === undefined) {
|
|
145
|
+
if (path !== '')
|
|
146
|
+
found.push(finding(path, false));
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (isArray) {
|
|
150
|
+
let items;
|
|
151
|
+
try {
|
|
152
|
+
items = Array.from(node);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
if (path !== '')
|
|
156
|
+
found.push(finding(path, false));
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (items.length > 0 && items.every((item) => typeof item === 'string')) {
|
|
160
|
+
const hits = argvFindings(items);
|
|
161
|
+
for (const i of [...hits.keys()].sort((a, b) => a - b))
|
|
162
|
+
found.push(finding(`${path}[${i}]`, hits.get(i) === true));
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
items.forEach((item, i) => {
|
|
166
|
+
const itemPath = `${path}[${i}]`;
|
|
167
|
+
if (typeof item === 'string') {
|
|
168
|
+
if (stringCarriesCredential(item))
|
|
169
|
+
found.push(finding(itemPath, false));
|
|
170
|
+
}
|
|
171
|
+
else if (item !== null && typeof item === 'object') {
|
|
172
|
+
stack.push({ node: item, path: itemPath, inEnv: false });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const keys = ownKeysOf(node);
|
|
178
|
+
if (keys === undefined) {
|
|
179
|
+
if (path !== '')
|
|
180
|
+
found.push(finding(path, false));
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
for (const key of keys) {
|
|
184
|
+
const keyPath = path === '' ? key : `${path}.${key}`;
|
|
185
|
+
const nameIsCredential = inEnv ? isCredentialEnvName(key) : isCredentialConfigKey(key);
|
|
186
|
+
const slot = readSlot(node, key);
|
|
187
|
+
if (!slot.ok) {
|
|
188
|
+
if (nameIsCredential)
|
|
189
|
+
found.push(finding(keyPath, false));
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const value = slot.value;
|
|
193
|
+
if (typeof value === 'string') {
|
|
194
|
+
const byValue = stringCarriesCredential(value);
|
|
195
|
+
if (byValue || (nameIsCredential && isPresent(value)))
|
|
196
|
+
found.push(finding(keyPath, !byValue && DECIMAL_INTEGER_RE.test(value)));
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (value !== null && typeof value === 'object') {
|
|
200
|
+
let nonEmpty;
|
|
201
|
+
try {
|
|
202
|
+
nonEmpty = Array.isArray(value) ? value.length > 0 : Object.keys(value).length > 0;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
nonEmpty = true;
|
|
206
|
+
}
|
|
207
|
+
if (nameIsCredential && nonEmpty) {
|
|
208
|
+
found.push(finding(keyPath, false));
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
stack.push({ node: value, path: keyPath, inEnv: key === 'env' });
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (nameIsCredential && isPresent(value))
|
|
215
|
+
found.push(finding(keyPath, typeof value === 'number'));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return found;
|
|
219
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { WireHooksConfig } from './finalVerifyWire.js';
|
|
2
|
+
import type { SettingsDocument } from './host.js';
|
|
3
|
+
import type { HookSettingsSource } from './pluginHooksWire.js';
|
|
4
|
+
export type HooksSalvageOutcome = 'loaded' | 'loaded-partial' | 'rejected';
|
|
5
|
+
export type HooksSalvageDropCode = 'hooks-not-object' | 'unknown-event' | 'event-not-array' | 'matcher-invalid' | 'entry-invalid';
|
|
6
|
+
export type HooksSalvageRejectionCode = 'guard-outside-hooks' | 'hooks-is-matcher' | 'guard-under-unknown-event' | 'guard-event-not-array' | 'guard-matcher-invalid' | 'guard-entry-invalid';
|
|
7
|
+
export interface HooksSalvageDrop {
|
|
8
|
+
readonly code: HooksSalvageDropCode;
|
|
9
|
+
readonly path: readonly (string | number)[];
|
|
10
|
+
readonly field?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface HooksSalvageRejection {
|
|
13
|
+
readonly code: HooksSalvageRejectionCode;
|
|
14
|
+
readonly path: readonly (string | number)[];
|
|
15
|
+
readonly field?: string;
|
|
16
|
+
}
|
|
17
|
+
export type HooksSalvage = {
|
|
18
|
+
readonly outcome: 'loaded';
|
|
19
|
+
readonly hooks: WireHooksConfig | undefined;
|
|
20
|
+
readonly dropped: readonly [];
|
|
21
|
+
readonly rejections: readonly [];
|
|
22
|
+
} | {
|
|
23
|
+
readonly outcome: 'loaded-partial';
|
|
24
|
+
readonly hooks: WireHooksConfig | undefined;
|
|
25
|
+
readonly dropped: readonly HooksSalvageDrop[];
|
|
26
|
+
readonly rejections: readonly [];
|
|
27
|
+
} | {
|
|
28
|
+
readonly outcome: 'rejected';
|
|
29
|
+
readonly hooks: undefined;
|
|
30
|
+
readonly dropped: readonly [];
|
|
31
|
+
readonly rejections: readonly HooksSalvageRejection[];
|
|
32
|
+
};
|
|
33
|
+
export interface HooksSalvageMask {
|
|
34
|
+
readonly hooksDropped: boolean;
|
|
35
|
+
eventDropped(event: string): boolean;
|
|
36
|
+
groupDropped(event: string, groupIndex: number): boolean;
|
|
37
|
+
entryDropped(event: string, groupIndex: number, hookIndex: number): boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare function hooksSalvageMaskOf(dropped: readonly {
|
|
40
|
+
readonly path: readonly (string | number)[];
|
|
41
|
+
}[]): HooksSalvageMask;
|
|
42
|
+
export declare function salvageSettingsHooksEntries(doc: SettingsDocument | null | undefined): HooksSalvage;
|
|
43
|
+
export declare const HOOK_SETTINGS_SOURCE_TEXT: Readonly<Record<HookSettingsSource, string>>;
|
|
44
|
+
export declare function settingsHooksSalvageNotice(salvage: HooksSalvage, source: HookSettingsSource, doc?: SettingsDocument | null): string | undefined;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { displayUntrusted } from './displayUntrusted.js';
|
|
2
|
+
import { SETTINGS_CLASSIFY_HOOKS_CONFIG, } from './generated/settingsSchemaFns.js';
|
|
3
|
+
const _tuplePins = [true, true, true, true, true, true];
|
|
4
|
+
void _tuplePins;
|
|
5
|
+
const _classifierPins = [true, true, true, true, true, true];
|
|
6
|
+
void _classifierPins;
|
|
7
|
+
const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
8
|
+
const own = (o, k) => (Object.hasOwn(o, k) ? o[k] : undefined);
|
|
9
|
+
const freezeRow = (row) => Object.freeze(row);
|
|
10
|
+
function pieceOf(p) {
|
|
11
|
+
return freezeRow({ code: p.code, path: Object.freeze([...p.path]), ...(typeof p.field === 'string' ? { field: p.field } : {}) });
|
|
12
|
+
}
|
|
13
|
+
const EMPTY_MASK = Object.freeze({
|
|
14
|
+
hooksDropped: false,
|
|
15
|
+
eventDropped: () => false,
|
|
16
|
+
groupDropped: () => false,
|
|
17
|
+
entryDropped: () => false,
|
|
18
|
+
});
|
|
19
|
+
export function hooksSalvageMaskOf(dropped) {
|
|
20
|
+
if (dropped.length === 0)
|
|
21
|
+
return EMPTY_MASK;
|
|
22
|
+
let all = false;
|
|
23
|
+
const events = new Set();
|
|
24
|
+
const groups = new Set();
|
|
25
|
+
const entries = new Set();
|
|
26
|
+
for (const d of dropped) {
|
|
27
|
+
const p = d.path;
|
|
28
|
+
if (p[0] !== 'hooks') {
|
|
29
|
+
all = true;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (p.length === 1)
|
|
33
|
+
all = true;
|
|
34
|
+
else if (p.length === 2 && typeof p[1] === 'string')
|
|
35
|
+
events.add(p[1]);
|
|
36
|
+
else if (p.length === 3 && typeof p[1] === 'string' && typeof p[2] === 'number')
|
|
37
|
+
groups.add(`${p[1]}\u0000${p[2]}`);
|
|
38
|
+
else if (p.length === 5 && typeof p[1] === 'string' && typeof p[2] === 'number' && p[3] === 'hooks' && typeof p[4] === 'number')
|
|
39
|
+
entries.add(`${p[1]}\u0000${p[2]}\u0000${p[4]}`);
|
|
40
|
+
else
|
|
41
|
+
all = true;
|
|
42
|
+
}
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
hooksDropped: all,
|
|
45
|
+
eventDropped: (event) => all || events.has(event),
|
|
46
|
+
groupDropped: (event, gi) => all || events.has(event) || groups.has(`${event}\u0000${gi}`),
|
|
47
|
+
entryDropped: (event, gi, hi) => all || events.has(event) || groups.has(`${event}\u0000${gi}`) || entries.has(`${event}\u0000${gi}\u0000${hi}`),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function pruneHooks(hooks, mask) {
|
|
51
|
+
if (mask.hooksDropped || !isRecord(hooks))
|
|
52
|
+
return undefined;
|
|
53
|
+
const out = [];
|
|
54
|
+
for (const event of Object.keys(hooks)) {
|
|
55
|
+
if (mask.eventDropped(event))
|
|
56
|
+
continue;
|
|
57
|
+
const groups = own(hooks, event);
|
|
58
|
+
if (!Array.isArray(groups)) {
|
|
59
|
+
out.push([event, groups]);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const keptGroups = [];
|
|
63
|
+
let changed = false;
|
|
64
|
+
groups.forEach((group, gi) => {
|
|
65
|
+
if (mask.groupDropped(event, gi)) {
|
|
66
|
+
changed = true;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const entries = isRecord(group) ? own(group, 'hooks') : undefined;
|
|
70
|
+
if (!isRecord(group) || !Array.isArray(entries)) {
|
|
71
|
+
keptGroups.push(group);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const keptEntries = entries.filter((_e, hi) => !mask.entryDropped(event, gi, hi));
|
|
75
|
+
if (keptEntries.length === entries.length) {
|
|
76
|
+
keptGroups.push(group);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
changed = true;
|
|
80
|
+
if (keptEntries.length > 0)
|
|
81
|
+
keptGroups.push({ ...group, hooks: keptEntries });
|
|
82
|
+
});
|
|
83
|
+
if (!changed)
|
|
84
|
+
out.push([event, groups]);
|
|
85
|
+
else if (keptGroups.length > 0)
|
|
86
|
+
out.push([event, keptGroups]);
|
|
87
|
+
}
|
|
88
|
+
return out.length > 0 ? Object.fromEntries(out) : undefined;
|
|
89
|
+
}
|
|
90
|
+
export function salvageSettingsHooksEntries(doc) {
|
|
91
|
+
const r = SETTINGS_CLASSIFY_HOOKS_CONFIG(doc);
|
|
92
|
+
if (r.outcome === 'rejected') {
|
|
93
|
+
return Object.freeze({ outcome: 'rejected', hooks: undefined, dropped: Object.freeze([]), rejections: Object.freeze(r.rejections.map(pieceOf)) });
|
|
94
|
+
}
|
|
95
|
+
const original = isRecord(doc) ? own(doc, 'hooks') : undefined;
|
|
96
|
+
if (r.outcome === 'loaded') {
|
|
97
|
+
return Object.freeze({
|
|
98
|
+
outcome: 'loaded',
|
|
99
|
+
hooks: isRecord(original) ? original : undefined,
|
|
100
|
+
dropped: Object.freeze([]),
|
|
101
|
+
rejections: Object.freeze([]),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const dropped = Object.freeze(r.dropped.map(pieceOf));
|
|
105
|
+
return Object.freeze({
|
|
106
|
+
outcome: 'loaded-partial',
|
|
107
|
+
hooks: pruneHooks(original, hooksSalvageMaskOf(dropped)),
|
|
108
|
+
dropped,
|
|
109
|
+
rejections: Object.freeze([]),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
export const HOOK_SETTINGS_SOURCE_TEXT = Object.freeze({
|
|
113
|
+
policySettings: 'Managed settings',
|
|
114
|
+
userSettings: 'User settings',
|
|
115
|
+
projectSettings: 'Project settings',
|
|
116
|
+
localSettings: 'Local settings',
|
|
117
|
+
flagSettings: 'Flag settings',
|
|
118
|
+
});
|
|
119
|
+
const SALVAGE_LABEL_MAX = 80;
|
|
120
|
+
const SALVAGE_PIECES_SHOWN = 3;
|
|
121
|
+
const labelOf = (s) => (typeof s === 'number' ? String(s) : displayUntrusted(s, { max: SALVAGE_LABEL_MAX }));
|
|
122
|
+
function receivedOf(v) {
|
|
123
|
+
if (v === null)
|
|
124
|
+
return 'null';
|
|
125
|
+
if (Array.isArray(v))
|
|
126
|
+
return 'array';
|
|
127
|
+
return typeof v === 'object' ? 'object' : typeof v;
|
|
128
|
+
}
|
|
129
|
+
function valueAt(doc, path) {
|
|
130
|
+
let cur = doc;
|
|
131
|
+
for (const seg of path) {
|
|
132
|
+
if (typeof seg === 'number')
|
|
133
|
+
cur = Array.isArray(cur) ? cur[seg] : undefined;
|
|
134
|
+
else
|
|
135
|
+
cur = isRecord(cur) ? own(cur, seg) : undefined;
|
|
136
|
+
}
|
|
137
|
+
return cur;
|
|
138
|
+
}
|
|
139
|
+
function whereOf(path) {
|
|
140
|
+
const ev = path[1];
|
|
141
|
+
const gi = path[2];
|
|
142
|
+
const hi = path[4];
|
|
143
|
+
const evText = ev === undefined ? '' : `"${labelOf(ev)}"`;
|
|
144
|
+
if (typeof gi !== 'number')
|
|
145
|
+
return evText;
|
|
146
|
+
return typeof hi === 'number' ? `${evText} (matcher ${gi + 1}, hook ${hi + 1})` : `${evText} (matcher ${gi + 1})`;
|
|
147
|
+
}
|
|
148
|
+
const fieldTail = (field) => (field !== undefined ? ` (field "${labelOf(field)}")` : '');
|
|
149
|
+
function dropPhrase(d, doc) {
|
|
150
|
+
switch (d.code) {
|
|
151
|
+
case 'hooks-not-object':
|
|
152
|
+
return `"hooks" must be an object mapping event names to matcher arrays; received ${receivedOf(valueAt(doc, d.path))}. This field was ignored`;
|
|
153
|
+
case 'unknown-event':
|
|
154
|
+
return `Unknown hook event ${whereOf(d.path)} was ignored`;
|
|
155
|
+
case 'event-not-array':
|
|
156
|
+
return `Hook event ${whereOf(d.path)} must be an array of matchers; received ${receivedOf(valueAt(doc, d.path))}. This entry was ignored`;
|
|
157
|
+
case 'matcher-invalid':
|
|
158
|
+
return `an invalid matcher under hook event ${whereOf(d.path)}${fieldTail(d.field)} was ignored`;
|
|
159
|
+
case 'entry-invalid':
|
|
160
|
+
return `an invalid hook under hook event ${whereOf(d.path)}${fieldTail(d.field)} was ignored`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function rejectionPhrase(r) {
|
|
164
|
+
switch (r.code) {
|
|
165
|
+
case 'guard-outside-hooks':
|
|
166
|
+
return `a PreToolUse or PermissionRequest hook is written outside "hooks" (at ${r.path.map(labelOf).join('.') || 'the top level'})`;
|
|
167
|
+
case 'hooks-is-matcher':
|
|
168
|
+
return '"hooks" is written as a matcher or a single hook instead of an object of hook events';
|
|
169
|
+
case 'guard-under-unknown-event':
|
|
170
|
+
return `${whereOf(r.path)} is not a hook event, but it holds PreToolUse or PermissionRequest hooks`;
|
|
171
|
+
case 'guard-event-not-array':
|
|
172
|
+
return `hook event ${whereOf(r.path)} is not an array of matchers`;
|
|
173
|
+
case 'guard-matcher-invalid':
|
|
174
|
+
return `a matcher under hook event ${whereOf(r.path)}${fieldTail(r.field)} is invalid or holds a misplaced hook`;
|
|
175
|
+
case 'guard-entry-invalid':
|
|
176
|
+
return `a hook under hook event ${whereOf(r.path)}${fieldTail(r.field)} is invalid or holds a misplaced hook`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function joinPhrases(phrases) {
|
|
180
|
+
const seen = new Set();
|
|
181
|
+
const uniq = [];
|
|
182
|
+
for (const p of phrases) {
|
|
183
|
+
if (seen.has(p.key))
|
|
184
|
+
continue;
|
|
185
|
+
seen.add(p.key);
|
|
186
|
+
uniq.push(p.text);
|
|
187
|
+
}
|
|
188
|
+
const shown = uniq.slice(0, SALVAGE_PIECES_SHOWN).join('; ');
|
|
189
|
+
return uniq.length > SALVAGE_PIECES_SHOWN ? `${shown}; and ${uniq.length - SALVAGE_PIECES_SHOWN} more` : shown;
|
|
190
|
+
}
|
|
191
|
+
const GUARD_RISK_TAIL = 'a PreToolUse or PermissionRequest hook that cannot be loaded may be what guards the permissions declared beside it, so none of the hooks in this file are applied until it is fixed';
|
|
192
|
+
export function settingsHooksSalvageNotice(salvage, source, doc) {
|
|
193
|
+
const layer = HOOK_SETTINGS_SOURCE_TEXT[source] ?? 'Settings';
|
|
194
|
+
try {
|
|
195
|
+
if (salvage.outcome === 'loaded-partial') {
|
|
196
|
+
const what = joinPhrases(salvage.dropped.map((d) => ({ key: d.path.join('\u0000'), text: dropPhrase(d, doc) })));
|
|
197
|
+
return `${layer}: ${what}. The rest of this file's hooks are applied.`;
|
|
198
|
+
}
|
|
199
|
+
if (salvage.outcome === 'rejected') {
|
|
200
|
+
const why = joinPhrases(salvage.rejections.map((r) => ({ key: r.path.join('\u0000'), text: rejectionPhrase(r) })));
|
|
201
|
+
if (source === 'policySettings') {
|
|
202
|
+
return `${layer}: hooks could not be loaded — ${why}. Requests are not sent while the managed hooks cannot be applied; ask your administrator to fix the managed settings.`;
|
|
203
|
+
}
|
|
204
|
+
return `${layer}: hooks were not loaded — ${why}; ${GUARD_RISK_TAIL}.`;
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return salvage.outcome === 'loaded' ? undefined : `${layer}: some hooks in this file could not be loaded.`;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UiLanguage } from './env/uiLanguage.js';
|
|
1
2
|
export type SkillToolListsReading = {
|
|
2
3
|
kind: 'unobserved';
|
|
3
4
|
} | {
|
|
@@ -12,4 +13,4 @@ export type SkillToolListsReading = {
|
|
|
12
13
|
export declare function projectSkillToolListsCapability(caps: unknown): SkillToolListsReading | undefined;
|
|
13
14
|
export declare function observedSkillToolLists(baseUrl?: string | undefined): SkillToolListsReading;
|
|
14
15
|
export declare function skillToolListsAvailable(reading: SkillToolListsReading): 'yes' | 'no' | 'unknown';
|
|
15
|
-
export declare function skillToolListsDoctorDetail(reading: SkillToolListsReading): string;
|
|
16
|
+
export declare function skillToolListsDoctorDetail(reading: SkillToolListsReading, lang?: UiLanguage): string;
|