@omercnet/paseo-context-mode 3.1.4-next.382.1 → 3.1.4-next.434.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/OVERVIEW.md +3 -3
- package/README.md +13 -15
- package/client/analytics-dashboard.tsx +745 -613
- package/client/controls.tsx +295 -0
- package/client/diagnostics-section.tsx +178 -0
- package/client/{context-mode-footer.tsx → footer.tsx} +22 -20
- package/client/knowledge-section.tsx +725 -697
- package/client/{context-mode-pill.tsx → pill.tsx} +5 -5
- package/client/scoped-knowledge-section.tsx +14 -20
- package/client/screen-scope.ts +12 -6
- package/client/settings-screen.tsx +94 -97
- package/client/surface.tsx +377 -0
- package/index.client.tsx +3 -3
- package/index.server.ts +18 -27
- package/npm-shrinkwrap.json +6 -1745
- package/package.json +3 -3
- package/server/actions.ts +91 -102
- package/server/analytics.ts +257 -308
- package/server/binary.ts +119 -94
- package/server/database.ts +463 -0
- package/server/handlers.ts +40 -95
- package/server/integration.ts +230 -361
- package/server/knowledge.ts +65 -137
- package/server/mcp-process.ts +135 -86
- package/server/storage-discovery.ts +46 -199
- package/shared/actions.ts +2 -27
- package/shared/analytics.ts +60 -33
- package/shared/contracts.ts +16 -47
- package/shared/knowledge.ts +62 -65
- package/shared/providers.ts +62 -0
- package/shared/settings.ts +33 -18
- package/client/actions-section.tsx +0 -621
- package/client/context-mode-surface.tsx +0 -444
- package/server/context-mode-db.ts +0 -481
- package/shared/index.ts +0 -5
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { PluginButtonRegistration, PluginClientContext } from "@getpaseo/plugin/client";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
interface PillAgent {
|
|
4
4
|
id: string;
|
|
5
5
|
workspaceId?: string | null;
|
|
6
6
|
status: string;
|
|
7
7
|
archivedAt?: string | null;
|
|
8
|
-
}
|
|
8
|
+
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
interface PillEntry {
|
|
11
11
|
workspaceId: string;
|
|
12
12
|
registration: PluginButtonRegistration;
|
|
13
|
-
}
|
|
13
|
+
}
|
|
14
14
|
|
|
15
15
|
function shouldShowPill(agent: PillAgent): agent is PillAgent & { workspaceId: string } {
|
|
16
16
|
return Boolean(agent.workspaceId) && !agent.archivedAt && agent.status !== "closed";
|
|
@@ -44,7 +44,7 @@ export function contributeContextModeComposerPills(client: PluginClientContext):
|
|
|
44
44
|
workspaceId: agent.workspaceId,
|
|
45
45
|
agentId: agent.id,
|
|
46
46
|
button: {
|
|
47
|
-
title: "
|
|
47
|
+
title: "Open Context Mode knowledge",
|
|
48
48
|
icon: "Gauge",
|
|
49
49
|
label: "Context Mode",
|
|
50
50
|
behavior: {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { usePaseo } from "@getpaseo/plugin/client";
|
|
2
2
|
import { useQuery } from "@tanstack/react-query";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { View } from "react-native";
|
|
4
|
+
import { ErrorText, Pending } from "./controls";
|
|
5
|
+
import { KnowledgeSection, type KnowledgeSectionProps } from "./knowledge-section";
|
|
5
6
|
import { resolveKnowledgeScope, type ScreenParams } from "./screen-scope";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
params
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
interface ScopedKnowledgeSectionProps extends Omit<KnowledgeSectionProps, "initialScope"> {
|
|
9
|
+
params: ScreenParams;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ScopedKnowledgeSection({ params, ...props }: ScopedKnowledgeSectionProps) {
|
|
11
13
|
const paseo = usePaseo();
|
|
12
14
|
const scope = useQuery({
|
|
13
15
|
queryKey: [
|
|
@@ -20,16 +22,11 @@ export function ScopedKnowledgeSection({
|
|
|
20
22
|
queryFn: () => resolveKnowledgeScope(paseo, params),
|
|
21
23
|
retry: false,
|
|
22
24
|
});
|
|
23
|
-
if (scope.isPending)
|
|
24
|
-
return (
|
|
25
|
-
<Text style={{ color: props.theme.colors.foregroundMuted }}>Resolving Knowledge scope…</Text>
|
|
26
|
-
);
|
|
25
|
+
if (scope.isPending) return <Pending theme={props.theme} label="Resolving knowledge scope..." />;
|
|
27
26
|
if (scope.error)
|
|
28
27
|
return (
|
|
29
|
-
<View>
|
|
30
|
-
<
|
|
31
|
-
{scope.error.message}
|
|
32
|
-
</Text>
|
|
28
|
+
<View style={{ gap: 12 }}>
|
|
29
|
+
<ErrorText theme={props.theme} error={scope.error} />
|
|
33
30
|
<KnowledgeSection
|
|
34
31
|
key="scope-error"
|
|
35
32
|
{...props}
|
|
@@ -37,14 +34,11 @@ export function ScopedKnowledgeSection({
|
|
|
37
34
|
/>
|
|
38
35
|
</View>
|
|
39
36
|
);
|
|
37
|
+
const scopedProviderKnown = props.providers.includes(scope.data.provider ?? "");
|
|
40
38
|
// Remount only when the resolved destination changes so stale form state never survives it.
|
|
41
39
|
return (
|
|
42
40
|
<KnowledgeSection
|
|
43
|
-
key={JSON.stringify([
|
|
44
|
-
scope.data.provider,
|
|
45
|
-
scope.data.projectPath,
|
|
46
|
-
props.providers.includes(scope.data.provider ?? ""),
|
|
47
|
-
])}
|
|
41
|
+
key={JSON.stringify([scope.data.provider, scope.data.projectPath, scopedProviderKnown])}
|
|
48
42
|
{...props}
|
|
49
43
|
initialScope={scope.data}
|
|
50
44
|
/>
|
package/client/screen-scope.ts
CHANGED
|
@@ -2,14 +2,20 @@ import type { PluginClientContext } from "@getpaseo/plugin/client";
|
|
|
2
2
|
|
|
3
3
|
type PaseoApi = PluginClientContext["paseo"];
|
|
4
4
|
|
|
5
|
+
/** `setup` is the Diagnostics tab; the id stays so links shared before the rename still open it. */
|
|
5
6
|
export type SurfaceTab = "savings" | "knowledge" | "setup";
|
|
6
|
-
export
|
|
7
|
+
export interface KnowledgeScope {
|
|
7
8
|
provider: string | null;
|
|
8
9
|
projectPath: string;
|
|
9
10
|
note?: string;
|
|
10
|
-
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ScreenParams {
|
|
14
|
+
section: SurfaceTab;
|
|
15
|
+
agentId?: string;
|
|
16
|
+
workspaceId?: string;
|
|
17
|
+
}
|
|
11
18
|
|
|
12
|
-
export type ScreenParams = { section: SurfaceTab; agentId?: string; workspaceId?: string };
|
|
13
19
|
export function parseScreenParams(params: Record<string, string>): ScreenParams {
|
|
14
20
|
const section: SurfaceTab =
|
|
15
21
|
params.section === "knowledge" || params.section === "setup" ? params.section : "savings";
|
|
@@ -25,7 +31,7 @@ function notOnThisHost(kind: "agent" | "workspace"): KnowledgeScope {
|
|
|
25
31
|
return {
|
|
26
32
|
provider: null,
|
|
27
33
|
projectPath: "",
|
|
28
|
-
note: `The ${kind} this screen was opened from is not on this host. Select
|
|
34
|
+
note: `The ${kind} this screen was opened from is not on this host. Select an agent and project.`,
|
|
29
35
|
};
|
|
30
36
|
}
|
|
31
37
|
|
|
@@ -38,7 +44,7 @@ export async function resolveKnowledgeScope(
|
|
|
38
44
|
if (!result) return notOnThisHost("agent");
|
|
39
45
|
if (params.workspaceId && result.agent.workspaceId !== params.workspaceId) {
|
|
40
46
|
throw new Error(
|
|
41
|
-
"Agent no longer belongs to this workspace. Select
|
|
47
|
+
"Agent no longer belongs to this workspace. Select an agent and project explicitly.",
|
|
42
48
|
);
|
|
43
49
|
}
|
|
44
50
|
return { provider: result.agent.provider, projectPath: result.agent.cwd };
|
|
@@ -50,6 +56,6 @@ export async function resolveKnowledgeScope(
|
|
|
50
56
|
return {
|
|
51
57
|
provider: null,
|
|
52
58
|
projectPath: workspace.workspaceDirectory,
|
|
53
|
-
note: "Select
|
|
59
|
+
note: "Select which agent to record knowledge work as for this workspace.",
|
|
54
60
|
};
|
|
55
61
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { PluginTheme } from "@getpaseo/plugin";
|
|
1
2
|
import { type PluginSurfaceProps, type SettingsState, useSettings } from "@getpaseo/plugin/client";
|
|
2
3
|
import {
|
|
4
|
+
Loading,
|
|
3
5
|
SettingsAction,
|
|
4
6
|
SettingsCard,
|
|
5
7
|
SettingsInput,
|
|
@@ -8,95 +10,109 @@ import {
|
|
|
8
10
|
SettingsSelect,
|
|
9
11
|
SettingsSwitch,
|
|
10
12
|
} from "@getpaseo/plugin/client/ui";
|
|
11
|
-
import {
|
|
13
|
+
import { useState } from "react";
|
|
12
14
|
import { Text, View } from "react-native";
|
|
13
15
|
import {
|
|
14
16
|
type ContextModeSettings,
|
|
15
17
|
ContextModeSettingsSchema,
|
|
16
18
|
contextModeSettings,
|
|
17
|
-
} from "../shared";
|
|
19
|
+
} from "../shared/settings";
|
|
20
|
+
import { type ChoiceOption, ErrorText } from "./controls";
|
|
18
21
|
|
|
19
22
|
type ReadySettings = Extract<SettingsState<typeof contextModeSettings.schema>, { status: "ready" }>;
|
|
23
|
+
type RefreshInterval = ContextModeSettings["refreshIntervalMs"];
|
|
24
|
+
type RefreshValue = `${RefreshInterval}`;
|
|
25
|
+
type RuntimeChoice = "bundled" | "own";
|
|
20
26
|
|
|
21
|
-
const
|
|
22
|
-
{ label: "
|
|
23
|
-
{ label: "
|
|
27
|
+
const RUNTIME_OPTIONS: readonly ChoiceOption<RuntimeChoice>[] = [
|
|
28
|
+
{ label: "Bundled", value: "bundled" },
|
|
29
|
+
{ label: "Your own binary", value: "own" },
|
|
24
30
|
];
|
|
25
|
-
|
|
31
|
+
|
|
32
|
+
const REFRESH_INTERVALS: Record<RefreshValue, RefreshInterval> = {
|
|
33
|
+
"5000": 5_000,
|
|
34
|
+
"15000": 15_000,
|
|
35
|
+
"30000": 30_000,
|
|
36
|
+
"60000": 60_000,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const REFRESH_OPTIONS: readonly ChoiceOption<RefreshValue>[] = [
|
|
26
40
|
{ label: "5 seconds", value: "5000" },
|
|
27
41
|
{ label: "15 seconds", value: "15000" },
|
|
28
42
|
{ label: "30 seconds", value: "30000" },
|
|
29
43
|
{ label: "60 seconds", value: "60000" },
|
|
30
44
|
];
|
|
31
45
|
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
function refreshValue(interval: RefreshInterval): RefreshValue {
|
|
47
|
+
return `${interval}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface ReadyContextModeSettingsProps {
|
|
36
51
|
settings: ReadySettings;
|
|
37
|
-
theme:
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
theme: PluginTheme;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function ReadyContextModeSettings({ settings, theme }: ReadyContextModeSettingsProps) {
|
|
56
|
+
const savedPath = settings.values.binaryPath;
|
|
57
|
+
const [runtime, setRuntime] = useState<RuntimeChoice>(savedPath === null ? "bundled" : "own");
|
|
58
|
+
const [binaryPath, setBinaryPath] = useState(savedPath ?? "");
|
|
59
|
+
const textStyle = { color: theme.colors.foreground };
|
|
60
|
+
const mutedStyle = { color: theme.colors.foregroundMuted };
|
|
61
|
+
|
|
62
|
+
function save(patch: Partial<ContextModeSettings>) {
|
|
63
|
+
void settings.save({ ...settings.values, ...patch }, settings.revision);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function chooseRuntime(next: RuntimeChoice) {
|
|
67
|
+
setRuntime(next);
|
|
68
|
+
if (next === "bundled" && savedPath !== null) save({ binaryPath: null });
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
const normalizedPath = binaryPath.trim();
|
|
72
|
+
const pathChanged = normalizedPath !== savedPath;
|
|
55
73
|
const pathResult = ContextModeSettingsSchema.safeParse({
|
|
56
74
|
...settings.values,
|
|
57
|
-
binaryMode: "path",
|
|
58
75
|
binaryPath: normalizedPath,
|
|
59
76
|
});
|
|
60
|
-
const pathError =
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
const pathError =
|
|
78
|
+
pathResult.success || normalizedPath === ""
|
|
79
|
+
? null
|
|
80
|
+
: pathResult.error.issues.map((issue) => issue.message).join(" ");
|
|
81
|
+
const canSavePath = !settings.saving && pathChanged && pathResult.success;
|
|
63
82
|
|
|
64
83
|
return (
|
|
65
|
-
<View style={
|
|
84
|
+
<View style={{ gap: 16 }}>
|
|
66
85
|
<SettingsSection
|
|
67
|
-
title="
|
|
68
|
-
info="
|
|
86
|
+
title="Runtime"
|
|
87
|
+
info="Context Mode runs from the runtime bundled with this plugin unless you point it at your own executable."
|
|
69
88
|
>
|
|
70
89
|
<SettingsCard>
|
|
71
90
|
<SettingsSelect
|
|
72
|
-
label="
|
|
73
|
-
value={
|
|
74
|
-
options={
|
|
91
|
+
label="Runtime"
|
|
92
|
+
value={runtime}
|
|
93
|
+
options={RUNTIME_OPTIONS}
|
|
75
94
|
disabled={settings.saving}
|
|
76
|
-
onValueChange={
|
|
77
|
-
void save({ binaryMode: binaryMode as ContextModeSettings["binaryMode"] })
|
|
78
|
-
}
|
|
79
|
-
/>
|
|
80
|
-
<SettingsInput
|
|
81
|
-
label="Absolute binary path"
|
|
82
|
-
hint="Optional external override. Leave empty to use PATH or the bundled runtime."
|
|
83
|
-
error={normalizedPath === settings.values.binaryPath ? null : pathError}
|
|
84
|
-
initialValue={binaryPath}
|
|
85
|
-
onChangeText={setBinaryPath}
|
|
86
|
-
placeholder="/usr/local/bin/context-mode"
|
|
87
|
-
disabled={settings.saving || settings.values.binaryMode === "automatic"}
|
|
88
|
-
/>
|
|
89
|
-
<SettingsAction
|
|
90
|
-
label="Save binary path"
|
|
91
|
-
actionLabel="Save"
|
|
92
|
-
disabled={
|
|
93
|
-
settings.saving ||
|
|
94
|
-
settings.values.binaryMode === "automatic" ||
|
|
95
|
-
normalizedPath === settings.values.binaryPath ||
|
|
96
|
-
!pathResult.success
|
|
97
|
-
}
|
|
98
|
-
onPress={() => void save({ binaryPath: normalizedPath })}
|
|
95
|
+
onValueChange={chooseRuntime}
|
|
99
96
|
/>
|
|
97
|
+
{runtime === "own" ? (
|
|
98
|
+
<>
|
|
99
|
+
<SettingsInput
|
|
100
|
+
label="Absolute binary path"
|
|
101
|
+
hint="The Context Mode executable the daemon should run"
|
|
102
|
+
error={pathChanged ? pathError : null}
|
|
103
|
+
initialValue={binaryPath}
|
|
104
|
+
onChangeText={setBinaryPath}
|
|
105
|
+
placeholder="/usr/local/bin/context-mode"
|
|
106
|
+
disabled={settings.saving}
|
|
107
|
+
/>
|
|
108
|
+
<SettingsAction
|
|
109
|
+
label="Use this binary"
|
|
110
|
+
actionLabel="Save"
|
|
111
|
+
disabled={!canSavePath}
|
|
112
|
+
onPress={() => save({ binaryPath: normalizedPath })}
|
|
113
|
+
/>
|
|
114
|
+
</>
|
|
115
|
+
) : null}
|
|
100
116
|
</SettingsCard>
|
|
101
117
|
</SettingsSection>
|
|
102
118
|
|
|
@@ -107,17 +123,17 @@ function ReadyContextModeSettings({
|
|
|
107
123
|
<SettingsCard>
|
|
108
124
|
<SettingsSwitch
|
|
109
125
|
label="Automatically activate Context Mode"
|
|
110
|
-
hint="Inject the
|
|
126
|
+
hint="Inject the Context Mode MCP server into new Claude, Codex, Copilot, and OpenCode agents"
|
|
111
127
|
value={settings.values.autoInject}
|
|
112
128
|
disabled={settings.saving}
|
|
113
|
-
onValueChange={(autoInject) =>
|
|
129
|
+
onValueChange={(autoInject) => save({ autoInject })}
|
|
114
130
|
/>
|
|
115
131
|
<SettingsSwitch
|
|
116
132
|
label="Prefer native integrations"
|
|
117
|
-
hint="
|
|
133
|
+
hint="Skip injection when Context Mode is already installed natively for a provider, so its tools are not registered twice"
|
|
118
134
|
value={settings.values.preferNativeIntegrations}
|
|
119
135
|
disabled={settings.saving || !settings.values.autoInject}
|
|
120
|
-
onValueChange={(preferNativeIntegrations) =>
|
|
136
|
+
onValueChange={(preferNativeIntegrations) => save({ preferNativeIntegrations })}
|
|
121
137
|
/>
|
|
122
138
|
</SettingsCard>
|
|
123
139
|
</SettingsSection>
|
|
@@ -125,32 +141,27 @@ function ReadyContextModeSettings({
|
|
|
125
141
|
<SettingsSection title="Display">
|
|
126
142
|
<SettingsCard>
|
|
127
143
|
<SettingsSelect
|
|
128
|
-
label="
|
|
129
|
-
value={
|
|
130
|
-
options={
|
|
144
|
+
label="Status refresh interval"
|
|
145
|
+
value={refreshValue(settings.values.refreshIntervalMs)}
|
|
146
|
+
options={REFRESH_OPTIONS}
|
|
131
147
|
disabled={settings.saving}
|
|
132
|
-
onValueChange={(value) =>
|
|
133
|
-
void save({
|
|
134
|
-
refreshIntervalMs: Number(value) as ContextModeSettings["refreshIntervalMs"],
|
|
135
|
-
})
|
|
136
|
-
}
|
|
148
|
+
onValueChange={(value) => save({ refreshIntervalMs: REFRESH_INTERVALS[value] })}
|
|
137
149
|
/>
|
|
138
150
|
</SettingsCard>
|
|
139
151
|
</SettingsSection>
|
|
140
152
|
|
|
141
|
-
<SettingsSection title="
|
|
153
|
+
<SettingsSection title="Storage">
|
|
142
154
|
<SettingsCard>
|
|
143
155
|
<SettingsRow
|
|
144
|
-
label="
|
|
145
|
-
hint="
|
|
156
|
+
label="Data location"
|
|
157
|
+
hint="Context Mode keeps one store per project, shared by every provider"
|
|
146
158
|
>
|
|
147
|
-
<Text style={
|
|
159
|
+
<Text style={textStyle}>Per project</Text>
|
|
148
160
|
</SettingsRow>
|
|
149
161
|
<SettingsRow label="Context Mode runtime">
|
|
150
|
-
<Text style={
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
<Text style={styles.muted}>Explicit command only</Text>
|
|
162
|
+
<Text selectable style={mutedStyle}>
|
|
163
|
+
{savedPath ?? "Bundled with this plugin"}
|
|
164
|
+
</Text>
|
|
154
165
|
</SettingsRow>
|
|
155
166
|
</SettingsCard>
|
|
156
167
|
</SettingsSection>
|
|
@@ -158,7 +169,7 @@ function ReadyContextModeSettings({
|
|
|
158
169
|
<SettingsSection title="Persistence">
|
|
159
170
|
<SettingsCard>
|
|
160
171
|
<SettingsRow label="Settings" hint="Shared by clients connected to this host">
|
|
161
|
-
<Text style={
|
|
172
|
+
<Text style={textStyle}>{settings.saving ? "Saving..." : "Saved on host"}</Text>
|
|
162
173
|
</SettingsRow>
|
|
163
174
|
<SettingsAction
|
|
164
175
|
label="Restore defaults"
|
|
@@ -176,11 +187,6 @@ function ReadyContextModeSettings({
|
|
|
176
187
|
/>
|
|
177
188
|
) : null}
|
|
178
189
|
</SettingsCard>
|
|
179
|
-
{settings.saveError ? (
|
|
180
|
-
<Text accessibilityRole="alert" style={styles.error}>
|
|
181
|
-
{settings.saveError}
|
|
182
|
-
</Text>
|
|
183
|
-
) : null}
|
|
184
190
|
</SettingsSection>
|
|
185
191
|
</View>
|
|
186
192
|
);
|
|
@@ -188,21 +194,12 @@ function ReadyContextModeSettings({
|
|
|
188
194
|
|
|
189
195
|
export function ContextModeSettingsScreen({ theme }: PluginSurfaceProps) {
|
|
190
196
|
const settings = useSettings(contextModeSettings);
|
|
191
|
-
const styles = useMemo(
|
|
192
|
-
() => ({
|
|
193
|
-
text: { color: theme.colors.foreground },
|
|
194
|
-
error: { color: theme.colors.statusDanger },
|
|
195
|
-
}),
|
|
196
|
-
[theme],
|
|
197
|
-
);
|
|
198
197
|
|
|
199
|
-
if (settings.status === "loading") return <
|
|
198
|
+
if (settings.status === "loading") return <Loading />;
|
|
200
199
|
if (settings.status !== "ready") {
|
|
201
200
|
return (
|
|
202
201
|
<SettingsSection title="Context Mode settings">
|
|
203
|
-
<
|
|
204
|
-
{settings.error}
|
|
205
|
-
</Text>
|
|
202
|
+
<ErrorText theme={theme} error={settings.error} />
|
|
206
203
|
<SettingsCard>
|
|
207
204
|
<SettingsAction
|
|
208
205
|
label="Read settings again"
|