@omercnet/paseo-context-mode 1.2.3-next.233.1 → 1.2.3-next.235.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/client/context-mode-surface.tsx +16 -3
- package/client/knowledge-section.tsx +41 -22
- package/client/scoped-knowledge-section.tsx +6 -2
- package/client/screen-scope.ts +2 -17
- package/package.json +1 -1
- package/server/analytics.ts +15 -3
- package/server/integration.ts +1 -1
- package/server/knowledge.ts +70 -39
- package/server/storage-discovery.ts +14 -0
- package/shared/knowledge.ts +7 -4
package/README.md
CHANGED
|
@@ -22,18 +22,18 @@ These previews were captured from an isolated Paseo test daemon with Context Mod
|
|
|
22
22
|
|
|
23
23
|
- Bundles Context Mode as a runtime dependency; a separately installed global executable remains optional.
|
|
24
24
|
- Resolves a configured absolute executable first, then the daemon's `PATH`, then the bundled runtime.
|
|
25
|
-
- Shows Context Mode version, structured diagnostics, provider activation, storage roots, lifetime savings, activity, categories, and indexed sources.
|
|
25
|
+
- Shows Context Mode version, structured diagnostics, provider activation, storage roots, lifetime savings, activity, categories, and indexed sources. Savings lists the built-in harness storage plus the storage of every provider configured in Paseo (including ones derived with `extends`), the latter under its Paseo provider id and the root its own `env` resolves to. `ctx_stats` and the Setup tab report the daemon's storage only.
|
|
26
26
|
- Reads Context Mode 1.0.169 SQLite databases in query-only mode, honors active WAL state, isolates corrupt databases, and reports partial coverage instead of mutating schemas.
|
|
27
27
|
- Injects the bundled stdio MCP server into new supported agents through Paseo's `agent.create` hook.
|
|
28
28
|
- Injects provider identity and an existing provider-specific storage root on create, resume, refresh, and import.
|
|
29
29
|
- Preserves existing MCP servers and explicit environment overrides.
|
|
30
30
|
- Prefers detected native provider integrations to avoid duplicate `ctx_*` tools.
|
|
31
31
|
- Reuses existing provider-specific databases. It does not merge session databases across providers because Context Mode intentionally isolates their session identities.
|
|
32
|
-
- Provides provider- and project-scoped knowledge search, local-path indexing, URL fetch/index, indexed-source visibility, and an Insight link.
|
|
32
|
+
- Provides provider- and project-scoped knowledge search, local-path indexing, URL fetch/index, indexed-source visibility, and an Insight link. Knowledge covers the built-in providers and every configured provider that resolves to a Context Mode store, using that provider's own `env`.
|
|
33
33
|
- Requires an explicit provider, project path, scope review, and second confirmation before purge. Purge remains delegated to Context Mode's own MCP contract.
|
|
34
34
|
- Returns structured install and provider-specific upgrade argv for review; Paseo never executes those commands automatically.
|
|
35
35
|
- On Paseo 0.11, registers the `context-mode` screen with URL-persisted `section`, `agentId`, and `workspaceId` params. Composer pills open Knowledge using the selected host's agent provider and working directory. URL params never carry paths or queries.
|
|
36
|
-
-
|
|
36
|
+
- Providers that Context Mode does not support, and missing agents or mismatched workspaces, require explicit scope selection.
|
|
37
37
|
- Adds a live Healthy/Failing sidebar footer row on Paseo 0.11, reusing the screen's health data. Paseo 0.9/0.10 retain the static sidebar, Savings surface, and unscoped composer shortcut.
|
|
38
38
|
|
|
39
39
|
Context Mode currently couples sessions, stats, and indexed content under `CONTEXT_MODE_DIR`. Until upstream supports separate session and content roots, this plugin keeps storage provider-scoped instead of risking cross-provider session contamination.
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "../shared";
|
|
14
14
|
import { ActionsSection } from "./actions-section";
|
|
15
15
|
import { AnalyticsDashboard } from "./analytics-dashboard";
|
|
16
|
-
import { KnowledgeSection } from "./knowledge-section";
|
|
16
|
+
import { BUILTIN_KNOWLEDGE_PROVIDERS, KnowledgeSection } from "./knowledge-section";
|
|
17
17
|
import { ScopedKnowledgeSection } from "./scoped-knowledge-section";
|
|
18
18
|
import { parseScreenParams, type SurfaceTab } from "./screen-scope";
|
|
19
19
|
|
|
@@ -76,6 +76,10 @@ export function ContextModeSurface({
|
|
|
76
76
|
refetchInterval: refreshInterval,
|
|
77
77
|
enabled: readySettings !== null,
|
|
78
78
|
});
|
|
79
|
+
const auditedProviders = integration.data?.providers.map(({ provider }) => provider) ?? [];
|
|
80
|
+
const knowledgeProviders = auditedProviders.length
|
|
81
|
+
? auditedProviders
|
|
82
|
+
: BUILTIN_KNOWLEDGE_PROVIDERS;
|
|
79
83
|
const refresh = useMutation({
|
|
80
84
|
mutationFn: async () => {
|
|
81
85
|
const nextStatus = await loadStatus({ fresh: true });
|
|
@@ -280,16 +284,25 @@ export function ContextModeSurface({
|
|
|
280
284
|
/>
|
|
281
285
|
</View>
|
|
282
286
|
<View style={[styles.tabPanel, activeTab === "knowledge" ? null : styles.tabPanelHidden]}>
|
|
283
|
-
{activeTab !== "knowledge" ? null :
|
|
287
|
+
{activeTab !== "knowledge" ? null : integration.isLoading ? (
|
|
288
|
+
<Text style={{ color: theme.colors.foregroundMuted }}>Loading providers…</Text>
|
|
289
|
+
) : screenParams.agentId || screenParams.workspaceId ? (
|
|
284
290
|
<ScopedKnowledgeSection
|
|
285
291
|
key={JSON.stringify([host.id, screenParams.agentId, screenParams.workspaceId])}
|
|
286
292
|
params={screenParams}
|
|
287
293
|
host={host}
|
|
288
294
|
layout={layout}
|
|
289
295
|
theme={theme}
|
|
296
|
+
providers={knowledgeProviders}
|
|
290
297
|
/>
|
|
291
298
|
) : (
|
|
292
|
-
<KnowledgeSection
|
|
299
|
+
<KnowledgeSection
|
|
300
|
+
key={host.id}
|
|
301
|
+
host={host}
|
|
302
|
+
layout={layout}
|
|
303
|
+
theme={theme}
|
|
304
|
+
providers={knowledgeProviders}
|
|
305
|
+
/>
|
|
293
306
|
)}
|
|
294
307
|
</View>
|
|
295
308
|
<View style={[styles.tabPanel, activeTab === "setup" ? null : styles.tabPanelHidden]}>
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
type ViewStyle,
|
|
14
14
|
} from "react-native";
|
|
15
15
|
import {
|
|
16
|
-
type ContextModeProvider,
|
|
17
16
|
fetchAndIndexContextMode,
|
|
18
17
|
indexContextModePath,
|
|
19
18
|
type KnowledgeToolResult,
|
|
@@ -22,22 +21,29 @@ import {
|
|
|
22
21
|
} from "../shared/knowledge";
|
|
23
22
|
import type { KnowledgeScope } from "./screen-scope";
|
|
24
23
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
const PROVIDER_LABELS: Readonly<Record<string, string>> = {
|
|
25
|
+
claude: "Claude",
|
|
26
|
+
codex: "Codex",
|
|
27
|
+
copilot: "Copilot",
|
|
28
|
+
cursor: "Cursor",
|
|
29
|
+
opencode: "OpenCode",
|
|
30
|
+
pi: "Pi",
|
|
31
|
+
omp: "OMP",
|
|
32
|
+
"omp-plugin": "OMP plugin",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Used until the integration audit reports the configured providers. */
|
|
36
|
+
export const BUILTIN_KNOWLEDGE_PROVIDERS = Object.keys(PROVIDER_LABELS);
|
|
37
|
+
|
|
38
|
+
const providerLabel = (id: string) => PROVIDER_LABELS[id] ?? id;
|
|
35
39
|
|
|
36
40
|
const INSIGHT_URL = "https://context-mode.com/insight";
|
|
37
|
-
type Provider =
|
|
41
|
+
type Provider = string;
|
|
38
42
|
type PurgeScope = "project" | "session";
|
|
39
43
|
export type KnowledgeSectionProps = Pick<PluginSurfaceProps, "theme" | "layout" | "host"> & {
|
|
40
44
|
initialScope?: KnowledgeScope;
|
|
45
|
+
/** Paseo provider ids with a Context Mode store: the built-ins plus every configured provider. */
|
|
46
|
+
providers: readonly string[];
|
|
41
47
|
};
|
|
42
48
|
|
|
43
49
|
interface KnowledgeStyles {
|
|
@@ -238,7 +244,13 @@ function ResultPanel({
|
|
|
238
244
|
);
|
|
239
245
|
}
|
|
240
246
|
|
|
241
|
-
export function KnowledgeSection({
|
|
247
|
+
export function KnowledgeSection({
|
|
248
|
+
theme,
|
|
249
|
+
layout,
|
|
250
|
+
host,
|
|
251
|
+
initialScope,
|
|
252
|
+
providers,
|
|
253
|
+
}: KnowledgeSectionProps) {
|
|
242
254
|
const styles = useMemo(() => createStyles(theme, layout.compact), [layout.compact, theme]);
|
|
243
255
|
const searchKnowledge = useRpc(searchContextModeKnowledge);
|
|
244
256
|
const indexPath = useRpc(indexContextModePath);
|
|
@@ -250,8 +262,15 @@ export function KnowledgeSection({ theme, layout, host, initialScope }: Knowledg
|
|
|
250
262
|
queryKey: ["context-mode", host.id, "analytics-dashboard"],
|
|
251
263
|
});
|
|
252
264
|
|
|
265
|
+
const scopedProvider = initialScope?.provider ?? null;
|
|
266
|
+
const scopedProviderKnown = scopedProvider !== null && providers.includes(scopedProvider);
|
|
267
|
+
const note =
|
|
268
|
+
initialScope?.note ??
|
|
269
|
+
(scopedProvider && !scopedProviderKnown
|
|
270
|
+
? `Provider ${scopedProvider} has no Context Mode store on this host. Select one explicitly.`
|
|
271
|
+
: undefined);
|
|
253
272
|
const [provider, setProvider] = useState<Provider | null>(
|
|
254
|
-
initialScope ?
|
|
273
|
+
initialScope ? (scopedProviderKnown ? scopedProvider : null) : "claude",
|
|
255
274
|
);
|
|
256
275
|
const [projectPath, setProjectPath] = useState(initialScope?.projectPath ?? "");
|
|
257
276
|
const [query, setQuery] = useState("");
|
|
@@ -367,18 +386,18 @@ export function KnowledgeSection({ theme, layout, host, initialScope }: Knowledg
|
|
|
367
386
|
|
|
368
387
|
<View style={styles.card}>
|
|
369
388
|
<Text style={styles.fieldLabel}>Provider store</Text>
|
|
370
|
-
{
|
|
389
|
+
{note ? <Text style={styles.scopeNote}>{note}</Text> : null}
|
|
371
390
|
<View accessibilityRole="radiogroup" style={styles.choiceRow}>
|
|
372
|
-
{
|
|
373
|
-
const selected = provider ===
|
|
391
|
+
{providers.map((id) => {
|
|
392
|
+
const selected = provider === id;
|
|
374
393
|
return (
|
|
375
394
|
<Pressable
|
|
376
|
-
key={
|
|
377
|
-
accessibilityLabel={`${
|
|
395
|
+
key={id}
|
|
396
|
+
accessibilityLabel={`${providerLabel(id)} provider store`}
|
|
378
397
|
disabled={busy}
|
|
379
398
|
accessibilityRole="radio"
|
|
380
399
|
accessibilityState={{ checked: selected, disabled: busy }}
|
|
381
|
-
onPress={() => changeProvider(
|
|
400
|
+
onPress={() => changeProvider(id)}
|
|
382
401
|
style={[
|
|
383
402
|
styles.choice,
|
|
384
403
|
selected ? styles.choiceSelected : null,
|
|
@@ -386,7 +405,7 @@ export function KnowledgeSection({ theme, layout, host, initialScope }: Knowledg
|
|
|
386
405
|
]}
|
|
387
406
|
>
|
|
388
407
|
<Text style={[styles.choiceText, selected ? styles.choiceTextSelected : null]}>
|
|
389
|
-
{
|
|
408
|
+
{providerLabel(id)}
|
|
390
409
|
</Text>
|
|
391
410
|
</Pressable>
|
|
392
411
|
);
|
|
@@ -406,7 +425,7 @@ export function KnowledgeSection({ theme, layout, host, initialScope }: Knowledg
|
|
|
406
425
|
/>
|
|
407
426
|
<Text accessibilityLiveRegion="polite" style={styles.scopeNote}>
|
|
408
427
|
{provider && projectPath.trim()
|
|
409
|
-
? `Current destination: ${
|
|
428
|
+
? `Current destination: ${providerLabel(provider)} storage for ${projectPath.trim()}.`
|
|
410
429
|
: "Select a provider and absolute project path. No storage destination is selected by default."}
|
|
411
430
|
</Text>
|
|
412
431
|
</View>
|
|
@@ -7,7 +7,7 @@ import { resolveKnowledgeScope, type ScreenParams } from "./screen-scope";
|
|
|
7
7
|
export function ScopedKnowledgeSection({
|
|
8
8
|
params,
|
|
9
9
|
...props
|
|
10
|
-
}: PluginSurfaceProps & { params: ScreenParams }) {
|
|
10
|
+
}: PluginSurfaceProps & { params: ScreenParams; providers: readonly string[] }) {
|
|
11
11
|
const paseo = usePaseo();
|
|
12
12
|
const scope = useQuery({
|
|
13
13
|
queryKey: [
|
|
@@ -40,7 +40,11 @@ export function ScopedKnowledgeSection({
|
|
|
40
40
|
// Remount only when the resolved destination changes so stale form state never survives it.
|
|
41
41
|
return (
|
|
42
42
|
<KnowledgeSection
|
|
43
|
-
key={JSON.stringify([
|
|
43
|
+
key={JSON.stringify([
|
|
44
|
+
scope.data.provider,
|
|
45
|
+
scope.data.projectPath,
|
|
46
|
+
props.providers.includes(scope.data.provider ?? ""),
|
|
47
|
+
])}
|
|
44
48
|
{...props}
|
|
45
49
|
initialScope={scope.data}
|
|
46
50
|
/>
|
package/client/screen-scope.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { PluginClientContext } from "@getpaseo/plugin/client";
|
|
2
|
-
import { type ContextModeProvider, ContextModeProviderSchema } from "../shared/knowledge";
|
|
3
2
|
|
|
4
3
|
type PaseoApi = PluginClientContext["paseo"];
|
|
5
4
|
|
|
6
5
|
export type SurfaceTab = "savings" | "knowledge" | "setup";
|
|
7
6
|
export type KnowledgeScope = {
|
|
8
|
-
provider:
|
|
7
|
+
provider: string | null;
|
|
9
8
|
projectPath: string;
|
|
10
9
|
note?: string;
|
|
11
10
|
};
|
|
@@ -21,11 +20,6 @@ export function parseScreenParams(params: Record<string, string>): ScreenParams
|
|
|
21
20
|
};
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
export function knowledgeProvider(provider: string): ContextModeProvider | null {
|
|
25
|
-
const parsed = ContextModeProviderSchema.safeParse(provider);
|
|
26
|
-
return parsed.success ? parsed.data : null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
23
|
export async function resolveKnowledgeScope(
|
|
30
24
|
paseo: PaseoApi,
|
|
31
25
|
params: ScreenParams,
|
|
@@ -39,16 +33,7 @@ export async function resolveKnowledgeScope(
|
|
|
39
33
|
"Agent no longer belongs to this workspace. Select a provider and project explicitly.",
|
|
40
34
|
);
|
|
41
35
|
}
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
provider,
|
|
45
|
-
projectPath: result.agent.cwd,
|
|
46
|
-
...(!provider
|
|
47
|
-
? {
|
|
48
|
-
note: `Provider ${result.agent.provider} cannot be mapped to a knowledge store. Select one explicitly.`,
|
|
49
|
-
}
|
|
50
|
-
: {}),
|
|
51
|
-
};
|
|
36
|
+
return { provider: result.agent.provider, projectPath: result.agent.cwd };
|
|
52
37
|
}
|
|
53
38
|
const workspace = params.workspaceId
|
|
54
39
|
? await paseo.workspaces.ref(params.workspaceId).refresh()
|
package/package.json
CHANGED
package/server/analytics.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
readContextModeDatabase,
|
|
17
17
|
type SessionDatabaseSnapshot,
|
|
18
18
|
} from "./context-mode-db";
|
|
19
|
+
import { type ProviderConfigSource, readProviderOverrides } from "./integration";
|
|
19
20
|
import {
|
|
20
21
|
discoverContextModeStorageRoots,
|
|
21
22
|
listRegularContextModeDatabases,
|
|
@@ -257,7 +258,11 @@ export async function collectContextModeAnalytics(
|
|
|
257
258
|
const listDatabases = dependencies.listDatabases ?? listRegularContextModeDatabases;
|
|
258
259
|
const readDatabase = dependencies.readDatabase ?? readContextModeDatabase;
|
|
259
260
|
const warningCollector = new WarningCollector();
|
|
260
|
-
const discovery = await discoverStorage({
|
|
261
|
+
const discovery = await discoverStorage({
|
|
262
|
+
home: dependencies.home,
|
|
263
|
+
env: dependencies.env,
|
|
264
|
+
providers: dependencies.providers,
|
|
265
|
+
});
|
|
261
266
|
for (const warning of discovery.warnings) warningCollector.add(warning);
|
|
262
267
|
|
|
263
268
|
const total = createAggregate();
|
|
@@ -419,6 +424,13 @@ export async function collectContextModeAnalytics(
|
|
|
419
424
|
|
|
420
425
|
export function createContextModeAnalyticsHandler(
|
|
421
426
|
dependencies: ContextModeAnalyticsDependencies = {},
|
|
422
|
-
): (
|
|
423
|
-
|
|
427
|
+
): (
|
|
428
|
+
_input: Record<string, never>,
|
|
429
|
+
context?: { paseo?: ProviderConfigSource },
|
|
430
|
+
) => Promise<AnalyticsDashboard> {
|
|
431
|
+
return async (_input, context) =>
|
|
432
|
+
collectContextModeAnalytics({
|
|
433
|
+
...dependencies,
|
|
434
|
+
providers: dependencies.providers ?? (await readProviderOverrides(context?.paseo)),
|
|
435
|
+
});
|
|
424
436
|
}
|
package/server/integration.ts
CHANGED
|
@@ -296,7 +296,7 @@ function providerPolicy(provider: string): ProviderPolicy | null {
|
|
|
296
296
|
* Mirrors Paseo's single-level `extends`: a derived provider uses its parent's policy, and its
|
|
297
297
|
* effective env is the parent's override env overlaid with its own.
|
|
298
298
|
*/
|
|
299
|
-
function resolveProvider(
|
|
299
|
+
export function resolveProvider(
|
|
300
300
|
id: string,
|
|
301
301
|
providers: ProviderOverrides = {},
|
|
302
302
|
): { base: string; policy: ProviderPolicy | null; env: Record<string, string> } {
|
package/server/knowledge.ts
CHANGED
|
@@ -12,7 +12,13 @@ import type {
|
|
|
12
12
|
} from "../shared/knowledge";
|
|
13
13
|
import type { ContextModeSettings, contextModeSettings } from "../shared/settings";
|
|
14
14
|
import { type BinaryResolution, type LaunchDescriptor, resolveContextModeBinary } from "./binary";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
contextModeStorageRootFor,
|
|
17
|
+
type ProviderConfigSource,
|
|
18
|
+
type ProviderOverrides,
|
|
19
|
+
readProviderOverrides,
|
|
20
|
+
resolveProvider,
|
|
21
|
+
} from "./integration";
|
|
16
22
|
import {
|
|
17
23
|
type ContextModeToolName,
|
|
18
24
|
callContextModeTool,
|
|
@@ -35,11 +41,16 @@ export interface KnowledgeHandlerDependencies {
|
|
|
35
41
|
callTool?: KnowledgeToolCaller;
|
|
36
42
|
}
|
|
37
43
|
|
|
44
|
+
type KnowledgeContext = { paseo?: ProviderConfigSource };
|
|
45
|
+
|
|
38
46
|
export interface ContextModeKnowledgeHandlers {
|
|
39
|
-
search(input: SearchKnowledgeInput): Promise<KnowledgeToolResult>;
|
|
40
|
-
indexPath(input: IndexPathInput): Promise<KnowledgeToolResult>;
|
|
41
|
-
fetchAndIndex(
|
|
42
|
-
|
|
47
|
+
search(input: SearchKnowledgeInput, context?: KnowledgeContext): Promise<KnowledgeToolResult>;
|
|
48
|
+
indexPath(input: IndexPathInput, context?: KnowledgeContext): Promise<KnowledgeToolResult>;
|
|
49
|
+
fetchAndIndex(
|
|
50
|
+
input: FetchAndIndexInput,
|
|
51
|
+
context?: KnowledgeContext,
|
|
52
|
+
): Promise<KnowledgeToolResult>;
|
|
53
|
+
purge(input: PurgeKnowledgeInput, context?: KnowledgeContext): Promise<KnowledgeToolResult>;
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
const PROVIDER_PLATFORMS: Readonly<Record<ContextModeProvider, ContextModePlatform>> = {
|
|
@@ -57,17 +68,21 @@ export function contextModePlatformFor(provider: ContextModeProvider): ContextMo
|
|
|
57
68
|
return PROVIDER_PLATFORMS[provider];
|
|
58
69
|
}
|
|
59
70
|
|
|
71
|
+
/**
|
|
72
|
+
* `provider` is a Paseo provider id: a built-in, or a configured one whose own `extends` and env
|
|
73
|
+
* decide the storage root (`providers` is `agents.providers`).
|
|
74
|
+
*/
|
|
60
75
|
export function contextModeEnvironmentFor(
|
|
61
|
-
provider:
|
|
76
|
+
provider: string,
|
|
62
77
|
home = homedir(),
|
|
63
78
|
env: NodeJS.ProcessEnv = process.env,
|
|
79
|
+
providers: ProviderOverrides = {},
|
|
64
80
|
): NodeJS.ProcessEnv {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
};
|
|
81
|
+
const { base, policy, env: providerEnv } = resolveProvider(provider, providers);
|
|
82
|
+
const storageRoot = contextModeStorageRootFor(base, { home, env: { ...env, ...providerEnv } });
|
|
83
|
+
if (!policy || !storageRoot)
|
|
84
|
+
throw new Error(`Context Mode does not support provider ${provider}.`);
|
|
85
|
+
return { CONTEXT_MODE_PLATFORM: policy.platform, CONTEXT_MODE_DIR: storageRoot };
|
|
71
86
|
}
|
|
72
87
|
|
|
73
88
|
function definedArguments(values: Record<string, unknown>): McpToolArguments {
|
|
@@ -95,42 +110,51 @@ export function createContextModeKnowledgeHandlers(
|
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
async function run(
|
|
98
|
-
provider:
|
|
113
|
+
provider: string,
|
|
99
114
|
projectPath: string,
|
|
100
115
|
name: ContextModeToolName,
|
|
101
116
|
arguments_: McpToolArguments,
|
|
117
|
+
context: KnowledgeContext | undefined,
|
|
102
118
|
environmentOverrides: NodeJS.ProcessEnv = {},
|
|
103
119
|
): Promise<KnowledgeToolResult> {
|
|
104
120
|
const launch = await launchDescriptor();
|
|
121
|
+
const providers = await readProviderOverrides(context?.paseo);
|
|
105
122
|
const output = await callTool(launch, name, arguments_, {
|
|
106
|
-
env: {
|
|
123
|
+
env: {
|
|
124
|
+
...contextModeEnvironmentFor(provider, homedir(), process.env, providers),
|
|
125
|
+
...environmentOverrides,
|
|
126
|
+
},
|
|
107
127
|
cwd: projectPath,
|
|
108
128
|
});
|
|
109
129
|
return { provider, output, completedAt: now().toISOString() };
|
|
110
130
|
}
|
|
111
131
|
|
|
112
132
|
return {
|
|
113
|
-
search({ provider, projectPath, queries, limit, source, contentType, sort }) {
|
|
133
|
+
search({ provider, projectPath, queries, limit, source, contentType, sort }, context) {
|
|
114
134
|
return run(
|
|
115
135
|
provider,
|
|
116
136
|
projectPath,
|
|
117
137
|
"ctx_search",
|
|
118
138
|
definedArguments({ queries, limit, source, contentType, sort }),
|
|
139
|
+
context,
|
|
119
140
|
);
|
|
120
141
|
},
|
|
121
|
-
indexPath(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
142
|
+
indexPath(
|
|
143
|
+
{
|
|
144
|
+
provider,
|
|
145
|
+
projectPath,
|
|
146
|
+
path,
|
|
147
|
+
source,
|
|
148
|
+
include,
|
|
149
|
+
exclude,
|
|
150
|
+
maxDepth,
|
|
151
|
+
maxFiles,
|
|
152
|
+
extensions,
|
|
153
|
+
respectGitignore,
|
|
154
|
+
followSymlinks,
|
|
155
|
+
},
|
|
156
|
+
context,
|
|
157
|
+
) {
|
|
134
158
|
if (!isAbsolute(path)) {
|
|
135
159
|
throw new Error("Knowledge indexing requires an absolute path on this host.");
|
|
136
160
|
}
|
|
@@ -149,18 +173,20 @@ export function createContextModeKnowledgeHandlers(
|
|
|
149
173
|
respectGitignore,
|
|
150
174
|
followSymlinks,
|
|
151
175
|
}),
|
|
176
|
+
context,
|
|
152
177
|
);
|
|
153
178
|
},
|
|
154
|
-
fetchAndIndex({ provider, projectPath, url, source, force, ttl }) {
|
|
179
|
+
fetchAndIndex({ provider, projectPath, url, source, force, ttl }, context) {
|
|
155
180
|
return run(
|
|
156
181
|
provider,
|
|
157
182
|
projectPath,
|
|
158
183
|
"ctx_fetch_and_index",
|
|
159
184
|
definedArguments({ url, source, force, ttl }),
|
|
185
|
+
context,
|
|
160
186
|
{ CTX_FETCH_STRICT: "1" },
|
|
161
187
|
);
|
|
162
188
|
},
|
|
163
|
-
async purge(input) {
|
|
189
|
+
async purge(input, context) {
|
|
164
190
|
const raw = input as PurgeKnowledgeInput & { sessionId?: string };
|
|
165
191
|
if (raw.confirm !== true) {
|
|
166
192
|
throw new Error("Purge requires exact confirmation with confirm: true.");
|
|
@@ -169,19 +195,24 @@ export function createContextModeKnowledgeHandlers(
|
|
|
169
195
|
if (raw.sessionId !== undefined) {
|
|
170
196
|
throw new Error("Purge requires exactly one scope.");
|
|
171
197
|
}
|
|
172
|
-
return await run(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
198
|
+
return await run(
|
|
199
|
+
raw.provider,
|
|
200
|
+
raw.projectPath,
|
|
201
|
+
"ctx_purge",
|
|
202
|
+
{ confirm: true, scope: "project" },
|
|
203
|
+
context,
|
|
204
|
+
);
|
|
176
205
|
}
|
|
177
206
|
if (raw.scope !== "session" || !raw.sessionId) {
|
|
178
207
|
throw new Error("Session purge requires exactly one non-empty sessionId scope.");
|
|
179
208
|
}
|
|
180
|
-
return await run(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
209
|
+
return await run(
|
|
210
|
+
raw.provider,
|
|
211
|
+
raw.projectPath,
|
|
212
|
+
"ctx_purge",
|
|
213
|
+
{ confirm: true, scope: "session", sessionId: raw.sessionId },
|
|
214
|
+
context,
|
|
215
|
+
);
|
|
185
216
|
},
|
|
186
217
|
};
|
|
187
218
|
}
|
|
@@ -3,6 +3,7 @@ import { lstat, readdir, realpath } from "node:fs/promises";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import type { AnalyticsWarning } from "../shared/analytics";
|
|
6
|
+
import { contextModeStorageRootFor, type ProviderOverrides, resolveProvider } from "./integration";
|
|
6
7
|
|
|
7
8
|
const MAX_DATABASES_PER_DIRECTORY = 512;
|
|
8
9
|
const MAX_LOCAL_DISCOVERY_WARNINGS = 10;
|
|
@@ -23,6 +24,8 @@ export interface StorageDiscoveryOptions {
|
|
|
23
24
|
home?: string;
|
|
24
25
|
env?: NodeJS.ProcessEnv;
|
|
25
26
|
platform?: NodeJS.Platform;
|
|
27
|
+
/** Paseo `agents.providers`; each one contributes the storage its own env resolves to. */
|
|
28
|
+
providers?: ProviderOverrides;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
interface ProviderLocation {
|
|
@@ -54,6 +57,7 @@ function providerLocations(
|
|
|
54
57
|
env: NodeJS.ProcessEnv,
|
|
55
58
|
warnings: AnalyticsWarning[],
|
|
56
59
|
platform: NodeJS.Platform,
|
|
60
|
+
providers: ProviderOverrides,
|
|
57
61
|
): ProviderLocation[] {
|
|
58
62
|
const configuredRoot = configuredDirectory(
|
|
59
63
|
env.CONTEXT_MODE_DIR,
|
|
@@ -82,6 +86,14 @@ function providerLocations(
|
|
|
82
86
|
: (configuredDirectory(env.XDG_CONFIG_HOME, home, "XDG_CONFIG_HOME", warnings) ??
|
|
83
87
|
join(home, ".config"));
|
|
84
88
|
|
|
89
|
+
const configured = Object.keys(providers).flatMap((id) => {
|
|
90
|
+
const { base, policy, env: providerEnv } = resolveProvider(id, providers);
|
|
91
|
+
const root = policy
|
|
92
|
+
? contextModeStorageRootFor(base, { home, env: { ...env, ...providerEnv } })
|
|
93
|
+
: null;
|
|
94
|
+
return root ? [{ provider: id, root }] : [];
|
|
95
|
+
});
|
|
96
|
+
|
|
85
97
|
return [
|
|
86
98
|
...(configuredRoot ? [{ provider: "custom", root: configuredRoot }] : []),
|
|
87
99
|
...(configuredDataRoot
|
|
@@ -106,6 +118,7 @@ function providerLocations(
|
|
|
106
118
|
provider: "jetbrains-copilot",
|
|
107
119
|
root: join(home, ".config", "JetBrains", "context-mode"),
|
|
108
120
|
},
|
|
121
|
+
...configured,
|
|
109
122
|
];
|
|
110
123
|
}
|
|
111
124
|
|
|
@@ -132,6 +145,7 @@ export async function discoverContextModeStorageRoots(
|
|
|
132
145
|
env,
|
|
133
146
|
warnings,
|
|
134
147
|
options.platform ?? process.platform,
|
|
148
|
+
options.providers ?? {},
|
|
135
149
|
)) {
|
|
136
150
|
try {
|
|
137
151
|
const info = await lstat(location.root);
|
package/shared/knowledge.ts
CHANGED
|
@@ -27,6 +27,9 @@ export const ContextModePlatformSchema = z.enum([
|
|
|
27
27
|
"omp",
|
|
28
28
|
]);
|
|
29
29
|
|
|
30
|
+
/** A Paseo provider id: a built-in or one configured in `agents.providers`. */
|
|
31
|
+
export const KnowledgeProviderSchema = z.string().trim().min(1).max(128);
|
|
32
|
+
|
|
30
33
|
const boundedLabel = z.string().trim().min(1).max(MAX_LABEL_LENGTH);
|
|
31
34
|
const boundedPattern = z.string().trim().min(1).max(256);
|
|
32
35
|
const absoluteLocalPath = z
|
|
@@ -43,7 +46,7 @@ const absoluteLocalPath = z
|
|
|
43
46
|
{ message: "path must be an absolute local path" },
|
|
44
47
|
);
|
|
45
48
|
const providerScope = z
|
|
46
|
-
.object({ provider:
|
|
49
|
+
.object({ provider: KnowledgeProviderSchema, projectPath: absoluteLocalPath })
|
|
47
50
|
.strict();
|
|
48
51
|
const httpUrl = z
|
|
49
52
|
.string()
|
|
@@ -55,7 +58,7 @@ const httpUrl = z
|
|
|
55
58
|
|
|
56
59
|
export const KnowledgeToolResultSchema = z
|
|
57
60
|
.object({
|
|
58
|
-
provider:
|
|
61
|
+
provider: KnowledgeProviderSchema,
|
|
59
62
|
output: z.string().max(MAX_OUTPUT_LENGTH),
|
|
60
63
|
completedAt: z.string().datetime(),
|
|
61
64
|
})
|
|
@@ -96,7 +99,7 @@ export const FetchAndIndexInputSchema = providerScope
|
|
|
96
99
|
|
|
97
100
|
const projectPurge = z
|
|
98
101
|
.object({
|
|
99
|
-
provider:
|
|
102
|
+
provider: KnowledgeProviderSchema,
|
|
100
103
|
projectPath: absoluteLocalPath,
|
|
101
104
|
confirm: z.literal(true),
|
|
102
105
|
scope: z.literal("project"),
|
|
@@ -104,7 +107,7 @@ const projectPurge = z
|
|
|
104
107
|
.strict();
|
|
105
108
|
const sessionPurge = z
|
|
106
109
|
.object({
|
|
107
|
-
provider:
|
|
110
|
+
provider: KnowledgeProviderSchema,
|
|
108
111
|
projectPath: absoluteLocalPath,
|
|
109
112
|
confirm: z.literal(true),
|
|
110
113
|
scope: z.literal("session"),
|