@agent-native/core 0.111.4 → 0.112.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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/package.json +3 -1
- package/corpus/core/src/cli/code-agent-executor.ts +17 -5
- package/corpus/core/src/cli/code-agent-runs.ts +43 -2
- package/corpus/core/src/cli/multi-frontier-runs.ts +841 -0
- package/corpus/core/src/coding-tools/index.ts +27 -8
- package/corpus/core/src/embeddings/index.ts +233 -0
- package/corpus/core/src/index.ts +2 -0
- package/corpus/core/src/search/index.ts +413 -0
- package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +48 -5
- package/corpus/templates/brain/.agents/skills/brain/SKILL.md +46 -14
- package/corpus/templates/brain/README.md +34 -3
- package/corpus/templates/brain/actions/approve-proposal.ts +2 -0
- package/corpus/templates/brain/actions/claim-distillation.ts +24 -27
- package/corpus/templates/brain/actions/create-source.ts +1 -1
- package/corpus/templates/brain/actions/get-knowledge.ts +6 -2
- package/corpus/templates/brain/actions/get-pilot-report.ts +44 -8
- package/corpus/templates/brain/actions/import-capture.ts +21 -9
- package/corpus/templates/brain/actions/import-transcript.ts +42 -14
- package/corpus/templates/brain/actions/list-captures.ts +10 -1
- package/corpus/templates/brain/actions/list-distillation-queue.ts +39 -14
- package/corpus/templates/brain/actions/list-projects.ts +51 -0
- package/corpus/templates/brain/actions/list-proposals.ts +80 -4
- package/corpus/templates/brain/actions/list-sources.ts +17 -3
- package/corpus/templates/brain/actions/manage-project.ts +118 -0
- package/corpus/templates/brain/actions/mark-capture-distilled.ts +68 -20
- package/corpus/templates/brain/actions/navigate.ts +1 -0
- package/corpus/templates/brain/actions/reject-proposal.ts +6 -1
- package/corpus/templates/brain/actions/resanitize-captures.ts +159 -33
- package/corpus/templates/brain/actions/review-proposal.ts +2 -0
- package/corpus/templates/brain/actions/search-everything.ts +10 -1
- package/corpus/templates/brain/actions/search-knowledge.ts +1 -1
- package/corpus/templates/brain/actions/set-settings.ts +36 -1
- package/corpus/templates/brain/actions/update-proposal.ts +2 -0
- package/corpus/templates/brain/app/hooks/use-distillation-bridge.ts +18 -9
- package/corpus/templates/brain/app/hooks/use-navigation-state.ts +3 -0
- package/corpus/templates/brain/app/i18n-data.ts +731 -2
- package/corpus/templates/brain/app/lib/brain.ts +33 -1
- package/corpus/templates/brain/app/routes/ops.tsx +47 -0
- package/corpus/templates/brain/app/routes/review.tsx +41 -2
- package/corpus/templates/brain/app/routes/search.tsx +33 -2
- package/corpus/templates/brain/app/routes/settings.tsx +158 -0
- package/corpus/templates/brain/app/routes/sources.tsx +31 -0
- package/corpus/templates/brain/changelog/2026-07-19-added-private-source-safe-semantic-search.md +6 -0
- package/corpus/templates/brain/jobs/process-ingest-queue.ts +270 -36
- package/corpus/templates/brain/package.json +1 -0
- package/corpus/templates/brain/server/db/index.ts +18 -0
- package/corpus/templates/brain/server/db/schema.ts +197 -1
- package/corpus/templates/brain/server/jobs/distillation-queue.ts +12 -5
- package/corpus/templates/brain/server/lib/audiences.ts +708 -0
- package/corpus/templates/brain/server/lib/brain-health.ts +251 -38
- package/corpus/templates/brain/server/lib/brain.ts +877 -50
- package/corpus/templates/brain/server/lib/capture-sanitization.ts +217 -53
- package/corpus/templates/brain/server/lib/connectors.ts +556 -70
- package/corpus/templates/brain/server/lib/demo.ts +17 -4
- package/corpus/templates/brain/server/lib/hybrid-search.ts +363 -0
- package/corpus/templates/brain/server/lib/ingest-queue.ts +104 -0
- package/corpus/templates/brain/server/lib/privacy-readiness.ts +24 -0
- package/corpus/templates/brain/server/lib/search-index-contracts.ts +72 -0
- package/corpus/templates/brain/server/lib/search-index.ts +714 -0
- package/corpus/templates/brain/server/lib/search.ts +120 -4
- package/corpus/templates/brain/server/lib/sensitivity-policy.ts +174 -0
- package/corpus/templates/brain/server/lib/slack-events.ts +281 -0
- package/corpus/templates/brain/server/onboarding.ts +29 -0
- package/corpus/templates/brain/server/plugins/db.ts +204 -0
- package/corpus/templates/brain/server/register-secrets.ts +46 -0
- package/corpus/templates/brain/server/routes/api/_agent-native/brain/ingest.post.ts +36 -16
- package/corpus/templates/brain/server/routes/api/_agent-native/brain/slack-events.post.ts +28 -0
- package/corpus/templates/brain/shared/types.ts +10 -0
- package/dist/cli/code-agent-executor.d.ts +8 -9
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +9 -3
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/code-agent-runs.d.ts +5 -0
- package/dist/cli/code-agent-runs.d.ts.map +1 -1
- package/dist/cli/code-agent-runs.js +33 -2
- package/dist/cli/code-agent-runs.js.map +1 -1
- package/dist/cli/multi-frontier-runs.d.ts +131 -0
- package/dist/cli/multi-frontier-runs.d.ts.map +1 -0
- package/dist/cli/multi-frontier-runs.js +529 -0
- package/dist/cli/multi-frontier-runs.js.map +1 -0
- package/dist/coding-tools/index.d.ts +1 -0
- package/dist/coding-tools/index.d.ts.map +1 -1
- package/dist/coding-tools/index.js +25 -7
- package/dist/coding-tools/index.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/embeddings/index.d.ts +24 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +172 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/search/index.d.ts +88 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +267 -0
- package/dist/search/index.js.map +1 -0
- package/package.json +3 -1
|
@@ -97,7 +97,13 @@ export interface ReviewItem {
|
|
|
97
97
|
url?: string | null;
|
|
98
98
|
timestampMs?: number | null;
|
|
99
99
|
}>;
|
|
100
|
-
status?:
|
|
100
|
+
status?:
|
|
101
|
+
| "pending"
|
|
102
|
+
| "queued"
|
|
103
|
+
| "approved"
|
|
104
|
+
| "rejected"
|
|
105
|
+
| "needs_changes"
|
|
106
|
+
| "quarantine";
|
|
101
107
|
visibility?: string;
|
|
102
108
|
reviewerNotes?: string | null;
|
|
103
109
|
createdBy?: string | null;
|
|
@@ -182,6 +188,8 @@ export interface SearchEverythingResult {
|
|
|
182
188
|
confidence?: number | null;
|
|
183
189
|
updatedAt?: string | null;
|
|
184
190
|
score?: number | null;
|
|
191
|
+
matchLane?: "semantic" | "keyword" | "hybrid" | string | null;
|
|
192
|
+
retrievalReason?: string | null;
|
|
185
193
|
}
|
|
186
194
|
|
|
187
195
|
export interface SearchEverythingResponse {
|
|
@@ -197,6 +205,20 @@ export interface SearchEverythingResponse {
|
|
|
197
205
|
};
|
|
198
206
|
}
|
|
199
207
|
|
|
208
|
+
export interface BrainProject {
|
|
209
|
+
id: string;
|
|
210
|
+
title: string;
|
|
211
|
+
description?: string | null;
|
|
212
|
+
visibility?: string | null;
|
|
213
|
+
sourceIds?: string[];
|
|
214
|
+
isDefault?: boolean;
|
|
215
|
+
updatedAt?: string | null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface BrainProjectsResponse {
|
|
219
|
+
projects?: BrainProject[];
|
|
220
|
+
}
|
|
221
|
+
|
|
200
222
|
export interface ReviewQueueResponse {
|
|
201
223
|
count?: number;
|
|
202
224
|
items?: ReviewItem[];
|
|
@@ -867,6 +889,11 @@ export interface BrainSettings {
|
|
|
867
889
|
requireCitations?: boolean;
|
|
868
890
|
autoArchiveResolved?: boolean;
|
|
869
891
|
notifyOnSourceErrors?: boolean;
|
|
892
|
+
privacyClassifierModel?: string;
|
|
893
|
+
privacyClassifierEngine?: string;
|
|
894
|
+
sensitivityCustomInstructions?: string;
|
|
895
|
+
publicChannelExclusionPatterns?: string[];
|
|
896
|
+
quarantineRetentionHours?: number;
|
|
870
897
|
}
|
|
871
898
|
|
|
872
899
|
export interface SettingsResponse {
|
|
@@ -951,6 +978,11 @@ export const defaultSettings: BrainSettings = {
|
|
|
951
978
|
requireCitations: true,
|
|
952
979
|
autoArchiveResolved: true,
|
|
953
980
|
notifyOnSourceErrors: true,
|
|
981
|
+
privacyClassifierModel: "",
|
|
982
|
+
privacyClassifierEngine: "",
|
|
983
|
+
sensitivityCustomInstructions: "",
|
|
984
|
+
publicChannelExclusionPatterns: [],
|
|
985
|
+
quarantineRetentionHours: 72,
|
|
954
986
|
};
|
|
955
987
|
|
|
956
988
|
export function viewFromPath(pathname: string): BrainView {
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
type BrainDistillationQueueStatus,
|
|
45
45
|
type BrainOpsQueueItem,
|
|
46
46
|
type BrainOpsQueueResponse,
|
|
47
|
+
type BrainHealthResponse,
|
|
47
48
|
statusLabel,
|
|
48
49
|
} from "@/lib/brain";
|
|
49
50
|
import { cn } from "@/lib/utils";
|
|
@@ -135,6 +136,21 @@ export default function OpsRoute() {
|
|
|
135
136
|
RetryDistillationResponse,
|
|
136
137
|
RetryDistillationRequest
|
|
137
138
|
>("retry-distillation" as any);
|
|
139
|
+
const healthQuery = useActionQuery<BrainHealthResponse>(
|
|
140
|
+
"get-brain-health" as any,
|
|
141
|
+
{} as any,
|
|
142
|
+
{ refetchInterval: 30_000 },
|
|
143
|
+
);
|
|
144
|
+
const semantic = (
|
|
145
|
+
healthQuery.data as BrainHealthResponse & {
|
|
146
|
+
semanticIndex?: {
|
|
147
|
+
coverage?: number;
|
|
148
|
+
embeddingLag?: number;
|
|
149
|
+
aclFreshness?: number;
|
|
150
|
+
suppressed?: number;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
)?.semanticIndex;
|
|
138
154
|
|
|
139
155
|
const items = (queueQuery.data?.items ?? []) as BrainOpsQueueItemWithReason[];
|
|
140
156
|
const summary = queueQuery.data?.summary ?? emptySummary;
|
|
@@ -377,6 +393,37 @@ export default function OpsRoute() {
|
|
|
377
393
|
/>
|
|
378
394
|
</div>
|
|
379
395
|
|
|
396
|
+
<section className="grid gap-3 rounded-md border border-border bg-muted/20 p-4">
|
|
397
|
+
<div>
|
|
398
|
+
<p className="text-sm font-medium">{t("ops.semanticIndex")}</p>
|
|
399
|
+
<p className="mt-1 text-xs leading-5 text-muted-foreground">
|
|
400
|
+
{t("ops.semanticIndexDescription")}
|
|
401
|
+
</p>
|
|
402
|
+
</div>
|
|
403
|
+
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
|
404
|
+
<MetricCard
|
|
405
|
+
label={t("ops.indexCoverage")}
|
|
406
|
+
value={semantic?.coverage ?? "—"}
|
|
407
|
+
detail={t("ops.adminOnly")}
|
|
408
|
+
/>
|
|
409
|
+
<MetricCard
|
|
410
|
+
label={t("ops.embeddingLag")}
|
|
411
|
+
value={semantic?.embeddingLag ?? "—"}
|
|
412
|
+
detail={t("ops.adminOnly")}
|
|
413
|
+
/>
|
|
414
|
+
<MetricCard
|
|
415
|
+
label={t("ops.aclFreshness")}
|
|
416
|
+
value={semantic?.aclFreshness ?? "—"}
|
|
417
|
+
detail={t("ops.adminOnly")}
|
|
418
|
+
/>
|
|
419
|
+
<MetricCard
|
|
420
|
+
label={t("ops.suppressed")}
|
|
421
|
+
value={semantic?.suppressed ?? "—"}
|
|
422
|
+
detail={t("ops.adminOnly")}
|
|
423
|
+
/>
|
|
424
|
+
</div>
|
|
425
|
+
</section>
|
|
426
|
+
|
|
380
427
|
<div className="flex flex-col gap-3 rounded-md border border-border bg-card p-4 sm:flex-row sm:items-center sm:justify-between">
|
|
381
428
|
<div className="min-w-0">
|
|
382
429
|
<p className="text-sm font-medium">{t("ops.retryControls")}</p>
|
|
@@ -65,7 +65,7 @@ import { cn } from "@/lib/utils";
|
|
|
65
65
|
|
|
66
66
|
type BrainT = ReturnType<typeof useT>;
|
|
67
67
|
|
|
68
|
-
type ProposalStatus = "pending" | "approved" | "rejected";
|
|
68
|
+
type ProposalStatus = "pending" | "approved" | "rejected" | "quarantine";
|
|
69
69
|
|
|
70
70
|
interface ProposalDraft {
|
|
71
71
|
title?: string;
|
|
@@ -337,6 +337,9 @@ export default function ReviewRoute() {
|
|
|
337
337
|
<SelectItem value="pending">{t("review.pending")}</SelectItem>
|
|
338
338
|
<SelectItem value="approved">{t("review.approved")}</SelectItem>
|
|
339
339
|
<SelectItem value="rejected">{t("review.rejected")}</SelectItem>
|
|
340
|
+
<SelectItem value="quarantine">
|
|
341
|
+
{t("review.quarantine")}
|
|
342
|
+
</SelectItem>
|
|
340
343
|
</SelectContent>
|
|
341
344
|
</Select>
|
|
342
345
|
</div>
|
|
@@ -349,6 +352,11 @@ export default function ReviewRoute() {
|
|
|
349
352
|
) : proposals.length ? (
|
|
350
353
|
<div className="grid gap-4">
|
|
351
354
|
{proposals.map((proposal) => {
|
|
355
|
+
if (status === "quarantine") {
|
|
356
|
+
return (
|
|
357
|
+
<QuarantineMetadataCard key={proposal.id} item={proposal} />
|
|
358
|
+
);
|
|
359
|
+
}
|
|
352
360
|
const evidence = proposal.evidence ?? [];
|
|
353
361
|
const sourceUrl = firstSourceUrl(proposal);
|
|
354
362
|
const canReview = proposal.status === "pending";
|
|
@@ -641,10 +649,41 @@ export default function ReviewRoute() {
|
|
|
641
649
|
}
|
|
642
650
|
|
|
643
651
|
function proposalStatus(value: string | null): ProposalStatus {
|
|
644
|
-
if (value === "approved" || value === "rejected"
|
|
652
|
+
if (value === "approved" || value === "rejected" || value === "quarantine")
|
|
653
|
+
return value;
|
|
645
654
|
return "pending";
|
|
646
655
|
}
|
|
647
656
|
|
|
657
|
+
function QuarantineMetadataCard({ item }: { item: ReviewItem }) {
|
|
658
|
+
const t = useT();
|
|
659
|
+
return (
|
|
660
|
+
<Card>
|
|
661
|
+
<CardHeader className="pb-3">
|
|
662
|
+
<div className="flex items-start justify-between gap-3">
|
|
663
|
+
<div className="min-w-0">
|
|
664
|
+
<CardTitle className="text-base">
|
|
665
|
+
{t("review.quarantineEvent")}
|
|
666
|
+
</CardTitle>
|
|
667
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
668
|
+
{item.reason ?? t("review.quarantineNoReason")}
|
|
669
|
+
</p>
|
|
670
|
+
</div>
|
|
671
|
+
<Badge variant="outline">{t("review.metadataOnly")}</Badge>
|
|
672
|
+
</div>
|
|
673
|
+
</CardHeader>
|
|
674
|
+
<CardContent className="grid gap-2 text-sm text-muted-foreground sm:grid-cols-3">
|
|
675
|
+
<span>{t("review.quarantineId", { id: item.id })}</span>
|
|
676
|
+
<span>
|
|
677
|
+
{t("review.quarantineSource", {
|
|
678
|
+
source: item.sourceName ?? item.sourceId ?? t("review.notRecorded"),
|
|
679
|
+
})}
|
|
680
|
+
</span>
|
|
681
|
+
<span>{formatDate(item.createdAt, t)}</span>
|
|
682
|
+
</CardContent>
|
|
683
|
+
</Card>
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
648
687
|
function cleanNote(value: string | undefined) {
|
|
649
688
|
const trimmed = value?.trim();
|
|
650
689
|
return trimmed ? trimmed : undefined;
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
SheetTrigger,
|
|
43
43
|
} from "@/components/ui/sheet";
|
|
44
44
|
import {
|
|
45
|
+
type BrainProjectsResponse,
|
|
45
46
|
type KnowledgeRow,
|
|
46
47
|
type SearchEverythingResponse,
|
|
47
48
|
type SearchEverythingResult,
|
|
@@ -83,6 +84,7 @@ export default function SearchRoute() {
|
|
|
83
84
|
const type = params.get("type") ?? "all";
|
|
84
85
|
const provider = params.get("provider") ?? "all";
|
|
85
86
|
const status = params.get("status") ?? "all";
|
|
87
|
+
const projectId = params.get("projectId") ?? "all";
|
|
86
88
|
const limit = params.get("limit") ?? "25";
|
|
87
89
|
|
|
88
90
|
const actionParams = useMemo(
|
|
@@ -90,10 +92,11 @@ export default function SearchRoute() {
|
|
|
90
92
|
query: query.trim() || undefined,
|
|
91
93
|
type: type === "all" ? undefined : type,
|
|
92
94
|
provider: provider === "all" ? undefined : provider,
|
|
95
|
+
projectId: projectId === "all" ? undefined : projectId,
|
|
93
96
|
status: status === "all" ? undefined : status,
|
|
94
97
|
limit: Number.parseInt(limit, 10) || 25,
|
|
95
98
|
}),
|
|
96
|
-
[limit, provider, query, status, type],
|
|
99
|
+
[limit, projectId, provider, query, status, type],
|
|
97
100
|
);
|
|
98
101
|
|
|
99
102
|
const searchQuery = useActionQuery<SearchEverythingResponse>(
|
|
@@ -101,6 +104,10 @@ export default function SearchRoute() {
|
|
|
101
104
|
actionParams as any,
|
|
102
105
|
{ enabled: Boolean(query.trim()), retry: false },
|
|
103
106
|
);
|
|
107
|
+
const projectsQuery = useActionQuery<BrainProjectsResponse>(
|
|
108
|
+
"list-projects" as any,
|
|
109
|
+
{} as any,
|
|
110
|
+
);
|
|
104
111
|
|
|
105
112
|
const results = useMemo(
|
|
106
113
|
() => filterResults(normalizeResults(searchQuery.data), provider, status),
|
|
@@ -110,6 +117,7 @@ export default function SearchRoute() {
|
|
|
110
117
|
Boolean(query.trim()) ||
|
|
111
118
|
type !== "all" ||
|
|
112
119
|
provider !== "all" ||
|
|
120
|
+
projectId !== "all" ||
|
|
113
121
|
status !== "all";
|
|
114
122
|
const groupedResults = useMemo(() => groupResults(results), [results]);
|
|
115
123
|
const resultFacets = useMemo(
|
|
@@ -168,6 +176,24 @@ export default function SearchRoute() {
|
|
|
168
176
|
</div>
|
|
169
177
|
|
|
170
178
|
<div className="brain-search-filter-grid grid gap-2">
|
|
179
|
+
<Select
|
|
180
|
+
value={projectId}
|
|
181
|
+
onValueChange={(value) => updateParam("projectId", value)}
|
|
182
|
+
>
|
|
183
|
+
<SelectTrigger>
|
|
184
|
+
<SelectValue placeholder={t("searchPage.project")} />
|
|
185
|
+
</SelectTrigger>
|
|
186
|
+
<SelectContent>
|
|
187
|
+
<SelectItem value="all">
|
|
188
|
+
{t("searchPage.allProjects")}
|
|
189
|
+
</SelectItem>
|
|
190
|
+
{(projectsQuery.data?.projects ?? []).map((project) => (
|
|
191
|
+
<SelectItem key={project.id} value={project.id}>
|
|
192
|
+
{project.title}
|
|
193
|
+
</SelectItem>
|
|
194
|
+
))}
|
|
195
|
+
</SelectContent>
|
|
196
|
+
</Select>
|
|
171
197
|
<Select
|
|
172
198
|
value={type}
|
|
173
199
|
onValueChange={(value) => updateParam("type", value)}
|
|
@@ -346,6 +372,9 @@ function SearchResultRow({
|
|
|
346
372
|
{displayLabel(sourceProvider)}
|
|
347
373
|
</Badge>
|
|
348
374
|
) : null}
|
|
375
|
+
{result.matchLane ? (
|
|
376
|
+
<Badge variant="outline">{result.matchLane}</Badge>
|
|
377
|
+
) : null}
|
|
349
378
|
</div>
|
|
350
379
|
<h3 className="mt-3 text-base font-semibold leading-6 text-foreground">
|
|
351
380
|
{result.title || t("searchPage.untitledResult")}
|
|
@@ -356,7 +385,9 @@ function SearchResultRow({
|
|
|
356
385
|
{whyMatched ? (
|
|
357
386
|
<div className="mt-3 flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
|
|
358
387
|
<IconCircleCheck className="size-3.5 shrink-0 text-foreground" />
|
|
359
|
-
<span className="truncate">
|
|
388
|
+
<span className="truncate">
|
|
389
|
+
{result.retrievalReason ?? whyMatched}
|
|
390
|
+
</span>
|
|
360
391
|
</div>
|
|
361
392
|
) : null}
|
|
362
393
|
</div>
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
IconDeviceFloppy,
|
|
17
17
|
IconFileText,
|
|
18
18
|
IconGauge,
|
|
19
|
+
IconLock,
|
|
19
20
|
IconMessageCircle,
|
|
20
21
|
IconShieldCheck,
|
|
21
22
|
IconUsersGroup,
|
|
@@ -50,6 +51,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|
|
50
51
|
import {
|
|
51
52
|
type BrainSettings,
|
|
52
53
|
type SettingsResponse,
|
|
54
|
+
type BrainHealthResponse,
|
|
53
55
|
defaultSettings,
|
|
54
56
|
} from "@/lib/brain";
|
|
55
57
|
import {
|
|
@@ -267,6 +269,152 @@ function PublishingReviewSettings({
|
|
|
267
269
|
);
|
|
268
270
|
}
|
|
269
271
|
|
|
272
|
+
function PrivacySensitivitySettings({
|
|
273
|
+
settings,
|
|
274
|
+
update,
|
|
275
|
+
}: {
|
|
276
|
+
settings: BrainSettings;
|
|
277
|
+
update: UpdateBrainSettings;
|
|
278
|
+
}) {
|
|
279
|
+
const t = useT();
|
|
280
|
+
const healthQuery = useActionQuery<BrainHealthResponse>(
|
|
281
|
+
"get-brain-health" as any,
|
|
282
|
+
{} as any,
|
|
283
|
+
);
|
|
284
|
+
const privacy = (
|
|
285
|
+
healthQuery.data as BrainHealthResponse & {
|
|
286
|
+
privacy?: {
|
|
287
|
+
classifierReady?: boolean;
|
|
288
|
+
classifierModel?: string | null;
|
|
289
|
+
quarantineRetentionDays?: number | null;
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
)?.privacy;
|
|
293
|
+
return (
|
|
294
|
+
<div className="mx-auto w-full max-w-3xl">
|
|
295
|
+
<Card id="privacy-sensitivity" className="scroll-mt-4">
|
|
296
|
+
<CardHeader>
|
|
297
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
298
|
+
<IconLock className="size-4 text-primary" />
|
|
299
|
+
{t("settings.privacySensitivityTitle")}
|
|
300
|
+
</CardTitle>
|
|
301
|
+
<CardDescription>
|
|
302
|
+
{t("settings.privacySensitivityDescription")}
|
|
303
|
+
</CardDescription>
|
|
304
|
+
</CardHeader>
|
|
305
|
+
<CardContent className="grid gap-4 text-sm">
|
|
306
|
+
<div className="grid gap-3 rounded-md border border-border bg-muted/20 p-3 sm:grid-cols-3">
|
|
307
|
+
<PolicyRow
|
|
308
|
+
label={t("settings.privacyClassifier")}
|
|
309
|
+
value={
|
|
310
|
+
privacy?.classifierReady
|
|
311
|
+
? t("settings.ready")
|
|
312
|
+
: t("settings.readinessPending")
|
|
313
|
+
}
|
|
314
|
+
/>
|
|
315
|
+
<PolicyRow
|
|
316
|
+
label={t("settings.privacyModel")}
|
|
317
|
+
value={privacy?.classifierModel ?? t("settings.notSet")}
|
|
318
|
+
/>
|
|
319
|
+
<PolicyRow
|
|
320
|
+
label={t("settings.quarantineRetention")}
|
|
321
|
+
value={
|
|
322
|
+
privacy?.quarantineRetentionDays
|
|
323
|
+
? t("settings.days", {
|
|
324
|
+
count: privacy.quarantineRetentionDays,
|
|
325
|
+
})
|
|
326
|
+
: t("settings.notSet")
|
|
327
|
+
}
|
|
328
|
+
/>
|
|
329
|
+
</div>
|
|
330
|
+
<p className="rounded-md border border-border bg-background p-3 text-xs leading-5 text-muted-foreground">
|
|
331
|
+
{t("settings.tightenOnly")}
|
|
332
|
+
</p>
|
|
333
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
334
|
+
<TextField
|
|
335
|
+
id="privacy-classifier-model"
|
|
336
|
+
label={t("settings.privacyClassifierModel")}
|
|
337
|
+
value={settings.privacyClassifierModel ?? ""}
|
|
338
|
+
placeholder={t("settings.privacyClassifierModelPlaceholder")}
|
|
339
|
+
onChange={(value) => update("privacyClassifierModel", value)}
|
|
340
|
+
/>
|
|
341
|
+
<TextField
|
|
342
|
+
id="privacy-classifier-engine"
|
|
343
|
+
label={t("settings.privacyClassifierEngine")}
|
|
344
|
+
value={settings.privacyClassifierEngine ?? ""}
|
|
345
|
+
placeholder={t("settings.privacyClassifierEnginePlaceholder")}
|
|
346
|
+
onChange={(value) => update("privacyClassifierEngine", value)}
|
|
347
|
+
/>
|
|
348
|
+
</div>
|
|
349
|
+
<div className="grid gap-2">
|
|
350
|
+
<Label htmlFor="quarantine-retention-hours">
|
|
351
|
+
{t("settings.quarantineRetentionHours")}
|
|
352
|
+
</Label>
|
|
353
|
+
<Input
|
|
354
|
+
id="quarantine-retention-hours"
|
|
355
|
+
type="number"
|
|
356
|
+
min={1}
|
|
357
|
+
max={8760}
|
|
358
|
+
value={settings.quarantineRetentionHours ?? 72}
|
|
359
|
+
onChange={(event) =>
|
|
360
|
+
update(
|
|
361
|
+
"quarantineRetentionHours",
|
|
362
|
+
Math.max(1, Math.min(8760, Number(event.target.value) || 1)),
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
/>
|
|
366
|
+
<p className="text-xs leading-5 text-muted-foreground">
|
|
367
|
+
{t("settings.quarantineRetentionHoursDescription")}
|
|
368
|
+
</p>
|
|
369
|
+
</div>
|
|
370
|
+
<div className="grid gap-2">
|
|
371
|
+
<Label htmlFor="sensitivity-custom-instructions">
|
|
372
|
+
{t("settings.sensitivityCustomInstructions")}
|
|
373
|
+
</Label>
|
|
374
|
+
<Textarea
|
|
375
|
+
id="sensitivity-custom-instructions"
|
|
376
|
+
value={settings.sensitivityCustomInstructions ?? ""}
|
|
377
|
+
placeholder={t(
|
|
378
|
+
"settings.sensitivityCustomInstructionsPlaceholder",
|
|
379
|
+
)}
|
|
380
|
+
onChange={(event) =>
|
|
381
|
+
update("sensitivityCustomInstructions", event.target.value)
|
|
382
|
+
}
|
|
383
|
+
/>
|
|
384
|
+
<p className="text-xs leading-5 text-muted-foreground">
|
|
385
|
+
{t("settings.sensitivityCustomInstructionsDescription")}
|
|
386
|
+
</p>
|
|
387
|
+
</div>
|
|
388
|
+
<div className="grid gap-2">
|
|
389
|
+
<Label htmlFor="public-channel-exclusion-patterns">
|
|
390
|
+
{t("settings.publicChannelExclusionPatterns")}
|
|
391
|
+
</Label>
|
|
392
|
+
<Textarea
|
|
393
|
+
id="public-channel-exclusion-patterns"
|
|
394
|
+
value={(settings.publicChannelExclusionPatterns ?? []).join("\n")}
|
|
395
|
+
placeholder={t(
|
|
396
|
+
"settings.publicChannelExclusionPatternsPlaceholder",
|
|
397
|
+
)}
|
|
398
|
+
onChange={(event) =>
|
|
399
|
+
update(
|
|
400
|
+
"publicChannelExclusionPatterns",
|
|
401
|
+
event.target.value
|
|
402
|
+
.split("\n")
|
|
403
|
+
.map((value) => value.trim())
|
|
404
|
+
.filter(Boolean),
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
/>
|
|
408
|
+
<p className="text-xs leading-5 text-muted-foreground">
|
|
409
|
+
{t("settings.publicChannelExclusionPatternsDescription")}
|
|
410
|
+
</p>
|
|
411
|
+
</div>
|
|
412
|
+
</CardContent>
|
|
413
|
+
</Card>
|
|
414
|
+
</div>
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
270
418
|
function SafetyEvidenceSettings({
|
|
271
419
|
settings,
|
|
272
420
|
update,
|
|
@@ -399,6 +547,16 @@ export default function SettingsRoute() {
|
|
|
399
547
|
|
|
400
548
|
const generalSearchEntries = useMemo<SettingsSearchEntry[]>(
|
|
401
549
|
() => [
|
|
550
|
+
{
|
|
551
|
+
id: "privacy-sensitivity",
|
|
552
|
+
label: t("settings.privacySensitivityTitle"),
|
|
553
|
+
icon: IconLock,
|
|
554
|
+
keywords:
|
|
555
|
+
"privacy sensitivity classifier quarantine retention tighten only",
|
|
556
|
+
content: (
|
|
557
|
+
<PrivacySensitivitySettings settings={settings} update={update} />
|
|
558
|
+
),
|
|
559
|
+
},
|
|
402
560
|
{
|
|
403
561
|
id: "brain-identity",
|
|
404
562
|
label: t("settings.identityTitle"),
|
|
@@ -112,6 +112,7 @@ interface SourceFormState {
|
|
|
112
112
|
sourceKey: string;
|
|
113
113
|
autoSync: boolean;
|
|
114
114
|
reviewRequired: boolean;
|
|
115
|
+
includePublicChannels: boolean;
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
const providers: Array<{
|
|
@@ -200,6 +201,7 @@ function defaultForm(
|
|
|
200
201
|
autoSync:
|
|
201
202
|
provider === "slack" || provider === "granola" || provider === "github",
|
|
202
203
|
reviewRequired: true,
|
|
204
|
+
includePublicChannels: false,
|
|
203
205
|
};
|
|
204
206
|
}
|
|
205
207
|
|
|
@@ -251,6 +253,7 @@ function formFromSource(source: BrainSource): SourceFormState {
|
|
|
251
253
|
sourceKey: "",
|
|
252
254
|
autoSync: sourceAutoSync(source),
|
|
253
255
|
reviewRequired: sourceReviewRequired(source),
|
|
256
|
+
includePublicChannels: config.includePublicChannels === true,
|
|
254
257
|
};
|
|
255
258
|
}
|
|
256
259
|
|
|
@@ -281,6 +284,7 @@ function buildConfig(form: SourceFormState) {
|
|
|
281
284
|
if (form.provider === "slack") {
|
|
282
285
|
config.channelIds = splitLines(form.channelRefs);
|
|
283
286
|
config.historyLimit = numberValue(form.historyLimit, 15, 1, 15);
|
|
287
|
+
config.includePublicChannels = form.includePublicChannels;
|
|
284
288
|
}
|
|
285
289
|
if (form.provider === "granola") {
|
|
286
290
|
config.pageSize = numberValue(form.granolaPageSize, 10, 1, 30);
|
|
@@ -2393,6 +2397,17 @@ export default function SourcesRoute() {
|
|
|
2393
2397
|
|
|
2394
2398
|
{form.provider === "slack" && (
|
|
2395
2399
|
<div className="grid gap-4 rounded-md border border-border p-4">
|
|
2400
|
+
<div className="grid gap-3 rounded-md border border-border bg-muted/20 p-3 text-xs leading-5 text-muted-foreground">
|
|
2401
|
+
<p>
|
|
2402
|
+
<span className="font-medium text-foreground">
|
|
2403
|
+
{t("sources.slackDiscoveryMode")}
|
|
2404
|
+
</span>{" "}
|
|
2405
|
+
— {t("sources.slackDiscoveryModeDescription")}
|
|
2406
|
+
</p>
|
|
2407
|
+
<p>{t("sources.slackExclusions")}</p>
|
|
2408
|
+
<p>{t("sources.slackManualInvite")}</p>
|
|
2409
|
+
<p>{t("sources.sensitivityPreviewDescription")}</p>
|
|
2410
|
+
</div>
|
|
2396
2411
|
<div className="rounded-md border border-border bg-muted/25 p-3">
|
|
2397
2412
|
<div className="flex items-center gap-2 text-sm font-medium">
|
|
2398
2413
|
<IconShieldCheck className="size-4 text-muted-foreground" />
|
|
@@ -2419,6 +2434,22 @@ export default function SourcesRoute() {
|
|
|
2419
2434
|
{t("sources.allowedChannelsDescription")}
|
|
2420
2435
|
</p>
|
|
2421
2436
|
</div>
|
|
2437
|
+
<label className="flex items-center justify-between gap-4 rounded-md border border-border bg-muted/20 p-3">
|
|
2438
|
+
<span>
|
|
2439
|
+
<span className="block text-sm font-medium">
|
|
2440
|
+
{t("sources.includePublicChannels")}
|
|
2441
|
+
</span>
|
|
2442
|
+
<span className="mt-1 block text-xs leading-5 text-muted-foreground">
|
|
2443
|
+
{t("sources.includePublicChannelsDescription")}
|
|
2444
|
+
</span>
|
|
2445
|
+
</span>
|
|
2446
|
+
<Switch
|
|
2447
|
+
checked={form.includePublicChannels}
|
|
2448
|
+
onCheckedChange={(includePublicChannels) =>
|
|
2449
|
+
updateForm({ includePublicChannels })
|
|
2450
|
+
}
|
|
2451
|
+
/>
|
|
2452
|
+
</label>
|
|
2422
2453
|
<div className="grid gap-2 sm:grid-cols-2">
|
|
2423
2454
|
<div className="grid gap-2">
|
|
2424
2455
|
<Label htmlFor="history-limit">
|