@getcatalystiq/agent-plane-ui 0.1.4 → 0.1.6

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/dist/index.cjs CHANGED
@@ -3099,129 +3099,389 @@ function AgentConnectorsManager({ agentId, toolkits: initialToolkits, composioAl
3099
3099
  )
3100
3100
  ] });
3101
3101
  }
3102
- function AgentSkillManager({ agentId, initialSkills, onSaved }) {
3103
- const client = useAgentPlaneClient();
3104
- const initialFiles = React3.useMemo(
3105
- () => initialSkills.flatMap(
3106
- (s) => s.files.map((f) => ({
3107
- path: s.folder === "(root)" ? f.path : `${s.folder}/${f.path}`,
3108
- content: f.content
3109
- }))
3110
- ),
3111
- [initialSkills]
3112
- );
3102
+ var CodeEditor = React3.lazy(() => import('./code-editor-ZD5ZILTM.cjs'));
3103
+ function buildTree(files) {
3104
+ const rootFiles = [];
3105
+ const dirMap = /* @__PURE__ */ new Map();
3106
+ function ensureDir(dirPath) {
3107
+ const existing = dirMap.get(dirPath);
3108
+ if (existing) return existing;
3109
+ const parts = dirPath.split("/");
3110
+ const node = {
3111
+ name: parts[parts.length - 1],
3112
+ fullPath: dirPath,
3113
+ children: [],
3114
+ files: []
3115
+ };
3116
+ dirMap.set(dirPath, node);
3117
+ if (parts.length > 1) {
3118
+ const parentPath = parts.slice(0, -1).join("/");
3119
+ const parent = ensureDir(parentPath);
3120
+ if (!parent.children.some((c) => c.fullPath === dirPath)) {
3121
+ parent.children.push(node);
3122
+ }
3123
+ }
3124
+ return node;
3125
+ }
3126
+ for (const file of files) {
3127
+ const slashIdx = file.path.lastIndexOf("/");
3128
+ if (slashIdx === -1) {
3129
+ rootFiles.push(file);
3130
+ } else {
3131
+ const dirPath = file.path.slice(0, slashIdx);
3132
+ const dir = ensureDir(dirPath);
3133
+ dir.files.push(file);
3134
+ }
3135
+ }
3136
+ const topLevel = [];
3137
+ for (const node of dirMap.values()) {
3138
+ if (!node.fullPath.includes("/")) {
3139
+ topLevel.push(node);
3140
+ }
3141
+ }
3142
+ function sortNode(node) {
3143
+ node.children.sort((a, b) => a.name.localeCompare(b.name));
3144
+ node.files.sort((a, b) => a.path.localeCompare(b.path));
3145
+ node.children.forEach(sortNode);
3146
+ }
3147
+ topLevel.forEach(sortNode);
3148
+ topLevel.sort((a, b) => a.name.localeCompare(b.name));
3149
+ rootFiles.sort((a, b) => a.path.localeCompare(b.path));
3150
+ return { rootFiles, rootDirs: topLevel };
3151
+ }
3152
+ function collectAllDirPaths(nodes) {
3153
+ const paths = /* @__PURE__ */ new Set();
3154
+ function walk(node) {
3155
+ paths.add(node.fullPath);
3156
+ node.children.forEach(walk);
3157
+ }
3158
+ nodes.forEach(walk);
3159
+ return paths;
3160
+ }
3161
+ function FileTreeEditor({
3162
+ initialFiles,
3163
+ onSave,
3164
+ onChange,
3165
+ readOnly = false,
3166
+ hideSave = false,
3167
+ title = "Files",
3168
+ saveLabel = "Save",
3169
+ addFolderLabel = "Folder",
3170
+ newFileTemplate = { filename: "SKILL.md", content: "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n" },
3171
+ savedVersion
3172
+ }) {
3113
3173
  const [files, setFiles] = React3.useState(initialFiles);
3114
- const [selectedPath, setSelectedPath] = React3.useState(files[0]?.path ?? null);
3174
+ const [selectedPath, setSelectedPath] = React3.useState(
3175
+ initialFiles.length > 0 ? initialFiles[0].path : null
3176
+ );
3115
3177
  const [saving, setSaving] = React3.useState(false);
3116
- const [addingFile, setAddingFile] = React3.useState(false);
3178
+ const [expanded, setExpanded] = React3.useState(() => {
3179
+ const { rootDirs } = buildTree(initialFiles);
3180
+ return collectAllDirPaths(rootDirs);
3181
+ });
3182
+ const [showAddFolder, setShowAddFolder] = React3.useState(false);
3183
+ const [newFolderName, setNewFolderName] = React3.useState("");
3184
+ const [addingFileInDir, setAddingFileInDir] = React3.useState(null);
3117
3185
  const [newFileName, setNewFileName] = React3.useState("");
3186
+ const [savedSnapshot, setSavedSnapshot] = React3.useState(() => JSON.stringify(initialFiles));
3187
+ React3.useEffect(() => {
3188
+ const snap = JSON.stringify(initialFiles);
3189
+ setSavedSnapshot(snap);
3190
+ setFiles(initialFiles);
3191
+ const { rootDirs } = buildTree(initialFiles);
3192
+ setExpanded(collectAllDirPaths(rootDirs));
3193
+ }, [initialFiles]);
3194
+ React3.useEffect(() => {
3195
+ if (savedVersion !== void 0 && savedVersion > 0) {
3196
+ setSavedSnapshot(JSON.stringify(files));
3197
+ }
3198
+ }, [savedVersion]);
3199
+ const onChangeRef = React3.useRef(onChange);
3200
+ onChangeRef.current = onChange;
3201
+ React3.useEffect(() => {
3202
+ if (onChangeRef.current && JSON.stringify(files) !== savedSnapshot) {
3203
+ onChangeRef.current(files);
3204
+ }
3205
+ }, [files, savedSnapshot]);
3118
3206
  const isDirty = React3.useMemo(
3119
- () => JSON.stringify(files) !== JSON.stringify(initialFiles),
3120
- [files, initialFiles]
3207
+ () => JSON.stringify(files) !== savedSnapshot,
3208
+ [files, savedSnapshot]
3209
+ );
3210
+ const tree = React3.useMemo(() => buildTree(files), [files]);
3211
+ const activeFile = React3.useMemo(
3212
+ () => selectedPath ? files.find((f) => f.path === selectedPath) ?? null : null,
3213
+ [files, selectedPath]
3121
3214
  );
3122
- const selectedFile = files.find((f) => f.path === selectedPath);
3123
- function updateFileContent(path, content) {
3124
- setFiles((prev) => prev.map((f) => f.path === path ? { ...f, content } : f));
3215
+ const handleEditorChange = React3.useCallback((value) => {
3216
+ if (readOnly || !selectedPath) return;
3217
+ setFiles((prev) => prev.map((f) => f.path === selectedPath ? { ...f, content: value } : f));
3218
+ }, [readOnly, selectedPath]);
3219
+ function toggleExpand(dirPath) {
3220
+ setExpanded((prev) => {
3221
+ const next = new Set(prev);
3222
+ if (next.has(dirPath)) next.delete(dirPath);
3223
+ else next.add(dirPath);
3224
+ return next;
3225
+ });
3226
+ }
3227
+ function addFolder() {
3228
+ const name = newFolderName.trim();
3229
+ if (!name) return;
3230
+ const filePath = `${name}/${newFileTemplate.filename}`;
3231
+ if (files.some((f) => f.path === filePath)) return;
3232
+ const content = newFileTemplate.content.replace("# New", `# ${name}`);
3233
+ setFiles((prev) => [...prev, { path: filePath, content }]);
3234
+ setSelectedPath(filePath);
3235
+ setExpanded((prev) => /* @__PURE__ */ new Set([...prev, name]));
3236
+ setNewFolderName("");
3237
+ setShowAddFolder(false);
3238
+ }
3239
+ function removeDir(dirPath) {
3240
+ const prefix = dirPath + "/";
3241
+ const affectedFiles = files.filter((f) => f.path.startsWith(prefix));
3242
+ if (affectedFiles.length === 0) return;
3243
+ if (!confirm(`Remove "${dirPath}" and all ${affectedFiles.length} file(s)?`)) return;
3244
+ setFiles((prev) => prev.filter((f) => !f.path.startsWith(prefix)));
3245
+ if (selectedPath && selectedPath.startsWith(prefix)) setSelectedPath(null);
3246
+ }
3247
+ function removeFile(filePath) {
3248
+ const fileName = filePath.split("/").pop() ?? filePath;
3249
+ if (!confirm(`Remove file "${fileName}"?`)) return;
3250
+ setFiles((prev) => prev.filter((f) => f.path !== filePath));
3251
+ if (selectedPath === filePath) setSelectedPath(null);
3125
3252
  }
3126
- function addFile() {
3253
+ function addFileInDir(dirPath) {
3127
3254
  const name = newFileName.trim();
3128
- if (!name || files.some((f) => f.path === name)) return;
3129
- const content = name.endsWith(".md") ? "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n" : "";
3130
- setFiles((prev) => [...prev, { path: name, content }]);
3131
- setSelectedPath(name);
3132
- setAddingFile(false);
3255
+ if (!name) return;
3256
+ const filePath = dirPath ? `${dirPath}/${name}` : name;
3257
+ if (files.some((f) => f.path === filePath)) return;
3258
+ setFiles((prev) => [...prev, { path: filePath, content: "" }]);
3259
+ setSelectedPath(filePath);
3133
3260
  setNewFileName("");
3261
+ setAddingFileInDir(null);
3134
3262
  }
3135
- function removeFile(path) {
3136
- setFiles((prev) => prev.filter((f) => f.path !== path));
3137
- if (selectedPath === path) {
3138
- setSelectedPath(files.find((f) => f.path !== path)?.path ?? null);
3139
- }
3140
- }
3141
- const handleSave = React3.useCallback(async () => {
3263
+ async function handleSave() {
3142
3264
  setSaving(true);
3143
3265
  try {
3144
- const folderMap = /* @__PURE__ */ new Map();
3145
- for (const file of files) {
3146
- const slashIdx = file.path.lastIndexOf("/");
3147
- if (slashIdx === -1) {
3148
- const existing = folderMap.get("(root)") ?? [];
3149
- existing.push({ path: file.path, content: file.content });
3150
- folderMap.set("(root)", existing);
3151
- } else {
3152
- const folder = file.path.slice(0, slashIdx);
3153
- const fileName = file.path.slice(slashIdx + 1);
3154
- const existing = folderMap.get(folder) ?? [];
3155
- existing.push({ path: fileName, content: file.content });
3156
- folderMap.set(folder, existing);
3157
- }
3158
- }
3159
- const skills = Array.from(folderMap.entries()).map(([folder, files2]) => ({ folder, files: files2 }));
3160
- await client.agents.update(agentId, { skills });
3161
- onSaved?.();
3266
+ await onSave(files);
3162
3267
  } finally {
3163
3268
  setSaving(false);
3164
3269
  }
3165
- }, [files, agentId, client, onSaved]);
3166
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-muted-foreground/25 p-5", children: [
3167
- /* @__PURE__ */ jsxRuntime.jsx(SectionHeader, { title: "Skills", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
3168
- isDirty && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "destructive", className: "text-xs", children: "Unsaved" }),
3169
- /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", variant: "outline", onClick: () => setAddingFile(true), children: "Add File" }),
3170
- /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", onClick: handleSave, disabled: saving || !isDirty, children: saving ? "Saving..." : "Save Skills" })
3171
- ] }) }),
3172
- addingFile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-3", children: [
3173
- /* @__PURE__ */ jsxRuntime.jsx(
3174
- Input,
3175
- {
3176
- value: newFileName,
3177
- onChange: (e) => setNewFileName(e.target.value),
3178
- placeholder: "folder/SKILL.md",
3179
- className: "max-w-xs text-sm",
3180
- onKeyDown: (e) => e.key === "Enter" && addFile()
3181
- }
3182
- ),
3183
- /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", onClick: addFile, children: "Add" }),
3184
- /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
3185
- setAddingFile(false);
3186
- setNewFileName("");
3187
- }, children: "Cancel" })
3188
- ] }),
3189
- files.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: 'No skills defined. Click "Add File" to create a skill.' }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 min-h-[300px]", children: [
3190
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-48 shrink-0 border-r border-border pr-3 space-y-1", children: files.map((f) => /* @__PURE__ */ jsxRuntime.jsxs(
3270
+ }
3271
+ function renderTreeNode(node, depth) {
3272
+ const isExpanded = expanded.has(node.fullPath);
3273
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3274
+ /* @__PURE__ */ jsxRuntime.jsxs(
3191
3275
  "div",
3192
3276
  {
3193
- className: `flex items-center justify-between group rounded px-2 py-1 text-xs cursor-pointer ${selectedPath === f.path ? "bg-accent text-accent-foreground" : "hover:bg-muted/50"}`,
3194
- onClick: () => setSelectedPath(f.path),
3277
+ className: "flex items-center justify-between cursor-pointer hover:bg-muted/50 py-1 pr-2",
3278
+ style: { paddingLeft: `${depth * 16 + 8}px` },
3279
+ onClick: () => toggleExpand(node.fullPath),
3195
3280
  children: [
3196
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: f.path }),
3197
- /* @__PURE__ */ jsxRuntime.jsx(
3281
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium text-xs truncate flex items-center gap-1", children: [
3282
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: isExpanded ? "\u25BE" : "\u25B8" }),
3283
+ node.name,
3284
+ "/"
3285
+ ] }),
3286
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
3198
3287
  "button",
3199
3288
  {
3200
- type: "button",
3201
3289
  onClick: (e) => {
3202
3290
  e.stopPropagation();
3203
- removeFile(f.path);
3291
+ removeDir(node.fullPath);
3204
3292
  },
3205
- className: "opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-destructive ml-1",
3293
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3206
3294
  children: "\xD7"
3207
3295
  }
3208
3296
  )
3209
3297
  ]
3210
- },
3211
- f.path
3212
- )) }),
3213
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: selectedFile ? /* @__PURE__ */ jsxRuntime.jsx(
3214
- Textarea,
3215
- {
3216
- value: selectedFile.content,
3217
- onChange: (e) => updateFileContent(selectedFile.path, e.target.value),
3218
- className: "h-full min-h-[300px] font-mono text-xs resize-y",
3219
- placeholder: "Enter skill content..."
3220
3298
  }
3221
- ) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Select a file to edit" }) })
3299
+ ),
3300
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3301
+ node.children.map((child) => renderTreeNode(child, depth + 1)),
3302
+ node.files.map((file) => {
3303
+ const fileName = file.path.split("/").pop() ?? file.path;
3304
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3305
+ "div",
3306
+ {
3307
+ className: `flex items-center justify-between cursor-pointer hover:bg-muted/30 py-1 pr-2 ${selectedPath === file.path ? "bg-primary/10 text-primary" : ""}`,
3308
+ style: { paddingLeft: `${(depth + 1) * 16 + 8}px` },
3309
+ onClick: () => setSelectedPath(file.path),
3310
+ children: [
3311
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs truncate", children: fileName }),
3312
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
3313
+ "button",
3314
+ {
3315
+ onClick: (e) => {
3316
+ e.stopPropagation();
3317
+ removeFile(file.path);
3318
+ },
3319
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3320
+ children: "\xD7"
3321
+ }
3322
+ )
3323
+ ]
3324
+ },
3325
+ file.path
3326
+ );
3327
+ }),
3328
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { paddingLeft: `${(depth + 1) * 16 + 8}px` }, className: "py-1 pr-2", children: addingFileInDir === node.fullPath ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1", children: [
3329
+ /* @__PURE__ */ jsxRuntime.jsx(
3330
+ Input,
3331
+ {
3332
+ value: newFileName,
3333
+ onChange: (e) => setNewFileName(e.target.value),
3334
+ placeholder: "file.md",
3335
+ className: "h-6 text-xs",
3336
+ onKeyDown: (e) => e.key === "Enter" && addFileInDir(node.fullPath),
3337
+ autoFocus: true
3338
+ }
3339
+ ),
3340
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: () => addFileInDir(node.fullPath), size: "sm", className: "h-6 text-xs px-2", children: "+" })
3341
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
3342
+ "button",
3343
+ {
3344
+ onClick: () => {
3345
+ setAddingFileInDir(node.fullPath);
3346
+ setNewFileName("");
3347
+ },
3348
+ className: "text-xs text-primary hover:underline",
3349
+ children: "+ File"
3350
+ }
3351
+ ) })
3352
+ ] })
3353
+ ] }, node.fullPath);
3354
+ }
3355
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3356
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
3357
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
3358
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold", children: title }),
3359
+ isDirty && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "destructive", className: "text-xs", children: "Unsaved changes" }),
3360
+ readOnly && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "secondary", className: "text-xs", children: "Read-only" })
3361
+ ] }),
3362
+ !readOnly && !hideSave && /* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: handleSave, disabled: saving || !isDirty, size: "sm", children: saving ? "Saving..." : saveLabel })
3363
+ ] }),
3364
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 min-h-[500px]", children: [
3365
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-64 shrink-0 border border-border rounded-md overflow-hidden", children: [
3366
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-2 bg-muted/50 border-b border-border flex items-center justify-between", children: [
3367
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground", children: title }),
3368
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsxs(
3369
+ "button",
3370
+ {
3371
+ onClick: () => setShowAddFolder(!showAddFolder),
3372
+ className: "text-xs text-primary hover:underline",
3373
+ children: [
3374
+ "+ ",
3375
+ addFolderLabel
3376
+ ]
3377
+ }
3378
+ )
3379
+ ] }),
3380
+ showAddFolder && !readOnly && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-2 border-b border-border flex gap-1", children: [
3381
+ /* @__PURE__ */ jsxRuntime.jsx(
3382
+ Input,
3383
+ {
3384
+ value: newFolderName,
3385
+ onChange: (e) => setNewFolderName(e.target.value),
3386
+ placeholder: "folder-name",
3387
+ className: "h-7 text-xs",
3388
+ onKeyDown: (e) => e.key === "Enter" && addFolder(),
3389
+ autoFocus: true
3390
+ }
3391
+ ),
3392
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: addFolder, size: "sm", className: "h-7 text-xs px-2", children: "Add" })
3393
+ ] }),
3394
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm overflow-y-auto", children: [
3395
+ tree.rootFiles.map((file) => /* @__PURE__ */ jsxRuntime.jsxs(
3396
+ "div",
3397
+ {
3398
+ className: `flex items-center justify-between cursor-pointer hover:bg-muted/30 py-1 pr-2 ${selectedPath === file.path ? "bg-primary/10 text-primary" : ""}`,
3399
+ style: { paddingLeft: "8px" },
3400
+ onClick: () => setSelectedPath(file.path),
3401
+ children: [
3402
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs truncate", children: file.path }),
3403
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
3404
+ "button",
3405
+ {
3406
+ onClick: (e) => {
3407
+ e.stopPropagation();
3408
+ removeFile(file.path);
3409
+ },
3410
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3411
+ children: "\xD7"
3412
+ }
3413
+ )
3414
+ ]
3415
+ },
3416
+ file.path
3417
+ )),
3418
+ tree.rootDirs.map((node) => renderTreeNode(node, 0)),
3419
+ files.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "p-3 text-xs text-muted-foreground", children: readOnly ? "No files." : `No ${title.toLowerCase()} yet. Add a ${addFolderLabel.toLowerCase()} to get started.` })
3420
+ ] })
3421
+ ] }),
3422
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 border border-border rounded-md overflow-hidden", children: activeFile ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-full flex flex-col", children: [
3423
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-1.5 bg-muted/50 border-b border-border text-xs text-muted-foreground", children: activeFile.path }),
3424
+ /* @__PURE__ */ jsxRuntime.jsx(React3.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 animate-pulse bg-muted/50" }), children: /* @__PURE__ */ jsxRuntime.jsx(
3425
+ CodeEditor,
3426
+ {
3427
+ value: activeFile.content,
3428
+ onChange: handleEditorChange,
3429
+ filename: activeFile.path
3430
+ }
3431
+ ) })
3432
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-full flex items-center justify-center text-muted-foreground text-sm", children: [
3433
+ "Select a file to ",
3434
+ readOnly ? "view" : "edit"
3435
+ ] }) })
3222
3436
  ] })
3223
3437
  ] });
3224
3438
  }
3439
+ function AgentSkillManager({ agentId, initialSkills, onSaved }) {
3440
+ const client = useAgentPlaneClient();
3441
+ const initialFiles = React3.useMemo(
3442
+ () => initialSkills.flatMap(
3443
+ (s) => s.files.map((f) => ({
3444
+ path: s.folder === "(root)" ? f.path : `${s.folder}/${f.path}`,
3445
+ content: f.content
3446
+ }))
3447
+ ),
3448
+ [initialSkills]
3449
+ );
3450
+ const handleSave = React3.useCallback(async (files) => {
3451
+ const folderMap = /* @__PURE__ */ new Map();
3452
+ for (const file of files) {
3453
+ const slashIdx = file.path.lastIndexOf("/");
3454
+ if (slashIdx === -1) {
3455
+ const existing = folderMap.get("(root)") ?? [];
3456
+ existing.push({ path: file.path, content: file.content });
3457
+ folderMap.set("(root)", existing);
3458
+ } else {
3459
+ const folder = file.path.slice(0, slashIdx);
3460
+ const fileName = file.path.slice(slashIdx + 1);
3461
+ const existing = folderMap.get(folder) ?? [];
3462
+ existing.push({ path: fileName, content: file.content });
3463
+ folderMap.set(folder, existing);
3464
+ }
3465
+ }
3466
+ const skills = Array.from(folderMap.entries()).map(([folder, files2]) => ({ folder, files: files2 }));
3467
+ await client.agents.update(agentId, { skills });
3468
+ onSaved?.();
3469
+ }, [agentId, client, onSaved]);
3470
+ return /* @__PURE__ */ jsxRuntime.jsx(
3471
+ FileTreeEditor,
3472
+ {
3473
+ initialFiles,
3474
+ onSave: handleSave,
3475
+ title: "Skills",
3476
+ saveLabel: "Save Skills",
3477
+ addFolderLabel: "Skill",
3478
+ newFileTemplate: {
3479
+ filename: "SKILL.md",
3480
+ content: "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n"
3481
+ }
3482
+ }
3483
+ );
3484
+ }
3225
3485
  function AgentPluginManager({ agentId, initialPlugins, onSaved }) {
3226
3486
  const client = useAgentPlaneClient();
3227
3487
  const [plugins, setPlugins] = React3.useState(initialPlugins);
@@ -3377,141 +3637,6 @@ function AgentPluginManager({ agentId, initialPlugins, onSaved }) {
3377
3637
  ] })
3378
3638
  ] });
3379
3639
  }
3380
- var FREQUENCIES = [
3381
- { value: "manual", label: "Manual (no schedule)" },
3382
- { value: "hourly", label: "Hourly" },
3383
- { value: "daily", label: "Daily" },
3384
- { value: "weekdays", label: "Weekdays (Mon-Fri)" },
3385
- { value: "weekly", label: "Weekly" }
3386
- ];
3387
- var DAYS_OF_WEEK = [
3388
- { value: 0, label: "Sunday" },
3389
- { value: 1, label: "Monday" },
3390
- { value: 2, label: "Tuesday" },
3391
- { value: 3, label: "Wednesday" },
3392
- { value: 4, label: "Thursday" },
3393
- { value: 5, label: "Friday" },
3394
- { value: 6, label: "Saturday" }
3395
- ];
3396
- function formatTimeForInput(time) {
3397
- if (!time) return "09:00";
3398
- return time.slice(0, 5);
3399
- }
3400
- function AgentScheduleForm({ initialSchedules, timezone }) {
3401
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-muted-foreground/25 p-5", children: [
3402
- /* @__PURE__ */ jsxRuntime.jsx(SectionHeader, { title: "Schedules", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
3403
- "Timezone: ",
3404
- timezone
3405
- ] }) }),
3406
- initialSchedules.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground py-4", children: "No schedules configured." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: initialSchedules.map((schedule) => /* @__PURE__ */ jsxRuntime.jsx(
3407
- ScheduleCard,
3408
- {
3409
- schedule,
3410
- timezone
3411
- },
3412
- schedule.id
3413
- )) })
3414
- ] });
3415
- }
3416
- function ScheduleCard({
3417
- schedule,
3418
- timezone
3419
- }) {
3420
- const [frequency, setFrequency] = React3.useState(schedule.frequency);
3421
- const [time, setTime] = React3.useState(formatTimeForInput(schedule.time));
3422
- const [dayOfWeek, setDayOfWeek] = React3.useState(schedule.day_of_week ?? 1);
3423
- const [prompt, setPrompt] = React3.useState(schedule.prompt ?? "");
3424
- const [enabled, setEnabled] = React3.useState(schedule.enabled);
3425
- const [name, setName] = React3.useState(schedule.name ?? "");
3426
- const showTimePicker = ["daily", "weekdays", "weekly"].includes(frequency);
3427
- const showDayPicker = frequency === "weekly";
3428
- const canEnable = frequency !== "manual";
3429
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded border border-muted-foreground/15 p-4 space-y-3", children: [
3430
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
3431
- /* @__PURE__ */ jsxRuntime.jsx(
3432
- Input,
3433
- {
3434
- value: name,
3435
- onChange: (e) => setName(e.target.value),
3436
- placeholder: "Schedule name (optional)",
3437
- className: "max-w-xs text-sm"
3438
- }
3439
- ),
3440
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3", children: canEnable && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
3441
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: enabled ? "Enabled" : "Disabled" }),
3442
- /* @__PURE__ */ jsxRuntime.jsx(
3443
- "button",
3444
- {
3445
- type: "button",
3446
- role: "switch",
3447
- "aria-checked": enabled,
3448
- onClick: () => setEnabled(!enabled),
3449
- className: `relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${enabled ? "bg-primary" : "bg-muted"}`,
3450
- children: /* @__PURE__ */ jsxRuntime.jsx(
3451
- "span",
3452
- {
3453
- className: `pointer-events-none inline-block h-4 w-4 transform rounded-full bg-background shadow-lg ring-0 transition-transform ${enabled ? "translate-x-4" : "translate-x-0"}`
3454
- }
3455
- )
3456
- }
3457
- )
3458
- ] }) })
3459
- ] }),
3460
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
3461
- /* @__PURE__ */ jsxRuntime.jsx(FormField, { label: "Frequency", children: /* @__PURE__ */ jsxRuntime.jsx(
3462
- Select,
3463
- {
3464
- value: frequency,
3465
- onChange: (e) => {
3466
- const newFreq = e.target.value;
3467
- setFrequency(newFreq);
3468
- if (newFreq === "manual") setEnabled(false);
3469
- else setEnabled(true);
3470
- },
3471
- children: FREQUENCIES.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f.value, children: f.label }, f.value))
3472
- }
3473
- ) }),
3474
- showTimePicker && /* @__PURE__ */ jsxRuntime.jsx(FormField, { label: `Time (${timezone})`, children: /* @__PURE__ */ jsxRuntime.jsx(
3475
- Input,
3476
- {
3477
- type: "time",
3478
- value: time,
3479
- onChange: (e) => setTime(e.target.value)
3480
- }
3481
- ) }),
3482
- showDayPicker && /* @__PURE__ */ jsxRuntime.jsx(FormField, { label: "Day of Week", children: /* @__PURE__ */ jsxRuntime.jsx(
3483
- Select,
3484
- {
3485
- value: dayOfWeek.toString(),
3486
- onChange: (e) => setDayOfWeek(parseInt(e.target.value)),
3487
- children: DAYS_OF_WEEK.map((d) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: d.value, children: d.label }, d.value))
3488
- }
3489
- ) })
3490
- ] }),
3491
- frequency !== "manual" && /* @__PURE__ */ jsxRuntime.jsx(FormField, { label: "Prompt", children: /* @__PURE__ */ jsxRuntime.jsx(
3492
- Textarea,
3493
- {
3494
- value: prompt,
3495
- onChange: (e) => setPrompt(e.target.value),
3496
- rows: 3,
3497
- placeholder: "Enter the prompt to send on each scheduled run...",
3498
- className: "resize-y min-h-[60px]"
3499
- }
3500
- ) }),
3501
- (schedule.last_run_at || schedule.next_run_at) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-6 text-sm text-muted-foreground pt-1", children: [
3502
- schedule.last_run_at && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3503
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Last run:" }),
3504
- " ",
3505
- /* @__PURE__ */ jsxRuntime.jsx(LocalDate, { value: schedule.last_run_at })
3506
- ] }),
3507
- schedule.next_run_at && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3508
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Next run:" }),
3509
- " ",
3510
- /* @__PURE__ */ jsxRuntime.jsx(LocalDate, { value: schedule.next_run_at })
3511
- ] })
3512
- ] })
3513
- ] });
3514
- }
3515
3640
  function AgentRuns({ agentId }) {
3516
3641
  const { LinkComponent, basePath } = useNavigation();
3517
3642
  const { data, error, isLoading } = useApi(
@@ -3751,7 +3876,7 @@ function AgentA2aInfo({
3751
3876
  ] })
3752
3877
  ] });
3753
3878
  }
3754
- function AgentDetailPage({ agentId, a2aBaseUrl, tenantSlug, timezone = "UTC" }) {
3879
+ function AgentDetailPage({ agentId, a2aBaseUrl, tenantSlug }) {
3755
3880
  const { LinkComponent, basePath } = useNavigation();
3756
3881
  const { mutate } = useSWR.useSWRConfig();
3757
3882
  const cacheKey = `agent-${agentId}`;
@@ -3854,16 +3979,6 @@ function AgentDetailPage({ agentId, a2aBaseUrl, tenantSlug, timezone = "UTC" })
3854
3979
  }
3855
3980
  )
3856
3981
  },
3857
- {
3858
- label: "Schedules",
3859
- content: /* @__PURE__ */ jsxRuntime.jsx(
3860
- AgentScheduleForm,
3861
- {
3862
- initialSchedules: [],
3863
- timezone
3864
- }
3865
- )
3866
- },
3867
3982
  {
3868
3983
  label: "Runs",
3869
3984
  content: /* @__PURE__ */ jsxRuntime.jsx(AgentRuns, { agentId: agent.id })
@@ -3873,6 +3988,141 @@ function AgentDetailPage({ agentId, a2aBaseUrl, tenantSlug, timezone = "UTC" })
3873
3988
  )
3874
3989
  ] });
3875
3990
  }
3991
+ var FREQUENCIES = [
3992
+ { value: "manual", label: "Manual (no schedule)" },
3993
+ { value: "hourly", label: "Hourly" },
3994
+ { value: "daily", label: "Daily" },
3995
+ { value: "weekdays", label: "Weekdays (Mon-Fri)" },
3996
+ { value: "weekly", label: "Weekly" }
3997
+ ];
3998
+ var DAYS_OF_WEEK = [
3999
+ { value: 0, label: "Sunday" },
4000
+ { value: 1, label: "Monday" },
4001
+ { value: 2, label: "Tuesday" },
4002
+ { value: 3, label: "Wednesday" },
4003
+ { value: 4, label: "Thursday" },
4004
+ { value: 5, label: "Friday" },
4005
+ { value: 6, label: "Saturday" }
4006
+ ];
4007
+ function formatTimeForInput(time) {
4008
+ if (!time) return "09:00";
4009
+ return time.slice(0, 5);
4010
+ }
4011
+ function AgentScheduleForm({ initialSchedules, timezone }) {
4012
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-muted-foreground/25 p-5", children: [
4013
+ /* @__PURE__ */ jsxRuntime.jsx(SectionHeader, { title: "Schedules", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
4014
+ "Timezone: ",
4015
+ timezone
4016
+ ] }) }),
4017
+ initialSchedules.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground py-4", children: "No schedules configured." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: initialSchedules.map((schedule) => /* @__PURE__ */ jsxRuntime.jsx(
4018
+ ScheduleCard,
4019
+ {
4020
+ schedule,
4021
+ timezone
4022
+ },
4023
+ schedule.id
4024
+ )) })
4025
+ ] });
4026
+ }
4027
+ function ScheduleCard({
4028
+ schedule,
4029
+ timezone
4030
+ }) {
4031
+ const [frequency, setFrequency] = React3.useState(schedule.frequency);
4032
+ const [time, setTime] = React3.useState(formatTimeForInput(schedule.time));
4033
+ const [dayOfWeek, setDayOfWeek] = React3.useState(schedule.day_of_week ?? 1);
4034
+ const [prompt, setPrompt] = React3.useState(schedule.prompt ?? "");
4035
+ const [enabled, setEnabled] = React3.useState(schedule.enabled);
4036
+ const [name, setName] = React3.useState(schedule.name ?? "");
4037
+ const showTimePicker = ["daily", "weekdays", "weekly"].includes(frequency);
4038
+ const showDayPicker = frequency === "weekly";
4039
+ const canEnable = frequency !== "manual";
4040
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded border border-muted-foreground/15 p-4 space-y-3", children: [
4041
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
4042
+ /* @__PURE__ */ jsxRuntime.jsx(
4043
+ Input,
4044
+ {
4045
+ value: name,
4046
+ onChange: (e) => setName(e.target.value),
4047
+ placeholder: "Schedule name (optional)",
4048
+ className: "max-w-xs text-sm"
4049
+ }
4050
+ ),
4051
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3", children: canEnable && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
4052
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: enabled ? "Enabled" : "Disabled" }),
4053
+ /* @__PURE__ */ jsxRuntime.jsx(
4054
+ "button",
4055
+ {
4056
+ type: "button",
4057
+ role: "switch",
4058
+ "aria-checked": enabled,
4059
+ onClick: () => setEnabled(!enabled),
4060
+ className: `relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${enabled ? "bg-primary" : "bg-muted"}`,
4061
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4062
+ "span",
4063
+ {
4064
+ className: `pointer-events-none inline-block h-4 w-4 transform rounded-full bg-background shadow-lg ring-0 transition-transform ${enabled ? "translate-x-4" : "translate-x-0"}`
4065
+ }
4066
+ )
4067
+ }
4068
+ )
4069
+ ] }) })
4070
+ ] }),
4071
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
4072
+ /* @__PURE__ */ jsxRuntime.jsx(FormField, { label: "Frequency", children: /* @__PURE__ */ jsxRuntime.jsx(
4073
+ Select,
4074
+ {
4075
+ value: frequency,
4076
+ onChange: (e) => {
4077
+ const newFreq = e.target.value;
4078
+ setFrequency(newFreq);
4079
+ if (newFreq === "manual") setEnabled(false);
4080
+ else setEnabled(true);
4081
+ },
4082
+ children: FREQUENCIES.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f.value, children: f.label }, f.value))
4083
+ }
4084
+ ) }),
4085
+ showTimePicker && /* @__PURE__ */ jsxRuntime.jsx(FormField, { label: `Time (${timezone})`, children: /* @__PURE__ */ jsxRuntime.jsx(
4086
+ Input,
4087
+ {
4088
+ type: "time",
4089
+ value: time,
4090
+ onChange: (e) => setTime(e.target.value)
4091
+ }
4092
+ ) }),
4093
+ showDayPicker && /* @__PURE__ */ jsxRuntime.jsx(FormField, { label: "Day of Week", children: /* @__PURE__ */ jsxRuntime.jsx(
4094
+ Select,
4095
+ {
4096
+ value: dayOfWeek.toString(),
4097
+ onChange: (e) => setDayOfWeek(parseInt(e.target.value)),
4098
+ children: DAYS_OF_WEEK.map((d) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: d.value, children: d.label }, d.value))
4099
+ }
4100
+ ) })
4101
+ ] }),
4102
+ frequency !== "manual" && /* @__PURE__ */ jsxRuntime.jsx(FormField, { label: "Prompt", children: /* @__PURE__ */ jsxRuntime.jsx(
4103
+ Textarea,
4104
+ {
4105
+ value: prompt,
4106
+ onChange: (e) => setPrompt(e.target.value),
4107
+ rows: 3,
4108
+ placeholder: "Enter the prompt to send on each scheduled run...",
4109
+ className: "resize-y min-h-[60px]"
4110
+ }
4111
+ ) }),
4112
+ (schedule.last_run_at || schedule.next_run_at) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-6 text-sm text-muted-foreground pt-1", children: [
4113
+ schedule.last_run_at && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
4114
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Last run:" }),
4115
+ " ",
4116
+ /* @__PURE__ */ jsxRuntime.jsx(LocalDate, { value: schedule.last_run_at })
4117
+ ] }),
4118
+ schedule.next_run_at && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
4119
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Next run:" }),
4120
+ " ",
4121
+ /* @__PURE__ */ jsxRuntime.jsx(LocalDate, { value: schedule.next_run_at })
4122
+ ] })
4123
+ ] })
4124
+ ] });
4125
+ }
3876
4126
 
3877
4127
  exports.AdminTable = AdminTable;
3878
4128
  exports.AdminTableHead = AdminTableHead;