@agent-native/core 0.111.3 → 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 +18 -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/migration-codemod.ts +59 -9
- 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/migration-codemod.d.ts.map +1 -1
- package/dist/cli/migration-codemod.js +48 -10
- package/dist/cli/migration-codemod.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/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/package.json +3 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { accessFilter } from "@agent-native/core/sharing";
|
|
3
|
-
import { and, desc, eq, lte, or } from "drizzle-orm";
|
|
3
|
+
import { and, desc, eq, inArray, isNull, lte, or } from "drizzle-orm";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import { listAccessibleAudienceIds } from "../server/lib/audiences.js";
|
|
7
8
|
import { redactSensitiveText } from "../server/lib/search.js";
|
|
8
9
|
|
|
9
10
|
const queueStatuses = ["queued", "processing", "done", "failed"] as const;
|
|
@@ -118,27 +119,45 @@ export async function readDistillationQueue(
|
|
|
118
119
|
const staleCutoff = staleProcessingCutoff(now);
|
|
119
120
|
const issue = args.staleOnly ? "stale" : (args.issue ?? "all");
|
|
120
121
|
const limit = args.limit ?? 100;
|
|
122
|
+
const accessibleAudienceIds = await listAccessibleAudienceIds();
|
|
123
|
+
const captureVisibility = or(
|
|
124
|
+
isNull(schema.brainIngestQueue.captureId),
|
|
125
|
+
accessibleAudienceIds.length
|
|
126
|
+
? and(
|
|
127
|
+
eq(schema.brainRawCaptures.sensitivityDisposition, "allowed"),
|
|
128
|
+
inArray(
|
|
129
|
+
schema.brainCaptureAudiences.audienceId,
|
|
130
|
+
accessibleAudienceIds,
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
: undefined,
|
|
134
|
+
);
|
|
121
135
|
const baseClauses = [
|
|
122
136
|
eq(schema.brainIngestQueue.operation, "distill"),
|
|
123
137
|
accessFilter(schema.brainSources, schema.brainSourceShares),
|
|
138
|
+
captureVisibility,
|
|
124
139
|
];
|
|
125
140
|
if (args.sourceId) {
|
|
126
141
|
baseClauses.push(eq(schema.brainSources.id, args.sourceId));
|
|
127
142
|
}
|
|
128
143
|
|
|
129
144
|
const summaryRows = await db
|
|
130
|
-
.
|
|
145
|
+
.selectDistinct({
|
|
131
146
|
status: schema.brainIngestQueue.status,
|
|
132
147
|
updatedAt: schema.brainIngestQueue.updatedAt,
|
|
133
148
|
})
|
|
134
149
|
.from(schema.brainIngestQueue)
|
|
135
150
|
.innerJoin(
|
|
151
|
+
schema.brainSources,
|
|
152
|
+
eq(schema.brainIngestQueue.sourceId, schema.brainSources.id),
|
|
153
|
+
)
|
|
154
|
+
.leftJoin(
|
|
136
155
|
schema.brainRawCaptures,
|
|
137
156
|
eq(schema.brainIngestQueue.captureId, schema.brainRawCaptures.id),
|
|
138
157
|
)
|
|
139
|
-
.
|
|
140
|
-
schema.
|
|
141
|
-
eq(schema.
|
|
158
|
+
.leftJoin(
|
|
159
|
+
schema.brainCaptureAudiences,
|
|
160
|
+
eq(schema.brainCaptureAudiences.captureId, schema.brainRawCaptures.id),
|
|
142
161
|
)
|
|
143
162
|
.where(and(...baseClauses));
|
|
144
163
|
|
|
@@ -164,7 +183,7 @@ export async function readDistillationQueue(
|
|
|
164
183
|
}
|
|
165
184
|
|
|
166
185
|
const rows = await db
|
|
167
|
-
.
|
|
186
|
+
.selectDistinct({
|
|
168
187
|
id: schema.brainIngestQueue.id,
|
|
169
188
|
sourceId: schema.brainIngestQueue.sourceId,
|
|
170
189
|
captureId: schema.brainIngestQueue.captureId,
|
|
@@ -184,12 +203,16 @@ export async function readDistillationQueue(
|
|
|
184
203
|
})
|
|
185
204
|
.from(schema.brainIngestQueue)
|
|
186
205
|
.innerJoin(
|
|
206
|
+
schema.brainSources,
|
|
207
|
+
eq(schema.brainIngestQueue.sourceId, schema.brainSources.id),
|
|
208
|
+
)
|
|
209
|
+
.leftJoin(
|
|
187
210
|
schema.brainRawCaptures,
|
|
188
211
|
eq(schema.brainIngestQueue.captureId, schema.brainRawCaptures.id),
|
|
189
212
|
)
|
|
190
|
-
.
|
|
191
|
-
schema.
|
|
192
|
-
eq(schema.
|
|
213
|
+
.leftJoin(
|
|
214
|
+
schema.brainCaptureAudiences,
|
|
215
|
+
eq(schema.brainCaptureAudiences.captureId, schema.brainRawCaptures.id),
|
|
193
216
|
)
|
|
194
217
|
.where(and(...clauses))
|
|
195
218
|
.orderBy(desc(schema.brainIngestQueue.updatedAt))
|
|
@@ -223,11 +246,13 @@ export async function readDistillationQueue(
|
|
|
223
246
|
provider: row.sourceProvider,
|
|
224
247
|
status: row.sourceStatus,
|
|
225
248
|
},
|
|
226
|
-
capture:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
249
|
+
capture: row.captureId
|
|
250
|
+
? {
|
|
251
|
+
id: row.captureId,
|
|
252
|
+
title: redactOptionalText(row.captureTitle),
|
|
253
|
+
status: row.captureStatus,
|
|
254
|
+
}
|
|
255
|
+
: null,
|
|
231
256
|
};
|
|
232
257
|
});
|
|
233
258
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
3
|
+
import { getUserSetting } from "@agent-native/core/settings";
|
|
4
|
+
import { accessFilter } from "@agent-native/core/sharing";
|
|
5
|
+
import { inArray } from "drizzle-orm";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_PROJECT_SETTING = "brain-default-project";
|
|
11
|
+
|
|
12
|
+
export default defineAction({
|
|
13
|
+
description: "List accessible Brain projects and their source membership.",
|
|
14
|
+
schema: z.object({}),
|
|
15
|
+
http: { method: "GET" },
|
|
16
|
+
readOnly: true,
|
|
17
|
+
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
18
|
+
run: async () => {
|
|
19
|
+
const db = getDb();
|
|
20
|
+
const projects = await db
|
|
21
|
+
.select()
|
|
22
|
+
.from(schema.brainProjects)
|
|
23
|
+
.where(accessFilter(schema.brainProjects, schema.brainProjectShares));
|
|
24
|
+
const projectIds = projects.map((project) => project.id);
|
|
25
|
+
const links = projectIds.length
|
|
26
|
+
? await db
|
|
27
|
+
.select()
|
|
28
|
+
.from(schema.brainProjectSources)
|
|
29
|
+
.where(inArray(schema.brainProjectSources.projectId, projectIds))
|
|
30
|
+
: [];
|
|
31
|
+
const userEmail = getRequestUserEmail();
|
|
32
|
+
const setting = userEmail
|
|
33
|
+
? ((await getUserSetting(userEmail, DEFAULT_PROJECT_SETTING)) as {
|
|
34
|
+
projectId?: string;
|
|
35
|
+
} | null)
|
|
36
|
+
: null;
|
|
37
|
+
return {
|
|
38
|
+
projects: projects.map((project) => ({
|
|
39
|
+
id: project.id,
|
|
40
|
+
title: project.title,
|
|
41
|
+
description: project.description,
|
|
42
|
+
visibility: project.visibility,
|
|
43
|
+
sourceIds: links
|
|
44
|
+
.filter((link) => link.projectId === project.id)
|
|
45
|
+
.map((link) => link.sourceId),
|
|
46
|
+
isDefault: setting?.projectId === project.id,
|
|
47
|
+
updatedAt: project.updatedAt,
|
|
48
|
+
})),
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -1,26 +1,102 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import { accessFilter } from "@agent-native/core/sharing";
|
|
3
|
-
import { and, desc, eq } from "drizzle-orm";
|
|
2
|
+
import { accessFilter, assertAccess } from "@agent-native/core/sharing";
|
|
3
|
+
import { and, desc, eq, inArray, isNull, or, sql } from "drizzle-orm";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
-
import {
|
|
7
|
+
import { listAccessibleAudienceIds } from "../server/lib/audiences.js";
|
|
8
|
+
import { parseJson, serializeProposal } from "../server/lib/brain.js";
|
|
8
9
|
|
|
9
10
|
export default defineAction({
|
|
10
11
|
description: "List Brain knowledge proposals requiring review.",
|
|
11
12
|
schema: z.object({
|
|
12
|
-
status: z
|
|
13
|
+
status: z
|
|
14
|
+
.enum(["pending", "approved", "rejected", "quarantine"])
|
|
15
|
+
.default("pending"),
|
|
13
16
|
limit: z.coerce.number().int().min(1).max(100).default(50),
|
|
14
17
|
}),
|
|
15
18
|
http: { method: "GET" },
|
|
16
19
|
readOnly: true,
|
|
17
20
|
run: async ({ status, limit }) => {
|
|
21
|
+
if (status === "quarantine") {
|
|
22
|
+
const accessibleSources = await getDb()
|
|
23
|
+
.select({ id: schema.brainSources.id })
|
|
24
|
+
.from(schema.brainSources)
|
|
25
|
+
.where(accessFilter(schema.brainSources, schema.brainSourceShares));
|
|
26
|
+
const adminSourceIds = (
|
|
27
|
+
await Promise.all(
|
|
28
|
+
accessibleSources.map(async ({ id }) => {
|
|
29
|
+
try {
|
|
30
|
+
await assertAccess("brain-source", id, "admin");
|
|
31
|
+
return id;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}),
|
|
36
|
+
)
|
|
37
|
+
).filter((sourceId): sourceId is string => Boolean(sourceId));
|
|
38
|
+
if (!adminSourceIds.length) return { count: 0, proposals: [] };
|
|
39
|
+
const rows = await getDb()
|
|
40
|
+
.select({
|
|
41
|
+
id: schema.brainSensitivityEvents.id,
|
|
42
|
+
sourceId: schema.brainSensitivityEvents.sourceId,
|
|
43
|
+
sourceName: schema.brainSources.title,
|
|
44
|
+
disposition: schema.brainSensitivityEvents.disposition,
|
|
45
|
+
categoriesJson: schema.brainSensitivityEvents.categoriesJson,
|
|
46
|
+
confidenceBand: schema.brainSensitivityEvents.confidenceBand,
|
|
47
|
+
expiresAt: schema.brainSensitivityEvents.expiresAt,
|
|
48
|
+
createdAt: schema.brainSensitivityEvents.createdAt,
|
|
49
|
+
updatedAt: schema.brainSensitivityEvents.updatedAt,
|
|
50
|
+
})
|
|
51
|
+
.from(schema.brainSensitivityEvents)
|
|
52
|
+
.innerJoin(
|
|
53
|
+
schema.brainSources,
|
|
54
|
+
eq(schema.brainSources.id, schema.brainSensitivityEvents.sourceId),
|
|
55
|
+
)
|
|
56
|
+
.where(
|
|
57
|
+
and(
|
|
58
|
+
eq(schema.brainSensitivityEvents.disposition, "quarantined"),
|
|
59
|
+
inArray(schema.brainSensitivityEvents.sourceId, adminSourceIds),
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
.orderBy(desc(schema.brainSensitivityEvents.createdAt))
|
|
63
|
+
.limit(limit);
|
|
64
|
+
const proposals = rows.map((row) => ({
|
|
65
|
+
id: row.id,
|
|
66
|
+
title: "Privacy quarantine",
|
|
67
|
+
sourceId: row.sourceId,
|
|
68
|
+
sourceName: row.sourceName,
|
|
69
|
+
reason: [
|
|
70
|
+
row.confidenceBand,
|
|
71
|
+
...parseJson<string[]>(row.categoriesJson, []),
|
|
72
|
+
row.expiresAt ? `expires ${row.expiresAt}` : "metadata-only",
|
|
73
|
+
].join(" · "),
|
|
74
|
+
status: "quarantine" as const,
|
|
75
|
+
createdAt: row.createdAt,
|
|
76
|
+
updatedAt: row.updatedAt,
|
|
77
|
+
}));
|
|
78
|
+
return { count: proposals.length, proposals };
|
|
79
|
+
}
|
|
80
|
+
const audienceIds = await listAccessibleAudienceIds();
|
|
18
81
|
const rows = await getDb()
|
|
19
82
|
.select()
|
|
20
83
|
.from(schema.brainProposals)
|
|
21
84
|
.where(
|
|
22
85
|
and(
|
|
23
86
|
accessFilter(schema.brainProposals, schema.brainProposalShares),
|
|
87
|
+
or(
|
|
88
|
+
isNull(schema.brainProposals.captureId),
|
|
89
|
+
audienceIds.length
|
|
90
|
+
? and(
|
|
91
|
+
sql`exists (
|
|
92
|
+
select 1 from ${schema.brainSources}
|
|
93
|
+
where ${schema.brainSources.id} = ${schema.brainProposals.sourceId}
|
|
94
|
+
and ${accessFilter(schema.brainSources, schema.brainSourceShares)}
|
|
95
|
+
)`,
|
|
96
|
+
inArray(schema.brainProposals.audienceId, audienceIds),
|
|
97
|
+
)
|
|
98
|
+
: undefined,
|
|
99
|
+
),
|
|
24
100
|
eq(schema.brainProposals.status, status),
|
|
25
101
|
),
|
|
26
102
|
)
|
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { accessFilter } from "@agent-native/core/sharing";
|
|
3
|
-
import { and,
|
|
3
|
+
import { and, countDistinct, desc, eq, inArray, ne } from "drizzle-orm";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
7
|
import { nextBrainSourceSyncAt } from "../server/jobs/sync-sources.js";
|
|
8
|
+
import { listAccessibleAudienceIds } from "../server/lib/audiences.js";
|
|
8
9
|
import { parseJson, serializeSource } from "../server/lib/brain.js";
|
|
9
10
|
import { sourceProviderSchema } from "./_schemas.js";
|
|
10
11
|
|
|
11
12
|
async function sourceRecordCount(sourceId: string): Promise<number> {
|
|
13
|
+
const audienceIds = await listAccessibleAudienceIds([sourceId]);
|
|
14
|
+
if (!audienceIds.length) return 0;
|
|
15
|
+
|
|
12
16
|
// Count only — never load the heavy `content` blob of every capture row
|
|
13
17
|
// just to take `.length`.
|
|
14
18
|
const [row] = await getDb()
|
|
15
|
-
.select({ value:
|
|
19
|
+
.select({ value: countDistinct(schema.brainRawCaptures.id) })
|
|
16
20
|
.from(schema.brainRawCaptures)
|
|
17
|
-
.
|
|
21
|
+
.innerJoin(
|
|
22
|
+
schema.brainCaptureAudiences,
|
|
23
|
+
eq(schema.brainCaptureAudiences.captureId, schema.brainRawCaptures.id),
|
|
24
|
+
)
|
|
25
|
+
.where(
|
|
26
|
+
and(
|
|
27
|
+
eq(schema.brainRawCaptures.sourceId, sourceId),
|
|
28
|
+
eq(schema.brainRawCaptures.sensitivityDisposition, "allowed"),
|
|
29
|
+
inArray(schema.brainCaptureAudiences.audienceId, audienceIds),
|
|
30
|
+
),
|
|
31
|
+
);
|
|
18
32
|
return row?.value ?? 0;
|
|
19
33
|
}
|
|
20
34
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server/request-context";
|
|
6
|
+
import { putUserSetting } from "@agent-native/core/settings";
|
|
7
|
+
import { accessFilter, assertAccess } from "@agent-native/core/sharing";
|
|
8
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
12
|
+
import { nanoid, nowIso } from "../server/lib/brain.js";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_PROJECT_SETTING = "brain-default-project";
|
|
15
|
+
|
|
16
|
+
export default defineAction({
|
|
17
|
+
description:
|
|
18
|
+
"Create, update, remove, or select a Brain project that scopes related knowledge sources.",
|
|
19
|
+
schema: z.object({
|
|
20
|
+
operation: z.enum(["create", "update", "delete", "set-default"]),
|
|
21
|
+
projectId: z.string().optional(),
|
|
22
|
+
title: z.string().min(1).optional(),
|
|
23
|
+
description: z.string().optional(),
|
|
24
|
+
sourceIds: z.array(z.string().min(1)).optional(),
|
|
25
|
+
}),
|
|
26
|
+
run: async (args) => {
|
|
27
|
+
const userEmail = getRequestUserEmail();
|
|
28
|
+
if (!userEmail) throw new Error("Not authenticated");
|
|
29
|
+
const db = getDb();
|
|
30
|
+
const now = nowIso();
|
|
31
|
+
|
|
32
|
+
if (args.operation === "create") {
|
|
33
|
+
if (!args.title) throw new Error("title is required");
|
|
34
|
+
const id = args.projectId ?? nanoid();
|
|
35
|
+
await db.insert(schema.brainProjects).values({
|
|
36
|
+
id,
|
|
37
|
+
title: args.title,
|
|
38
|
+
description: args.description ?? "",
|
|
39
|
+
ownerEmail: userEmail,
|
|
40
|
+
orgId: getRequestOrgId() ?? null,
|
|
41
|
+
visibility: "private",
|
|
42
|
+
createdAt: now,
|
|
43
|
+
updatedAt: now,
|
|
44
|
+
});
|
|
45
|
+
await replaceProjectSources(id, args.sourceIds ?? []);
|
|
46
|
+
return { projectId: id, operation: args.operation };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!args.projectId) throw new Error("projectId is required");
|
|
50
|
+
if (args.operation === "set-default") {
|
|
51
|
+
await assertAccess("brain-project", args.projectId, "viewer");
|
|
52
|
+
await putUserSetting(userEmail, DEFAULT_PROJECT_SETTING, {
|
|
53
|
+
projectId: args.projectId,
|
|
54
|
+
});
|
|
55
|
+
return { projectId: args.projectId, default: true };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await assertAccess("brain-project", args.projectId, "editor");
|
|
59
|
+
if (args.operation === "delete") {
|
|
60
|
+
await db
|
|
61
|
+
.delete(schema.brainProjectSources)
|
|
62
|
+
.where(eq(schema.brainProjectSources.projectId, args.projectId));
|
|
63
|
+
await db
|
|
64
|
+
.delete(schema.brainProjectShares)
|
|
65
|
+
.where(eq(schema.brainProjectShares.resourceId, args.projectId));
|
|
66
|
+
await db
|
|
67
|
+
.delete(schema.brainProjects)
|
|
68
|
+
.where(eq(schema.brainProjects.id, args.projectId));
|
|
69
|
+
return { projectId: args.projectId, deleted: true };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await db
|
|
73
|
+
.update(schema.brainProjects)
|
|
74
|
+
.set({
|
|
75
|
+
...(args.title ? { title: args.title } : {}),
|
|
76
|
+
...(args.description !== undefined
|
|
77
|
+
? { description: args.description }
|
|
78
|
+
: {}),
|
|
79
|
+
updatedAt: now,
|
|
80
|
+
})
|
|
81
|
+
.where(eq(schema.brainProjects.id, args.projectId));
|
|
82
|
+
if (args.sourceIds) {
|
|
83
|
+
await replaceProjectSources(args.projectId, args.sourceIds);
|
|
84
|
+
}
|
|
85
|
+
return { projectId: args.projectId, operation: args.operation };
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
async function replaceProjectSources(projectId: string, sourceIds: string[]) {
|
|
90
|
+
const db = getDb();
|
|
91
|
+
const unique = Array.from(new Set(sourceIds));
|
|
92
|
+
if (unique.length) {
|
|
93
|
+
const accessible = await db
|
|
94
|
+
.select({ id: schema.brainSources.id })
|
|
95
|
+
.from(schema.brainSources)
|
|
96
|
+
.where(
|
|
97
|
+
and(
|
|
98
|
+
accessFilter(schema.brainSources, schema.brainSourceShares),
|
|
99
|
+
inArray(schema.brainSources.id, unique),
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
if (accessible.length !== unique.length) {
|
|
103
|
+
throw new Error("One or more Brain sources were not found");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
await db
|
|
107
|
+
.delete(schema.brainProjectSources)
|
|
108
|
+
.where(eq(schema.brainProjectSources.projectId, projectId));
|
|
109
|
+
if (!unique.length) return;
|
|
110
|
+
await db.insert(schema.brainProjectSources).values(
|
|
111
|
+
unique.map((sourceId) => ({
|
|
112
|
+
id: nanoid(),
|
|
113
|
+
projectId,
|
|
114
|
+
sourceId,
|
|
115
|
+
createdAt: nowIso(),
|
|
116
|
+
})),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import {
|
|
2
|
+
import { getDbExec } from "@agent-native/core/db";
|
|
3
|
+
import { and, desc, eq } from "drizzle-orm";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
|
|
5
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -15,15 +16,76 @@ import {
|
|
|
15
16
|
|
|
16
17
|
export default defineAction({
|
|
17
18
|
description: "Mark a raw Brain capture as distilled or ignored.",
|
|
18
|
-
schema: z
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
schema: z
|
|
20
|
+
.object({
|
|
21
|
+
captureId: z.string().min(1),
|
|
22
|
+
status: z.enum(["distilled", "ignored"]).default("distilled"),
|
|
23
|
+
queueId: z.string().min(1).optional(),
|
|
24
|
+
claimToken: z.string().min(1).optional(),
|
|
25
|
+
})
|
|
26
|
+
.superRefine((value, ctx) => {
|
|
27
|
+
if (Boolean(value.queueId) !== Boolean(value.claimToken)) {
|
|
28
|
+
ctx.addIssue({
|
|
29
|
+
code: z.ZodIssueCode.custom,
|
|
30
|
+
message: "queueId and claimToken must be supplied together.",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}),
|
|
34
|
+
run: async ({ captureId, status, queueId, claimToken }) => {
|
|
23
35
|
const access = await getAccessibleCapture(captureId);
|
|
24
36
|
if (!access) throw new Error(`No access to capture ${captureId}`);
|
|
25
37
|
const db = getDb();
|
|
26
38
|
const now = nowIso();
|
|
39
|
+
|
|
40
|
+
if (queueId && claimToken) {
|
|
41
|
+
const result = await getDbExec().execute({
|
|
42
|
+
sql: `UPDATE brain_ingest_queue
|
|
43
|
+
SET status = ?, error = NULL, lease_token = NULL,
|
|
44
|
+
lease_expires_at = NULL, updated_at = ?
|
|
45
|
+
WHERE id = ? AND capture_id = ? AND operation = 'distill'
|
|
46
|
+
AND status = 'processing' AND lease_token = ?`,
|
|
47
|
+
args: ["done", now, queueId, captureId, claimToken],
|
|
48
|
+
});
|
|
49
|
+
if (result.rowsAffected === 0) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"The distillation claim is no longer active; do not complete a newer worker's queue item.",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
const [processing] = await db
|
|
56
|
+
.select()
|
|
57
|
+
.from(schema.brainIngestQueue)
|
|
58
|
+
.where(
|
|
59
|
+
and(
|
|
60
|
+
eq(schema.brainIngestQueue.captureId, captureId),
|
|
61
|
+
eq(schema.brainIngestQueue.operation, "distill"),
|
|
62
|
+
eq(schema.brainIngestQueue.status, "processing"),
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
.orderBy(desc(schema.brainIngestQueue.updatedAt))
|
|
66
|
+
.limit(1);
|
|
67
|
+
if (processing) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"This capture has an active distillation claim. Supply its queueId and claimToken to complete it.",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
await db
|
|
73
|
+
.update(schema.brainIngestQueue)
|
|
74
|
+
.set({
|
|
75
|
+
status: "done",
|
|
76
|
+
error: null,
|
|
77
|
+
leaseToken: null,
|
|
78
|
+
leaseExpiresAt: null,
|
|
79
|
+
updatedAt: now,
|
|
80
|
+
})
|
|
81
|
+
.where(
|
|
82
|
+
and(
|
|
83
|
+
eq(schema.brainIngestQueue.captureId, captureId),
|
|
84
|
+
eq(schema.brainIngestQueue.operation, "distill"),
|
|
85
|
+
eq(schema.brainIngestQueue.status, "queued"),
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
27
89
|
await db
|
|
28
90
|
.update(schema.brainRawCaptures)
|
|
29
91
|
.set({
|
|
@@ -32,20 +94,6 @@ export default defineAction({
|
|
|
32
94
|
updatedAt: now,
|
|
33
95
|
})
|
|
34
96
|
.where(eq(schema.brainRawCaptures.id, captureId));
|
|
35
|
-
await db
|
|
36
|
-
.update(schema.brainIngestQueue)
|
|
37
|
-
.set({
|
|
38
|
-
status: "done",
|
|
39
|
-
error: null,
|
|
40
|
-
updatedAt: now,
|
|
41
|
-
})
|
|
42
|
-
.where(
|
|
43
|
-
and(
|
|
44
|
-
eq(schema.brainIngestQueue.captureId, captureId),
|
|
45
|
-
eq(schema.brainIngestQueue.operation, "distill"),
|
|
46
|
-
inArray(schema.brainIngestQueue.status, ["queued", "processing"]),
|
|
47
|
-
),
|
|
48
|
-
);
|
|
49
97
|
const updated = await getAccessibleCapture(captureId);
|
|
50
98
|
if (!updated) return { capture: null };
|
|
51
99
|
const capture = serializeCapture(updated.capture);
|
|
@@ -28,6 +28,7 @@ export default defineAction({
|
|
|
28
28
|
proposalId: z.string().optional(),
|
|
29
29
|
extensionId: z.string().optional(),
|
|
30
30
|
query: z.string().optional(),
|
|
31
|
+
projectId: z.string().optional(),
|
|
31
32
|
type: z.enum(["knowledge", "capture", "source", "all"]).optional(),
|
|
32
33
|
provider: z.string().optional(),
|
|
33
34
|
status: z.string().optional(),
|
|
@@ -5,7 +5,11 @@ import { eq } from "drizzle-orm";
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
assertDerivedAudienceAccess,
|
|
10
|
+
nowIso,
|
|
11
|
+
serializeProposal,
|
|
12
|
+
} from "../server/lib/brain.js";
|
|
9
13
|
|
|
10
14
|
export default defineAction({
|
|
11
15
|
description: "Reject a pending Brain proposal with optional reviewer notes.",
|
|
@@ -15,6 +19,7 @@ export default defineAction({
|
|
|
15
19
|
}),
|
|
16
20
|
run: async ({ proposalId, reviewerNotes }) => {
|
|
17
21
|
const access = await assertAccess("brain-proposal", proposalId, "editor");
|
|
22
|
+
await assertDerivedAudienceAccess(access.resource);
|
|
18
23
|
if (access.resource.status !== "pending") {
|
|
19
24
|
throw new Error(
|
|
20
25
|
`Proposal ${proposalId} is already ${access.resource.status}`,
|