@smithers-orchestrator/cli 0.16.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/LICENSE +21 -0
- package/package.json +55 -0
- package/src/AgentAvailability.ts +13 -0
- package/src/AgentAvailabilityStatus.ts +5 -0
- package/src/AggregateNodeDetailParams.ts +5 -0
- package/src/AskOptions.ts +12 -0
- package/src/ChatAttemptMeta.ts +7 -0
- package/src/ChatAttemptRow.ts +12 -0
- package/src/ChatOutputEvent.ts +6 -0
- package/src/DiffBundleLike.ts +6 -0
- package/src/DiscoveredWorkflow.ts +9 -0
- package/src/EnrichedNodeDetail.ts +60 -0
- package/src/EventCategory.ts +18 -0
- package/src/FindDbWaitOptions.ts +4 -0
- package/src/FormatEventLineOptions.ts +4 -0
- package/src/HijackCandidate.ts +11 -0
- package/src/HijackLaunchSpec.ts +6 -0
- package/src/InitWorkflowPackOptions.ts +4 -0
- package/src/InitWorkflowPackResult.ts +6 -0
- package/src/NativeHijackEngine.ts +8 -0
- package/src/NodeDetailAttempt.ts +22 -0
- package/src/NodeDetailTokenUsage.ts +11 -0
- package/src/NodeDetailToolCall.ts +12 -0
- package/src/ParsedNodeOutputEvent.ts +9 -0
- package/src/RenderNodeDetailOptions.ts +4 -0
- package/src/RunAutoResumeSkipReason.ts +4 -0
- package/src/RunDiffCommandInput.ts +13 -0
- package/src/RunDiffCommandResult.ts +3 -0
- package/src/RunOutputCommandInput.ts +12 -0
- package/src/RunOutputCommandResult.ts +3 -0
- package/src/RunRewindCommandInput.ts +14 -0
- package/src/RunRewindCommandResult.ts +3 -0
- package/src/RunTreeCommandInput.ts +14 -0
- package/src/RunTreeCommandResult.ts +3 -0
- package/src/SmithersEventType.ts +3 -0
- package/src/SupervisorOptions.ts +33 -0
- package/src/SupervisorPollSummary.ts +6 -0
- package/src/TreeRenderOptions.ts +5 -0
- package/src/WatchLoopOptions.ts +9 -0
- package/src/WatchLoopResult.ts +8 -0
- package/src/WatchRenderContext.ts +4 -0
- package/src/WhyBlocker.ts +17 -0
- package/src/WhyBlockerKind.ts +9 -0
- package/src/WhyDiagnosis.ts +10 -0
- package/src/WorkflowCta.ts +4 -0
- package/src/WorkflowSourceType.ts +1 -0
- package/src/agent-detection.js +257 -0
- package/src/ask.js +491 -0
- package/src/chat.js +226 -0
- package/src/diff.js +221 -0
- package/src/event-categories.js +141 -0
- package/src/find-db.js +93 -0
- package/src/format.js +272 -0
- package/src/hijack-session.js +207 -0
- package/src/hijack.js +226 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +4868 -0
- package/src/mcp/SemanticMcpServerOptions.ts +4 -0
- package/src/mcp/SemanticToolCallResult.ts +14 -0
- package/src/mcp/SemanticToolContext.ts +6 -0
- package/src/mcp/SemanticToolDefinition.ts +13 -0
- package/src/mcp/SemanticToolError.ts +6 -0
- package/src/mcp/semantic-server.js +41 -0
- package/src/mcp/semantic-tools.js +1242 -0
- package/src/node-detail.js +682 -0
- package/src/output.js +111 -0
- package/src/resume-detached.js +37 -0
- package/src/rewind.js +88 -0
- package/src/scheduler.js +112 -0
- package/src/smithersRuntime.js +63 -0
- package/src/supervisor.js +418 -0
- package/src/tree.js +307 -0
- package/src/tui/app.jsx +139 -0
- package/src/tui/app.tsx +5 -0
- package/src/tui/components/AskModal.jsx +109 -0
- package/src/tui/components/AskModal.tsx +3 -0
- package/src/tui/components/AttentionPane.jsx +112 -0
- package/src/tui/components/AttentionPane.tsx +6 -0
- package/src/tui/components/ChatPane.jsx +57 -0
- package/src/tui/components/ChatPane.tsx +7 -0
- package/src/tui/components/CronList.jsx +87 -0
- package/src/tui/components/CronList.tsx +5 -0
- package/src/tui/components/DetailsPane.jsx +96 -0
- package/src/tui/components/DetailsPane.tsx +7 -0
- package/src/tui/components/FramesPane.jsx +147 -0
- package/src/tui/components/FramesPane.tsx +8 -0
- package/src/tui/components/LogsPane.jsx +46 -0
- package/src/tui/components/LogsPane.tsx +6 -0
- package/src/tui/components/MetricsPane.jsx +108 -0
- package/src/tui/components/MetricsPane.tsx +5 -0
- package/src/tui/components/NodeDetailView.jsx +284 -0
- package/src/tui/components/NodeDetailView.tsx +7 -0
- package/src/tui/components/NodeInspector.jsx +51 -0
- package/src/tui/components/NodeInspector.tsx +7 -0
- package/src/tui/components/RunDetailView.jsx +190 -0
- package/src/tui/components/RunDetailView.tsx +7 -0
- package/src/tui/components/RunsList.jsx +184 -0
- package/src/tui/components/RunsList.tsx +7 -0
- package/src/tui/components/SqliteBrowser.jsx +131 -0
- package/src/tui/components/SqliteBrowser.tsx +5 -0
- package/src/tui/components/WorkflowLauncher.jsx +63 -0
- package/src/tui/components/WorkflowLauncher.tsx +3 -0
- package/src/util/CliErrorMapping.ts +7 -0
- package/src/util/CliExitCode.ts +10 -0
- package/src/util/errorMessage.js +212 -0
- package/src/util/exitCodes.js +18 -0
- package/src/watch.js +128 -0
- package/src/why-diagnosis.js +1000 -0
- package/src/workflow-pack.js +2151 -0
- package/src/workflows.js +122 -0
package/src/ask.js
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
|
+
import { ClaudeCodeAgent } from "@smithers-orchestrator/agents/ClaudeCodeAgent";
|
|
8
|
+
import { CodexAgent } from "@smithers-orchestrator/agents/CodexAgent";
|
|
9
|
+
import { GeminiAgent } from "@smithers-orchestrator/agents/GeminiAgent";
|
|
10
|
+
import { KimiAgent } from "@smithers-orchestrator/agents/KimiAgent";
|
|
11
|
+
import { PiAgent } from "@smithers-orchestrator/agents/PiAgent";
|
|
12
|
+
import { SmithersError } from "@smithers-orchestrator/errors";
|
|
13
|
+
import { createSmithersAgentContract, renderSmithersAgentPromptGuidance, } from "@smithers-orchestrator/agents/agent-contract";
|
|
14
|
+
import { detectAvailableAgents, } from "./agent-detection.js";
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {typeof ASK_AGENT_IDS[number]} AskAgentId
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {{ agent?: AskAgentId; listAgents?: boolean; dumpPrompt?: boolean; toolSurface?: SmithersToolSurface; noMcp?: boolean; printBootstrap?: boolean; }} AskOptions
|
|
20
|
+
*/
|
|
21
|
+
/** @typedef {import("@smithers-orchestrator/agents/agent-contract").SmithersToolSurface} SmithersToolSurface */
|
|
22
|
+
|
|
23
|
+
const ASK_AGENT_IDS = ["claude", "codex", "kimi", "gemini", "pi"];
|
|
24
|
+
const DEFAULT_SERVER_NAME = "smithers";
|
|
25
|
+
/**
|
|
26
|
+
* @param {AgentAvailability["id"]} value
|
|
27
|
+
* @returns {value is AskAgentId}
|
|
28
|
+
*/
|
|
29
|
+
function isAskAgentId(value) {
|
|
30
|
+
return ASK_AGENT_IDS.includes(value);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* @param {AgentAvailability} availability
|
|
34
|
+
* @returns {availability is AskSupportedAvailability}
|
|
35
|
+
*/
|
|
36
|
+
function isSupportedAvailability(availability) {
|
|
37
|
+
return isAskAgentId(availability.id);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* @param {AskAgentId} agentId
|
|
41
|
+
* @returns {AskBootstrapMode}
|
|
42
|
+
*/
|
|
43
|
+
function resolveBootstrapMode(agentId, noMcp = false) {
|
|
44
|
+
if (noMcp) {
|
|
45
|
+
return "prompt-only";
|
|
46
|
+
}
|
|
47
|
+
switch (agentId) {
|
|
48
|
+
case "claude":
|
|
49
|
+
case "kimi":
|
|
50
|
+
return "mcp-config-file";
|
|
51
|
+
case "codex":
|
|
52
|
+
return "mcp-config-inline";
|
|
53
|
+
case "gemini":
|
|
54
|
+
return "mcp-allow-list";
|
|
55
|
+
case "pi":
|
|
56
|
+
return "prompt-only";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* @param {AskBootstrapMode} mode
|
|
61
|
+
*/
|
|
62
|
+
function bootstrapRank(mode) {
|
|
63
|
+
switch (mode) {
|
|
64
|
+
case "mcp-config-file":
|
|
65
|
+
case "mcp-config-inline":
|
|
66
|
+
return 3;
|
|
67
|
+
case "mcp-allow-list":
|
|
68
|
+
return 2;
|
|
69
|
+
case "prompt-only":
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* @param {SmithersToolSurface} [toolSurface]
|
|
75
|
+
* @returns {{ command: string; args: string[] }}
|
|
76
|
+
*/
|
|
77
|
+
function buildSmithersMcpLaunchSpec(toolSurface = "semantic") {
|
|
78
|
+
return {
|
|
79
|
+
command: process.execPath,
|
|
80
|
+
args: [
|
|
81
|
+
"run",
|
|
82
|
+
resolve(dirname(fileURLToPath(import.meta.url)), "index.js"),
|
|
83
|
+
"--mcp",
|
|
84
|
+
"--surface",
|
|
85
|
+
toolSurface,
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* @param {SmithersToolSurface} toolSurface
|
|
91
|
+
*/
|
|
92
|
+
function buildJsonMcpConfig(toolSurface, serverName = DEFAULT_SERVER_NAME) {
|
|
93
|
+
const launchSpec = buildSmithersMcpLaunchSpec(toolSurface);
|
|
94
|
+
return {
|
|
95
|
+
mcpServers: {
|
|
96
|
+
[serverName]: {
|
|
97
|
+
command: launchSpec.command,
|
|
98
|
+
args: launchSpec.args,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @param {SmithersToolSurface} [toolSurface]
|
|
105
|
+
*/
|
|
106
|
+
function buildSmithersMcpConfigFile(toolSurface = "semantic", serverName = DEFAULT_SERVER_NAME) {
|
|
107
|
+
const dir = mkdtempSync(join(tmpdir(), "smithers-ask-"));
|
|
108
|
+
const configPath = join(dir, "mcp.json");
|
|
109
|
+
const contents = buildJsonMcpConfig(toolSurface, serverName);
|
|
110
|
+
writeFileSync(configPath, JSON.stringify(contents, null, 2));
|
|
111
|
+
return {
|
|
112
|
+
dir,
|
|
113
|
+
path: configPath,
|
|
114
|
+
contents,
|
|
115
|
+
cleanup() {
|
|
116
|
+
rmSync(dir, { recursive: true, force: true });
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* @param {SmithersToolSurface} toolSurface
|
|
122
|
+
*/
|
|
123
|
+
function buildCodexConfigOverrides(toolSurface, serverName = DEFAULT_SERVER_NAME) {
|
|
124
|
+
const launchSpec = buildSmithersMcpLaunchSpec(toolSurface);
|
|
125
|
+
return [
|
|
126
|
+
`mcp_servers.${serverName}.command=${JSON.stringify(launchSpec.command)}`,
|
|
127
|
+
`mcp_servers.${serverName}.args=${JSON.stringify(launchSpec.args)}`,
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* @param {AskSelection} selection
|
|
132
|
+
* @param {SmithersToolSurface} toolSurface
|
|
133
|
+
* @returns {AskBootstrap}
|
|
134
|
+
*/
|
|
135
|
+
function buildBootstrap(selection, toolSurface) {
|
|
136
|
+
switch (selection.bootstrapMode) {
|
|
137
|
+
case "mcp-config-file":
|
|
138
|
+
return {
|
|
139
|
+
mode: "mcp-config-file",
|
|
140
|
+
serverName: DEFAULT_SERVER_NAME,
|
|
141
|
+
toolSurface,
|
|
142
|
+
config: buildJsonMcpConfig(toolSurface),
|
|
143
|
+
};
|
|
144
|
+
case "mcp-config-inline":
|
|
145
|
+
return {
|
|
146
|
+
mode: "mcp-config-inline",
|
|
147
|
+
serverName: DEFAULT_SERVER_NAME,
|
|
148
|
+
toolSurface,
|
|
149
|
+
configOverrides: buildCodexConfigOverrides(toolSurface),
|
|
150
|
+
};
|
|
151
|
+
case "mcp-allow-list":
|
|
152
|
+
return {
|
|
153
|
+
mode: "mcp-allow-list",
|
|
154
|
+
serverName: DEFAULT_SERVER_NAME,
|
|
155
|
+
toolSurface,
|
|
156
|
+
allowedMcpServerNames: [DEFAULT_SERVER_NAME],
|
|
157
|
+
note: "Gemini can only allow-list preconfigured MCP servers. Configure the local Smithers server under the same name before relying on MCP.",
|
|
158
|
+
};
|
|
159
|
+
case "prompt-only":
|
|
160
|
+
return {
|
|
161
|
+
mode: "prompt-only",
|
|
162
|
+
serverName: DEFAULT_SERVER_NAME,
|
|
163
|
+
toolSurface,
|
|
164
|
+
note: selection.availability.id === "pi"
|
|
165
|
+
? "PI falls back to prompt-only bootstrap for smithers ask."
|
|
166
|
+
: "MCP bootstrap is disabled for this run.",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* @param {AskSupportedAvailability} left
|
|
172
|
+
* @param {AskSupportedAvailability} right
|
|
173
|
+
*/
|
|
174
|
+
function compareAgents(left, right, noMcp = false) {
|
|
175
|
+
const leftBootstrap = resolveBootstrapMode(left.id, noMcp);
|
|
176
|
+
const rightBootstrap = resolveBootstrapMode(right.id, noMcp);
|
|
177
|
+
const bootstrapDelta = bootstrapRank(rightBootstrap) - bootstrapRank(leftBootstrap);
|
|
178
|
+
if (bootstrapDelta !== 0) {
|
|
179
|
+
return bootstrapDelta;
|
|
180
|
+
}
|
|
181
|
+
if (right.score !== left.score) {
|
|
182
|
+
return right.score - left.score;
|
|
183
|
+
}
|
|
184
|
+
return ASK_AGENT_IDS.indexOf(left.id) - ASK_AGENT_IDS.indexOf(right.id);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* @param {AgentAvailability} agent
|
|
188
|
+
*/
|
|
189
|
+
function formatAgentChecks(agent) {
|
|
190
|
+
return agent.checks.join(", ");
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* @param {AgentAvailability[]} agents
|
|
194
|
+
*/
|
|
195
|
+
function noUsableAgentError(agents) {
|
|
196
|
+
return new SmithersError("NO_USABLE_AGENTS", `No usable agents detected. Checked: ${agents
|
|
197
|
+
.map((agent) => `${agent.id} => ${formatAgentChecks(agent)}`)
|
|
198
|
+
.join(" | ")}`);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* @param {AgentAvailability[]} agents
|
|
202
|
+
* @param {AskOptions} options
|
|
203
|
+
* @returns {AskSelection}
|
|
204
|
+
*/
|
|
205
|
+
function selectAgent(agents, options) {
|
|
206
|
+
const supported = agents.filter(isSupportedAvailability);
|
|
207
|
+
if (options.agent) {
|
|
208
|
+
const explicit = supported.find((agent) => agent.id === options.agent);
|
|
209
|
+
if (!explicit) {
|
|
210
|
+
throw new SmithersError("CLI_AGENT_UNSUPPORTED", `Agent "${options.agent}" is not supported for \`smithers ask\`.`, { agentId: options.agent });
|
|
211
|
+
}
|
|
212
|
+
if (!explicit.usable) {
|
|
213
|
+
throw new SmithersError("NO_USABLE_AGENTS", `Agent "${explicit.id}" is not usable. Checked: ${formatAgentChecks(explicit)}`, { agentId: explicit.id });
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
availability: explicit,
|
|
217
|
+
bootstrapMode: resolveBootstrapMode(explicit.id, options.noMcp),
|
|
218
|
+
selectionReason: "requested via --agent",
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const usable = supported.filter((agent) => agent.usable);
|
|
222
|
+
if (usable.length === 0) {
|
|
223
|
+
throw noUsableAgentError(agents);
|
|
224
|
+
}
|
|
225
|
+
const best = [...usable].sort((left, right) => compareAgents(left, right, options.noMcp))[0];
|
|
226
|
+
if (!best) {
|
|
227
|
+
throw noUsableAgentError(agents);
|
|
228
|
+
}
|
|
229
|
+
const bootstrapMode = resolveBootstrapMode(best.id, options.noMcp);
|
|
230
|
+
return {
|
|
231
|
+
availability: best,
|
|
232
|
+
bootstrapMode,
|
|
233
|
+
selectionReason: `best available ${bootstrapMode} bootstrap`,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* @param {SmithersAgentContract} contract
|
|
238
|
+
* @param {AskBootstrap} bootstrap
|
|
239
|
+
*/
|
|
240
|
+
function buildSystemPrompt(contract, bootstrap) {
|
|
241
|
+
const lines = [
|
|
242
|
+
"You are an autonomous AI agent operating inside the Smithers repository and control plane.",
|
|
243
|
+
bootstrap.mode === "prompt-only"
|
|
244
|
+
? "MCP is disabled or unavailable for this run. Use the local Smithers repo and CLI directly when shell access is needed."
|
|
245
|
+
: "Prefer the live Smithers MCP tools over shell commands whenever they can answer the request.",
|
|
246
|
+
bootstrap.mode === "prompt-only"
|
|
247
|
+
? renderSmithersAgentPromptGuidance(contract, { available: false })
|
|
248
|
+
: contract.promptGuidance,
|
|
249
|
+
"If you need repository documentation, read local files in this checkout, starting with docs/llms-full.txt.",
|
|
250
|
+
"Use `smithers` or `bun run src/index.js --help` to inspect the current CLI surface when you need shell fallbacks.",
|
|
251
|
+
"Be concise and act directly.",
|
|
252
|
+
];
|
|
253
|
+
return lines.join("\n\n");
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* @param {AskSelection} selection
|
|
257
|
+
* @param {AskBootstrap} bootstrap
|
|
258
|
+
*/
|
|
259
|
+
function formatBootstrap(selection, bootstrap) {
|
|
260
|
+
const lines = [
|
|
261
|
+
`agent: ${selection.availability.id}`,
|
|
262
|
+
`selectionReason: ${selection.selectionReason}`,
|
|
263
|
+
`bootstrapMode: ${bootstrap.mode}`,
|
|
264
|
+
`toolSurface: ${bootstrap.toolSurface}`,
|
|
265
|
+
`serverName: ${bootstrap.serverName}`,
|
|
266
|
+
];
|
|
267
|
+
switch (bootstrap.mode) {
|
|
268
|
+
case "mcp-config-file":
|
|
269
|
+
lines.push("config:");
|
|
270
|
+
lines.push(JSON.stringify(bootstrap.config, null, 2));
|
|
271
|
+
break;
|
|
272
|
+
case "mcp-config-inline":
|
|
273
|
+
lines.push("configOverrides:");
|
|
274
|
+
lines.push(...bootstrap.configOverrides.map((entry) => `- ${entry}`));
|
|
275
|
+
break;
|
|
276
|
+
case "mcp-allow-list":
|
|
277
|
+
lines.push(`allowedMcpServerNames: ${bootstrap.allowedMcpServerNames.join(", ")}`);
|
|
278
|
+
lines.push(`note: ${bootstrap.note}`);
|
|
279
|
+
break;
|
|
280
|
+
case "prompt-only":
|
|
281
|
+
lines.push(`note: ${bootstrap.note}`);
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
return lines.join("\n");
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* @param {AgentAvailability[]} agents
|
|
288
|
+
* @param {AskOptions} options
|
|
289
|
+
* @param {AskAgentId} [selectedAgentId]
|
|
290
|
+
*/
|
|
291
|
+
function formatAgentList(agents, options, selectedAgentId) {
|
|
292
|
+
const supported = agents.filter(isSupportedAvailability);
|
|
293
|
+
return supported
|
|
294
|
+
.sort((left, right) => compareAgents(left, right, options.noMcp))
|
|
295
|
+
.map((agent) => {
|
|
296
|
+
const marker = agent.id === selectedAgentId ? "*" : " ";
|
|
297
|
+
return `${marker} ${agent.id} usable=${agent.usable ? "yes" : "no"} status=${agent.status} bootstrap=${resolveBootstrapMode(agent.id, options.noMcp)}`;
|
|
298
|
+
})
|
|
299
|
+
.join("\n");
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* @param {AskSelection} selection
|
|
303
|
+
* @param {AskBootstrap} bootstrap
|
|
304
|
+
* @param {string} systemPrompt
|
|
305
|
+
* @param {string} cwd
|
|
306
|
+
* @returns {{ agent: BaseCliAgent; cleanup: () => void }}
|
|
307
|
+
*/
|
|
308
|
+
function buildAgent(selection, bootstrap, systemPrompt, cwd) {
|
|
309
|
+
switch (selection.availability.id) {
|
|
310
|
+
case "claude": {
|
|
311
|
+
if (bootstrap.mode !== "mcp-config-file") {
|
|
312
|
+
return {
|
|
313
|
+
agent: new ClaudeCodeAgent({
|
|
314
|
+
cwd,
|
|
315
|
+
model: "claude-sonnet-4-20250514",
|
|
316
|
+
systemPrompt,
|
|
317
|
+
dangerouslySkipPermissions: true,
|
|
318
|
+
}),
|
|
319
|
+
cleanup() { },
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const mcpConfig = buildSmithersMcpConfigFile(bootstrap.toolSurface, bootstrap.serverName);
|
|
323
|
+
return {
|
|
324
|
+
agent: new ClaudeCodeAgent({
|
|
325
|
+
cwd,
|
|
326
|
+
model: "claude-sonnet-4-20250514",
|
|
327
|
+
mcpConfig: [mcpConfig.path],
|
|
328
|
+
strictMcpConfig: true,
|
|
329
|
+
systemPrompt,
|
|
330
|
+
dangerouslySkipPermissions: true,
|
|
331
|
+
}),
|
|
332
|
+
cleanup() {
|
|
333
|
+
mcpConfig.cleanup();
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
case "kimi": {
|
|
338
|
+
if (bootstrap.mode !== "mcp-config-file") {
|
|
339
|
+
return {
|
|
340
|
+
agent: new KimiAgent({
|
|
341
|
+
cwd,
|
|
342
|
+
model: "kimi-latest",
|
|
343
|
+
systemPrompt,
|
|
344
|
+
}),
|
|
345
|
+
cleanup() { },
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const mcpConfig = buildSmithersMcpConfigFile(bootstrap.toolSurface, bootstrap.serverName);
|
|
349
|
+
return {
|
|
350
|
+
agent: new KimiAgent({
|
|
351
|
+
cwd,
|
|
352
|
+
model: "kimi-latest",
|
|
353
|
+
mcpConfigFile: [mcpConfig.path],
|
|
354
|
+
systemPrompt,
|
|
355
|
+
}),
|
|
356
|
+
cleanup() {
|
|
357
|
+
mcpConfig.cleanup();
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
case "gemini":
|
|
362
|
+
return {
|
|
363
|
+
agent: new GeminiAgent({
|
|
364
|
+
cwd,
|
|
365
|
+
model: "gemini-3.1-pro-preview",
|
|
366
|
+
allowedMcpServerNames: bootstrap.mode === "mcp-allow-list"
|
|
367
|
+
? bootstrap.allowedMcpServerNames
|
|
368
|
+
: undefined,
|
|
369
|
+
systemPrompt,
|
|
370
|
+
approvalMode: "yolo",
|
|
371
|
+
}),
|
|
372
|
+
cleanup() { },
|
|
373
|
+
};
|
|
374
|
+
case "codex":
|
|
375
|
+
return {
|
|
376
|
+
agent: new CodexAgent({
|
|
377
|
+
cwd,
|
|
378
|
+
model: "gpt-5.3-codex",
|
|
379
|
+
config: bootstrap.mode === "mcp-config-inline"
|
|
380
|
+
? bootstrap.configOverrides
|
|
381
|
+
: undefined,
|
|
382
|
+
systemPrompt,
|
|
383
|
+
fullAuto: true,
|
|
384
|
+
skipGitRepoCheck: true,
|
|
385
|
+
}),
|
|
386
|
+
cleanup() { },
|
|
387
|
+
};
|
|
388
|
+
case "pi":
|
|
389
|
+
return {
|
|
390
|
+
agent: new PiAgent({
|
|
391
|
+
cwd,
|
|
392
|
+
provider: "openai",
|
|
393
|
+
model: "gpt-5.3-codex",
|
|
394
|
+
systemPrompt,
|
|
395
|
+
}),
|
|
396
|
+
cleanup() { },
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* @param {string | undefined} question
|
|
402
|
+
* @param {string} cwd
|
|
403
|
+
* @param {AskOptions} [options]
|
|
404
|
+
* @returns {Promise<void>}
|
|
405
|
+
*/
|
|
406
|
+
export async function ask(question, cwd, options = {}) {
|
|
407
|
+
const agents = detectAvailableAgents();
|
|
408
|
+
if (options.listAgents) {
|
|
409
|
+
let selectedAgentId;
|
|
410
|
+
try {
|
|
411
|
+
selectedAgentId = selectAgent(agents, options).availability.id;
|
|
412
|
+
}
|
|
413
|
+
catch { }
|
|
414
|
+
process.stdout.write(`${formatAgentList(agents, options, selectedAgentId)}\n`);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const selection = selectAgent(agents, options);
|
|
418
|
+
const toolSurface = options.toolSurface ?? "semantic";
|
|
419
|
+
const launchSpec = buildSmithersMcpLaunchSpec(toolSurface);
|
|
420
|
+
const transport = new StdioClientTransport({
|
|
421
|
+
command: launchSpec.command,
|
|
422
|
+
args: launchSpec.args,
|
|
423
|
+
cwd,
|
|
424
|
+
stderr: "pipe",
|
|
425
|
+
});
|
|
426
|
+
const client = new Client({
|
|
427
|
+
name: "smithers-ask-contract-probe",
|
|
428
|
+
version: "1.0.0",
|
|
429
|
+
});
|
|
430
|
+
let tools;
|
|
431
|
+
try {
|
|
432
|
+
await client.connect(transport);
|
|
433
|
+
const listed = await client.listTools();
|
|
434
|
+
tools = listed.tools.map((tool) => ({
|
|
435
|
+
name: tool.name,
|
|
436
|
+
description: tool.description,
|
|
437
|
+
}));
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
throw new SmithersError("ASK_BOOTSTRAP_FAILED", `Failed to probe the live Smithers MCP tools: ${error?.message ?? String(error)}`, {
|
|
441
|
+
cwd,
|
|
442
|
+
toolSurface,
|
|
443
|
+
command: launchSpec.command,
|
|
444
|
+
args: launchSpec.args,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
finally {
|
|
448
|
+
try {
|
|
449
|
+
await client.close();
|
|
450
|
+
}
|
|
451
|
+
catch { }
|
|
452
|
+
try {
|
|
453
|
+
await transport.close();
|
|
454
|
+
}
|
|
455
|
+
catch { }
|
|
456
|
+
}
|
|
457
|
+
const contract = createSmithersAgentContract({
|
|
458
|
+
toolSurface,
|
|
459
|
+
serverName: DEFAULT_SERVER_NAME,
|
|
460
|
+
tools,
|
|
461
|
+
});
|
|
462
|
+
const bootstrap = buildBootstrap(selection, toolSurface);
|
|
463
|
+
const systemPrompt = buildSystemPrompt(contract, bootstrap);
|
|
464
|
+
if (options.dumpPrompt || options.printBootstrap) {
|
|
465
|
+
const sections = [];
|
|
466
|
+
if (options.printBootstrap) {
|
|
467
|
+
sections.push("[bootstrap]");
|
|
468
|
+
sections.push(formatBootstrap(selection, bootstrap));
|
|
469
|
+
}
|
|
470
|
+
if (options.dumpPrompt) {
|
|
471
|
+
sections.push("[system-prompt]");
|
|
472
|
+
sections.push(systemPrompt);
|
|
473
|
+
}
|
|
474
|
+
process.stdout.write(`${sections.join("\n\n")}\n`);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (!question?.trim()) {
|
|
478
|
+
throw new SmithersError("INVALID_ARGUMENT", "A question is required unless you use --list-agents, --dump-prompt, or --print-bootstrap.");
|
|
479
|
+
}
|
|
480
|
+
const { agent, cleanup } = buildAgent(selection, bootstrap, systemPrompt, cwd);
|
|
481
|
+
try {
|
|
482
|
+
await agent.generate({
|
|
483
|
+
prompt: question,
|
|
484
|
+
onStdout: (chunk) => process.stdout.write(chunk),
|
|
485
|
+
});
|
|
486
|
+
process.stdout.write("\n");
|
|
487
|
+
}
|
|
488
|
+
finally {
|
|
489
|
+
cleanup();
|
|
490
|
+
}
|
|
491
|
+
}
|