@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
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ A new Consultation requires the agent, an available model, and Workspace Access:
|
|
|
65
65
|
|
|
66
66
|
The Model Catalogue is the intersection of the curated Antigravity models and the models available to the signed-in account. It stays fixed until the next session start or reload. There is no default model or automatic agent fallback.
|
|
67
67
|
|
|
68
|
-
Set `workspace` to `true` only when the Consultation needs repository evidence. This exposes the current PI workspace. Set it to `false` to use the empty Consultation Workspace.
|
|
68
|
+
Set `workspace` to `true` only when the Consultation needs repository evidence. This exposes the current PI workspace. Set it to `false` to use the empty Consultation Workspace. Explicitly configured additional read-only directories remain accessible with either selection.
|
|
69
69
|
|
|
70
70
|
A follow-up supplies only a returned Conversation Handle:
|
|
71
71
|
|
|
@@ -82,16 +82,43 @@ 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
|
+
## Additional read-only directories
|
|
86
|
+
|
|
87
|
+
Use `/supi-settings` → **Consulting** → **Additional read-only directories** to enter comma-separated absolute directory paths. The default is an empty list. No shell expansion or glob matching is performed. For paths containing commas, edit the JSON configuration instead.
|
|
88
|
+
|
|
89
|
+
Global configuration lives at `~/.pi/agent/supi/config.json`; project configuration lives at `.pi/supi/config.json`:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"consulting": {
|
|
94
|
+
"additionalReadOnlyDirectories": ["/tmp/octalog-filter-previews"]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- Project values replace the global list, rather than extending it. An explicit `[]` removes inherited grants; Inherit/Reset deletes the scoped key. Untrusted project configuration cannot grant access; global grants still apply.
|
|
100
|
+
- Directories must exist and be accessible when a Consultation starts. Symlinks are resolved and duplicate roots are removed, so `/tmp` resolves to `/private/tmp` on macOS without needing both entries.
|
|
101
|
+
- Settings are read from the current Pi workspace before each new Consultation and follow-up. Changes apply to the next invocation without reloading. They do not modify an already-running process.
|
|
102
|
+
- The directories are registered with Antigravity per process using `--add-dir`, not persisted as permission grants in the shared profile. The fixed write, command, MCP, and URL-execution denials remain in force; `allowNonWorkspaceAccess` stays `false`.
|
|
103
|
+
- Configured directories are available even with `workspace: false`. Prefer task-specific artifact folders over all of `/tmp`, which may contain unrelated sensitive files. This setting does not authorize shell-based browser automation.
|
|
104
|
+
- Reads from extra directories can contribute inspection activity, but references outside the selected workspace are not reported as validated workspace-relative evidence.
|
|
105
|
+
|
|
106
|
+
Directory registration and deny-rule precedence follow Antigravity's [CLI reference](https://antigravity.google/docs/cli/reference/) and [permissions reference](https://antigravity.google/docs/permissions/). These are Antigravity policy restrictions, not a new operating-system security boundary.
|
|
107
|
+
|
|
85
108
|
## Failure diagnostics
|
|
86
109
|
|
|
87
110
|
Execution failures report a fixed reason when the adapter can identify it:
|
|
88
111
|
|
|
89
|
-
- Missing final result, missing structured answer, or answer
|
|
112
|
+
- Missing final result, missing structured answer, answer schema mismatch, or an answer not bound to the current request.
|
|
90
113
|
- Unsuccessful final result or data received after the final result.
|
|
91
114
|
- Malformed JSON, a non-object event, or an output limit exceeded.
|
|
92
115
|
- Missing conversation identifier, process failure, timeout, or cancellation.
|
|
93
116
|
|
|
94
|
-
|
|
117
|
+
Each invocation adds a unique response token to its private answer schema and validates the returned token locally before accepting the answer. The token is removed from public results and excluded from failure diagnostics. This prevents a resumed Antigravity conversation's previous structured answer from being accepted as the current result. Missing or mismatched tokens fail with `unbound-answer`; plain-text answers are not substituted for the structured contract. A started follow-up that fails retires its Conversation Handle; start a new Consultation.
|
|
118
|
+
|
|
119
|
+
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 alone does not grant access to files outside the selected workspace; configure additional read-only directories explicitly.
|
|
120
|
+
|
|
121
|
+
Failure messages returned to the calling agent include the observed terminal status and denial counts by fixed operation category (`command`, `file-read`, `file-write`, `web-read`, `browser`, `mcp`, or `other`). They distinguish a successful agent terminal status from a usable final answer and recommend permitted inspection or a limited/blocked report rather than repeating denied actions unchanged. This feedback remains available when Debug is disabled.
|
|
95
122
|
|
|
96
123
|
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
124
|
|
|
@@ -101,13 +128,26 @@ When SuPi Debug is enabled, failed executions emit one `consulting/consultation.
|
|
|
101
128
|
|
|
102
129
|
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
130
|
|
|
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.
|
|
131
|
+
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. `permissionDenialsByOperation` breaks down deduplicated denials by fixed category; unknown tool names become `other`. `deniedFileAccessOutsideReadRoots` counts denied file operations whose requested paths resolve outside the selected directory and configured additional read-only directories, following symlinks and existing parents of missing targets. Unavailable path information, dangling symlink destinations, and paths containing parent traversal (`..`) remain unclassified rather than risking an incorrect outside-root claim. This location observation does not identify which rule caused a denial.
|
|
132
|
+
|
|
133
|
+
Antigravity's terminal `denied_actions` summary can establish a denial even when its detailed tool-error update is absent. It establishes a minimum of one observation per operation category, not additional calls on top of already-correlated denials. Summary entries have no tool-call IDs or file parameters, so the adapter does not attribute them to nearby or unfinished file requests.
|
|
134
|
+
|
|
135
|
+
Failures also include bounded requested file-target metadata in both Debug and caller-visible feedback:
|
|
136
|
+
|
|
137
|
+
- `deniedResources` lists at most eight distinct `{ operation, requestedPath }` references for known file operations observed as denied. Targets come from structured parameters on the denied event or earlier updates correlated by tool-call ID, never from error text, command parsing, nearby calls, or transcripts.
|
|
138
|
+
- `requestedPath` preserves the supplied path, including significant whitespace. It is not a canonical filesystem path or the permission checker's resolved target, and does not identify a permanent path blacklist or the denying rule. Paths are limited to 1,024 UTF-16 characters; longer paths are unavailable rather than silently shortened.
|
|
139
|
+
- `deniedResourcesOmitted` counts file-denial observations whose known targets were omitted because the list was full, not unique omitted paths. Repeated denials for listed targets do not duplicate entries.
|
|
140
|
+
- `deniedResourcePathsUnavailable` counts known file-denial observations without an available, bounded structured target. Missing correlation or parameters remain unknown; the adapter does not infer a path from prose. Explicit replacement targets supersede earlier metadata, including when the replacement is invalid or overlong; only omitted path parameters preserve an earlier target.
|
|
141
|
+
- `unfinishedResources` separately lists at most eight distinct requested targets from ID-correlated file calls with no observed terminal outcome. These are **unknown outcomes, not confirmed denials**, even if a terminal summary reports the same operation category. Successful and already-denied calls are excluded. `unfinishedResourcesOmitted` counts capacity-omitted observations; `unfinishedResourcePathsUnavailable` counts observations lacking a usable target. The same path limits, replacement semantics, and deduplication rules apply as for denied resources.
|
|
142
|
+
- Caller-visible references are JSON-quoted, with terminal control characters and line separators escaped. **Denied and unfinished requested paths can contain sensitive metadata and are exposed to the calling agent and persisted with enabled Debug events.**
|
|
143
|
+
|
|
144
|
+
Events otherwise exclude prompts, answers, arbitrary tool names, raw stdout, stderr, complete tool payloads, and error causes, even when raw Debug access is enabled. The event's `cwd` identifies the selected working directory. Successful Consultations do not gain a failure event merely because an action was denied.
|
|
105
145
|
|
|
106
146
|
## Security limits
|
|
107
147
|
|
|
108
|
-
The Antigravity adapter enforces its Inspection Permission Set in the Isolated Antigravity Home. It allows `read_url(*)` and denies known file writes, commands,
|
|
148
|
+
The Antigravity adapter enforces its Inspection Permission Set in the Isolated Antigravity Home. It allows `read_url(*)` and denies known file writes, commands, MCP, and URL execution. The `command(*)` rule covers commands inside and outside Antigravity's terminal sandbox; the obsolete, ignored `unsandboxed(*)` rule is not emitted. These rules are **not an operating-system sandbox**.
|
|
109
149
|
|
|
110
|
-
For
|
|
150
|
+
For Consultations with Workspace Access or additional read-only directories, project-local hooks can run commands, read the Antigravity transcript, and cause side effects outside these permission rules. The adapter probes hook state with the additional roots before the paid process and warns when hooks are active or unknown. Directory grants are intended for inspection artifacts, not untrusted project customizations.
|
|
111
151
|
|
|
112
152
|
`read_url(*)` can reach local, private-network, and link-local endpoints that Antigravity accepts. It does not enforce public-internet-only access.
|
|
113
153
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-consulting",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.2.0",
|
|
4
4
|
"description": "Bounded consultations with explicit agent and workspace selection and observed evidence",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"docs/adr/*.md"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@mrclrchtr/supi-core": "11.
|
|
40
|
+
"@mrclrchtr/supi-core": "11.2.0"
|
|
41
41
|
},
|
|
42
42
|
"bundledDependencies": [
|
|
43
43
|
"@mrclrchtr/supi-core"
|
package/scripts/live-probe.ts
CHANGED
|
@@ -30,14 +30,17 @@ async function main(): Promise<void> {
|
|
|
30
30
|
requireWebEvidence(first);
|
|
31
31
|
|
|
32
32
|
const second = await adapter.execute({
|
|
33
|
-
prompt:
|
|
33
|
+
prompt:
|
|
34
|
+
"Without using tools, return only the exact context token I gave you in the preceding request.",
|
|
34
35
|
model: MODEL,
|
|
35
36
|
canonicalWorkingDirectory: consultationWorkspace,
|
|
36
37
|
workspaceAccess: false,
|
|
37
38
|
continuation: first.continuation,
|
|
38
39
|
});
|
|
39
|
-
if (
|
|
40
|
-
throw new Error(
|
|
40
|
+
if (second.answer.answer.trim() !== CONTEXT_TOKEN) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"The follow-up did not return the requested current-turn answer from conversation context.",
|
|
43
|
+
);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
const fixture = await realpath(await mkdtemp(join(tmpdir(), "supi-consulting-live-")));
|
|
@@ -111,8 +111,10 @@ class AntigravityAdapter implements ConsultingAgentAdapter {
|
|
|
111
111
|
? decodeContinuation(options.continuation)
|
|
112
112
|
: undefined;
|
|
113
113
|
await prepareIsolatedAntigravityHome(this.#paths);
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
const inspectHooks =
|
|
115
|
+
options.workspaceAccess || (options.additionalReadOnlyDirectories?.length ?? 0) > 0;
|
|
116
|
+
if (inspectHooks) onPhase("hook-probe");
|
|
117
|
+
const hookWarning = inspectHooks
|
|
116
118
|
? await inspectProjectHooks(this.#paths, options, options.onWarning)
|
|
117
119
|
: undefined;
|
|
118
120
|
onPhase("conversation");
|
|
@@ -123,6 +125,7 @@ class AntigravityAdapter implements ConsultingAgentAdapter {
|
|
|
123
125
|
model: options.model,
|
|
124
126
|
...(expectedConversationId ? { conversationId: expectedConversationId } : {}),
|
|
125
127
|
...(options.workspaceAccess ? { workspaceDirectory: options.canonicalWorkingDirectory } : {}),
|
|
128
|
+
additionalReadOnlyDirectories: options.additionalReadOnlyDirectories,
|
|
126
129
|
schema: ANTIGRAVITY_ANSWER_SCHEMA as Record<string, unknown>,
|
|
127
130
|
signal: options.signal,
|
|
128
131
|
onActivity: options.onProgress,
|
|
@@ -161,6 +164,7 @@ async function inspectProjectHooks(
|
|
|
161
164
|
const probe = await probeProjectHooks({
|
|
162
165
|
paths,
|
|
163
166
|
cwd: options.canonicalWorkingDirectory,
|
|
167
|
+
additionalReadOnlyDirectories: options.additionalReadOnlyDirectories,
|
|
164
168
|
signal: options.signal,
|
|
165
169
|
onProcessStart: options.onProcessStart,
|
|
166
170
|
});
|
|
@@ -67,13 +67,7 @@ export const INSPECTION_PERMISSION_SET = Object.freeze({
|
|
|
67
67
|
allowNonWorkspaceAccess: false,
|
|
68
68
|
permissions: Object.freeze({
|
|
69
69
|
allow: Object.freeze(["read_url(*)"]),
|
|
70
|
-
deny: Object.freeze([
|
|
71
|
-
"write_file(*)",
|
|
72
|
-
"command(*)",
|
|
73
|
-
"unsandboxed(*)",
|
|
74
|
-
"mcp(*)",
|
|
75
|
-
"execute_url(*)",
|
|
76
|
-
]),
|
|
70
|
+
deny: Object.freeze(["write_file(*)", "command(*)", "mcp(*)", "execute_url(*)"]),
|
|
77
71
|
}),
|
|
78
72
|
});
|
|
79
73
|
|
|
@@ -171,24 +171,42 @@ export function extractUrl(value: unknown): string | undefined {
|
|
|
171
171
|
);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
174
|
+
const WORKSPACE_PATH_FIELDS = [
|
|
175
|
+
"path",
|
|
176
|
+
"file",
|
|
177
|
+
"file_path",
|
|
178
|
+
"relative_path",
|
|
179
|
+
"directory_path",
|
|
180
|
+
"search_path",
|
|
181
|
+
"search_directory",
|
|
182
|
+
"absolute_path",
|
|
183
|
+
"directory",
|
|
184
|
+
] as const;
|
|
185
|
+
|
|
186
|
+
/** Distinguish an omitted path parameter from an explicitly supplied but invalid target. */
|
|
187
|
+
export function structuredWorkspacePath(value: unknown): { value: unknown } | undefined {
|
|
188
|
+
if (!isRecord(value)) return undefined;
|
|
189
|
+
for (const name of WORKSPACE_PATH_FIELDS) {
|
|
190
|
+
for (const [key, field] of Object.entries(value)) {
|
|
191
|
+
if (normalizeFieldName(key) === normalizeFieldName(name)) return { value: field };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Read a structured path; requested-target diagnostics preserve significant filename whitespace. */
|
|
198
|
+
export function extractWorkspacePath(
|
|
199
|
+
value: unknown,
|
|
200
|
+
options: { preserveWhitespace?: boolean } = {},
|
|
201
|
+
): string | undefined {
|
|
202
|
+
return readBoundedField(value, WORKSPACE_PATH_FIELDS, () => true, options.preserveWhitespace);
|
|
186
203
|
}
|
|
187
204
|
|
|
188
205
|
function readBoundedField(
|
|
189
206
|
value: unknown,
|
|
190
207
|
names: readonly string[],
|
|
191
208
|
predicate: (candidate: string) => boolean = () => true,
|
|
209
|
+
preserveWhitespace = false,
|
|
192
210
|
): string | undefined {
|
|
193
211
|
if (!isRecord(value)) return undefined;
|
|
194
212
|
const fields = Object.entries(value);
|
|
@@ -196,13 +214,20 @@ function readBoundedField(
|
|
|
196
214
|
const normalizedName = normalizeFieldName(name);
|
|
197
215
|
for (const [key, field] of fields) {
|
|
198
216
|
if (normalizeFieldName(key) !== normalizedName) continue;
|
|
199
|
-
const candidate =
|
|
217
|
+
const candidate = boundedFieldText(field, preserveWhitespace);
|
|
200
218
|
if (candidate && predicate(candidate)) return candidate;
|
|
201
219
|
}
|
|
202
220
|
}
|
|
203
221
|
return undefined;
|
|
204
222
|
}
|
|
205
223
|
|
|
224
|
+
function boundedFieldText(value: unknown, preserveWhitespace: boolean): string | undefined {
|
|
225
|
+
if (preserveWhitespace && typeof value === "string") {
|
|
226
|
+
return value.length > 0 && value.length <= 4_096 ? value : undefined;
|
|
227
|
+
}
|
|
228
|
+
return safeString(value, 4_096);
|
|
229
|
+
}
|
|
230
|
+
|
|
206
231
|
function normalizeFieldName(value: string): string {
|
|
207
232
|
return value.toLowerCase().replace(/[-_]/g, "");
|
|
208
233
|
}
|
|
@@ -246,6 +271,7 @@ function selectAnswerFields(value: Record<string, unknown>): Record<string, unkn
|
|
|
246
271
|
answer: value.answer,
|
|
247
272
|
sources: value.sources,
|
|
248
273
|
workspaceEvidence: value.workspaceEvidence,
|
|
274
|
+
...(Object.hasOwn(value, "responseToken") ? { responseToken: value.responseToken } : {}),
|
|
249
275
|
};
|
|
250
276
|
}
|
|
251
277
|
|