stagent 0.5.0 → 0.6.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 +8 -8
- package/dist/cli.js +146 -2
- package/docs/.coverage-gaps.json +21 -0
- package/docs/.last-generated +1 -1
- package/docs/features/agent-intelligence.md +36 -14
- package/docs/features/chat.md +33 -56
- package/docs/features/cost-usage.md +14 -10
- package/docs/features/dashboard-kanban.md +30 -13
- package/docs/features/delivery-channels.md +198 -0
- package/docs/features/design-system.md +10 -10
- package/docs/features/documents.md +8 -8
- package/docs/features/home-workspace.md +20 -15
- package/docs/features/inbox-notifications.md +22 -10
- package/docs/features/keyboard-navigation.md +11 -11
- package/docs/features/monitoring.md +1 -1
- package/docs/features/playbook.md +30 -32
- package/docs/features/profiles.md +33 -11
- package/docs/features/projects.md +2 -2
- package/docs/features/provider-runtimes.md +58 -14
- package/docs/features/schedules.md +70 -40
- package/docs/features/settings.md +74 -46
- package/docs/features/shared-components.md +7 -15
- package/docs/features/tool-permissions.md +9 -9
- package/docs/features/workflows.md +32 -21
- package/docs/getting-started.md +33 -9
- package/docs/index.md +25 -16
- package/docs/journeys/developer.md +124 -207
- package/docs/journeys/personal-use.md +70 -79
- package/docs/journeys/power-user.md +107 -151
- package/docs/journeys/work-use.md +81 -113
- package/docs/manifest.json +77 -45
- package/docs/superpowers/plans/2026-03-30-finish-in-progress-features.md +547 -0
- package/docs/use-cases/agency-operator.md +84 -0
- package/docs/use-cases/solo-founder.md +75 -0
- package/docs/why-stagent.md +59 -0
- package/package.json +10 -3
- package/src/app/api/channels/[id]/route.ts +104 -0
- package/src/app/api/channels/[id]/test/route.ts +52 -0
- package/src/app/api/channels/inbound/slack/route.ts +116 -0
- package/src/app/api/channels/inbound/telegram/poll/route.ts +140 -0
- package/src/app/api/channels/inbound/telegram/route.ts +87 -0
- package/src/app/api/channels/route.ts +72 -0
- package/src/app/api/chat/conversations/route.ts +15 -0
- package/src/app/api/chat/entities/search/route.ts +46 -31
- package/src/app/api/data/clear/route.ts +4 -0
- package/src/app/api/data/seed/route.ts +4 -0
- package/src/app/api/documents/route.ts +36 -6
- package/src/app/api/environment/profiles/suggest/route.ts +19 -3
- package/src/app/api/environment/scan/route.ts +8 -1
- package/src/app/api/handoffs/[id]/route.ts +76 -0
- package/src/app/api/handoffs/route.ts +89 -0
- package/src/app/api/memory/route.ts +181 -0
- package/src/app/api/profiles/[id]/route.ts +16 -1
- package/src/app/api/profiles/[id]/test/route.ts +4 -0
- package/src/app/api/profiles/[id]/test-results/route.ts +22 -0
- package/src/app/api/profiles/[id]/test-single/route.ts +64 -0
- package/src/app/api/profiles/assist/route.ts +35 -0
- package/src/app/api/profiles/import-repo/apply-updates/route.ts +123 -0
- package/src/app/api/profiles/import-repo/check-updates/route.ts +163 -0
- package/src/app/api/profiles/import-repo/confirm/route.ts +118 -0
- package/src/app/api/profiles/import-repo/preview/route.ts +107 -0
- package/src/app/api/profiles/import-repo/route.ts +29 -0
- package/src/app/api/profiles/import-repo/scan/route.ts +25 -0
- package/src/app/api/profiles/route.ts +73 -22
- package/src/app/api/runtimes/ollama/route.ts +86 -0
- package/src/app/api/runtimes/suggest/route.ts +29 -0
- package/src/app/api/schedules/[id]/heartbeat-history/route.ts +77 -0
- package/src/app/api/schedules/[id]/route.ts +41 -3
- package/src/app/api/schedules/parse/route.ts +66 -0
- package/src/app/api/schedules/route.ts +71 -12
- package/src/app/api/settings/author-default/route.ts +7 -0
- package/src/app/api/settings/learning/route.ts +41 -0
- package/src/app/api/settings/ollama/route.ts +34 -0
- package/src/app/api/settings/providers/route.ts +57 -0
- package/src/app/api/settings/routing/route.ts +24 -0
- package/src/app/api/settings/web-search/route.ts +28 -0
- package/src/app/api/tasks/[id]/execute/route.ts +13 -1
- package/src/app/api/tasks/[id]/respond/route.ts +23 -1
- package/src/app/documents/page.tsx +3 -0
- package/src/app/environment/page.tsx +8 -1
- package/src/app/settings/page.tsx +10 -4
- package/src/app/workflows/[id]/edit/page.tsx +2 -0
- package/src/app/workflows/new/page.tsx +2 -0
- package/src/components/chat/chat-command-popover.tsx +22 -19
- package/src/components/chat/chat-input.tsx +5 -0
- package/src/components/chat/chat-model-selector.tsx +42 -1
- package/src/components/chat/chat-shell.tsx +2 -0
- package/src/components/dashboard/welcome-landing.tsx +9 -9
- package/src/components/environment/artifact-card.tsx +27 -1
- package/src/components/environment/environment-dashboard.tsx +50 -2
- package/src/components/environment/environment-summary-card.tsx +5 -2
- package/src/components/environment/suggested-profiles.tsx +117 -52
- package/src/components/handoffs/handoff-approval-card.tsx +159 -0
- package/src/components/memory/memory-browser.tsx +315 -0
- package/src/components/profiles/learned-context-panel.tsx +4 -4
- package/src/components/profiles/profile-assist-panel.tsx +512 -0
- package/src/components/profiles/profile-browser.tsx +109 -8
- package/src/components/profiles/profile-card.tsx +29 -1
- package/src/components/profiles/profile-detail-view.tsx +200 -28
- package/src/components/profiles/profile-form-view.tsx +220 -82
- package/src/components/profiles/repo-import-wizard.tsx +648 -0
- package/src/components/profiles/smoke-test-editor.tsx +106 -0
- package/src/components/schedules/schedule-create-sheet.tsx +9 -1
- package/src/components/schedules/schedule-form.tsx +348 -9
- package/src/components/schedules/schedule-list.tsx +15 -2
- package/src/components/settings/auth-method-selector.tsx +7 -1
- package/src/components/settings/budget-guardrails-section.tsx +111 -48
- package/src/components/settings/channels-section.tsx +526 -0
- package/src/components/settings/chat-settings-section.tsx +27 -1
- package/src/components/settings/data-management-section.tsx +8 -6
- package/src/components/settings/learning-context-section.tsx +124 -0
- package/src/components/settings/ollama-section.tsx +270 -0
- package/src/components/settings/providers-runtimes-section.tsx +499 -0
- package/src/components/settings/web-search-section.tsx +101 -0
- package/src/components/shared/tag-input.tsx +156 -0
- package/src/components/tasks/kanban-board.tsx +32 -0
- package/src/components/tasks/kanban-column.tsx +4 -2
- package/src/components/tasks/task-card.tsx +1 -0
- package/src/components/tasks/task-chip-bar.tsx +6 -1
- package/src/components/tasks/task-create-panel.tsx +55 -5
- package/src/components/workflows/workflow-form-view.tsx +38 -3
- package/src/hooks/use-chat-autocomplete.ts +24 -26
- package/src/hooks/use-project-skills.ts +66 -0
- package/src/hooks/use-tag-suggestions.ts +31 -0
- package/src/instrumentation.ts +4 -1
- package/src/lib/agents/__tests__/claude-agent.test.ts +3 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +10 -0
- package/src/lib/agents/agentic-loop.ts +235 -0
- package/src/lib/agents/browser-mcp.ts +59 -4
- package/src/lib/agents/claude-agent.ts +27 -200
- package/src/lib/agents/handoff/bus.ts +164 -0
- package/src/lib/agents/handoff/governance.ts +47 -0
- package/src/lib/agents/handoff/types.ts +16 -0
- package/src/lib/agents/learned-context.ts +27 -7
- package/src/lib/agents/memory/decay.ts +61 -0
- package/src/lib/agents/memory/extractor.ts +181 -0
- package/src/lib/agents/memory/retrieval.ts +96 -0
- package/src/lib/agents/memory/types.ts +6 -0
- package/src/lib/agents/profiles/__tests__/project-profiles.test.ts +119 -0
- package/src/lib/agents/profiles/__tests__/registry.test.ts +11 -3
- package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/content-creator/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/content-creator/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/customer-support-agent/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/customer-support-agent/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/financial-analyst/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/financial-analyst/profile.yaml +24 -0
- package/src/lib/agents/profiles/builtins/general/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/marketing-strategist/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/marketing-strategist/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/operations-coordinator/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/operations-coordinator/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/researcher/SKILL.md +1 -0
- package/src/lib/agents/profiles/builtins/researcher/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/sales-researcher/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/sales-researcher/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/shopping-assistant/SKILL.md +1 -0
- package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/sweep/profile.yaml +1 -1
- package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/travel-planner/SKILL.md +2 -0
- package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/wealth-manager/SKILL.md +2 -0
- package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +2 -2
- package/src/lib/agents/profiles/project-profiles.ts +193 -0
- package/src/lib/agents/profiles/registry.ts +130 -6
- package/src/lib/agents/profiles/types.ts +28 -0
- package/src/lib/agents/router.ts +174 -2
- package/src/lib/agents/runtime/__tests__/catalog.test.ts +15 -4
- package/src/lib/agents/runtime/anthropic-direct.ts +644 -0
- package/src/lib/agents/runtime/catalog.ts +57 -2
- package/src/lib/agents/runtime/claude.ts +205 -1
- package/src/lib/agents/runtime/index.ts +22 -0
- package/src/lib/agents/runtime/ollama-adapter.ts +409 -0
- package/src/lib/agents/runtime/openai-direct.ts +514 -0
- package/src/lib/agents/runtime/profile-assist-types.ts +30 -0
- package/src/lib/agents/runtime/types.ts +2 -0
- package/src/lib/agents/tool-permissions.ts +203 -0
- package/src/lib/channels/gateway.ts +321 -0
- package/src/lib/channels/poller.ts +268 -0
- package/src/lib/channels/registry.ts +90 -0
- package/src/lib/channels/slack-adapter.ts +188 -0
- package/src/lib/channels/telegram-adapter.ts +218 -0
- package/src/lib/channels/types.ts +75 -0
- package/src/lib/channels/webhook-adapter.ts +74 -0
- package/src/lib/chat/context-builder.ts +22 -2
- package/src/lib/chat/engine.ts +95 -13
- package/src/lib/chat/ollama-engine.ts +198 -0
- package/src/lib/chat/stagent-tools.ts +106 -20
- package/src/lib/chat/tool-catalog.ts +24 -0
- package/src/lib/chat/tool-registry.ts +90 -0
- package/src/lib/chat/tools/chat-history-tools.ts +4 -4
- package/src/lib/chat/tools/document-tools.ts +7 -7
- package/src/lib/chat/tools/handoff-tools.ts +70 -0
- package/src/lib/chat/tools/notification-tools.ts +4 -4
- package/src/lib/chat/tools/profile-tools.ts +3 -3
- package/src/lib/chat/tools/project-tools.ts +3 -3
- package/src/lib/chat/tools/schedule-tools.ts +29 -13
- package/src/lib/chat/tools/settings-tools.ts +2 -2
- package/src/lib/chat/tools/task-tools.ts +66 -11
- package/src/lib/chat/tools/usage-tools.ts +2 -2
- package/src/lib/chat/tools/workflow-tools.ts +8 -8
- package/src/lib/chat/types.ts +11 -5
- package/src/lib/constants/known-tools.ts +19 -0
- package/src/lib/constants/prose-styles.ts +1 -1
- package/src/lib/constants/settings.ts +7 -0
- package/src/lib/data/channel-bindings.ts +85 -0
- package/src/lib/data/clear.ts +22 -0
- package/src/lib/data/profile-test-results.ts +48 -0
- package/src/lib/data/seed-data/conversations.ts +196 -0
- package/src/lib/data/seed-data/learned-context.ts +99 -0
- package/src/lib/data/seed-data/notifications.ts +54 -1
- package/src/lib/data/seed-data/profile-test-results.ts +96 -0
- package/src/lib/data/seed-data/repo-imports.ts +51 -0
- package/src/lib/data/seed-data/views.ts +60 -0
- package/src/lib/data/seed.ts +51 -0
- package/src/lib/db/bootstrap.ts +162 -0
- package/src/lib/db/migrations/0013_add_repo_imports.sql +15 -0
- package/src/lib/db/migrations/0014_add_linked_profile_id.sql +3 -0
- package/src/lib/db/migrations/0015_add_channel_bindings.sql +23 -0
- package/src/lib/db/schema.ts +190 -1
- package/src/lib/environment/__tests__/auto-scan.test.ts +86 -0
- package/src/lib/environment/__tests__/profile-linker.test.ts +187 -0
- package/src/lib/environment/auto-scan.ts +48 -0
- package/src/lib/environment/data.ts +25 -0
- package/src/lib/environment/profile-generator.ts +40 -10
- package/src/lib/environment/profile-linker.ts +143 -0
- package/src/lib/environment/profile-rules.ts +96 -0
- package/src/lib/import/dedup.ts +149 -0
- package/src/lib/import/format-adapter.ts +631 -0
- package/src/lib/import/github-api.ts +219 -0
- package/src/lib/import/repo-scanner.ts +251 -0
- package/src/lib/schedules/__tests__/nlp-parser.test.ts +330 -0
- package/src/lib/schedules/active-hours.ts +120 -0
- package/src/lib/schedules/heartbeat-parser.ts +224 -0
- package/src/lib/schedules/heartbeat-prompt.ts +153 -0
- package/src/lib/schedules/nlp-parser.ts +357 -0
- package/src/lib/schedules/scheduler.ts +218 -3
- package/src/lib/settings/__tests__/budget-guardrails.test.ts +39 -1
- package/src/lib/settings/helpers.ts +6 -0
- package/src/lib/settings/routing.ts +24 -0
- package/src/lib/settings/runtime-setup.ts +28 -1
- package/src/lib/usage/ledger.ts +2 -1
- package/src/lib/validators/__tests__/settings.test.ts +9 -0
- package/src/lib/validators/profile.ts +39 -0
- package/src/lib/workflows/blueprints/builtins/business-daily-briefing.yaml +102 -0
- package/src/lib/workflows/blueprints/builtins/content-marketing-pipeline.yaml +90 -0
- package/src/lib/workflows/blueprints/builtins/customer-support-triage.yaml +107 -0
- package/src/lib/workflows/blueprints/builtins/financial-reporting.yaml +104 -0
- package/src/lib/workflows/blueprints/builtins/lead-research-pipeline.yaml +82 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
ArrowRight,
|
|
6
|
+
CheckCircle2,
|
|
7
|
+
XCircle,
|
|
8
|
+
Clock,
|
|
9
|
+
AlertTriangle,
|
|
10
|
+
} from "lucide-react";
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
import { Button } from "@/components/ui/button";
|
|
13
|
+
import { Badge } from "@/components/ui/badge";
|
|
14
|
+
|
|
15
|
+
interface HandoffApprovalCardProps {
|
|
16
|
+
id: string;
|
|
17
|
+
fromProfileId: string;
|
|
18
|
+
toProfileId: string;
|
|
19
|
+
subject: string;
|
|
20
|
+
body: string;
|
|
21
|
+
priority: number;
|
|
22
|
+
chainDepth: number;
|
|
23
|
+
status: string;
|
|
24
|
+
requiresApproval: boolean;
|
|
25
|
+
onActionComplete?: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const PRIORITY_LABELS: Record<number, string> = {
|
|
29
|
+
0: "Critical",
|
|
30
|
+
1: "High",
|
|
31
|
+
2: "Medium",
|
|
32
|
+
3: "Low",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const PRIORITY_VARIANTS: Record<number, "destructive" | "default" | "secondary" | "outline"> = {
|
|
36
|
+
0: "destructive",
|
|
37
|
+
1: "default",
|
|
38
|
+
2: "secondary",
|
|
39
|
+
3: "outline",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const STATUS_ICONS: Record<string, typeof Clock> = {
|
|
43
|
+
pending: Clock,
|
|
44
|
+
accepted: CheckCircle2,
|
|
45
|
+
in_progress: Clock,
|
|
46
|
+
completed: CheckCircle2,
|
|
47
|
+
rejected: XCircle,
|
|
48
|
+
expired: AlertTriangle,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const STATUS_VARIANTS: Record<string, "default" | "secondary" | "destructive" | "outline"> = {
|
|
52
|
+
pending: "outline",
|
|
53
|
+
accepted: "default",
|
|
54
|
+
in_progress: "default",
|
|
55
|
+
completed: "secondary",
|
|
56
|
+
rejected: "destructive",
|
|
57
|
+
expired: "outline",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export function HandoffApprovalCard({
|
|
61
|
+
id,
|
|
62
|
+
fromProfileId,
|
|
63
|
+
toProfileId,
|
|
64
|
+
subject,
|
|
65
|
+
body,
|
|
66
|
+
priority,
|
|
67
|
+
chainDepth,
|
|
68
|
+
status,
|
|
69
|
+
requiresApproval,
|
|
70
|
+
onActionComplete,
|
|
71
|
+
}: HandoffApprovalCardProps) {
|
|
72
|
+
const [acting, setActing] = useState(false);
|
|
73
|
+
|
|
74
|
+
const StatusIcon = STATUS_ICONS[status] ?? Clock;
|
|
75
|
+
|
|
76
|
+
const handleAction = async (action: "approve" | "reject") => {
|
|
77
|
+
setActing(true);
|
|
78
|
+
try {
|
|
79
|
+
const res = await fetch(`/api/handoffs/${id}`, {
|
|
80
|
+
method: "PATCH",
|
|
81
|
+
headers: { "Content-Type": "application/json" },
|
|
82
|
+
body: JSON.stringify({ action, approvedBy: "user" }),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (res.ok) {
|
|
86
|
+
toast.success(`Handoff ${action === "approve" ? "approved" : "rejected"}`);
|
|
87
|
+
onActionComplete?.();
|
|
88
|
+
} else {
|
|
89
|
+
const data = await res.json();
|
|
90
|
+
toast.error(data.error ?? `Failed to ${action} handoff`);
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
toast.error(`Failed to ${action} handoff`);
|
|
94
|
+
} finally {
|
|
95
|
+
setActing(false);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className="rounded-lg border p-4 space-y-3">
|
|
101
|
+
<div className="flex items-start justify-between">
|
|
102
|
+
<div className="space-y-1">
|
|
103
|
+
<h4 className="text-sm font-medium">{subject}</h4>
|
|
104
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
105
|
+
<span className="font-mono">{fromProfileId}</span>
|
|
106
|
+
<ArrowRight className="h-3 w-3" />
|
|
107
|
+
<span className="font-mono">{toProfileId}</span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
<div className="flex items-center gap-2">
|
|
111
|
+
<Badge variant={PRIORITY_VARIANTS[priority] ?? "secondary"}>
|
|
112
|
+
{PRIORITY_LABELS[priority] ?? "Medium"}
|
|
113
|
+
</Badge>
|
|
114
|
+
<Badge variant={STATUS_VARIANTS[status] ?? "outline"}>
|
|
115
|
+
<StatusIcon className="mr-1 h-3 w-3" />
|
|
116
|
+
{status}
|
|
117
|
+
</Badge>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<p className="text-sm text-muted-foreground line-clamp-3">{body}</p>
|
|
122
|
+
|
|
123
|
+
<div className="flex items-center justify-between">
|
|
124
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
125
|
+
{chainDepth > 0 && (
|
|
126
|
+
<span>Chain depth: {chainDepth}</span>
|
|
127
|
+
)}
|
|
128
|
+
{requiresApproval && status === "pending" && (
|
|
129
|
+
<Badge variant="outline" className="text-xs">
|
|
130
|
+
Awaiting approval
|
|
131
|
+
</Badge>
|
|
132
|
+
)}
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
{status === "pending" && requiresApproval && (
|
|
136
|
+
<div className="flex items-center gap-2">
|
|
137
|
+
<Button
|
|
138
|
+
variant="outline"
|
|
139
|
+
size="sm"
|
|
140
|
+
onClick={() => handleAction("reject")}
|
|
141
|
+
disabled={acting}
|
|
142
|
+
>
|
|
143
|
+
<XCircle className="mr-1 h-3.5 w-3.5" />
|
|
144
|
+
Reject
|
|
145
|
+
</Button>
|
|
146
|
+
<Button
|
|
147
|
+
size="sm"
|
|
148
|
+
onClick={() => handleAction("approve")}
|
|
149
|
+
disabled={acting}
|
|
150
|
+
>
|
|
151
|
+
<CheckCircle2 className="mr-1 h-3.5 w-3.5" />
|
|
152
|
+
Approve
|
|
153
|
+
</Button>
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from "react";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Input } from "@/components/ui/input";
|
|
7
|
+
import {
|
|
8
|
+
Select,
|
|
9
|
+
SelectContent,
|
|
10
|
+
SelectItem,
|
|
11
|
+
SelectTrigger,
|
|
12
|
+
SelectValue,
|
|
13
|
+
} from "@/components/ui/select";
|
|
14
|
+
import {
|
|
15
|
+
Table,
|
|
16
|
+
TableBody,
|
|
17
|
+
TableCell,
|
|
18
|
+
TableHead,
|
|
19
|
+
TableHeader,
|
|
20
|
+
TableRow,
|
|
21
|
+
} from "@/components/ui/table";
|
|
22
|
+
import { EmptyState } from "@/components/shared/empty-state";
|
|
23
|
+
import { Brain, Archive, XCircle, Check, Pencil } from "lucide-react";
|
|
24
|
+
import { toast } from "sonner";
|
|
25
|
+
import type { AgentMemoryRow } from "@/lib/db/schema";
|
|
26
|
+
|
|
27
|
+
const CATEGORY_VARIANTS: Record<string, "default" | "secondary" | "outline" | "destructive"> = {
|
|
28
|
+
fact: "default",
|
|
29
|
+
preference: "secondary",
|
|
30
|
+
pattern: "outline",
|
|
31
|
+
outcome: "destructive",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const STATUS_VARIANTS: Record<string, "default" | "secondary" | "outline" | "destructive"> = {
|
|
35
|
+
active: "default",
|
|
36
|
+
decayed: "secondary",
|
|
37
|
+
archived: "outline",
|
|
38
|
+
rejected: "destructive",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
interface MemoryBrowserProps {
|
|
42
|
+
profileId: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function MemoryBrowser({ profileId }: MemoryBrowserProps) {
|
|
46
|
+
const [memories, setMemories] = useState<AgentMemoryRow[]>([]);
|
|
47
|
+
const [loading, setLoading] = useState(true);
|
|
48
|
+
const [categoryFilter, setCategoryFilter] = useState<string>("all");
|
|
49
|
+
const [statusFilter, setStatusFilter] = useState<string>("active");
|
|
50
|
+
const [editingId, setEditingId] = useState<string | null>(null);
|
|
51
|
+
const [editConfidence, setEditConfidence] = useState<string>("");
|
|
52
|
+
|
|
53
|
+
const fetchMemories = useCallback(async () => {
|
|
54
|
+
setLoading(true);
|
|
55
|
+
try {
|
|
56
|
+
const params = new URLSearchParams({ profileId });
|
|
57
|
+
if (categoryFilter !== "all") params.set("category", categoryFilter);
|
|
58
|
+
if (statusFilter !== "all") params.set("status", statusFilter);
|
|
59
|
+
|
|
60
|
+
const res = await fetch(`/api/memory?${params}`);
|
|
61
|
+
if (res.ok) {
|
|
62
|
+
setMemories(await res.json());
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
toast.error("Failed to load memories");
|
|
66
|
+
} finally {
|
|
67
|
+
setLoading(false);
|
|
68
|
+
}
|
|
69
|
+
}, [profileId, categoryFilter, statusFilter]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
fetchMemories();
|
|
73
|
+
}, [fetchMemories]);
|
|
74
|
+
|
|
75
|
+
async function handleArchive(id: string) {
|
|
76
|
+
try {
|
|
77
|
+
const res = await fetch("/api/memory", {
|
|
78
|
+
method: "DELETE",
|
|
79
|
+
headers: { "Content-Type": "application/json" },
|
|
80
|
+
body: JSON.stringify({ id }),
|
|
81
|
+
});
|
|
82
|
+
if (res.ok) {
|
|
83
|
+
toast.success("Memory archived");
|
|
84
|
+
fetchMemories();
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
toast.error("Failed to archive memory");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function handleReject(id: string) {
|
|
92
|
+
try {
|
|
93
|
+
const res = await fetch("/api/memory", {
|
|
94
|
+
method: "PATCH",
|
|
95
|
+
headers: { "Content-Type": "application/json" },
|
|
96
|
+
body: JSON.stringify({ id, status: "rejected" }),
|
|
97
|
+
});
|
|
98
|
+
if (res.ok) {
|
|
99
|
+
toast.success("Memory rejected");
|
|
100
|
+
fetchMemories();
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
toast.error("Failed to reject memory");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function handleSaveConfidence(id: string) {
|
|
108
|
+
const value = parseInt(editConfidence, 10);
|
|
109
|
+
if (isNaN(value) || value < 0 || value > 1000) {
|
|
110
|
+
toast.error("Confidence must be between 0 and 1000");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const res = await fetch("/api/memory", {
|
|
115
|
+
method: "PATCH",
|
|
116
|
+
headers: { "Content-Type": "application/json" },
|
|
117
|
+
body: JSON.stringify({ id, confidence: value }),
|
|
118
|
+
});
|
|
119
|
+
if (res.ok) {
|
|
120
|
+
toast.success("Confidence updated");
|
|
121
|
+
setEditingId(null);
|
|
122
|
+
fetchMemories();
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
toast.error("Failed to update confidence");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function formatDate(dateValue: string | Date | null | undefined): string {
|
|
130
|
+
if (!dateValue) return "Never";
|
|
131
|
+
const d = typeof dateValue === "string" ? new Date(dateValue) : dateValue;
|
|
132
|
+
return d.toLocaleDateString();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!loading && memories.length === 0) {
|
|
136
|
+
return (
|
|
137
|
+
<div className="space-y-4">
|
|
138
|
+
<div className="flex items-center gap-2">
|
|
139
|
+
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
|
140
|
+
<SelectTrigger className="w-[140px]">
|
|
141
|
+
<SelectValue placeholder="Status" />
|
|
142
|
+
</SelectTrigger>
|
|
143
|
+
<SelectContent>
|
|
144
|
+
<SelectItem value="all">All Status</SelectItem>
|
|
145
|
+
<SelectItem value="active">Active</SelectItem>
|
|
146
|
+
<SelectItem value="decayed">Decayed</SelectItem>
|
|
147
|
+
<SelectItem value="archived">Archived</SelectItem>
|
|
148
|
+
<SelectItem value="rejected">Rejected</SelectItem>
|
|
149
|
+
</SelectContent>
|
|
150
|
+
</Select>
|
|
151
|
+
</div>
|
|
152
|
+
<EmptyState
|
|
153
|
+
icon={Brain}
|
|
154
|
+
heading="No memories yet"
|
|
155
|
+
description="Episodic memories are extracted from task results and stored as factual knowledge for this profile."
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div className="space-y-4">
|
|
163
|
+
{/* Filters */}
|
|
164
|
+
<div className="flex items-center gap-2">
|
|
165
|
+
<Select value={categoryFilter} onValueChange={setCategoryFilter}>
|
|
166
|
+
<SelectTrigger className="w-[140px]">
|
|
167
|
+
<SelectValue placeholder="Category" />
|
|
168
|
+
</SelectTrigger>
|
|
169
|
+
<SelectContent>
|
|
170
|
+
<SelectItem value="all">All Categories</SelectItem>
|
|
171
|
+
<SelectItem value="fact">Fact</SelectItem>
|
|
172
|
+
<SelectItem value="preference">Preference</SelectItem>
|
|
173
|
+
<SelectItem value="pattern">Pattern</SelectItem>
|
|
174
|
+
<SelectItem value="outcome">Outcome</SelectItem>
|
|
175
|
+
</SelectContent>
|
|
176
|
+
</Select>
|
|
177
|
+
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
|
178
|
+
<SelectTrigger className="w-[140px]">
|
|
179
|
+
<SelectValue placeholder="Status" />
|
|
180
|
+
</SelectTrigger>
|
|
181
|
+
<SelectContent>
|
|
182
|
+
<SelectItem value="all">All Status</SelectItem>
|
|
183
|
+
<SelectItem value="active">Active</SelectItem>
|
|
184
|
+
<SelectItem value="decayed">Decayed</SelectItem>
|
|
185
|
+
<SelectItem value="archived">Archived</SelectItem>
|
|
186
|
+
<SelectItem value="rejected">Rejected</SelectItem>
|
|
187
|
+
</SelectContent>
|
|
188
|
+
</Select>
|
|
189
|
+
<span className="text-sm text-muted-foreground ml-auto">
|
|
190
|
+
{memories.length} {memories.length === 1 ? "memory" : "memories"}
|
|
191
|
+
</span>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{/* Table */}
|
|
195
|
+
<div className="rounded-lg border">
|
|
196
|
+
<Table>
|
|
197
|
+
<TableHeader>
|
|
198
|
+
<TableRow>
|
|
199
|
+
<TableHead className="w-[40%]">Content</TableHead>
|
|
200
|
+
<TableHead>Category</TableHead>
|
|
201
|
+
<TableHead>Confidence</TableHead>
|
|
202
|
+
<TableHead className="text-right">Accesses</TableHead>
|
|
203
|
+
<TableHead>Last Accessed</TableHead>
|
|
204
|
+
<TableHead>Status</TableHead>
|
|
205
|
+
<TableHead className="text-right">Actions</TableHead>
|
|
206
|
+
</TableRow>
|
|
207
|
+
</TableHeader>
|
|
208
|
+
<TableBody>
|
|
209
|
+
{loading ? (
|
|
210
|
+
<TableRow>
|
|
211
|
+
<TableCell colSpan={7} className="text-center py-8 text-muted-foreground">
|
|
212
|
+
Loading...
|
|
213
|
+
</TableCell>
|
|
214
|
+
</TableRow>
|
|
215
|
+
) : (
|
|
216
|
+
memories.map((memory) => (
|
|
217
|
+
<TableRow key={memory.id}>
|
|
218
|
+
<TableCell className="max-w-[300px]">
|
|
219
|
+
<p className="text-sm truncate" title={memory.content}>
|
|
220
|
+
{memory.content}
|
|
221
|
+
</p>
|
|
222
|
+
</TableCell>
|
|
223
|
+
<TableCell>
|
|
224
|
+
<Badge variant={CATEGORY_VARIANTS[memory.category] ?? "outline"}>
|
|
225
|
+
{memory.category}
|
|
226
|
+
</Badge>
|
|
227
|
+
</TableCell>
|
|
228
|
+
<TableCell>
|
|
229
|
+
{editingId === memory.id ? (
|
|
230
|
+
<div className="flex items-center gap-1">
|
|
231
|
+
<Input
|
|
232
|
+
type="number"
|
|
233
|
+
min={0}
|
|
234
|
+
max={1000}
|
|
235
|
+
value={editConfidence}
|
|
236
|
+
onChange={(e) => setEditConfidence(e.target.value)}
|
|
237
|
+
className="w-20 h-7 text-xs"
|
|
238
|
+
/>
|
|
239
|
+
<Button
|
|
240
|
+
size="icon"
|
|
241
|
+
variant="ghost"
|
|
242
|
+
className="h-6 w-6"
|
|
243
|
+
onClick={() => handleSaveConfidence(memory.id)}
|
|
244
|
+
>
|
|
245
|
+
<Check className="h-3 w-3" />
|
|
246
|
+
</Button>
|
|
247
|
+
</div>
|
|
248
|
+
) : (
|
|
249
|
+
<div className="flex items-center gap-2">
|
|
250
|
+
<div className="w-16 h-1.5 rounded-full bg-muted overflow-hidden">
|
|
251
|
+
<div
|
|
252
|
+
className="h-full bg-primary rounded-full"
|
|
253
|
+
style={{ width: `${(memory.confidence / 1000) * 100}%` }}
|
|
254
|
+
/>
|
|
255
|
+
</div>
|
|
256
|
+
<span className="text-xs text-muted-foreground">
|
|
257
|
+
{Math.round((memory.confidence / 1000) * 100)}%
|
|
258
|
+
</span>
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
</TableCell>
|
|
262
|
+
<TableCell className="text-right text-sm">
|
|
263
|
+
{memory.accessCount}
|
|
264
|
+
</TableCell>
|
|
265
|
+
<TableCell className="text-sm text-muted-foreground">
|
|
266
|
+
{formatDate(memory.lastAccessedAt)}
|
|
267
|
+
</TableCell>
|
|
268
|
+
<TableCell>
|
|
269
|
+
<Badge variant={STATUS_VARIANTS[memory.status] ?? "outline"}>
|
|
270
|
+
{memory.status}
|
|
271
|
+
</Badge>
|
|
272
|
+
</TableCell>
|
|
273
|
+
<TableCell className="text-right">
|
|
274
|
+
<div className="flex items-center justify-end gap-1">
|
|
275
|
+
<Button
|
|
276
|
+
size="icon"
|
|
277
|
+
variant="ghost"
|
|
278
|
+
className="h-7 w-7"
|
|
279
|
+
title="Edit confidence"
|
|
280
|
+
onClick={() => {
|
|
281
|
+
setEditingId(memory.id);
|
|
282
|
+
setEditConfidence(String(memory.confidence));
|
|
283
|
+
}}
|
|
284
|
+
>
|
|
285
|
+
<Pencil className="h-3 w-3" />
|
|
286
|
+
</Button>
|
|
287
|
+
<Button
|
|
288
|
+
size="icon"
|
|
289
|
+
variant="ghost"
|
|
290
|
+
className="h-7 w-7"
|
|
291
|
+
title="Archive"
|
|
292
|
+
onClick={() => handleArchive(memory.id)}
|
|
293
|
+
>
|
|
294
|
+
<Archive className="h-3 w-3" />
|
|
295
|
+
</Button>
|
|
296
|
+
<Button
|
|
297
|
+
size="icon"
|
|
298
|
+
variant="ghost"
|
|
299
|
+
className="h-7 w-7 text-destructive"
|
|
300
|
+
title="Reject"
|
|
301
|
+
onClick={() => handleReject(memory.id)}
|
|
302
|
+
>
|
|
303
|
+
<XCircle className="h-3 w-3" />
|
|
304
|
+
</Button>
|
|
305
|
+
</div>
|
|
306
|
+
</TableCell>
|
|
307
|
+
</TableRow>
|
|
308
|
+
))
|
|
309
|
+
)}
|
|
310
|
+
</TableBody>
|
|
311
|
+
</Table>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
);
|
|
315
|
+
}
|
|
@@ -236,7 +236,7 @@ export function LearnedContextPanel({ profileId }: LearnedContextPanelProps) {
|
|
|
236
236
|
<History className="h-3 w-3" />
|
|
237
237
|
Version History
|
|
238
238
|
</div>
|
|
239
|
-
<div className="max-h-
|
|
239
|
+
<div className="max-h-[32rem] space-y-2 overflow-y-auto">
|
|
240
240
|
{entries.map((entry) => {
|
|
241
241
|
const { row, snapshotContent, derivedDiff } = entry;
|
|
242
242
|
const badgeConfig = CHANGE_TYPE_BADGE[row.changeType] ?? {
|
|
@@ -327,13 +327,13 @@ export function LearnedContextPanel({ profileId }: LearnedContextPanelProps) {
|
|
|
327
327
|
</div>
|
|
328
328
|
|
|
329
329
|
{snapshotContent ? (
|
|
330
|
-
<div className={`${PROSE_NOTIFICATION} max-h-
|
|
330
|
+
<div className={`${PROSE_NOTIFICATION} max-h-48 overflow-auto rounded-md bg-background/50 p-2`}>
|
|
331
331
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
|
332
332
|
{snapshotContent}
|
|
333
333
|
</ReactMarkdown>
|
|
334
334
|
</div>
|
|
335
335
|
) : row.diff ? (
|
|
336
|
-
<div className={`${PROSE_NOTIFICATION} max-h-
|
|
336
|
+
<div className={`${PROSE_NOTIFICATION} max-h-40 overflow-auto rounded-md bg-background/50 p-2`}>
|
|
337
337
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
|
338
338
|
{row.diff}
|
|
339
339
|
</ReactMarkdown>
|
|
@@ -353,7 +353,7 @@ export function LearnedContextPanel({ profileId }: LearnedContextPanelProps) {
|
|
|
353
353
|
: `vs v${derivedDiff.previousVersion}`}
|
|
354
354
|
</span>
|
|
355
355
|
</div>
|
|
356
|
-
<div className="max-h-
|
|
356
|
+
<div className="max-h-64 space-y-1 overflow-y-auto font-mono text-xs">
|
|
357
357
|
{derivedDiff.lines.map((line, index) => {
|
|
358
358
|
const toneClass =
|
|
359
359
|
line.kind === "added"
|