@sema-agent/client-core 0.109.0 → 0.111.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 +342 -0
- package/README.md +88 -32
- package/dist/abortableSleep.d.ts +7 -0
- package/dist/abortableSleep.js +37 -0
- package/dist/adapt/arms.js +45 -11
- package/dist/adapt/toolCards.d.ts +3 -1
- package/dist/adapt/toolCards.js +4 -2
- package/dist/adapter/activeRunSelfHeal.d.ts +39 -0
- package/dist/adapter/activeRunSelfHeal.js +171 -3
- package/dist/adapter/downstream/eventToSdkMessage.js +20 -15
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -0
- package/dist/adapter/downstream/terminalToSdkResult.js +11 -0
- package/dist/adapter/runStream.d.ts +5 -0
- package/dist/adapter/runStream.js +15 -3
- package/dist/attachmentRoute.d.ts +20 -0
- package/dist/attachmentRoute.js +39 -0
- package/dist/attachmentsWireCaps.js +1 -1
- package/dist/builtinOutputStyles.d.ts +12 -0
- package/dist/builtinOutputStyles.js +147 -0
- package/dist/compactionStatus.d.ts +9 -0
- package/dist/compactionStatus.js +29 -5
- package/dist/controlRouter.js +2 -1
- package/dist/coreValuePorts.d.ts +39 -1
- package/dist/coreValuePorts.js +10 -0
- package/dist/declared/supportFloorDeclared.js +1 -1
- package/dist/displayUntrusted.d.ts +1 -0
- package/dist/displayUntrusted.js +138 -14
- package/dist/engineErrorCodes.d.ts +3 -0
- package/dist/engineErrorCodes.js +3 -0
- package/dist/engineHealthFacts.d.ts +20 -0
- package/dist/engineHealthFacts.js +42 -1
- package/dist/engineHealthProbeVerdict.d.ts +23 -0
- package/dist/engineHealthProbeVerdict.js +115 -0
- package/dist/engineInlineTaskStats.d.ts +4 -3
- package/dist/engineInlineTaskStats.js +18 -2
- package/dist/engineNoticeCodes.d.ts +10 -0
- package/dist/engineNoticeCodes.js +36 -6
- package/dist/engineRepoint.d.ts +147 -0
- package/dist/engineRepoint.js +310 -0
- package/dist/engineRowRef.d.ts +3 -0
- package/dist/engineRowRef.js +13 -1
- package/dist/engineSessionMessages.d.ts +2 -1
- package/dist/engineSessionMessages.js +5 -0
- package/dist/engineSwapCrossing.d.ts +57 -0
- package/dist/engineSwapCrossing.js +297 -0
- package/dist/engineUnavailable.d.ts +44 -0
- package/dist/engineUnavailable.js +492 -0
- package/dist/errorSupplementRoles.js +2 -1
- package/dist/eventLeg.js +3 -3
- package/dist/fleet/fleetProjection.js +3 -2
- package/dist/generated/engineFactTables.d.ts +2 -0
- package/dist/generated/engineFactTables.js +12 -0
- package/dist/generated/sdkFactTables.d.ts +2 -1
- package/dist/generated/sdkFactTables.js +5 -0
- package/dist/generated/settingsSchemaTables.d.ts +4 -0
- package/dist/generated/settingsSchemaTables.js +1 -0
- package/dist/hitl/approvalAskers.d.ts +6 -0
- package/dist/hitl/approvalAskers.js +30 -0
- package/dist/hitl/approvalCardReasons.js +6 -3
- package/dist/hitl/approvalDelivery.d.ts +1 -0
- package/dist/hitl/approvalDelivery.js +1 -1
- package/dist/hitl/approvalRequestFrameReading.d.ts +7 -0
- package/dist/hitl/approvalRequestFrameReading.js +11 -0
- package/dist/hitl/approvalResolution.d.ts +9 -2
- package/dist/hitl/approvalResolution.js +6 -1
- package/dist/hitl/askGateWire.js +5 -8
- package/dist/hitl/automaticAnswerVerdict.js +2 -2
- package/dist/hitl/decisionConflict.js +6 -3
- package/dist/hitl/decisionRefusal.d.ts +2 -0
- package/dist/hitl/decisionRefusal.js +8 -8
- package/dist/hitl/decisionRefusalCarrier.d.ts +3 -0
- package/dist/hitl/decisionRefusalCarrier.js +348 -0
- package/dist/hitl/decisionSend.d.ts +10 -0
- package/dist/hitl/decisionSend.js +57 -17
- package/dist/hitl/editedApprovalAcceptance.d.ts +25 -0
- package/dist/hitl/editedApprovalAcceptance.js +26 -0
- package/dist/hitl/elicitationRefusal.d.ts +4 -0
- package/dist/hitl/elicitationRefusal.js +23 -0
- package/dist/hitl/frameRouter.d.ts +2 -2
- package/dist/hitl/frameRouter.js +12 -33
- package/dist/hitl/gateIdentity.d.ts +5 -0
- package/dist/hitl/gateIdentity.js +23 -0
- package/dist/hitl/gateLedger.d.ts +0 -4
- package/dist/hitl/gateLedger.js +0 -19
- package/dist/hitl/hitlBridge.d.ts +7 -0
- package/dist/hitl/hitlBridge.js +14 -8
- package/dist/hitl/hitlHostSurface.d.ts +3 -1
- package/dist/hitl/hitlHostSurface.js +14 -4
- package/dist/hitl/hostWithdrawal.d.ts +15 -0
- package/dist/hitl/hostWithdrawal.js +38 -0
- package/dist/hitl/localAllowRedemption.d.ts +67 -0
- package/dist/hitl/localAllowRedemption.js +256 -0
- package/dist/hitl/parkResolver.js +32 -13
- package/dist/hitl/persistedRulesWire.d.ts +16 -0
- package/dist/hitl/persistedRulesWire.js +72 -2
- package/dist/hitl/planReviewWire.d.ts +3 -0
- package/dist/hitl/planReviewWire.js +37 -10
- package/dist/hitl/policyDenyCause.d.ts +1 -1
- package/dist/hitl/policyDenyCause.js +1 -1
- package/dist/hitl/resumeRunningCard.d.ts +1 -0
- package/dist/hitl/resumeRunningCard.js +1 -0
- package/dist/hitl/screenCardCarriers.d.ts +15 -0
- package/dist/hitl/screenCardCarriers.js +41 -0
- package/dist/hitl/screenDecisionCards.d.ts +98 -0
- package/dist/hitl/screenDecisionCards.js +362 -0
- package/dist/hitl/sessionPolicyWire.js +6 -4
- package/dist/hitl/standingRows.d.ts +4 -1
- package/dist/hitl/standingRows.js +33 -1
- package/dist/hitl/toolApprovalWire.d.ts +64 -15
- package/dist/hitl/toolApprovalWire.js +470 -125
- package/dist/hooksWireCaps.js +31 -1
- package/dist/hostSettingNoticeArm.d.ts +3 -0
- package/dist/hostSettingNoticeArm.js +12 -0
- package/dist/hostTurnSettings.d.ts +13 -4
- package/dist/hostTurnSettings.js +150 -10
- package/dist/humanTurnRead.js +11 -1
- package/dist/i18n/messages.en.d.ts +212 -0
- package/dist/i18n/messages.en.js +217 -5
- package/dist/i18n/messages.zh.js +235 -5
- package/dist/i18n/translate.d.ts +3 -0
- package/dist/i18n/translate.js +78 -10
- package/dist/i18n/zh.d.ts +1 -0
- package/dist/i18n/zh.js +1 -0
- package/dist/index.d.ts +56 -16
- package/dist/index.js +40 -12
- package/dist/inheritEnvCapability.js +2 -0
- package/dist/inputAuthor.d.ts +16 -0
- package/dist/inputAuthor.js +44 -0
- package/dist/interactiveHalt.js +2 -1
- package/dist/interactiveInterrupt.js +20 -2
- package/dist/interruptRefusalNote.d.ts +2 -0
- package/dist/interruptRefusalNote.js +17 -0
- package/dist/liveModelCatalog.d.ts +2 -0
- package/dist/liveModelCatalog.js +23 -9
- package/dist/liveQuestionStore.d.ts +4 -1
- package/dist/liveQuestionStore.js +12 -3
- package/dist/localEngineSeatReading.d.ts +2 -2
- package/dist/localEngineSeatReading.js +5 -3
- package/dist/localEngineSpawn.d.ts +0 -7
- package/dist/localEngineSpawn.js +0 -92
- package/dist/localEngineStep.d.ts +2 -2
- package/dist/localEngineStep.js +2 -0
- package/dist/mcpEngineLeg.d.ts +3 -0
- package/dist/mcpEngineLeg.js +38 -1
- package/dist/mcpPanel.js +2 -0
- package/dist/mcpReconnect.d.ts +9 -1
- package/dist/mcpReconnect.js +124 -19
- package/dist/mcpWireCaps.js +1 -1
- package/dist/model/engineModelWords.d.ts +3 -0
- package/dist/model/engineModelWords.js +13 -0
- package/dist/model/subagentModelWord.d.ts +21 -2
- package/dist/model/subagentModelWord.js +100 -29
- package/dist/modelCapabilityProbe.js +7 -43
- package/dist/modsFace.d.ts +8 -1
- package/dist/modsFace.js +27 -0
- package/dist/notifications.d.ts +36 -6
- package/dist/notifications.js +170 -20
- package/dist/ownKey.d.ts +6 -5
- package/dist/ownKey.js +31 -7
- package/dist/peerFrames.d.ts +40 -2
- package/dist/peerFrames.js +158 -3
- package/dist/pendingUserRow.d.ts +4 -0
- package/dist/pendingUserRow.js +16 -0
- package/dist/permissionsDeclaration.d.ts +1 -0
- package/dist/permissionsDeclaration.js +11 -1
- package/dist/planReviewCardFacts.d.ts +3 -0
- package/dist/planReviewCardFacts.js +52 -2
- package/dist/pluginHooksWire.d.ts +10 -1
- package/dist/pluginHooksWire.js +96 -7
- package/dist/printToolResultFrame.js +4 -2
- package/dist/promptBlockedCopy.js +11 -6
- package/dist/promptSuggestionBatch.d.ts +7 -0
- package/dist/promptSuggestionBatch.js +9 -0
- package/dist/promptSuggestionsTail.d.ts +39 -0
- package/dist/promptSuggestionsTail.js +116 -0
- package/dist/replayBody.js +10 -2
- package/dist/request/taskRequest.d.ts +5 -0
- package/dist/request/taskRequest.js +142 -21
- package/dist/requestCoordinate.d.ts +1 -0
- package/dist/requestCoordinate.js +13 -0
- package/dist/resumeRefusalCopy.d.ts +1 -3
- package/dist/resumeRefusalCopy.js +2 -1
- package/dist/retryStatus.d.ts +2 -2
- package/dist/retryStatus.js +2 -0
- package/dist/rewindArchiveReading.js +4 -4
- package/dist/runCancelContext.d.ts +2 -1
- package/dist/runCancelContext.js +1 -0
- package/dist/runPermissionModeSwitch.js +2 -2
- package/dist/scenarioRefusal.js +11 -13
- package/dist/scenarioSkills.js +11 -6
- package/dist/seam.d.ts +27 -2
- package/dist/seam.js +17 -1
- package/dist/seatContract.d.ts +189 -12
- package/dist/seatContract.js +401 -9
- package/dist/sessionBackground.js +2 -1
- package/dist/sessionBackgroundHandoff.js +2 -1
- package/dist/sessionCostReading.d.ts +70 -0
- package/dist/sessionCostReading.js +150 -0
- package/dist/sessionEngine.d.ts +2 -0
- package/dist/sessionEngine.js +111 -0
- package/dist/sessionHooksRoster.d.ts +4 -0
- package/dist/sessionHooksRoster.js +158 -0
- package/dist/sessionMemoryStatus.d.ts +2 -1
- package/dist/sessionMemoryStatus.js +2 -2
- package/dist/sessionReadRedaction.d.ts +7 -0
- package/dist/sessionReadRedaction.js +26 -0
- package/dist/sessionSyncFileHistory.d.ts +4 -0
- package/dist/sessionSyncFileHistory.js +27 -0
- package/dist/settingsHooksSalvage.js +2 -1
- package/dist/sseIdleTriage.d.ts +13 -1
- package/dist/sseIdleTriage.js +25 -6
- package/dist/storeDurabilityWord.d.ts +15 -0
- package/dist/storeDurabilityWord.js +18 -0
- package/dist/storePostureCapability.d.ts +7 -3
- package/dist/storePostureCapability.js +69 -39
- package/dist/subagent/engineSubagentResume.js +2 -2
- package/dist/subagent/engineSubagentTail.js +8 -1
- package/dist/subagentContentStore.d.ts +11 -0
- package/dist/subagentContentStore.js +28 -2
- package/dist/terminalStatuses.d.ts +1 -0
- package/dist/terminalStatuses.js +4 -1
- package/dist/toolResult.d.ts +1 -0
- package/dist/toolResult.js +2 -0
- package/dist/turnFailurePresentation.js +9 -2
- package/dist/typePins.d.ts +11 -0
- package/dist/usageAnalytics.d.ts +17 -0
- package/dist/usageAnalytics.js +44 -2
- package/dist/userAttachmentEnvelope.d.ts +2 -1
- package/dist/userAttachmentEnvelope.js +10 -0
- package/dist/wireErrorTriage.js +2 -1
- package/dist/wireFailureShape.d.ts +6 -5
- package/dist/wireFailureShape.js +150 -38
- package/dist/wireRefusalCopy.d.ts +16 -5
- package/dist/wireRefusalCopy.js +119 -86
- package/docs/INTEGRATION-CLIENTS.md +4228 -21
- package/package.json +7 -3
|
@@ -115,6 +115,7 @@ export declare function assembledRuleRows(input: PermissionsDeclarationInput, ca
|
|
|
115
115
|
export interface PermissionsAllowKeptFacts {
|
|
116
116
|
readonly problemCount?: number;
|
|
117
117
|
readonly managedPolicyPending?: true;
|
|
118
|
+
readonly managedPolicyFetch?: 'in_progress' | 'failed' | 'unknown';
|
|
118
119
|
readonly where?: string;
|
|
119
120
|
}
|
|
120
121
|
export declare function permissionsAllowKeptNotice(report: Pick<PermissionsDeclarationReport, 'kept'>, settingsRead: 'clean' | 'partial' | 'unreadable', facts?: PermissionsAllowKeptFacts): string | undefined;
|
|
@@ -543,11 +543,21 @@ export function permissionsAllowKeptNotice(report, settingsRead, facts) {
|
|
|
543
543
|
const rawCount = readFact('problemCount');
|
|
544
544
|
const rawWhere = readFact('where');
|
|
545
545
|
const managedPending = readFact('managedPolicyPending') === true;
|
|
546
|
+
const rawFetch = readFact('managedPolicyFetch');
|
|
547
|
+
const fetchState = rawFetch === 'in_progress' || rawFetch === 'failed' ? rawFetch : 'unknown';
|
|
546
548
|
const count = typeof rawCount === 'number' && Number.isInteger(rawCount) && rawCount >= 1 ? rawCount : undefined;
|
|
547
549
|
const where = typeof rawWhere === 'string' && rawWhere.length >= 1 && rawWhere.length <= 200 && !/\p{Cc}/u.test(rawWhere) ? rawWhere : undefined;
|
|
548
550
|
const head = `${String(held)} permissions.allow rule${held === 1 ? ' was' : 's were'} not sent to the engine for this run because `;
|
|
549
551
|
if (settingsRead === 'partial' && managedPending && count === 1) {
|
|
550
|
-
|
|
552
|
+
if (fetchState === 'in_progress') {
|
|
553
|
+
return (`${head}the managed policy for your account has not been received yet (sema keeps fetching it; allow rules apply once it arrives). ` +
|
|
554
|
+
'Run again once it has been received (deny and ask rules, and rules given with --allowedTools, are still sent).');
|
|
555
|
+
}
|
|
556
|
+
if (fetchState === 'failed') {
|
|
557
|
+
return (`${head}the managed policy for your account has not been received yet (this run will not fetch again; the next run retries). ` +
|
|
558
|
+
'Run again to retry fetching it (deny and ask rules, and rules given with --allowedTools, are still sent).');
|
|
559
|
+
}
|
|
560
|
+
return (`${head}the managed policy for your account has not been received yet. ` +
|
|
551
561
|
'Run again once it has been received (deny and ask rules, and rules given with --allowedTools, are still sent).');
|
|
552
562
|
}
|
|
553
563
|
const why = settingsRead === 'partial'
|
|
@@ -16,6 +16,7 @@ export interface PlanReviewCardFacts {
|
|
|
16
16
|
readonly taskId: string;
|
|
17
17
|
readonly checkpointId?: string;
|
|
18
18
|
readonly body: PlanReviewBody;
|
|
19
|
+
readonly evidenceRevision?: number;
|
|
19
20
|
}
|
|
20
21
|
export declare function planReviewBodyOf(text: string): Exclude<PlanReviewBody, {
|
|
21
22
|
kind: 'unknown';
|
|
@@ -91,10 +92,12 @@ export interface PlanBodyClaimLeg {
|
|
|
91
92
|
}>;
|
|
92
93
|
overflow: boolean;
|
|
93
94
|
unreadable: boolean;
|
|
95
|
+
readonly seen: Set<string>;
|
|
94
96
|
}
|
|
95
97
|
export declare function openPlanBodyClaimLeg(): PlanBodyClaimLeg;
|
|
96
98
|
export declare function stepPlanBodyClaim(leg: PlanBodyClaimLeg, frame: object, subFlow: boolean): void;
|
|
97
99
|
export declare function planReviewParkFacts(leg: PlanBodyClaimLeg, result: {} | null | undefined): PlanReviewCardFacts | undefined;
|
|
100
|
+
export declare function planReviewClaimStep(leg: PlanBodyClaimLeg, frame: object, subFlow: boolean, seq: string | undefined): PlanReviewCardFacts | undefined;
|
|
98
101
|
export interface PlanReviewFactsTaken {
|
|
99
102
|
readonly sessionKey: string;
|
|
100
103
|
readonly taskId: string;
|
|
@@ -3,6 +3,7 @@ import { readRunTerminal, runTerminalGateKind } from './runTerminal.js';
|
|
|
3
3
|
import { SDK_PLAN_REVIEW_GATE_KINDS } from './generated/sdkFactTables.js';
|
|
4
4
|
import { CORE_PRESENT_PLAN_TOOL_NAME } from './generated/engineFactTables.js';
|
|
5
5
|
import { DEFAULT_SESSION_KEY } from './sessionSlot.js';
|
|
6
|
+
import { doneResultView } from './replayBody.js';
|
|
6
7
|
const PLAN_BODY_MAX_LENGTH = 200_000;
|
|
7
8
|
const PLAN_BODY_UNKNOWN_CAUSES = Object.freeze([
|
|
8
9
|
'not_observed', 'gate_unidentified', 'gate_moved', 'unreadable', 'ambiguous_call', 'malformed_frame', 'conflicting_claims', 'not_presented', 'not_carried',
|
|
@@ -29,6 +30,13 @@ export function mintPlanReviewCardFacts(taskId, checkpointId, body) {
|
|
|
29
30
|
const b = checkpointId === undefined ? unknownBody('gate_unidentified') : body;
|
|
30
31
|
return Object.freeze({ taskId, ...(checkpointId !== undefined ? { checkpointId } : {}), body: Object.isFrozen(b) ? b : Object.freeze({ ...b }) });
|
|
31
32
|
}
|
|
33
|
+
let evidenceRevisionSeq = 0;
|
|
34
|
+
function withEvidenceRevision(facts, evidenceRevision) {
|
|
35
|
+
return Object.freeze({ taskId: facts.taskId, ...(facts.checkpointId !== undefined ? { checkpointId: facts.checkpointId } : {}), body: facts.body, evidenceRevision });
|
|
36
|
+
}
|
|
37
|
+
function isEvidenceRevision(v) {
|
|
38
|
+
return typeof v === 'number' && Number.isSafeInteger(v) && v > 0;
|
|
39
|
+
}
|
|
32
40
|
export function unknownBody(cause) {
|
|
33
41
|
return Object.freeze({ kind: 'unknown', cause });
|
|
34
42
|
}
|
|
@@ -59,7 +67,11 @@ export function planReviewCardFactsFrom(v) {
|
|
|
59
67
|
const checkpointId = ck.present ? ck.value : undefined;
|
|
60
68
|
if (checkpointId === undefined && !(body.kind === 'unknown' && body.cause === 'gate_unidentified'))
|
|
61
69
|
return undefined;
|
|
62
|
-
|
|
70
|
+
const eg = ownData(v, 'evidenceRevision');
|
|
71
|
+
if (eg.present && !isEvidenceRevision(eg.value))
|
|
72
|
+
return undefined;
|
|
73
|
+
const minted = mintPlanReviewCardFacts(taskId, checkpointId, body);
|
|
74
|
+
return eg.present ? withEvidenceRevision(minted, eg.value) : minted;
|
|
63
75
|
}
|
|
64
76
|
catch {
|
|
65
77
|
return undefined;
|
|
@@ -167,6 +179,10 @@ export function commitPlanReviewFacts(ticket, facts, claim, highWater) {
|
|
|
167
179
|
if (prev !== undefined && prev.checkpointId === checkpointId && (isConflict(prev.facts) || !samePlanClaim(prev.claim, claim))) {
|
|
168
180
|
out = mintPlanReviewCardFacts(facts.taskId, checkpointId, unknownBody('conflicting_claims'));
|
|
169
181
|
}
|
|
182
|
+
const reused = prev !== undefined && prev.checkpointId === checkpointId && prev.facts.evidenceRevision !== undefined && samePlanReviewCardFacts(prev.facts, out)
|
|
183
|
+
? prev.facts.evidenceRevision
|
|
184
|
+
: undefined;
|
|
185
|
+
out = withEvidenceRevision(out, reused ?? ++evidenceRevisionSeq);
|
|
170
186
|
rec.gate = { checkpointId, facts: out, claim, generation: ticket.generation, ...(highWater !== undefined ? { highWater } : {}) };
|
|
171
187
|
return out;
|
|
172
188
|
}
|
|
@@ -338,7 +354,7 @@ function foldMarker(rec, frame) {
|
|
|
338
354
|
}
|
|
339
355
|
const LEG_BUFFER_MAX = 256;
|
|
340
356
|
export function openPlanBodyClaimLeg() {
|
|
341
|
-
return { buffer: [], overflow: false, unreadable: false };
|
|
357
|
+
return { buffer: [], overflow: false, unreadable: false, seen: new Set() };
|
|
342
358
|
}
|
|
343
359
|
function bindLeg(leg, taskId) {
|
|
344
360
|
const rec = recordFor(DEFAULT_SESSION_KEY, taskId);
|
|
@@ -425,6 +441,40 @@ export function planReviewParkFacts(leg, result) {
|
|
|
425
441
|
return taskId === undefined ? undefined : mintPlanReviewCardFacts(taskId, checkpointId, unknownBody('unreadable'));
|
|
426
442
|
}
|
|
427
443
|
}
|
|
444
|
+
export function planReviewClaimStep(leg, frame, subFlow, seq) {
|
|
445
|
+
if (claimSeqDedupe(leg, frame, seq) === 'duplicate')
|
|
446
|
+
return undefined;
|
|
447
|
+
stepPlanBodyClaim(leg, frame, subFlow);
|
|
448
|
+
let result;
|
|
449
|
+
try {
|
|
450
|
+
if (frame.type !== 'done')
|
|
451
|
+
return undefined;
|
|
452
|
+
result = frame.result;
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
return undefined;
|
|
456
|
+
}
|
|
457
|
+
return planReviewParkFacts(leg, doneResultView(result).body);
|
|
458
|
+
}
|
|
459
|
+
function claimSeqDedupe(leg, frame, seq) {
|
|
460
|
+
try {
|
|
461
|
+
if (seq !== undefined && classOf(frame) !== 'other') {
|
|
462
|
+
if (leg.seen.has(seq))
|
|
463
|
+
return 'duplicate';
|
|
464
|
+
leg.seen.add(seq);
|
|
465
|
+
while (leg.seen.size > SETTLED_CALLS_MAX) {
|
|
466
|
+
const oldest = leg.seen.values().next();
|
|
467
|
+
if (oldest.done === true)
|
|
468
|
+
break;
|
|
469
|
+
leg.seen.delete(oldest.value);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return 'fresh';
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
return 'class_unreadable';
|
|
476
|
+
}
|
|
477
|
+
}
|
|
428
478
|
function legRecord(leg, taskId) {
|
|
429
479
|
return leg.taskId === undefined || leg.taskId === taskId ? bindLeg(leg, taskId) : recordFor(DEFAULT_SESSION_KEY, taskId);
|
|
430
480
|
}
|
|
@@ -12,8 +12,10 @@ export declare const PLUGIN_HOOK_REASONS: readonly ["projected", "shell_event",
|
|
|
12
12
|
export type PluginHookReason = (typeof PLUGIN_HOOK_REASONS)[number];
|
|
13
13
|
export declare const PLUGIN_HOOK_EXCLUSION_REASONS: readonly ["workspace_untrusted", "governance_unreadable", "policy_disable_all", "startup_mode", "managed_plugins_only", "prompt_not_user_authored"];
|
|
14
14
|
export type PluginHookExclusionReason = (typeof PLUGIN_HOOK_EXCLUSION_REASONS)[number];
|
|
15
|
-
export declare const SETTINGS_HOOK_DROP_REASONS: readonly ["exec_form_unsupported", "mcp_tool_unsupported", "key_not_read", "source_not_authored"];
|
|
15
|
+
export declare const SETTINGS_HOOK_DROP_REASONS: readonly ["exec_form_unsupported", "mcp_tool_unsupported", "key_not_read", "source_not_authored", "workspace_untrusted", "governance_unreadable", "policy_disable_all", "no_executor"];
|
|
16
16
|
export type SettingsHookDropReason = (typeof SETTINGS_HOOK_DROP_REASONS)[number];
|
|
17
|
+
export type HookEntryTypeWord = 'command' | 'prompt' | 'agent' | 'http' | 'mcp_tool';
|
|
18
|
+
export declare const HOOK_ENTRY_TYPE_WORDS: readonly ["command", "prompt", "agent", "http", "mcp_tool"];
|
|
17
19
|
export type HookSettingsSource = 'policySettings' | 'userSettings' | 'projectSettings' | 'localSettings' | 'flagSettings';
|
|
18
20
|
export interface PluginHookKey {
|
|
19
21
|
readonly pluginId: string;
|
|
@@ -34,6 +36,7 @@ export interface SettingsHookDrop {
|
|
|
34
36
|
readonly event: string;
|
|
35
37
|
readonly groupIndex: number;
|
|
36
38
|
readonly hookIndex: number;
|
|
39
|
+
readonly type?: HookEntryTypeWord;
|
|
37
40
|
readonly reason: SettingsHookDropReason;
|
|
38
41
|
readonly notRun: boolean;
|
|
39
42
|
readonly unreadKeys?: readonly (readonly (string | number)[])[];
|
|
@@ -43,6 +46,7 @@ export interface SettingsHookVerdict {
|
|
|
43
46
|
readonly event: string;
|
|
44
47
|
readonly groupIndex: number;
|
|
45
48
|
readonly hookIndex: number;
|
|
49
|
+
readonly type?: HookEntryTypeWord;
|
|
46
50
|
readonly disposition: PluginHookDisposition;
|
|
47
51
|
readonly reason: PluginHookReason;
|
|
48
52
|
}
|
|
@@ -51,6 +55,7 @@ export interface SettingsHookExclusion {
|
|
|
51
55
|
readonly event: string;
|
|
52
56
|
readonly groupIndex: number;
|
|
53
57
|
readonly hookIndex: number;
|
|
58
|
+
readonly type?: HookEntryTypeWord;
|
|
54
59
|
readonly reason: PluginHookExclusionReason;
|
|
55
60
|
}
|
|
56
61
|
export interface SettingsHooksSalvageRow {
|
|
@@ -169,6 +174,9 @@ export declare function decideSessionHooks(input: SessionHookDecisionInput): {
|
|
|
169
174
|
readonly unreadable: number;
|
|
170
175
|
};
|
|
171
176
|
export declare function filterSettingsHookGroups(source: HookSettingsSource, event: string, groups: readonly WireHookGroup[], execFormCapable: boolean, shellRunsType: (type: string | undefined) => boolean, judge: SettingsHookJudge, readerNotRead: readonly string[], mask?: HooksSalvageMask): SettingsHookFilterResult;
|
|
177
|
+
export declare function hookEntryTypeWordOf(entry: {} | null | undefined): {
|
|
178
|
+
readonly type?: HookEntryTypeWord;
|
|
179
|
+
};
|
|
172
180
|
export interface SettingsHookFilterResult {
|
|
173
181
|
readonly groups: WireHookGroup[];
|
|
174
182
|
readonly drops: SettingsHookDrop[];
|
|
@@ -196,6 +204,7 @@ export interface SettingsHooksLocalGroup<T> {
|
|
|
196
204
|
export declare function settingsHooksKeptLocally<T>(plan: HooksWirePlan, event: string, group: SettingsHooksLocalGroup<T>, opts?: {
|
|
197
205
|
readonly engineCompaction?: 'performed' | 'not_performed';
|
|
198
206
|
}): readonly T[];
|
|
207
|
+
export declare function pluginHooksKeptLocally<T>(plan: HooksWirePlan, pluginId: string, event: string, groupIndex: number, hooks: readonly T[]): readonly T[];
|
|
199
208
|
export declare function pluginHookReasonText(reason: PluginHookReason): string;
|
|
200
209
|
export declare function settingsHookDropText(reason: SettingsHookDropReason): string;
|
|
201
210
|
export declare function pluginHookExecutorLabel(v: PluginHookVerdict): string;
|
package/dist/pluginHooksWire.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { displayUntrusted } from './displayUntrusted.js';
|
|
2
2
|
import { appendOwnElement, readOwnDataValue } from './ownKey.js';
|
|
3
|
+
import { inSlotReadOf } from './wireFailureShape.js';
|
|
3
4
|
import { msg, renderMessage } from './i18n/translate.js';
|
|
4
5
|
import { SETTINGS_ENGINE_OWNED_HOOK_EVENTS } from './generated/settingsSchemaTables.js';
|
|
6
|
+
import { SETTINGS_HOOK_EVENTS } from './generated/settingsSchemaFns.js';
|
|
5
7
|
import { HOOK_SETTINGS_SOURCE_TEXT, hooksEntryUnreadKeys, hooksGroupAuthoredSource, hooksGroupUnreadKeys, hooksKeyCoordinateText, hooksSalvageMaskOf, salvageSettingsHooksEntries, } from './settingsHooksSalvage.js';
|
|
6
8
|
export const ENGINE_FIRED_HOOK_EVENTS = SETTINGS_ENGINE_OWNED_HOOK_EVENTS;
|
|
7
9
|
export const PLUGIN_HOOK_DISPOSITIONS = ['engine', 'shell', 'not_run'];
|
|
@@ -40,7 +42,19 @@ export const PLUGIN_HOOK_EXCLUSION_REASONS = [
|
|
|
40
42
|
'managed_plugins_only',
|
|
41
43
|
'prompt_not_user_authored',
|
|
42
44
|
];
|
|
43
|
-
export const SETTINGS_HOOK_DROP_REASONS = [
|
|
45
|
+
export const SETTINGS_HOOK_DROP_REASONS = [
|
|
46
|
+
'exec_form_unsupported',
|
|
47
|
+
'mcp_tool_unsupported',
|
|
48
|
+
'key_not_read',
|
|
49
|
+
'source_not_authored',
|
|
50
|
+
'workspace_untrusted',
|
|
51
|
+
'governance_unreadable',
|
|
52
|
+
'policy_disable_all',
|
|
53
|
+
'no_executor',
|
|
54
|
+
];
|
|
55
|
+
export const HOOK_ENTRY_TYPE_WORDS = Object.freeze(['command', 'prompt', 'agent', 'http', 'mcp_tool']);
|
|
56
|
+
const _hookEntryTypePins = [true, true, true, true];
|
|
57
|
+
void _hookEntryTypePins;
|
|
44
58
|
export function hooksWirePlanShapeOf(v) {
|
|
45
59
|
try {
|
|
46
60
|
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
@@ -135,6 +149,7 @@ function hookExecutorOf(x) {
|
|
|
135
149
|
return judged(x.shellRuns ? 'shell' : 'not_run', why);
|
|
136
150
|
}
|
|
137
151
|
const ENGINE_EVENTS = new Set(ENGINE_FIRED_HOOK_EVENTS);
|
|
152
|
+
const SHELL_OWNED_EVENTS = new Set(SETTINGS_HOOK_EVENTS.filter((e) => !ENGINE_EVENTS.has(e)));
|
|
138
153
|
const USER_CONFIG_REF_ONE = /\$\{user_config\.[^}]+\}/;
|
|
139
154
|
const USER_CONFIG_REF_ALL = /\$\{user_config\.([^}]+)\}/g;
|
|
140
155
|
function isRecord(v) {
|
|
@@ -484,10 +499,23 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
|
|
|
484
499
|
const excluded = [];
|
|
485
500
|
const out = [];
|
|
486
501
|
let stripped = 0;
|
|
487
|
-
const engineEvent =
|
|
502
|
+
const engineEvent = isEngineFiredHookEvent(event);
|
|
503
|
+
const shellOwned = !engineEvent && SHELL_OWNED_EVENTS.has(event);
|
|
488
504
|
groups.forEach((group, groupIndex) => {
|
|
489
505
|
if (mask !== undefined && mask.groupDropped(event, groupIndex))
|
|
490
506
|
return;
|
|
507
|
+
if (!engineEvent) {
|
|
508
|
+
if (!shellOwned || !isRecord(group) || !Array.isArray(group.hooks))
|
|
509
|
+
return;
|
|
510
|
+
group.hooks.forEach((entry, hookIndex) => {
|
|
511
|
+
if (mask !== undefined && mask.entryDropped(event, groupIndex, hookIndex))
|
|
512
|
+
return;
|
|
513
|
+
if (shellRunsType(isRecord(entry) && typeof entry.type === 'string' ? entry.type : undefined))
|
|
514
|
+
return;
|
|
515
|
+
appendOwnElement(drops, { source, event, groupIndex, hookIndex, reason: 'no_executor', notRun: true });
|
|
516
|
+
});
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
491
519
|
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
492
520
|
appendOwnElement(out, group);
|
|
493
521
|
return;
|
|
@@ -513,27 +541,29 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
|
|
|
513
541
|
const sourceNotAuthored = earlier === undefined && groupSourceAuthored && (authored === 'unreadable' || entryUnread !== undefined);
|
|
514
542
|
const unreadKeys = earlier === undefined && !sourceNotAuthored ? unreadKeyPathsOf(event, groupIndex, hookIndex, groupUnread ?? [], entryUnread) : [];
|
|
515
543
|
const reason = earlier ?? (sourceNotAuthored ? 'source_not_authored' : unreadKeys.length > 0 ? 'key_not_read' : undefined);
|
|
544
|
+
const typed = hookEntryTypeWordOf(entry);
|
|
516
545
|
if (reason !== undefined) {
|
|
517
546
|
appendOwnElement(drops, {
|
|
518
547
|
source,
|
|
519
548
|
event,
|
|
520
549
|
groupIndex,
|
|
521
550
|
hookIndex,
|
|
551
|
+
...typed,
|
|
522
552
|
reason,
|
|
523
|
-
notRun:
|
|
553
|
+
notRun: !shellRunsType(isRecord(entry) && typeof entry.type === 'string' ? entry.type : undefined),
|
|
524
554
|
...(reason === 'key_not_read' ? { unreadKeys } : {}),
|
|
525
555
|
});
|
|
526
556
|
changed = true;
|
|
527
557
|
return;
|
|
528
558
|
}
|
|
529
|
-
if (
|
|
559
|
+
if (isRecord(entry) && judgeableSettingsEntry(entry)) {
|
|
530
560
|
const ex = judge(event, group, entry);
|
|
531
561
|
if (ex.kind === 'excluded') {
|
|
532
|
-
appendOwnElement(excluded, { source, event, groupIndex, hookIndex, reason: ex.reason });
|
|
562
|
+
appendOwnElement(excluded, { source, event, groupIndex, hookIndex, ...typed, reason: ex.reason });
|
|
533
563
|
changed = true;
|
|
534
564
|
return;
|
|
535
565
|
}
|
|
536
|
-
appendOwnElement(verdicts, { source, event, groupIndex, hookIndex, disposition: ex.disposition, reason: ex.reason });
|
|
566
|
+
appendOwnElement(verdicts, { source, event, groupIndex, hookIndex, ...typed, disposition: ex.disposition, reason: ex.reason });
|
|
537
567
|
if (ex.disposition === 'shell') {
|
|
538
568
|
changed = true;
|
|
539
569
|
return;
|
|
@@ -558,6 +588,17 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
|
|
|
558
588
|
});
|
|
559
589
|
return { groups: out, drops, stripped, verdicts, excluded };
|
|
560
590
|
}
|
|
591
|
+
export function hookEntryTypeWordOf(entry) {
|
|
592
|
+
try {
|
|
593
|
+
if (!isRecord(entry))
|
|
594
|
+
return {};
|
|
595
|
+
const t = readOwnDataValue(entry, 'type');
|
|
596
|
+
return typeof t === 'string' && HOOK_ENTRY_TYPE_WORDS.includes(t) ? { type: t } : {};
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
return {};
|
|
600
|
+
}
|
|
601
|
+
}
|
|
561
602
|
function judgeableSettingsEntry(entry) {
|
|
562
603
|
const type = entry.type;
|
|
563
604
|
if (typeof type !== 'string' || !ENGINE_HOOK_TYPE_RUNS.has(type))
|
|
@@ -729,6 +770,8 @@ function skipMatcherKey(matcher) {
|
|
|
729
770
|
return matcher === undefined || matcher === null || matcher === '' || matcher === '*' ? 'null' : JSON.stringify(matcher);
|
|
730
771
|
}
|
|
731
772
|
export function settingsHooksKeptLocally(plan, event, group, opts) {
|
|
773
|
+
if (planClosedLocally(plan))
|
|
774
|
+
return [];
|
|
732
775
|
let raw;
|
|
733
776
|
try {
|
|
734
777
|
raw = group?.hooks;
|
|
@@ -802,6 +845,42 @@ export function settingsHooksKeptLocally(plan, event, group, opts) {
|
|
|
802
845
|
return hooks;
|
|
803
846
|
}
|
|
804
847
|
}
|
|
848
|
+
function planClosedLocally(plan) {
|
|
849
|
+
if (typeof plan !== 'object' || plan === null)
|
|
850
|
+
return false;
|
|
851
|
+
let slot;
|
|
852
|
+
try {
|
|
853
|
+
slot = inSlotReadOf(plan, 'closedBy');
|
|
854
|
+
}
|
|
855
|
+
catch {
|
|
856
|
+
return true;
|
|
857
|
+
}
|
|
858
|
+
if (slot.state === 'absent')
|
|
859
|
+
return false;
|
|
860
|
+
if (slot.state !== 'present')
|
|
861
|
+
return true;
|
|
862
|
+
return slot.value !== null && slot.value !== undefined;
|
|
863
|
+
}
|
|
864
|
+
export function pluginHooksKeptLocally(plan, pluginId, event, groupIndex, hooks) {
|
|
865
|
+
if (planClosedLocally(plan))
|
|
866
|
+
return [];
|
|
867
|
+
if (!Array.isArray(hooks))
|
|
868
|
+
return hooks;
|
|
869
|
+
try {
|
|
870
|
+
let kept = null;
|
|
871
|
+
hooks.forEach((h, i) => {
|
|
872
|
+
const engineRuns = pluginHookVerdictOf(plan, pluginId, event, groupIndex, i)?.disposition === 'engine';
|
|
873
|
+
if (engineRuns)
|
|
874
|
+
kept ??= hooks.slice(0, i);
|
|
875
|
+
else if (kept !== null)
|
|
876
|
+
appendOwnElement(kept, h);
|
|
877
|
+
});
|
|
878
|
+
return kept ?? hooks;
|
|
879
|
+
}
|
|
880
|
+
catch {
|
|
881
|
+
return hooks;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
805
884
|
function settingsRowBaseOf(v) {
|
|
806
885
|
if (!isRecord(v))
|
|
807
886
|
return undefined;
|
|
@@ -852,7 +931,7 @@ function settingsExclusionRowOf(v) {
|
|
|
852
931
|
const REASON_TEXT = {
|
|
853
932
|
projected: 'it is sent to the engine',
|
|
854
933
|
shell_event: 'this client runs this event itself',
|
|
855
|
-
no_executor: '
|
|
934
|
+
no_executor: renderMessage(msg('config.hooks.plan.reason.noExecutor')),
|
|
856
935
|
engine_not_local: 'the engine was not started by this client on this machine, so it cannot reach the plugin files',
|
|
857
936
|
engine_locality_unknown: 'it is not known whether the engine was started by this client on this machine, so plugin hooks are not sent to it',
|
|
858
937
|
engine_capability_absent: 'the connected engine does not support plugin hooks',
|
|
@@ -881,6 +960,10 @@ const DROP_TEXT = {
|
|
|
881
960
|
mcp_tool_unsupported: 'MCP tool hooks are not supported by the engine',
|
|
882
961
|
key_not_read: REASON_TEXT.key_not_read,
|
|
883
962
|
source_not_authored: REASON_TEXT.source_not_authored,
|
|
963
|
+
workspace_untrusted: renderMessage(msg('config.hooks.plan.reason.closedUntrusted')),
|
|
964
|
+
governance_unreadable: renderMessage(msg('config.hooks.plan.reason.closedGovernanceUnreadable')),
|
|
965
|
+
policy_disable_all: renderMessage(msg('config.hooks.plan.reason.closedDisableAll')),
|
|
966
|
+
no_executor: REASON_TEXT.no_executor,
|
|
884
967
|
};
|
|
885
968
|
const SOURCE_TEXT = HOOK_SETTINGS_SOURCE_TEXT;
|
|
886
969
|
export function pluginHookReasonText(reason) {
|
|
@@ -958,6 +1041,10 @@ function namedPathsOfRow(d) {
|
|
|
958
1041
|
return typeof d.event === 'string' && Number.isInteger(d.groupIndex) && d.groupIndex >= 0 ? [['hooks', d.event, d.groupIndex, 'source']] : [];
|
|
959
1042
|
case 'exec_form_unsupported':
|
|
960
1043
|
case 'mcp_tool_unsupported':
|
|
1044
|
+
case 'workspace_untrusted':
|
|
1045
|
+
case 'governance_unreadable':
|
|
1046
|
+
case 'policy_disable_all':
|
|
1047
|
+
case 'no_executor':
|
|
961
1048
|
return [];
|
|
962
1049
|
}
|
|
963
1050
|
}
|
|
@@ -1009,6 +1096,8 @@ export function hooksNotRunNotice(plan) {
|
|
|
1009
1096
|
for (const d of plan.settingsDropped) {
|
|
1010
1097
|
if (!d.notRun)
|
|
1011
1098
|
continue;
|
|
1099
|
+
if (d.reason === 'workspace_untrusted' || d.reason === 'policy_disable_all')
|
|
1100
|
+
continue;
|
|
1012
1101
|
const named = d.reason === 'key_not_read' || d.reason === 'source_not_authored' ? namedPathsOfRow(d) : [];
|
|
1013
1102
|
if (named.length > 0 && (d.reason === 'key_not_read' || d.reason === 'source_not_authored')) {
|
|
1014
1103
|
const nk = `${d.source}\u0000${d.reason}`;
|
|
@@ -24,6 +24,8 @@ export function toolEndResultToUserFrame(arm) {
|
|
|
24
24
|
}, undefined, parent === null ? 'leader' : 'subagent');
|
|
25
25
|
const envelope = toolResultEnvelopeParts(outcome);
|
|
26
26
|
const gateView = guardedRead(() => gateOutcomeOf(arm));
|
|
27
|
+
const denialKindRaw = arm._sema_denial_kind;
|
|
28
|
+
const denialKind = typeof denialKindRaw === 'string' ? denialKindRaw : undefined;
|
|
27
29
|
const content = Array.isArray(outcome.body.content) ? outcome.body.content : arm.truncated === true ? `${text}\n…(truncated)` : text;
|
|
28
30
|
return {
|
|
29
31
|
type: 'user',
|
|
@@ -39,9 +41,9 @@ export function toolEndResultToUserFrame(arm) {
|
|
|
39
41
|
],
|
|
40
42
|
},
|
|
41
43
|
parent_tool_use_id: parent,
|
|
42
|
-
...(isCcToolDenialKind(
|
|
44
|
+
...(isCcToolDenialKind(denialKind) ? { _sema_denial_kind: denialKind } : {}),
|
|
43
45
|
...(gateView !== undefined ? { _sema_gate: gateView } : {}),
|
|
44
|
-
...toolResultRowFlagParts(arm, gateView),
|
|
46
|
+
...toolResultRowFlagParts(arm, gateView, denialKind),
|
|
45
47
|
...(Object.hasOwn(envelope, 'tool_use_result') ? { tool_use_result: envelope.tool_use_result } : {}),
|
|
46
48
|
...(Object.hasOwn(envelope, '_sema_degraded') ? { _sema_degraded: envelope._sema_degraded } : {}),
|
|
47
49
|
uuid: rowIdentity.uuid,
|
|
@@ -4,7 +4,10 @@ import { PROMPT_BLOCKED_BY_HOOK_ROW_LEAD } from './rowKind.js';
|
|
|
4
4
|
export { PROMPT_BLOCKED_BY_HOOK_ROW_LEAD };
|
|
5
5
|
const RUN_BLOCKED_REASONED = 'Run blocked';
|
|
6
6
|
const RUN_BLOCKED_NO_REASON = 'Run blocked (the agent reported it cannot proceed)';
|
|
7
|
-
const
|
|
7
|
+
const WORDS_SHOWN_MAX = 1000;
|
|
8
|
+
function shownWords(words) {
|
|
9
|
+
return displayUntrusted(words, { max: WORDS_SHOWN_MAX }).trim();
|
|
10
|
+
}
|
|
8
11
|
const CAUSE_CLAUSE = Object.freeze({
|
|
9
12
|
crashed: 'the hook failed before it answered; the prompt was not submitted',
|
|
10
13
|
timeout: 'the hook did not answer in time; the prompt was not submitted',
|
|
@@ -13,7 +16,7 @@ const CAUSE_CLAUSE = Object.freeze({
|
|
|
13
16
|
function shownHookWords(words) {
|
|
14
17
|
if (words === undefined)
|
|
15
18
|
return undefined;
|
|
16
|
-
const shown = stripSystemReminderTagTokens(
|
|
19
|
+
const shown = stripSystemReminderTagTokens(shownWords(stripSystemReminderTagTokens(words))).trim();
|
|
17
20
|
return shown === '' ? undefined : shown;
|
|
18
21
|
}
|
|
19
22
|
export function runBlockedText(blocked) {
|
|
@@ -29,8 +32,10 @@ export function runBlockedText(blocked) {
|
|
|
29
32
|
if (reason === undefined)
|
|
30
33
|
return RUN_BLOCKED_NO_REASON;
|
|
31
34
|
const unwrapped = unwrapSystemReminder(reason);
|
|
32
|
-
if (unwrapped === reason)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
if (unwrapped === reason) {
|
|
36
|
+
const shown = shownWords(reason);
|
|
37
|
+
return shown === '' ? RUN_BLOCKED_NO_REASON : `${RUN_BLOCKED_REASONED}: ${shown}`;
|
|
38
|
+
}
|
|
39
|
+
const body = shownHookWords(unwrapped);
|
|
40
|
+
return body === undefined ? RUN_BLOCKED_NO_REASON : `${RUN_BLOCKED_REASONED}: ${body}`;
|
|
36
41
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const SUGGESTION_BATCH_CAP = 8;
|
|
2
|
+
export declare const SUGGESTION_CHARS_CAP = 200;
|
|
3
|
+
export interface PromptSuggestionBatch {
|
|
4
|
+
readonly list: readonly string[];
|
|
5
|
+
readonly truncatedCount?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function promptSuggestionBatchOf(raw: {} | null | undefined): PromptSuggestionBatch | undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const SUGGESTION_BATCH_CAP = 8;
|
|
2
|
+
export const SUGGESTION_CHARS_CAP = 200;
|
|
3
|
+
export function promptSuggestionBatchOf(raw) {
|
|
4
|
+
const filtered = (Array.isArray(raw) ? raw : []).filter((s) => typeof s === 'string' && s.trim().length > 0);
|
|
5
|
+
const list = filtered.slice(0, SUGGESTION_BATCH_CAP).map((s) => (s.length > SUGGESTION_CHARS_CAP ? `${s.slice(0, SUGGESTION_CHARS_CAP)}…` : s));
|
|
6
|
+
if (list.length === 0)
|
|
7
|
+
return undefined;
|
|
8
|
+
return { list, ...(filtered.length > list.length ? { truncatedCount: filtered.length - list.length } : {}) };
|
|
9
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AgentClient } from '@sema-agent/sdk';
|
|
2
|
+
import type { LaneProof } from './seam.js';
|
|
3
|
+
export declare const PROMPT_SUGGESTIONS_TAIL_MAX_ATTEMPTS = 6;
|
|
4
|
+
export declare const PROMPT_SUGGESTIONS_TAIL_BUDGET_MS = 12000;
|
|
5
|
+
export interface PromptSuggestionsTailEvent {
|
|
6
|
+
readonly kind: 'prompt_suggestions';
|
|
7
|
+
readonly laneProof: LaneProof;
|
|
8
|
+
readonly suggestions: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export type PromptSuggestionsStreamSeen = 'frame' | 'done' | 'cut';
|
|
11
|
+
export interface PromptSuggestionsTailInput {
|
|
12
|
+
readonly runs: Pick<AgentClient['runs'], 'get'>;
|
|
13
|
+
readonly runId: string;
|
|
14
|
+
readonly sessionId?: string | undefined;
|
|
15
|
+
readonly requested: boolean;
|
|
16
|
+
readonly stream: PromptSuggestionsStreamSeen;
|
|
17
|
+
readonly baseUrl: string | undefined;
|
|
18
|
+
readonly signal?: AbortSignal | undefined;
|
|
19
|
+
readonly sleep?: ((ms: number) => Promise<void>) | undefined;
|
|
20
|
+
readonly now?: (() => number) | undefined;
|
|
21
|
+
}
|
|
22
|
+
export type PromptSuggestionsTailOutcome = {
|
|
23
|
+
readonly kind: 'skipped';
|
|
24
|
+
readonly why: 'not_requested' | 'frame_on_stream' | 'settled_on_stream';
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: 'settled';
|
|
27
|
+
readonly event?: PromptSuggestionsTailEvent;
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: 'not_completed';
|
|
30
|
+
readonly attempts: number;
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: 'gave_up';
|
|
33
|
+
readonly attempts: number;
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: 'unavailable';
|
|
36
|
+
readonly attempts: number;
|
|
37
|
+
};
|
|
38
|
+
export declare function promptSuggestionsOnStream(baseUrl: string | undefined): boolean;
|
|
39
|
+
export declare function followUpPromptSuggestions(input: PromptSuggestionsTailInput): Promise<PromptSuggestionsTailOutcome>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { abortableSleep, requestDeadline } from './abortableSleep.js';
|
|
2
|
+
import { engineCapString, engineCapValue } from './engineCapsCache.js';
|
|
3
|
+
import { readOwnDataValue } from './ownKey.js';
|
|
4
|
+
import { promptSuggestionBatchOf } from './promptSuggestionBatch.js';
|
|
5
|
+
import { engineVersionTier } from './supportFloor.js';
|
|
6
|
+
export const PROMPT_SUGGESTIONS_TAIL_MAX_ATTEMPTS = 6;
|
|
7
|
+
export const PROMPT_SUGGESTIONS_TAIL_BUDGET_MS = 12_000;
|
|
8
|
+
const RETRY_STEP_MS = 750;
|
|
9
|
+
const RETRY_CAP_MS = 3_000;
|
|
10
|
+
const ON_STREAM_SERVER_FLOOR = '7.149.0';
|
|
11
|
+
const MAY_STILL_SUGGEST = Object.freeze(['completed', 'running']);
|
|
12
|
+
export function promptSuggestionsOnStream(baseUrl) {
|
|
13
|
+
try {
|
|
14
|
+
const bit = engineCapValue(baseUrl, 'promptSuggestions');
|
|
15
|
+
if (bit.kind !== 'value' || bit.value !== true)
|
|
16
|
+
return false;
|
|
17
|
+
return engineVersionTier(engineCapString(baseUrl, 'version'), ON_STREAM_SERVER_FLOOR) === 'engine';
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const freeze = (o) => Object.freeze(o);
|
|
24
|
+
const own = (o, key) => {
|
|
25
|
+
try {
|
|
26
|
+
return typeof o === 'object' && o !== null ? readOwnDataValue(o, key) : undefined;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
function eventOf(raw) {
|
|
33
|
+
const batch = promptSuggestionBatchOf(raw);
|
|
34
|
+
return batch === undefined ? undefined : freeze({ kind: 'prompt_suggestions', laneProof: freeze({ lane: 'main' }), suggestions: freeze([...batch.list]) });
|
|
35
|
+
}
|
|
36
|
+
function skipWhy(input) {
|
|
37
|
+
if (own(input, 'requested') !== true)
|
|
38
|
+
return 'not_requested';
|
|
39
|
+
const seen = own(input, 'stream');
|
|
40
|
+
if (seen === 'frame')
|
|
41
|
+
return 'frame_on_stream';
|
|
42
|
+
if (seen === 'done' && promptSuggestionsOnStream(own(input, 'baseUrl')))
|
|
43
|
+
return 'settled_on_stream';
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
export async function followUpPromptSuggestions(input) {
|
|
47
|
+
let attempts = 0;
|
|
48
|
+
try {
|
|
49
|
+
const why = skipWhy(input);
|
|
50
|
+
if (why !== undefined)
|
|
51
|
+
return freeze({ kind: 'skipped', why });
|
|
52
|
+
const runs = own(input, 'runs');
|
|
53
|
+
const runId = own(input, 'runId');
|
|
54
|
+
const get = typeof runs === 'object' && runs !== null ? runs.get : undefined;
|
|
55
|
+
if (typeof get !== 'function' || typeof runId !== 'string' || runId.length === 0)
|
|
56
|
+
return freeze({ kind: 'unavailable', attempts });
|
|
57
|
+
const sessionId = own(input, 'sessionId');
|
|
58
|
+
const signal = own(input, 'signal');
|
|
59
|
+
const sleepIn = own(input, 'sleep');
|
|
60
|
+
const nowIn = own(input, 'now');
|
|
61
|
+
const sleep = typeof sleepIn === 'function' ? sleepIn : (ms) => abortableSleep(ms, signal ?? new AbortController().signal);
|
|
62
|
+
const now = typeof nowIn === 'function' ? nowIn : Date.now;
|
|
63
|
+
const signalState = () => {
|
|
64
|
+
if (signal === undefined || signal === null)
|
|
65
|
+
return 'live';
|
|
66
|
+
try {
|
|
67
|
+
return signal.aborted === true ? 'aborted' : 'live';
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return 'unreadable';
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const t0 = now();
|
|
74
|
+
for (;;) {
|
|
75
|
+
if (signalState() !== 'live')
|
|
76
|
+
return freeze({ kind: 'unavailable', attempts });
|
|
77
|
+
const left = PROMPT_SUGGESTIONS_TAIL_BUDGET_MS - (now() - t0);
|
|
78
|
+
if (!(left > 0))
|
|
79
|
+
return freeze({ kind: 'gave_up', attempts });
|
|
80
|
+
attempts++;
|
|
81
|
+
const d = requestDeadline(left, signal);
|
|
82
|
+
let rec;
|
|
83
|
+
try {
|
|
84
|
+
rec = await d.race(Promise.resolve(get.call(runs, runId, {
|
|
85
|
+
...(typeof sessionId === 'string' && sessionId.length > 0 ? { session: sessionId } : {}),
|
|
86
|
+
signal: d.signal,
|
|
87
|
+
})));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return freeze({ kind: d.expired() ? 'gave_up' : 'unavailable', attempts });
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
d.abort();
|
|
94
|
+
}
|
|
95
|
+
const suggestions = own(rec, 'suggestions');
|
|
96
|
+
if (Array.isArray(suggestions)) {
|
|
97
|
+
const event = eventOf(suggestions);
|
|
98
|
+
return freeze({ kind: 'settled', ...(event !== undefined ? { event } : {}) });
|
|
99
|
+
}
|
|
100
|
+
const status = own(rec, 'status');
|
|
101
|
+
if (typeof status === 'string' && !MAY_STILL_SUGGEST.includes(status))
|
|
102
|
+
return freeze({ kind: 'not_completed', attempts });
|
|
103
|
+
if (attempts >= PROMPT_SUGGESTIONS_TAIL_MAX_ATTEMPTS)
|
|
104
|
+
return freeze({ kind: 'gave_up', attempts });
|
|
105
|
+
const rest = PROMPT_SUGGESTIONS_TAIL_BUDGET_MS - (now() - t0);
|
|
106
|
+
if (!(rest > 0))
|
|
107
|
+
return freeze({ kind: 'gave_up', attempts });
|
|
108
|
+
if (signalState() !== 'live')
|
|
109
|
+
return freeze({ kind: 'unavailable', attempts });
|
|
110
|
+
await sleep(Math.min(RETRY_STEP_MS * attempts, RETRY_CAP_MS, rest));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return freeze({ kind: 'unavailable', attempts });
|
|
115
|
+
}
|
|
116
|
+
}
|