@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.
- package/CHANGELOG.md +72 -0
- package/README.md +10 -8
- package/dist/engineAgentAbsence.d.ts +52 -0
- package/dist/engineAgentAbsence.js +131 -0
- package/dist/engineNoticeCodes.js +4 -0
- package/dist/gateVocabulary.d.ts +4 -0
- package/dist/gateVocabulary.js +28 -0
- package/dist/hitl/askSurvivesPosture.d.ts +15 -0
- package/dist/hitl/askSurvivesPosture.js +31 -0
- package/dist/hitl/persistedRulesWire.d.ts +2 -1
- package/dist/hitl/toolApprovalWire.d.ts +13 -5
- package/dist/hitl/toolApprovalWire.js +16 -2
- package/dist/hooksWireCaps.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/modelIdentityResolvability.d.ts +2 -1
- package/dist/modelIdentityResolvability.js +57 -15
- package/dist/ownKey.d.ts +1 -0
- package/dist/ownKey.js +6 -0
- package/dist/pluginHooksWire.d.ts +1 -1
- package/dist/pluginHooksWire.js +1 -0
- package/docs/INTEGRATION-CLIENTS.md +314 -11
- package/package.json +2 -2
|
@@ -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',
|
package/dist/gateVocabulary.d.ts
CHANGED
|
@@ -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;
|
package/dist/gateVocabulary.js
CHANGED
|
@@ -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
|
|
199
|
-
mandated?: true;
|
|
200
|
-
ruleOffersAbsence?: string;
|
|
201
|
-
|
|
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
|
-
|
|
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
|
-
...(
|
|
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)`);
|
package/dist/hooksWireCaps.js
CHANGED
|
@@ -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]:
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 (
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
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;
|
package/dist/pluginHooksWire.js
CHANGED
|
@@ -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];
|