umbrella-context 0.1.39 → 0.1.41
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 +80 -0
- package/dist/adapters/byterover-runtime-bridge.js +47 -35
- package/dist/adapters/umbrella-provider-runtime.d.ts +9 -2
- package/dist/adapters/umbrella-provider-runtime.js +86 -7
- package/dist/adaptive/runtime.d.ts +27 -0
- package/dist/adaptive/runtime.js +154 -0
- package/dist/commands/adaptive.d.ts +7 -0
- package/dist/commands/adaptive.js +92 -0
- package/dist/commands/connectors.js +96 -1
- package/dist/commands/curate.js +2 -0
- package/dist/commands/logout.js +1 -0
- package/dist/commands/model.js +30 -5
- package/dist/commands/providers.js +9 -3
- package/dist/commands/search.d.ts +1 -0
- package/dist/commands/search.js +51 -12
- package/dist/commands/setup.js +1 -1
- package/dist/commands/source.d.ts +11 -0
- package/dist/commands/source.js +152 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.js +18 -3
- package/dist/commands/swarm.d.ts +16 -0
- package/dist/commands/swarm.js +211 -0
- package/dist/commands/tui.js +223 -52
- package/dist/commands/worktree.d.ts +12 -0
- package/dist/commands/worktree.js +141 -0
- package/dist/index.js +8 -0
- package/dist/repo-state.d.ts +71 -0
- package/dist/repo-state.js +260 -7
- package/dist/swarm/runtime.d.ts +502 -0
- package/dist/swarm/runtime.js +957 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { promises as fs } from "fs";
|
|
1
|
+
import { existsSync, promises as fs } from "fs";
|
|
2
|
+
import os from "os";
|
|
2
3
|
import path from "path";
|
|
3
4
|
import { randomUUID } from "crypto";
|
|
4
5
|
import chalk from "chalk";
|
|
@@ -11,6 +12,12 @@ const CONNECTOR_TEMPLATES = [
|
|
|
11
12
|
type: "mcp",
|
|
12
13
|
description: "Connect IDE agents to Umbrella Context through the local MCP server.",
|
|
13
14
|
},
|
|
15
|
+
{
|
|
16
|
+
key: "claude-desktop-mcp",
|
|
17
|
+
name: "Claude Desktop Connector",
|
|
18
|
+
type: "mcp",
|
|
19
|
+
description: "Connect Claude Desktop to Umbrella Context through its MCP config on macOS or Windows.",
|
|
20
|
+
},
|
|
14
21
|
{
|
|
15
22
|
key: "github-review-rule",
|
|
16
23
|
name: "GitHub Review Rule",
|
|
@@ -59,6 +66,20 @@ function buildConnectorAsset(entry) {
|
|
|
59
66
|
],
|
|
60
67
|
};
|
|
61
68
|
}
|
|
69
|
+
function getClaudeDesktopConfigPath() {
|
|
70
|
+
if (process.platform === "win32") {
|
|
71
|
+
const localAppData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local");
|
|
72
|
+
const msixDir = path.join(localAppData, "Packages", "Claude_pzs8sxrjxfjjc", "LocalCache", "Roaming", "Claude");
|
|
73
|
+
const baseDir = existsSync(msixDir)
|
|
74
|
+
? msixDir
|
|
75
|
+
: path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "Claude");
|
|
76
|
+
return path.join(baseDir, "claude_desktop_config.json");
|
|
77
|
+
}
|
|
78
|
+
if (process.platform === "darwin") {
|
|
79
|
+
return path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
80
|
+
}
|
|
81
|
+
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "Claude", "claude_desktop_config.json");
|
|
82
|
+
}
|
|
62
83
|
async function runConnectorCheck(entry, cwd = process.cwd()) {
|
|
63
84
|
const { repoRoot } = await getRepoContext(cwd);
|
|
64
85
|
if (entry.source === "codex-mcp") {
|
|
@@ -80,6 +101,25 @@ async function runConnectorCheck(entry, cwd = process.cwd()) {
|
|
|
80
101
|
};
|
|
81
102
|
}
|
|
82
103
|
}
|
|
104
|
+
if (entry.source === "claude-desktop-mcp") {
|
|
105
|
+
const filePath = getClaudeDesktopConfigPath();
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
108
|
+
const configured = Boolean(parsed?.mcpServers?.["umbrella-context"]);
|
|
109
|
+
return {
|
|
110
|
+
status: configured ? "success" : "failure",
|
|
111
|
+
summary: configured
|
|
112
|
+
? `${entry.name} verified that Claude Desktop is pointing at umbrella-context mcp.`
|
|
113
|
+
: `${entry.name} could not find an umbrella-context MCP entry in Claude Desktop's config.`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return {
|
|
118
|
+
status: "failure",
|
|
119
|
+
summary: `${entry.name} could not read Claude Desktop's config file yet.`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
83
123
|
if (entry.source === "github-review-rule") {
|
|
84
124
|
return {
|
|
85
125
|
status: "success",
|
|
@@ -214,6 +254,29 @@ function buildConnectorFiles(entry) {
|
|
|
214
254
|
},
|
|
215
255
|
];
|
|
216
256
|
}
|
|
257
|
+
if (entry.key === "claude-desktop-mcp") {
|
|
258
|
+
return [
|
|
259
|
+
{
|
|
260
|
+
name: "claude-desktop-connector.md",
|
|
261
|
+
content: [
|
|
262
|
+
"# Claude Desktop Connector",
|
|
263
|
+
"",
|
|
264
|
+
"This connector adds `umbrella-context mcp` to Claude Desktop's MCP config.",
|
|
265
|
+
"",
|
|
266
|
+
"## What it should change",
|
|
267
|
+
"",
|
|
268
|
+
"- Write or update Claude Desktop's `claude_desktop_config.json` file",
|
|
269
|
+
"- Add an `umbrella-context` MCP server entry",
|
|
270
|
+
"",
|
|
271
|
+
"## Important restart step",
|
|
272
|
+
"",
|
|
273
|
+
"Fully quit Claude Desktop from the tray or menu bar, then reopen it.",
|
|
274
|
+
"A normal window close is not enough for MCP changes to reload.",
|
|
275
|
+
"",
|
|
276
|
+
].join("\n"),
|
|
277
|
+
},
|
|
278
|
+
];
|
|
279
|
+
}
|
|
217
280
|
if (entry.key === "github-review-rule") {
|
|
218
281
|
return [
|
|
219
282
|
{
|
|
@@ -294,6 +357,29 @@ async function installUmbrellaMcpIntoRepo(cwd = process.cwd()) {
|
|
|
294
357
|
await fs.writeFile(filePath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
295
358
|
return filePath;
|
|
296
359
|
}
|
|
360
|
+
async function installUmbrellaMcpIntoClaudeDesktop() {
|
|
361
|
+
const filePath = getClaudeDesktopConfigPath();
|
|
362
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
363
|
+
let current = { mcpServers: {} };
|
|
364
|
+
try {
|
|
365
|
+
current = JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
current = { mcpServers: {} };
|
|
369
|
+
}
|
|
370
|
+
const next = {
|
|
371
|
+
...current,
|
|
372
|
+
mcpServers: {
|
|
373
|
+
...(current.mcpServers ?? {}),
|
|
374
|
+
"umbrella-context": {
|
|
375
|
+
command: "umbrella-context",
|
|
376
|
+
args: ["mcp"],
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
await fs.writeFile(filePath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
381
|
+
return filePath;
|
|
382
|
+
}
|
|
297
383
|
async function getConnectorOutputSummary(source, cwd = process.cwd()) {
|
|
298
384
|
const { repoRoot } = await getRepoContext(cwd);
|
|
299
385
|
const connectorDir = path.join(repoRoot, ".um", "connectors", source);
|
|
@@ -466,6 +552,9 @@ export async function connectorsCommandAction(action, target) {
|
|
|
466
552
|
if (selected.key === "codex-mcp") {
|
|
467
553
|
repoConfigPath = await installUmbrellaMcpIntoRepo();
|
|
468
554
|
}
|
|
555
|
+
else if (selected.key === "claude-desktop-mcp") {
|
|
556
|
+
repoConfigPath = await installUmbrellaMcpIntoClaudeDesktop();
|
|
557
|
+
}
|
|
469
558
|
await recordSessionEvent({
|
|
470
559
|
kind: "connector",
|
|
471
560
|
title: `${selected.name} was already installed`,
|
|
@@ -510,6 +599,9 @@ export async function connectorsCommandAction(action, target) {
|
|
|
510
599
|
if (selected.key === "codex-mcp") {
|
|
511
600
|
repoConfigPath = await installUmbrellaMcpIntoRepo();
|
|
512
601
|
}
|
|
602
|
+
else if (selected.key === "claude-desktop-mcp") {
|
|
603
|
+
repoConfigPath = await installUmbrellaMcpIntoClaudeDesktop();
|
|
604
|
+
}
|
|
513
605
|
await recordSessionEvent({
|
|
514
606
|
kind: "connector",
|
|
515
607
|
title: `Installed connector ${selected.name}`,
|
|
@@ -526,6 +618,9 @@ export async function connectorsCommandAction(action, target) {
|
|
|
526
618
|
if (repoConfigPath) {
|
|
527
619
|
console.log(chalk.gray(` Updated repo MCP config at ${repoConfigPath}`));
|
|
528
620
|
}
|
|
621
|
+
if (selected.key === "claude-desktop-mcp") {
|
|
622
|
+
console.log(chalk.yellow(" Fully quit Claude Desktop from the tray or menu bar, then reopen it so the new MCP config is picked up."));
|
|
623
|
+
}
|
|
529
624
|
return {
|
|
530
625
|
action: "install",
|
|
531
626
|
changed: true,
|
package/dist/commands/curate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { configManager } from "../config.js";
|
|
3
|
+
import { refreshAdaptiveKnowledge } from "../adaptive/runtime.js";
|
|
3
4
|
import { addPendingMemory, completeTask, createTask, ensureRepoContext, getPendingMemories, getRepoContext, recordSessionCuration, recordSessionEvent, setSessionPanel, } from "../repo-state.js";
|
|
4
5
|
const RELATIVE_TIME_PATTERN = /^(\d+)(m|h|d|w)$/;
|
|
5
6
|
function printCurateResult(format, payload) {
|
|
@@ -105,6 +106,7 @@ export async function curateCommandAction(content, opts = {}) {
|
|
|
105
106
|
focus: entry.id,
|
|
106
107
|
status: "success",
|
|
107
108
|
});
|
|
109
|
+
await refreshAdaptiveKnowledge();
|
|
108
110
|
await completeTask(task.id, "completed", `Saved local draft ${entry.id} in .um.`);
|
|
109
111
|
printCurateResult(format, {
|
|
110
112
|
ok: true,
|
package/dist/commands/logout.js
CHANGED
|
@@ -20,6 +20,7 @@ export async function logoutCommandAction(force = false) {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
configManager.clearUmbrellaSession();
|
|
23
|
+
configManager.clearAuthSnapshot();
|
|
23
24
|
console.log(chalk.green("\n This device is now signed out of Umbrella Context."));
|
|
24
25
|
console.log(chalk.gray(' Your local providers and hub registries were kept on this device.'));
|
|
25
26
|
console.log(chalk.gray(' Run "umbrella-context setup" to connect again.'));
|
package/dist/commands/model.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import prompts from "prompts";
|
|
3
3
|
import { configManager } from "../config.js";
|
|
4
|
+
import { getProviderChoice } from "../adapters/umbrella-provider-runtime.js";
|
|
4
5
|
import { getSessionState, recordSessionEvent, recordSessionModel, setSessionPanel } from "../repo-state.js";
|
|
5
6
|
const MODELS_BY_PROVIDER = {
|
|
6
|
-
anthropic: ["claude-
|
|
7
|
-
openai: ["gpt-5.4", "gpt-5.4-mini", "gpt-4.1"],
|
|
7
|
+
anthropic: ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5"],
|
|
8
|
+
openai: ["gpt-5.4", "gpt-5.4-mini", "gpt-5", "gpt-4.1", "o4-mini"],
|
|
8
9
|
google: ["gemini-2.5-pro", "gemini-2.5-flash"],
|
|
9
|
-
openrouter: ["openai/gpt-
|
|
10
|
+
openrouter: ["openai/gpt-5", "anthropic/claude-sonnet-4-6", "google/gemini-2.5-pro", "custom-model"],
|
|
11
|
+
minimax: ["MiniMax-M2.7", "MiniMax-M2.5", "custom-model"],
|
|
12
|
+
groq: ["custom-model"],
|
|
13
|
+
together: ["custom-model"],
|
|
14
|
+
deepseek: ["deepseek-chat", "deepseek-reasoner", "custom-model"],
|
|
15
|
+
fireworks: ["custom-model"],
|
|
10
16
|
"openai-compatible": ["custom-model"],
|
|
11
17
|
};
|
|
12
18
|
function getActiveProvider() {
|
|
@@ -18,7 +24,11 @@ export function getModelsForActiveProvider() {
|
|
|
18
24
|
const provider = getActiveProvider();
|
|
19
25
|
if (!provider)
|
|
20
26
|
return [];
|
|
21
|
-
|
|
27
|
+
const builtInModels = MODELS_BY_PROVIDER[provider.kind] ?? getProviderChoice(provider.kind)?.exampleModels ?? ["custom-model"];
|
|
28
|
+
if (provider.name && configManager.config?.activeModel && !builtInModels.includes(configManager.config.activeModel)) {
|
|
29
|
+
return [...builtInModels, configManager.config.activeModel];
|
|
30
|
+
}
|
|
31
|
+
return builtInModels;
|
|
22
32
|
}
|
|
23
33
|
function resolveModel(models, target) {
|
|
24
34
|
if (!target)
|
|
@@ -184,7 +194,10 @@ export async function modelCommandAction(action, target) {
|
|
|
184
194
|
type: "select",
|
|
185
195
|
name: "value",
|
|
186
196
|
message: `Choose the active model for ${provider.name}`,
|
|
187
|
-
choices:
|
|
197
|
+
choices: [
|
|
198
|
+
...models.map((model) => ({ title: model, value: model })),
|
|
199
|
+
{ title: "Custom model ID…", value: "__custom__" },
|
|
200
|
+
],
|
|
188
201
|
});
|
|
189
202
|
if (!answer.value) {
|
|
190
203
|
console.log(chalk.yellow("\n No model selected."));
|
|
@@ -192,6 +205,18 @@ export async function modelCommandAction(action, target) {
|
|
|
192
205
|
}
|
|
193
206
|
selectedModel = answer.value;
|
|
194
207
|
}
|
|
208
|
+
if (selectedModel === "__custom__") {
|
|
209
|
+
const customAnswer = await prompts({
|
|
210
|
+
type: "text",
|
|
211
|
+
name: "value",
|
|
212
|
+
message: `Type the exact model ID for ${provider.name}`,
|
|
213
|
+
});
|
|
214
|
+
if (!customAnswer.value?.trim()) {
|
|
215
|
+
console.log(chalk.yellow("\n No model selected."));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
selectedModel = customAnswer.value.trim();
|
|
219
|
+
}
|
|
195
220
|
if (!selectedModel) {
|
|
196
221
|
console.log(chalk.yellow("\n No model selected."));
|
|
197
222
|
return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import prompts from "prompts";
|
|
3
3
|
import { configManager } from "../config.js";
|
|
4
|
-
import { connectSavedProviderFromDraft, createEmptyProviderConnectDraft, getActiveSavedProvider, getProviderRuntimeReadiness, inspectRecentSavedProvider, inspectSavedProvider, resolveSavedProvider, switchActiveSavedProvider, testActiveSavedProviderRuntime, disconnectSavedProvider, UMBRELLA_PROVIDER_CHOICES, } from "../adapters/umbrella-provider-runtime.js";
|
|
4
|
+
import { connectSavedProviderFromDraft, createEmptyProviderConnectDraft, getActiveSavedProvider, getProviderChoice, getProviderRuntimeReadiness, inspectRecentSavedProvider, inspectSavedProvider, resolveSavedProvider, switchActiveSavedProvider, testActiveSavedProviderRuntime, disconnectSavedProvider, UMBRELLA_PROVIDER_CHOICES, } from "../adapters/umbrella-provider-runtime.js";
|
|
5
5
|
import { recordSessionEvent, setSessionPanel } from "../repo-state.js";
|
|
6
6
|
import { modelCommandAction } from "./model.js";
|
|
7
7
|
function printProviders(providers, activeProvider) {
|
|
@@ -264,12 +264,17 @@ export async function providersCommandAction(action, target) {
|
|
|
264
264
|
type: "select",
|
|
265
265
|
name: "kind",
|
|
266
266
|
message: "Choose a provider",
|
|
267
|
-
choices: UMBRELLA_PROVIDER_CHOICES.map((entry) => ({
|
|
267
|
+
choices: UMBRELLA_PROVIDER_CHOICES.map((entry) => ({
|
|
268
|
+
title: entry.name,
|
|
269
|
+
description: entry.description,
|
|
270
|
+
value: entry.kind,
|
|
271
|
+
})),
|
|
268
272
|
},
|
|
269
273
|
{
|
|
270
274
|
type: "text",
|
|
271
275
|
name: "label",
|
|
272
276
|
message: "What label should this provider use on this device?",
|
|
277
|
+
initial: (prev) => getProviderChoice(prev)?.suggestedLabel ?? "",
|
|
273
278
|
},
|
|
274
279
|
{
|
|
275
280
|
type: "password",
|
|
@@ -277,9 +282,10 @@ export async function providersCommandAction(action, target) {
|
|
|
277
282
|
message: "Paste the provider API key (stored only on this device)",
|
|
278
283
|
},
|
|
279
284
|
{
|
|
280
|
-
type: (
|
|
285
|
+
type: (_prev, values) => getProviderChoice(values.kind)?.requiresBaseUrlInput ? "text" : null,
|
|
281
286
|
name: "baseUrl",
|
|
282
287
|
message: "Base URL for the compatible API",
|
|
288
|
+
initial: (_prev, values) => getProviderChoice(values.kind)?.baseUrl ?? "",
|
|
283
289
|
},
|
|
284
290
|
]);
|
|
285
291
|
if (!answer.kind || !answer.label || !answer.apiKey) {
|
package/dist/commands/search.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { configManager } from "../config.js";
|
|
3
3
|
import { getProviderReadinessSummary } from "./providers.js";
|
|
4
|
+
import { getAdaptiveKnowledgeBoost, recordAdaptiveConsultations, refreshAdaptiveKnowledge, } from "../adaptive/runtime.js";
|
|
4
5
|
import { addTaskReasoningContent, addTaskToolCall, appendTaskStreamingContent, buildLocalQueryFingerprint, completeTask, createTask, getQueryCacheEntry, getPendingMemories, getPulledMemories, markQueryCacheHit, patchTaskLifecycle, removeTask, recordSessionEvent, recordSessionQuery, setSessionPanel, storeQueryCacheEntry, summarizeLocalMemoryMatches, } from "../repo-state.js";
|
|
5
6
|
const DIRECT_RESPONSE_SCORE_THRESHOLD = 0.85;
|
|
6
7
|
const DIRECT_RESPONSE_HIGH_CONFIDENCE_THRESHOLD = 0.93;
|
|
7
8
|
const DIRECT_RESPONSE_MIN_GAP = 0.08;
|
|
8
9
|
const SYNTHESIS_CONTEXT_LIMIT = 6;
|
|
10
|
+
const DEFAULT_TIMEOUT_SECONDS = 300;
|
|
11
|
+
const MIN_TIMEOUT_SECONDS = 1;
|
|
12
|
+
const MAX_TIMEOUT_SECONDS = 3600;
|
|
9
13
|
function printJson(payload) {
|
|
10
14
|
console.log(JSON.stringify(payload, null, 2));
|
|
11
15
|
}
|
|
@@ -130,9 +134,12 @@ function scoreLocalMatch(entry, query) {
|
|
|
130
134
|
}
|
|
131
135
|
return score;
|
|
132
136
|
}
|
|
133
|
-
function rankLocalMatches(entries, query) {
|
|
134
|
-
|
|
135
|
-
|
|
137
|
+
async function rankLocalMatches(entries, query) {
|
|
138
|
+
const scoredEntries = await Promise.all([...entries].map(async (entry) => ({
|
|
139
|
+
entry,
|
|
140
|
+
score: scoreLocalMatch(entry, query) + (await getAdaptiveKnowledgeBoost(entry.id)),
|
|
141
|
+
})));
|
|
142
|
+
return scoredEntries
|
|
136
143
|
.sort((a, b) => b.score - a.score || b.entry.createdAt.localeCompare(a.entry.createdAt))
|
|
137
144
|
.map(({ entry, score }) => ({ entry, score }));
|
|
138
145
|
}
|
|
@@ -356,6 +363,16 @@ function extractGoogleText(data) {
|
|
|
356
363
|
.join("\n")
|
|
357
364
|
.trim();
|
|
358
365
|
}
|
|
366
|
+
function buildTimeoutController(timeoutMs) {
|
|
367
|
+
const controller = new AbortController();
|
|
368
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
369
|
+
return {
|
|
370
|
+
controller,
|
|
371
|
+
dispose() {
|
|
372
|
+
clearTimeout(timeout);
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
359
376
|
function inferAnswerGaps(details, query, fallback) {
|
|
360
377
|
const normalized = details.toLowerCase();
|
|
361
378
|
if (normalized.includes("not enough") ||
|
|
@@ -396,7 +413,7 @@ function parseStructuredProviderAnswer(rawText) {
|
|
|
396
413
|
return null;
|
|
397
414
|
}
|
|
398
415
|
}
|
|
399
|
-
async function synthesizeAnswerFromProvider(query, localMatches, serverMatches) {
|
|
416
|
+
async function synthesizeAnswerFromProvider(query, localMatches, serverMatches, timeoutMs) {
|
|
400
417
|
const config = configManager.config;
|
|
401
418
|
if (!config?.activeProvider || !config.activeModel)
|
|
402
419
|
return null;
|
|
@@ -420,6 +437,7 @@ async function synthesizeAnswerFromProvider(query, localMatches, serverMatches)
|
|
|
420
437
|
].join("\n");
|
|
421
438
|
let responseText = null;
|
|
422
439
|
if (provider.kind === "anthropic") {
|
|
440
|
+
const timeout = buildTimeoutController(timeoutMs);
|
|
423
441
|
const res = await fetch(provider.baseUrl?.replace(/\/+$/, "") || "https://api.anthropic.com/v1/messages", {
|
|
424
442
|
method: "POST",
|
|
425
443
|
headers: {
|
|
@@ -433,13 +451,16 @@ async function synthesizeAnswerFromProvider(query, localMatches, serverMatches)
|
|
|
433
451
|
system: "You answer engineering questions using only the provided saved context. Be concise and do not invent facts.",
|
|
434
452
|
messages: [{ role: "user", content: userPrompt }],
|
|
435
453
|
}),
|
|
454
|
+
signal: timeout.controller.signal,
|
|
436
455
|
});
|
|
456
|
+
timeout.dispose();
|
|
437
457
|
if (!res.ok)
|
|
438
458
|
return null;
|
|
439
459
|
responseText = extractAnthropicText(await res.json());
|
|
440
460
|
}
|
|
441
461
|
else if (provider.kind === "google") {
|
|
442
462
|
const model = encodeURIComponent(config.activeModel);
|
|
463
|
+
const timeout = buildTimeoutController(timeoutMs);
|
|
443
464
|
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${encodeURIComponent(provider.apiKey)}`, {
|
|
444
465
|
method: "POST",
|
|
445
466
|
headers: { "content-type": "application/json" },
|
|
@@ -457,7 +478,9 @@ async function synthesizeAnswerFromProvider(query, localMatches, serverMatches)
|
|
|
457
478
|
maxOutputTokens: 220,
|
|
458
479
|
},
|
|
459
480
|
}),
|
|
481
|
+
signal: timeout.controller.signal,
|
|
460
482
|
});
|
|
483
|
+
timeout.dispose();
|
|
461
484
|
if (!res.ok)
|
|
462
485
|
return null;
|
|
463
486
|
responseText = extractGoogleText(await res.json());
|
|
@@ -472,6 +495,7 @@ async function synthesizeAnswerFromProvider(query, localMatches, serverMatches)
|
|
|
472
495
|
headers["HTTP-Referer"] = "https://github.com/buildingwithai/Umbrella";
|
|
473
496
|
headers["X-Title"] = "Umbrella Context CLI";
|
|
474
497
|
}
|
|
498
|
+
const timeout = buildTimeoutController(timeoutMs);
|
|
475
499
|
const res = await fetch(endpoint, {
|
|
476
500
|
method: "POST",
|
|
477
501
|
headers,
|
|
@@ -490,7 +514,9 @@ async function synthesizeAnswerFromProvider(query, localMatches, serverMatches)
|
|
|
490
514
|
},
|
|
491
515
|
],
|
|
492
516
|
}),
|
|
517
|
+
signal: timeout.controller.signal,
|
|
493
518
|
});
|
|
519
|
+
timeout.dispose();
|
|
494
520
|
if (!res.ok)
|
|
495
521
|
return null;
|
|
496
522
|
responseText = extractOpenAiStyleText(await res.json());
|
|
@@ -687,11 +713,11 @@ async function enrichCachedPayload(query, payload, providerReady) {
|
|
|
687
713
|
if (payload.total === 0) {
|
|
688
714
|
return payload;
|
|
689
715
|
}
|
|
690
|
-
const rankedLocalMatches = rankLocalMatches(payload.localMatches, query);
|
|
716
|
+
const rankedLocalMatches = await rankLocalMatches(payload.localMatches, query);
|
|
691
717
|
const directAnswer = buildDirectAnswer(query, rankedLocalMatches, payload.serverMatches);
|
|
692
718
|
const synthesizedAnswer = !directAnswer && providerReady
|
|
693
719
|
? (payload.synthesizedAnswer
|
|
694
|
-
?? await synthesizeAnswerFromProvider(query, payload.localMatches, payload.serverMatches))
|
|
720
|
+
?? await synthesizeAnswerFromProvider(query, payload.localMatches, payload.serverMatches, DEFAULT_TIMEOUT_SECONDS * 1000))
|
|
695
721
|
: null;
|
|
696
722
|
const nextProviderRequiredForSynthesis = !directAnswer && payload.total > 0;
|
|
697
723
|
const nextSuggestions = !directAnswer && nextProviderRequiredForSynthesis && !providerReady
|
|
@@ -723,6 +749,8 @@ export async function searchCommandAction(query, opts = {}) {
|
|
|
723
749
|
const format = opts.format === "json" ? "json" : "text";
|
|
724
750
|
const providerReadiness = getProviderReadinessSummary();
|
|
725
751
|
const providerReady = providerReadiness.providerReady && providerReadiness.modelReady;
|
|
752
|
+
const timeoutSeconds = Math.max(MIN_TIMEOUT_SECONDS, Math.min(MAX_TIMEOUT_SECONDS, Number(opts.timeout ?? DEFAULT_TIMEOUT_SECONDS)));
|
|
753
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
726
754
|
if (!config) {
|
|
727
755
|
if (format === "json") {
|
|
728
756
|
printJson({ ok: false, action: "query", error: "Not configured. Run: umbrella-context setup" });
|
|
@@ -765,6 +793,7 @@ export async function searchCommandAction(query, opts = {}) {
|
|
|
765
793
|
try {
|
|
766
794
|
await setSessionPanel("query", query);
|
|
767
795
|
await recordSessionQuery(query);
|
|
796
|
+
await refreshAdaptiveKnowledge();
|
|
768
797
|
const fingerprint = await buildLocalQueryFingerprint();
|
|
769
798
|
const cached = await getQueryCacheEntry(query, fingerprint);
|
|
770
799
|
if (cached) {
|
|
@@ -786,6 +815,7 @@ export async function searchCommandAction(query, opts = {}) {
|
|
|
786
815
|
focus: query,
|
|
787
816
|
status: "success",
|
|
788
817
|
});
|
|
818
|
+
await recordAdaptiveConsultations(enrichedPayload.localMatches.slice(0, 3).map((entry) => entry.id));
|
|
789
819
|
printSearchPayload(format, enrichedPayload, true);
|
|
790
820
|
return;
|
|
791
821
|
}
|
|
@@ -797,7 +827,7 @@ export async function searchCommandAction(query, opts = {}) {
|
|
|
797
827
|
panel: "query",
|
|
798
828
|
focus: query,
|
|
799
829
|
});
|
|
800
|
-
const rankedLocalMatches = rankLocalMatches(summarizeLocalMemoryMatches([...(await getPendingMemories()), ...(await getPulledMemories())], query), query);
|
|
830
|
+
const rankedLocalMatches = await rankLocalMatches(summarizeLocalMemoryMatches([...(await getPendingMemories()), ...(await getPulledMemories())], query), query);
|
|
801
831
|
const localMatches = rankedLocalMatches.map(({ entry }) => entry);
|
|
802
832
|
await addTaskReasoningContent(task.id, `Checked local repo context first and found ${localMatches.length} candidate match${localMatches.length === 1 ? "" : "es"}.`);
|
|
803
833
|
await addTaskToolCall(task.id, {
|
|
@@ -811,9 +841,12 @@ export async function searchCommandAction(query, opts = {}) {
|
|
|
811
841
|
reason: "Moved from local scan to shared search.",
|
|
812
842
|
});
|
|
813
843
|
await appendTaskStreamingContent(task.id, `local=${localMatches.length};`);
|
|
844
|
+
const timeout = buildTimeoutController(timeoutMs);
|
|
814
845
|
const res = await fetch(`${config.serverUrl}/api/memories/search?query=${encodeURIComponent(query)}`, {
|
|
815
846
|
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
847
|
+
signal: timeout.controller.signal,
|
|
816
848
|
});
|
|
849
|
+
timeout.dispose();
|
|
817
850
|
if (!res.ok) {
|
|
818
851
|
const err = await res.json();
|
|
819
852
|
await addTaskToolCall(task.id, {
|
|
@@ -879,7 +912,7 @@ export async function searchCommandAction(query, opts = {}) {
|
|
|
879
912
|
await addTaskReasoningContent(task.id, "Partial context exists, but there is no provider/model ready for a richer synthesized answer yet.");
|
|
880
913
|
}
|
|
881
914
|
else {
|
|
882
|
-
payload.synthesizedAnswer = await synthesizeAnswerFromProvider(query, localMatches, data.results);
|
|
915
|
+
payload.synthesizedAnswer = await synthesizeAnswerFromProvider(query, localMatches, data.results, timeoutMs);
|
|
883
916
|
await addTaskReasoningContent(task.id, payload.synthesizedAnswer
|
|
884
917
|
? "Partial context existed, and the connected provider synthesized one cleaner answer from the saved matches."
|
|
885
918
|
: "Partial context exists and a provider is ready, so this query can support richer synthesis in a later pass.");
|
|
@@ -903,23 +936,27 @@ export async function searchCommandAction(query, opts = {}) {
|
|
|
903
936
|
fingerprint,
|
|
904
937
|
payload,
|
|
905
938
|
});
|
|
939
|
+
await recordAdaptiveConsultations(localMatches.slice(0, 3).map((entry) => entry.id));
|
|
906
940
|
printSearchPayload(format, payload);
|
|
907
941
|
}
|
|
908
942
|
catch (err) {
|
|
943
|
+
const message = err?.name === "AbortError"
|
|
944
|
+
? `Query timed out after ${timeoutSeconds} second${timeoutSeconds === 1 ? "" : "s"}.`
|
|
945
|
+
: err.message;
|
|
909
946
|
const failedTask = await createTask({
|
|
910
947
|
kind: "query",
|
|
911
948
|
title: `Query failed: ${query}`,
|
|
912
|
-
detail:
|
|
949
|
+
detail: message,
|
|
913
950
|
status: "failed",
|
|
914
951
|
panel: "query",
|
|
915
952
|
focus: query,
|
|
916
953
|
});
|
|
917
|
-
await completeTask(failedTask.id, "failed",
|
|
954
|
+
await completeTask(failedTask.id, "failed", message);
|
|
918
955
|
if (format === "json") {
|
|
919
|
-
printJson({ ok: false, action: "query", error:
|
|
956
|
+
printJson({ ok: false, action: "query", error: message });
|
|
920
957
|
return;
|
|
921
958
|
}
|
|
922
|
-
console.log(chalk.red(`\n Error: ${
|
|
959
|
+
console.log(chalk.red(`\n Error: ${message}`));
|
|
923
960
|
}
|
|
924
961
|
}
|
|
925
962
|
export function searchCommand(cli) {
|
|
@@ -934,8 +971,10 @@ Bad:
|
|
|
934
971
|
- "authentication"
|
|
935
972
|
- "show me code"`)
|
|
936
973
|
.option("--format <format>", "Output format (text or json)")
|
|
974
|
+
.option("--timeout <seconds>", "Maximum seconds to wait before query work times out")
|
|
937
975
|
.example('search "How is authentication implemented?"')
|
|
938
976
|
.example('search "How does auth work?" --format json')
|
|
977
|
+
.example('search "How does auth work?" --timeout 900')
|
|
939
978
|
.action(async (query, opts) => {
|
|
940
979
|
await searchCommandAction(query, opts);
|
|
941
980
|
});
|
package/dist/commands/setup.js
CHANGED
|
@@ -25,7 +25,7 @@ function detectMixedSavedConnection(umbrellaUrl, serverUrl) {
|
|
|
25
25
|
const backend = new URL(serverUrl);
|
|
26
26
|
const umbrellaIsLoopback = isLocalOnlyServerUrl(umbrellaUrl);
|
|
27
27
|
const backendIsLoopback = isLocalOnlyServerUrl(serverUrl);
|
|
28
|
-
const differentHosts = umbrella.hostname !== backend.hostname
|
|
28
|
+
const differentHosts = umbrella.hostname !== backend.hostname;
|
|
29
29
|
if (!differentHosts && umbrellaIsLoopback === backendIsLoopback) {
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function sourceAddCommandAction(targetPath: string, opts?: {
|
|
2
|
+
alias?: string;
|
|
3
|
+
format?: string;
|
|
4
|
+
}): Promise<void>;
|
|
5
|
+
export declare function sourceListCommandAction(opts?: {
|
|
6
|
+
format?: string;
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
export declare function sourceRemoveCommandAction(aliasOrPath: string, opts?: {
|
|
9
|
+
format?: string;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
export declare function sourceCommand(cli: any): void;
|