@circuitwall/jarela 1.9.0 → 1.9.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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +2 -2
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page.js +30057 -29350
- package/.next/standalone/.next/server/app/page.js.map +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +2 -2
- package/.next/standalone/.next/server/chunks/8954.js +35 -0
- package/.next/standalone/.next/server/chunks/8954.js.map +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/{9476-ea25d4553680515b.js → 1998-31a617131197a83a.js} +53 -2
- package/.next/standalone/.next/static/chunks/1998-31a617131197a83a.js.map +1 -0
- package/.next/standalone/.next/static/chunks/{2747-d06d5caf7bca15e5.js → 2747-4a6287cacd57d231.js} +36 -1
- package/.next/standalone/.next/static/chunks/2747-4a6287cacd57d231.js.map +1 -0
- package/.next/standalone/.next/static/chunks/app/{page-a9dcc924ad127090.js → page-cd662565eba5ef59.js} +675 -4
- package/.next/standalone/.next/static/chunks/app/page-cd662565eba5ef59.js.map +1 -0
- package/.next/standalone/.next/static/css/11aaed27d2989cc1.css +5 -0
- package/.next/standalone/.next/static/css/11aaed27d2989cc1.css.map +1 -0
- package/.next/standalone/package.json +1 -1
- package/CHANGELOG.md +24 -0
- package/README.md +6 -0
- package/api/client.ts +57 -0
- package/api/types.ts +81 -1
- package/components/tools/LangChainPackagesPanel.tsx +469 -0
- package/components/tools/ToolsPanel.tsx +21 -2
- package/hooks/usePackages.ts +111 -0
- package/package.json +1 -1
- package/.next/standalone/.next/static/chunks/2747-d06d5caf7bca15e5.js.map +0 -1
- package/.next/standalone/.next/static/chunks/9476-ea25d4553680515b.js.map +0 -1
- package/.next/standalone/.next/static/chunks/app/page-a9dcc924ad127090.js.map +0 -1
- package/.next/standalone/.next/static/css/fb519215ea1c4fd8.css +0 -5
- package/.next/standalone/.next/static/css/fb519215ea1c4fd8.css.map +0 -1
- /package/.next/standalone/.next/static/{TE5LAkHzp2Kipn0ypeCyH → tTk-KuLcT7O-E0z6PdMmO}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{TE5LAkHzp2Kipn0ypeCyH → tTk-KuLcT7O-E0z6PdMmO}/_ssgManifest.js +0 -0
package/api/types.ts
CHANGED
|
@@ -1159,4 +1159,84 @@ export interface ExtensionsListResponse {
|
|
|
1159
1159
|
providers: ExtensionInfo[];
|
|
1160
1160
|
tools: ExternalToolInfo[];
|
|
1161
1161
|
errors: ExtensionLoadError[];
|
|
1162
|
-
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Hot-loaded vanilla LangChain tool packages. Surface for the
|
|
1166
|
+
* Tools → LangChain packages UI panel.
|
|
1167
|
+
*/
|
|
1168
|
+
export interface LangChainPackageManifest {
|
|
1169
|
+
package: string;
|
|
1170
|
+
export: string;
|
|
1171
|
+
category: string;
|
|
1172
|
+
capability: "read" | "write" | "execute";
|
|
1173
|
+
args?: Record<string, unknown>;
|
|
1174
|
+
requiredEnv?: string[];
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
export interface LangChainPackageManifestRecord {
|
|
1178
|
+
name: string;
|
|
1179
|
+
manifest: LangChainPackageManifest;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
export interface LangChainPackageLoadResult {
|
|
1183
|
+
registered: string[];
|
|
1184
|
+
skipped: { manifest: string; reason: string }[];
|
|
1185
|
+
errors: { manifest: string; error: string }[];
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
export interface LangChainPackageListResponse extends LangChainPackageLoadResult {
|
|
1189
|
+
packagesDir: string;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
export interface LangChainPackageManifestInput {
|
|
1193
|
+
name: string;
|
|
1194
|
+
package: string;
|
|
1195
|
+
export?: string;
|
|
1196
|
+
category: string;
|
|
1197
|
+
capability?: "read" | "write" | "execute";
|
|
1198
|
+
args?: Record<string, unknown>;
|
|
1199
|
+
requiredEnv?: string[];
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
export interface LangChainPackagePendingInstall {
|
|
1203
|
+
id: string;
|
|
1204
|
+
spec: string;
|
|
1205
|
+
version: string | null;
|
|
1206
|
+
publisher: string;
|
|
1207
|
+
reason: string;
|
|
1208
|
+
createdAt: string;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
export interface LangChainPackageIntrospectedTool {
|
|
1212
|
+
export: string;
|
|
1213
|
+
name: string;
|
|
1214
|
+
description: string;
|
|
1215
|
+
requiredEnv: string[];
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
export interface LangChainPackageInstallResult {
|
|
1219
|
+
status: "installed";
|
|
1220
|
+
spec: string;
|
|
1221
|
+
publisher: string;
|
|
1222
|
+
resolvedPackage: string;
|
|
1223
|
+
installedVersion: string | null;
|
|
1224
|
+
tools: LangChainPackageIntrospectedTool[];
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
export interface LangChainPackagePendingResponse {
|
|
1228
|
+
status: "pending";
|
|
1229
|
+
approvalId: string;
|
|
1230
|
+
publisher: string;
|
|
1231
|
+
spec: string;
|
|
1232
|
+
reason: string;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
export type LangChainPackageInstallResponse =
|
|
1236
|
+
| LangChainPackageInstallResult
|
|
1237
|
+
| LangChainPackagePendingResponse;
|
|
1238
|
+
|
|
1239
|
+
export interface LangChainPackageManifestCreateResult {
|
|
1240
|
+
record: LangChainPackageManifestRecord;
|
|
1241
|
+
load: LangChainPackageLoadResult;
|
|
1242
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { RefreshCw, Trash2, Check, X, Package } from "lucide-react";
|
|
5
|
+
import { usePackages } from "@/hooks/usePackages";
|
|
6
|
+
import { pushErrorToast } from "@/lib/ui/error-report";
|
|
7
|
+
import type {
|
|
8
|
+
LangChainPackageInstallResponse,
|
|
9
|
+
LangChainPackageManifestRecord,
|
|
10
|
+
} from "@/api/types";
|
|
11
|
+
|
|
12
|
+
const CATEGORIES = [
|
|
13
|
+
"Memory", "Documents", "Files", "Shell", "Web", "Images", "Voice",
|
|
14
|
+
"Schedule", "Atlassian", "JiraAlign", "GitHub", "Mail", "Calendar",
|
|
15
|
+
"Config", "Agent",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
const CAPABILITIES = ["read", "write", "execute"] as const;
|
|
19
|
+
|
|
20
|
+
interface ManifestFormState {
|
|
21
|
+
name: string;
|
|
22
|
+
package: string;
|
|
23
|
+
exportName: string;
|
|
24
|
+
category: (typeof CATEGORIES)[number];
|
|
25
|
+
capability: (typeof CAPABILITIES)[number];
|
|
26
|
+
requiredEnv: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const EMPTY_FORM: ManifestFormState = {
|
|
30
|
+
name: "",
|
|
31
|
+
package: "",
|
|
32
|
+
exportName: "",
|
|
33
|
+
category: "Web",
|
|
34
|
+
capability: "execute",
|
|
35
|
+
requiredEnv: "",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function LangChainPackagesPanel() {
|
|
39
|
+
const {
|
|
40
|
+
loadResult,
|
|
41
|
+
manifests,
|
|
42
|
+
pending,
|
|
43
|
+
loading,
|
|
44
|
+
refresh,
|
|
45
|
+
install,
|
|
46
|
+
approveInstall,
|
|
47
|
+
denyInstall,
|
|
48
|
+
createManifest,
|
|
49
|
+
deleteManifest,
|
|
50
|
+
reload,
|
|
51
|
+
} = usePackages();
|
|
52
|
+
|
|
53
|
+
const [installSpec, setInstallSpec] = useState("");
|
|
54
|
+
const [installVersion, setInstallVersion] = useState("");
|
|
55
|
+
const [installing, setInstalling] = useState(false);
|
|
56
|
+
const [installNotice, setInstallNotice] = useState<string | null>(null);
|
|
57
|
+
|
|
58
|
+
const [form, setForm] = useState<ManifestFormState>(EMPTY_FORM);
|
|
59
|
+
const [savingManifest, setSavingManifest] = useState(false);
|
|
60
|
+
|
|
61
|
+
const errorRows = useMemo(() => loadResult?.errors ?? [], [loadResult]);
|
|
62
|
+
const skippedRows = useMemo(() => loadResult?.skipped ?? [], [loadResult]);
|
|
63
|
+
|
|
64
|
+
async function handleInstall(e: React.FormEvent) {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
if (!installSpec.trim()) return;
|
|
67
|
+
setInstalling(true);
|
|
68
|
+
setInstallNotice(null);
|
|
69
|
+
try {
|
|
70
|
+
const res: LangChainPackageInstallResponse = await install(
|
|
71
|
+
installSpec.trim(),
|
|
72
|
+
installVersion.trim() || undefined,
|
|
73
|
+
);
|
|
74
|
+
if (res.status === "pending") {
|
|
75
|
+
setInstallNotice(
|
|
76
|
+
`Publisher "${res.publisher}" not in allowlist — pending approval below.`,
|
|
77
|
+
);
|
|
78
|
+
} else {
|
|
79
|
+
setInstallNotice(
|
|
80
|
+
`Installed ${res.resolvedPackage}@${res.installedVersion ?? "?"} (${res.tools.length} tool${res.tools.length === 1 ? "" : "s"} found).`,
|
|
81
|
+
);
|
|
82
|
+
setInstallSpec("");
|
|
83
|
+
setInstallVersion("");
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
pushErrorToast({
|
|
87
|
+
title: "Install failed",
|
|
88
|
+
error: e,
|
|
89
|
+
context: { panel: "packages", action: "install", spec: installSpec },
|
|
90
|
+
});
|
|
91
|
+
} finally {
|
|
92
|
+
setInstalling(false);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function handleApprove(id: string) {
|
|
97
|
+
try {
|
|
98
|
+
await approveInstall(id);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
pushErrorToast({
|
|
101
|
+
title: "Approval failed",
|
|
102
|
+
error: e,
|
|
103
|
+
context: { panel: "packages", action: "approve", id },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function handleDeny(id: string) {
|
|
109
|
+
try {
|
|
110
|
+
await denyInstall(id);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
pushErrorToast({
|
|
113
|
+
title: "Deny failed",
|
|
114
|
+
error: e,
|
|
115
|
+
context: { panel: "packages", action: "deny", id },
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function handleCreateManifest(e: React.FormEvent) {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
if (!form.name.trim() || !form.package.trim()) return;
|
|
123
|
+
setSavingManifest(true);
|
|
124
|
+
try {
|
|
125
|
+
const requiredEnv = form.requiredEnv
|
|
126
|
+
.split(",")
|
|
127
|
+
.map((s) => s.trim())
|
|
128
|
+
.filter(Boolean);
|
|
129
|
+
await createManifest({
|
|
130
|
+
name: form.name.trim(),
|
|
131
|
+
package: form.package.trim(),
|
|
132
|
+
export: form.exportName.trim() || undefined,
|
|
133
|
+
category: form.category,
|
|
134
|
+
capability: form.capability,
|
|
135
|
+
requiredEnv: requiredEnv.length > 0 ? requiredEnv : undefined,
|
|
136
|
+
});
|
|
137
|
+
setForm(EMPTY_FORM);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
pushErrorToast({
|
|
140
|
+
title: "Couldn't create manifest",
|
|
141
|
+
error: e,
|
|
142
|
+
context: { panel: "packages", action: "create-manifest", name: form.name },
|
|
143
|
+
});
|
|
144
|
+
} finally {
|
|
145
|
+
setSavingManifest(false);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function handleDeleteManifest(record: LangChainPackageManifestRecord) {
|
|
150
|
+
if (typeof window !== "undefined") {
|
|
151
|
+
if (!window.confirm(`Delete manifest "${record.name}"?`)) return;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
await deleteManifest(record.name);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
pushErrorToast({
|
|
157
|
+
title: "Couldn't delete manifest",
|
|
158
|
+
error: e,
|
|
159
|
+
context: { panel: "packages", action: "delete-manifest", name: record.name },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function handleReload() {
|
|
165
|
+
try {
|
|
166
|
+
await reload();
|
|
167
|
+
} catch (e) {
|
|
168
|
+
pushErrorToast({
|
|
169
|
+
title: "Reload failed",
|
|
170
|
+
error: e,
|
|
171
|
+
context: { panel: "packages", action: "reload" },
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div className="p-4 space-y-5">
|
|
178
|
+
<header className="space-y-1">
|
|
179
|
+
<h2 className="text-sm font-semibold text-fg">LangChain packages</h2>
|
|
180
|
+
<p className="text-xs text-fg-faint">
|
|
181
|
+
Hot-load vanilla LangChain tool packages from npm without a rebuild. Trusted
|
|
182
|
+
publishers (<code className="font-mono text-[11px]">@langchain/</code>,{" "}
|
|
183
|
+
<code className="font-mono text-[11px]">@circuitwall/</code>,{" "}
|
|
184
|
+
<code className="font-mono text-[11px]">langchain</code>) install
|
|
185
|
+
immediately; everything else waits for approval below.
|
|
186
|
+
</p>
|
|
187
|
+
{loadResult?.packagesDir && (
|
|
188
|
+
<p className="text-[11px] text-fg-faint font-mono">
|
|
189
|
+
{loadResult.packagesDir}
|
|
190
|
+
</p>
|
|
191
|
+
)}
|
|
192
|
+
</header>
|
|
193
|
+
|
|
194
|
+
<section className="space-y-2">
|
|
195
|
+
<div className="flex items-center gap-2">
|
|
196
|
+
<h3 className="text-sm font-semibold text-fg flex-1">Install from npm</h3>
|
|
197
|
+
<button
|
|
198
|
+
type="button"
|
|
199
|
+
onClick={handleReload}
|
|
200
|
+
className="shrink-0 rounded-md border border-border px-2.5 py-1.5 text-xs text-fg-muted hover:text-fg hover:bg-surface-2 inline-flex items-center gap-1.5"
|
|
201
|
+
aria-label="Reload installed packages"
|
|
202
|
+
>
|
|
203
|
+
<RefreshCw className="w-3.5 h-3.5" /> Reload
|
|
204
|
+
</button>
|
|
205
|
+
</div>
|
|
206
|
+
<form
|
|
207
|
+
onSubmit={handleInstall}
|
|
208
|
+
className="grid gap-2 md:grid-cols-[minmax(0,1fr)_140px_auto]"
|
|
209
|
+
>
|
|
210
|
+
<input
|
|
211
|
+
type="text"
|
|
212
|
+
value={installSpec}
|
|
213
|
+
onChange={(e) => setInstallSpec(e.target.value)}
|
|
214
|
+
placeholder="@langchain/community"
|
|
215
|
+
className="rounded-md border border-border bg-surface-2 px-3 py-2 text-sm text-fg outline-none focus:border-fg-faint"
|
|
216
|
+
aria-label="Package spec"
|
|
217
|
+
/>
|
|
218
|
+
<input
|
|
219
|
+
type="text"
|
|
220
|
+
value={installVersion}
|
|
221
|
+
onChange={(e) => setInstallVersion(e.target.value)}
|
|
222
|
+
placeholder="version (optional)"
|
|
223
|
+
className="rounded-md border border-border bg-surface-2 px-3 py-2 text-sm text-fg outline-none focus:border-fg-faint"
|
|
224
|
+
aria-label="Version"
|
|
225
|
+
/>
|
|
226
|
+
<button
|
|
227
|
+
type="submit"
|
|
228
|
+
disabled={installing || !installSpec.trim()}
|
|
229
|
+
className="rounded-md border border-border bg-surface-2 px-3 py-2 text-sm text-fg hover:bg-surface-3 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
230
|
+
>
|
|
231
|
+
{installing ? "Installing…" : "Install"}
|
|
232
|
+
</button>
|
|
233
|
+
</form>
|
|
234
|
+
{installNotice && (
|
|
235
|
+
<p className="text-xs text-fg-muted">{installNotice}</p>
|
|
236
|
+
)}
|
|
237
|
+
</section>
|
|
238
|
+
|
|
239
|
+
{pending.length > 0 && (
|
|
240
|
+
<section className="space-y-2">
|
|
241
|
+
<h3 className="text-sm font-semibold text-fg">
|
|
242
|
+
Pending approvals ({pending.length})
|
|
243
|
+
</h3>
|
|
244
|
+
<ul className="space-y-2">
|
|
245
|
+
{pending.map((p) => (
|
|
246
|
+
<li
|
|
247
|
+
key={p.id}
|
|
248
|
+
className="rounded-lg border border-border bg-surface-2 px-3 py-2.5"
|
|
249
|
+
>
|
|
250
|
+
<div className="flex items-start gap-3">
|
|
251
|
+
<div className="flex-1 min-w-0">
|
|
252
|
+
<div className="text-sm font-mono text-fg">
|
|
253
|
+
{p.spec}
|
|
254
|
+
{p.version ? `@${p.version}` : ""}
|
|
255
|
+
</div>
|
|
256
|
+
<p className="text-xs text-fg-muted mt-0.5">
|
|
257
|
+
Publisher{" "}
|
|
258
|
+
<span className="font-mono">{p.publisher}</span> · {p.reason}
|
|
259
|
+
</p>
|
|
260
|
+
</div>
|
|
261
|
+
<div className="flex gap-1.5 shrink-0">
|
|
262
|
+
<button
|
|
263
|
+
type="button"
|
|
264
|
+
onClick={() => handleApprove(p.id)}
|
|
265
|
+
className="rounded-md border border-border px-2 py-1.5 text-xs text-fg-muted hover:text-fg hover:bg-surface-3 inline-flex items-center gap-1"
|
|
266
|
+
aria-label={`Approve ${p.spec}`}
|
|
267
|
+
>
|
|
268
|
+
<Check className="w-3.5 h-3.5" /> Approve
|
|
269
|
+
</button>
|
|
270
|
+
<button
|
|
271
|
+
type="button"
|
|
272
|
+
onClick={() => handleDeny(p.id)}
|
|
273
|
+
className="rounded-md border border-border px-2 py-1.5 text-xs text-fg-muted hover:text-fg hover:bg-surface-3 inline-flex items-center gap-1"
|
|
274
|
+
aria-label={`Deny ${p.spec}`}
|
|
275
|
+
>
|
|
276
|
+
<X className="w-3.5 h-3.5" /> Deny
|
|
277
|
+
</button>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</li>
|
|
281
|
+
))}
|
|
282
|
+
</ul>
|
|
283
|
+
</section>
|
|
284
|
+
)}
|
|
285
|
+
|
|
286
|
+
<section className="space-y-2 pt-3 border-t border-border">
|
|
287
|
+
<h3 className="text-sm font-semibold text-fg">Tool manifests</h3>
|
|
288
|
+
<p className="text-xs text-fg-faint">
|
|
289
|
+
A manifest maps an exported class from an installed npm package to a
|
|
290
|
+
built-in category. Surfaced under{" "}
|
|
291
|
+
<code className="font-mono text-[11px]">/api/v1/tools</code> after a
|
|
292
|
+
reload.
|
|
293
|
+
</p>
|
|
294
|
+
|
|
295
|
+
{loading && (
|
|
296
|
+
<p className="text-fg-faint text-sm py-3 text-center">Loading…</p>
|
|
297
|
+
)}
|
|
298
|
+
|
|
299
|
+
{!loading && manifests.length === 0 && (
|
|
300
|
+
<p className="text-xs text-fg-faint py-2">
|
|
301
|
+
No manifests yet. Add one below.
|
|
302
|
+
</p>
|
|
303
|
+
)}
|
|
304
|
+
|
|
305
|
+
<ul className="space-y-2">
|
|
306
|
+
{manifests.map((r) => (
|
|
307
|
+
<li
|
|
308
|
+
key={r.name}
|
|
309
|
+
className="rounded-lg border border-border bg-surface-2 px-3 py-2.5"
|
|
310
|
+
>
|
|
311
|
+
<div className="flex items-start gap-3">
|
|
312
|
+
<Package className="w-4 h-4 text-fg-faint mt-0.5 shrink-0" />
|
|
313
|
+
<div className="flex-1 min-w-0">
|
|
314
|
+
<div className="flex items-baseline gap-2 flex-wrap">
|
|
315
|
+
<span className="text-sm font-semibold text-fg">{r.name}</span>
|
|
316
|
+
<span className="text-[11px] text-fg-faint">
|
|
317
|
+
{r.manifest.category} · {r.manifest.capability}
|
|
318
|
+
</span>
|
|
319
|
+
</div>
|
|
320
|
+
<p className="text-xs text-fg-muted mt-0.5 font-mono break-all">
|
|
321
|
+
{r.manifest.package}#{r.manifest.export}
|
|
322
|
+
</p>
|
|
323
|
+
{r.manifest.requiredEnv && r.manifest.requiredEnv.length > 0 && (
|
|
324
|
+
<p className="text-[11px] text-fg-faint mt-1">
|
|
325
|
+
env: {r.manifest.requiredEnv.join(", ")}
|
|
326
|
+
</p>
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
<button
|
|
330
|
+
type="button"
|
|
331
|
+
onClick={() => handleDeleteManifest(r)}
|
|
332
|
+
className="rounded-md border border-border px-2 py-1.5 text-xs text-fg-muted hover:text-fg hover:bg-surface-3 shrink-0"
|
|
333
|
+
aria-label={`Delete manifest ${r.name}`}
|
|
334
|
+
>
|
|
335
|
+
<Trash2 className="w-3.5 h-3.5" />
|
|
336
|
+
</button>
|
|
337
|
+
</div>
|
|
338
|
+
</li>
|
|
339
|
+
))}
|
|
340
|
+
</ul>
|
|
341
|
+
|
|
342
|
+
<form
|
|
343
|
+
onSubmit={handleCreateManifest}
|
|
344
|
+
className="rounded-lg border border-border bg-surface-2 p-3 space-y-2"
|
|
345
|
+
>
|
|
346
|
+
<h4 className="text-xs font-semibold text-fg-muted uppercase tracking-wide">
|
|
347
|
+
Add manifest
|
|
348
|
+
</h4>
|
|
349
|
+
<div className="grid gap-2 md:grid-cols-2">
|
|
350
|
+
<input
|
|
351
|
+
type="text"
|
|
352
|
+
value={form.name}
|
|
353
|
+
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
|
354
|
+
placeholder="name (e.g. tavily)"
|
|
355
|
+
className="rounded-md border border-border bg-surface-2 px-3 py-2 text-sm text-fg outline-none focus:border-fg-faint"
|
|
356
|
+
aria-label="Manifest name"
|
|
357
|
+
/>
|
|
358
|
+
<input
|
|
359
|
+
type="text"
|
|
360
|
+
value={form.package}
|
|
361
|
+
onChange={(e) => setForm({ ...form, package: e.target.value })}
|
|
362
|
+
placeholder="package (e.g. @langchain/community/tools/tavily_search)"
|
|
363
|
+
className="rounded-md border border-border bg-surface-2 px-3 py-2 text-sm text-fg outline-none focus:border-fg-faint"
|
|
364
|
+
aria-label="Package import path"
|
|
365
|
+
/>
|
|
366
|
+
<input
|
|
367
|
+
type="text"
|
|
368
|
+
value={form.exportName}
|
|
369
|
+
onChange={(e) => setForm({ ...form, exportName: e.target.value })}
|
|
370
|
+
placeholder="export (default: default)"
|
|
371
|
+
className="rounded-md border border-border bg-surface-2 px-3 py-2 text-sm text-fg outline-none focus:border-fg-faint"
|
|
372
|
+
aria-label="Export name"
|
|
373
|
+
/>
|
|
374
|
+
<input
|
|
375
|
+
type="text"
|
|
376
|
+
value={form.requiredEnv}
|
|
377
|
+
onChange={(e) => setForm({ ...form, requiredEnv: e.target.value })}
|
|
378
|
+
placeholder="required env, comma-separated"
|
|
379
|
+
className="rounded-md border border-border bg-surface-2 px-3 py-2 text-sm text-fg outline-none focus:border-fg-faint"
|
|
380
|
+
aria-label="Required env vars"
|
|
381
|
+
/>
|
|
382
|
+
<select
|
|
383
|
+
value={form.category}
|
|
384
|
+
onChange={(e) =>
|
|
385
|
+
setForm({
|
|
386
|
+
...form,
|
|
387
|
+
category: e.target.value as (typeof CATEGORIES)[number],
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
className="rounded-md border border-border bg-surface-2 px-3 py-2 text-sm text-fg outline-none focus:border-fg-faint"
|
|
391
|
+
aria-label="Category"
|
|
392
|
+
>
|
|
393
|
+
{CATEGORIES.map((c) => (
|
|
394
|
+
<option key={c} value={c}>
|
|
395
|
+
{c}
|
|
396
|
+
</option>
|
|
397
|
+
))}
|
|
398
|
+
</select>
|
|
399
|
+
<select
|
|
400
|
+
value={form.capability}
|
|
401
|
+
onChange={(e) =>
|
|
402
|
+
setForm({
|
|
403
|
+
...form,
|
|
404
|
+
capability: e.target.value as (typeof CAPABILITIES)[number],
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
className="rounded-md border border-border bg-surface-2 px-3 py-2 text-sm text-fg outline-none focus:border-fg-faint"
|
|
408
|
+
aria-label="Capability"
|
|
409
|
+
>
|
|
410
|
+
{CAPABILITIES.map((c) => (
|
|
411
|
+
<option key={c} value={c}>
|
|
412
|
+
{c}
|
|
413
|
+
</option>
|
|
414
|
+
))}
|
|
415
|
+
</select>
|
|
416
|
+
</div>
|
|
417
|
+
<div className="flex justify-end">
|
|
418
|
+
<button
|
|
419
|
+
type="submit"
|
|
420
|
+
disabled={savingManifest || !form.name.trim() || !form.package.trim()}
|
|
421
|
+
className="rounded-md border border-border bg-surface-2 px-3 py-1.5 text-xs text-fg hover:bg-surface-3 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
422
|
+
>
|
|
423
|
+
{savingManifest ? "Saving…" : "Add manifest"}
|
|
424
|
+
</button>
|
|
425
|
+
</div>
|
|
426
|
+
</form>
|
|
427
|
+
</section>
|
|
428
|
+
|
|
429
|
+
{(errorRows.length > 0 || skippedRows.length > 0) && (
|
|
430
|
+
<section className="space-y-2 pt-3 border-t border-border">
|
|
431
|
+
<div className="flex items-center gap-2">
|
|
432
|
+
<h3 className="text-sm font-semibold text-fg flex-1">Load status</h3>
|
|
433
|
+
<button
|
|
434
|
+
type="button"
|
|
435
|
+
onClick={refresh}
|
|
436
|
+
className="shrink-0 rounded-md border border-border px-2.5 py-1.5 text-xs text-fg-muted hover:text-fg hover:bg-surface-2"
|
|
437
|
+
>
|
|
438
|
+
Refresh
|
|
439
|
+
</button>
|
|
440
|
+
</div>
|
|
441
|
+
{errorRows.length > 0 && (
|
|
442
|
+
<ul className="space-y-1">
|
|
443
|
+
{errorRows.map((e) => (
|
|
444
|
+
<li
|
|
445
|
+
key={e.manifest}
|
|
446
|
+
className="rounded border border-red-500/40 bg-red-500/10 px-2 py-1.5 text-xs text-fg"
|
|
447
|
+
>
|
|
448
|
+
<span className="font-mono">{e.manifest}</span>: {e.error}
|
|
449
|
+
</li>
|
|
450
|
+
))}
|
|
451
|
+
</ul>
|
|
452
|
+
)}
|
|
453
|
+
{skippedRows.length > 0 && (
|
|
454
|
+
<ul className="space-y-1">
|
|
455
|
+
{skippedRows.map((s) => (
|
|
456
|
+
<li
|
|
457
|
+
key={s.manifest}
|
|
458
|
+
className="rounded border border-border bg-surface-2 px-2 py-1.5 text-xs text-fg-muted"
|
|
459
|
+
>
|
|
460
|
+
<span className="font-mono">{s.manifest}</span> skipped — {s.reason}
|
|
461
|
+
</li>
|
|
462
|
+
))}
|
|
463
|
+
</ul>
|
|
464
|
+
)}
|
|
465
|
+
</section>
|
|
466
|
+
)}
|
|
467
|
+
</div>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
@@ -6,10 +6,12 @@ import { DocumentsPanel } from "@/components/documents/DocumentsPanel";
|
|
|
6
6
|
import { MemoryPanel } from "@/components/memory/MemoryPanel";
|
|
7
7
|
import { BridgesPanel } from "@/components/bridges/BridgesPanel";
|
|
8
8
|
import { BuiltinToolsPanel } from "./BuiltinToolsPanel";
|
|
9
|
+
import { LangChainPackagesPanel } from "./LangChainPackagesPanel";
|
|
9
10
|
|
|
10
11
|
// "Tools" is about *capability presence* — every surface that determines
|
|
11
12
|
// what an agent can sense or act on lives here:
|
|
12
13
|
// - "Built-in" — categories of tools that ship with Jarela.
|
|
14
|
+
// - "Packages" — vanilla LangChain tool packages hot-loaded from npm.
|
|
13
15
|
// - "Documents" — indexed knowledge sources the agent can search.
|
|
14
16
|
// - "Memory" — long-lived facts persisted across conversations.
|
|
15
17
|
// - "MCP" — external Model Context Protocol servers.
|
|
@@ -18,12 +20,28 @@ import { BuiltinToolsPanel } from "./BuiltinToolsPanel";
|
|
|
18
20
|
// Credentials still flow through the Credentials tab; this surface owns
|
|
19
21
|
// the capability roster.
|
|
20
22
|
|
|
21
|
-
type Sub =
|
|
23
|
+
type Sub =
|
|
24
|
+
| "builtin"
|
|
25
|
+
| "packages"
|
|
26
|
+
| "documents"
|
|
27
|
+
| "memory"
|
|
28
|
+
| "mcp"
|
|
29
|
+
| "extensions"
|
|
30
|
+
| "bridges";
|
|
22
31
|
|
|
23
|
-
const SUBS: Sub[] = [
|
|
32
|
+
const SUBS: Sub[] = [
|
|
33
|
+
"builtin",
|
|
34
|
+
"packages",
|
|
35
|
+
"documents",
|
|
36
|
+
"memory",
|
|
37
|
+
"mcp",
|
|
38
|
+
"extensions",
|
|
39
|
+
"bridges",
|
|
40
|
+
];
|
|
24
41
|
|
|
25
42
|
const SUB_TITLES: Record<Sub, string> = {
|
|
26
43
|
builtin: "Built-in",
|
|
44
|
+
packages: "LangChain packages",
|
|
27
45
|
documents: "Documents",
|
|
28
46
|
memory: "Memory",
|
|
29
47
|
mcp: "MCP servers",
|
|
@@ -70,6 +88,7 @@ export function ToolsPanel() {
|
|
|
70
88
|
</div>
|
|
71
89
|
<div className="flex-1 min-h-0 overflow-auto">
|
|
72
90
|
{active === "builtin" && <BuiltinToolsPanel />}
|
|
91
|
+
{active === "packages" && <LangChainPackagesPanel />}
|
|
73
92
|
{active === "documents" && <DocumentsPanel />}
|
|
74
93
|
{active === "memory" && <MemoryPanel />}
|
|
75
94
|
{active === "mcp" && <MCPPanel />}
|