@mrclrchtr/supi-consulting 0.0.0-stage → 8.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/CLAUDE.md +23 -0
- package/CONTEXT.md +53 -0
- package/README.md +104 -2
- package/docs/adr/0001-use-an-isolated-antigravity-home.md +5 -0
- package/node_modules/@mrclrchtr/supi-core/README.md +137 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +77 -0
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +42 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +342 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +363 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +17 -0
- package/node_modules/@mrclrchtr/supi-core/src/context/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-core/src/context/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/context.ts +8 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +313 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +120 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +14 -0
- package/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +41 -0
- package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +64 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +38 -0
- package/node_modules/@mrclrchtr/supi-core/src/llm.ts +356 -0
- package/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
- package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +44 -0
- package/node_modules/@mrclrchtr/supi-core/src/path.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-core/src/project.ts +15 -0
- package/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +93 -0
- package/node_modules/@mrclrchtr/supi-core/src/report.ts +121 -0
- package/node_modules/@mrclrchtr/supi-core/src/session-capabilities.ts +242 -0
- package/node_modules/@mrclrchtr/supi-core/src/session-capability-profiles.ts +240 -0
- package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +71 -0
- package/node_modules/@mrclrchtr/supi-core/src/session.ts +47 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +105 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +454 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings.ts +36 -0
- package/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
- package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/node_modules/@mrclrchtr/supi-core/src/tui.ts +141 -0
- package/package.json +78 -4
- package/scripts/live-probe.ts +139 -0
- package/src/agents/antigravity/activity.ts +22 -0
- package/src/agents/antigravity/adapter.ts +196 -0
- package/src/agents/antigravity/availability.ts +231 -0
- package/src/agents/antigravity/isolated-home.ts +346 -0
- package/src/agents/antigravity/models.ts +15 -0
- package/src/agents/antigravity/process/environment.ts +33 -0
- package/src/agents/antigravity/process/event-values.ts +273 -0
- package/src/agents/antigravity/process/events.ts +366 -0
- package/src/agents/antigravity/process/hooks.ts +219 -0
- package/src/agents/antigravity/process/ndjson.ts +120 -0
- package/src/agents/antigravity/process/protocol.ts +69 -0
- package/src/agents/antigravity/process/runner.ts +147 -0
- package/src/agents/antigravity/process/subprocess.ts +278 -0
- package/src/agents/antigravity/process/usage.ts +50 -0
- package/src/agents/antigravity/structured-output.ts +58 -0
- package/src/agents/antigravity/types.ts +17 -0
- package/src/agents/errors.ts +27 -0
- package/src/agents/types.ts +43 -0
- package/src/catalogue.ts +17 -0
- package/src/config.ts +26 -0
- package/src/conversation/handles.ts +187 -0
- package/src/evidence-hash.ts +6 -0
- package/src/extension.ts +26 -0
- package/src/footer-constants.ts +5 -0
- package/src/footer.ts +25 -0
- package/src/runtime.ts +244 -0
- package/src/settings.ts +38 -0
- package/src/tool/consulting_run/evidence.ts +169 -0
- package/src/tool/consulting_run/execute.ts +201 -0
- package/src/tool/consulting_run/guidance.ts +3 -0
- package/src/tool/consulting_run/input.ts +117 -0
- package/src/tool/consulting_run/register.ts +36 -0
- package/src/tool/consulting_run/render.ts +261 -0
- package/src/tool/consulting_run/result.ts +185 -0
- package/src/tool/consulting_run/spec.ts +20 -0
- package/src/types.ts +104 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/** Compact TUI renderers for Consultation calls and results. */
|
|
2
|
+
import { getMarkdownTheme, type Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
4
|
+
import type {
|
|
5
|
+
ClassifiedSource,
|
|
6
|
+
ClassifiedWorkspaceEvidence,
|
|
7
|
+
ConsultationActivity,
|
|
8
|
+
ConsultationResultDetails,
|
|
9
|
+
} from "../../types.ts";
|
|
10
|
+
import { CONSULTING_RUN_TOOL_LABEL } from "./spec.ts";
|
|
11
|
+
|
|
12
|
+
const MAX_PROMPT_PREVIEW = 120;
|
|
13
|
+
const MAX_ERROR_PREVIEW = 500;
|
|
14
|
+
|
|
15
|
+
/** Render the tool label, safe prompt preview, selected agent, model, and workspace. */
|
|
16
|
+
export function renderConsultingCall(args: unknown, theme: Theme, _context?: unknown): Text {
|
|
17
|
+
const params = isRecord(args) ? args : {};
|
|
18
|
+
const prompt = typeof params.prompt === "string" ? safePreview(params.prompt) : undefined;
|
|
19
|
+
const newInput = isRecord(params.new) ? params.new : undefined;
|
|
20
|
+
const agent = typeof newInput?.agent === "string" ? newInput.agent : undefined;
|
|
21
|
+
const model = typeof newInput?.model === "string" ? newInput.model : undefined;
|
|
22
|
+
const workspace = typeof newInput?.workspace === "boolean" ? newInput.workspace : undefined;
|
|
23
|
+
const continuation = isRecord(params.continue);
|
|
24
|
+
const parts = [
|
|
25
|
+
theme.fg("toolTitle", CONSULTING_RUN_TOOL_LABEL),
|
|
26
|
+
agent ? theme.fg("accent", agent) : undefined,
|
|
27
|
+
model ? theme.fg("accent", model) : continuation ? theme.fg("accent", "follow-up") : undefined,
|
|
28
|
+
workspace === undefined
|
|
29
|
+
? undefined
|
|
30
|
+
: theme.fg("dim", workspace ? "workspace" : "Consultation Workspace"),
|
|
31
|
+
prompt ? theme.fg("dim", `· ${prompt}`) : undefined,
|
|
32
|
+
].filter((part): part is string => part !== undefined);
|
|
33
|
+
return new Text(parts.join(" "), 0, 0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Render partial, collapsed, expanded, and error Consultation states. */
|
|
37
|
+
export function renderConsultingResult(
|
|
38
|
+
result: {
|
|
39
|
+
content?: Array<{ type: string; text?: string }>;
|
|
40
|
+
details?: unknown;
|
|
41
|
+
isError?: boolean;
|
|
42
|
+
},
|
|
43
|
+
options: { expanded: boolean; isPartial: boolean },
|
|
44
|
+
theme: Theme,
|
|
45
|
+
context: { isError?: boolean } = {},
|
|
46
|
+
): Container | Text {
|
|
47
|
+
if (options.isPartial) return renderPartial(result.details, options.expanded, theme);
|
|
48
|
+
if (result.isError || context.isError)
|
|
49
|
+
return renderError(result.content, options.expanded, theme);
|
|
50
|
+
const details = asDetails(result.details);
|
|
51
|
+
if (!details) return new Text(theme.fg("dim", "Consultation finished."), 0, 0);
|
|
52
|
+
return options.expanded
|
|
53
|
+
? renderExpanded(details, result.content, theme)
|
|
54
|
+
: renderCollapsed(details, theme);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function renderPartial(detailsValue: unknown, expanded: boolean, theme: Theme): Text | Container {
|
|
58
|
+
const details = isRecord(detailsValue) ? detailsValue : {};
|
|
59
|
+
const model = typeof details.model === "string" ? details.model : "model pending";
|
|
60
|
+
const agent = typeof details.agent === "string" ? details.agent : "agent pending";
|
|
61
|
+
const workspace = details.workspaceAccess === true ? "workspace" : "Consultation Workspace";
|
|
62
|
+
const activity =
|
|
63
|
+
typeof details.latestActivity === "string" ? safePreview(details.latestActivity) : "preparing";
|
|
64
|
+
const line = theme.fg(
|
|
65
|
+
"warning",
|
|
66
|
+
`● ${CONSULTING_RUN_TOOL_LABEL} · ${agent} · ${model} · ${workspace}`,
|
|
67
|
+
);
|
|
68
|
+
if (!expanded) return new Text(`${line}\n${theme.fg("dim", activity)}`, 0, 0);
|
|
69
|
+
const container = new Container();
|
|
70
|
+
container.addChild(new Text(line, 0, 0));
|
|
71
|
+
container.addChild(new Text(theme.fg("dim", `activity: ${activity}`), 1, 0));
|
|
72
|
+
return container;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function renderCollapsed(details: ConsultationResultDetails, theme: Theme): Text {
|
|
76
|
+
const usage = formatUsage(details.usage);
|
|
77
|
+
const evidence = [
|
|
78
|
+
`${details.observedSources.length} observed source${details.observedSources.length === 1 ? "" : "s"}`,
|
|
79
|
+
`${details.observedWorkspaceEvidence.length} observed path${details.observedWorkspaceEvidence.length === 1 ? "" : "s"}`,
|
|
80
|
+
];
|
|
81
|
+
if (usage) evidence.push(usage);
|
|
82
|
+
const warning = details.warnings.length > 0 ? ` · ${details.warnings.length} warning(s)` : "";
|
|
83
|
+
return new Text(
|
|
84
|
+
`${theme.fg("success", "✓ Consultation complete")} · ${details.agent} · ${details.handle} · ${evidence.join(" · ")}${warning}`,
|
|
85
|
+
0,
|
|
86
|
+
0,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function renderExpanded(
|
|
91
|
+
details: ConsultationResultDetails,
|
|
92
|
+
content: Array<{ type: string; text?: string }> | undefined,
|
|
93
|
+
theme: Theme,
|
|
94
|
+
): Container {
|
|
95
|
+
const container = new Container();
|
|
96
|
+
container.addChild(
|
|
97
|
+
new Text(
|
|
98
|
+
theme.fg(
|
|
99
|
+
"accent",
|
|
100
|
+
`${theme.bold("Consultation complete")} · ${details.agent} · ${details.model} · ${details.workingDirectoryKind}`,
|
|
101
|
+
),
|
|
102
|
+
0,
|
|
103
|
+
0,
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
container.addChild(new Text(theme.fg("dim", `handle: ${details.handle}`), 1, 0));
|
|
107
|
+
container.addChild(
|
|
108
|
+
new Text(
|
|
109
|
+
theme.fg(
|
|
110
|
+
"dim",
|
|
111
|
+
`Agent version: ${details.agentVersion} · ${formatUsage(details.usage) || "usage unavailable"}`,
|
|
112
|
+
),
|
|
113
|
+
1,
|
|
114
|
+
0,
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
container.addChild(
|
|
118
|
+
new Text(
|
|
119
|
+
theme.fg(
|
|
120
|
+
"dim",
|
|
121
|
+
`web: ${details.webUsed ? "used" : "not used"} · workspace: ${details.workspaceUsed ? "used" : "not used"} · project guidance: ${details.ambientProjectGuidanceAvailable ? "available" : "not available"}`,
|
|
122
|
+
),
|
|
123
|
+
1,
|
|
124
|
+
0,
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
renderSources(container, "Observed sources", details.observedSources, theme);
|
|
128
|
+
renderSources(container, "Claimed sources", details.claimedSources, theme);
|
|
129
|
+
renderWorkspace(container, "Observed workspace paths", details.observedWorkspaceEvidence, theme);
|
|
130
|
+
renderWorkspace(container, "Claimed workspace paths", details.claimedWorkspaceEvidence, theme);
|
|
131
|
+
if (details.warnings.length > 0) {
|
|
132
|
+
container.addChild(new Spacer(1));
|
|
133
|
+
container.addChild(new Text(theme.fg("warning", "Warnings"), 1, 0));
|
|
134
|
+
for (const warning of details.warnings)
|
|
135
|
+
container.addChild(new Text(theme.fg("dim", `- ${warning}`), 1, 0));
|
|
136
|
+
}
|
|
137
|
+
const markdown = content?.find((item) => item.type === "text")?.text;
|
|
138
|
+
if (markdown) {
|
|
139
|
+
container.addChild(new Spacer(1));
|
|
140
|
+
container.addChild(new Markdown(markdown, 1, 0, getMarkdownTheme()));
|
|
141
|
+
}
|
|
142
|
+
return container;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function renderSources(
|
|
146
|
+
container: Container,
|
|
147
|
+
title: string,
|
|
148
|
+
sources: readonly ClassifiedSource[],
|
|
149
|
+
theme: Theme,
|
|
150
|
+
): void {
|
|
151
|
+
if (sources.length === 0) return;
|
|
152
|
+
container.addChild(new Spacer(1));
|
|
153
|
+
container.addChild(new Text(theme.fg("muted", title), 1, 0));
|
|
154
|
+
for (const source of sources) {
|
|
155
|
+
container.addChild(
|
|
156
|
+
new Text(theme.fg("dim", `- ${safePreview(source.title)}: ${source.url}`), 2, 0),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function renderWorkspace(
|
|
162
|
+
container: Container,
|
|
163
|
+
title: string,
|
|
164
|
+
evidence: readonly ClassifiedWorkspaceEvidence[],
|
|
165
|
+
theme: Theme,
|
|
166
|
+
): void {
|
|
167
|
+
if (evidence.length === 0) return;
|
|
168
|
+
container.addChild(new Spacer(1));
|
|
169
|
+
container.addChild(new Text(theme.fg("muted", title), 1, 0));
|
|
170
|
+
for (const item of evidence) {
|
|
171
|
+
container.addChild(
|
|
172
|
+
new Text(theme.fg("dim", `- ${item.path}: ${safePreview(item.summary)}`), 2, 0),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function renderError(
|
|
178
|
+
content: Array<{ type: string; text?: string }> | undefined,
|
|
179
|
+
expanded: boolean,
|
|
180
|
+
theme: Theme,
|
|
181
|
+
): Text {
|
|
182
|
+
const text = content?.find((item) => item.type === "text")?.text;
|
|
183
|
+
const message = text ? safePreview(text, expanded ? MAX_ERROR_PREVIEW : 160) : "operation failed";
|
|
184
|
+
return new Text(theme.fg("error", `Consultation failed: ${message}`), 0, 0);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function asDetails(value: unknown): ConsultationResultDetails | undefined {
|
|
188
|
+
if (!isRecord(value)) return undefined;
|
|
189
|
+
if (
|
|
190
|
+
typeof value.agent !== "string" ||
|
|
191
|
+
typeof value.model !== "string" ||
|
|
192
|
+
typeof value.agentVersion !== "string" ||
|
|
193
|
+
typeof value.handle !== "string" ||
|
|
194
|
+
typeof value.continuation !== "string" ||
|
|
195
|
+
(value.workingDirectoryKind !== "workspace" && value.workingDirectoryKind !== "consultation") ||
|
|
196
|
+
!Array.isArray(value.observedActivities) ||
|
|
197
|
+
!value.observedActivities.every(isConsultationActivity) ||
|
|
198
|
+
!isActivityCounts(value.activityCounts) ||
|
|
199
|
+
!Array.isArray(value.observedSources) ||
|
|
200
|
+
!Array.isArray(value.claimedSources) ||
|
|
201
|
+
!Array.isArray(value.observedWorkspaceEvidence) ||
|
|
202
|
+
!Array.isArray(value.claimedWorkspaceEvidence) ||
|
|
203
|
+
!Array.isArray(value.warnings) ||
|
|
204
|
+
!value.observedSources.every(isClassifiedSource) ||
|
|
205
|
+
!value.claimedSources.every(isClassifiedSource) ||
|
|
206
|
+
!value.observedWorkspaceEvidence.every(isClassifiedWorkspaceEvidence) ||
|
|
207
|
+
!value.claimedWorkspaceEvidence.every(isClassifiedWorkspaceEvidence) ||
|
|
208
|
+
!value.warnings.every((warning) => typeof warning === "string")
|
|
209
|
+
) {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
return value as unknown as ConsultationResultDetails;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function formatUsage(
|
|
216
|
+
usage: { totalTokens?: number; inputTokens?: number; outputTokens?: number } | undefined,
|
|
217
|
+
): string {
|
|
218
|
+
if (!usage) return "";
|
|
219
|
+
const total = usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
|
|
220
|
+
return `${total.toLocaleString("en-US")} tokens`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function isClassifiedSource(value: unknown): value is ClassifiedSource {
|
|
224
|
+
return (
|
|
225
|
+
isRecord(value) &&
|
|
226
|
+
typeof value.title === "string" &&
|
|
227
|
+
typeof value.url === "string" &&
|
|
228
|
+
(value.evidence === "observed" || value.evidence === "claimed")
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function isClassifiedWorkspaceEvidence(value: unknown): value is ClassifiedWorkspaceEvidence {
|
|
233
|
+
return (
|
|
234
|
+
isRecord(value) &&
|
|
235
|
+
typeof value.path === "string" &&
|
|
236
|
+
typeof value.summary === "string" &&
|
|
237
|
+
(value.evidence === "observed" || value.evidence === "claimed")
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function isConsultationActivity(value: unknown): value is ConsultationActivity {
|
|
242
|
+
return value === "web" || value === "workspace" || value === "other";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function isActivityCounts(value: unknown): value is Record<ConsultationActivity, number> {
|
|
246
|
+
return (
|
|
247
|
+
isRecord(value) &&
|
|
248
|
+
["web", "workspace", "other"].every(
|
|
249
|
+
(key) => typeof value[key] === "number" && Number.isFinite(value[key]),
|
|
250
|
+
)
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function safePreview(value: string, maxLength = MAX_PROMPT_PREVIEW): string {
|
|
255
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
256
|
+
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 1)}…`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
260
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
261
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { Usage } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type {
|
|
4
|
+
ClassifiedSource,
|
|
5
|
+
ClassifiedWorkspaceEvidence,
|
|
6
|
+
ConsultationExecutionFacts,
|
|
7
|
+
ConsultationResultDetails,
|
|
8
|
+
ConversationHandleRecord,
|
|
9
|
+
} from "../../types.ts";
|
|
10
|
+
import { classifyAnswerEvidence } from "./evidence.ts";
|
|
11
|
+
|
|
12
|
+
/** PI's hard result bounds for model-visible Consultation output. */
|
|
13
|
+
export const MAX_RESULT_BYTES = 50 * 1024;
|
|
14
|
+
export const MAX_RESULT_LINES = 2_000;
|
|
15
|
+
|
|
16
|
+
/** Inputs needed to assemble one completed Consultation result. */
|
|
17
|
+
export interface ConsultationResultOptions {
|
|
18
|
+
facts: ConsultationExecutionFacts;
|
|
19
|
+
handle: ConversationHandleRecord;
|
|
20
|
+
durationMs: number;
|
|
21
|
+
hookWarning?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Assemble model-visible evidence and free structured renderer details. */
|
|
25
|
+
export function buildConsultationResult(
|
|
26
|
+
options: ConsultationResultOptions,
|
|
27
|
+
): AgentToolResult<ConsultationResultDetails> {
|
|
28
|
+
const { facts, handle } = options;
|
|
29
|
+
const evidence = classifyAnswerEvidence(facts.answer, facts, handle.canonicalWorkingDirectory);
|
|
30
|
+
const warnings = buildWarnings({
|
|
31
|
+
agentWarnings: facts.warnings,
|
|
32
|
+
evidenceWarnings: evidence.warnings,
|
|
33
|
+
observedSourceCount: evidence.observedSources.length,
|
|
34
|
+
observedWorkspaceEvidenceCount: evidence.observedWorkspaceEvidence.length,
|
|
35
|
+
webUsed: facts.webUsed,
|
|
36
|
+
workspaceUsed: facts.workspaceUsed,
|
|
37
|
+
permissionDenials: facts.permissionDenials,
|
|
38
|
+
hookWarning: options.hookWarning,
|
|
39
|
+
});
|
|
40
|
+
const details: ConsultationResultDetails = {
|
|
41
|
+
agent: handle.agent,
|
|
42
|
+
model: handle.model,
|
|
43
|
+
workingDirectoryKind: handle.workspaceAccess ? "workspace" : "consultation",
|
|
44
|
+
canonicalWorkingDirectory: handle.canonicalWorkingDirectory,
|
|
45
|
+
workspaceAccess: handle.workspaceAccess,
|
|
46
|
+
agentVersion: handle.agentVersion,
|
|
47
|
+
durationMs: Math.max(0, Math.floor(options.durationMs)),
|
|
48
|
+
...(facts.usage ? { usage: facts.usage } : {}),
|
|
49
|
+
observedActivities: facts.observedActivities,
|
|
50
|
+
activityCounts: facts.activityCounts,
|
|
51
|
+
permissionDenials: facts.permissionDenials,
|
|
52
|
+
webUsed: facts.webUsed,
|
|
53
|
+
workspaceUsed: facts.workspaceUsed,
|
|
54
|
+
ambientProjectGuidanceAvailable: handle.workspaceAccess,
|
|
55
|
+
...(options.hookWarning ? { activeProjectHookWarning: options.hookWarning } : {}),
|
|
56
|
+
handle: handle.handle,
|
|
57
|
+
continuation: handle.continuation,
|
|
58
|
+
handleState: "active",
|
|
59
|
+
observedSources: evidence.observedSources,
|
|
60
|
+
claimedSources: evidence.claimedSources,
|
|
61
|
+
observedWorkspaceEvidence: evidence.observedWorkspaceEvidence,
|
|
62
|
+
claimedWorkspaceEvidence: evidence.claimedWorkspaceEvidence,
|
|
63
|
+
warnings,
|
|
64
|
+
};
|
|
65
|
+
const text = formatModelResult(facts.answer.answer, details);
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: "text", text }],
|
|
68
|
+
details,
|
|
69
|
+
...(facts.usage ? { usage: toPiUsage(facts.usage) } : {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function formatModelResult(answer: string, details: ConsultationResultDetails): string {
|
|
74
|
+
const sections = [
|
|
75
|
+
answer.trim(),
|
|
76
|
+
"",
|
|
77
|
+
`Web Capability: ${details.webUsed ? "used" : "not used"}`,
|
|
78
|
+
`Workspace Access: ${details.workspaceAccess ? "current workspace" : "Consultation Workspace"}`,
|
|
79
|
+
`Ambient Project Guidance: ${details.ambientProjectGuidanceAvailable ? "available" : "not available"}`,
|
|
80
|
+
formatSources("Observed Sources", details.observedSources),
|
|
81
|
+
formatSources("Claimed Sources (not observed)", details.claimedSources),
|
|
82
|
+
formatWorkspace("Observed Workspace Evidence", details.observedWorkspaceEvidence),
|
|
83
|
+
formatWorkspace("Claimed Workspace Evidence (not observed)", details.claimedWorkspaceEvidence),
|
|
84
|
+
formatWarnings(details.warnings),
|
|
85
|
+
`Conversation Handle: ${details.handle}`,
|
|
86
|
+
];
|
|
87
|
+
return boundResult(sections.filter((section) => section.length > 0).join("\n"));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function formatSources(title: string, sources: readonly ClassifiedSource[]): string {
|
|
91
|
+
if (sources.length === 0) return `${title}: none`;
|
|
92
|
+
return [
|
|
93
|
+
title,
|
|
94
|
+
...sources.map((source) => `- [${safeMarkdown(source.title)}](${source.url})`),
|
|
95
|
+
].join("\n");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatWorkspace(title: string, evidence: readonly ClassifiedWorkspaceEvidence[]): string {
|
|
99
|
+
if (evidence.length === 0) return `${title}: none`;
|
|
100
|
+
return [title, ...evidence.map((item) => `- ${item.path}: ${safeMarkdown(item.summary)}`)].join(
|
|
101
|
+
"\n",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatWarnings(warnings: readonly string[]): string {
|
|
106
|
+
return warnings.length > 0
|
|
107
|
+
? ["Warnings", ...warnings.map((warning) => `- ${warning}`)].join("\n")
|
|
108
|
+
: "Warnings: none";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildWarnings(options: {
|
|
112
|
+
agentWarnings: readonly string[];
|
|
113
|
+
evidenceWarnings: readonly string[];
|
|
114
|
+
observedSourceCount: number;
|
|
115
|
+
observedWorkspaceEvidenceCount: number;
|
|
116
|
+
webUsed: boolean;
|
|
117
|
+
workspaceUsed: boolean;
|
|
118
|
+
permissionDenials: number;
|
|
119
|
+
hookWarning?: string;
|
|
120
|
+
}): string[] {
|
|
121
|
+
const warnings = [...options.agentWarnings, ...options.evidenceWarnings];
|
|
122
|
+
if (options.hookWarning) warnings.push(options.hookWarning);
|
|
123
|
+
if (options.permissionDenials > 0) {
|
|
124
|
+
warnings.push(
|
|
125
|
+
`${options.permissionDenials} agent action(s) were denied by its permission policy.`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (options.webUsed && options.observedSourceCount === 0) {
|
|
129
|
+
warnings.push("The agent used web capability, but no source URL was observed in the answer.");
|
|
130
|
+
}
|
|
131
|
+
if (options.workspaceUsed && options.observedWorkspaceEvidenceCount === 0) {
|
|
132
|
+
warnings.push(
|
|
133
|
+
"The agent used workspace access, but no workspace path was observed in the answer.",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return [...new Set(warnings)].slice(0, 24);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function boundResult(value: string): string {
|
|
140
|
+
const lines = value.split(/\r?\n/);
|
|
141
|
+
const result = lines.slice(0, MAX_RESULT_LINES).join("\n");
|
|
142
|
+
const truncated =
|
|
143
|
+
lines.length > MAX_RESULT_LINES || Buffer.byteLength(result, "utf8") > MAX_RESULT_BYTES;
|
|
144
|
+
if (!truncated) return result;
|
|
145
|
+
|
|
146
|
+
const marker = "[Consultation result truncated to PI limits]";
|
|
147
|
+
const markerBytes = Buffer.byteLength(marker, "utf8") + 1;
|
|
148
|
+
const contentLimit = Math.max(0, MAX_RESULT_BYTES - markerBytes);
|
|
149
|
+
const contentLines = result.split(/\r?\n/).slice(0, MAX_RESULT_LINES - 1);
|
|
150
|
+
const content = truncateUtf8(contentLines.join("\n"), contentLimit);
|
|
151
|
+
return content ? `${content}\n${marker}` : marker;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function truncateUtf8(value: string, maxBytes: number): string {
|
|
155
|
+
let bytes = 0;
|
|
156
|
+
let result = "";
|
|
157
|
+
for (const character of value) {
|
|
158
|
+
const characterBytes = Buffer.byteLength(character, "utf8");
|
|
159
|
+
if (bytes + characterBytes > maxBytes) break;
|
|
160
|
+
result += character;
|
|
161
|
+
bytes += characterBytes;
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function safeMarkdown(value: string): string {
|
|
167
|
+
return value
|
|
168
|
+
.replaceAll("[", "\\[")
|
|
169
|
+
.replaceAll("]", "\\]")
|
|
170
|
+
.replace(/[\r\n]/g, " ");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function toPiUsage(usage: NonNullable<ConsultationExecutionFacts["usage"]>): Usage {
|
|
174
|
+
const input = usage.inputTokens ?? 0;
|
|
175
|
+
const output = usage.outputTokens ?? 0;
|
|
176
|
+
const totalTokens = usage.totalTokens ?? input + output;
|
|
177
|
+
return {
|
|
178
|
+
input,
|
|
179
|
+
output,
|
|
180
|
+
cacheRead: 0,
|
|
181
|
+
cacheWrite: 0,
|
|
182
|
+
totalTokens,
|
|
183
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TSchema } from "typebox";
|
|
2
|
+
import { buildConsultingRunSchema } from "./input.ts";
|
|
3
|
+
|
|
4
|
+
/** Canonical Consulting tool name. */
|
|
5
|
+
export const CONSULTING_RUN_TOOL_NAME = "consulting_run";
|
|
6
|
+
/** Human-facing tool label. */
|
|
7
|
+
export const CONSULTING_RUN_TOOL_LABEL = "Consultation";
|
|
8
|
+
|
|
9
|
+
/** Build provider-facing parameters from one immutable availability snapshot. */
|
|
10
|
+
export function buildConsultingRunParameters(agent: string, catalogue: readonly string[]): TSchema {
|
|
11
|
+
return buildConsultingRunSchema(agent, catalogue);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Canonical machine-readable metadata for consulting_run. */
|
|
15
|
+
export const consultingRunSpec = {
|
|
16
|
+
name: CONSULTING_RUN_TOOL_NAME,
|
|
17
|
+
label: CONSULTING_RUN_TOOL_LABEL,
|
|
18
|
+
exposure: "model-only",
|
|
19
|
+
executionMode: "parallel",
|
|
20
|
+
} as const;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** Identity of a supported Consulting Agent. */
|
|
2
|
+
export type ConsultingAgentId = string;
|
|
3
|
+
|
|
4
|
+
/** Agent-specific model identifier retained as a neutral string. */
|
|
5
|
+
export type ConsultingModel = string;
|
|
6
|
+
|
|
7
|
+
/** Caller choice to expose the current PI workspace to a Consultation. */
|
|
8
|
+
export type WorkspaceAccess = boolean;
|
|
9
|
+
|
|
10
|
+
/** An answer source returned by a Consulting Agent. */
|
|
11
|
+
export interface ConsultationSource {
|
|
12
|
+
title: string;
|
|
13
|
+
url: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Workspace evidence returned by a Consulting Agent. */
|
|
17
|
+
export interface ConsultationWorkspaceEvidence {
|
|
18
|
+
path: string;
|
|
19
|
+
summary: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The structured answer required from a Consulting Agent. */
|
|
23
|
+
export interface ConsultationAnswer {
|
|
24
|
+
answer: string;
|
|
25
|
+
sources: ConsultationSource[];
|
|
26
|
+
workspaceEvidence: ConsultationWorkspaceEvidence[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Token accounting returned by a Consulting Agent. */
|
|
30
|
+
export interface ConsultationUsage {
|
|
31
|
+
inputTokens?: number;
|
|
32
|
+
outputTokens?: number;
|
|
33
|
+
totalTokens?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A validated source classified by observed execution evidence. */
|
|
37
|
+
export interface ClassifiedSource extends ConsultationSource {
|
|
38
|
+
evidence: "observed" | "claimed";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A validated workspace reference classified by observed execution evidence. */
|
|
42
|
+
export interface ClassifiedWorkspaceEvidence extends ConsultationWorkspaceEvidence {
|
|
43
|
+
evidence: "observed" | "claimed";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Normalized activity categories reported by a Consulting Agent adapter. */
|
|
47
|
+
export type ConsultationActivity = "web" | "workspace" | "other";
|
|
48
|
+
|
|
49
|
+
/** Bounded, protocol-neutral facts from one completed Consultation. */
|
|
50
|
+
export interface ConsultationExecutionFacts {
|
|
51
|
+
answer: ConsultationAnswer;
|
|
52
|
+
continuation: string;
|
|
53
|
+
usage?: ConsultationUsage;
|
|
54
|
+
observedActivities: ConsultationActivity[];
|
|
55
|
+
activityCounts: Record<ConsultationActivity, number>;
|
|
56
|
+
webUsed: boolean;
|
|
57
|
+
workspaceUsed: boolean;
|
|
58
|
+
permissionDenials: number;
|
|
59
|
+
observedSourceHashes: string[];
|
|
60
|
+
observedWorkspacePathHashes: string[];
|
|
61
|
+
warnings: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Branch-aware state retained for one opaque Conversation Handle. */
|
|
65
|
+
export interface ConversationHandleRecord {
|
|
66
|
+
handle: string;
|
|
67
|
+
agent: ConsultingAgentId;
|
|
68
|
+
model: ConsultingModel;
|
|
69
|
+
continuation: string;
|
|
70
|
+
canonicalWorkingDirectory: string;
|
|
71
|
+
workspaceAccess: WorkspaceAccess;
|
|
72
|
+
agentVersion: string;
|
|
73
|
+
status: "active" | "retired";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Structured details retained for one completed Consultation. */
|
|
77
|
+
export interface ConsultationResultDetails {
|
|
78
|
+
agent: ConsultingAgentId;
|
|
79
|
+
model: ConsultingModel;
|
|
80
|
+
workingDirectoryKind: "consultation" | "workspace";
|
|
81
|
+
canonicalWorkingDirectory: string;
|
|
82
|
+
workspaceAccess: WorkspaceAccess;
|
|
83
|
+
agentVersion: string;
|
|
84
|
+
durationMs: number;
|
|
85
|
+
usage?: ConsultationUsage;
|
|
86
|
+
observedActivities: ConsultationActivity[];
|
|
87
|
+
activityCounts: Record<ConsultationActivity, number>;
|
|
88
|
+
permissionDenials: number;
|
|
89
|
+
webUsed: boolean;
|
|
90
|
+
workspaceUsed: boolean;
|
|
91
|
+
ambientProjectGuidanceAvailable: boolean;
|
|
92
|
+
activeProjectHookWarning?: string;
|
|
93
|
+
handle: string;
|
|
94
|
+
continuation: string;
|
|
95
|
+
handleState: "active";
|
|
96
|
+
observedSources: ClassifiedSource[];
|
|
97
|
+
claimedSources: ClassifiedSource[];
|
|
98
|
+
observedWorkspaceEvidence: ClassifiedWorkspaceEvidence[];
|
|
99
|
+
claimedWorkspaceEvidence: ClassifiedWorkspaceEvidence[];
|
|
100
|
+
warnings: string[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Callback for safe, bounded progress activity. */
|
|
104
|
+
export type ConsultingProgressCallback = (activity: string) => void;
|