@sema-agent/client-core 0.83.1 → 0.83.3

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.
@@ -0,0 +1,131 @@
1
+ import { displayUntrusted } from './displayUntrusted.js';
2
+ import { isEnginePanelTaskResident, isStaleEngineAgentPanelEnd, } from './engineAgentPanelStore.js';
3
+ export const ENGINE_AGENT_ABSENT_ROW_TEXT = 'engine no longer reports this agent · it may still be running';
4
+ export const ENGINE_AGENT_ABSENCE_TTL_MS = 30 * 60_000;
5
+ function effectiveTtl(ttlMs) {
6
+ return typeof ttlMs === 'number' && Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : ENGINE_AGENT_ABSENCE_TTL_MS;
7
+ }
8
+ function markOf(row) {
9
+ if (typeof row !== 'object' || row === null)
10
+ return undefined;
11
+ const a = row.absence;
12
+ return typeof a === 'object' && a !== null ? a : undefined;
13
+ }
14
+ export function isEngineAgentAbsentRow(row) {
15
+ return markOf(row) !== undefined;
16
+ }
17
+ export function engineAgentAbsenceMarkOf(row, ev) {
18
+ if (row?.status !== 'running')
19
+ return null;
20
+ if (isEngineAgentAbsentRow(row))
21
+ return null;
22
+ if (typeof ev?.lastSeenAtMs !== 'number' || !Number.isFinite(ev.lastSeenAtMs))
23
+ return null;
24
+ return { lastSeenAtMs: ev.lastSeenAtMs, absentForMs: ev.absentForMs };
25
+ }
26
+ export function engineAgentPanelEventClearsAbsence(ev, current) {
27
+ if (typeof ev !== 'object' || ev === null)
28
+ return false;
29
+ switch (ev.kind) {
30
+ case 'tick':
31
+ case 'fleet-row':
32
+ return true;
33
+ case 'end':
34
+ return !isStaleEngineAgentPanelEnd(ev, current);
35
+ default:
36
+ return false;
37
+ }
38
+ }
39
+ export function engineAgentAbsenceClockStopMs(row) {
40
+ return markOf(row)?.lastSeenAtMs;
41
+ }
42
+ export function engineAgentRowCountsAsRunning(row) {
43
+ if (typeof row !== 'object' || row === null)
44
+ return false;
45
+ if (isEngineAgentAbsentRow(row))
46
+ return false;
47
+ return row.status === 'running' || row.status === 'pending';
48
+ }
49
+ export function tallyEngineAgentRows(rows) {
50
+ const list = Array.isArray(rows) ? rows : Object.values(rows ?? {});
51
+ let running = 0;
52
+ let absent = 0;
53
+ const byStatus = Object.create(null);
54
+ for (const row of list) {
55
+ if (typeof row !== 'object' || row === null)
56
+ continue;
57
+ const r = row;
58
+ if (isEngineAgentAbsentRow(r)) {
59
+ absent++;
60
+ continue;
61
+ }
62
+ if (engineAgentRowCountsAsRunning(r)) {
63
+ running++;
64
+ continue;
65
+ }
66
+ const word = String(r.status);
67
+ byStatus[word] = (byStatus[word] ?? 0) + 1;
68
+ }
69
+ return { running, absent, byStatus };
70
+ }
71
+ export function engineAgentTurnSweepSettles(taskId, row) {
72
+ if (typeof row !== 'object' || row === null)
73
+ return false;
74
+ if (isEnginePanelTaskResident(taskId))
75
+ return false;
76
+ if (row.status !== 'running')
77
+ return false;
78
+ return !isEngineAgentAbsentRow(row);
79
+ }
80
+ export function engineAgentAbsenceTtlDueAtMs(row, ttlMs) {
81
+ const a = markOf(row);
82
+ if (a === undefined || typeof a.lastSeenAtMs !== 'number' || !Number.isFinite(a.lastSeenAtMs))
83
+ return undefined;
84
+ return a.lastSeenAtMs + effectiveTtl(ttlMs);
85
+ }
86
+ export const ENGINE_AGENT_REGISTRY_READINGS = Object.freeze(['running', 'ended', 'not_listed', 'unknown']);
87
+ function registryConfirmsGone(reading) {
88
+ return reading === 'ended' || reading === 'not_listed';
89
+ }
90
+ export function reapEngineAgentAbsentRows(rows, opts) {
91
+ const reclaim = [];
92
+ const heldByViewer = [];
93
+ const now = opts?.nowMs;
94
+ if (typeof now !== 'number' || !Number.isFinite(now))
95
+ return { reclaim, heldByViewer };
96
+ const ttl = effectiveTtl(opts.ttlMs);
97
+ for (const [taskId, row] of Object.entries(rows ?? {})) {
98
+ if (opts.owned !== undefined && !opts.owned.has(taskId))
99
+ continue;
100
+ const a = markOf(row);
101
+ if (a === undefined || typeof a.lastSeenAtMs !== 'number' || !Number.isFinite(a.lastSeenAtMs))
102
+ continue;
103
+ const dueAtMs = a.lastSeenAtMs + ttl;
104
+ if (now < dueAtMs)
105
+ continue;
106
+ const registry = row?.registry;
107
+ if (!registryConfirmsGone(registry))
108
+ continue;
109
+ if (row?.viewing === true) {
110
+ heldByViewer.push(taskId);
111
+ continue;
112
+ }
113
+ reclaim.push({ taskId, lastSeenAtMs: a.lastSeenAtMs, absentForMs: a.absentForMs, dueAtMs, registry });
114
+ }
115
+ return { reclaim, heldByViewer };
116
+ }
117
+ const REMOVE_REASON_DISPLAY_MAX = 64;
118
+ export function engineAgentAbsenceDroppedLine(label, ttlMs, removeReason) {
119
+ const minutes = Math.round(effectiveTtl(ttlMs) / 60_000);
120
+ const reason = typeof removeReason === 'string' && removeReason !== ''
121
+ ? displayUntrusted(removeReason, { max: REMOVE_REASON_DISPLAY_MAX })
122
+ : '';
123
+ const why = reason !== ''
124
+ ? ` · the engine removed it from the fleet list (${reason}); that is not an outcome — read the run to settle it`
125
+ : ' · outcome unknown';
126
+ return `${displayUntrusted(label)} was dropped from the task panel after ${minutes} min without an engine report${why}`;
127
+ }
128
+ export function engineAgentTerminalAfterDropLine(label, status, hasReport) {
129
+ const tail = hasReport ? ' · its final report arrived too late to keep in the panel' : '';
130
+ return `${displayUntrusted(label)} reported ${displayUntrusted(status)} after it was dropped from the task panel${tail}`;
131
+ }
@@ -65,6 +65,8 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
65
65
  'task.halt_unconsumed',
66
66
  'task.late_approval',
67
67
  'task.interrupt_unconsumed',
68
+ 'approval.read_root_granted',
69
+ 'approval.read_root_grant_rejected',
68
70
  'memory.capture_opted_out',
69
71
  'memory.capture_optout_unpersisted',
70
72
  'skills.listing_truncated',
@@ -142,6 +144,8 @@ export const ENGINE_NOTICE_AUDIENCE = Object.freeze({
142
144
  'task.halt_unconsumed': 'user',
143
145
  'task.late_approval': 'user',
144
146
  'task.interrupt_unconsumed': 'user',
147
+ 'approval.read_root_granted': 'user',
148
+ 'approval.read_root_grant_rejected': 'user',
145
149
  'memory.capture_opted_out': 'user',
146
150
  'memory.capture_optout_unpersisted': 'user',
147
151
  'skills.listing_truncated': 'operator',
@@ -14,3 +14,7 @@ export type RuleStoreUnreadableKind = (typeof RULE_STORE_UNREADABLE_KINDS)[numbe
14
14
  export declare function isRuleStoreUnreadableKind(v: unknown): v is RuleStoreUnreadableKind;
15
15
  export declare function ruleStoreUnreadableDetail(kind: RuleStoreUnreadableKind): string;
16
16
  export declare function mandatedApprovalDetail(): string;
17
+ export declare const APPROVAL_MANDATE_WORDS: readonly ["operator_always", "tool_marks", "probe_mandate", "probe_unanswered", "write_protection", "write_protection_unresolved"];
18
+ export type ApprovalMandateWord = (typeof APPROVAL_MANDATE_WORDS)[number];
19
+ export declare function isApprovalMandateWord(v: unknown): v is ApprovalMandateWord;
20
+ export declare function approvalMandateDetail(word: ApprovalMandateWord): string;
@@ -118,3 +118,31 @@ export function ruleStoreUnreadableDetail(kind) {
118
118
  export function mandatedApprovalDetail() {
119
119
  return 'no saved allow rule and no remembered answer can retire this question; each call is settled on its own — by you here, by a hook, or by an automatic check the deployment runs — and an answer covers only that call';
120
120
  }
121
+ export const APPROVAL_MANDATE_WORDS = Object.freeze([
122
+ 'operator_always',
123
+ 'tool_marks',
124
+ 'probe_mandate',
125
+ 'probe_unanswered',
126
+ 'write_protection',
127
+ 'write_protection_unresolved',
128
+ ]);
129
+ const _mandateWordsPin = true;
130
+ void _mandateWordsPin;
131
+ const APPROVAL_MANDATE_SET = new Set(APPROVAL_MANDATE_WORDS);
132
+ export function isApprovalMandateWord(v) {
133
+ return APPROVAL_MANDATE_SET.has(v);
134
+ }
135
+ const APPROVAL_MANDATE_SENTENCES = Object.freeze({
136
+ operator_always: 'this deployment requires a separate confirmation for each shell command, and this call is one of them',
137
+ tool_marks: 'this tool declares that it reaches outside this machine or makes changes that cannot be undone',
138
+ probe_mandate: 'the check run on this call marked it as needing its own confirmation (the built-in shell check does so for a read outside the directories allowed for this session)',
139
+ probe_unanswered: 'the check run on this call did not answer (it timed out or failed), so whether the call stays inside the directories allowed for this session is unknown',
140
+ write_protection: "this change lands on a protected location (the agent's own configuration), which is confirmed one change at a time",
141
+ write_protection_unresolved: 'the protected-location check could not tell where this change lands, so whether it touches a protected location is unknown',
142
+ });
143
+ export function approvalMandateDetail(word) {
144
+ if (typeof word === 'string' && Object.hasOwn(APPROVAL_MANDATE_SENTENCES, word)) {
145
+ return APPROVAL_MANDATE_SENTENCES[word];
146
+ }
147
+ return 'the reason reported for this confirmation is not one this client recognises, so it shows no reason';
148
+ }
@@ -0,0 +1,15 @@
1
+ export interface AskPostureFacts {
2
+ readonly settingsAskRuleMatched?: boolean;
3
+ }
4
+ export type AskPostureSurvivalRoad = 'origin' | 'settings_ask_rule';
5
+ export type AskPostureVerdict = {
6
+ readonly kind: 'must_ask';
7
+ readonly by: AskPostureSurvivalRoad;
8
+ } | {
9
+ readonly kind: 'posture_may_answer';
10
+ } | {
11
+ readonly kind: 'unknown';
12
+ };
13
+ export declare function askSurvivesPosture(req: {
14
+ readonly origin?: string;
15
+ } | null | undefined, facts?: AskPostureFacts | null): AskPostureVerdict;
@@ -0,0 +1,31 @@
1
+ import { readOwnNonEmptyString } from '../ownKey.js';
2
+ const ORIGIN_SURVIVES_POSTURE = Object.freeze({
3
+ content_question: true,
4
+ ancestor_marked: false,
5
+ org_unavailable: true,
6
+ org_rule: true,
7
+ rule_store_unavailable: true,
8
+ hook: true,
9
+ ask_rule: true,
10
+ denial_limit_fallback: true,
11
+ shell_gate_tighten: false,
12
+ safety_tighten: false,
13
+ policy: false,
14
+ });
15
+ const MUST_ASK_BY_ORIGIN = Object.freeze({ kind: 'must_ask', by: 'origin' });
16
+ const MUST_ASK_BY_SETTINGS = Object.freeze({ kind: 'must_ask', by: 'settings_ask_rule' });
17
+ const POSTURE_MAY_ANSWER = Object.freeze({ kind: 'posture_may_answer' });
18
+ const UNKNOWN = Object.freeze({ kind: 'unknown' });
19
+ export function askSurvivesPosture(req, facts) {
20
+ const origin = readOwnNonEmptyString(req, 'origin');
21
+ const rawMatched = typeof facts === 'object' && facts !== null ? facts.settingsAskRuleMatched : undefined;
22
+ const matched = typeof rawMatched === 'boolean' ? rawMatched : undefined;
23
+ const known = origin !== undefined && Object.hasOwn(ORIGIN_SURVIVES_POSTURE, origin);
24
+ if (known && ORIGIN_SURVIVES_POSTURE[origin] === true)
25
+ return MUST_ASK_BY_ORIGIN;
26
+ if (matched === true)
27
+ return MUST_ASK_BY_SETTINGS;
28
+ if (matched === false && (origin === undefined || known))
29
+ return POSTURE_MAY_ANSWER;
30
+ return UNKNOWN;
31
+ }
@@ -16,6 +16,7 @@ export interface RulesFacade {
16
16
  signal?: AbortSignal;
17
17
  }): Promise<CcImportRedeemResult>;
18
18
  }
19
+ export type RulesListFacade = Pick<RulesFacade, 'list'>;
19
20
  export declare function persistedRulesLaneAvailable(baseUrl: string | undefined): boolean;
20
21
  export declare function persistedRulesGovernanceAvailable(baseUrl: string | undefined): boolean;
21
22
  export declare function persistedRulesWriteAvailable(baseUrl: string | undefined): boolean;
@@ -90,7 +91,7 @@ export type ListAllPersistedRulesOutcome = {
90
91
  ok: false;
91
92
  failure: RulesFailure;
92
93
  };
93
- export declare function listAllPersistedRules(facade: RulesFacade, params?: Omit<RuleListParams, 'cursor'>, opts?: {
94
+ export declare function listAllPersistedRules(facade: RulesListFacade & Partial<Omit<RulesFacade, 'list'>>, params?: Omit<RuleListParams, 'cursor'>, opts?: {
94
95
  signal?: AbortSignal;
95
96
  }): Promise<ListAllPersistedRulesOutcome>;
96
97
  declare const DIRECT_WRITE_EXCLUDED_BEHAVIOR = "allow";
@@ -1,5 +1,6 @@
1
1
  import { type GateCurrentPending, type HitlFailureStage, type HitlSafetyCode, type HitlClientLike, type DecideOperationObserver } from './hitlBridge.js';
2
2
  import { type RuleStoreUnreadableKind } from '../gateVocabulary.js';
3
+ import { type ApprovalMandateWord } from '../gateVocabulary.js';
3
4
  import { type DenyAttributionRefusalDetail } from '../wireRefusalCopy.js';
4
5
  import { type DecideReceiptView } from '../decideReceipt.js';
5
6
  import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, DENIAL_LIMIT_KINDS } from '@sema-agent/sdk';
@@ -137,6 +138,7 @@ export interface ApprovalCardRequest {
137
138
  receivedAtMs?: number;
138
139
  ruleOffersAbsence?: string;
139
140
  mandated?: true;
141
+ mandate?: ApprovalMandateWord;
140
142
  denialLimitFallback?: DenialLimitFallback;
141
143
  origin?: string;
142
144
  ruleStoreUnreadable?: RuleStoreUnreadableKind;
@@ -176,6 +178,7 @@ export interface ToolApprovalFrame {
176
178
  ruleEvidence?: unknown;
177
179
  requiresRealApproval?: true;
178
180
  mandated?: true;
181
+ mandate?: string;
179
182
  origin?: string;
180
183
  inputHasBidi?: true;
181
184
  parked?: true;
@@ -194,11 +197,16 @@ export interface ToolApprovalDelegation {
194
197
  depth: number;
195
198
  agentName?: string;
196
199
  }
197
- export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "ruleOffers", "ruleOffersAbsence", "denialLimitFallback", "ruleStoreUnreadable", "readRootCandidate", "mandated", "origin", "inputHasBidi", "parked", "persistedRuleShadowed", "probeCause", "ruleEvidence", "requiresRealApproval", "expiresInMs", "expiresAtMs", "serverNowMs", "delegation", "outcome"];
198
- export declare function approvalIsMandated(v: {
199
- mandated?: true;
200
- ruleOffersAbsence?: string;
201
- } | undefined | null): boolean;
200
+ export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "ruleOffers", "ruleOffersAbsence", "denialLimitFallback", "ruleStoreUnreadable", "readRootCandidate", "mandated", "mandate", "origin", "inputHasBidi", "parked", "persistedRuleShadowed", "probeCause", "ruleEvidence", "requiresRealApproval", "expiresInMs", "expiresAtMs", "serverNowMs", "delegation", "outcome"];
201
+ export interface ApprovalMandatedFacts {
202
+ readonly mandated?: true;
203
+ readonly ruleOffersAbsence?: string;
204
+ readonly mandate?: string;
205
+ }
206
+ export declare function approvalIsMandated(v: ApprovalMandatedFacts | undefined | null): boolean;
207
+ export declare function readApprovalMandate(v: {
208
+ readonly mandate?: string;
209
+ } | undefined | null): ApprovalMandateWord | undefined;
202
210
  export declare function isFromSubagent(frame: ToolApprovalFrame): boolean;
203
211
  export type ToolApprovalRespondDecision = 'allow' | 'allow_session' | 'deny';
204
212
  export interface RespondToolApprovalOpts {
@@ -1,11 +1,13 @@
1
1
  import { DEFAULT_DENY_REASON, DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, denyReasonForWire, findPendingForTask, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
2
2
  import { askParkRowArm } from './askParkRowRouting.js';
3
3
  import { hostLog } from '../host.js';
4
+ import { readOwnNonEmptyString } from '../ownKey.js';
4
5
  import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
5
6
  import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
6
7
  import { observeCancelByDeny, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceEditRefusedOnBlindAsk, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
7
8
  import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
8
9
  import { isRuleStoreUnreadableKind } from '../gateVocabulary.js';
10
+ import { isApprovalMandateWord } from '../gateVocabulary.js';
9
11
  import { denyAttributionRefusalFromError } from '../wireRefusalCopy.js';
10
12
  import { decideFailureNote } from '../decideFailureNote.js';
11
13
  import { readDecideReceipt } from '../decideReceipt.js';
@@ -205,6 +207,8 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
205
207
  ...(pending.governanceForced === true ? { governanceForced: true } : {}),
206
208
  ...(pending.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
207
209
  ...(pending.mandated === true ? { mandated: true } : {}),
210
+ ...((w) => (w !== undefined ? { mandate: w } : {}))(readApprovalMandate(pending)),
211
+ ...((o) => (o !== undefined ? { origin: o } : {}))(readOwnNonEmptyString(pending, 'origin')),
208
212
  ...(pending.gateKind === 'irreversible_ask' ? { irreversibleParkGate: true } : {}),
209
213
  ...(pending.hasBidiControls === true ? { hasBidiControls: true } : {}),
210
214
  ...(ruleOffersReadOnly !== undefined ? { ruleOffersReadOnly } : {}),
@@ -379,6 +383,7 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
379
383
  'ruleStoreUnreadable',
380
384
  'readRootCandidate',
381
385
  'mandated',
386
+ 'mandate',
382
387
  'origin',
383
388
  'inputHasBidi',
384
389
  'parked',
@@ -400,7 +405,15 @@ export function approvalIsMandated(v) {
400
405
  return false;
401
406
  if (v.mandated === true)
402
407
  return true;
403
- return v.ruleOffersAbsence === MANDATED_ABSENCE_WORD;
408
+ if (v.ruleOffersAbsence === MANDATED_ABSENCE_WORD)
409
+ return true;
410
+ return readApprovalMandate(v) !== undefined;
411
+ }
412
+ export function readApprovalMandate(v) {
413
+ if (typeof v !== 'object' || v === null || !Object.hasOwn(v, 'mandate'))
414
+ return undefined;
415
+ const raw = v.mandate;
416
+ return isApprovalMandateWord(raw) ? raw : undefined;
404
417
  }
405
418
  function isToolApprovalDelegation(v) {
406
419
  if (v === null || typeof v !== 'object')
@@ -828,6 +841,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
828
841
  ...(isWireRecordCarrier(frame.ruleEvidence) ? { ruleEvidence: frame.ruleEvidence } : {}),
829
842
  ...(frame.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
830
843
  ...(frame.mandated === true ? { mandated: true } : {}),
844
+ ...((w) => (w !== undefined ? { mandate: w } : {}))(readApprovalMandate(frame)),
831
845
  ...(() => { const rc = readReadRootCandidate(frame.readRootCandidate); return rc !== undefined ? { readRootCandidate: rc } : {}; })(),
832
846
  ...(frame.inputHasBidi === true ? { inputHasBidi: true } : {}),
833
847
  ...(typeof frame.ruleOffersAbsence === 'string' && frame.ruleOffersAbsence !== ''
@@ -835,7 +849,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
835
849
  : {}),
836
850
  ...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
837
851
  ...((k) => (k !== undefined ? { ruleStoreUnreadable: k } : {}))(readRuleStoreUnreadable(frame.ruleStoreUnreadable)),
838
- ...(typeof frame.origin === 'string' && frame.origin !== '' ? { origin: frame.origin } : {}),
852
+ ...((o) => (o !== undefined ? { origin: o } : {}))(readOwnNonEmptyString(frame, 'origin')),
839
853
  });
840
854
  if (argsNotRealInput && card.kind === 'allow' && card.updatedInput !== undefined) {
841
855
  hostLog('error', `liveToolApprovalWire: ${approvalId} card returned an edited approval on a card whose arguments are not the tool's real input (${lane?.argsUnavailable === true ? 'args-unavailable ask' : 'arguments omitted or absent on the frame'}) — nothing sent (the ask stays pending)`);
@@ -7,7 +7,7 @@ import { MAX_HOOK_NOTICE_TEXT_CHARS } from './notifications.js';
7
7
  import { goalStopHookMatcher, __resetGoalStopHookForTests } from './goalStopHook.js';
8
8
  export { GOAL_STOP_HOOK_WIRE_ENV, CC_STOP_SEMANTICS_MIN_SERVER, ccStopSemanticsFromVersion, engineCcStopSemantics, isGoalStopHookWireArmed, setWireSessionStopHook, getWireSessionStopHook, getWireSessionStopHookCcSemantics, buildGoalStopHookPrompt, } from './goalStopHook.js';
9
9
  const EDITABLE_HOOK_SOURCES = ['userSettings', 'projectSettings', 'localSettings'];
10
- const ALL_HOOK_SOURCES = ['policySettings', ...EDITABLE_HOOK_SOURCES];
10
+ const ALL_HOOK_SOURCES = ['policySettings', ...EDITABLE_HOOK_SOURCES, 'flagSettings'];
11
11
  const _hookSourceListsPin = [true, true];
12
12
  void _hookSourceListsPin;
13
13
  function hooksLockedToPluginOnly(policyValue) {
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export * from './sdkWireTransit.js';
14
14
  export { SEMA_SEGMENT_ID_KEY } from './adapt/textStream.js';
15
15
  export * from './subagentContentStore.js';
16
16
  export * from './engineAgentPanelStore.js';
17
+ export * from './engineAgentAbsence.js';
17
18
  export * from './fleetAgentPanelProjection.js';
18
19
  export * from './liveQuestionStore.js';
19
20
  export * from './engineInlineTaskStats.js';
@@ -166,6 +167,7 @@ export * from './hitl/sessionPolicyWire.js';
166
167
  export * from './hitl/sessionPolicyDeliverable.js';
167
168
  export * from './hitl/approvalsFeed.js';
168
169
  export * from './hitl/livePendingAsk.js';
170
+ export * from './hitl/askSurvivesPosture.js';
169
171
  export * from './hitl/crashConverged.js';
170
172
  export * from './interactiveHalt.js';
171
173
  export * from './compensations.js';
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export * from './sdkWireTransit.js';
14
14
  export { SEMA_SEGMENT_ID_KEY } from './adapt/textStream.js';
15
15
  export * from './subagentContentStore.js';
16
16
  export * from './engineAgentPanelStore.js';
17
+ export * from './engineAgentAbsence.js';
17
18
  export * from './fleetAgentPanelProjection.js';
18
19
  export * from './liveQuestionStore.js';
19
20
  export * from './engineInlineTaskStats.js';
@@ -163,6 +164,7 @@ export * from './hitl/sessionPolicyWire.js';
163
164
  export * from './hitl/sessionPolicyDeliverable.js';
164
165
  export * from './hitl/approvalsFeed.js';
165
166
  export * from './hitl/livePendingAsk.js';
167
+ export * from './hitl/askSurvivesPosture.js';
166
168
  export * from './hitl/crashConverged.js';
167
169
  export * from './interactiveHalt.js';
168
170
  export * from './compensations.js';
@@ -8,13 +8,14 @@ type ModelIdentityLaneTuple = readonly [
8
8
  export type ModelIdentityLane = ModelIdentityLaneTuple[number];
9
9
  export type ModelIdentityLaneReading = 'complete' | 'partial' | 'absent' | 'unreadable';
10
10
  export type ModelIdentityLanes = {
11
- [L in ModelIdentityLane]: L extends 'modelCatalogFile' ? Exclude<ModelIdentityLaneReading, 'complete'> : ModelIdentityLaneReading;
11
+ [L in ModelIdentityLane]: ModelIdentityLaneReading;
12
12
  };
13
13
  export type EngineAnswerObservation = 'answered' | 'not_observed' | 'unreachable';
14
14
  export interface LocalEngineModelIdentityReading {
15
15
  engine: 'local';
16
16
  lanes: ModelIdentityLanes;
17
17
  engineAnswer?: EngineAnswerObservation;
18
+ engineAcceptsCatalogDefault?: boolean;
18
19
  }
19
20
  export interface RemoteEngineModelIdentityReading {
20
21
  engine: 'remote';
@@ -19,11 +19,12 @@ function laneState(lanes, lane) {
19
19
  if (hit === undefined)
20
20
  return 'not_reported';
21
21
  const v = hit.v;
22
- const _catalogPin = true;
23
- void _catalogPin;
24
- if (v === 'complete')
25
- return lane === 'modelCatalogFile' ? 'unusable' : v;
26
- return v === 'partial' || v === 'absent' || v === 'unreadable' ? v : 'unusable';
22
+ return v === 'complete' || v === 'partial' || v === 'absent' || v === 'unreadable' ? v : 'unusable';
23
+ }
24
+ function catalogDefaultEvidence(catalog, accepts) {
25
+ const _evidencePin = true;
26
+ void _evidencePin;
27
+ return catalog === 'complete' && accepts === true ? { catalog, engineAccepts: accepts } : undefined;
27
28
  }
28
29
  function analyse(reading) {
29
30
  try {
@@ -40,16 +41,36 @@ function analyse(reading) {
40
41
  return malformed();
41
42
  const lanesHit = own(reading, 'lanes')?.v;
42
43
  const order = laneOrder();
43
- if (!isPlainObject(lanesHit))
44
- return { verdict: 'unknown', why: 'lanes', unreadable: [], unreported: [...order], unusable: [] };
44
+ if (!isPlainObject(lanesHit)) {
45
+ return { verdict: 'unknown', why: 'lanes', unreadable: [], unreported: [...order], unusable: [], catalogUnconfirmed: null, catalogRefused: false };
46
+ }
45
47
  const partial = [];
46
48
  const unreadable = [];
47
49
  const unreported = [];
48
50
  const unusable = [];
51
+ let catalogRefused = false;
52
+ let catalogUnconfirmed = null;
49
53
  for (const lane of order) {
50
54
  const s = laneState(lanesHit, lane);
51
- if (s === 'complete')
55
+ if (lane === 'modelCatalogFile') {
56
+ if (s === 'complete') {
57
+ const bit = own(reading, 'engineAcceptsCatalogDefault');
58
+ const evidence = catalogDefaultEvidence(s, bit?.v);
59
+ if (evidence !== undefined)
60
+ return { verdict: 'resolvable', why: 'catalog_default', evidence };
61
+ if (bit !== undefined && bit.v === false) {
62
+ partial.push(lane);
63
+ catalogRefused = true;
64
+ }
65
+ else {
66
+ catalogUnconfirmed = bit === undefined || bit.v === undefined ? 'not_reported' : 'unusable';
67
+ }
68
+ continue;
69
+ }
70
+ }
71
+ else if (s === 'complete') {
52
72
  return { verdict: 'resolvable', why: 'lane', lane };
73
+ }
53
74
  if (s === 'partial')
54
75
  partial.push(lane);
55
76
  else if (s === 'unreadable')
@@ -59,10 +80,10 @@ function analyse(reading) {
59
80
  else if (s === 'unusable')
60
81
  unusable.push(lane);
61
82
  }
62
- if (unreadable.length > 0 || unreported.length > 0 || unusable.length > 0) {
63
- return { verdict: 'unknown', why: 'lanes', unreadable, unreported, unusable };
83
+ if (unreadable.length > 0 || unreported.length > 0 || unusable.length > 0 || catalogUnconfirmed !== null) {
84
+ return { verdict: 'unknown', why: 'lanes', unreadable, unreported, unusable, catalogUnconfirmed, catalogRefused };
64
85
  }
65
- return { verdict: 'not_resolvable', partial };
86
+ return { verdict: 'not_resolvable', partial, catalogRefused };
66
87
  }
67
88
  catch {
68
89
  return malformed();
@@ -97,13 +118,25 @@ export function modelIdentityDetail(reading) {
97
118
  if (a.why === 'engine_answered') {
98
119
  return 'the engine is up and answering, and it does not start without a model name, so it has one — whether its gateway accepts that name is a separate check';
99
120
  }
121
+ if (a.why === 'catalog_default') {
122
+ return `${laneLabel('modelCatalogFile')} supplies a model name as its default, and the local engine accepts a catalog default in place of a startup model name, so it can start with it — whether its gateway accepts that name is a separate check`;
123
+ }
100
124
  return `${laneLabel(a.lane)} supplies a model name, so the local engine can start with it — whether its gateway accepts that name is a separate check`;
101
- case 'not_resolvable':
102
- if (a.partial.length > 0) {
103
- const labels = joinPhrases(a.partial.map(laneLabel));
104
- return `no model name is configured for the local engine — ${labels} ${a.partial.length > 1 ? 'carry' : 'carries'} part of a model configuration (such as a gateway address or a credential) but no model name, and the engine does not start without one`;
125
+ case 'not_resolvable': {
126
+ const others = a.catalogRefused ? a.partial.filter((l) => l !== 'modelCatalogFile') : a.partial;
127
+ const partialClause = others.length > 0
128
+ ? `${joinPhrases(others.map(laneLabel))} ${others.length > 1 ? 'carry' : 'carries'} part of a model configuration (such as a gateway address or a credential) but no model name`
129
+ : '';
130
+ if (a.catalogRefused) {
131
+ return (`no model name is configured for the local engine to start with — ${laneLabel('modelCatalogFile')} names a default model, but the local engine is reported not to accept a catalog default in place of a startup model name` +
132
+ (partialClause !== '' ? `, and ${partialClause}` : '') +
133
+ ', and the engine does not start without one');
134
+ }
135
+ if (others.length > 0) {
136
+ return `no model name is configured for the local engine — ${partialClause}, and the engine does not start without one`;
105
137
  }
106
138
  return 'no model name is configured for the local engine — none of the places this client reads supplies one, and the engine does not start without it';
139
+ }
107
140
  case 'unknown':
108
141
  if (a.why === 'remote') {
109
142
  return a.answer === 'unreachable'
@@ -116,6 +149,15 @@ export function modelIdentityDetail(reading) {
116
149
  ...a.unreported.map((l) => `${laneLabel(l)} was not reported`),
117
150
  ...a.unusable.map((l) => `${laneLabel(l)} gave no usable reading`),
118
151
  ];
152
+ if (a.catalogUnconfirmed === 'not_reported') {
153
+ parts.push(`${laneLabel('modelCatalogFile')} names a default model, but whether the local engine accepts a catalog default in place of a startup model name was not reported`);
154
+ }
155
+ else if (a.catalogUnconfirmed === 'unusable') {
156
+ parts.push(`${laneLabel('modelCatalogFile')} names a default model, but the report of whether the local engine accepts a catalog default in place of a startup model name gave no usable reading`);
157
+ }
158
+ else if (a.catalogRefused) {
159
+ parts.push(`${laneLabel('modelCatalogFile')} names a default model, but the local engine is reported not to accept a catalog default in place of a startup model name`);
160
+ }
119
161
  return `whether the local engine has a model name is unknown — ${joinPhrases(parts)}, and no other place this client reads provides one`;
120
162
  }
121
163
  return "this reading of the engine's model configuration could not be interpreted, so whether the engine has a model name is unknown";
package/dist/ownKey.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export declare function putOwnKey<T>(table: Record<string, T>, key: string, value: T): void;
2
+ export declare function readOwnNonEmptyString(v: object | null | undefined, key: string): string | undefined;
package/dist/ownKey.js CHANGED
@@ -1,3 +1,9 @@
1
1
  export function putOwnKey(table, key, value) {
2
2
  Object.defineProperty(table, key, { value, enumerable: true, writable: true, configurable: true });
3
3
  }
4
+ export function readOwnNonEmptyString(v, key) {
5
+ if (typeof v !== 'object' || v === null || !Object.hasOwn(v, key))
6
+ return undefined;
7
+ const raw = v[key];
8
+ return typeof raw === 'string' && raw !== '' ? raw : undefined;
9
+ }
@@ -10,7 +10,7 @@ export declare const PLUGIN_HOOK_EXCLUSION_REASONS: readonly ["workspace_untrust
10
10
  export type PluginHookExclusionReason = (typeof PLUGIN_HOOK_EXCLUSION_REASONS)[number];
11
11
  export declare const SETTINGS_HOOK_DROP_REASONS: readonly ["exec_form_unsupported", "mcp_tool_unsupported"];
12
12
  export type SettingsHookDropReason = (typeof SETTINGS_HOOK_DROP_REASONS)[number];
13
- export type HookSettingsSource = 'policySettings' | 'userSettings' | 'projectSettings' | 'localSettings';
13
+ export type HookSettingsSource = 'policySettings' | 'userSettings' | 'projectSettings' | 'localSettings' | 'flagSettings';
14
14
  export interface PluginHookKey {
15
15
  readonly pluginId: string;
16
16
  readonly pluginName: string;
@@ -332,6 +332,7 @@ const SOURCE_TEXT = {
332
332
  userSettings: 'User settings',
333
333
  projectSettings: 'Project settings',
334
334
  localSettings: 'Local settings',
335
+ flagSettings: 'Flag settings',
335
336
  };
336
337
  export function pluginHookReasonText(reason) {
337
338
  return REASON_TEXT[reason];