@mrclrchtr/supi-consulting 10.0.0 → 11.0.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/README.md +21 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/package.json +3 -3
- package/src/agents/antigravity/adapter.ts +52 -8
- package/src/agents/antigravity/process/events.ts +20 -9
- package/src/agents/antigravity/process/ndjson.ts +9 -7
- package/src/agents/antigravity/process/protocol.ts +4 -3
- package/src/agents/antigravity/process/response-error.ts +12 -0
- package/src/agents/antigravity/process/runner.ts +16 -7
- package/src/agents/antigravity/process/subprocess.ts +18 -2
- package/src/agents/antigravity/structured-output.ts +2 -1
- package/src/agents/errors.ts +53 -4
- package/src/agents/types.ts +1 -1
- /package/{CONTEXT.md → GLOSSARY.md} +0 -0
package/README.md
CHANGED
|
@@ -82,6 +82,27 @@ Results report web use, workspace use, observed and claimed references, warnings
|
|
|
82
82
|
|
|
83
83
|
Use `/supi-settings` → **Consulting** to disable the tool. Disabling it skips availability checks and removes `consulting_run` from the active tool list.
|
|
84
84
|
|
|
85
|
+
## Failure diagnostics
|
|
86
|
+
|
|
87
|
+
Execution failures report a fixed reason when the adapter can identify it:
|
|
88
|
+
|
|
89
|
+
- Missing final result, missing structured answer, or answer schema mismatch.
|
|
90
|
+
- Unsuccessful final result or data received after the final result.
|
|
91
|
+
- Malformed JSON, a non-object event, or an output limit exceeded.
|
|
92
|
+
- Missing conversation identifier, process failure, timeout, or cancellation.
|
|
93
|
+
|
|
94
|
+
An observed tool permission denial is reported separately. It does not prove the cause of a later failure. A denial does not fail a Consultation that returns a valid answer. Workspace Access does not grant access to files outside the selected workspace.
|
|
95
|
+
|
|
96
|
+
When SuPi Debug is enabled, failed executions emit one `consulting/consultation.failed` event at `error` level. Cancellations emit `consulting/consultation.cancelled` at `info` level. Use the `debug` tool:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{ "source": "consulting", "level": "error", "limit": 10 }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Omit `level` to include cancellations. Add `sessionFile` to inspect events persisted in a saved PI session. A response aborted before the adapter starts has no Consulting event.
|
|
103
|
+
|
|
104
|
+
Events contain the execution phase, timing, selected model, workspace-access and follow-up flags, failure category and reason, and available event counts, terminal-state flags, permission-denial count, and exit code. They exclude prompts, answers, resource paths, conversation IDs, raw stdout, stderr, tool payloads, and error causes, even when raw Debug access is enabled. The event's `cwd` identifies the selected working directory. Failure messages remain specific when Debug is disabled.
|
|
105
|
+
|
|
85
106
|
## Security limits
|
|
86
107
|
|
|
87
108
|
The Antigravity adapter enforces its Inspection Permission Set in the Isolated Antigravity Home. It allows `read_url(*)` and denies known file writes, commands, unsandboxed actions, MCP, and URL execution. These rules are **not an operating-system sandbox**.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-consulting",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.1",
|
|
4
4
|
"description": "Bounded consultations with explicit agent and workspace selection and observed evidence",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"scripts/live-probe.ts",
|
|
34
34
|
"README.md",
|
|
35
35
|
"CLAUDE.md",
|
|
36
|
-
"
|
|
36
|
+
"GLOSSARY.md",
|
|
37
37
|
"docs/adr/*.md"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@mrclrchtr/supi-core": "
|
|
40
|
+
"@mrclrchtr/supi-core": "11.0.1"
|
|
41
41
|
},
|
|
42
42
|
"bundledDependencies": [
|
|
43
43
|
"@mrclrchtr/supi-core"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { startDebugTimer } from "@mrclrchtr/supi-core/debug";
|
|
1
2
|
import type { ConsultationActivity, ConsultationExecutionFacts } from "../../types.ts";
|
|
2
3
|
import { ConsultingAgentError, type ConsultingAgentFailureKind } from "../errors.ts";
|
|
3
4
|
import type {
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
type IsolatedAntigravityPaths,
|
|
14
15
|
prepareIsolatedAntigravityHome,
|
|
15
16
|
} from "./isolated-home.ts";
|
|
17
|
+
import { isCuratedModel } from "./models.ts";
|
|
16
18
|
import { probeProjectHooks } from "./process/hooks.ts";
|
|
17
19
|
import { AntigravityProcessError, runAntigravityConversation } from "./process/runner.ts";
|
|
18
20
|
import { ANTIGRAVITY_ANSWER_SCHEMA } from "./structured-output.ts";
|
|
@@ -22,6 +24,8 @@ const MAX_WARNING_COUNT = 24;
|
|
|
22
24
|
const MAX_WARNING_CHARS = 500;
|
|
23
25
|
const CONTINUATION_PREFIX = "agy1.";
|
|
24
26
|
|
|
27
|
+
type ExecutionPhase = "preparation" | "hook-probe" | "conversation" | "validation";
|
|
28
|
+
|
|
25
29
|
/** The one agent supported by Consulting in this package. */
|
|
26
30
|
export const ANTIGRAVITY_AGENT_ID = "antigravity" as const;
|
|
27
31
|
|
|
@@ -65,21 +69,53 @@ class AntigravityAdapter implements ConsultingAgentAdapter {
|
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
async execute(options: ConsultingAgentExecutionOptions): Promise<ConsultationExecutionFacts> {
|
|
72
|
+
const timer = startDebugTimer();
|
|
73
|
+
let phase: ExecutionPhase = "preparation";
|
|
68
74
|
try {
|
|
69
|
-
return await this.#execute(options)
|
|
75
|
+
return await this.#execute(options, (nextPhase) => {
|
|
76
|
+
timer.mark(phase);
|
|
77
|
+
phase = nextPhase;
|
|
78
|
+
});
|
|
70
79
|
} catch (error) {
|
|
71
|
-
|
|
80
|
+
const failure = safeExecutionFailure(error);
|
|
81
|
+
const canceled = failure.kind === "cancelled";
|
|
82
|
+
timer.finish(
|
|
83
|
+
() => ({
|
|
84
|
+
source: "consulting",
|
|
85
|
+
level: canceled ? "info" : "error",
|
|
86
|
+
category: canceled ? "consultation.cancelled" : "consultation.failed",
|
|
87
|
+
message: failure.message,
|
|
88
|
+
cwd: options.canonicalWorkingDirectory,
|
|
89
|
+
data: {
|
|
90
|
+
agent: this.identity,
|
|
91
|
+
model: isCuratedModel(options.model) ? options.model : "unknown",
|
|
92
|
+
workspaceAccess: options.workspaceAccess,
|
|
93
|
+
followUp: options.continuation !== undefined,
|
|
94
|
+
phase,
|
|
95
|
+
kind: failure.kind,
|
|
96
|
+
reason: failure.reason ?? failure.kind,
|
|
97
|
+
...failure.diagnostics,
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
phase,
|
|
101
|
+
);
|
|
102
|
+
throw failure;
|
|
72
103
|
}
|
|
73
104
|
}
|
|
74
105
|
|
|
75
|
-
async #execute(
|
|
106
|
+
async #execute(
|
|
107
|
+
options: ConsultingAgentExecutionOptions,
|
|
108
|
+
onPhase: (phase: ExecutionPhase) => void,
|
|
109
|
+
): Promise<ConsultationExecutionFacts> {
|
|
76
110
|
const expectedConversationId = options.continuation
|
|
77
111
|
? decodeContinuation(options.continuation)
|
|
78
112
|
: undefined;
|
|
79
113
|
await prepareIsolatedAntigravityHome(this.#paths);
|
|
114
|
+
if (options.workspaceAccess) onPhase("hook-probe");
|
|
80
115
|
const hookWarning = options.workspaceAccess
|
|
81
116
|
? await inspectProjectHooks(this.#paths, options, options.onWarning)
|
|
82
117
|
: undefined;
|
|
118
|
+
onPhase("conversation");
|
|
83
119
|
const rawFacts = await runAntigravityConversation({
|
|
84
120
|
paths: this.#paths,
|
|
85
121
|
cwd: options.canonicalWorkingDirectory,
|
|
@@ -92,6 +128,7 @@ class AntigravityAdapter implements ConsultingAgentAdapter {
|
|
|
92
128
|
onActivity: options.onProgress,
|
|
93
129
|
onProcessStart: options.onProcessStart,
|
|
94
130
|
});
|
|
131
|
+
onPhase("validation");
|
|
95
132
|
if (expectedConversationId && rawFacts.conversationId !== expectedConversationId) {
|
|
96
133
|
throw new ConsultingAgentError("conversation-mismatch");
|
|
97
134
|
}
|
|
@@ -179,9 +216,14 @@ function decodeContinuation(value: string): string {
|
|
|
179
216
|
return conversationId;
|
|
180
217
|
}
|
|
181
218
|
|
|
182
|
-
/**
|
|
219
|
+
/** Keep only fixed reasons and safe facts, including preparation and callback failures. */
|
|
183
220
|
function safeExecutionFailure(error: unknown): ConsultingAgentError {
|
|
184
|
-
if (error instanceof ConsultingAgentError)
|
|
221
|
+
if (error instanceof ConsultingAgentError) {
|
|
222
|
+
return new ConsultingAgentError(error.kind, {
|
|
223
|
+
reason: error.reason,
|
|
224
|
+
diagnostics: error.diagnostics,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
185
227
|
const processKinds: Record<AntigravityProcessError["kind"], ConsultingAgentFailureKind> = {
|
|
186
228
|
missing: "unavailable",
|
|
187
229
|
timeout: "timeout",
|
|
@@ -190,7 +232,9 @@ function safeExecutionFailure(error: unknown): ConsultingAgentError {
|
|
|
190
232
|
protocol: "invalid-response",
|
|
191
233
|
process: "execution",
|
|
192
234
|
};
|
|
193
|
-
return new ConsultingAgentError(
|
|
194
|
-
|
|
195
|
-
|
|
235
|
+
if (!(error instanceof AntigravityProcessError)) return new ConsultingAgentError("execution");
|
|
236
|
+
return new ConsultingAgentError(processKinds[error.kind], {
|
|
237
|
+
reason: error.reason,
|
|
238
|
+
diagnostics: { ...error.diagnostics, exitCode: error.exitCode },
|
|
239
|
+
});
|
|
196
240
|
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
toolName,
|
|
24
24
|
} from "./event-values.ts";
|
|
25
25
|
import { normalizeProtocolEvent } from "./protocol.ts";
|
|
26
|
+
import { AntigravityResponseError } from "./response-error.ts";
|
|
26
27
|
import { mergeUsage, readUsage } from "./usage.ts";
|
|
27
28
|
|
|
28
29
|
interface PendingTool {
|
|
@@ -67,8 +68,7 @@ export class AntigravityEventAccumulator {
|
|
|
67
68
|
|
|
68
69
|
/** Consume one already-parsed event. */
|
|
69
70
|
consume(event: Record<string, unknown>): void {
|
|
70
|
-
if (this.#terminalSeen)
|
|
71
|
-
throw new Error("Antigravity returned events after its terminal event.");
|
|
71
|
+
if (this.#terminalSeen) throw new AntigravityResponseError("events-after-terminal");
|
|
72
72
|
const normalizedEvent = normalizeProtocolEvent(event);
|
|
73
73
|
this.#conversationId =
|
|
74
74
|
conversationId(event) ?? conversationId(normalizedEvent) ?? this.#conversationId;
|
|
@@ -103,14 +103,25 @@ export class AntigravityEventAccumulator {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/** Return safe execution facts even when the response is incomplete. */
|
|
107
|
+
get diagnostics(): {
|
|
108
|
+
permissionDenials: number;
|
|
109
|
+
terminalSeen: boolean;
|
|
110
|
+
terminalSucceeded: boolean;
|
|
111
|
+
} {
|
|
112
|
+
return {
|
|
113
|
+
permissionDenials: this.#permissionDenials,
|
|
114
|
+
terminalSeen: this.#terminalSeen,
|
|
115
|
+
terminalSucceeded: this.#terminalSucceeded,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
106
119
|
/** Finish the reduction and validate terminal structured output. */
|
|
107
120
|
finish(): AntigravityExecutionFacts {
|
|
108
|
-
if (!this.#terminalSeen
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (!this.#
|
|
112
|
-
throw new Error("Antigravity terminal output was missing structured data.");
|
|
113
|
-
if (!this.#conversationId) throw new Error("Antigravity did not return a conversation handle.");
|
|
121
|
+
if (!this.#terminalSeen) throw new AntigravityResponseError("missing-terminal");
|
|
122
|
+
if (!this.#terminalSucceeded) throw new AntigravityResponseError("terminal-failed");
|
|
123
|
+
if (!this.#candidate) throw new AntigravityResponseError("missing-answer");
|
|
124
|
+
if (!this.#conversationId) throw new AntigravityResponseError("missing-conversation");
|
|
114
125
|
return {
|
|
115
126
|
answer: validateAntigravityAnswer(this.#candidate),
|
|
116
127
|
conversationId: this.#conversationId,
|
|
@@ -254,7 +265,7 @@ export class AntigravityEventAccumulator {
|
|
|
254
265
|
this.#terminalSeen = true;
|
|
255
266
|
const status = eventStatus(event);
|
|
256
267
|
if (isErrorStatus(status) || !hasExplicitSuccess(event, status)) {
|
|
257
|
-
throw new
|
|
268
|
+
throw new AntigravityResponseError("terminal-failed");
|
|
258
269
|
}
|
|
259
270
|
this.#terminalSucceeded = true;
|
|
260
271
|
this.#candidate = answerCandidate(event);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AntigravityResponseError } from "./response-error.ts";
|
|
2
|
+
|
|
1
3
|
/** Limits for streamed Antigravity output. */
|
|
2
4
|
export const MAX_STDOUT_LINE_BYTES = 128 * 1024;
|
|
3
5
|
export const MAX_STDOUT_BYTES = 5 * 1024 * 1024;
|
|
@@ -5,9 +7,9 @@ export const MAX_STREAM_EVENTS = 512;
|
|
|
5
7
|
export const MAX_RETAINED_STDERR_BYTES = 8 * 1024;
|
|
6
8
|
|
|
7
9
|
/** Error raised when a process stream exceeds a package-owned bound. */
|
|
8
|
-
export class StreamLimitError extends
|
|
9
|
-
constructor(
|
|
10
|
-
super(
|
|
10
|
+
export class StreamLimitError extends AntigravityResponseError {
|
|
11
|
+
constructor(reason: "stdout-line-limit" | "stdout-byte-limit" | "event-limit") {
|
|
12
|
+
super(reason);
|
|
11
13
|
this.name = "StreamLimitError";
|
|
12
14
|
}
|
|
13
15
|
}
|
|
@@ -40,7 +42,7 @@ export class BoundedLineParser {
|
|
|
40
42
|
feed(chunk: Uint8Array, onLine: (line: string) => void): void {
|
|
41
43
|
this.#totalBytes += chunk.byteLength;
|
|
42
44
|
if (this.#totalBytes > this.#maxTotalBytes) {
|
|
43
|
-
throw new StreamLimitError("
|
|
45
|
+
throw new StreamLimitError("stdout-byte-limit");
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
let start = 0;
|
|
@@ -62,7 +64,7 @@ export class BoundedLineParser {
|
|
|
62
64
|
if (part.byteLength === 0) return;
|
|
63
65
|
this.#lineBytes += part.byteLength;
|
|
64
66
|
if (this.#lineBytes > this.#maxLineBytes) {
|
|
65
|
-
throw new StreamLimitError("
|
|
67
|
+
throw new StreamLimitError("stdout-line-limit");
|
|
66
68
|
}
|
|
67
69
|
this.#lineParts.push(Buffer.from(part));
|
|
68
70
|
}
|
|
@@ -111,10 +113,10 @@ export function parseNdjsonLine(line: string): Record<string, unknown> | undefin
|
|
|
111
113
|
try {
|
|
112
114
|
parsed = JSON.parse(line);
|
|
113
115
|
} catch (error) {
|
|
114
|
-
throw new
|
|
116
|
+
throw new AntigravityResponseError("malformed-json", { cause: error });
|
|
115
117
|
}
|
|
116
118
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
117
|
-
throw new
|
|
119
|
+
throw new AntigravityResponseError("invalid-event");
|
|
118
120
|
}
|
|
119
121
|
return parsed as Record<string, unknown>;
|
|
120
122
|
}
|
|
@@ -9,9 +9,10 @@ export function normalizeProtocolEvent(event: Record<string, unknown>): Record<s
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function normalizeResultEnvelope(event: Record<string, unknown>): Record<string, unknown> {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
if (!isRecord(event.result)) return { ...event, type: "result" };
|
|
13
|
+
// Remove the envelope so its status fields cannot be mistaken for answer data.
|
|
14
|
+
const { result, ...envelope } = event;
|
|
15
|
+
return { ...envelope, ...result, type: "result" };
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
function normalizeStepUpdateEnvelope(event: Record<string, unknown>): Record<string, unknown> {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ConsultingFailureReason } from "../../errors.ts";
|
|
2
|
+
|
|
3
|
+
/** Internal response failure with a fixed reason that survives process error wrapping. */
|
|
4
|
+
export class AntigravityResponseError extends Error {
|
|
5
|
+
readonly reason: ConsultingFailureReason;
|
|
6
|
+
|
|
7
|
+
constructor(reason: ConsultingFailureReason, options?: ErrorOptions) {
|
|
8
|
+
super(`Antigravity response failure: ${reason}.`, options);
|
|
9
|
+
this.name = "AntigravityResponseError";
|
|
10
|
+
this.reason = reason;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -6,6 +6,7 @@ import type { AntigravityExecutionFacts, AntigravityProgressCallback } from "../
|
|
|
6
6
|
import { eventType, toolName } from "./event-values.ts";
|
|
7
7
|
import { AntigravityEventAccumulator } from "./events.ts";
|
|
8
8
|
import { MAX_STREAM_EVENTS, parseNdjsonLine, StreamLimitError } from "./ndjson.ts";
|
|
9
|
+
import { AntigravityResponseError } from "./response-error.ts";
|
|
9
10
|
import type { AntigravityProbeResult } from "./subprocess.ts";
|
|
10
11
|
import { AntigravityProcessError, runBoundedChildProcess } from "./subprocess.ts";
|
|
11
12
|
|
|
@@ -72,6 +73,8 @@ export async function runAntigravityConversation(options: {
|
|
|
72
73
|
join(options.schemaDirectoryParent ?? tmpdir(), "supi-consulting-schema-"),
|
|
73
74
|
);
|
|
74
75
|
const schemaPath = join(schemaDirectory, "answer.json");
|
|
76
|
+
const accumulator = new AntigravityEventAccumulator({ workspaceDirectory: options.cwd });
|
|
77
|
+
let eventCount = 0;
|
|
75
78
|
try {
|
|
76
79
|
await writeFile(schemaPath, `${JSON.stringify(options.schema)}\n`, { mode: 0o600 });
|
|
77
80
|
const args = buildAntigravityRunArguments(
|
|
@@ -80,8 +83,6 @@ export async function runAntigravityConversation(options: {
|
|
|
80
83
|
options.conversationId,
|
|
81
84
|
options.workspaceDirectory,
|
|
82
85
|
);
|
|
83
|
-
const accumulator = new AntigravityEventAccumulator({ workspaceDirectory: options.cwd });
|
|
84
|
-
let eventCount = 0;
|
|
85
86
|
await runBoundedChildProcess({
|
|
86
87
|
args,
|
|
87
88
|
cwd: options.cwd,
|
|
@@ -94,7 +95,7 @@ export async function runAntigravityConversation(options: {
|
|
|
94
95
|
if (!event) return;
|
|
95
96
|
eventCount += 1;
|
|
96
97
|
if (eventCount > MAX_STREAM_EVENTS) {
|
|
97
|
-
throw new StreamLimitError("
|
|
98
|
+
throw new StreamLimitError("event-limit");
|
|
98
99
|
}
|
|
99
100
|
options.onActivity?.(safeActivityLabel(event));
|
|
100
101
|
accumulator.consume(event);
|
|
@@ -103,11 +104,19 @@ export async function runAntigravityConversation(options: {
|
|
|
103
104
|
});
|
|
104
105
|
return accumulator.finish();
|
|
105
106
|
} catch (error) {
|
|
106
|
-
|
|
107
|
+
const processError = error instanceof AntigravityProcessError ? error : undefined;
|
|
107
108
|
// biome-ignore lint/style/useErrorCause: the custom process error preserves the protocol cause.
|
|
108
|
-
throw new AntigravityProcessError(
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
throw new AntigravityProcessError(
|
|
110
|
+
processError?.message ?? "Antigravity returned an invalid stream.",
|
|
111
|
+
processError?.kind ?? "protocol",
|
|
112
|
+
{
|
|
113
|
+
cause: error,
|
|
114
|
+
reason: error instanceof AntigravityResponseError ? error.reason : processError?.reason,
|
|
115
|
+
exitCode: processError?.exitCode,
|
|
116
|
+
stderr: processError?.stderr,
|
|
117
|
+
diagnostics: { ...accumulator.diagnostics, eventCount },
|
|
118
|
+
},
|
|
119
|
+
);
|
|
111
120
|
} finally {
|
|
112
121
|
await rm(schemaDirectory, { recursive: true, force: true });
|
|
113
122
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
2
|
+
import type { ConsultingFailureDiagnostics, ConsultingFailureReason } from "../../errors.ts";
|
|
2
3
|
import { buildAntigravityEnvironment } from "./environment.ts";
|
|
3
4
|
import {
|
|
4
5
|
BoundedLineParser,
|
|
@@ -6,6 +7,7 @@ import {
|
|
|
6
7
|
MAX_STDOUT_BYTES,
|
|
7
8
|
MAX_STDOUT_LINE_BYTES,
|
|
8
9
|
} from "./ndjson.ts";
|
|
10
|
+
import { AntigravityResponseError } from "./response-error.ts";
|
|
9
11
|
|
|
10
12
|
const COMMAND = "agy";
|
|
11
13
|
const TERMINATION_GRACE_MS = 500;
|
|
@@ -24,17 +26,27 @@ export class AntigravityProcessError extends Error {
|
|
|
24
26
|
readonly exitCode: number | null | undefined;
|
|
25
27
|
/** Bounded stderr retained for an in-memory caller, never put in result details. */
|
|
26
28
|
readonly stderr: string | undefined;
|
|
29
|
+
readonly reason: ConsultingFailureReason | undefined;
|
|
30
|
+
readonly diagnostics: ConsultingFailureDiagnostics | undefined;
|
|
27
31
|
|
|
28
32
|
constructor(
|
|
29
33
|
message: string,
|
|
30
34
|
kind: AntigravityProcessError["kind"],
|
|
31
|
-
options: {
|
|
35
|
+
options: {
|
|
36
|
+
cause?: unknown;
|
|
37
|
+
exitCode?: number | null;
|
|
38
|
+
stderr?: string;
|
|
39
|
+
reason?: ConsultingFailureReason;
|
|
40
|
+
diagnostics?: ConsultingFailureDiagnostics;
|
|
41
|
+
} = {},
|
|
32
42
|
) {
|
|
33
43
|
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
34
44
|
this.name = "AntigravityProcessError";
|
|
35
45
|
this.kind = kind;
|
|
36
46
|
this.exitCode = options.exitCode;
|
|
37
47
|
this.stderr = options.stderr;
|
|
48
|
+
this.reason = options.reason;
|
|
49
|
+
this.diagnostics = options.diagnostics;
|
|
38
50
|
}
|
|
39
51
|
}
|
|
40
52
|
|
|
@@ -125,7 +137,10 @@ export function runBoundedChildProcess(
|
|
|
125
137
|
} catch (error) {
|
|
126
138
|
terminateAndReject(
|
|
127
139
|
error instanceof Error
|
|
128
|
-
? new AntigravityProcessError(error.message, "stream", {
|
|
140
|
+
? new AntigravityProcessError(error.message, "stream", {
|
|
141
|
+
cause: error,
|
|
142
|
+
...(error instanceof AntigravityResponseError ? { reason: error.reason } : {}),
|
|
143
|
+
})
|
|
129
144
|
: new AntigravityProcessError("Antigravity stdout exceeded its limits.", "stream"),
|
|
130
145
|
);
|
|
131
146
|
}
|
|
@@ -221,6 +236,7 @@ function toProtocolOrProcessError(error: unknown): AntigravityProcessError {
|
|
|
221
236
|
if (error instanceof AntigravityProcessError) return error;
|
|
222
237
|
return new AntigravityProcessError("Antigravity returned an invalid stream.", "protocol", {
|
|
223
238
|
cause: error,
|
|
239
|
+
...(error instanceof AntigravityResponseError ? { reason: error.reason } : {}),
|
|
224
240
|
});
|
|
225
241
|
}
|
|
226
242
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type TSchema, Type } from "typebox";
|
|
2
2
|
import { Value } from "typebox/value";
|
|
3
3
|
import type { ConsultationAnswer } from "../../types.ts";
|
|
4
|
+
import { AntigravityResponseError } from "./process/response-error.ts";
|
|
4
5
|
|
|
5
6
|
/** Maximum length of Antigravity's final answer text. */
|
|
6
7
|
export const MAX_ANSWER_CHARS = 16_000;
|
|
@@ -52,7 +53,7 @@ export const ANTIGRAVITY_ANSWER_SCHEMA: TSchema = Type.Object(
|
|
|
52
53
|
/** Validate and return the exact structured answer shape. */
|
|
53
54
|
export function validateAntigravityAnswer(value: unknown): ConsultationAnswer {
|
|
54
55
|
if (!Value.Check(ANTIGRAVITY_ANSWER_SCHEMA, value)) {
|
|
55
|
-
throw new
|
|
56
|
+
throw new AntigravityResponseError("invalid-answer");
|
|
56
57
|
}
|
|
57
58
|
return value as ConsultationAnswer;
|
|
58
59
|
}
|
package/src/agents/errors.ts
CHANGED
|
@@ -9,19 +9,68 @@ const FAILURE_MESSAGES = {
|
|
|
9
9
|
execution: "Consultation execution failed.",
|
|
10
10
|
} as const;
|
|
11
11
|
|
|
12
|
+
const FAILURE_REASON_MESSAGES = {
|
|
13
|
+
"missing-terminal": "The Consulting Agent did not return a final result.",
|
|
14
|
+
"terminal-failed": "The Consulting Agent reported an unsuccessful final result.",
|
|
15
|
+
"missing-answer": "The Consulting Agent did not return a final structured answer.",
|
|
16
|
+
"invalid-answer":
|
|
17
|
+
"The Consulting Agent returned an answer that does not match the required schema.",
|
|
18
|
+
"missing-conversation": "The Consulting Agent did not return a conversation identifier.",
|
|
19
|
+
"events-after-terminal": "The Consulting Agent sent data after its final result.",
|
|
20
|
+
"malformed-json": "The Consulting Agent returned malformed JSON.",
|
|
21
|
+
"invalid-event": "The Consulting Agent returned a non-object response event.",
|
|
22
|
+
"stdout-line-limit": "The Consulting Agent response exceeded the line-size limit.",
|
|
23
|
+
"stdout-byte-limit": "The Consulting Agent response exceeded the total output-size limit.",
|
|
24
|
+
"event-limit": "The Consulting Agent response exceeded the event limit.",
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
/** Fixed failure reasons that do not contain agent output. */
|
|
28
|
+
export type ConsultingFailureReason = keyof typeof FAILURE_REASON_MESSAGES;
|
|
29
|
+
|
|
30
|
+
/** Execution facts that can leave the adapter on failure. No resource names or raw output. */
|
|
31
|
+
export interface ConsultingFailureDiagnostics {
|
|
32
|
+
permissionDenials?: number;
|
|
33
|
+
eventCount?: number;
|
|
34
|
+
terminalSeen?: boolean;
|
|
35
|
+
terminalSucceeded?: boolean;
|
|
36
|
+
exitCode?: number | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
12
39
|
/** Safe failure categories that can leave a private Consulting Agent adapter. */
|
|
13
40
|
export type ConsultingAgentFailureKind = keyof typeof FAILURE_MESSAGES;
|
|
14
41
|
|
|
15
42
|
/**
|
|
16
|
-
* A bounded failure with
|
|
17
|
-
*
|
|
43
|
+
* A bounded failure with fixed messages and safe execution facts, but no raw cause.
|
|
44
|
+
* Permission denials are observations, not a claim about the cause of failure.
|
|
18
45
|
*/
|
|
19
46
|
export class ConsultingAgentError extends Error {
|
|
20
47
|
readonly kind: ConsultingAgentFailureKind;
|
|
48
|
+
readonly reason: ConsultingFailureReason | undefined;
|
|
49
|
+
readonly diagnostics: ConsultingFailureDiagnostics | undefined;
|
|
21
50
|
|
|
22
|
-
constructor(
|
|
23
|
-
|
|
51
|
+
constructor(
|
|
52
|
+
kind: ConsultingAgentFailureKind,
|
|
53
|
+
options: { reason?: ConsultingFailureReason; diagnostics?: ConsultingFailureDiagnostics } = {},
|
|
54
|
+
) {
|
|
55
|
+
const message = options.reason
|
|
56
|
+
? FAILURE_REASON_MESSAGES[options.reason]
|
|
57
|
+
: FAILURE_MESSAGES[kind];
|
|
58
|
+
const denial = (options.diagnostics?.permissionDenials ?? 0) > 0;
|
|
59
|
+
super(
|
|
60
|
+
`${message}${denial ? " A tool permission denial was observed. Check the requested resources and the Consulting Agent permissions." : ""}`,
|
|
61
|
+
);
|
|
24
62
|
this.name = "ConsultingAgentError";
|
|
25
63
|
this.kind = kind;
|
|
64
|
+
this.reason = options.reason;
|
|
65
|
+
const facts = options.diagnostics;
|
|
66
|
+
this.diagnostics = facts
|
|
67
|
+
? {
|
|
68
|
+
permissionDenials: facts.permissionDenials,
|
|
69
|
+
eventCount: facts.eventCount,
|
|
70
|
+
terminalSeen: facts.terminalSeen,
|
|
71
|
+
terminalSucceeded: facts.terminalSucceeded,
|
|
72
|
+
exitCode: facts.exitCode,
|
|
73
|
+
}
|
|
74
|
+
: undefined;
|
|
26
75
|
}
|
|
27
76
|
}
|
package/src/agents/types.ts
CHANGED
|
@@ -38,6 +38,6 @@ export interface ConsultingAgentAdapter {
|
|
|
38
38
|
/** The agent's stable, package-managed empty Consultation Workspace. */
|
|
39
39
|
readonly consultationWorkspace: string;
|
|
40
40
|
discover(options?: { signal?: AbortSignal }): Promise<ConsultingAvailability>;
|
|
41
|
-
/** Reject with a ConsultingAgentError; raw diagnostics and causes stay inside the adapter. */
|
|
41
|
+
/** Reject with a ConsultingAgentError and safe facts; raw diagnostics and causes stay inside the adapter. */
|
|
42
42
|
execute(options: ConsultingAgentExecutionOptions): Promise<ConsultationExecutionFacts>;
|
|
43
43
|
}
|
|
File without changes
|