@sema-agent/client-core 0.82.7 → 0.83.1
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 +97 -0
- package/README.md +15 -7
- package/dist/adapt/arms.d.ts +3 -2
- package/dist/adapt/arms.js +17 -11
- package/dist/adapt/ids.js +12 -8
- package/dist/adapt/textStream.d.ts +3 -2
- package/dist/adapt/toolCards.d.ts +2 -3
- package/dist/adapt/toolCards.js +1 -1
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +13 -6
- package/dist/adapter/activeRunSelfHeal.js +34 -17
- package/dist/adapter/downstream/eventToSdkMessage.js +1 -1
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -4
- package/dist/adapter/downstream/terminalToSdkResult.js +46 -8
- package/dist/adapter/runStream.js +25 -8
- package/dist/clientSlice.d.ts +0 -5
- package/dist/displayUntrusted.d.ts +24 -0
- package/dist/displayUntrusted.js +1437 -0
- package/dist/engineNoticeCodes.js +4 -0
- package/dist/fleet/fleetRowAgentType.d.ts +0 -1
- package/dist/fleet/fleetRowAgentType.js +0 -3
- package/dist/fleetTaskDesc.js +3 -3
- package/dist/gateVocabulary.js +1 -1
- package/dist/hitl/approvalDecisionNoteAudit.js +4 -2
- package/dist/hitl/approvalResolution.js +2 -0
- package/dist/hitl/frameRouter.d.ts +3 -2
- package/dist/hitl/sessionPolicyDeliverable.d.ts +14 -0
- package/dist/hitl/sessionPolicyDeliverable.js +80 -0
- package/dist/hitl/toolApprovalWire.d.ts +1 -0
- package/dist/hitl/toolApprovalWire.js +56 -10
- package/dist/hooksWireCaps.d.ts +6 -1
- package/dist/hooksWireCaps.js +220 -13
- package/dist/host.d.ts +25 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/liveInitToolFace.d.ts +4 -2
- package/dist/liveInitToolFace.js +2 -2
- package/dist/model/catalogLoader.d.ts +1 -6
- package/dist/panelRunningHistory.d.ts +3 -2
- package/dist/peerFrames.d.ts +0 -1
- package/dist/peerFrames.js +2 -1
- package/dist/pluginHooksWire.d.ts +92 -0
- package/dist/pluginHooksWire.js +429 -0
- package/dist/printInitToolFace.js +1 -1
- package/dist/request/printNotification.js +26 -26
- package/dist/seam.d.ts +8 -2
- package/dist/seam.js +18 -7
- package/dist/subagent/engineSubagentResume.js +2 -1
- package/dist/systemReminderTag.d.ts +0 -1
- package/dist/systemReminderTag.js +0 -3
- package/dist/toolResult.js +1 -0
- package/dist/uuidV5.d.ts +3 -0
- package/dist/uuidV5.js +178 -0
- package/dist/webSearchWireCaps.d.ts +0 -3
- package/dist/webSearchWireCaps.js +21 -51
- package/dist/wireFailureShape.d.ts +2 -1
- package/docs/INTEGRATION-CLIENTS.md +794 -13
- package/package.json +2 -2
|
@@ -3,6 +3,7 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
3
3
|
'config.env_timeout_discarded',
|
|
4
4
|
'config.materialize_env_discarded',
|
|
5
5
|
'config.models_swapped',
|
|
6
|
+
'config.context_settings_swapped',
|
|
6
7
|
'config.read_face_swapped',
|
|
7
8
|
'config.read_face_deployment_clamped',
|
|
8
9
|
'config.write_face_swapped',
|
|
@@ -22,6 +23,7 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
22
23
|
'config.durable_gate_unavailable',
|
|
23
24
|
'config.peer_admission_out_of_range',
|
|
24
25
|
'config.peer_lane_unmounted',
|
|
26
|
+
'config.secret_env_scrubbed',
|
|
25
27
|
'peer.inbound_disposition',
|
|
26
28
|
'peer.held_settled',
|
|
27
29
|
'peer.idle_subscription',
|
|
@@ -78,6 +80,7 @@ export const ENGINE_NOTICE_AUDIENCE = Object.freeze({
|
|
|
78
80
|
'config.env_timeout_discarded': 'operator',
|
|
79
81
|
'config.materialize_env_discarded': 'operator',
|
|
80
82
|
'config.models_swapped': 'operator',
|
|
83
|
+
'config.context_settings_swapped': 'operator',
|
|
81
84
|
'config.read_face_swapped': 'operator',
|
|
82
85
|
'config.read_face_deployment_clamped': 'operator',
|
|
83
86
|
'config.write_face_swapped': 'operator',
|
|
@@ -97,6 +100,7 @@ export const ENGINE_NOTICE_AUDIENCE = Object.freeze({
|
|
|
97
100
|
'config.durable_gate_unavailable': 'user',
|
|
98
101
|
'config.peer_admission_out_of_range': 'operator',
|
|
99
102
|
'config.peer_lane_unmounted': 'operator',
|
|
103
|
+
'config.secret_env_scrubbed': 'operator',
|
|
100
104
|
'peer.inbound_disposition': 'user',
|
|
101
105
|
'peer.held_settled': 'user',
|
|
102
106
|
'peer.idle_subscription': 'user',
|
package/dist/fleetTaskDesc.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { applyDisplayFace, LEGACY_ESCAPE_FACE, LEGACY_LABEL_FACE } from './displayUntrusted.js';
|
|
2
2
|
export function escapeDisplayControlChars(s) {
|
|
3
|
-
return s
|
|
3
|
+
return applyDisplayFace(s, LEGACY_ESCAPE_FACE);
|
|
4
4
|
}
|
|
5
5
|
export function capForDisplay(raw, max) {
|
|
6
6
|
const escaped = escapeDisplayControlChars(raw);
|
|
@@ -19,7 +19,7 @@ export function capForDisplay(raw, max) {
|
|
|
19
19
|
return cut > 0 ? escaped.slice(0, cut) : '';
|
|
20
20
|
}
|
|
21
21
|
export function collapseLabel(s) {
|
|
22
|
-
return
|
|
22
|
+
return applyDisplayFace(s, LEGACY_LABEL_FACE);
|
|
23
23
|
}
|
|
24
24
|
export function taskDescFromName(name, agentType, agentName) {
|
|
25
25
|
if (!name || !agentType)
|
package/dist/gateVocabulary.js
CHANGED
|
@@ -116,5 +116,5 @@ export function ruleStoreUnreadableDetail(kind) {
|
|
|
116
116
|
return `your standing deny/ask rules could not be applied to this call; the reported reason ${word} is not one of the mechanisms this client's closed set knows, so answer this ask as usual`;
|
|
117
117
|
}
|
|
118
118
|
export function mandatedApprovalDetail() {
|
|
119
|
-
return 'no saved allow rule and no
|
|
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
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { displayUntrusted } from '../displayUntrusted.js';
|
|
1
2
|
const NOTE_DISPLAY_MAX = 200;
|
|
2
3
|
export const DECISION_NOTE_NOTICE_KEY = 'approval-decision-note';
|
|
3
4
|
function cleanNote(raw) {
|
|
4
|
-
const flat = raw
|
|
5
|
-
.replace(/[\u0000-\u001f\u007f-\u009f]+/g, ' ')
|
|
5
|
+
const flat = displayUntrusted(raw, { credentialUrls: false, credentialWords: false, foldLines: false, mark: 'space' })
|
|
6
6
|
.replace(/ {2,}/g, ' ')
|
|
7
7
|
.trim();
|
|
8
|
+
if (flat === '')
|
|
9
|
+
return undefined;
|
|
8
10
|
return flat.length <= NOTE_DISPLAY_MAX ? flat : `${flat.slice(0, NOTE_DISPLAY_MAX - 1)}…`;
|
|
9
11
|
}
|
|
10
12
|
export function readDecisionNoteAudit(ack) {
|
|
@@ -70,6 +70,8 @@ function fsLegResolution(outcome, kind) {
|
|
|
70
70
|
}
|
|
71
71
|
if (stage === 'input')
|
|
72
72
|
return { kind: 'not_sent', cause: 'no_input', ...call, ...detail };
|
|
73
|
+
if (stage === 'card' && pick(outcome, 'editRefused') === true)
|
|
74
|
+
return { kind: 'not_sent', cause: 'edit_refused', ...call, ...detail };
|
|
73
75
|
if (stage === 'card')
|
|
74
76
|
return { kind: 'not_sent', cause: 'card_unavailable', ...call, ...detail };
|
|
75
77
|
if (stage === 'decide') {
|
|
@@ -24,7 +24,7 @@ export interface AskGateWireDeps {
|
|
|
24
24
|
onToolApprovalOutcome?: (frame: ToolApprovalFrame, outcome: ToolApprovalFrameOutcome) => void | Promise<void>;
|
|
25
25
|
onDecideOperation?: DecideOperationObserver;
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
type GateKind = 'ask' | 'fs';
|
|
28
28
|
export interface GatePark {
|
|
29
29
|
gate: GateKind;
|
|
30
30
|
pendingDone?: AgentEvent | undefined;
|
|
@@ -55,10 +55,11 @@ export interface FrameRouterCtx {
|
|
|
55
55
|
}
|
|
56
56
|
export declare function isAskTool(toolName: string | undefined): boolean;
|
|
57
57
|
export declare function isHostProgressFrame(ev: AgentEvent): boolean;
|
|
58
|
-
|
|
58
|
+
interface HeldFlushInterruptEvidence {
|
|
59
59
|
terminal?: AgentEvent | undefined;
|
|
60
60
|
signal?: AbortSignal | undefined;
|
|
61
61
|
gateAbortedByUser?: boolean | undefined;
|
|
62
62
|
}
|
|
63
63
|
export declare function flushHeldWithInterruptRewrite(led: GateLedger, evidence: HeldFlushInterruptEvidence): Generator<AgentEvent>;
|
|
64
64
|
export declare function routeFrame(ev: AgentEvent, ctx: FrameRouterCtx): Promise<FrameAction>;
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SessionPermissionRules } from '@sema-agent/sdk';
|
|
2
|
+
export type SessionPolicyWithheldWhy = 'ask_no_bucket' | 'qualified_deny' | 'peer_wide' | 'wildcard' | 'not_a_tool_name';
|
|
3
|
+
export declare const SESSION_POLICY_WITHHELD_WHY: readonly SessionPolicyWithheldWhy[];
|
|
4
|
+
export interface SessionPolicyWithheldRule {
|
|
5
|
+
readonly rule: string;
|
|
6
|
+
readonly why: SessionPolicyWithheldWhy;
|
|
7
|
+
}
|
|
8
|
+
export interface SessionPolicyDeliverability {
|
|
9
|
+
readonly deliverable: SessionPermissionRules;
|
|
10
|
+
readonly withheld: readonly SessionPolicyWithheldRule[];
|
|
11
|
+
readonly unreadable?: true;
|
|
12
|
+
}
|
|
13
|
+
export declare function sessionPolicyDeliverable(behavior: 'deny' | 'ask', rules: readonly string[]): SessionPolicyDeliverability;
|
|
14
|
+
export declare function sessionPolicyWithheldNotice(why: SessionPolicyWithheldWhy): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const WITHHELD_NOTICE = {
|
|
2
|
+
ask_no_bucket: 'Not written to this session’s rule record: that record only holds rules that block outright, and this rule asks before running instead — recording it as a block would change what it means.',
|
|
3
|
+
qualified_deny: 'Not written to this session’s rule record: that record holds whole tool names and command names only, and this rule has a part in parentheses it cannot carry — recording just the name could block more than this rule does.',
|
|
4
|
+
peer_wide: 'Not written to this session’s rule record: this rule covers every tool of one server or agent peer, and an engine that matches that record by exact tool name would block none of them.',
|
|
5
|
+
wildcard: 'Not written to this session’s rule record: this rule uses a wildcard (*), and an engine that matches that record by exact tool name would block none of the tools it covers.',
|
|
6
|
+
not_a_tool_name: 'Not written to this session’s rule record: this entry is not exactly one tool name — it is empty, not text, or has spaces around the name.',
|
|
7
|
+
};
|
|
8
|
+
export const SESSION_POLICY_WITHHELD_WHY = Object.freeze(Object.keys(WITHHELD_NOTICE));
|
|
9
|
+
const MAX_RULES = 10_000;
|
|
10
|
+
function unreadable() {
|
|
11
|
+
return { deliverable: {}, withheld: [], unreadable: true };
|
|
12
|
+
}
|
|
13
|
+
const COVERING_NAMESPACE_PREFIXES = ['mcp__', 'a2a__'];
|
|
14
|
+
function withheldWhyOf(rule) {
|
|
15
|
+
if (rule.length === 0)
|
|
16
|
+
return 'not_a_tool_name';
|
|
17
|
+
if (/(?:^|[^\\])\(/.test(rule))
|
|
18
|
+
return 'qualified_deny';
|
|
19
|
+
if (rule.includes('*'))
|
|
20
|
+
return 'wildcard';
|
|
21
|
+
for (const prefix of COVERING_NAMESPACE_PREFIXES) {
|
|
22
|
+
if (!rule.startsWith(prefix))
|
|
23
|
+
continue;
|
|
24
|
+
const segments = rule.slice(prefix.length).split('__');
|
|
25
|
+
if (segments[0] !== '' && segments.slice(1).join('__') === '')
|
|
26
|
+
return 'peer_wide';
|
|
27
|
+
}
|
|
28
|
+
if (rule.trim() !== rule)
|
|
29
|
+
return 'not_a_tool_name';
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
export function sessionPolicyDeliverable(behavior, rules) {
|
|
33
|
+
const entries = [];
|
|
34
|
+
try {
|
|
35
|
+
if (behavior !== 'deny' && behavior !== 'ask')
|
|
36
|
+
return unreadable();
|
|
37
|
+
if (!Array.isArray(rules))
|
|
38
|
+
return unreadable();
|
|
39
|
+
const n = rules.length;
|
|
40
|
+
if (typeof n !== 'number' || !Number.isSafeInteger(n) || n < 0 || n > MAX_RULES)
|
|
41
|
+
return unreadable();
|
|
42
|
+
for (let i = 0; i < n; i++) {
|
|
43
|
+
if (!Object.hasOwn(rules, i)) {
|
|
44
|
+
entries.push(undefined);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const x = rules[i];
|
|
48
|
+
entries.push(typeof x === 'string' ? x : undefined);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return unreadable();
|
|
53
|
+
}
|
|
54
|
+
const withheld = [];
|
|
55
|
+
if (behavior === 'ask') {
|
|
56
|
+
for (const r of entries)
|
|
57
|
+
withheld.push({ rule: r ?? '', why: 'ask_no_bucket' });
|
|
58
|
+
return { deliverable: {}, withheld };
|
|
59
|
+
}
|
|
60
|
+
const exact = [];
|
|
61
|
+
for (const r of entries) {
|
|
62
|
+
if (r === undefined) {
|
|
63
|
+
withheld.push({ rule: '', why: 'not_a_tool_name' });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const why = withheldWhyOf(r);
|
|
67
|
+
if (why === undefined)
|
|
68
|
+
exact.push(r);
|
|
69
|
+
else
|
|
70
|
+
withheld.push({ rule: r, why });
|
|
71
|
+
}
|
|
72
|
+
if (withheld.length > 0 || exact.length === 0)
|
|
73
|
+
return { deliverable: {}, withheld };
|
|
74
|
+
return { deliverable: { toolDeny: exact }, withheld };
|
|
75
|
+
}
|
|
76
|
+
export function sessionPolicyWithheldNotice(why) {
|
|
77
|
+
return typeof why === 'string' && Object.hasOwn(WITHHELD_NOTICE, why)
|
|
78
|
+
? WITHHELD_NOTICE[why]
|
|
79
|
+
: 'Not written to this session’s rule record.';
|
|
80
|
+
}
|
|
@@ -39,6 +39,7 @@ export type FsApprovalOutcome = {
|
|
|
39
39
|
retryExhausted?: true;
|
|
40
40
|
currentPending?: GateCurrentPending | undefined;
|
|
41
41
|
denyAttributionRefusal?: DenyAttributionRefusalDetail | undefined;
|
|
42
|
+
editRefused?: true;
|
|
42
43
|
};
|
|
43
44
|
export interface FsApprovalWireDeps {
|
|
44
45
|
client: HitlClientLike;
|
|
@@ -140,15 +140,46 @@ function logAfterDecided(msg) {
|
|
|
140
140
|
catch {
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
+
const ARGS_OMITTED_PATH_RECOVERED_NOTE = 'tool arguments exceeded the wire cap and were omitted — only the target path was recovered (from the approval question); ' +
|
|
144
|
+
'the rest of the arguments, such as the content to write, is not shown, so you are deciding without seeing it. ' +
|
|
145
|
+
'Edits are not accepted on this card: there is no original input to edit';
|
|
146
|
+
const ARGS_OMITTED_NOTHING_RECOVERED_NOTE = 'tool arguments exceeded the wire cap and were omitted, and nothing could be recovered from the approval question — ' +
|
|
147
|
+
'you are deciding without seeing what this call will do; deny it if you are not sure. ' +
|
|
148
|
+
'Edits are not accepted on this card: there is no original input to edit';
|
|
149
|
+
const PARK_ROW_ARGS_ABSENT_NOTE = "this request's tool arguments are not available on this surface — neither the event stream nor the pending approval record " +
|
|
150
|
+
'carries them, so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
|
|
151
|
+
'Edits are not accepted on this card: there is no original input to edit';
|
|
152
|
+
const PARK_ROW_ARGS_TRUNCATED_NOTE = "this request's tool arguments were too large to keep on the pending approval record and were replaced by a size marker, " +
|
|
153
|
+
'so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
|
|
154
|
+
'Edits are not accepted on this card: there is no original input to edit';
|
|
155
|
+
const PARK_EDIT_REFUSED_REASON = "the card returned an edited approval, but this request's tool arguments are not available on this surface " +
|
|
156
|
+
'(missing, or replaced by a size marker on the pending approval record), so the edit cannot be applied — nothing was sent; ' +
|
|
157
|
+
'approve or deny it as it is';
|
|
158
|
+
function isParkRowInputSizeMarker(v) {
|
|
159
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
160
|
+
return false;
|
|
161
|
+
const keys = Object.keys(v);
|
|
162
|
+
if (keys.length !== 2 || !keys.includes('truncated') || !keys.includes('bytes'))
|
|
163
|
+
return false;
|
|
164
|
+
const o = v;
|
|
165
|
+
return o.truncated === true && typeof o.bytes === 'number' && Number.isFinite(o.bytes) && o.bytes >= 0;
|
|
166
|
+
}
|
|
143
167
|
export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signal, parkGatedCallId) {
|
|
144
168
|
const found = await findPendingForTask(deps.client, taskId, (toolName) => toolName !== undefined && (toolNameIsFsWrite(toolName) || toolNameIsShellExec(toolName)), signal ? { signal } : undefined, parkGatedCallId, (row) => askParkRowArm(row) === 'tool-gate');
|
|
145
169
|
if (!found.ok)
|
|
146
170
|
return { kind: 'failed', stage: 'fetch', reason: found.reason, ...(found.code !== undefined ? { code: found.code } : {}) };
|
|
147
171
|
const { pending, gatedCallId } = found;
|
|
148
172
|
const toolName = typeof pending.toolName === 'string' ? pending.toolName : '';
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
173
|
+
const streamArgs = gatedCallId !== undefined ? argsByCall.get(gatedCallId) : undefined;
|
|
174
|
+
const rowInput = pending.input;
|
|
175
|
+
const rowArgsGap = streamArgs !== undefined && streamArgs !== null
|
|
176
|
+
? undefined
|
|
177
|
+
: rowInput === undefined || rowInput === null
|
|
178
|
+
? 'absent'
|
|
179
|
+
: isParkRowInputSizeMarker(rowInput)
|
|
180
|
+
? 'truncated'
|
|
181
|
+
: undefined;
|
|
182
|
+
const args = streamArgs !== undefined && streamArgs !== null ? streamArgs : rowArgsGap !== undefined ? {} : rowInput;
|
|
152
183
|
if (typeof args !== 'object' || args === null) {
|
|
153
184
|
return { kind: 'failed', stage: 'input', gatedCallId, reason: 'gate has no tool input payload' };
|
|
154
185
|
}
|
|
@@ -165,8 +196,15 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
165
196
|
args,
|
|
166
197
|
callKey,
|
|
167
198
|
...(signal ? { signal } : {}),
|
|
199
|
+
...(rowArgsGap !== undefined
|
|
200
|
+
? {
|
|
201
|
+
argsUnavailable: true,
|
|
202
|
+
wireNote: rowArgsGap === 'truncated' ? PARK_ROW_ARGS_TRUNCATED_NOTE : PARK_ROW_ARGS_ABSENT_NOTE,
|
|
203
|
+
}
|
|
204
|
+
: {}),
|
|
168
205
|
...(pending.governanceForced === true ? { governanceForced: true } : {}),
|
|
169
206
|
...(pending.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
207
|
+
...(pending.mandated === true ? { mandated: true } : {}),
|
|
170
208
|
...(pending.gateKind === 'irreversible_ask' ? { irreversibleParkGate: true } : {}),
|
|
171
209
|
...(pending.hasBidiControls === true ? { hasBidiControls: true } : {}),
|
|
172
210
|
...(ruleOffersReadOnly !== undefined ? { ruleOffersReadOnly } : {}),
|
|
@@ -184,6 +222,11 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
184
222
|
? { toolCallId: pending.toolCallId }
|
|
185
223
|
: {}),
|
|
186
224
|
});
|
|
225
|
+
if (rowArgsGap !== undefined && card.kind === 'allow' && card.updatedInput !== undefined) {
|
|
226
|
+
hostLog('error', `liveToolApprovalWire: approval card for ${gatedCallId ?? `task ${taskId}`} returned an edited approval, but the tool input is ${rowArgsGap === 'truncated' ? 'a size marker on the pending row' : 'missing from both the stream and the pending row'} — nothing sent (the park stays pending)`);
|
|
227
|
+
surfaceEditRefusedOnBlindAsk();
|
|
228
|
+
return { kind: 'failed', stage: 'card', gatedCallId, reason: PARK_EDIT_REFUSED_REASON, editRefused: true };
|
|
229
|
+
}
|
|
187
230
|
const decideSpan = bridge.openDecideOperation();
|
|
188
231
|
const decideOpts = { ...(signal ? { signal } : {}), operation: decideSpan };
|
|
189
232
|
try {
|
|
@@ -733,10 +776,14 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
733
776
|
? frame.args
|
|
734
777
|
: undefined;
|
|
735
778
|
let wireNote;
|
|
779
|
+
let argsFallback = false;
|
|
736
780
|
if (typeof args !== 'object' || args === null) {
|
|
781
|
+
argsFallback = true;
|
|
782
|
+
const p = pathFromGateMessage(frame.message);
|
|
783
|
+
args = p !== undefined ? { file_path: p } : {};
|
|
737
784
|
if (frame.argsOmitted === true) {
|
|
738
|
-
wireNote =
|
|
739
|
-
hostLog('debug', `liveToolApprovalWire: frame ${approvalId} args omitted (>16KiB wire cap) — card falls back to message-derived path`);
|
|
785
|
+
wireNote = p !== undefined ? ARGS_OMITTED_PATH_RECOVERED_NOTE : ARGS_OMITTED_NOTHING_RECOVERED_NOTE;
|
|
786
|
+
hostLog('debug', `liveToolApprovalWire: frame ${approvalId} args omitted (>16KiB wire cap) — card falls back to ${p !== undefined ? 'the message-derived path' : 'an empty input (no path could be derived from the message)'}`);
|
|
740
787
|
}
|
|
741
788
|
if (lane?.argsUnavailable === true) {
|
|
742
789
|
wireNote =
|
|
@@ -744,9 +791,8 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
744
791
|
'so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
|
|
745
792
|
'Edits are not accepted on this card: there is no original input to edit';
|
|
746
793
|
}
|
|
747
|
-
const p = pathFromGateMessage(frame.message);
|
|
748
|
-
args = p !== undefined ? { file_path: p } : {};
|
|
749
794
|
}
|
|
795
|
+
const argsNotRealInput = argsFallback || lane?.argsUnavailable === true;
|
|
750
796
|
const ruleOffers = readRuleOfferSupply(frame.ruleOffers, frame.ruleSuggestions);
|
|
751
797
|
const denialLimitFallback = readDenialLimitFallback(frame.denialLimitFallback);
|
|
752
798
|
const card = await surfaceApprovalCard({
|
|
@@ -759,7 +805,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
759
805
|
...(signal ? { signal } : {}),
|
|
760
806
|
...(isFromSubagent(frame) ? { workerBadge: subagentBadgeFor(frame) } : {}),
|
|
761
807
|
...(wireNote !== undefined ? { wireNote } : {}),
|
|
762
|
-
...(
|
|
808
|
+
...(argsNotRealInput ? { argsUnavailable: true } : {}),
|
|
763
809
|
...(frame.governanceForced === true ? { governanceForced: true } : {}),
|
|
764
810
|
...(lane?.windowIsCurrent === true
|
|
765
811
|
? {
|
|
@@ -791,8 +837,8 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
791
837
|
...((k) => (k !== undefined ? { ruleStoreUnreadable: k } : {}))(readRuleStoreUnreadable(frame.ruleStoreUnreadable)),
|
|
792
838
|
...(typeof frame.origin === 'string' && frame.origin !== '' ? { origin: frame.origin } : {}),
|
|
793
839
|
});
|
|
794
|
-
if (
|
|
795
|
-
hostLog('error', `liveToolApprovalWire: ${approvalId} card returned an edited approval on
|
|
840
|
+
if (argsNotRealInput && card.kind === 'allow' && card.updatedInput !== undefined) {
|
|
841
|
+
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)`);
|
|
796
842
|
surfaceEditRefusedOnBlindAsk();
|
|
797
843
|
return { decision: 'unresolved', editRefused: true };
|
|
798
844
|
}
|
package/dist/hooksWireCaps.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { type HooksWirePlan, type HooksWirePlanOptions } from './pluginHooksWire.js';
|
|
1
2
|
import { type HookFailureNotice } from './notifications.js';
|
|
2
3
|
import type { WireHooksConfig } from './finalVerifyWire.js';
|
|
3
4
|
export { GOAL_STOP_HOOK_WIRE_ENV, CC_STOP_SEMANTICS_MIN_SERVER, ccStopSemanticsFromVersion, engineCcStopSemantics, isGoalStopHookWireArmed, setWireSessionStopHook, getWireSessionStopHook, getWireSessionStopHookCcSemantics, buildGoalStopHookPrompt, } from './goalStopHook.js';
|
|
4
5
|
export type { WireHooksConfig };
|
|
5
|
-
export
|
|
6
|
+
export interface HooksForWireOptions {
|
|
7
|
+
readonly plan?: HooksWirePlan;
|
|
8
|
+
}
|
|
9
|
+
export declare function hooksForWire(opts?: HooksForWireOptions): WireHooksConfig | undefined;
|
|
10
|
+
export declare function hooksWirePlan(opts?: HooksWirePlanOptions): HooksWirePlan;
|
|
6
11
|
export declare function __resetHooksWireCapsForTests(): void;
|
|
7
12
|
export declare function fleetHookFailureNoticeCapable(baseUrl: string | undefined): boolean;
|
|
8
13
|
export declare const HOOK_FAILURE_WARN_PREFIX = "Hook error";
|