@mrclrchtr/supi-consulting 11.1.0 → 11.2.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/README.md +46 -6
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/package.json +2 -2
- package/scripts/live-probe.ts +6 -3
- package/src/agents/antigravity/adapter.ts +6 -2
- package/src/agents/antigravity/isolated-home.ts +1 -7
- package/src/agents/antigravity/process/event-values.ts +39 -13
- package/src/agents/antigravity/process/events.ts +154 -131
- package/src/agents/antigravity/process/hooks.ts +9 -1
- package/src/agents/antigravity/process/permission-diagnostics.ts +260 -0
- package/src/agents/antigravity/process/protocol.ts +57 -1
- package/src/agents/antigravity/process/runner.ts +50 -19
- package/src/agents/errors.ts +190 -10
- package/src/agents/types.ts +2 -0
- package/src/config.ts +60 -4
- package/src/runtime.ts +3 -1
- package/src/settings.ts +21 -2
- package/src/tool/consulting_run/execute.ts +13 -0
- package/src/tool/consulting_run/guidance.ts +1 -1
- package/src/tool/consulting_run/register.ts +3 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isAbsolute, normalize as normalizePath, relative } from "node:path";
|
|
2
2
|
import { hashEvidence } from "../../../evidence-hash.ts";
|
|
3
3
|
import type { ConsultationUsage } from "../../../types.ts";
|
|
4
|
+
import type { ConsultingDeniedOperation, ConsultingFailureDiagnostics } from "../../errors.ts";
|
|
4
5
|
import { isWebToolName, isWorkspaceToolName } from "../activity.ts";
|
|
5
6
|
import { validateAntigravityAnswer } from "../structured-output.ts";
|
|
6
7
|
import type { AntigravityExecutionFacts } from "../types.ts";
|
|
@@ -13,20 +14,33 @@ import {
|
|
|
13
14
|
extractUrl,
|
|
14
15
|
extractWorkspacePath,
|
|
15
16
|
isErrorStatus,
|
|
16
|
-
isRecord,
|
|
17
|
-
isSuccessStatus,
|
|
18
17
|
isTerminalEvent,
|
|
19
18
|
isToolResultEvent,
|
|
20
19
|
isToolStartEvent,
|
|
21
|
-
safeString,
|
|
22
20
|
toolInput,
|
|
23
21
|
toolName,
|
|
24
22
|
} from "./event-values.ts";
|
|
25
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
classifyDeniedOperation,
|
|
25
|
+
classifyToolResult,
|
|
26
|
+
type DiagnosticReadScope,
|
|
27
|
+
FileResources,
|
|
28
|
+
type FileTargetObservation,
|
|
29
|
+
fileTargetUpdate,
|
|
30
|
+
isPermissionDenial,
|
|
31
|
+
terminalDeniedOperations,
|
|
32
|
+
} from "./permission-diagnostics.ts";
|
|
33
|
+
import {
|
|
34
|
+
hasExplicitSuccess,
|
|
35
|
+
isNonToolStepUpdate,
|
|
36
|
+
isSuccessfulActivity,
|
|
37
|
+
nestedToolEvents,
|
|
38
|
+
normalizeProtocolEvent,
|
|
39
|
+
} from "./protocol.ts";
|
|
26
40
|
import { AntigravityResponseError } from "./response-error.ts";
|
|
27
41
|
import { mergeUsage, readUsage } from "./usage.ts";
|
|
28
42
|
|
|
29
|
-
interface PendingTool {
|
|
43
|
+
interface PendingTool extends FileTargetObservation {
|
|
30
44
|
name: string;
|
|
31
45
|
urlHash?: string;
|
|
32
46
|
pathHash?: string;
|
|
@@ -34,19 +48,16 @@ interface PendingTool {
|
|
|
34
48
|
denied?: boolean;
|
|
35
49
|
}
|
|
36
50
|
|
|
37
|
-
interface ToolResultClassification {
|
|
38
|
-
kind: "pending" | "failure" | "success";
|
|
39
|
-
denied: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
51
|
const MAX_COMPLETED_TOOL_IDS = 4_096;
|
|
43
52
|
|
|
44
53
|
/**
|
|
45
54
|
* Reduces parsed events to bounded execution facts. Raw events and tool payloads
|
|
46
|
-
* are not retained
|
|
55
|
+
* are not retained; only bounded structured requested file targets survive pending calls.
|
|
47
56
|
*/
|
|
48
57
|
export class AntigravityEventAccumulator {
|
|
49
58
|
readonly #workspaceDirectory: string | undefined;
|
|
59
|
+
readonly #readScope: DiagnosticReadScope;
|
|
60
|
+
readonly #expectedResponseToken: string | undefined;
|
|
50
61
|
#pendingTools = new Map<string, PendingTool>();
|
|
51
62
|
#completedToolIds = new Set<string>();
|
|
52
63
|
#conversationId: string | undefined;
|
|
@@ -58,12 +69,17 @@ export class AntigravityEventAccumulator {
|
|
|
58
69
|
#toolCounts = new Map<string, number>();
|
|
59
70
|
#successfulToolNames = new Set<string>();
|
|
60
71
|
#permissionDenials = 0;
|
|
72
|
+
#permissionDenialsByOperation: Partial<Record<ConsultingDeniedOperation, number>> = {};
|
|
73
|
+
#deniedFileAccessOutsideReadRoots = 0;
|
|
74
|
+
#deniedFileResources = new FileResources();
|
|
61
75
|
#unattributedPermissionDenialSeen = false;
|
|
62
76
|
#sourceHashes = new Set<string>();
|
|
63
77
|
#workspacePathHashes = new Set<string>();
|
|
64
78
|
|
|
65
|
-
constructor(options: {
|
|
79
|
+
constructor(options: DiagnosticReadScope & { expectedResponseToken?: string } = {}) {
|
|
66
80
|
this.#workspaceDirectory = options.workspaceDirectory;
|
|
81
|
+
this.#readScope = options;
|
|
82
|
+
this.#expectedResponseToken = options.expectedResponseToken;
|
|
67
83
|
}
|
|
68
84
|
|
|
69
85
|
/** Consume one already-parsed event. */
|
|
@@ -74,8 +90,9 @@ export class AntigravityEventAccumulator {
|
|
|
74
90
|
conversationId(event) ?? conversationId(normalizedEvent) ?? this.#conversationId;
|
|
75
91
|
this.#usage = mergeUsage(this.#usage, mergeUsage(readUsage(event), readUsage(normalizedEvent)));
|
|
76
92
|
const terminal = isTerminalEvent(normalizedEvent);
|
|
77
|
-
const nestedTools =
|
|
78
|
-
|
|
93
|
+
const nestedTools = nestedToolEvents(normalizedEvent);
|
|
94
|
+
for (const toolEvent of nestedTools) this.consume(toolEvent);
|
|
95
|
+
if (nestedTools.length && !terminal) return;
|
|
79
96
|
|
|
80
97
|
if (isToolStartEvent(normalizedEvent)) {
|
|
81
98
|
this.#rememberToolStart(normalizedEvent);
|
|
@@ -90,27 +107,28 @@ export class AntigravityEventAccumulator {
|
|
|
90
107
|
return;
|
|
91
108
|
}
|
|
92
109
|
|
|
93
|
-
|
|
94
|
-
if (directName && isSuccessfulActivity(normalizedEvent)) {
|
|
95
|
-
this.#recordSuccessfulTool(directName, normalizedEvent);
|
|
96
|
-
}
|
|
97
|
-
const denied = isPermissionDenial(normalizedEvent);
|
|
98
|
-
if (denied && !isNonToolStepUpdate(normalizedEvent)) {
|
|
99
|
-
const counted = directName
|
|
100
|
-
? this.#recordPermissionDenial(directName, normalizedEvent)
|
|
101
|
-
: this.#recordUnattributedPermissionDenial();
|
|
102
|
-
if (counted) this.#permissionDenials += 1;
|
|
103
|
-
}
|
|
110
|
+
this.#consumeActivity(normalizedEvent);
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
/** Return safe execution facts even when the response is incomplete. */
|
|
107
|
-
get diagnostics(): {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
get diagnostics(): ConsultingFailureDiagnostics {
|
|
115
|
+
const denied = this.#deniedFileResources.snapshot;
|
|
116
|
+
const unfinished = new FileResources();
|
|
117
|
+
for (const pending of this.#pendingTools.values()) {
|
|
118
|
+
if (!pending.denied)
|
|
119
|
+
unfinished.record(classifyDeniedOperation(pending.name), pending.requestedResource);
|
|
120
|
+
}
|
|
121
|
+
const pending = unfinished.snapshot;
|
|
112
122
|
return {
|
|
113
123
|
permissionDenials: this.#permissionDenials,
|
|
124
|
+
permissionDenialsByOperation: { ...this.#permissionDenialsByOperation },
|
|
125
|
+
deniedFileAccessOutsideReadRoots: this.#deniedFileAccessOutsideReadRoots,
|
|
126
|
+
deniedResources: denied.resources,
|
|
127
|
+
deniedResourcesOmitted: denied.omitted,
|
|
128
|
+
deniedResourcePathsUnavailable: denied.unavailable,
|
|
129
|
+
unfinishedResources: pending.resources,
|
|
130
|
+
unfinishedResourcesOmitted: pending.omitted,
|
|
131
|
+
unfinishedResourcePathsUnavailable: pending.unavailable,
|
|
114
132
|
terminalSeen: this.#terminalSeen,
|
|
115
133
|
terminalSucceeded: this.#terminalSucceeded,
|
|
116
134
|
};
|
|
@@ -122,8 +140,16 @@ export class AntigravityEventAccumulator {
|
|
|
122
140
|
if (!this.#terminalSucceeded) throw new AntigravityResponseError("terminal-failed");
|
|
123
141
|
if (!this.#candidate) throw new AntigravityResponseError("missing-answer");
|
|
124
142
|
if (!this.#conversationId) throw new AntigravityResponseError("missing-conversation");
|
|
143
|
+
const { responseToken, ...candidate } = this.#candidate;
|
|
144
|
+
const answer = validateAntigravityAnswer(candidate);
|
|
145
|
+
if (
|
|
146
|
+
this.#expectedResponseToken !== undefined &&
|
|
147
|
+
responseToken !== this.#expectedResponseToken
|
|
148
|
+
) {
|
|
149
|
+
throw new AntigravityResponseError("unbound-answer");
|
|
150
|
+
}
|
|
125
151
|
return {
|
|
126
|
-
answer
|
|
152
|
+
answer,
|
|
127
153
|
conversationId: this.#conversationId,
|
|
128
154
|
...(this.#usage ? { usage: this.#usage } : {}),
|
|
129
155
|
observedToolNames: [...this.#toolNames],
|
|
@@ -135,6 +161,44 @@ export class AntigravityEventAccumulator {
|
|
|
135
161
|
};
|
|
136
162
|
}
|
|
137
163
|
|
|
164
|
+
#consumeActivity(event: Record<string, unknown>): void {
|
|
165
|
+
if (isNonToolStepUpdate(event)) return;
|
|
166
|
+
const id = eventId(event);
|
|
167
|
+
if (id && this.#completedToolIds.has(id)) return;
|
|
168
|
+
let pending = this.#pendingTools.get(id ?? "");
|
|
169
|
+
const name = toolName(event) ?? pending?.name;
|
|
170
|
+
if (name && eventType(event) === "step_update") {
|
|
171
|
+
pending = this.#recordPendingToolResult(id, name, event, pending);
|
|
172
|
+
}
|
|
173
|
+
if (isPermissionDenial(event)) {
|
|
174
|
+
this.#observeDeniedActivity(name, event, pending);
|
|
175
|
+
} else if (name) {
|
|
176
|
+
this.#observeExplicitOutcome(name, event, pending);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#observeDeniedActivity(
|
|
181
|
+
name: string | undefined,
|
|
182
|
+
event: Record<string, unknown>,
|
|
183
|
+
pending?: PendingTool,
|
|
184
|
+
): void {
|
|
185
|
+
const counted = name
|
|
186
|
+
? this.#recordPermissionDenial(name, event, pending)
|
|
187
|
+
: this.#recordUnattributedPermissionDenial();
|
|
188
|
+
if (counted) this.#countPermissionDenial(name, event, pending);
|
|
189
|
+
if (isErrorStatus(eventStatus(event))) this.#completeToolResult(eventId(event));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#observeExplicitOutcome(
|
|
193
|
+
name: string,
|
|
194
|
+
event: Record<string, unknown>,
|
|
195
|
+
pending?: PendingTool,
|
|
196
|
+
): void {
|
|
197
|
+
const successful = isSuccessfulActivity(event);
|
|
198
|
+
if (successful) this.#recordSuccessfulTool(name, event, pending);
|
|
199
|
+
if (successful || isErrorStatus(eventStatus(event))) this.#completeToolResult(eventId(event));
|
|
200
|
+
}
|
|
201
|
+
|
|
138
202
|
#rememberToolStart(event: Record<string, unknown>): void {
|
|
139
203
|
const name = toolName(event);
|
|
140
204
|
if (!name) return;
|
|
@@ -152,14 +216,15 @@ export class AntigravityEventAccumulator {
|
|
|
152
216
|
observed: true,
|
|
153
217
|
...(urlHash ? { urlHash } : {}),
|
|
154
218
|
...(pathHash ? { pathHash } : {}),
|
|
219
|
+
...fileTargetUpdate(name, event, this.#readScope, previous),
|
|
155
220
|
...(denied ? { denied: true } : {}),
|
|
156
221
|
},
|
|
157
222
|
previous,
|
|
158
223
|
);
|
|
159
224
|
if (!previous) this.#recordObservedTool(name);
|
|
160
225
|
if (id) this.#pendingTools.set(id, pending);
|
|
161
|
-
if (
|
|
162
|
-
if (
|
|
226
|
+
if (denied && !previous?.denied) this.#countPermissionDenial(name, event, pending);
|
|
227
|
+
if (!denied) this.#observeExplicitOutcome(name, event, pending);
|
|
163
228
|
}
|
|
164
229
|
|
|
165
230
|
#consumeToolResult(event: Record<string, unknown>): void {
|
|
@@ -171,12 +236,14 @@ export class AntigravityEventAccumulator {
|
|
|
171
236
|
if (!name) return;
|
|
172
237
|
const classification = classifyToolResult(event);
|
|
173
238
|
if (classification.kind === "pending") {
|
|
174
|
-
this.#recordPendingToolResult(id, name, pending);
|
|
239
|
+
this.#recordPendingToolResult(id, name, event, pending);
|
|
175
240
|
return;
|
|
176
241
|
}
|
|
177
242
|
if (id) this.#completeToolResult(id);
|
|
178
243
|
if (classification.kind === "failure") {
|
|
179
|
-
|
|
244
|
+
if (classification.denied && !pending?.denied)
|
|
245
|
+
this.#countPermissionDenial(name, event, pending);
|
|
246
|
+
if (!pending?.observed) this.#recordObservedTool(name);
|
|
180
247
|
return;
|
|
181
248
|
}
|
|
182
249
|
this.#recordSuccessfulTool(name, event, pending);
|
|
@@ -185,20 +252,41 @@ export class AntigravityEventAccumulator {
|
|
|
185
252
|
#recordPendingToolResult(
|
|
186
253
|
id: string | undefined,
|
|
187
254
|
name: string,
|
|
255
|
+
event: Record<string, unknown>,
|
|
188
256
|
pending: PendingTool | undefined,
|
|
189
|
-
):
|
|
257
|
+
): PendingTool {
|
|
190
258
|
if (!pending?.observed) this.#recordObservedTool(name);
|
|
191
|
-
|
|
259
|
+
const updated = {
|
|
260
|
+
...pending,
|
|
261
|
+
name,
|
|
262
|
+
observed: true,
|
|
263
|
+
...fileTargetUpdate(name, event, this.#readScope, pending),
|
|
264
|
+
};
|
|
265
|
+
if (id) this.#pendingTools.set(id, updated);
|
|
266
|
+
return updated;
|
|
192
267
|
}
|
|
193
268
|
|
|
194
|
-
#completeToolResult(id: string): void {
|
|
269
|
+
#completeToolResult(id: string | undefined): void {
|
|
270
|
+
if (!id) return;
|
|
195
271
|
this.#pendingTools.delete(id);
|
|
196
272
|
this.#rememberCompletedToolId(id);
|
|
197
273
|
}
|
|
198
274
|
|
|
199
|
-
#
|
|
200
|
-
|
|
201
|
-
|
|
275
|
+
#countPermissionDenial(
|
|
276
|
+
name: string | undefined,
|
|
277
|
+
event: Record<string, unknown>,
|
|
278
|
+
pending?: PendingTool,
|
|
279
|
+
): void {
|
|
280
|
+
this.#permissionDenials += 1;
|
|
281
|
+
const operation = classifyDeniedOperation(name);
|
|
282
|
+
this.#permissionDenialsByOperation[operation] =
|
|
283
|
+
(this.#permissionDenialsByOperation[operation] ?? 0) + 1;
|
|
284
|
+
const started = pending ?? this.#pendingTools.get(eventId(event) ?? "");
|
|
285
|
+
const target = fileTargetUpdate(name, event, this.#readScope, started);
|
|
286
|
+
this.#deniedFileResources.record(operation, target.requestedResource);
|
|
287
|
+
if (target.outsideReadRoots) {
|
|
288
|
+
this.#deniedFileAccessOutsideReadRoots += 1;
|
|
289
|
+
}
|
|
202
290
|
}
|
|
203
291
|
|
|
204
292
|
#rememberCompletedToolId(id: string): void {
|
|
@@ -219,10 +307,14 @@ export class AntigravityEventAccumulator {
|
|
|
219
307
|
return true;
|
|
220
308
|
}
|
|
221
309
|
|
|
222
|
-
#recordPermissionDenial(
|
|
310
|
+
#recordPermissionDenial(
|
|
311
|
+
name: string,
|
|
312
|
+
event: Record<string, unknown>,
|
|
313
|
+
observation?: PendingTool,
|
|
314
|
+
): boolean {
|
|
223
315
|
const id = eventId(event);
|
|
224
316
|
if (id && this.#completedToolIds.has(id)) return false;
|
|
225
|
-
const pending = id ? this.#pendingTools.get(id) : undefined;
|
|
317
|
+
const pending = observation ?? (id ? this.#pendingTools.get(id) : undefined);
|
|
226
318
|
if (pending?.denied) return false;
|
|
227
319
|
if (!pending?.observed) this.#recordObservedTool(name);
|
|
228
320
|
if (id) {
|
|
@@ -261,8 +353,27 @@ export class AntigravityEventAccumulator {
|
|
|
261
353
|
return normalizeWorkspacePath(normalized);
|
|
262
354
|
}
|
|
263
355
|
|
|
356
|
+
/** Terminal summaries prove an operation was denied, but supply no call IDs or targets. */
|
|
357
|
+
#consumeTerminalDenials(event: Record<string, unknown>): void {
|
|
358
|
+
const operations = terminalDeniedOperations(event);
|
|
359
|
+
// The generic notice may describe the same denial as the terminal summary.
|
|
360
|
+
// Keep a conservative lower bound without attributing it to a particular call.
|
|
361
|
+
if (operations.size && this.#unattributedPermissionDenialSeen) {
|
|
362
|
+
this.#permissionDenials -= 1;
|
|
363
|
+
const other = (this.#permissionDenialsByOperation.other ?? 1) - 1;
|
|
364
|
+
if (other) this.#permissionDenialsByOperation.other = other;
|
|
365
|
+
else delete this.#permissionDenialsByOperation.other;
|
|
366
|
+
}
|
|
367
|
+
for (const [operation, name] of operations) {
|
|
368
|
+
if (!this.#permissionDenialsByOperation[operation]) {
|
|
369
|
+
this.#countPermissionDenial(name, {});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
264
374
|
#consumeTerminal(event: Record<string, unknown>): void {
|
|
265
375
|
this.#terminalSeen = true;
|
|
376
|
+
this.#consumeTerminalDenials(event);
|
|
266
377
|
const status = eventStatus(event);
|
|
267
378
|
if (isErrorStatus(status) || !hasExplicitSuccess(event, status)) {
|
|
268
379
|
throw new AntigravityResponseError("terminal-failed");
|
|
@@ -273,94 +384,6 @@ export class AntigravityEventAccumulator {
|
|
|
273
384
|
}
|
|
274
385
|
}
|
|
275
386
|
|
|
276
|
-
function consumeNestedToolBlocks(
|
|
277
|
-
accumulator: AntigravityEventAccumulator,
|
|
278
|
-
event: Record<string, unknown>,
|
|
279
|
-
): boolean {
|
|
280
|
-
const message = isRecord(event.message) ? event.message : undefined;
|
|
281
|
-
const content = message?.content;
|
|
282
|
-
if (!Array.isArray(content)) return false;
|
|
283
|
-
let consumed = false;
|
|
284
|
-
for (const block of content) {
|
|
285
|
-
if (!isRecord(block)) continue;
|
|
286
|
-
const normalizedBlock = normalizeProtocolEvent(block);
|
|
287
|
-
if (!isToolActivityBlock(normalizedBlock)) continue;
|
|
288
|
-
accumulator.consume(normalizedBlock);
|
|
289
|
-
consumed = true;
|
|
290
|
-
}
|
|
291
|
-
return consumed;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function isToolActivityBlock(event: Record<string, unknown>): boolean {
|
|
295
|
-
if (isToolStartEvent(event) || isToolResultEvent(event)) return true;
|
|
296
|
-
return eventType(event) === "step_update" && !isNonToolStepUpdate(event);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function hasExplicitSuccess(event: Record<string, unknown>, status: string | undefined): boolean {
|
|
300
|
-
return status !== undefined
|
|
301
|
-
? isSuccessStatus(status)
|
|
302
|
-
: event.is_error === false || event.isError === false || event.success === true;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function isSuccessfulActivity(event: Record<string, unknown>): boolean {
|
|
306
|
-
const status = eventStatus(event);
|
|
307
|
-
return (
|
|
308
|
-
!isErrorStatus(status) &&
|
|
309
|
-
isSuccessStatus(status) &&
|
|
310
|
-
(status !== undefined || event.success === true || event.ok === true)
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function isNonToolStepUpdate(event: Record<string, unknown>): boolean {
|
|
315
|
-
const type = eventType(event);
|
|
316
|
-
if (!["assistant", "step_update"].includes(type)) return false;
|
|
317
|
-
const declaredType = safeString(event.step_type ?? event.stepType, 40)?.toLowerCase();
|
|
318
|
-
if (declaredType) return declaredType !== "tool";
|
|
319
|
-
return toolName(event) === undefined && !isRecord(event.tool_info) && !isRecord(event.toolInfo);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function classifyToolResult(event: Record<string, unknown>): ToolResultClassification {
|
|
323
|
-
const status = eventStatus(event);
|
|
324
|
-
const denied = isPermissionDenial(event);
|
|
325
|
-
if (denied || isErrorStatus(status)) return { kind: "failure", denied };
|
|
326
|
-
return { kind: isSuccessStatus(status) ? "success" : "pending", denied };
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function isPermissionDenial(event: Record<string, unknown>): boolean {
|
|
330
|
-
const status = eventStatus(event);
|
|
331
|
-
if (["denied", "permission_denied", "forbidden", "blocked"].includes(status ?? "")) return true;
|
|
332
|
-
const type = eventType(event);
|
|
333
|
-
if (type.includes("permission") || type.includes("denied") || type.includes("blocked"))
|
|
334
|
-
return true;
|
|
335
|
-
const hasExplicitFailure =
|
|
336
|
-
event.is_error === true ||
|
|
337
|
-
event.isError === true ||
|
|
338
|
-
isErrorStatus(status) ||
|
|
339
|
-
event.error !== undefined ||
|
|
340
|
-
event.reason !== undefined;
|
|
341
|
-
if (!hasExplicitFailure) return false;
|
|
342
|
-
const message = [event.error, event.reason, event.message, event.content]
|
|
343
|
-
.map((value) => safeDenialText(value))
|
|
344
|
-
.join(" ")
|
|
345
|
-
.toLowerCase();
|
|
346
|
-
return message.includes("permission") || message.includes("denied");
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function safeDenialText(value: unknown, depth = 0): string {
|
|
350
|
-
if (depth > 3) return "";
|
|
351
|
-
if (typeof value === "string") return value.slice(0, 4_000);
|
|
352
|
-
if (Array.isArray(value)) {
|
|
353
|
-
return value
|
|
354
|
-
.slice(0, 20)
|
|
355
|
-
.map((item) => safeDenialText(item, depth + 1))
|
|
356
|
-
.join(" ");
|
|
357
|
-
}
|
|
358
|
-
if (!isRecord(value)) return "";
|
|
359
|
-
return ["text", "message", "content", "error", "reason"]
|
|
360
|
-
.map((key) => safeDenialText(value[key], depth + 1))
|
|
361
|
-
.join(" ");
|
|
362
|
-
}
|
|
363
|
-
|
|
364
387
|
function mergePendingTool(current: PendingTool, previous: PendingTool | undefined): PendingTool {
|
|
365
388
|
if (previous?.urlHash && !current.urlHash) current.urlHash = previous.urlHash;
|
|
366
389
|
if (previous?.pathHash && !current.pathHash) current.pathHash = previous.pathHash;
|
|
@@ -14,6 +14,8 @@ export interface ProjectHookProbeResult {
|
|
|
14
14
|
export async function probeProjectHooks(options: {
|
|
15
15
|
paths: IsolatedAntigravityPaths;
|
|
16
16
|
cwd: string;
|
|
17
|
+
/** Match the paid process's inspection roots when checking project hook state. */
|
|
18
|
+
additionalReadOnlyDirectories?: readonly string[];
|
|
17
19
|
signal?: AbortSignal;
|
|
18
20
|
onProcessStart?: () => void;
|
|
19
21
|
}): Promise<ProjectHookProbeResult> {
|
|
@@ -21,7 +23,13 @@ export async function probeProjectHooks(options: {
|
|
|
21
23
|
const result = await runAntigravityProbe({
|
|
22
24
|
paths: options.paths,
|
|
23
25
|
cwd: options.cwd,
|
|
24
|
-
args: [
|
|
26
|
+
args: [
|
|
27
|
+
...PROJECT_HOOK_PROBE_ARGUMENTS,
|
|
28
|
+
...(options.additionalReadOnlyDirectories ?? []).flatMap((directory) => [
|
|
29
|
+
"--add-dir",
|
|
30
|
+
directory,
|
|
31
|
+
]),
|
|
32
|
+
],
|
|
25
33
|
signal: options.signal,
|
|
26
34
|
timeoutMs: 15_000,
|
|
27
35
|
onProcessStart: options.onProcessStart,
|