stagent 0.9.5 → 0.10.0
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 +5 -42
- package/dist/cli.js +42 -18
- package/docs/.coverage-gaps.json +13 -55
- package/docs/.last-generated +1 -1
- package/docs/features/provider-runtimes.md +4 -0
- package/docs/features/schedules.md +32 -4
- package/docs/features/settings.md +28 -5
- package/docs/features/tables.md +9 -2
- package/docs/features/workflows.md +10 -4
- package/docs/journeys/developer.md +15 -1
- package/docs/journeys/personal-use.md +21 -4
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
- package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
- package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
- package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
- package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
- package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
- package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
- package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
- package/package.json +3 -2
- package/src/__tests__/instrumentation-smoke.test.ts +15 -0
- package/src/app/analytics/page.tsx +1 -21
- package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
- package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
- package/src/app/api/instance/config/route.ts +41 -0
- package/src/app/api/instance/init/route.ts +34 -0
- package/src/app/api/instance/upgrade/check/route.ts +26 -0
- package/src/app/api/instance/upgrade/route.ts +96 -0
- package/src/app/api/instance/upgrade/status/route.ts +35 -0
- package/src/app/api/memory/route.ts +0 -11
- package/src/app/api/notifications/route.ts +4 -2
- package/src/app/api/projects/[id]/route.ts +5 -155
- package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
- package/src/app/api/schedules/[id]/execute/route.ts +111 -0
- package/src/app/api/schedules/[id]/route.ts +9 -1
- package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
- package/src/app/api/schedules/route.ts +3 -12
- package/src/app/api/settings/openai/login/route.ts +22 -0
- package/src/app/api/settings/openai/logout/route.ts +7 -0
- package/src/app/api/settings/openai/route.ts +21 -1
- package/src/app/api/settings/providers/route.ts +35 -8
- package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
- package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
- package/src/app/api/tables/[id]/enrich/route.ts +147 -0
- package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
- package/src/app/api/tasks/[id]/execute/route.ts +0 -21
- package/src/app/api/workflows/[id]/resume/route.ts +59 -0
- package/src/app/api/workflows/[id]/status/route.ts +22 -8
- package/src/app/api/workspace/context/route.ts +2 -0
- package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
- package/src/app/chat/page.tsx +11 -0
- package/src/app/inbox/page.tsx +12 -5
- package/src/app/layout.tsx +42 -21
- package/src/app/page.tsx +0 -2
- package/src/app/settings/page.tsx +6 -9
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +408 -0
- package/src/components/chat/chat-command-popover.tsx +2 -2
- package/src/components/chat/chat-input.tsx +2 -3
- package/src/components/chat/chat-session-provider.tsx +720 -0
- package/src/components/chat/chat-shell.tsx +92 -401
- package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
- package/src/components/instance/instance-section.tsx +382 -0
- package/src/components/instance/upgrade-badge.tsx +219 -0
- package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
- package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
- package/src/components/notifications/batch-proposal-review.tsx +20 -5
- package/src/components/notifications/inbox-list.tsx +11 -2
- package/src/components/notifications/notification-item.tsx +56 -2
- package/src/components/notifications/pending-approval-host.tsx +56 -37
- package/src/components/schedules/schedule-create-sheet.tsx +19 -1
- package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
- package/src/components/schedules/schedule-form.tsx +31 -0
- package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
- package/src/components/settings/auth-method-selector.tsx +19 -4
- package/src/components/settings/auth-status-badge.tsx +28 -3
- package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
- package/src/components/settings/openai-runtime-section.tsx +7 -1
- package/src/components/settings/providers-runtimes-section.tsx +138 -19
- package/src/components/shared/app-sidebar.tsx +4 -3
- package/src/components/shared/command-palette.tsx +4 -5
- package/src/components/shared/theme-toggle.tsx +5 -24
- package/src/components/shared/workspace-indicator.tsx +61 -2
- package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
- package/src/components/tables/table-create-sheet.tsx +4 -0
- package/src/components/tables/table-enrichment-runs.tsx +103 -0
- package/src/components/tables/table-enrichment-sheet.tsx +538 -0
- package/src/components/tables/table-spreadsheet.tsx +29 -5
- package/src/components/tables/table-toolbar.tsx +10 -1
- package/src/components/tasks/kanban-board.tsx +1 -0
- package/src/components/tasks/kanban-column.tsx +53 -14
- package/src/components/tasks/task-bento-grid.tsx +19 -0
- package/src/components/tasks/task-card.tsx +26 -3
- package/src/components/tasks/task-chip-bar.tsx +24 -0
- package/src/components/tasks/task-result-renderer.tsx +1 -1
- package/src/components/workflows/delay-step-body.tsx +109 -0
- package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
- package/src/components/workflows/loop-status-view.tsx +1 -1
- package/src/components/workflows/shared/step-result.tsx +78 -0
- package/src/components/workflows/shared/workflow-header.tsx +141 -0
- package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
- package/src/components/workflows/swarm-dashboard.tsx +2 -15
- package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
- package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
- package/src/components/workflows/workflow-form-view.tsx +133 -16
- package/src/components/workflows/workflow-status-view.tsx +30 -740
- package/src/instrumentation-node.ts +94 -0
- package/src/instrumentation.ts +4 -48
- package/src/lib/agents/__tests__/claude-agent.test.ts +199 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
- package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
- package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
- package/src/lib/agents/claude-agent.ts +155 -18
- package/src/lib/agents/execution-manager.ts +0 -35
- package/src/lib/agents/learned-context.ts +0 -12
- package/src/lib/agents/learning-session.ts +18 -5
- package/src/lib/agents/profiles/__tests__/registry.test.ts +6 -4
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +70 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +32 -0
- package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
- package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
- package/src/lib/agents/runtime/openai-codex.ts +29 -60
- package/src/lib/agents/runtime/types.ts +8 -0
- package/src/lib/book/chapter-mapping.ts +11 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
- package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
- package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
- package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
- package/src/lib/chat/active-streams.ts +27 -0
- package/src/lib/chat/codex-engine.ts +16 -17
- package/src/lib/chat/context-builder.ts +5 -3
- package/src/lib/chat/engine.ts +50 -3
- package/src/lib/chat/reconcile.ts +117 -0
- package/src/lib/chat/stagent-tools.ts +1 -0
- package/src/lib/chat/stream-telemetry.ts +132 -0
- package/src/lib/chat/suggested-prompts.ts +28 -1
- package/src/lib/chat/system-prompt.ts +26 -1
- package/src/lib/chat/tool-catalog.ts +2 -1
- package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
- package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +352 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +217 -0
- package/src/lib/chat/tools/document-tools.ts +29 -13
- package/src/lib/chat/tools/helpers.ts +39 -0
- package/src/lib/chat/tools/notification-tools.ts +9 -5
- package/src/lib/chat/tools/project-tools.ts +33 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -11
- package/src/lib/chat/tools/table-tools.ts +71 -0
- package/src/lib/chat/tools/task-tools.ts +84 -20
- package/src/lib/chat/tools/workflow-tools.ts +234 -32
- package/src/lib/constants/settings.ts +8 -18
- package/src/lib/data/__tests__/clear.test.ts +56 -2
- package/src/lib/data/clear.ts +20 -15
- package/src/lib/data/delete-project.ts +171 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
- package/src/lib/db/bootstrap.ts +45 -16
- package/src/lib/db/index.ts +5 -0
- package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
- package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
- package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
- package/src/lib/db/migrations/0026_drop_license.sql +3 -0
- package/src/lib/db/migrations/meta/_journal.json +21 -0
- package/src/lib/db/schema.ts +68 -23
- package/src/lib/environment/workspace-context.ts +13 -1
- package/src/lib/import/dedup.ts +4 -54
- package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
- package/src/lib/instance/__tests__/detect.test.ts +115 -0
- package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
- package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
- package/src/lib/instance/__tests__/settings.test.ts +83 -0
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +131 -0
- package/src/lib/instance/bootstrap.ts +270 -0
- package/src/lib/instance/detect.ts +49 -0
- package/src/lib/instance/fingerprint.ts +78 -0
- package/src/lib/instance/git-ops.ts +95 -0
- package/src/lib/instance/settings.ts +61 -0
- package/src/lib/instance/types.ts +77 -0
- package/src/lib/instance/upgrade-poller.ts +153 -0
- package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
- package/src/lib/notifications/visibility.ts +33 -0
- package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
- package/src/lib/schedules/__tests__/config.test.ts +62 -0
- package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
- package/src/lib/schedules/__tests__/integration.test.ts +82 -0
- package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
- package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
- package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
- package/src/lib/schedules/collision-check.ts +105 -0
- package/src/lib/schedules/config.ts +53 -0
- package/src/lib/schedules/scheduler.ts +232 -13
- package/src/lib/schedules/slot-claim.ts +105 -0
- package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
- package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
- package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
- package/src/lib/settings/openai-auth.ts +105 -10
- package/src/lib/settings/openai-login-manager.ts +260 -0
- package/src/lib/settings/runtime-setup.ts +14 -4
- package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
- package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
- package/src/lib/tables/enrichment-planner.ts +454 -0
- package/src/lib/tables/enrichment.ts +328 -0
- package/src/lib/tables/query-builder.ts +5 -2
- package/src/lib/tables/trigger-evaluator.ts +3 -2
- package/src/lib/theme.ts +71 -0
- package/src/lib/usage/ledger.ts +2 -18
- package/src/lib/util/__tests__/similarity.test.ts +106 -0
- package/src/lib/util/similarity.ts +77 -0
- package/src/lib/utils/format-timestamp.ts +24 -0
- package/src/lib/utils/stagent-paths.ts +12 -0
- package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
- package/src/lib/validators/__tests__/settings.test.ts +10 -0
- package/src/lib/validators/blueprint.ts +70 -9
- package/src/lib/validators/profile.ts +2 -2
- package/src/lib/validators/settings.ts +3 -1
- package/src/lib/workflows/__tests__/delay.test.ts +196 -0
- package/src/lib/workflows/__tests__/engine.test.ts +8 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
- package/src/lib/workflows/blueprints/instantiator.ts +22 -1
- package/src/lib/workflows/blueprints/types.ts +10 -2
- package/src/lib/workflows/delay.ts +106 -0
- package/src/lib/workflows/engine.ts +207 -4
- package/src/lib/workflows/loop-executor.ts +349 -24
- package/src/lib/workflows/post-action.ts +91 -0
- package/src/lib/workflows/types.ts +166 -1
- package/src/app/api/license/checkout/route.ts +0 -28
- package/src/app/api/license/portal/route.ts +0 -26
- package/src/app/api/license/route.ts +0 -89
- package/src/app/api/license/usage/route.ts +0 -63
- package/src/app/api/marketplace/browse/route.ts +0 -15
- package/src/app/api/marketplace/import/route.ts +0 -28
- package/src/app/api/marketplace/publish/route.ts +0 -40
- package/src/app/api/onboarding/email/route.ts +0 -53
- package/src/app/api/settings/telemetry/route.ts +0 -14
- package/src/app/api/sync/export/route.ts +0 -54
- package/src/app/api/sync/restore/route.ts +0 -37
- package/src/app/api/sync/sessions/route.ts +0 -24
- package/src/app/auth/callback/route.ts +0 -73
- package/src/app/marketplace/page.tsx +0 -19
- package/src/components/analytics/analytics-gate-card.tsx +0 -101
- package/src/components/marketplace/blueprint-card.tsx +0 -61
- package/src/components/marketplace/marketplace-browser.tsx +0 -131
- package/src/components/onboarding/email-capture-card.tsx +0 -104
- package/src/components/settings/activation-form.tsx +0 -95
- package/src/components/settings/cloud-account-section.tsx +0 -147
- package/src/components/settings/cloud-sync-section.tsx +0 -155
- package/src/components/settings/subscription-section.tsx +0 -410
- package/src/components/settings/telemetry-section.tsx +0 -80
- package/src/components/shared/premium-gate-overlay.tsx +0 -50
- package/src/components/shared/schedule-gate-dialog.tsx +0 -64
- package/src/components/shared/upgrade-banner.tsx +0 -112
- package/src/hooks/use-supabase-auth.ts +0 -79
- package/src/lib/billing/email.ts +0 -54
- package/src/lib/billing/products.ts +0 -80
- package/src/lib/billing/stripe.ts +0 -101
- package/src/lib/cloud/supabase-browser.ts +0 -32
- package/src/lib/cloud/supabase-client.ts +0 -56
- package/src/lib/license/__tests__/features.test.ts +0 -56
- package/src/lib/license/__tests__/key-format.test.ts +0 -88
- package/src/lib/license/__tests__/manager.test.ts +0 -64
- package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
- package/src/lib/license/cloud-validation.ts +0 -60
- package/src/lib/license/features.ts +0 -44
- package/src/lib/license/key-format.ts +0 -101
- package/src/lib/license/limit-check.ts +0 -111
- package/src/lib/license/limit-queries.ts +0 -51
- package/src/lib/license/manager.ts +0 -345
- package/src/lib/license/notifications.ts +0 -59
- package/src/lib/license/tier-limits.ts +0 -71
- package/src/lib/marketplace/marketplace-client.ts +0 -107
- package/src/lib/sync/cloud-sync.ts +0 -235
- package/src/lib/telemetry/conversion-events.ts +0 -71
- package/src/lib/telemetry/queue.ts +0 -122
- package/src/lib/validators/license.ts +0 -33
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { ArrowUpCircle } from "lucide-react";
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogHeader,
|
|
10
|
+
DialogTitle,
|
|
11
|
+
DialogDescription,
|
|
12
|
+
DialogFooter,
|
|
13
|
+
DialogTrigger,
|
|
14
|
+
} from "@/components/ui/dialog";
|
|
15
|
+
import { Button } from "@/components/ui/button";
|
|
16
|
+
import type { UpgradeState } from "@/lib/instance/types";
|
|
17
|
+
|
|
18
|
+
interface InstanceConfig {
|
|
19
|
+
instanceId: string;
|
|
20
|
+
branchName: string;
|
|
21
|
+
isPrivateInstance: boolean;
|
|
22
|
+
createdAt: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ConfigResponse {
|
|
26
|
+
devMode?: boolean;
|
|
27
|
+
config: InstanceConfig | null;
|
|
28
|
+
upgrade: UpgradeState | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type StatusResponse = UpgradeState & { devMode?: boolean };
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Sidebar upgrade badge + pre-flight modal combined into a single Client
|
|
35
|
+
* Component. Fetches status on mount and every 5 minutes; renders nothing
|
|
36
|
+
* when no upgrade is available. When clicked, opens the pre-flight modal
|
|
37
|
+
* and loads the full config for the fact panel.
|
|
38
|
+
*
|
|
39
|
+
* Combined into one component because Next.js 16's stricter client/server
|
|
40
|
+
* boundary rules reject passing callback props between separately-imported
|
|
41
|
+
* client components unless they're Server Actions. Bundling the two here
|
|
42
|
+
* preserves the spec's separation of concerns at the design level while
|
|
43
|
+
* satisfying the framework.
|
|
44
|
+
*/
|
|
45
|
+
export function UpgradeBadge() {
|
|
46
|
+
const router = useRouter();
|
|
47
|
+
const [state, setState] = useState<StatusResponse | null>(null);
|
|
48
|
+
const [open, setOpen] = useState(false);
|
|
49
|
+
const [config, setConfig] = useState<ConfigResponse | null>(null);
|
|
50
|
+
const [loading, setLoading] = useState(false);
|
|
51
|
+
const [starting, setStarting] = useState(false);
|
|
52
|
+
const [error, setError] = useState<string | null>(null);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
let cancelled = false;
|
|
56
|
+
|
|
57
|
+
async function fetchStatus() {
|
|
58
|
+
try {
|
|
59
|
+
const res = await fetch("/api/instance/upgrade/status", {
|
|
60
|
+
cache: "no-store",
|
|
61
|
+
});
|
|
62
|
+
if (!res.ok) return;
|
|
63
|
+
const data = (await res.json()) as StatusResponse;
|
|
64
|
+
if (!cancelled) setState(data);
|
|
65
|
+
} catch {
|
|
66
|
+
// Silent — the badge is ambient; status fetch failures should not
|
|
67
|
+
// produce UI noise. Persistent poll failures surface as a warning
|
|
68
|
+
// variant via state.pollFailureCount >= 3.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fetchStatus();
|
|
73
|
+
const interval = setInterval(fetchStatus, 5 * 60 * 1000);
|
|
74
|
+
// Refetch when the tab regains focus — picks up DB changes made by the
|
|
75
|
+
// hourly poller or by a manual "Check for upgrades" click while the user
|
|
76
|
+
// was running git commands in the terminal.
|
|
77
|
+
window.addEventListener("focus", fetchStatus);
|
|
78
|
+
return () => {
|
|
79
|
+
cancelled = true;
|
|
80
|
+
clearInterval(interval);
|
|
81
|
+
window.removeEventListener("focus", fetchStatus);
|
|
82
|
+
};
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!open) return;
|
|
87
|
+
let cancelled = false;
|
|
88
|
+
setLoading(true);
|
|
89
|
+
setError(null);
|
|
90
|
+
(async () => {
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetch("/api/instance/config", { cache: "no-store" });
|
|
93
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
94
|
+
const data = (await res.json()) as ConfigResponse;
|
|
95
|
+
if (!cancelled) setConfig(data);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (!cancelled) setError(err instanceof Error ? err.message : String(err));
|
|
98
|
+
} finally {
|
|
99
|
+
if (!cancelled) setLoading(false);
|
|
100
|
+
}
|
|
101
|
+
})();
|
|
102
|
+
return () => {
|
|
103
|
+
cancelled = true;
|
|
104
|
+
};
|
|
105
|
+
}, [open]);
|
|
106
|
+
|
|
107
|
+
async function startUpgrade() {
|
|
108
|
+
setStarting(true);
|
|
109
|
+
setError(null);
|
|
110
|
+
try {
|
|
111
|
+
const res = await fetch("/api/instance/upgrade", { method: "POST" });
|
|
112
|
+
if (!res.ok) {
|
|
113
|
+
const body = await res.json().catch(() => ({}));
|
|
114
|
+
throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
115
|
+
}
|
|
116
|
+
const data = (await res.json()) as { taskId: string };
|
|
117
|
+
setOpen(false);
|
|
118
|
+
router.push(`/tasks/${data.taskId}`);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
121
|
+
} finally {
|
|
122
|
+
setStarting(false);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!state || state.devMode || !state.upgradeAvailable) return null;
|
|
127
|
+
|
|
128
|
+
const failing = state.pollFailureCount >= 3;
|
|
129
|
+
const count = state.commitsBehind;
|
|
130
|
+
const label = failing
|
|
131
|
+
? "Check failing"
|
|
132
|
+
: `${count} update${count === 1 ? "" : "s"}`;
|
|
133
|
+
const tooltip = failing
|
|
134
|
+
? "Upgrade check failing — click to retry"
|
|
135
|
+
: `${count} upstream update${count === 1 ? "" : "s"} ready to merge`;
|
|
136
|
+
const buttonClass = failing
|
|
137
|
+
? "h-7 px-2 rounded-md border border-amber-500/40 bg-amber-500/10 text-[11px] font-medium text-amber-700 dark:text-amber-400 hover:bg-amber-500/20 transition-colors cursor-pointer inline-flex items-center gap-1.5 group-data-[collapsible=icon]:hidden"
|
|
138
|
+
: "h-7 px-2 rounded-md border border-blue-500/40 bg-blue-500/10 text-[11px] font-medium text-blue-700 dark:text-blue-400 hover:bg-blue-500/20 transition-colors cursor-pointer inline-flex items-center gap-1.5 group-data-[collapsible=icon]:hidden";
|
|
139
|
+
|
|
140
|
+
const modalUpgrade = config?.upgrade ?? null;
|
|
141
|
+
const modalCount = modalUpgrade?.commitsBehind ?? count;
|
|
142
|
+
const lastUpgradeText = modalUpgrade?.lastSuccessfulUpgradeAt
|
|
143
|
+
? new Date(modalUpgrade.lastSuccessfulUpgradeAt * 1000).toLocaleString()
|
|
144
|
+
: "never";
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
148
|
+
<DialogTrigger asChild>
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
aria-label={tooltip}
|
|
152
|
+
title={tooltip}
|
|
153
|
+
className={buttonClass}
|
|
154
|
+
onClick={(e) => {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
e.stopPropagation();
|
|
157
|
+
setOpen(true);
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
160
|
+
<ArrowUpCircle className="h-3 w-3" aria-hidden />
|
|
161
|
+
<span>{label}</span>
|
|
162
|
+
</button>
|
|
163
|
+
</DialogTrigger>
|
|
164
|
+
<DialogContent className="sm:max-w-lg">
|
|
165
|
+
<DialogHeader>
|
|
166
|
+
<DialogTitle>Upgrade available</DialogTitle>
|
|
167
|
+
<DialogDescription>
|
|
168
|
+
{modalCount} commit{modalCount === 1 ? "" : "s"} ready to merge into{" "}
|
|
169
|
+
<code className="font-mono text-xs">
|
|
170
|
+
{config?.config?.branchName ?? "…"}
|
|
171
|
+
</code>
|
|
172
|
+
</DialogDescription>
|
|
173
|
+
</DialogHeader>
|
|
174
|
+
|
|
175
|
+
{loading && (
|
|
176
|
+
<div className="py-4 text-sm text-muted-foreground">Loading instance state…</div>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{error && (
|
|
180
|
+
<div className="rounded-md border border-destructive/30 bg-destructive/10 p-3 text-sm text-destructive">
|
|
181
|
+
{error}
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
{config && !loading && (
|
|
186
|
+
<div className="space-y-3 py-2">
|
|
187
|
+
<div className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm">
|
|
188
|
+
<span className="text-muted-foreground">Branch</span>
|
|
189
|
+
<code className="font-mono text-xs">{config.config?.branchName ?? "—"}</code>
|
|
190
|
+
<span className="text-muted-foreground">Data directory</span>
|
|
191
|
+
<code className="font-mono text-xs break-all">
|
|
192
|
+
{config.config?.isPrivateInstance ? "custom" : "default"}
|
|
193
|
+
</code>
|
|
194
|
+
<span className="text-muted-foreground">Commits behind</span>
|
|
195
|
+
<span>{modalCount}</span>
|
|
196
|
+
<span className="text-muted-foreground">Last successful upgrade</span>
|
|
197
|
+
<span>{lastUpgradeText}</span>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
201
|
+
Stagent will stash any uncommitted work, merge <code className="font-mono">main</code> into{" "}
|
|
202
|
+
<code className="font-mono">{config.config?.branchName ?? "your branch"}</code>, install any new
|
|
203
|
+
dependencies, and ask you to resolve conflicts if any appear.
|
|
204
|
+
</p>
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
<DialogFooter>
|
|
209
|
+
<Button variant="ghost" onClick={() => setOpen(false)} disabled={starting}>
|
|
210
|
+
Cancel
|
|
211
|
+
</Button>
|
|
212
|
+
<Button onClick={startUpgrade} disabled={loading || starting || !config?.config}>
|
|
213
|
+
{starting ? "Starting…" : "Start upgrade"}
|
|
214
|
+
</Button>
|
|
215
|
+
</DialogFooter>
|
|
216
|
+
</DialogContent>
|
|
217
|
+
</Dialog>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { BatchProposalReview } from "@/components/notifications/batch-proposal-review";
|
|
5
|
+
|
|
6
|
+
const { toastError } = vi.hoisted(() => ({
|
|
7
|
+
toastError: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock("sonner", () => ({
|
|
11
|
+
toast: {
|
|
12
|
+
error: toastError,
|
|
13
|
+
},
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe("batch proposal review", () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
vi.unstubAllGlobals();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("optimistically resolves the batch before the request finishes", async () => {
|
|
26
|
+
const onResponded = vi.fn();
|
|
27
|
+
let resolveFetch: ((value: Response) => void) | null = null;
|
|
28
|
+
|
|
29
|
+
vi.stubGlobal(
|
|
30
|
+
"fetch",
|
|
31
|
+
vi.fn().mockImplementation(
|
|
32
|
+
() =>
|
|
33
|
+
new Promise<Response>((resolve) => {
|
|
34
|
+
resolveFetch = resolve;
|
|
35
|
+
})
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
render(
|
|
40
|
+
<BatchProposalReview
|
|
41
|
+
proposalIds={["p1", "p2"]}
|
|
42
|
+
profileIds={["general"]}
|
|
43
|
+
body="Batch summary"
|
|
44
|
+
onResponded={onResponded}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
fireEvent.click(screen.getByRole("button", { name: /approve all/i }));
|
|
49
|
+
|
|
50
|
+
expect(onResponded).toHaveBeenCalledTimes(1);
|
|
51
|
+
|
|
52
|
+
resolveFetch?.(
|
|
53
|
+
new Response(JSON.stringify({ action: "approve", count: 2 }), {
|
|
54
|
+
status: 200,
|
|
55
|
+
headers: { "Content-Type": "application/json" },
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
await waitFor(() => {
|
|
60
|
+
expect(screen.getByText("2 proposals approved")).toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("restores server truth when the batch request fails", async () => {
|
|
65
|
+
const onResponded = vi.fn();
|
|
66
|
+
const onRequestFailed = vi.fn();
|
|
67
|
+
|
|
68
|
+
vi.stubGlobal(
|
|
69
|
+
"fetch",
|
|
70
|
+
vi.fn().mockRejectedValue(new Error("Batch approval failed"))
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
render(
|
|
74
|
+
<BatchProposalReview
|
|
75
|
+
proposalIds={["p1", "p2"]}
|
|
76
|
+
profileIds={["general"]}
|
|
77
|
+
body="Batch summary"
|
|
78
|
+
onResponded={onResponded}
|
|
79
|
+
onRequestFailed={onRequestFailed}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
fireEvent.click(screen.getByRole("button", { name: /approve all/i }));
|
|
84
|
+
|
|
85
|
+
expect(onResponded).toHaveBeenCalledTimes(1);
|
|
86
|
+
|
|
87
|
+
await waitFor(() => {
|
|
88
|
+
expect(onRequestFailed).toHaveBeenCalledTimes(1);
|
|
89
|
+
});
|
|
90
|
+
expect(toastError).toHaveBeenCalledWith("Batch approval failed");
|
|
91
|
+
expect(
|
|
92
|
+
screen.getByRole("button", { name: /approve all \(2\)/i })
|
|
93
|
+
).toBeInTheDocument();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { NotificationItem } from "@/components/notifications/notification-item";
|
|
5
|
+
|
|
6
|
+
const { push, contextReviewSpy, batchReviewSpy } = vi.hoisted(() => ({
|
|
7
|
+
push: vi.fn(),
|
|
8
|
+
contextReviewSpy: vi.fn(),
|
|
9
|
+
batchReviewSpy: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock("next/navigation", () => ({
|
|
13
|
+
useRouter: () => ({ push }),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock("@/components/profiles/context-proposal-review", () => ({
|
|
17
|
+
ContextProposalReview: (props: {
|
|
18
|
+
notificationId: string;
|
|
19
|
+
profileId: string;
|
|
20
|
+
proposedAdditions: string;
|
|
21
|
+
onResponded: () => void;
|
|
22
|
+
}) => {
|
|
23
|
+
contextReviewSpy(props);
|
|
24
|
+
return <div>Context proposal review</div>;
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
vi.mock("@/components/notifications/batch-proposal-review", () => ({
|
|
29
|
+
BatchProposalReview: (props: {
|
|
30
|
+
proposalIds: string[];
|
|
31
|
+
profileIds: string[];
|
|
32
|
+
body: string;
|
|
33
|
+
onResponded?: () => void;
|
|
34
|
+
}) => {
|
|
35
|
+
batchReviewSpy(props);
|
|
36
|
+
return <div>Batch proposal review</div>;
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
describe("notification item", () => {
|
|
41
|
+
it("renders context proposal review actions using the full additions payload", () => {
|
|
42
|
+
render(
|
|
43
|
+
<NotificationItem
|
|
44
|
+
notification={{
|
|
45
|
+
id: "notif-1",
|
|
46
|
+
taskId: null,
|
|
47
|
+
type: "context_proposal",
|
|
48
|
+
title: "Context proposal",
|
|
49
|
+
body: "truncated body",
|
|
50
|
+
read: false,
|
|
51
|
+
toolName: "general",
|
|
52
|
+
toolInput: JSON.stringify({
|
|
53
|
+
profileId: "general",
|
|
54
|
+
additions: "Full learned additions",
|
|
55
|
+
}),
|
|
56
|
+
response: null,
|
|
57
|
+
respondedAt: null,
|
|
58
|
+
createdAt: "2026-04-10T00:00:00.000Z",
|
|
59
|
+
}}
|
|
60
|
+
onUpdated={vi.fn()}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(screen.getByText("Context proposal review")).toBeInTheDocument();
|
|
65
|
+
expect(contextReviewSpy).toHaveBeenCalledWith(
|
|
66
|
+
expect.objectContaining({
|
|
67
|
+
notificationId: "notif-1",
|
|
68
|
+
profileId: "general",
|
|
69
|
+
proposedAdditions: "Full learned additions",
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("renders batch proposal review actions for workflow learning notifications", () => {
|
|
75
|
+
render(
|
|
76
|
+
<NotificationItem
|
|
77
|
+
notification={{
|
|
78
|
+
id: "notif-2",
|
|
79
|
+
taskId: null,
|
|
80
|
+
type: "context_proposal_batch",
|
|
81
|
+
title: "Workflow learning batch",
|
|
82
|
+
body: "Batch summary",
|
|
83
|
+
read: false,
|
|
84
|
+
toolName: "workflow-context-batch",
|
|
85
|
+
toolInput: JSON.stringify({
|
|
86
|
+
proposalIds: ["p1", "p2"],
|
|
87
|
+
profileIds: ["general", "researcher"],
|
|
88
|
+
}),
|
|
89
|
+
response: null,
|
|
90
|
+
respondedAt: null,
|
|
91
|
+
createdAt: "2026-04-10T00:00:00.000Z",
|
|
92
|
+
}}
|
|
93
|
+
onUpdated={vi.fn()}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(screen.getByText("Batch proposal review")).toBeInTheDocument();
|
|
98
|
+
expect(batchReviewSpy).toHaveBeenCalledWith(
|
|
99
|
+
expect.objectContaining({
|
|
100
|
+
proposalIds: ["p1", "p2"],
|
|
101
|
+
profileIds: ["general", "researcher"],
|
|
102
|
+
body: "Batch summary",
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
+
import { toast } from "sonner";
|
|
4
5
|
import { Button } from "@/components/ui/button";
|
|
5
6
|
import { Badge } from "@/components/ui/badge";
|
|
6
7
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
@@ -12,6 +13,7 @@ interface BatchProposalReviewProps {
|
|
|
12
13
|
profileIds: string[];
|
|
13
14
|
body: string;
|
|
14
15
|
onResponded?: () => void;
|
|
16
|
+
onRequestFailed?: () => void;
|
|
15
17
|
compact?: boolean;
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -20,6 +22,7 @@ export function BatchProposalReview({
|
|
|
20
22
|
profileIds,
|
|
21
23
|
body,
|
|
22
24
|
onResponded,
|
|
25
|
+
onRequestFailed,
|
|
23
26
|
compact = false,
|
|
24
27
|
}: BatchProposalReviewProps) {
|
|
25
28
|
const [loading, setLoading] = useState<"approve" | "reject" | null>(null);
|
|
@@ -31,18 +34,30 @@ export function BatchProposalReview({
|
|
|
31
34
|
|
|
32
35
|
async function handleBatchAction(action: "approve" | "reject") {
|
|
33
36
|
setLoading(action);
|
|
37
|
+
setResponded(true);
|
|
38
|
+
onResponded?.();
|
|
39
|
+
|
|
34
40
|
try {
|
|
35
41
|
const res = await fetch("/api/context/batch", {
|
|
36
42
|
method: "POST",
|
|
37
43
|
headers: { "Content-Type": "application/json" },
|
|
38
44
|
body: JSON.stringify({ proposalIds, action }),
|
|
39
45
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
onResponded?.();
|
|
46
|
+
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const data = await res.json().catch(() => null);
|
|
49
|
+
throw new Error(data?.error ?? `Failed to ${action} batch proposal`);
|
|
45
50
|
}
|
|
51
|
+
|
|
52
|
+
const data = await res.json();
|
|
53
|
+
setResult({ action: data.action, count: data.count });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
setResponded(false);
|
|
56
|
+
setResult(null);
|
|
57
|
+
toast.error(
|
|
58
|
+
error instanceof Error ? error.message : "Batch approval failed"
|
|
59
|
+
);
|
|
60
|
+
onRequestFailed?.();
|
|
46
61
|
} finally {
|
|
47
62
|
setLoading(null);
|
|
48
63
|
}
|
|
@@ -7,6 +7,7 @@ import { Eye, Inbox, RefreshCw, Trash2 } from "lucide-react";
|
|
|
7
7
|
import { toast } from "sonner";
|
|
8
8
|
import { NotificationItem } from "./notification-item";
|
|
9
9
|
import { EmptyState } from "@/components/shared/empty-state";
|
|
10
|
+
import { filterDefaultVisibleNotifications } from "@/lib/notifications/visibility";
|
|
10
11
|
|
|
11
12
|
interface Notification {
|
|
12
13
|
id: string;
|
|
@@ -28,12 +29,15 @@ export function InboxList({
|
|
|
28
29
|
initialNotifications: Notification[];
|
|
29
30
|
}) {
|
|
30
31
|
const [notifications, setNotifications] =
|
|
31
|
-
useState<Notification[]>(initialNotifications);
|
|
32
|
+
useState<Notification[]>(() => filterDefaultVisibleNotifications(initialNotifications));
|
|
32
33
|
const [tab, setTab] = useState("all");
|
|
33
34
|
|
|
34
35
|
const refresh = useCallback(async () => {
|
|
35
36
|
const res = await fetch("/api/notifications");
|
|
36
|
-
if (res.ok)
|
|
37
|
+
if (res.ok) {
|
|
38
|
+
const next = (await res.json()) as Notification[];
|
|
39
|
+
setNotifications(filterDefaultVisibleNotifications(next));
|
|
40
|
+
}
|
|
37
41
|
}, []);
|
|
38
42
|
|
|
39
43
|
// Poll every 10 seconds (consolidated from 3s inbox + 5s badge)
|
|
@@ -154,6 +158,11 @@ export function InboxList({
|
|
|
154
158
|
<NotificationItem
|
|
155
159
|
key={n.id}
|
|
156
160
|
notification={n}
|
|
161
|
+
onRemoved={(notificationId) =>
|
|
162
|
+
setNotifications((current) =>
|
|
163
|
+
current.filter((item) => item.id !== notificationId)
|
|
164
|
+
)
|
|
165
|
+
}
|
|
157
166
|
onUpdated={refresh}
|
|
158
167
|
/>
|
|
159
168
|
))
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
parseNotificationToolInput,
|
|
19
19
|
type PermissionToolInput,
|
|
20
20
|
} from "@/lib/notifications/permissions";
|
|
21
|
+
import { ContextProposalReview } from "@/components/profiles/context-proposal-review";
|
|
22
|
+
import { BatchProposalReview } from "./batch-proposal-review";
|
|
21
23
|
|
|
22
24
|
interface Notification {
|
|
23
25
|
id: string;
|
|
@@ -35,6 +37,7 @@ interface Notification {
|
|
|
35
37
|
|
|
36
38
|
interface NotificationItemProps {
|
|
37
39
|
notification: Notification;
|
|
40
|
+
onRemoved?: (notificationId: string) => void;
|
|
38
41
|
onUpdated: () => void;
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -104,7 +107,25 @@ function formatToolInput(
|
|
|
104
107
|
|
|
105
108
|
const navigableTypes = new Set(["task_completed", "task_failed", "permission_required", "agent_message"]);
|
|
106
109
|
|
|
107
|
-
|
|
110
|
+
function parseBatchToolInput(toolInput: PermissionToolInput | null): {
|
|
111
|
+
proposalIds: string[];
|
|
112
|
+
profileIds: string[];
|
|
113
|
+
} {
|
|
114
|
+
return {
|
|
115
|
+
proposalIds: Array.isArray(toolInput?.proposalIds)
|
|
116
|
+
? toolInput.proposalIds.filter((id): id is string => typeof id === "string")
|
|
117
|
+
: [],
|
|
118
|
+
profileIds: Array.isArray(toolInput?.profileIds)
|
|
119
|
+
? toolInput.profileIds.filter((id): id is string => typeof id === "string")
|
|
120
|
+
: [],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function NotificationItem({
|
|
125
|
+
notification,
|
|
126
|
+
onRemoved,
|
|
127
|
+
onUpdated,
|
|
128
|
+
}: NotificationItemProps) {
|
|
108
129
|
const router = useRouter();
|
|
109
130
|
const [toggling, setToggling] = useState(false);
|
|
110
131
|
const [dismissing, setDismissing] = useState(false);
|
|
@@ -212,7 +233,9 @@ export function NotificationItem({ notification, onUpdated }: NotificationItemPr
|
|
|
212
233
|
{/* Body for non-tool notifications */}
|
|
213
234
|
{notification.body &&
|
|
214
235
|
notification.type !== "permission_required" &&
|
|
215
|
-
notification.type !== "agent_message" &&
|
|
236
|
+
notification.type !== "agent_message" &&
|
|
237
|
+
notification.type !== "context_proposal" &&
|
|
238
|
+
notification.type !== "context_proposal_batch" && (
|
|
216
239
|
<div className="mt-1" onClick={(e) => e.stopPropagation()}>
|
|
217
240
|
<div
|
|
218
241
|
className={`${PROSE_NOTIFICATION} ${
|
|
@@ -270,6 +293,37 @@ export function NotificationItem({ notification, onUpdated }: NotificationItemPr
|
|
|
270
293
|
<FailureAction taskId={notification.taskId} onRetried={onUpdated} />
|
|
271
294
|
)}
|
|
272
295
|
|
|
296
|
+
{notification.type === "context_proposal" && (
|
|
297
|
+
<div className="mt-3" onClick={(e) => e.stopPropagation()}>
|
|
298
|
+
<ContextProposalReview
|
|
299
|
+
notificationId={notification.id}
|
|
300
|
+
profileId={
|
|
301
|
+
typeof parsedToolInput?.profileId === "string"
|
|
302
|
+
? parsedToolInput.profileId
|
|
303
|
+
: (notification.toolName ?? "")
|
|
304
|
+
}
|
|
305
|
+
proposedAdditions={
|
|
306
|
+
typeof parsedToolInput?.additions === "string"
|
|
307
|
+
? parsedToolInput.additions
|
|
308
|
+
: (notification.body ?? "")
|
|
309
|
+
}
|
|
310
|
+
onResponded={onUpdated}
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
314
|
+
|
|
315
|
+
{notification.type === "context_proposal_batch" && (
|
|
316
|
+
<div className="mt-3" onClick={(e) => e.stopPropagation()}>
|
|
317
|
+
<BatchProposalReview
|
|
318
|
+
proposalIds={parseBatchToolInput(parsedToolInput).proposalIds}
|
|
319
|
+
profileIds={parseBatchToolInput(parsedToolInput).profileIds}
|
|
320
|
+
body={notification.body ?? ""}
|
|
321
|
+
onResponded={() => onRemoved?.(notification.id)}
|
|
322
|
+
onRequestFailed={onUpdated}
|
|
323
|
+
/>
|
|
324
|
+
</div>
|
|
325
|
+
)}
|
|
326
|
+
|
|
273
327
|
<p className="text-xs text-muted-foreground mt-2">
|
|
274
328
|
{formatTimestamp(notification.createdAt)}
|
|
275
329
|
{notification.respondedAt && (
|