@siming-org/server 0.4.0 → 0.5.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/dist/{chunk-XVFWTF42.js → chunk-XZWVPHDG.js} +1276 -180
- package/dist/index.d.ts +39 -2
- package/dist/index.js +3 -1
- package/dist/main.js +14 -3
- package/package.json +2 -2
- package/web-dist/assets/index-CDjWDFDb.js +150 -0
- package/web-dist/assets/index-DRm1IYKE.css +1 -0
- package/web-dist/index.html +2 -2
- package/web-dist/assets/index-BDVtljmz.css +0 -1
- package/web-dist/assets/index-D9vR6Pjw.js +0 -122
|
@@ -1,3 +1,113 @@
|
|
|
1
|
+
// src/middleware/auth.ts
|
|
2
|
+
import {
|
|
3
|
+
createAuthTokenRepo,
|
|
4
|
+
createAuthSessionRepo,
|
|
5
|
+
hashToken,
|
|
6
|
+
tokenTypeFromValue,
|
|
7
|
+
ForbiddenError,
|
|
8
|
+
UnauthorizedError
|
|
9
|
+
} from "@siming-org/core";
|
|
10
|
+
|
|
11
|
+
// src/middleware/auth-cookie.ts
|
|
12
|
+
var SESSION_COOKIE_NAME = "siming_session";
|
|
13
|
+
|
|
14
|
+
// src/middleware/auth.ts
|
|
15
|
+
var SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
16
|
+
var AUTH_EXEMPT_ROUTES = /* @__PURE__ */ new Set(["GET /health", "GET /api/auth/status"]);
|
|
17
|
+
function isWhoamiRoute(method, path) {
|
|
18
|
+
return (method === "GET" || method === "POST") && path === "/api/auth/whoami";
|
|
19
|
+
}
|
|
20
|
+
var AUTH_FLOW_ROUTES = /* @__PURE__ */ new Set(["POST /api/auth/login", "POST /api/auth/setup"]);
|
|
21
|
+
var ADMIN_ONLY_ROUTES = [
|
|
22
|
+
{ method: "POST", pattern: /^\/api\/projects$/ },
|
|
23
|
+
{ method: "PUT", pattern: /^\/api\/projects\/[^/]+$/ },
|
|
24
|
+
{ method: "PUT", pattern: /^\/api\/settings\/enums\/[^/]+$/ },
|
|
25
|
+
{ method: "DELETE", pattern: /^\/api\/settings\/enums\/[^/]+\/entries\/[^/]+$/ },
|
|
26
|
+
{ method: "POST", pattern: /^\/api\/model-aliases$/ },
|
|
27
|
+
{ method: "PUT", pattern: /^\/api\/model-aliases\/[^/]+$/ },
|
|
28
|
+
{ method: "DELETE", pattern: /^\/api\/model-aliases\/[^/]+$/ },
|
|
29
|
+
// Token 管理全 admin
|
|
30
|
+
{ method: "GET", pattern: /^\/api\/auth\/tokens(\/.*)?$/ },
|
|
31
|
+
{ method: "POST", pattern: /^\/api\/auth\/tokens(\/.*)?$/ }
|
|
32
|
+
];
|
|
33
|
+
function createAuthMiddleware(client, deps) {
|
|
34
|
+
const tokenRepo = createAuthTokenRepo(client.db());
|
|
35
|
+
const sessionRepo = createAuthSessionRepo(client.db());
|
|
36
|
+
const parseCredential = async (c) => {
|
|
37
|
+
const authHeader = c.req.header("Authorization");
|
|
38
|
+
if (authHeader !== void 0 && authHeader.startsWith("Bearer ")) {
|
|
39
|
+
const token = authHeader.slice("Bearer ".length).trim();
|
|
40
|
+
const type = tokenTypeFromValue(token);
|
|
41
|
+
if (type === null) return null;
|
|
42
|
+
const matched = await tokenRepo.findByHash(hashToken(token));
|
|
43
|
+
if (!matched) return null;
|
|
44
|
+
if (matched.type === "admin") return { kind: "admin" };
|
|
45
|
+
return { kind: "project", projectId: matched.projectId ?? "" };
|
|
46
|
+
}
|
|
47
|
+
const sessionId = getCookie(c, SESSION_COOKIE_NAME);
|
|
48
|
+
if (sessionId !== void 0) {
|
|
49
|
+
const session = await sessionRepo.findValidSession(sessionId);
|
|
50
|
+
if (session) return { kind: "admin" };
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
};
|
|
54
|
+
return async (c, next) => {
|
|
55
|
+
const method = c.req.method;
|
|
56
|
+
const path = c.req.path;
|
|
57
|
+
if (isWhoamiRoute(method, path)) {
|
|
58
|
+
const parsed2 = deps.authEnabled ? await parseCredential(c) : null;
|
|
59
|
+
c.set("auth", parsed2 === null ? { kind: deps.authEnabled ? "none" : "open" } : parsed2);
|
|
60
|
+
await next();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (AUTH_EXEMPT_ROUTES.has(`${method} ${path}`) || AUTH_FLOW_ROUTES.has(`${method} ${path}`)) {
|
|
64
|
+
c.set("auth", { kind: "open" });
|
|
65
|
+
await next();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!deps.authEnabled) {
|
|
69
|
+
c.set("auth", { kind: "open" });
|
|
70
|
+
await next();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const parsed = await parseCredential(c);
|
|
74
|
+
if (parsed === null) {
|
|
75
|
+
const authHeader = c.req.header("Authorization");
|
|
76
|
+
if (authHeader !== void 0 && authHeader.startsWith("Bearer ")) {
|
|
77
|
+
throw new UnauthorizedError("AUTH_TOKEN_INVALID");
|
|
78
|
+
}
|
|
79
|
+
throw new UnauthorizedError("AUTH_REQUIRED");
|
|
80
|
+
}
|
|
81
|
+
const auth = parsed.kind === "admin" ? { kind: "admin" } : { kind: "project", projectId: parsed.projectId ?? "" };
|
|
82
|
+
if (auth.kind === "project") {
|
|
83
|
+
for (const rule of ADMIN_ONLY_ROUTES) {
|
|
84
|
+
if (rule.method === method && rule.pattern.test(path)) {
|
|
85
|
+
throw new ForbiddenError();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
c.set("auth", auth);
|
|
90
|
+
await next();
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function getCookie(c, name) {
|
|
94
|
+
const cookieHeader = c.req.header("Cookie");
|
|
95
|
+
if (cookieHeader === void 0) return void 0;
|
|
96
|
+
for (const part of cookieHeader.split(";")) {
|
|
97
|
+
const eq = part.indexOf("=");
|
|
98
|
+
if (eq === -1) continue;
|
|
99
|
+
if (part.slice(0, eq).trim() === name) {
|
|
100
|
+
const raw = part.slice(eq + 1).trim();
|
|
101
|
+
try {
|
|
102
|
+
return decodeURIComponent(raw);
|
|
103
|
+
} catch {
|
|
104
|
+
return raw;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return void 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
1
111
|
// src/static.ts
|
|
2
112
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
3
113
|
import { join, resolve, sep } from "path";
|
|
@@ -104,7 +214,7 @@ function createStaticRoutes(webRoot) {
|
|
|
104
214
|
}
|
|
105
215
|
|
|
106
216
|
// src/app.ts
|
|
107
|
-
import { Hono as
|
|
217
|
+
import { Hono as Hono15 } from "hono";
|
|
108
218
|
|
|
109
219
|
// src/errors.ts
|
|
110
220
|
import { AppError } from "@siming-org/core";
|
|
@@ -126,7 +236,7 @@ function setupErrorHandler(app) {
|
|
|
126
236
|
}
|
|
127
237
|
|
|
128
238
|
// src/routes/index.ts
|
|
129
|
-
import { Hono as
|
|
239
|
+
import { Hono as Hono13 } from "hono";
|
|
130
240
|
|
|
131
241
|
// src/routes/health.ts
|
|
132
242
|
import { Hono as Hono2 } from "hono";
|
|
@@ -155,11 +265,43 @@ function createHealth(client) {
|
|
|
155
265
|
// src/routes/project.routes.ts
|
|
156
266
|
import { Hono as Hono3 } from "hono";
|
|
157
267
|
import { zValidator } from "@hono/zod-validator";
|
|
268
|
+
|
|
269
|
+
// src/routes/auth-guard.ts
|
|
270
|
+
import { NotFoundError, ForbiddenError as ForbiddenError2 } from "@siming-org/core";
|
|
271
|
+
function getAuth(c) {
|
|
272
|
+
const auth = c.get("auth");
|
|
273
|
+
return auth ?? { kind: "open" };
|
|
274
|
+
}
|
|
275
|
+
function assertProjectVisible(c, resourceProjectId) {
|
|
276
|
+
const auth = getAuth(c);
|
|
277
|
+
if (auth.kind === "project" && resourceProjectId !== void 0 && resourceProjectId !== "" && resourceProjectId !== auth.projectId) {
|
|
278
|
+
throw new NotFoundError("resource", resourceProjectId);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function assertProjectScope(c, resourceProjectId) {
|
|
282
|
+
const auth = getAuth(c);
|
|
283
|
+
if (auth.kind === "project" && resourceProjectId !== auth.projectId) {
|
|
284
|
+
throw new NotFoundError("resource", resourceProjectId);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function requireAdminLike(c) {
|
|
288
|
+
const auth = getAuth(c);
|
|
289
|
+
if (auth.kind === "project") {
|
|
290
|
+
throw new ForbiddenError2();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function resolveProjectFilter(c, requested) {
|
|
294
|
+
const auth = getAuth(c);
|
|
295
|
+
if (auth.kind === "project") return auth.projectId;
|
|
296
|
+
return requested;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/routes/project.routes.ts
|
|
158
300
|
import {
|
|
159
301
|
createProjectRepo,
|
|
160
302
|
ProjectCreateSchema,
|
|
161
303
|
ProjectUpdateSchema,
|
|
162
|
-
NotFoundError,
|
|
304
|
+
NotFoundError as NotFoundError2,
|
|
163
305
|
ConflictError,
|
|
164
306
|
BIZ_CODE_MESSAGES,
|
|
165
307
|
DEFAULT_PROJECT_KEY
|
|
@@ -167,7 +309,7 @@ import {
|
|
|
167
309
|
async function loadProjectForWrite(projectRepo, projectId) {
|
|
168
310
|
const project = await projectRepo.getById(projectId);
|
|
169
311
|
if (!project) {
|
|
170
|
-
throw new
|
|
312
|
+
throw new NotFoundError2("project", projectId, {
|
|
171
313
|
bizCode: "PROJECT_NOT_FOUND",
|
|
172
314
|
message: BIZ_CODE_MESSAGES.PROJECT_NOT_FOUND
|
|
173
315
|
});
|
|
@@ -185,13 +327,19 @@ function createProjectRoutes(client) {
|
|
|
185
327
|
const repo = createProjectRepo(client.db());
|
|
186
328
|
app.get("/api/projects", async (c) => {
|
|
187
329
|
const status = c.req.query("status");
|
|
330
|
+
const auth = getAuth(c);
|
|
331
|
+
if (auth.kind === "project") {
|
|
332
|
+
const own = await repo.getById(auth.projectId);
|
|
333
|
+
return c.json(own ? [own] : []);
|
|
334
|
+
}
|
|
188
335
|
const projects = await repo.list();
|
|
189
336
|
return c.json(status ? projects.filter((p) => p.status === status) : projects);
|
|
190
337
|
});
|
|
191
338
|
app.get("/api/projects/:id", async (c) => {
|
|
192
339
|
const id = c.req.param("id");
|
|
193
340
|
const project = await repo.getById(id);
|
|
194
|
-
if (!project) throw new
|
|
341
|
+
if (!project) throw new NotFoundError2("project", id);
|
|
342
|
+
assertProjectScope(c, project.id ?? id);
|
|
195
343
|
return c.json(project);
|
|
196
344
|
});
|
|
197
345
|
app.post("/api/projects", zValidator("json", ProjectCreateSchema), async (c) => {
|
|
@@ -202,7 +350,7 @@ function createProjectRoutes(client) {
|
|
|
202
350
|
const id = c.req.param("id");
|
|
203
351
|
const data = c.req.valid("json");
|
|
204
352
|
const existing = await repo.getById(id);
|
|
205
|
-
if (!existing) throw new
|
|
353
|
+
if (!existing) throw new NotFoundError2("project", id);
|
|
206
354
|
if (data.status === "archived" && existing.key === DEFAULT_PROJECT_KEY) {
|
|
207
355
|
throw new ConflictError("project", id, {
|
|
208
356
|
bizCode: "DEFAULT_PROJECT_IMMUTABLE",
|
|
@@ -218,7 +366,7 @@ function createProjectRoutes(client) {
|
|
|
218
366
|
if (data.description !== void 0) patch.description = data.description;
|
|
219
367
|
if (data.status !== void 0) patch.status = data.status;
|
|
220
368
|
const project = await repo.update(id, patch);
|
|
221
|
-
if (!project) throw new
|
|
369
|
+
if (!project) throw new NotFoundError2("project", id);
|
|
222
370
|
return c.json(project);
|
|
223
371
|
});
|
|
224
372
|
return app;
|
|
@@ -239,7 +387,7 @@ import {
|
|
|
239
387
|
OBJECT_ID_HEX,
|
|
240
388
|
assertReferencesDeletable,
|
|
241
389
|
assertReferenceAggregateLimit,
|
|
242
|
-
NotFoundError as
|
|
390
|
+
NotFoundError as NotFoundError3,
|
|
243
391
|
ConflictError as ConflictError2,
|
|
244
392
|
BadRequestError,
|
|
245
393
|
BIZ_CODE_MESSAGES as BIZ_CODE_MESSAGES2
|
|
@@ -283,6 +431,10 @@ function createSkillRoutes(client) {
|
|
|
283
431
|
app.get("/api/skills", zValidator2("query", SkillListQuerySchema), async (c) => {
|
|
284
432
|
const q = c.req.valid("query");
|
|
285
433
|
const opts = { includeDisabled: q.includeDisabled, q: q.q };
|
|
434
|
+
const auth = getAuth(c);
|
|
435
|
+
if (auth.kind === "project") {
|
|
436
|
+
return c.json((await repo.listSkillsByScope(auth.projectId, opts)).map(skillToListItem));
|
|
437
|
+
}
|
|
286
438
|
if (q.scope) {
|
|
287
439
|
return c.json((await repo.listSkillsByScopeFilter(q.scope, q.projectId, opts)).map(skillToListItem));
|
|
288
440
|
}
|
|
@@ -290,17 +442,28 @@ function createSkillRoutes(client) {
|
|
|
290
442
|
(q.projectId ? await repo.listSkillsByScope(q.projectId, opts) : await repo.listSkills(opts)).map(skillToListItem)
|
|
291
443
|
);
|
|
292
444
|
});
|
|
445
|
+
app.get("/api/skills/:name/scope-availability", zValidator2("query", z.object({ scope: z.enum(["global", "project"]) })), async (c) => {
|
|
446
|
+
const name = c.req.param("name");
|
|
447
|
+
const { scope } = c.req.valid("query");
|
|
448
|
+
return c.json({ name, scope, available: !await repo.hasNameScopeConflict(name, scope) });
|
|
449
|
+
});
|
|
293
450
|
app.get("/api/skills/:name", zValidator2("query", SkillByNameQuerySchema), async (c) => {
|
|
294
451
|
const name = c.req.param("name");
|
|
295
452
|
const q = c.req.valid("query");
|
|
296
453
|
const skill = await resolveAssetByScope(repo, name, q, { includeDisabled: q.includeDisabled });
|
|
297
|
-
if (!skill) throw new
|
|
454
|
+
if (!skill) throw new NotFoundError3("skill", name);
|
|
455
|
+
assertProjectVisible(c, skill.projectId);
|
|
298
456
|
return c.json(skill);
|
|
299
457
|
});
|
|
300
458
|
app.post("/api/skills", zValidator2("json", SkillCreateSchema), async (c) => {
|
|
301
459
|
const data = c.req.valid("json");
|
|
302
460
|
await assertCategoryValid(data.category);
|
|
303
|
-
if (data.scope === "project")
|
|
461
|
+
if (data.scope === "project") {
|
|
462
|
+
assertProjectScope(c, data.projectId);
|
|
463
|
+
await loadProjectForWrite(projectRepo, data.projectId);
|
|
464
|
+
} else {
|
|
465
|
+
requireAdminLike(c);
|
|
466
|
+
}
|
|
304
467
|
return c.json(await repo.createSkill(data), 201);
|
|
305
468
|
});
|
|
306
469
|
app.put("/api/skills/:name", zValidator2("query", SkillByNameQuerySchema), zValidator2("json", SkillUpdateSchema), async (c) => {
|
|
@@ -308,7 +471,12 @@ function createSkillRoutes(client) {
|
|
|
308
471
|
const data = c.req.valid("json");
|
|
309
472
|
const q = c.req.valid("query");
|
|
310
473
|
const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
|
|
311
|
-
if (!existing) throw new
|
|
474
|
+
if (!existing) throw new NotFoundError3("skill", name);
|
|
475
|
+
if ((q.scope ?? "global") === "global") {
|
|
476
|
+
requireAdminLike(c);
|
|
477
|
+
} else {
|
|
478
|
+
assertProjectScope(c, existing.projectId ?? "");
|
|
479
|
+
}
|
|
312
480
|
if (data.category !== void 0) await assertCategoryValid(data.category);
|
|
313
481
|
if (isScopeMutation(existing, data)) {
|
|
314
482
|
throw new ConflictError2("skill", name, {
|
|
@@ -333,7 +501,7 @@ function createSkillRoutes(client) {
|
|
|
333
501
|
nextReferences: data.references
|
|
334
502
|
});
|
|
335
503
|
const skill = await repo.updateByNameScoped(name, q.scope ?? "global", q.projectId, data);
|
|
336
|
-
if (!skill) throw new
|
|
504
|
+
if (!skill) throw new NotFoundError3("skill", name);
|
|
337
505
|
return c.json(skill);
|
|
338
506
|
});
|
|
339
507
|
app.post("/api/skills/:name/copy", zValidator2("query", SkillByNameQuerySchema), zValidator2("json", SkillCopySchema), async (c) => {
|
|
@@ -341,9 +509,13 @@ function createSkillRoutes(client) {
|
|
|
341
509
|
const input = c.req.valid("json");
|
|
342
510
|
const q = c.req.valid("query");
|
|
343
511
|
const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
|
|
344
|
-
if (!existing) throw new
|
|
512
|
+
if (!existing) throw new NotFoundError3("skill", name);
|
|
513
|
+
assertProjectVisible(c, existing.projectId);
|
|
345
514
|
if (input.newScope === "project") {
|
|
515
|
+
assertProjectScope(c, input.targetProjectId);
|
|
346
516
|
await loadProjectForWrite(projectRepo, input.targetProjectId);
|
|
517
|
+
} else {
|
|
518
|
+
requireAdminLike(c);
|
|
347
519
|
}
|
|
348
520
|
const copy = await repo.copySkill(
|
|
349
521
|
name,
|
|
@@ -357,9 +529,14 @@ function createSkillRoutes(client) {
|
|
|
357
529
|
const name = c.req.param("name");
|
|
358
530
|
const q = c.req.valid("query");
|
|
359
531
|
const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
|
|
360
|
-
if (!existing) throw new
|
|
532
|
+
if (!existing) throw new NotFoundError3("skill", name);
|
|
533
|
+
if ((q.scope ?? "global") === "global") {
|
|
534
|
+
requireAdminLike(c);
|
|
535
|
+
} else {
|
|
536
|
+
assertProjectScope(c, existing.projectId ?? "");
|
|
537
|
+
}
|
|
361
538
|
const deleted = await repo.deleteByNameScoped(name, q.scope ?? "global", q.projectId);
|
|
362
|
-
if (!deleted) throw new
|
|
539
|
+
if (!deleted) throw new NotFoundError3("skill", name);
|
|
363
540
|
return c.body(null, 204);
|
|
364
541
|
});
|
|
365
542
|
app.post("/api/skills/:name/enabled", zValidator2("query", SkillByNameQuerySchema), zValidator2("json", AssetSetEnabledSchema), async (c) => {
|
|
@@ -367,13 +544,16 @@ function createSkillRoutes(client) {
|
|
|
367
544
|
const body = c.req.valid("json");
|
|
368
545
|
const q = c.req.valid("query");
|
|
369
546
|
const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
|
|
370
|
-
if (!existing) throw new
|
|
547
|
+
if (!existing) throw new NotFoundError3("skill", name);
|
|
371
548
|
const scope = q.scope ?? "global";
|
|
372
|
-
if (scope === "
|
|
549
|
+
if (scope === "global") {
|
|
550
|
+
requireAdminLike(c);
|
|
551
|
+
} else {
|
|
552
|
+
assertProjectScope(c, existing.projectId ?? "");
|
|
373
553
|
await loadProjectForWrite(projectRepo, existing.projectId ?? q.projectId);
|
|
374
554
|
}
|
|
375
555
|
const skill = await repo.updateByNameScoped(name, scope, q.projectId, { enabled: body.enabled });
|
|
376
|
-
if (!skill) throw new
|
|
556
|
+
if (!skill) throw new NotFoundError3("skill", name);
|
|
377
557
|
return c.json({ name: skill.name, enabled: skill.enabled ?? true });
|
|
378
558
|
});
|
|
379
559
|
return app;
|
|
@@ -403,7 +583,7 @@ import {
|
|
|
403
583
|
AssetSetEnabledSchema as AssetSetEnabledSchema2,
|
|
404
584
|
assertReferencesDeletable as assertReferencesDeletable2,
|
|
405
585
|
assertReferenceAggregateLimit as assertReferenceAggregateLimit2,
|
|
406
|
-
NotFoundError as
|
|
586
|
+
NotFoundError as NotFoundError4,
|
|
407
587
|
ConflictError as ConflictError3,
|
|
408
588
|
BIZ_CODE_MESSAGES as BIZ_CODE_MESSAGES3,
|
|
409
589
|
assertBoundSkillsCompatible,
|
|
@@ -431,6 +611,10 @@ function createAgentRoutes(client) {
|
|
|
431
611
|
app.get("/api/agents", zValidator3("query", AgentListQuerySchema), async (c) => {
|
|
432
612
|
const q = c.req.valid("query");
|
|
433
613
|
const opts = { includeDisabled: q.includeDisabled, q: q.q };
|
|
614
|
+
const auth = getAuth(c);
|
|
615
|
+
if (auth.kind === "project") {
|
|
616
|
+
return c.json((await repo.listAgentsByScope(auth.projectId, opts)).map(agentToListItem));
|
|
617
|
+
}
|
|
434
618
|
if (q.scope) {
|
|
435
619
|
return c.json((await repo.listAgentsByScopeFilter(q.scope, q.projectId, opts)).map(agentToListItem));
|
|
436
620
|
}
|
|
@@ -438,16 +622,27 @@ function createAgentRoutes(client) {
|
|
|
438
622
|
(q.projectId ? await repo.listAgentsByScope(q.projectId, opts) : await repo.listAgents(opts)).map(agentToListItem)
|
|
439
623
|
);
|
|
440
624
|
});
|
|
625
|
+
app.get("/api/agents/:name/scope-availability", zValidator3("query", z2.object({ scope: z2.enum(["global", "project"]) })), async (c) => {
|
|
626
|
+
const name = c.req.param("name");
|
|
627
|
+
const { scope } = c.req.valid("query");
|
|
628
|
+
return c.json({ name, scope, available: !await repo.hasNameScopeConflict(name, scope) });
|
|
629
|
+
});
|
|
441
630
|
app.get("/api/agents/:name", zValidator3("query", SkillByNameQuerySchema), async (c) => {
|
|
442
631
|
const name = c.req.param("name");
|
|
443
632
|
const q = c.req.valid("query");
|
|
444
633
|
const agent = await resolveAssetByScope(repo, name, q, { includeDisabled: q.includeDisabled });
|
|
445
|
-
if (!agent) throw new
|
|
634
|
+
if (!agent) throw new NotFoundError4("agent", name);
|
|
635
|
+
assertProjectVisible(c, agent.projectId);
|
|
446
636
|
return c.json(agent);
|
|
447
637
|
});
|
|
448
638
|
app.post("/api/agents", zValidator3("json", AgentCreateSchema), async (c) => {
|
|
449
639
|
const data = c.req.valid("json");
|
|
450
|
-
if (data.scope === "project")
|
|
640
|
+
if (data.scope === "project") {
|
|
641
|
+
assertProjectScope(c, data.projectId);
|
|
642
|
+
await loadProjectForWrite(projectRepo, data.projectId);
|
|
643
|
+
} else {
|
|
644
|
+
requireAdminLike(c);
|
|
645
|
+
}
|
|
451
646
|
await assertBoundSkillsCompatible(skillRepo, data.scope, data.projectId, data.boundSkills);
|
|
452
647
|
await assertModelAliasExists(aliasRepo, data.model);
|
|
453
648
|
return c.json(await repo.createAgent(data), 201);
|
|
@@ -457,7 +652,12 @@ function createAgentRoutes(client) {
|
|
|
457
652
|
const data = c.req.valid("json");
|
|
458
653
|
const q = c.req.valid("query");
|
|
459
654
|
const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
|
|
460
|
-
if (!existing) throw new
|
|
655
|
+
if (!existing) throw new NotFoundError4("agent", name);
|
|
656
|
+
if ((q.scope ?? "global") === "global") {
|
|
657
|
+
requireAdminLike(c);
|
|
658
|
+
} else {
|
|
659
|
+
assertProjectScope(c, existing.projectId ?? "");
|
|
660
|
+
}
|
|
461
661
|
let payload = data;
|
|
462
662
|
if (data["function"] !== void 0 && data["function"] !== existing["function"]) {
|
|
463
663
|
payload = { ...data, version: bumpPatch(data.version ?? existing.version) };
|
|
@@ -496,7 +696,7 @@ function createAgentRoutes(client) {
|
|
|
496
696
|
nextReferences: data.references
|
|
497
697
|
});
|
|
498
698
|
const agent = await repo.updateByNameScoped(name, q.scope ?? "global", q.projectId, payload);
|
|
499
|
-
if (!agent) throw new
|
|
699
|
+
if (!agent) throw new NotFoundError4("agent", name);
|
|
500
700
|
return c.json(agent);
|
|
501
701
|
});
|
|
502
702
|
app.post("/api/agents/:name/copy", zValidator3("query", SkillByNameQuerySchema), zValidator3("json", AgentCopySchema), async (c) => {
|
|
@@ -504,9 +704,13 @@ function createAgentRoutes(client) {
|
|
|
504
704
|
const input = c.req.valid("json");
|
|
505
705
|
const q = c.req.valid("query");
|
|
506
706
|
const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
|
|
507
|
-
if (!existing) throw new
|
|
707
|
+
if (!existing) throw new NotFoundError4("agent", name);
|
|
708
|
+
assertProjectVisible(c, existing.projectId);
|
|
508
709
|
if (input.newScope === "project") {
|
|
710
|
+
assertProjectScope(c, input.targetProjectId);
|
|
509
711
|
await loadProjectForWrite(projectRepo, input.targetProjectId);
|
|
712
|
+
} else {
|
|
713
|
+
requireAdminLike(c);
|
|
510
714
|
}
|
|
511
715
|
const copy = await repo.copyAgent(
|
|
512
716
|
name,
|
|
@@ -521,9 +725,14 @@ function createAgentRoutes(client) {
|
|
|
521
725
|
const name = c.req.param("name");
|
|
522
726
|
const q = c.req.valid("query");
|
|
523
727
|
const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
|
|
524
|
-
if (!existing) throw new
|
|
728
|
+
if (!existing) throw new NotFoundError4("agent", name);
|
|
729
|
+
if ((q.scope ?? "global") === "global") {
|
|
730
|
+
requireAdminLike(c);
|
|
731
|
+
} else {
|
|
732
|
+
assertProjectScope(c, existing.projectId ?? "");
|
|
733
|
+
}
|
|
525
734
|
const deleted = await repo.deleteByNameScoped(name, q.scope ?? "global", q.projectId);
|
|
526
|
-
if (!deleted) throw new
|
|
735
|
+
if (!deleted) throw new NotFoundError4("agent", name);
|
|
527
736
|
return c.body(null, 204);
|
|
528
737
|
});
|
|
529
738
|
app.post("/api/agents/:name/enabled", zValidator3("query", SkillByNameQuerySchema), zValidator3("json", AssetSetEnabledSchema2), async (c) => {
|
|
@@ -531,13 +740,16 @@ function createAgentRoutes(client) {
|
|
|
531
740
|
const body = c.req.valid("json");
|
|
532
741
|
const q = c.req.valid("query");
|
|
533
742
|
const existing = await resolveAssetByScope(repo, name, q, { includeDisabled: true });
|
|
534
|
-
if (!existing) throw new
|
|
743
|
+
if (!existing) throw new NotFoundError4("agent", name);
|
|
535
744
|
const scope = q.scope ?? "global";
|
|
536
|
-
if (scope === "
|
|
745
|
+
if (scope === "global") {
|
|
746
|
+
requireAdminLike(c);
|
|
747
|
+
} else {
|
|
748
|
+
assertProjectScope(c, existing.projectId ?? "");
|
|
537
749
|
await loadProjectForWrite(projectRepo, existing.projectId ?? q.projectId);
|
|
538
750
|
}
|
|
539
751
|
const agent = await repo.updateByNameScoped(name, scope, q.projectId, { enabled: body.enabled });
|
|
540
|
-
if (!agent) throw new
|
|
752
|
+
if (!agent) throw new NotFoundError4("agent", name);
|
|
541
753
|
return c.json({ name: agent.name, enabled: agent.enabled ?? true });
|
|
542
754
|
});
|
|
543
755
|
return app;
|
|
@@ -550,7 +762,7 @@ import {
|
|
|
550
762
|
createModelAliasRepo as createModelAliasRepo2,
|
|
551
763
|
ModelAliasCreateSchema,
|
|
552
764
|
ModelAliasUpdateSchema,
|
|
553
|
-
NotFoundError as
|
|
765
|
+
NotFoundError as NotFoundError5,
|
|
554
766
|
ConflictError as ConflictError4,
|
|
555
767
|
BIZ_CODE_MESSAGES as BIZ_CODE_MESSAGES4
|
|
556
768
|
} from "@siming-org/core";
|
|
@@ -575,7 +787,7 @@ function createModelAliasRoutes(client) {
|
|
|
575
787
|
const code = c.req.param("code");
|
|
576
788
|
const data = c.req.valid("json");
|
|
577
789
|
const existing = await repo.getByCode(code);
|
|
578
|
-
if (!existing) throw new
|
|
790
|
+
if (!existing) throw new NotFoundError5("model-alias", code);
|
|
579
791
|
if (data.code !== void 0 && data.code !== code) {
|
|
580
792
|
throw new ConflictError4("model-alias", code, {
|
|
581
793
|
bizCode: "MODEL_CODE_IMMUTABLE",
|
|
@@ -586,13 +798,13 @@ function createModelAliasRoutes(client) {
|
|
|
586
798
|
if (data.name !== void 0) patch.name = data.name;
|
|
587
799
|
if (data.realModel !== void 0) patch.realModel = data.realModel;
|
|
588
800
|
const alias = await repo.updateByCode(code, patch);
|
|
589
|
-
if (!alias) throw new
|
|
801
|
+
if (!alias) throw new NotFoundError5("model-alias", code);
|
|
590
802
|
return c.json(alias);
|
|
591
803
|
});
|
|
592
804
|
app.delete("/api/model-aliases/:code", async (c) => {
|
|
593
805
|
const code = c.req.param("code");
|
|
594
806
|
const deleted = await repo.deleteByCode(code);
|
|
595
|
-
if (!deleted) throw new
|
|
807
|
+
if (!deleted) throw new NotFoundError5("model-alias", code);
|
|
596
808
|
return c.body(null, 204);
|
|
597
809
|
});
|
|
598
810
|
return app;
|
|
@@ -608,98 +820,152 @@ import {
|
|
|
608
820
|
createSkillRepo as createSkillRepo3,
|
|
609
821
|
createAgentRepo as createAgentRepo2,
|
|
610
822
|
createModelAliasRepo as createModelAliasRepo3,
|
|
823
|
+
createNodePresetRepo,
|
|
611
824
|
DagTemplateCreateSchema,
|
|
612
825
|
DagTemplateCopySchema,
|
|
613
826
|
DagTemplateUpdateSchema,
|
|
614
827
|
AssetSetEnabledSchema as AssetSetEnabledSchema3,
|
|
615
828
|
ImportPlanRequestSchema,
|
|
616
829
|
ImportApplyRequestSchema,
|
|
830
|
+
UpgradeApplyRequestSchema,
|
|
831
|
+
assertUniqueNodeIds,
|
|
832
|
+
buildUpgradePlan,
|
|
833
|
+
applyUpgradeDecisions,
|
|
617
834
|
composeExportBundle,
|
|
618
835
|
buildImportPlan,
|
|
619
836
|
applyImport,
|
|
620
|
-
NotFoundError as
|
|
837
|
+
NotFoundError as NotFoundError7,
|
|
621
838
|
ConflictError as ConflictError5,
|
|
622
839
|
BIZ_CODE_MESSAGES as BIZ_CODE_MESSAGES5
|
|
623
840
|
} from "@siming-org/core";
|
|
841
|
+
|
|
842
|
+
// src/routes/template-ref.ts
|
|
843
|
+
import { NotFoundError as NotFoundError6 } from "@siming-org/core";
|
|
844
|
+
var TEMPLATE_CANDIDATE_LIMIT = 5;
|
|
845
|
+
async function resolveTemplateOr404(repo, ref, projectCtx, refHint) {
|
|
846
|
+
const template = await repo.resolveTemplateByRef(ref, projectCtx);
|
|
847
|
+
if (!template || template.id === void 0) {
|
|
848
|
+
const message = !/^[a-f0-9]{24}$/.test(ref) && projectCtx === void 0 ? `dag-template not found: ${ref}${refHint}` : `dag-template not found: ${ref}`;
|
|
849
|
+
const err = new NotFoundError6("dag-template", ref, { message });
|
|
850
|
+
if (projectCtx !== void 0) {
|
|
851
|
+
err.withTemplateCandidates(await repo.findSimilarTemplates(projectCtx, ref, TEMPLATE_CANDIDATE_LIMIT));
|
|
852
|
+
}
|
|
853
|
+
throw err;
|
|
854
|
+
}
|
|
855
|
+
return template;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/routes/dag-template.routes.ts
|
|
624
859
|
var DagTemplateListQuerySchema = z3.object({
|
|
625
860
|
projectId: z3.string().optional(),
|
|
626
861
|
name: z3.string().optional(),
|
|
862
|
+
code: z3.string().optional(),
|
|
627
863
|
includeDisabled: z3.stringbool().optional()
|
|
628
864
|
});
|
|
865
|
+
var QUERY_PROJECT_HINT = "\uFF08code \u5F62\u6001\u5BFB\u5740\u9700 ?projectId= \u6307\u5B9A\u9879\u76EE\u2014\u2014code \u9879\u76EE\u5185\u552F\u4E00\uFF0C\u65E0\u9879\u76EE\u57DF\u4E0D\u53EF\u6D88\u6B67\uFF1B\u6216\u6539\u4F20 24 \u4F4D\u6570\u636E\u5E93 id\uFF09";
|
|
629
866
|
function createDagTemplateRoutes(client) {
|
|
630
867
|
const app = new Hono7();
|
|
631
868
|
const repo = createDagTemplateRepo(client.db());
|
|
632
869
|
const projectRepo = createProjectRepo4(client.db());
|
|
870
|
+
const nodePresetRepo = createNodePresetRepo(client.db());
|
|
633
871
|
const transferDeps = {
|
|
634
872
|
templateRepo: repo,
|
|
635
873
|
skillRepo: createSkillRepo3(client.db()),
|
|
636
874
|
agentRepo: createAgentRepo2(client.db()),
|
|
637
|
-
modelAliasRepo: createModelAliasRepo3(client.db())
|
|
875
|
+
modelAliasRepo: createModelAliasRepo3(client.db()),
|
|
876
|
+
nodePresetRepo
|
|
638
877
|
};
|
|
639
878
|
app.get("/api/dag/templates", zValidator5("query", DagTemplateListQuerySchema), async (c) => {
|
|
640
879
|
const q = c.req.valid("query");
|
|
880
|
+
const projectFilter = resolveProjectFilter(c, q.projectId);
|
|
641
881
|
return c.json(
|
|
642
882
|
await repo.listDagTemplates({
|
|
643
|
-
...
|
|
883
|
+
...projectFilter ? { projectId: projectFilter } : {},
|
|
644
884
|
...q.name ? { name: q.name } : {},
|
|
885
|
+
...q.code ? { code: q.code } : {},
|
|
645
886
|
...q.includeDisabled !== void 0 ? { includeDisabled: q.includeDisabled } : {}
|
|
646
887
|
})
|
|
647
888
|
);
|
|
648
889
|
});
|
|
649
890
|
app.get("/api/dag/templates/:id", async (c) => {
|
|
650
|
-
const
|
|
651
|
-
const template = await repo.
|
|
652
|
-
|
|
891
|
+
const ref = c.req.param("id");
|
|
892
|
+
const template = await resolveTemplateOr404(repo, ref, resolveProjectFilter(c, c.req.query("projectId")), QUERY_PROJECT_HINT);
|
|
893
|
+
assertProjectScope(c, template.projectId);
|
|
653
894
|
return c.json(template);
|
|
654
895
|
});
|
|
655
896
|
app.post("/api/dag/templates/:id/enabled", zValidator5("json", AssetSetEnabledSchema3), async (c) => {
|
|
656
|
-
const
|
|
897
|
+
const ref = c.req.param("id");
|
|
657
898
|
const body = c.req.valid("json");
|
|
658
|
-
const existing = await repo.
|
|
659
|
-
|
|
899
|
+
const existing = await resolveTemplateOr404(repo, ref, resolveProjectFilter(c, c.req.query("projectId")), QUERY_PROJECT_HINT);
|
|
900
|
+
assertProjectScope(c, existing.projectId);
|
|
660
901
|
await loadProjectForWrite(projectRepo, existing.projectId);
|
|
661
|
-
const template = await repo.update(id, { enabled: body.enabled });
|
|
662
|
-
if (!template) throw new
|
|
902
|
+
const template = await repo.update(existing.id, { enabled: body.enabled });
|
|
903
|
+
if (!template) throw new NotFoundError7("dag-template", existing.id);
|
|
663
904
|
return c.json({ id: template.id, name: template.name, enabled: template.enabled ?? true });
|
|
664
905
|
});
|
|
665
906
|
app.post("/api/dag/templates", zValidator5("json", DagTemplateCreateSchema), async (c) => {
|
|
666
907
|
const data = c.req.valid("json");
|
|
908
|
+
assertProjectScope(c, data.projectId);
|
|
667
909
|
await loadProjectForWrite(projectRepo, data.projectId);
|
|
910
|
+
assertUniqueNodeIds(data.nodes);
|
|
911
|
+
if (data.code !== void 0) {
|
|
912
|
+
const dup = await repo._collection.findOne(
|
|
913
|
+
{ projectId: data.projectId, code: data.code },
|
|
914
|
+
{ projection: { _id: 1, name: 1 } }
|
|
915
|
+
);
|
|
916
|
+
if (dup) {
|
|
917
|
+
throw new ConflictError5("dag-template", `code conflict: ${data.code}`, {
|
|
918
|
+
bizCode: "TEMPLATE_CODE_CONFLICT",
|
|
919
|
+
message: `${BIZ_CODE_MESSAGES5.TEMPLATE_CODE_CONFLICT}\uFF08\u51B2\u7A81\u6A21\u677F\uFF1A${String(dup.name)}\uFF09`
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
}
|
|
668
923
|
return c.json(await repo.createDagTemplate(data), 201);
|
|
669
924
|
});
|
|
670
925
|
app.put("/api/dag/templates/:id", zValidator5("json", DagTemplateUpdateSchema), async (c) => {
|
|
671
|
-
const
|
|
926
|
+
const ref = c.req.param("id");
|
|
672
927
|
const data = c.req.valid("json");
|
|
673
|
-
const existing = await repo.
|
|
674
|
-
|
|
675
|
-
if (data.
|
|
676
|
-
throw new ConflictError5("dag-template",
|
|
928
|
+
const existing = await resolveTemplateOr404(repo, ref, resolveProjectFilter(c, c.req.query("projectId")), QUERY_PROJECT_HINT);
|
|
929
|
+
assertProjectScope(c, existing.projectId);
|
|
930
|
+
if (data.code !== void 0 && data.code !== existing.code) {
|
|
931
|
+
throw new ConflictError5("dag-template", ref, {
|
|
932
|
+
bizCode: "TEMPLATE_CODE_IMMUTABLE",
|
|
933
|
+
message: BIZ_CODE_MESSAGES5.TEMPLATE_CODE_IMMUTABLE
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
const { code: _codeStrip, ...writable } = data;
|
|
937
|
+
if (writable.projectId !== void 0 && writable.projectId !== existing.projectId) {
|
|
938
|
+
throw new ConflictError5("dag-template", ref, {
|
|
677
939
|
bizCode: "TEMPLATE_PROJECT_IMMUTABLE",
|
|
678
940
|
message: BIZ_CODE_MESSAGES5.TEMPLATE_PROJECT_IMMUTABLE
|
|
679
941
|
});
|
|
680
942
|
}
|
|
681
943
|
await loadProjectForWrite(projectRepo, existing.projectId);
|
|
682
|
-
if (data.
|
|
944
|
+
if (data.nodes !== void 0) {
|
|
945
|
+
assertUniqueNodeIds(data.nodes);
|
|
946
|
+
}
|
|
947
|
+
if (writable.name !== void 0 && writable.name !== existing.name) {
|
|
683
948
|
const dup = await repo._collection.findOne(
|
|
684
|
-
{ projectId: existing.projectId, name:
|
|
949
|
+
{ projectId: existing.projectId, name: writable.name },
|
|
685
950
|
{ projection: { _id: 1 } }
|
|
686
951
|
);
|
|
687
952
|
if (dup) {
|
|
688
|
-
throw new ConflictError5("dag-template", `name conflict: ${
|
|
953
|
+
throw new ConflictError5("dag-template", `name conflict: ${writable.name}`, {
|
|
689
954
|
bizCode: "TEMPLATE_NAME_CONFLICT",
|
|
690
955
|
message: BIZ_CODE_MESSAGES5.TEMPLATE_NAME_CONFLICT
|
|
691
956
|
});
|
|
692
957
|
}
|
|
693
958
|
}
|
|
694
|
-
const template = await repo.update(id,
|
|
695
|
-
if (!template) throw new
|
|
959
|
+
const template = await repo.update(existing.id, writable);
|
|
960
|
+
if (!template) throw new NotFoundError7("dag-template", existing.id);
|
|
696
961
|
return c.json(template);
|
|
697
962
|
});
|
|
698
963
|
app.post("/api/dag/templates/:id/copy", zValidator5("json", DagTemplateCopySchema), async (c) => {
|
|
699
|
-
const
|
|
964
|
+
const ref = c.req.param("id");
|
|
700
965
|
const input = c.req.valid("json");
|
|
701
|
-
const existing = await repo.
|
|
702
|
-
|
|
966
|
+
const existing = await resolveTemplateOr404(repo, ref, resolveProjectFilter(c, c.req.query("projectId")), QUERY_PROJECT_HINT);
|
|
967
|
+
assertProjectScope(c, existing.projectId);
|
|
968
|
+
assertProjectScope(c, input.targetProjectId);
|
|
703
969
|
await loadProjectForWrite(projectRepo, input.targetProjectId);
|
|
704
970
|
const dup = await repo._collection.findOne(
|
|
705
971
|
{ projectId: input.targetProjectId, name: input.newName },
|
|
@@ -711,40 +977,289 @@ function createDagTemplateRoutes(client) {
|
|
|
711
977
|
message: BIZ_CODE_MESSAGES5.TEMPLATE_NAME_CONFLICT
|
|
712
978
|
});
|
|
713
979
|
}
|
|
714
|
-
const copy = await repo.copyDagTemplate(id, input.targetProjectId, input.newName);
|
|
980
|
+
const copy = await repo.copyDagTemplate(existing.id, input.targetProjectId, input.newName, input.newCode);
|
|
715
981
|
return c.json(copy, 201);
|
|
716
982
|
});
|
|
717
983
|
app.get("/api/dag/templates/:id/export", async (c) => {
|
|
718
|
-
const
|
|
719
|
-
|
|
984
|
+
const ref = c.req.param("id");
|
|
985
|
+
const template = await resolveTemplateOr404(repo, ref, resolveProjectFilter(c, c.req.query("projectId")), QUERY_PROJECT_HINT);
|
|
986
|
+
assertProjectScope(c, template.projectId);
|
|
987
|
+
return c.json(await composeExportBundle(transferDeps, template.id));
|
|
720
988
|
});
|
|
721
989
|
app.post("/api/dag/templates/import/plan", zValidator5("json", ImportPlanRequestSchema), async (c) => {
|
|
722
990
|
const body = c.req.valid("json");
|
|
991
|
+
assertProjectScope(c, body.targetProjectId);
|
|
723
992
|
await loadProjectForWrite(projectRepo, body.targetProjectId);
|
|
724
993
|
return c.json(await buildImportPlan(transferDeps, body.targetProjectId, body.bundle));
|
|
725
994
|
});
|
|
726
995
|
app.post("/api/dag/templates/import/apply", zValidator5("json", ImportApplyRequestSchema), async (c) => {
|
|
727
996
|
const body = c.req.valid("json");
|
|
997
|
+
assertProjectScope(c, body.targetProjectId);
|
|
728
998
|
await loadProjectForWrite(projectRepo, body.targetProjectId);
|
|
729
999
|
return c.json(await applyImport(transferDeps, body.targetProjectId, body.bundle, body.decisions));
|
|
730
1000
|
});
|
|
731
|
-
app.
|
|
1001
|
+
app.get("/api/dag/templates/:id/upgrade/plan", async (c) => {
|
|
732
1002
|
const id = c.req.param("id");
|
|
733
|
-
const
|
|
734
|
-
if (!
|
|
1003
|
+
const template = await repo.getById(id);
|
|
1004
|
+
if (!template) throw new NotFoundError7("dag-template", id);
|
|
1005
|
+
assertProjectScope(c, template.projectId);
|
|
1006
|
+
const codes = [...new Set(template.nodes.flatMap((n) => n.sourcePreset ? [n.sourcePreset.code] : []))];
|
|
1007
|
+
const presetsByCode = await nodePresetRepo.listByCodes(codes);
|
|
1008
|
+
return c.json(buildUpgradePlan(template, presetsByCode));
|
|
1009
|
+
});
|
|
1010
|
+
app.post("/api/dag/templates/:id/upgrade/apply", zValidator5("json", UpgradeApplyRequestSchema), async (c) => {
|
|
1011
|
+
const id = c.req.param("id");
|
|
1012
|
+
const body = c.req.valid("json");
|
|
1013
|
+
const template = await repo.getById(id);
|
|
1014
|
+
if (!template) throw new NotFoundError7("dag-template", id);
|
|
1015
|
+
assertProjectScope(c, template.projectId);
|
|
1016
|
+
await loadProjectForWrite(projectRepo, template.projectId);
|
|
1017
|
+
const codes = [...new Set(template.nodes.flatMap((n) => n.sourcePreset ? [n.sourcePreset.code] : []))];
|
|
1018
|
+
const presetsByCode = await nodePresetRepo.listByCodes(codes);
|
|
1019
|
+
const outcome = applyUpgradeDecisions(template, presetsByCode, body.decisions);
|
|
1020
|
+
let version = template.version;
|
|
1021
|
+
if (outcome.nextVersion !== null) {
|
|
1022
|
+
const updated = await repo.update(id, { nodes: outcome.nodes, version: outcome.nextVersion });
|
|
1023
|
+
if (!updated) throw new NotFoundError7("dag-template", id);
|
|
1024
|
+
version = updated.version;
|
|
1025
|
+
}
|
|
1026
|
+
return c.json({ template: { id, version }, results: outcome.results });
|
|
1027
|
+
});
|
|
1028
|
+
app.delete("/api/dag/templates/:id", async (c) => {
|
|
1029
|
+
const ref = c.req.param("id");
|
|
1030
|
+
const existing = await resolveTemplateOr404(repo, ref, resolveProjectFilter(c, c.req.query("projectId")), QUERY_PROJECT_HINT);
|
|
1031
|
+
assertProjectScope(c, existing.projectId);
|
|
1032
|
+
const deleted = await repo.delete(existing.id);
|
|
1033
|
+
if (!deleted) throw new NotFoundError7("dag-template", existing.id);
|
|
735
1034
|
return c.body(null, 204);
|
|
736
1035
|
});
|
|
737
1036
|
return app;
|
|
738
1037
|
}
|
|
739
1038
|
|
|
740
|
-
// src/routes/
|
|
1039
|
+
// src/routes/node-preset.routes.ts
|
|
741
1040
|
import { Hono as Hono8 } from "hono";
|
|
742
1041
|
import { zValidator as zValidator6 } from "@hono/zod-validator";
|
|
743
1042
|
import { z as z4 } from "zod";
|
|
1043
|
+
import {
|
|
1044
|
+
createNodePresetRepo as createNodePresetRepo2,
|
|
1045
|
+
createProjectRepo as createProjectRepo5,
|
|
1046
|
+
NodePresetCreateSchema,
|
|
1047
|
+
NodePresetUpdateSchema,
|
|
1048
|
+
NodePresetCopySchema,
|
|
1049
|
+
AssetSetEnabledSchema as AssetSetEnabledSchema4,
|
|
1050
|
+
NODE_PRESET_CODE_PATTERN,
|
|
1051
|
+
computeNodeContentHash,
|
|
1052
|
+
NotFoundError as NotFoundError8,
|
|
1053
|
+
ConflictError as ConflictError6,
|
|
1054
|
+
ValidationError,
|
|
1055
|
+
BIZ_CODE_MESSAGES as BIZ_CODE_MESSAGES6
|
|
1056
|
+
} from "@siming-org/core";
|
|
1057
|
+
var NodePresetListQuerySchema = z4.object({
|
|
1058
|
+
projectId: z4.string().optional(),
|
|
1059
|
+
scope: z4.enum(["global", "project"]).optional(),
|
|
1060
|
+
q: z4.string().trim().min(1).max(100).optional(),
|
|
1061
|
+
includeDisabled: z4.stringbool().optional()
|
|
1062
|
+
}).superRefine((val, ctx) => {
|
|
1063
|
+
if (val.scope === "project" && val.projectId === void 0) {
|
|
1064
|
+
ctx.addIssue({ code: "custom", path: ["projectId"], message: "scope=project \u65F6 projectId \u5FC5\u586B" });
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
var NodePresetByCodeQuerySchema = z4.object({
|
|
1068
|
+
includeDisabled: z4.stringbool().optional()
|
|
1069
|
+
});
|
|
1070
|
+
function assertCodeShape(code) {
|
|
1071
|
+
if (!NODE_PRESET_CODE_PATTERN.test(code)) {
|
|
1072
|
+
throw new NotFoundError8("node-preset", code);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
function nodePresetToListItem(preset) {
|
|
1076
|
+
const { prompt: _prompt, ...rest } = preset;
|
|
1077
|
+
return { ...rest, agents: preset.agents ?? [] };
|
|
1078
|
+
}
|
|
1079
|
+
var CONTENT_FIELDS = ["nodeId", "phase", "track", "prompt", "skills", "agents", "label"];
|
|
1080
|
+
function isContentChange(existing, data) {
|
|
1081
|
+
return CONTENT_FIELDS.some((field) => {
|
|
1082
|
+
const next = data[field];
|
|
1083
|
+
if (next === void 0) return false;
|
|
1084
|
+
const current = existing[field];
|
|
1085
|
+
if (field === "skills" || field === "agents") {
|
|
1086
|
+
const currentItems = current ?? [];
|
|
1087
|
+
return JSON.stringify([...next].sort()) !== JSON.stringify([...currentItems].sort());
|
|
1088
|
+
}
|
|
1089
|
+
return next !== current;
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
function createNodePresetRoutes(client) {
|
|
1093
|
+
const app = new Hono8();
|
|
1094
|
+
const repo = createNodePresetRepo2(client.db());
|
|
1095
|
+
const projectRepo = createProjectRepo5(client.db());
|
|
1096
|
+
app.get("/api/node-presets", zValidator6("query", NodePresetListQuerySchema), async (c) => {
|
|
1097
|
+
const q = c.req.valid("query");
|
|
1098
|
+
const auth = getAuth(c);
|
|
1099
|
+
const filter = { scope: q.scope, projectId: q.projectId, q: q.q, includeDisabled: q.includeDisabled };
|
|
1100
|
+
if (auth.kind === "project") {
|
|
1101
|
+
return c.json(
|
|
1102
|
+
(await repo.listNodePresets({ ...filter, scope: void 0, projectId: auth.projectId })).map(nodePresetToListItem)
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
return c.json((await repo.listNodePresets(filter)).map(nodePresetToListItem));
|
|
1106
|
+
});
|
|
1107
|
+
app.get("/api/node-presets/:code/availability", async (c) => {
|
|
1108
|
+
const code = c.req.param("code");
|
|
1109
|
+
assertCodeShape(code);
|
|
1110
|
+
return c.json({ code, available: !await repo.isCodeOccupied(code) });
|
|
1111
|
+
});
|
|
1112
|
+
app.get("/api/node-presets/:code", zValidator6("query", NodePresetByCodeQuerySchema), async (c) => {
|
|
1113
|
+
const code = c.req.param("code");
|
|
1114
|
+
const q = c.req.valid("query");
|
|
1115
|
+
assertCodeShape(code);
|
|
1116
|
+
const preset = await repo.getByCode(code, { includeDisabled: q.includeDisabled });
|
|
1117
|
+
if (!preset) throw new NotFoundError8("node-preset", code);
|
|
1118
|
+
assertProjectVisible(c, preset.projectId);
|
|
1119
|
+
return c.json({ ...preset, agents: preset.agents ?? [], contentHash: computeNodeContentHash(preset) });
|
|
1120
|
+
});
|
|
1121
|
+
app.post("/api/node-presets", zValidator6("json", NodePresetCreateSchema), async (c) => {
|
|
1122
|
+
const data = c.req.valid("json");
|
|
1123
|
+
if (data.scope === "project") {
|
|
1124
|
+
assertProjectScope(c, data.projectId);
|
|
1125
|
+
await loadProjectForWrite(projectRepo, data.projectId);
|
|
1126
|
+
} else {
|
|
1127
|
+
requireAdminLike(c);
|
|
1128
|
+
}
|
|
1129
|
+
const created = await repo.createNodePreset(data);
|
|
1130
|
+
return c.json({ ...created, agents: created.agents ?? [] }, 201);
|
|
1131
|
+
});
|
|
1132
|
+
app.put("/api/node-presets/:code", zValidator6("query", NodePresetByCodeQuerySchema), zValidator6("json", NodePresetUpdateSchema), async (c) => {
|
|
1133
|
+
const code = c.req.param("code");
|
|
1134
|
+
const data = c.req.valid("json");
|
|
1135
|
+
assertCodeShape(code);
|
|
1136
|
+
const existing = await repo.getByCode(code, { includeDisabled: true });
|
|
1137
|
+
if (!existing) throw new NotFoundError8("node-preset", code);
|
|
1138
|
+
if (existing.scope === "global") {
|
|
1139
|
+
requireAdminLike(c);
|
|
1140
|
+
} else {
|
|
1141
|
+
assertProjectScope(c, existing.projectId ?? "");
|
|
1142
|
+
await loadProjectForWrite(projectRepo, existing.projectId ?? "");
|
|
1143
|
+
}
|
|
1144
|
+
if (data.code !== void 0 && data.code !== existing.code) {
|
|
1145
|
+
throw new ConflictError6("node-preset", code, {
|
|
1146
|
+
bizCode: "NODE_PRESET_CODE_IMMUTABLE",
|
|
1147
|
+
message: BIZ_CODE_MESSAGES6.NODE_PRESET_CODE_IMMUTABLE
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
if (isScopeMutation(existing, data)) {
|
|
1151
|
+
throw new ConflictError6("node-preset", code, {
|
|
1152
|
+
bizCode: "SCOPE_IMMUTABLE",
|
|
1153
|
+
message: BIZ_CODE_MESSAGES6.SCOPE_IMMUTABLE
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
if (isContentChange(existing, data) && (data.version === void 0 || data.version === existing.version)) {
|
|
1157
|
+
throw new ValidationError(
|
|
1158
|
+
"\u8282\u70B9\u5185\u5BB9\u53D8\u66F4\u5FC5\u987B\u540C\u6B65\u63D0\u5347\u7248\u672C\u53F7\uFF08\u643A\u5E26\u53D8\u66F4\u5B57\u6BB5\u65F6 version \u5FC5\u586B\u4E14\u4E0D\u5F97\u4E0E\u73B0\u503C\u76F8\u540C\uFF09",
|
|
1159
|
+
[{ path: ["version"], message: "\u5185\u5BB9\u53D8\u66F4\u672A\u5347\u7248" }],
|
|
1160
|
+
{
|
|
1161
|
+
bizCode: "NODE_PRESET_VERSION_REQUIRED",
|
|
1162
|
+
message: BIZ_CODE_MESSAGES6.NODE_PRESET_VERSION_REQUIRED
|
|
1163
|
+
}
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
const preset = await repo.updateByCode(code, data);
|
|
1167
|
+
if (!preset) throw new NotFoundError8("node-preset", code);
|
|
1168
|
+
return c.json({ ...preset, agents: preset.agents ?? [] });
|
|
1169
|
+
});
|
|
1170
|
+
app.delete("/api/node-presets/:code", zValidator6("query", NodePresetByCodeQuerySchema), async (c) => {
|
|
1171
|
+
const code = c.req.param("code");
|
|
1172
|
+
assertCodeShape(code);
|
|
1173
|
+
const existing = await repo.getByCode(code, { includeDisabled: true });
|
|
1174
|
+
if (!existing) throw new NotFoundError8("node-preset", code);
|
|
1175
|
+
if (existing.scope === "global") {
|
|
1176
|
+
requireAdminLike(c);
|
|
1177
|
+
} else {
|
|
1178
|
+
assertProjectScope(c, existing.projectId ?? "");
|
|
1179
|
+
}
|
|
1180
|
+
const deleted = await repo.deleteByCode(code);
|
|
1181
|
+
if (!deleted) throw new NotFoundError8("node-preset", code);
|
|
1182
|
+
return c.body(null, 204);
|
|
1183
|
+
});
|
|
1184
|
+
app.post("/api/node-presets/:code/enabled", zValidator6("query", NodePresetByCodeQuerySchema), zValidator6("json", AssetSetEnabledSchema4), async (c) => {
|
|
1185
|
+
const code = c.req.param("code");
|
|
1186
|
+
const body = c.req.valid("json");
|
|
1187
|
+
assertCodeShape(code);
|
|
1188
|
+
const existing = await repo.getByCode(code, { includeDisabled: true });
|
|
1189
|
+
if (!existing) throw new NotFoundError8("node-preset", code);
|
|
1190
|
+
if (existing.scope === "global") {
|
|
1191
|
+
requireAdminLike(c);
|
|
1192
|
+
} else {
|
|
1193
|
+
assertProjectScope(c, existing.projectId ?? "");
|
|
1194
|
+
await loadProjectForWrite(projectRepo, existing.projectId ?? "");
|
|
1195
|
+
}
|
|
1196
|
+
const preset = await repo.updateByCode(code, { enabled: body.enabled });
|
|
1197
|
+
if (!preset) throw new NotFoundError8("node-preset", code);
|
|
1198
|
+
return c.json({ code: preset.code, enabled: preset.enabled ?? true });
|
|
1199
|
+
});
|
|
1200
|
+
app.post("/api/node-presets/:code/copy", zValidator6("query", NodePresetByCodeQuerySchema), zValidator6("json", NodePresetCopySchema), async (c) => {
|
|
1201
|
+
const code = c.req.param("code");
|
|
1202
|
+
const input = c.req.valid("json");
|
|
1203
|
+
assertCodeShape(code);
|
|
1204
|
+
const existing = await repo.getByCode(code, { includeDisabled: true });
|
|
1205
|
+
if (!existing) throw new NotFoundError8("node-preset", code);
|
|
1206
|
+
assertProjectVisible(c, existing.projectId);
|
|
1207
|
+
if (input.newScope === "project") {
|
|
1208
|
+
assertProjectScope(c, input.targetProjectId);
|
|
1209
|
+
await loadProjectForWrite(projectRepo, input.targetProjectId);
|
|
1210
|
+
} else {
|
|
1211
|
+
requireAdminLike(c);
|
|
1212
|
+
}
|
|
1213
|
+
const copy = await repo.copyNodePreset(
|
|
1214
|
+
code,
|
|
1215
|
+
{ scope: input.newScope, ...input.targetProjectId ? { projectId: input.targetProjectId } : {} },
|
|
1216
|
+
input
|
|
1217
|
+
);
|
|
1218
|
+
return c.json({ ...copy, agents: copy.agents ?? [] }, 201);
|
|
1219
|
+
});
|
|
1220
|
+
return app;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// src/routes/node-library.routes.ts
|
|
1224
|
+
import { Hono as Hono9 } from "hono";
|
|
1225
|
+
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
1226
|
+
import {
|
|
1227
|
+
createNodeLibraryRepo,
|
|
1228
|
+
createProjectRepo as createProjectRepo6,
|
|
1229
|
+
NodeLibraryUpsertSchema
|
|
1230
|
+
} from "@siming-org/core";
|
|
1231
|
+
function createNodeLibraryRoutes(client) {
|
|
1232
|
+
const app = new Hono9();
|
|
1233
|
+
const repo = createNodeLibraryRepo(client.db());
|
|
1234
|
+
const projectRepo = createProjectRepo6(client.db());
|
|
1235
|
+
app.get("/api/node-libraries", async (c) => {
|
|
1236
|
+
const auth = getAuth(c);
|
|
1237
|
+
if (auth.kind === "project") {
|
|
1238
|
+
return c.json(await repo.listNodeLibraries({ projectId: auth.projectId }));
|
|
1239
|
+
}
|
|
1240
|
+
return c.json(await repo.listNodeLibraries());
|
|
1241
|
+
});
|
|
1242
|
+
app.post("/api/node-libraries", zValidator7("json", NodeLibraryUpsertSchema), async (c) => {
|
|
1243
|
+
const data = c.req.valid("json");
|
|
1244
|
+
if (data.scope === "project") {
|
|
1245
|
+
assertProjectScope(c, data.projectId);
|
|
1246
|
+
await loadProjectForWrite(projectRepo, data.projectId);
|
|
1247
|
+
} else {
|
|
1248
|
+
requireAdminLike(c);
|
|
1249
|
+
}
|
|
1250
|
+
return c.json(await repo.upsertNodeLibrary(data));
|
|
1251
|
+
});
|
|
1252
|
+
return app;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// src/routes/task.routes.ts
|
|
1256
|
+
import { Hono as Hono10 } from "hono";
|
|
1257
|
+
import { zValidator as zValidator8 } from "@hono/zod-validator";
|
|
1258
|
+
import { z as z5 } from "zod";
|
|
744
1259
|
import {
|
|
745
1260
|
createTaskRepo,
|
|
746
1261
|
createDagTemplateRepo as createDagTemplateRepo2,
|
|
747
|
-
createProjectRepo as
|
|
1262
|
+
createProjectRepo as createProjectRepo7,
|
|
748
1263
|
TaskCreateInputSchema,
|
|
749
1264
|
AdvanceRequestSchema,
|
|
750
1265
|
ApproveRequestSchema,
|
|
@@ -758,11 +1273,11 @@ import {
|
|
|
758
1273
|
ARTIFACT_TYPES,
|
|
759
1274
|
NODE_ID_PATTERN,
|
|
760
1275
|
createEnumRegistryRepo as createEnumRegistryRepo2,
|
|
761
|
-
NotFoundError as
|
|
762
|
-
ConflictError as
|
|
1276
|
+
NotFoundError as NotFoundError9,
|
|
1277
|
+
ConflictError as ConflictError7,
|
|
763
1278
|
BadRequestError as BadRequestError2,
|
|
764
|
-
ValidationError,
|
|
765
|
-
BIZ_CODE_MESSAGES as
|
|
1279
|
+
ValidationError as ValidationError2,
|
|
1280
|
+
BIZ_CODE_MESSAGES as BIZ_CODE_MESSAGES7,
|
|
766
1281
|
advanceTask,
|
|
767
1282
|
approveTask,
|
|
768
1283
|
pauseTask,
|
|
@@ -774,43 +1289,43 @@ import {
|
|
|
774
1289
|
findNextEdge,
|
|
775
1290
|
generateEntryId
|
|
776
1291
|
} from "@siming-org/core";
|
|
777
|
-
var TaskListQuerySchema =
|
|
778
|
-
status:
|
|
779
|
-
track:
|
|
780
|
-
projectId:
|
|
781
|
-
q:
|
|
782
|
-
page:
|
|
783
|
-
limit:
|
|
784
|
-
sort:
|
|
1292
|
+
var TaskListQuerySchema = z5.object({
|
|
1293
|
+
status: z5.string().optional(),
|
|
1294
|
+
track: z5.string().optional(),
|
|
1295
|
+
projectId: z5.string().optional(),
|
|
1296
|
+
q: z5.string().trim().min(1).max(100).optional(),
|
|
1297
|
+
page: z5.coerce.number().int().min(1).default(1),
|
|
1298
|
+
limit: z5.coerce.number().int().min(1).max(500).default(20),
|
|
1299
|
+
sort: z5.enum(["progress", "createdAt", "updatedAt"]).optional()
|
|
785
1300
|
});
|
|
786
|
-
var TaskPatchSchema =
|
|
787
|
-
title:
|
|
1301
|
+
var TaskPatchSchema = z5.object({
|
|
1302
|
+
title: z5.string().min(1).optional()
|
|
788
1303
|
});
|
|
789
|
-
var TaskDocSetSchema =
|
|
790
|
-
what:
|
|
791
|
-
why:
|
|
792
|
-
trackNote:
|
|
1304
|
+
var TaskDocSetSchema = z5.object({
|
|
1305
|
+
what: z5.string().min(1).max(2e3).optional(),
|
|
1306
|
+
why: z5.string().min(1).max(2e3).optional(),
|
|
1307
|
+
trackNote: z5.string().max(2e3).optional()
|
|
793
1308
|
});
|
|
794
|
-
var TextItemSchema =
|
|
795
|
-
var RecordSummarySchema =
|
|
796
|
-
var CheckAddSchema =
|
|
797
|
-
var CheckPatchSchema =
|
|
798
|
-
var ArtifactAddSchema =
|
|
799
|
-
type:
|
|
800
|
-
path:
|
|
801
|
-
note:
|
|
1309
|
+
var TextItemSchema = z5.object({ text: z5.string().min(1).max(2e3) });
|
|
1310
|
+
var RecordSummarySchema = z5.object({ summary: z5.string().min(1).max(2e3) });
|
|
1311
|
+
var CheckAddSchema = z5.object({ item: z5.string().min(1).max(2e3), passed: z5.boolean().optional() });
|
|
1312
|
+
var CheckPatchSchema = z5.object({ passed: z5.boolean() });
|
|
1313
|
+
var ArtifactAddSchema = z5.object({
|
|
1314
|
+
type: z5.enum(ARTIFACT_TYPES),
|
|
1315
|
+
path: z5.string().min(1).max(2e3),
|
|
1316
|
+
note: z5.string().max(2e3).optional(),
|
|
802
1317
|
/** 全文快照(CLI --file 读文件后传入;≤200k——续跑会话凭 context 自足) */
|
|
803
|
-
content:
|
|
1318
|
+
content: z5.string().max(2e5).optional()
|
|
804
1319
|
});
|
|
805
|
-
var ConfirmAddSchema =
|
|
806
|
-
var DecisionAddSchema =
|
|
807
|
-
topic:
|
|
808
|
-
decision:
|
|
1320
|
+
var ConfirmAddSchema = z5.object({ quote: z5.string().min(1).max(2e3) });
|
|
1321
|
+
var DecisionAddSchema = z5.object({
|
|
1322
|
+
topic: z5.string().min(1).max(2e3),
|
|
1323
|
+
decision: z5.string().min(1).max(2e3)
|
|
809
1324
|
});
|
|
810
|
-
var ReviewSetSchema =
|
|
811
|
-
verdict:
|
|
812
|
-
rounds:
|
|
813
|
-
critical:
|
|
1325
|
+
var ReviewSetSchema = z5.object({
|
|
1326
|
+
verdict: z5.enum(["pass", "fail"]),
|
|
1327
|
+
rounds: z5.number().int().min(1),
|
|
1328
|
+
critical: z5.number().int().min(0)
|
|
814
1329
|
});
|
|
815
1330
|
function normalizeTaskDoc(doc) {
|
|
816
1331
|
if (!doc) return null;
|
|
@@ -856,17 +1371,18 @@ function writeAck(entity, updated, entry, echo) {
|
|
|
856
1371
|
};
|
|
857
1372
|
}
|
|
858
1373
|
function createTaskRoutes(client) {
|
|
859
|
-
const app = new
|
|
1374
|
+
const app = new Hono10();
|
|
860
1375
|
const repo = createTaskRepo(client.db());
|
|
861
1376
|
const templateRepo = createDagTemplateRepo2(client.db());
|
|
862
|
-
const projectRepo =
|
|
1377
|
+
const projectRepo = createProjectRepo7(client.db());
|
|
863
1378
|
const enumRegistryRepo = createEnumRegistryRepo2(client.db());
|
|
864
|
-
app.get("/api/tasks",
|
|
1379
|
+
app.get("/api/tasks", zValidator8("query", TaskListQuerySchema), async (c) => {
|
|
865
1380
|
const q = c.req.valid("query");
|
|
866
1381
|
const { items, total } = await repo.listTasks({
|
|
867
1382
|
...q.status ? { status: q.status } : {},
|
|
868
1383
|
...q.track ? { track: q.track } : {},
|
|
869
|
-
|
|
1384
|
+
// T202608310001:project 上下文强制覆写 projectId 过滤("只能看到自己的")
|
|
1385
|
+
...resolveProjectFilter(c, q.projectId) ? { projectId: resolveProjectFilter(c, q.projectId) } : {},
|
|
870
1386
|
...q.q !== void 0 ? { q: q.q } : {},
|
|
871
1387
|
page: q.page,
|
|
872
1388
|
limit: q.limit,
|
|
@@ -877,10 +1393,17 @@ function createTaskRoutes(client) {
|
|
|
877
1393
|
app.get("/api/tasks/:taskId", async (c) => {
|
|
878
1394
|
const taskId = c.req.param("taskId");
|
|
879
1395
|
const task = await repo.getByTaskId(taskId);
|
|
880
|
-
if (!task) throw new
|
|
881
|
-
|
|
1396
|
+
if (!task) throw new NotFoundError9("task", taskId);
|
|
1397
|
+
assertProjectScope(c, task.projectId);
|
|
1398
|
+
return c.json({
|
|
1399
|
+
...task,
|
|
1400
|
+
dagInstance: {
|
|
1401
|
+
...task.dagInstance,
|
|
1402
|
+
nodes: task.dagInstance.nodes.map((node) => ({ ...node, agents: node.agents ?? [] }))
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
882
1405
|
});
|
|
883
|
-
app.post("/api/tasks",
|
|
1406
|
+
app.post("/api/tasks", zValidator8("json", TaskCreateInputSchema), async (c) => {
|
|
884
1407
|
const input = c.req.valid("json");
|
|
885
1408
|
const dagTracks = (await enumRegistryRepo.getEntries("dag_track")).filter(
|
|
886
1409
|
(e) => e.active && e.value !== "all"
|
|
@@ -892,21 +1415,26 @@ function createTaskRoutes(client) {
|
|
|
892
1415
|
);
|
|
893
1416
|
}
|
|
894
1417
|
assertTypeTrackCompatible(input.type, input.track);
|
|
895
|
-
const template = await
|
|
896
|
-
|
|
1418
|
+
const template = await resolveTemplateOr404(
|
|
1419
|
+
templateRepo,
|
|
1420
|
+
input.dagTemplateId,
|
|
1421
|
+
resolveProjectFilter(c, input.projectId),
|
|
1422
|
+
"\uFF08code \u5F62\u6001\u5BFB\u5740\u9700 body \u643A\u5E26 projectId \u6307\u5B9A\u9879\u76EE\u2014\u2014code \u9879\u76EE\u5185\u552F\u4E00\uFF0C\u65E0\u9879\u76EE\u57DF\u4E0D\u53EF\u6D88\u6B67\uFF1B\u6216\u6539\u4F20 24 \u4F4D\u6570\u636E\u5E93 id\uFF09"
|
|
1423
|
+
);
|
|
897
1424
|
if (template.enabled === false) {
|
|
898
|
-
throw new
|
|
1425
|
+
throw new ConflictError7("task", input.dagTemplateId, {
|
|
899
1426
|
bizCode: "TEMPLATE_DISABLED",
|
|
900
|
-
message:
|
|
1427
|
+
message: BIZ_CODE_MESSAGES7.TEMPLATE_DISABLED
|
|
901
1428
|
});
|
|
902
1429
|
}
|
|
903
1430
|
if (input.projectId && input.projectId !== template.projectId) {
|
|
904
|
-
throw new
|
|
1431
|
+
throw new ConflictError7("task", input.dagTemplateId, {
|
|
905
1432
|
bizCode: "TEMPLATE_PROJECT_MISMATCH",
|
|
906
|
-
message:
|
|
1433
|
+
message: BIZ_CODE_MESSAGES7.TEMPLATE_PROJECT_MISMATCH
|
|
907
1434
|
});
|
|
908
1435
|
}
|
|
909
1436
|
const resolvedProjectId = input.projectId ?? template.projectId;
|
|
1437
|
+
assertProjectScope(c, resolvedProjectId);
|
|
910
1438
|
await loadProjectForWrite(projectRepo, resolvedProjectId);
|
|
911
1439
|
const created = await repo.createTask(input, template, resolvedProjectId);
|
|
912
1440
|
const firstActive = created.dagInstance.nodes.find(
|
|
@@ -923,23 +1451,23 @@ function createTaskRoutes(client) {
|
|
|
923
1451
|
};
|
|
924
1452
|
return c.json(response, 201);
|
|
925
1453
|
});
|
|
926
|
-
app.patch("/api/tasks/:taskId",
|
|
1454
|
+
app.patch("/api/tasks/:taskId", zValidator8("json", TaskPatchSchema), async (c) => {
|
|
927
1455
|
const taskId = c.req.param("taskId");
|
|
928
1456
|
const body = c.req.valid("json");
|
|
929
1457
|
if (body.title === void 0) {
|
|
930
1458
|
throw new BadRequestError2("PATCH /api/tasks/:taskId \u9700\u8981\u81F3\u5C11\u4E00\u4E2A\u53EF\u66F4\u65B0\u5B57\u6BB5\uFF08title\uFF09");
|
|
931
1459
|
}
|
|
932
|
-
await assertTaskProjectActive(repo, projectRepo, taskId);
|
|
1460
|
+
await assertTaskProjectActive(repo, projectRepo, taskId, c);
|
|
933
1461
|
const patch = { title: body.title };
|
|
934
1462
|
const updatedTask = await repo.updateTask(taskId, patch);
|
|
935
1463
|
return c.json(
|
|
936
1464
|
writeAck(updatedTask, ["title"], void 0, [{ path: "title", excerpt: excerpt(updatedTask.title) }])
|
|
937
1465
|
);
|
|
938
1466
|
});
|
|
939
|
-
app.post("/api/tasks/:taskId/advance",
|
|
1467
|
+
app.post("/api/tasks/:taskId/advance", zValidator8("json", AdvanceRequestSchema), async (c) => {
|
|
940
1468
|
const taskId = c.req.param("taskId");
|
|
941
1469
|
const body = c.req.valid("json");
|
|
942
|
-
await assertTaskProjectActive(repo, projectRepo, taskId);
|
|
1470
|
+
await assertTaskProjectActive(repo, projectRepo, taskId, c);
|
|
943
1471
|
const deps = mkDeps(repo);
|
|
944
1472
|
const result = await advanceTask(deps, taskId, {
|
|
945
1473
|
...body.note !== void 0 ? { note: body.note } : {},
|
|
@@ -947,32 +1475,32 @@ function createTaskRoutes(client) {
|
|
|
947
1475
|
});
|
|
948
1476
|
return c.json(result, 200);
|
|
949
1477
|
});
|
|
950
|
-
app.post("/api/tasks/:taskId/approve",
|
|
1478
|
+
app.post("/api/tasks/:taskId/approve", zValidator8("json", ApproveRequestSchema), async (c) => {
|
|
951
1479
|
const taskId = c.req.param("taskId");
|
|
952
1480
|
const body = c.req.valid("json");
|
|
953
|
-
await assertTaskProjectActive(repo, projectRepo, taskId);
|
|
1481
|
+
await assertTaskProjectActive(repo, projectRepo, taskId, c);
|
|
954
1482
|
const deps = mkDeps(repo);
|
|
955
1483
|
const result = await approveTask(deps, taskId, body);
|
|
956
1484
|
return c.json(result, 200);
|
|
957
1485
|
});
|
|
958
|
-
app.post("/api/tasks/:taskId/pause",
|
|
1486
|
+
app.post("/api/tasks/:taskId/pause", zValidator8("json", PauseRequestSchema), async (c) => {
|
|
959
1487
|
const taskId = c.req.param("taskId");
|
|
960
1488
|
const body = c.req.valid("json");
|
|
961
|
-
await assertTaskProjectActive(repo, projectRepo, taskId);
|
|
1489
|
+
await assertTaskProjectActive(repo, projectRepo, taskId, c);
|
|
962
1490
|
const result = await pauseTask(mkDeps(repo), taskId, body.reason);
|
|
963
1491
|
return c.json(writeAck(result, ["status", "pausedAt"], void 0, [{ path: "status", excerpt: result.status }]));
|
|
964
1492
|
});
|
|
965
|
-
app.post("/api/tasks/:taskId/resume",
|
|
1493
|
+
app.post("/api/tasks/:taskId/resume", zValidator8("json", ResumeRequestSchema), async (c) => {
|
|
966
1494
|
const taskId = c.req.param("taskId");
|
|
967
1495
|
const body = c.req.valid("json");
|
|
968
|
-
await assertTaskProjectActive(repo, projectRepo, taskId);
|
|
1496
|
+
await assertTaskProjectActive(repo, projectRepo, taskId, c);
|
|
969
1497
|
const result = await resumeTask(mkDeps(repo), taskId, body.decision);
|
|
970
1498
|
return c.json(writeAck(result, ["status"], void 0, [{ path: "status", excerpt: `${result.status}\uFF08\u5DF2\u6062\u590D\u624B\u52A8\u6682\u505C\uFF09` }]));
|
|
971
1499
|
});
|
|
972
|
-
app.post("/api/tasks/:taskId/cancel",
|
|
1500
|
+
app.post("/api/tasks/:taskId/cancel", zValidator8("json", CancelRequestSchema), async (c) => {
|
|
973
1501
|
const taskId = c.req.param("taskId");
|
|
974
1502
|
const body = c.req.valid("json");
|
|
975
|
-
await assertTaskProjectActive(repo, projectRepo, taskId);
|
|
1503
|
+
await assertTaskProjectActive(repo, projectRepo, taskId, c);
|
|
976
1504
|
const result = await cancelTask(mkDeps(repo), taskId, body.reason);
|
|
977
1505
|
return c.json(
|
|
978
1506
|
writeAck(
|
|
@@ -989,17 +1517,24 @@ function createTaskRoutes(client) {
|
|
|
989
1517
|
app.get("/api/tasks/:taskId/history", async (c) => {
|
|
990
1518
|
const taskId = c.req.param("taskId");
|
|
991
1519
|
const task = await repo.getByTaskId(taskId);
|
|
992
|
-
if (!task) throw new
|
|
1520
|
+
if (!task) throw new NotFoundError9("task", taskId);
|
|
1521
|
+
assertProjectScope(c, task.projectId);
|
|
993
1522
|
return c.json(task.history);
|
|
994
1523
|
});
|
|
995
1524
|
app.get("/api/tasks/:taskId/node/:nodeId", async (c) => {
|
|
996
1525
|
const taskId = c.req.param("taskId");
|
|
997
1526
|
const nodeId = c.req.param("nodeId");
|
|
998
1527
|
const task = await repo.getByTaskId(taskId);
|
|
999
|
-
if (!task) throw new
|
|
1528
|
+
if (!task) throw new NotFoundError9("task", taskId);
|
|
1529
|
+
assertProjectScope(c, task.projectId);
|
|
1000
1530
|
const node = task.dagInstance.nodes.find((n) => n.id === nodeId);
|
|
1001
|
-
if (!node) throw new
|
|
1002
|
-
return c.json({
|
|
1531
|
+
if (!node) throw new NotFoundError9("node", nodeId);
|
|
1532
|
+
return c.json({
|
|
1533
|
+
...node,
|
|
1534
|
+
skills: node.skills ?? [],
|
|
1535
|
+
agents: node.agents ?? [],
|
|
1536
|
+
prompt: renderPrompt(node.prompt, task)
|
|
1537
|
+
});
|
|
1003
1538
|
});
|
|
1004
1539
|
app.get("/api/tasks/:taskId/context", async (c) => {
|
|
1005
1540
|
const taskId = c.req.param("taskId");
|
|
@@ -1011,7 +1546,8 @@ function createTaskRoutes(client) {
|
|
|
1011
1546
|
}
|
|
1012
1547
|
const view = viewR.data;
|
|
1013
1548
|
const task = await repo.getByTaskId(taskId);
|
|
1014
|
-
if (!task) throw new
|
|
1549
|
+
if (!task) throw new NotFoundError9("task", taskId);
|
|
1550
|
+
assertProjectScope(c, task.projectId);
|
|
1015
1551
|
const currentNodeActive = task.status === "active" ? task.dagInstance.nodes.find((n) => n.id === task.currentNode) : void 0;
|
|
1016
1552
|
let pausedAtEdge = null;
|
|
1017
1553
|
if (task.status === "paused" && task.pausedAt !== null) {
|
|
@@ -1046,10 +1582,10 @@ function createTaskRoutes(client) {
|
|
|
1046
1582
|
};
|
|
1047
1583
|
return c.json(context);
|
|
1048
1584
|
});
|
|
1049
|
-
app.patch("/api/tasks/:taskId/doc",
|
|
1585
|
+
app.patch("/api/tasks/:taskId/doc", zValidator8("json", TaskDocSetSchema), async (c) => {
|
|
1050
1586
|
const taskId = c.req.param("taskId");
|
|
1051
1587
|
const body = c.req.valid("json");
|
|
1052
|
-
await loadTaskForRecordWrite(repo, projectRepo, taskId);
|
|
1588
|
+
await loadTaskForRecordWrite(repo, projectRepo, taskId, void 0, c);
|
|
1053
1589
|
const sets = {};
|
|
1054
1590
|
if (body.what !== void 0) sets["doc.what"] = body.what;
|
|
1055
1591
|
if (body.why !== void 0) sets["doc.why"] = body.why;
|
|
@@ -1064,10 +1600,10 @@ function createTaskRoutes(client) {
|
|
|
1064
1600
|
)
|
|
1065
1601
|
);
|
|
1066
1602
|
});
|
|
1067
|
-
app.post("/api/tasks/:taskId/doc/acceptance",
|
|
1603
|
+
app.post("/api/tasks/:taskId/doc/acceptance", zValidator8("json", TextItemSchema), async (c) => {
|
|
1068
1604
|
const taskId = c.req.param("taskId");
|
|
1069
1605
|
const body = c.req.valid("json");
|
|
1070
|
-
await loadTaskForRecordWrite(repo, projectRepo, taskId);
|
|
1606
|
+
await loadTaskForRecordWrite(repo, projectRepo, taskId, void 0, c);
|
|
1071
1607
|
const updatedAcc = await repo.updateTaskPaths(taskId, { pushes: { "doc.acceptance": body.text } });
|
|
1072
1608
|
return c.json(
|
|
1073
1609
|
writeAck(updatedAcc, ["doc.acceptance"], void 0, [
|
|
@@ -1075,20 +1611,20 @@ function createTaskRoutes(client) {
|
|
|
1075
1611
|
])
|
|
1076
1612
|
);
|
|
1077
1613
|
});
|
|
1078
|
-
app.post("/api/tasks/:taskId/doc/non-goal",
|
|
1614
|
+
app.post("/api/tasks/:taskId/doc/non-goal", zValidator8("json", TextItemSchema), async (c) => {
|
|
1079
1615
|
const taskId = c.req.param("taskId");
|
|
1080
1616
|
const body = c.req.valid("json");
|
|
1081
|
-
await loadTaskForRecordWrite(repo, projectRepo, taskId);
|
|
1617
|
+
await loadTaskForRecordWrite(repo, projectRepo, taskId, void 0, c);
|
|
1082
1618
|
const updatedNg = await repo.updateTaskPaths(taskId, { pushes: { "doc.nonGoals": body.text } });
|
|
1083
1619
|
return c.json(
|
|
1084
1620
|
writeAck(updatedNg, ["doc.nonGoals"], void 0, [{ path: "doc.nonGoals", excerpt: excerpt(body.text) }])
|
|
1085
1621
|
);
|
|
1086
1622
|
});
|
|
1087
|
-
app.patch("/api/tasks/:taskId/records/:nodeId/summary",
|
|
1623
|
+
app.patch("/api/tasks/:taskId/records/:nodeId/summary", zValidator8("json", RecordSummarySchema), async (c) => {
|
|
1088
1624
|
const taskId = c.req.param("taskId");
|
|
1089
1625
|
const nodeId = c.req.param("nodeId");
|
|
1090
1626
|
const body = c.req.valid("json");
|
|
1091
|
-
await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId);
|
|
1627
|
+
await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId, c);
|
|
1092
1628
|
const updatedSummary = await repo.updateTaskPaths(taskId, {
|
|
1093
1629
|
sets: { [`nodeRecords.${nodeId}.summary`]: body.summary }
|
|
1094
1630
|
});
|
|
@@ -1098,11 +1634,11 @@ function createTaskRoutes(client) {
|
|
|
1098
1634
|
])
|
|
1099
1635
|
);
|
|
1100
1636
|
});
|
|
1101
|
-
app.post("/api/tasks/:taskId/records/:nodeId/checks",
|
|
1637
|
+
app.post("/api/tasks/:taskId/records/:nodeId/checks", zValidator8("json", CheckAddSchema), async (c) => {
|
|
1102
1638
|
const taskId = c.req.param("taskId");
|
|
1103
1639
|
const nodeId = c.req.param("nodeId");
|
|
1104
1640
|
const body = c.req.valid("json");
|
|
1105
|
-
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId);
|
|
1641
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId, c);
|
|
1106
1642
|
const existing = (task.nodeRecords ?? {})[nodeId]?.checks ?? [];
|
|
1107
1643
|
const check = {
|
|
1108
1644
|
id: generateEntryId(existing.map((ch) => ch.id)),
|
|
@@ -1121,17 +1657,17 @@ function createTaskRoutes(client) {
|
|
|
1121
1657
|
)
|
|
1122
1658
|
);
|
|
1123
1659
|
});
|
|
1124
|
-
app.patch("/api/tasks/:taskId/records/:nodeId/checks/:checkId",
|
|
1660
|
+
app.patch("/api/tasks/:taskId/records/:nodeId/checks/:checkId", zValidator8("json", CheckPatchSchema), async (c) => {
|
|
1125
1661
|
const taskId = c.req.param("taskId");
|
|
1126
1662
|
const nodeId = c.req.param("nodeId");
|
|
1127
1663
|
const checkId = c.req.param("checkId");
|
|
1128
1664
|
const body = c.req.valid("json");
|
|
1129
|
-
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId);
|
|
1665
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId, c);
|
|
1130
1666
|
const check = ((task.nodeRecords ?? {})[nodeId]?.checks ?? []).find((ch) => ch.id === checkId);
|
|
1131
1667
|
if (!check) {
|
|
1132
|
-
throw new
|
|
1668
|
+
throw new NotFoundError9("check", checkId, {
|
|
1133
1669
|
bizCode: "CHECK_NOT_FOUND",
|
|
1134
|
-
message: `${
|
|
1670
|
+
message: `${BIZ_CODE_MESSAGES7.CHECK_NOT_FOUND}\uFF08node ${nodeId}, check ${checkId}\uFF09`
|
|
1135
1671
|
});
|
|
1136
1672
|
}
|
|
1137
1673
|
const updatedFlip = await repo.updateTaskPaths(taskId, {
|
|
@@ -1144,11 +1680,11 @@ function createTaskRoutes(client) {
|
|
|
1144
1680
|
])
|
|
1145
1681
|
);
|
|
1146
1682
|
});
|
|
1147
|
-
app.post("/api/tasks/:taskId/records/:nodeId/artifacts",
|
|
1683
|
+
app.post("/api/tasks/:taskId/records/:nodeId/artifacts", zValidator8("json", ArtifactAddSchema), async (c) => {
|
|
1148
1684
|
const taskId = c.req.param("taskId");
|
|
1149
1685
|
const nodeId = c.req.param("nodeId");
|
|
1150
1686
|
const body = c.req.valid("json");
|
|
1151
|
-
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId);
|
|
1687
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId, c);
|
|
1152
1688
|
const artifact = {
|
|
1153
1689
|
id: generateEntryId(((task.nodeRecords ?? {})[nodeId]?.artifacts ?? []).map((a) => a.id)),
|
|
1154
1690
|
type: body.type,
|
|
@@ -1175,11 +1711,11 @@ function createTaskRoutes(client) {
|
|
|
1175
1711
|
)
|
|
1176
1712
|
);
|
|
1177
1713
|
});
|
|
1178
|
-
app.post("/api/tasks/:taskId/records/:nodeId/confirmations",
|
|
1714
|
+
app.post("/api/tasks/:taskId/records/:nodeId/confirmations", zValidator8("json", ConfirmAddSchema), async (c) => {
|
|
1179
1715
|
const taskId = c.req.param("taskId");
|
|
1180
1716
|
const nodeId = c.req.param("nodeId");
|
|
1181
1717
|
const body = c.req.valid("json");
|
|
1182
|
-
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId);
|
|
1718
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId, c);
|
|
1183
1719
|
const confirmation = {
|
|
1184
1720
|
id: generateEntryId(((task.nodeRecords ?? {})[nodeId]?.confirmations ?? []).map((cf) => cf.id)),
|
|
1185
1721
|
quote: body.quote,
|
|
@@ -1194,11 +1730,11 @@ function createTaskRoutes(client) {
|
|
|
1194
1730
|
])
|
|
1195
1731
|
);
|
|
1196
1732
|
});
|
|
1197
|
-
app.post("/api/tasks/:taskId/records/:nodeId/decisions",
|
|
1733
|
+
app.post("/api/tasks/:taskId/records/:nodeId/decisions", zValidator8("json", DecisionAddSchema), async (c) => {
|
|
1198
1734
|
const taskId = c.req.param("taskId");
|
|
1199
1735
|
const nodeId = c.req.param("nodeId");
|
|
1200
1736
|
const body = c.req.valid("json");
|
|
1201
|
-
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId);
|
|
1737
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId, c);
|
|
1202
1738
|
const decision = {
|
|
1203
1739
|
id: generateEntryId(((task.nodeRecords ?? {})[nodeId]?.decisions ?? []).map((d) => d.id)),
|
|
1204
1740
|
topic: body.topic,
|
|
@@ -1213,11 +1749,11 @@ function createTaskRoutes(client) {
|
|
|
1213
1749
|
])
|
|
1214
1750
|
);
|
|
1215
1751
|
});
|
|
1216
|
-
app.put("/api/tasks/:taskId/records/:nodeId/review",
|
|
1752
|
+
app.put("/api/tasks/:taskId/records/:nodeId/review", zValidator8("json", ReviewSetSchema), async (c) => {
|
|
1217
1753
|
const taskId = c.req.param("taskId");
|
|
1218
1754
|
const nodeId = c.req.param("nodeId");
|
|
1219
1755
|
const body = c.req.valid("json");
|
|
1220
|
-
await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId);
|
|
1756
|
+
await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId, c);
|
|
1221
1757
|
const updatedReview = await repo.updateTaskPaths(taskId, {
|
|
1222
1758
|
sets: { [`nodeRecords.${nodeId}.review`]: body }
|
|
1223
1759
|
});
|
|
@@ -1227,10 +1763,10 @@ function createTaskRoutes(client) {
|
|
|
1227
1763
|
])
|
|
1228
1764
|
);
|
|
1229
1765
|
});
|
|
1230
|
-
app.post("/api/tasks/:taskId/archnotes",
|
|
1766
|
+
app.post("/api/tasks/:taskId/archnotes", zValidator8("json", TextItemSchema), async (c) => {
|
|
1231
1767
|
const taskId = c.req.param("taskId");
|
|
1232
1768
|
const body = c.req.valid("json");
|
|
1233
|
-
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId);
|
|
1769
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, void 0, c);
|
|
1234
1770
|
const note = { id: generateEntryId((task.archNotes ?? []).map((n) => n.id)), text: body.text, at: /* @__PURE__ */ new Date() };
|
|
1235
1771
|
const updatedNote = await repo.updateTaskPaths(taskId, { pushes: { archNotes: note } });
|
|
1236
1772
|
return c.json(
|
|
@@ -1242,7 +1778,7 @@ function createTaskRoutes(client) {
|
|
|
1242
1778
|
return app;
|
|
1243
1779
|
}
|
|
1244
1780
|
function assertTypeTrackCompatible(type, track) {
|
|
1245
|
-
const mismatch = (reason) => new
|
|
1781
|
+
const mismatch = (reason) => new ValidationError2(
|
|
1246
1782
|
reason,
|
|
1247
1783
|
[{ code: "custom", path: ["type"], message: reason }],
|
|
1248
1784
|
{ bizCode: "TASK_TYPE_TRACK_MISMATCH", message: reason }
|
|
@@ -1267,20 +1803,22 @@ function mkDeps(repo) {
|
|
|
1267
1803
|
updateTask: repo.updateTask.bind(repo)
|
|
1268
1804
|
};
|
|
1269
1805
|
}
|
|
1270
|
-
async function assertTaskProjectActive(repo, projectRepo, taskId) {
|
|
1806
|
+
async function assertTaskProjectActive(repo, projectRepo, taskId, c) {
|
|
1271
1807
|
const task = await repo.getByTaskId(taskId);
|
|
1272
|
-
if (!task) throw new
|
|
1808
|
+
if (!task) throw new NotFoundError9("task", taskId);
|
|
1809
|
+
assertProjectScope(c, task.projectId);
|
|
1273
1810
|
await loadProjectForWrite(projectRepo, task.projectId);
|
|
1274
1811
|
}
|
|
1275
|
-
async function loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId) {
|
|
1812
|
+
async function loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId, c) {
|
|
1276
1813
|
const task = await repo.getByTaskId(taskId);
|
|
1277
|
-
if (!task) throw new
|
|
1814
|
+
if (!task) throw new NotFoundError9("task", taskId);
|
|
1815
|
+
assertProjectScope(c, task.projectId);
|
|
1278
1816
|
await loadProjectForWrite(projectRepo, task.projectId);
|
|
1279
1817
|
if (task.status === "completed" || task.status === "cancelled") {
|
|
1280
1818
|
throw new BadRequestError2(
|
|
1281
1819
|
`Task '${taskId}' is ${task.status} \u2014 task records are read-only after termination`,
|
|
1282
1820
|
void 0,
|
|
1283
|
-
{ bizCode: "TASK_TERMINATED", message: `${
|
|
1821
|
+
{ bizCode: "TASK_TERMINATED", message: `${BIZ_CODE_MESSAGES7.TASK_TERMINATED}\uFF08status: ${task.status}\uFF09` }
|
|
1284
1822
|
);
|
|
1285
1823
|
}
|
|
1286
1824
|
if (nodeId === void 0) return task;
|
|
@@ -1289,9 +1827,9 @@ async function loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId) {
|
|
|
1289
1827
|
}
|
|
1290
1828
|
const node = task.dagInstance.nodes.find((n) => n.id === nodeId);
|
|
1291
1829
|
if (!node) {
|
|
1292
|
-
throw new
|
|
1830
|
+
throw new NotFoundError9("node", nodeId, {
|
|
1293
1831
|
bizCode: "NODE_NOT_IN_INSTANCE",
|
|
1294
|
-
message: `${
|
|
1832
|
+
message: `${BIZ_CODE_MESSAGES7.NODE_NOT_IN_INSTANCE}\uFF08nodeId: ${nodeId}\uFF09`
|
|
1295
1833
|
});
|
|
1296
1834
|
}
|
|
1297
1835
|
const state = task.dagInstance.nodeStates[nodeId];
|
|
@@ -1301,57 +1839,271 @@ async function loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId) {
|
|
|
1301
1839
|
void 0,
|
|
1302
1840
|
{
|
|
1303
1841
|
bizCode: "NODE_RECORD_NOT_WRITABLE",
|
|
1304
|
-
message: `${
|
|
1842
|
+
message: `${BIZ_CODE_MESSAGES7.NODE_RECORD_NOT_WRITABLE}\uFF08${nodeId}: ${state?.status ?? "pending"}\uFF09`
|
|
1305
1843
|
}
|
|
1306
1844
|
);
|
|
1307
1845
|
}
|
|
1308
1846
|
return task;
|
|
1309
1847
|
}
|
|
1310
1848
|
|
|
1849
|
+
// src/routes/task-batch.routes.ts
|
|
1850
|
+
import { Hono as Hono11 } from "hono";
|
|
1851
|
+
import { zValidator as zValidator9 } from "@hono/zod-validator";
|
|
1852
|
+
import { z as z6 } from "zod";
|
|
1853
|
+
import {
|
|
1854
|
+
createTaskRepo as createTaskRepo2,
|
|
1855
|
+
createProjectRepo as createProjectRepo8,
|
|
1856
|
+
ValidationError as ValidationError3,
|
|
1857
|
+
BIZ_CODE_MESSAGES as BIZ_CODE_MESSAGES8,
|
|
1858
|
+
toTaskPublic as toTaskPublic2,
|
|
1859
|
+
taskProgress as taskProgress2,
|
|
1860
|
+
generateEntryId as generateEntryId2
|
|
1861
|
+
} from "@siming-org/core";
|
|
1862
|
+
var BATCH_LIMIT = 50;
|
|
1863
|
+
var ARTIFACT_BATCH_CONTENT_LIMIT = 2e6;
|
|
1864
|
+
var BatchTextSchema = z6.string().min(1).max(2e3);
|
|
1865
|
+
var ObjectItemsSchema = z6.object({ items: z6.array(z6.record(z6.string(), z6.unknown())) });
|
|
1866
|
+
var StringItemsSchema = z6.object({ items: z6.array(z6.string()) });
|
|
1867
|
+
function zodIssueToMessage(issue) {
|
|
1868
|
+
const shape = issue;
|
|
1869
|
+
switch (issue.code) {
|
|
1870
|
+
case "too_small":
|
|
1871
|
+
return `\u957F\u5EA6\u4E0D\u8DB3\uFF08\u6700\u5C0F ${String(shape.minimum)}\uFF09`;
|
|
1872
|
+
case "too_big":
|
|
1873
|
+
return `\u8D85\u51FA\u4E0A\u9650\uFF08\u6700\u5927 ${String(shape.maximum)}\uFF09`;
|
|
1874
|
+
case "invalid_type":
|
|
1875
|
+
return `\u7C7B\u578B\u4E0D\u5339\u914D\uFF08\u671F\u671B ${String(shape.expected)}\uFF09`;
|
|
1876
|
+
case "invalid_value":
|
|
1877
|
+
return `\u503C\u4E0D\u5408\u6CD5\uFF08${issue.message}\uFF09`;
|
|
1878
|
+
default:
|
|
1879
|
+
return issue.message;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
function partitionItems(items, schema) {
|
|
1883
|
+
const ok = [];
|
|
1884
|
+
const bad = [];
|
|
1885
|
+
items.forEach((item, index) => {
|
|
1886
|
+
const r = schema.safeParse(item);
|
|
1887
|
+
if (r.success) {
|
|
1888
|
+
ok.push({ index, data: r.data });
|
|
1889
|
+
} else {
|
|
1890
|
+
const issue = r.error.issues[0];
|
|
1891
|
+
const loc = issue !== void 0 && issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
|
|
1892
|
+
bad.push({ index, message: `${loc}${issue !== void 0 ? zodIssueToMessage(issue) : "\u6761\u76EE\u683C\u5F0F\u4E0D\u5408\u6CD5"}` });
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
return { ok, bad };
|
|
1896
|
+
}
|
|
1897
|
+
function assertBatchSize(items) {
|
|
1898
|
+
if (items.length === 0) {
|
|
1899
|
+
throw new ValidationError3(BIZ_CODE_MESSAGES8.BATCH_EMPTY, [], { bizCode: "BATCH_EMPTY" });
|
|
1900
|
+
}
|
|
1901
|
+
if (items.length > BATCH_LIMIT) {
|
|
1902
|
+
throw new ValidationError3(BIZ_CODE_MESSAGES8.BATCH_LIMIT_EXCEEDED, [], {
|
|
1903
|
+
bizCode: "BATCH_LIMIT_EXCEEDED"
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
function batchAck(entity, updated, entries, failures) {
|
|
1908
|
+
const applied = entries.length;
|
|
1909
|
+
return {
|
|
1910
|
+
taskId: entity.taskId,
|
|
1911
|
+
applied,
|
|
1912
|
+
updated: applied > 0 ? updated : [],
|
|
1913
|
+
entries,
|
|
1914
|
+
failures,
|
|
1915
|
+
updatedAt: entity.updatedAt ?? /* @__PURE__ */ new Date(),
|
|
1916
|
+
task: { ...toTaskPublic2(entity), projectId: entity.projectId, progress: taskProgress2(entity) }
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
function createTaskBatchRoutes(client) {
|
|
1920
|
+
const app = new Hono11();
|
|
1921
|
+
const repo = createTaskRepo2(client.db());
|
|
1922
|
+
const projectRepo = createProjectRepo8(client.db());
|
|
1923
|
+
app.post("/api/tasks/:taskId/records/:nodeId/checks/batch", zValidator9("json", ObjectItemsSchema), async (c) => {
|
|
1924
|
+
const taskId = c.req.param("taskId");
|
|
1925
|
+
const nodeId = c.req.param("nodeId");
|
|
1926
|
+
const { items } = c.req.valid("json");
|
|
1927
|
+
assertBatchSize(items);
|
|
1928
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId, c);
|
|
1929
|
+
const { ok, bad } = partitionItems(items, CheckAddSchema);
|
|
1930
|
+
const idSeq = ((task.nodeRecords ?? {})[nodeId]?.checks ?? []).map((ch) => ch.id);
|
|
1931
|
+
const checks = [];
|
|
1932
|
+
const entries = [];
|
|
1933
|
+
for (const { index, data } of ok) {
|
|
1934
|
+
const check = { id: generateEntryId2(idSeq), item: data.item, passed: data.passed ?? true };
|
|
1935
|
+
idSeq.push(check.id);
|
|
1936
|
+
checks.push(check);
|
|
1937
|
+
entries.push({ index, id: check.id, kind: "check" });
|
|
1938
|
+
}
|
|
1939
|
+
if (checks.length === 0) {
|
|
1940
|
+
return c.json(batchAck(task, [`nodeRecords.${nodeId}.checks`], entries, bad));
|
|
1941
|
+
}
|
|
1942
|
+
const updated = await repo.updateTaskPaths(taskId, {
|
|
1943
|
+
pushes: { [`nodeRecords.${nodeId}.checks`]: { $each: checks } }
|
|
1944
|
+
});
|
|
1945
|
+
return c.json(batchAck(updated, [`nodeRecords.${nodeId}.checks`], entries, bad));
|
|
1946
|
+
});
|
|
1947
|
+
app.post("/api/tasks/:taskId/records/:nodeId/artifacts/batch", zValidator9("json", ObjectItemsSchema), async (c) => {
|
|
1948
|
+
const taskId = c.req.param("taskId");
|
|
1949
|
+
const nodeId = c.req.param("nodeId");
|
|
1950
|
+
const { items } = c.req.valid("json");
|
|
1951
|
+
assertBatchSize(items);
|
|
1952
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId, c);
|
|
1953
|
+
const { ok, bad } = partitionItems(items, ArtifactAddSchema);
|
|
1954
|
+
const totalContent = ok.reduce((sum, { data }) => sum + (data.content?.length ?? 0), 0);
|
|
1955
|
+
if (totalContent > ARTIFACT_BATCH_CONTENT_LIMIT) {
|
|
1956
|
+
throw new ValidationError3(BIZ_CODE_MESSAGES8.BATCH_PAYLOAD_TOO_LARGE, [], {
|
|
1957
|
+
bizCode: "BATCH_PAYLOAD_TOO_LARGE"
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
const idSeq = ((task.nodeRecords ?? {})[nodeId]?.artifacts ?? []).map((a) => a.id);
|
|
1961
|
+
const artifacts = [];
|
|
1962
|
+
const entries = [];
|
|
1963
|
+
for (const { index, data } of ok) {
|
|
1964
|
+
const artifact = {
|
|
1965
|
+
id: generateEntryId2(idSeq),
|
|
1966
|
+
type: data.type,
|
|
1967
|
+
path: data.path,
|
|
1968
|
+
...data.note !== void 0 ? { note: data.note } : {},
|
|
1969
|
+
...data.content !== void 0 ? { content: data.content } : {}
|
|
1970
|
+
};
|
|
1971
|
+
idSeq.push(artifact.id);
|
|
1972
|
+
artifacts.push(artifact);
|
|
1973
|
+
entries.push({ index, id: artifact.id, kind: "artifact" });
|
|
1974
|
+
}
|
|
1975
|
+
if (artifacts.length === 0) {
|
|
1976
|
+
return c.json(batchAck(task, [`nodeRecords.${nodeId}.artifacts`], entries, bad));
|
|
1977
|
+
}
|
|
1978
|
+
const updated = await repo.updateTaskPaths(taskId, {
|
|
1979
|
+
pushes: { [`nodeRecords.${nodeId}.artifacts`]: { $each: artifacts } }
|
|
1980
|
+
});
|
|
1981
|
+
return c.json(batchAck(updated, [`nodeRecords.${nodeId}.artifacts`], entries, bad));
|
|
1982
|
+
});
|
|
1983
|
+
app.post("/api/tasks/:taskId/records/:nodeId/decisions/batch", zValidator9("json", ObjectItemsSchema), async (c) => {
|
|
1984
|
+
const taskId = c.req.param("taskId");
|
|
1985
|
+
const nodeId = c.req.param("nodeId");
|
|
1986
|
+
const { items } = c.req.valid("json");
|
|
1987
|
+
assertBatchSize(items);
|
|
1988
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, nodeId, c);
|
|
1989
|
+
const { ok, bad } = partitionItems(items, DecisionAddSchema);
|
|
1990
|
+
const idSeq = ((task.nodeRecords ?? {})[nodeId]?.decisions ?? []).map((d) => d.id);
|
|
1991
|
+
const decisions = [];
|
|
1992
|
+
const entries = [];
|
|
1993
|
+
for (const { index, data } of ok) {
|
|
1994
|
+
const decision = { id: generateEntryId2(idSeq), topic: data.topic, decision: data.decision };
|
|
1995
|
+
idSeq.push(decision.id);
|
|
1996
|
+
decisions.push(decision);
|
|
1997
|
+
entries.push({ index, id: decision.id, kind: "decision" });
|
|
1998
|
+
}
|
|
1999
|
+
if (decisions.length === 0) {
|
|
2000
|
+
return c.json(batchAck(task, [`nodeRecords.${nodeId}.decisions`], entries, bad));
|
|
2001
|
+
}
|
|
2002
|
+
const updated = await repo.updateTaskPaths(taskId, {
|
|
2003
|
+
pushes: { [`nodeRecords.${nodeId}.decisions`]: { $each: decisions } }
|
|
2004
|
+
});
|
|
2005
|
+
return c.json(batchAck(updated, [`nodeRecords.${nodeId}.decisions`], entries, bad));
|
|
2006
|
+
});
|
|
2007
|
+
app.post("/api/tasks/:taskId/doc/acceptance/batch", zValidator9("json", StringItemsSchema), async (c) => {
|
|
2008
|
+
const taskId = c.req.param("taskId");
|
|
2009
|
+
const { items } = c.req.valid("json");
|
|
2010
|
+
assertBatchSize(items);
|
|
2011
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, void 0, c);
|
|
2012
|
+
const { ok, bad } = partitionItems(items, BatchTextSchema);
|
|
2013
|
+
const entries = ok.map(({ index }) => ({ index }));
|
|
2014
|
+
if (ok.length === 0) {
|
|
2015
|
+
return c.json(batchAck(task, ["doc.acceptance"], entries, bad));
|
|
2016
|
+
}
|
|
2017
|
+
const updated = await repo.updateTaskPaths(taskId, {
|
|
2018
|
+
pushes: { "doc.acceptance": { $each: ok.map(({ data }) => data) } }
|
|
2019
|
+
});
|
|
2020
|
+
return c.json(batchAck(updated, ["doc.acceptance"], entries, bad));
|
|
2021
|
+
});
|
|
2022
|
+
app.post("/api/tasks/:taskId/doc/non-goal/batch", zValidator9("json", StringItemsSchema), async (c) => {
|
|
2023
|
+
const taskId = c.req.param("taskId");
|
|
2024
|
+
const { items } = c.req.valid("json");
|
|
2025
|
+
assertBatchSize(items);
|
|
2026
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, void 0, c);
|
|
2027
|
+
const { ok, bad } = partitionItems(items, BatchTextSchema);
|
|
2028
|
+
const entries = ok.map(({ index }) => ({ index }));
|
|
2029
|
+
if (ok.length === 0) {
|
|
2030
|
+
return c.json(batchAck(task, ["doc.nonGoals"], entries, bad));
|
|
2031
|
+
}
|
|
2032
|
+
const updated = await repo.updateTaskPaths(taskId, {
|
|
2033
|
+
pushes: { "doc.nonGoals": { $each: ok.map(({ data }) => data) } }
|
|
2034
|
+
});
|
|
2035
|
+
return c.json(batchAck(updated, ["doc.nonGoals"], entries, bad));
|
|
2036
|
+
});
|
|
2037
|
+
app.post("/api/tasks/:taskId/archnotes/batch", zValidator9("json", StringItemsSchema), async (c) => {
|
|
2038
|
+
const taskId = c.req.param("taskId");
|
|
2039
|
+
const { items } = c.req.valid("json");
|
|
2040
|
+
assertBatchSize(items);
|
|
2041
|
+
const task = await loadTaskForRecordWrite(repo, projectRepo, taskId, void 0, c);
|
|
2042
|
+
const { ok, bad } = partitionItems(items, BatchTextSchema);
|
|
2043
|
+
const idSeq = (task.archNotes ?? []).map((n) => n.id);
|
|
2044
|
+
const notes = [];
|
|
2045
|
+
const entries = [];
|
|
2046
|
+
for (const { index, data } of ok) {
|
|
2047
|
+
const note = { id: generateEntryId2(idSeq), text: data, at: /* @__PURE__ */ new Date() };
|
|
2048
|
+
idSeq.push(note.id);
|
|
2049
|
+
notes.push(note);
|
|
2050
|
+
entries.push({ index, id: note.id, kind: "archnote" });
|
|
2051
|
+
}
|
|
2052
|
+
if (notes.length === 0) {
|
|
2053
|
+
return c.json(batchAck(task, ["archNotes"], entries, bad));
|
|
2054
|
+
}
|
|
2055
|
+
const updated = await repo.updateTaskPaths(taskId, {
|
|
2056
|
+
pushes: { archNotes: { $each: notes } }
|
|
2057
|
+
});
|
|
2058
|
+
return c.json(batchAck(updated, ["archNotes"], entries, bad));
|
|
2059
|
+
});
|
|
2060
|
+
return app;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
1311
2063
|
// src/routes/settings.routes.ts
|
|
1312
|
-
import { Hono as
|
|
1313
|
-
import { zValidator as
|
|
1314
|
-
import { z as
|
|
2064
|
+
import { Hono as Hono12 } from "hono";
|
|
2065
|
+
import { zValidator as zValidator10 } from "@hono/zod-validator";
|
|
2066
|
+
import { z as z7 } from "zod";
|
|
1315
2067
|
import {
|
|
1316
2068
|
createEnumRegistryRepo as createEnumRegistryRepo3,
|
|
1317
2069
|
EnumRegistryUpdateSchema,
|
|
1318
|
-
NotFoundError as
|
|
2070
|
+
NotFoundError as NotFoundError10
|
|
1319
2071
|
} from "@siming-org/core";
|
|
1320
2072
|
function createSettingsRoutes(client) {
|
|
1321
|
-
const app = new
|
|
2073
|
+
const app = new Hono12();
|
|
1322
2074
|
const repo = createEnumRegistryRepo3(client.db());
|
|
1323
2075
|
app.get("/api/settings/enums", async (c) => {
|
|
1324
2076
|
return c.json(await repo.listRegistries());
|
|
1325
2077
|
});
|
|
1326
2078
|
app.get(
|
|
1327
2079
|
"/api/settings/enums/:category",
|
|
1328
|
-
|
|
2080
|
+
zValidator10("param", z7.object({ category: z7.string() })),
|
|
1329
2081
|
async (c) => {
|
|
1330
2082
|
const { category } = c.req.valid("param");
|
|
1331
2083
|
const registry = await repo.getRegistry(category);
|
|
1332
|
-
if (!registry) throw new
|
|
2084
|
+
if (!registry) throw new NotFoundError10("enum-registry", category);
|
|
1333
2085
|
return c.json(registry);
|
|
1334
2086
|
}
|
|
1335
2087
|
);
|
|
1336
2088
|
app.put(
|
|
1337
2089
|
"/api/settings/enums/:category",
|
|
1338
|
-
|
|
1339
|
-
|
|
2090
|
+
zValidator10("param", z7.object({ category: z7.string() })),
|
|
2091
|
+
zValidator10("json", EnumRegistryUpdateSchema),
|
|
1340
2092
|
async (c) => {
|
|
1341
2093
|
const { category } = c.req.valid("param");
|
|
1342
2094
|
const data = c.req.valid("json");
|
|
1343
2095
|
const registry = await repo.updateRegistry(category, data);
|
|
1344
|
-
if (!registry) throw new
|
|
2096
|
+
if (!registry) throw new NotFoundError10("enum-registry", category);
|
|
1345
2097
|
return c.json(registry);
|
|
1346
2098
|
}
|
|
1347
2099
|
);
|
|
1348
2100
|
app.delete(
|
|
1349
2101
|
"/api/settings/enums/:category/entries/:value",
|
|
1350
|
-
|
|
2102
|
+
zValidator10("param", z7.object({ category: z7.string(), value: z7.string().min(1) })),
|
|
1351
2103
|
async (c) => {
|
|
1352
2104
|
const { category, value } = c.req.valid("param");
|
|
1353
2105
|
const deleted = await repo.deleteEntry(category, value);
|
|
1354
|
-
if (!deleted) throw new
|
|
2106
|
+
if (!deleted) throw new NotFoundError10("enum-registry", category);
|
|
1355
2107
|
return c.body(null, 204);
|
|
1356
2108
|
}
|
|
1357
2109
|
);
|
|
@@ -1360,23 +2112,195 @@ function createSettingsRoutes(client) {
|
|
|
1360
2112
|
|
|
1361
2113
|
// src/routes/index.ts
|
|
1362
2114
|
function createRoutes(client) {
|
|
1363
|
-
const router = new
|
|
2115
|
+
const router = new Hono13();
|
|
1364
2116
|
router.route("/", createHealth(client));
|
|
1365
2117
|
router.route("/", createProjectRoutes(client));
|
|
1366
2118
|
router.route("/", createSkillRoutes(client));
|
|
1367
2119
|
router.route("/", createAgentRoutes(client));
|
|
1368
2120
|
router.route("/", createModelAliasRoutes(client));
|
|
1369
2121
|
router.route("/", createDagTemplateRoutes(client));
|
|
2122
|
+
router.route("/", createNodePresetRoutes(client));
|
|
2123
|
+
router.route("/", createNodeLibraryRoutes(client));
|
|
1370
2124
|
router.route("/", createTaskRoutes(client));
|
|
2125
|
+
router.route("/", createTaskBatchRoutes(client));
|
|
1371
2126
|
router.route("/", createSettingsRoutes(client));
|
|
1372
2127
|
return router;
|
|
1373
2128
|
}
|
|
1374
2129
|
|
|
2130
|
+
// src/routes/auth.routes.ts
|
|
2131
|
+
import { Hono as Hono14 } from "hono";
|
|
2132
|
+
import { zValidator as zValidator11 } from "@hono/zod-validator";
|
|
2133
|
+
import {
|
|
2134
|
+
createAuthAccountRepo,
|
|
2135
|
+
createAuthTokenRepo as createAuthTokenRepo2,
|
|
2136
|
+
createAuthSessionRepo as createAuthSessionRepo2,
|
|
2137
|
+
createProjectRepo as createProjectRepo9,
|
|
2138
|
+
hashPassword,
|
|
2139
|
+
verifyPassword,
|
|
2140
|
+
generateToken,
|
|
2141
|
+
generateSessionId,
|
|
2142
|
+
hashToken as hashToken2,
|
|
2143
|
+
AuthAccountCreateSchema,
|
|
2144
|
+
LoginSchema,
|
|
2145
|
+
PasswordChangeSchema,
|
|
2146
|
+
ConflictError as ConflictError8,
|
|
2147
|
+
NotFoundError as NotFoundError11
|
|
2148
|
+
} from "@siming-org/core";
|
|
2149
|
+
var DUMMY_PASSWORD_HASH = await hashPassword("timing-equalizer-dummy");
|
|
2150
|
+
function createAuthRoutes(client, deps) {
|
|
2151
|
+
const app = new Hono14();
|
|
2152
|
+
const accountRepo = createAuthAccountRepo(client.db());
|
|
2153
|
+
const tokenRepo = createAuthTokenRepo2(client.db());
|
|
2154
|
+
const sessionRepo = createAuthSessionRepo2(client.db());
|
|
2155
|
+
const projectRepo = createProjectRepo9(client.db());
|
|
2156
|
+
function setSessionCookie(c, sessionId) {
|
|
2157
|
+
c.header("Set-Cookie", `${SESSION_COOKIE_NAME}=${sessionId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${Math.floor(SESSION_TTL_MS / 1e3)}`);
|
|
2158
|
+
}
|
|
2159
|
+
function clearSessionCookie(c) {
|
|
2160
|
+
c.header("Set-Cookie", `${SESSION_COOKIE_NAME}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
|
|
2161
|
+
}
|
|
2162
|
+
app.get("/api/auth/status", async (c) => {
|
|
2163
|
+
const account = await accountRepo.getAccount();
|
|
2164
|
+
return c.json({ authEnabled: deps.authEnabled, needsSetup: deps.authEnabled && account === null });
|
|
2165
|
+
});
|
|
2166
|
+
const whoamiHandler = async (c) => {
|
|
2167
|
+
const auth = getAuth(c);
|
|
2168
|
+
const base = { authEnabled: deps.authEnabled, server: { url: new URL(c.req.url).origin } };
|
|
2169
|
+
if (!deps.authEnabled || auth.kind === "open" || auth.kind === "none") {
|
|
2170
|
+
return c.json({ ...base, identity: "none", project: null });
|
|
2171
|
+
}
|
|
2172
|
+
if (auth.kind === "admin") {
|
|
2173
|
+
return c.json({ ...base, identity: "admin", project: null });
|
|
2174
|
+
}
|
|
2175
|
+
const project = await projectRepo.getById(auth.projectId);
|
|
2176
|
+
return c.json({
|
|
2177
|
+
...base,
|
|
2178
|
+
identity: "project",
|
|
2179
|
+
project: project ? { id: project.id ?? auth.projectId, key: project.key, name: project.name } : null
|
|
2180
|
+
});
|
|
2181
|
+
};
|
|
2182
|
+
app.get("/api/auth/whoami", whoamiHandler);
|
|
2183
|
+
app.post("/api/auth/whoami", whoamiHandler);
|
|
2184
|
+
const v422 = (schema) => zValidator11("json", schema, (result, c) => {
|
|
2185
|
+
if (!result.success) {
|
|
2186
|
+
return c.json({ error: "validation_error", message: "\u683C\u5F0F\u4E0D\u7B26", issues: result.error.issues }, 422);
|
|
2187
|
+
}
|
|
2188
|
+
return void 0;
|
|
2189
|
+
});
|
|
2190
|
+
app.post("/api/auth/setup", v422(AuthAccountCreateSchema), async (c) => {
|
|
2191
|
+
const body = c.req.valid("json");
|
|
2192
|
+
const existing = await accountRepo.getAccount();
|
|
2193
|
+
if (existing) {
|
|
2194
|
+
throw new ConflictError8("auth-account", existing.id ?? "unknown", {
|
|
2195
|
+
bizCode: "AUTH_ACCOUNT_EXISTS",
|
|
2196
|
+
message: "\u7BA1\u7406\u5458\u8D26\u53F7\u5DF2\u521D\u59CB\u5316\uFF08\u65E0\u9700\u91CD\u590D\u521B\u5EFA\uFF09"
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
const passwordHash = await hashPassword(body.password);
|
|
2200
|
+
const account = await accountRepo.createAccount(body.username, passwordHash);
|
|
2201
|
+
const sessionId = generateSessionId();
|
|
2202
|
+
await sessionRepo.createSession(sessionId, account.id ?? "", new Date(Date.now() + SESSION_TTL_MS));
|
|
2203
|
+
setSessionCookie(c, sessionId);
|
|
2204
|
+
return c.json({ username: account.username });
|
|
2205
|
+
});
|
|
2206
|
+
app.post("/api/auth/login", zValidator11("json", LoginSchema), async (c) => {
|
|
2207
|
+
const body = c.req.valid("json");
|
|
2208
|
+
const account = await accountRepo.getAccount();
|
|
2209
|
+
if (!account || account.username !== body.username) {
|
|
2210
|
+
await verifyPassword(body.password, DUMMY_PASSWORD_HASH);
|
|
2211
|
+
return c.json({ error: "AUTH_INVALID_CREDENTIALS", message: "\u8D26\u53F7\u6216\u5BC6\u7801\u9519\u8BEF" }, 401);
|
|
2212
|
+
}
|
|
2213
|
+
const ok = await verifyPassword(body.password, account.passwordHash);
|
|
2214
|
+
if (!ok) {
|
|
2215
|
+
return c.json({ error: "AUTH_INVALID_CREDENTIALS", message: "\u8D26\u53F7\u6216\u5BC6\u7801\u9519\u8BEF" }, 401);
|
|
2216
|
+
}
|
|
2217
|
+
const sessionId = generateSessionId();
|
|
2218
|
+
await sessionRepo.createSession(sessionId, account.id ?? "", new Date(Date.now() + SESSION_TTL_MS));
|
|
2219
|
+
setSessionCookie(c, sessionId);
|
|
2220
|
+
return c.json({ username: account.username });
|
|
2221
|
+
});
|
|
2222
|
+
app.post("/api/auth/logout", async (c) => {
|
|
2223
|
+
const sessionId = readSessionCookie(c);
|
|
2224
|
+
if (sessionId !== void 0) {
|
|
2225
|
+
await sessionRepo.deleteBySessionId(sessionId);
|
|
2226
|
+
}
|
|
2227
|
+
clearSessionCookie(c);
|
|
2228
|
+
return c.body(null, 204);
|
|
2229
|
+
});
|
|
2230
|
+
app.get("/api/auth/me", async (c) => {
|
|
2231
|
+
const auth = getAuth(c);
|
|
2232
|
+
if (auth.kind !== "admin") {
|
|
2233
|
+
return c.json({ error: "AUTH_REQUIRED", message: "\u9700\u8981\u767B\u5F55" }, 401);
|
|
2234
|
+
}
|
|
2235
|
+
const account = await accountRepo.getAccount();
|
|
2236
|
+
return c.json({ username: account?.username ?? "" });
|
|
2237
|
+
});
|
|
2238
|
+
app.put("/api/auth/password", v422(PasswordChangeSchema), async (c) => {
|
|
2239
|
+
const auth = getAuth(c);
|
|
2240
|
+
if (auth.kind !== "admin") {
|
|
2241
|
+
return c.json({ error: "AUTH_REQUIRED", message: "\u9700\u8981\u767B\u5F55" }, 401);
|
|
2242
|
+
}
|
|
2243
|
+
const body = c.req.valid("json");
|
|
2244
|
+
const account = await accountRepo.getAccount();
|
|
2245
|
+
if (!account) {
|
|
2246
|
+
return c.json({ error: "AUTH_REQUIRED", message: "\u9700\u8981\u767B\u5F55" }, 401);
|
|
2247
|
+
}
|
|
2248
|
+
const ok = await verifyPassword(body.currentPassword, account.passwordHash);
|
|
2249
|
+
if (!ok) {
|
|
2250
|
+
return c.json({ error: "AUTH_INVALID_CREDENTIALS", message: "\u8D26\u53F7\u6216\u5BC6\u7801\u9519\u8BEF" }, 401);
|
|
2251
|
+
}
|
|
2252
|
+
await accountRepo.updatePassword(account.id ?? "", await hashPassword(body.newPassword));
|
|
2253
|
+
return c.body(null, 204);
|
|
2254
|
+
});
|
|
2255
|
+
app.post("/api/auth/tokens/admin", async (c) => {
|
|
2256
|
+
const token = generateToken("admin");
|
|
2257
|
+
await tokenRepo.upsertToken("admin", hashToken2(token));
|
|
2258
|
+
return c.json({ token, type: "admin" }, 201);
|
|
2259
|
+
});
|
|
2260
|
+
app.get("/api/auth/tokens/admin", async (c) => {
|
|
2261
|
+
const status = await tokenRepo.getTokenStatus("admin");
|
|
2262
|
+
return c.json({ type: "admin", hasToken: status.hasToken, createdAt: status.createdAt });
|
|
2263
|
+
});
|
|
2264
|
+
app.get("/api/auth/tokens/project", async (c) => {
|
|
2265
|
+
const projects = await projectRepo.list();
|
|
2266
|
+
const statuses = await tokenRepo.listProjectTokenStatuses(projects.map((p) => p.id ?? ""));
|
|
2267
|
+
const items = projects.map((p) => {
|
|
2268
|
+
const st = statuses.get(p.id ?? "");
|
|
2269
|
+
return { type: "project", projectId: p.id, hasToken: st?.hasToken ?? false, createdAt: st?.createdAt ?? null };
|
|
2270
|
+
});
|
|
2271
|
+
return c.json({ items, total: items.length, page: 1, limit: items.length });
|
|
2272
|
+
});
|
|
2273
|
+
app.post("/api/auth/tokens/project/:projectId", async (c) => {
|
|
2274
|
+
const projectId = c.req.param("projectId");
|
|
2275
|
+
const project = await projectRepo.getById(projectId);
|
|
2276
|
+
if (!project) throw new NotFoundError11("project", projectId);
|
|
2277
|
+
if (project.status === "archived") {
|
|
2278
|
+
throw new ConflictError8("project", projectId, { bizCode: "PROJECT_ARCHIVED" });
|
|
2279
|
+
}
|
|
2280
|
+
const token = generateToken("project");
|
|
2281
|
+
await tokenRepo.upsertToken("project", hashToken2(token), projectId);
|
|
2282
|
+
return c.json({ token, type: "project", projectId }, 201);
|
|
2283
|
+
});
|
|
2284
|
+
app.get("/api/auth/tokens/project/:projectId", async (c) => {
|
|
2285
|
+
const projectId = c.req.param("projectId");
|
|
2286
|
+
const status = await tokenRepo.getTokenStatus("project", projectId);
|
|
2287
|
+
return c.json({ type: "project", projectId, hasToken: status.hasToken, createdAt: status.createdAt });
|
|
2288
|
+
});
|
|
2289
|
+
return app;
|
|
2290
|
+
}
|
|
2291
|
+
function readSessionCookie(c) {
|
|
2292
|
+
return getCookie(c, SESSION_COOKIE_NAME);
|
|
2293
|
+
}
|
|
2294
|
+
|
|
1375
2295
|
// src/app.ts
|
|
1376
2296
|
function createApp(client, opts = {}) {
|
|
1377
|
-
const app = new
|
|
2297
|
+
const app = new Hono15();
|
|
1378
2298
|
setupErrorHandler(app);
|
|
2299
|
+
const authEnabled = opts.authEnabled ?? false;
|
|
2300
|
+
app.use("/api/*", createAuthMiddleware(client, { authEnabled }));
|
|
2301
|
+
app.use("/health/*", createAuthMiddleware(client, { authEnabled }));
|
|
1379
2302
|
app.route("/", createRoutes(client));
|
|
2303
|
+
app.route("/", createAuthRoutes(client, { authEnabled }));
|
|
1380
2304
|
const webRoot = opts.webRoot !== void 0 ? opts.webRoot : resolveWebDistRoot();
|
|
1381
2305
|
app.route("/", createStaticRoutes(webRoot));
|
|
1382
2306
|
return app;
|
|
@@ -1387,7 +2311,7 @@ import { serve } from "@hono/node-server";
|
|
|
1387
2311
|
import { createMongoClient } from "@siming-org/core";
|
|
1388
2312
|
|
|
1389
2313
|
// src/ensure-collections.ts
|
|
1390
|
-
import { EnumRegistrySchema, bumpPatch as bumpPatch2 } from "@siming-org/core";
|
|
2314
|
+
import { EnumRegistrySchema, bumpPatch as bumpPatch2, createAuthSessionRepo as createAuthSessionRepo3, buildTemplateCode, isLegalTemplateCode } from "@siming-org/core";
|
|
1391
2315
|
async function ensureCollections(client) {
|
|
1392
2316
|
const db = client.db();
|
|
1393
2317
|
await Promise.all([
|
|
@@ -1397,13 +2321,17 @@ async function ensureCollections(client) {
|
|
|
1397
2321
|
ensureDagTemplateCollection(db),
|
|
1398
2322
|
ensureTaskCollection(db),
|
|
1399
2323
|
ensureModelAliasCollection(db),
|
|
1400
|
-
ensureEnumRegistryCollection(db)
|
|
2324
|
+
ensureEnumRegistryCollection(db),
|
|
2325
|
+
ensureAuthCollections(db),
|
|
2326
|
+
ensureNodePresetCollection(db),
|
|
2327
|
+
ensureNodeLibraryCollection(db)
|
|
1401
2328
|
]);
|
|
1402
2329
|
await migrateProjectSentinelData(db);
|
|
1403
2330
|
await migrateGateRemovalData(db);
|
|
1404
2331
|
await migrateTaskRecordData(db);
|
|
1405
2332
|
await migrateDagTrackTaskValues(db);
|
|
1406
2333
|
await migrateAgentFunctionBackfill(db);
|
|
2334
|
+
await migrateDagTemplateCode(db);
|
|
1407
2335
|
await ensurePostMigrationIndexes(db);
|
|
1408
2336
|
}
|
|
1409
2337
|
var ENUM_REGISTRY_SEEDS = {
|
|
@@ -1464,6 +2392,57 @@ function buildSeedEntries(seeds) {
|
|
|
1464
2392
|
...s.color ? { color: s.color } : {}
|
|
1465
2393
|
}));
|
|
1466
2394
|
}
|
|
2395
|
+
async function ensureAuthCollections(db) {
|
|
2396
|
+
const accountValidator = { $jsonSchema: {
|
|
2397
|
+
bsonType: "object",
|
|
2398
|
+
required: ["username", "passwordHash"],
|
|
2399
|
+
properties: {
|
|
2400
|
+
username: { bsonType: "string", minLength: 1 },
|
|
2401
|
+
passwordHash: { bsonType: "string", minLength: 1 },
|
|
2402
|
+
createdAt: { bsonType: "date" },
|
|
2403
|
+
updatedAt: { bsonType: "date" }
|
|
2404
|
+
}
|
|
2405
|
+
} };
|
|
2406
|
+
const collections = await db.listCollections({ name: "auth_accounts" }).toArray();
|
|
2407
|
+
if (collections.length === 0) {
|
|
2408
|
+
await db.createCollection("auth_accounts", { validator: accountValidator });
|
|
2409
|
+
}
|
|
2410
|
+
await db.collection("auth_accounts").createIndex({ username: 1 }, { unique: true });
|
|
2411
|
+
const tokenValidator = { $jsonSchema: {
|
|
2412
|
+
bsonType: "object",
|
|
2413
|
+
required: ["tokenHash", "type"],
|
|
2414
|
+
properties: {
|
|
2415
|
+
tokenHash: { bsonType: "string" },
|
|
2416
|
+
type: { enum: ["admin", "project"] },
|
|
2417
|
+
projectId: { bsonType: "string" },
|
|
2418
|
+
createdAt: { bsonType: "date" },
|
|
2419
|
+
updatedAt: { bsonType: "date" }
|
|
2420
|
+
}
|
|
2421
|
+
} };
|
|
2422
|
+
const tokenColls = await db.listCollections({ name: "auth_tokens" }).toArray();
|
|
2423
|
+
if (tokenColls.length === 0) {
|
|
2424
|
+
await db.createCollection("auth_tokens", { validator: tokenValidator });
|
|
2425
|
+
}
|
|
2426
|
+
await db.collection("auth_tokens").createIndex({ tokenHash: 1 }, { unique: true });
|
|
2427
|
+
await db.collection("auth_tokens").createIndex(
|
|
2428
|
+
{ projectId: 1 },
|
|
2429
|
+
{ unique: true, partialFilterExpression: { type: "project" } }
|
|
2430
|
+
);
|
|
2431
|
+
await db.collection("auth_tokens").createIndex(
|
|
2432
|
+
{ type: 1 },
|
|
2433
|
+
{ unique: true, partialFilterExpression: { type: "admin" } }
|
|
2434
|
+
);
|
|
2435
|
+
const sessionColls = await db.listCollections({ name: "auth_sessions" }).toArray();
|
|
2436
|
+
if (sessionColls.length === 0) {
|
|
2437
|
+
await db.createCollection("auth_sessions");
|
|
2438
|
+
}
|
|
2439
|
+
await db.collection("auth_sessions").createIndex({ sessionId: 1 }, { unique: true });
|
|
2440
|
+
await db.collection("auth_sessions").createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 });
|
|
2441
|
+
const removed = await createAuthSessionRepo3(db).deleteExpiredSessions();
|
|
2442
|
+
if (removed > 0) {
|
|
2443
|
+
console.log(`[siming] auth: \u6E05\u626B\u8FC7\u671F\u4F1A\u8BDD ${String(removed)} \u4E2A`);
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
1467
2446
|
async function ensureEnumRegistryCollection(db) {
|
|
1468
2447
|
const collections = await db.listCollections({ name: "enum_registry" }).toArray();
|
|
1469
2448
|
if (collections.length === 0) {
|
|
@@ -1618,6 +2597,9 @@ async function ensureDagTemplateCollection(db) {
|
|
|
1618
2597
|
properties: {
|
|
1619
2598
|
name: { bsonType: "string" },
|
|
1620
2599
|
projectId: { bsonType: "string" },
|
|
2600
|
+
// T202609020002:业务 code(可选——存量迁移前缺失合法,M13 回填;格式规则归应用层 Zod,
|
|
2601
|
+
// DB 只做类型兜底,pattern 禁令见技术架构「DB validator 职责边界」)
|
|
2602
|
+
code: { bsonType: "string" },
|
|
1621
2603
|
description: { bsonType: "string" },
|
|
1622
2604
|
nodes: { bsonType: "array" },
|
|
1623
2605
|
edges: { bsonType: "array" },
|
|
@@ -1778,6 +2760,95 @@ async function migrateAgentFunctionBackfill(db) {
|
|
|
1778
2760
|
}
|
|
1779
2761
|
}
|
|
1780
2762
|
}
|
|
2763
|
+
async function ensureNodePresetCollection(db) {
|
|
2764
|
+
const validator = { $jsonSchema: {
|
|
2765
|
+
bsonType: "object",
|
|
2766
|
+
required: ["code", "label", "nodeId", "phase", "track", "prompt", "skills", "scope", "version"],
|
|
2767
|
+
properties: {
|
|
2768
|
+
code: { bsonType: "string" },
|
|
2769
|
+
label: { bsonType: "string", minLength: 1 },
|
|
2770
|
+
nodeId: { bsonType: "string" },
|
|
2771
|
+
phase: { bsonType: "string" },
|
|
2772
|
+
track: { bsonType: "string" },
|
|
2773
|
+
prompt: { bsonType: "string" },
|
|
2774
|
+
skills: { bsonType: "array", items: { bsonType: "string" } },
|
|
2775
|
+
agents: { bsonType: "array", items: { bsonType: "string" } },
|
|
2776
|
+
scope: { enum: ["global", "project"] },
|
|
2777
|
+
projectId: { bsonType: "string" },
|
|
2778
|
+
description: { bsonType: "string" },
|
|
2779
|
+
version: { bsonType: "string" },
|
|
2780
|
+
source: { bsonType: "object" },
|
|
2781
|
+
enabled: { bsonType: "bool" },
|
|
2782
|
+
createdAt: { bsonType: "date" },
|
|
2783
|
+
updatedAt: { bsonType: "date" }
|
|
2784
|
+
}
|
|
2785
|
+
} };
|
|
2786
|
+
const collections = await db.listCollections({ name: "node_presets" }).toArray();
|
|
2787
|
+
if (collections.length === 0) {
|
|
2788
|
+
await db.createCollection("node_presets", { validator });
|
|
2789
|
+
} else {
|
|
2790
|
+
await db.command({ collMod: "node_presets", validationLevel: "strict", validator });
|
|
2791
|
+
}
|
|
2792
|
+
await db.collection("node_presets").createIndex({ code: 1 }, { unique: true });
|
|
2793
|
+
await db.collection("node_presets").createIndex({ scope: 1, projectId: 1 });
|
|
2794
|
+
}
|
|
2795
|
+
async function ensureNodeLibraryCollection(db) {
|
|
2796
|
+
const validator = { $jsonSchema: {
|
|
2797
|
+
bsonType: "object",
|
|
2798
|
+
required: ["name", "version", "scope", "nodes", "compositions"],
|
|
2799
|
+
properties: {
|
|
2800
|
+
name: { bsonType: "string", minLength: 1 },
|
|
2801
|
+
version: { bsonType: "string" },
|
|
2802
|
+
scope: { enum: ["global", "project"] },
|
|
2803
|
+
projectId: { bsonType: "string" },
|
|
2804
|
+
nodes: { bsonType: "array", items: { bsonType: "string" } },
|
|
2805
|
+
compositions: { bsonType: "array" },
|
|
2806
|
+
installedAt: { bsonType: "date" },
|
|
2807
|
+
createdAt: { bsonType: "date" },
|
|
2808
|
+
updatedAt: { bsonType: "date" }
|
|
2809
|
+
}
|
|
2810
|
+
} };
|
|
2811
|
+
const collections = await db.listCollections({ name: "node_libraries" }).toArray();
|
|
2812
|
+
if (collections.length === 0) {
|
|
2813
|
+
await db.createCollection("node_libraries", { validator });
|
|
2814
|
+
}
|
|
2815
|
+
await db.collection("node_libraries").createIndex({ name: 1 }, { unique: true });
|
|
2816
|
+
await db.collection("node_libraries").createIndex({ scope: 1, projectId: 1 });
|
|
2817
|
+
}
|
|
2818
|
+
async function migrateDagTemplateCode(db) {
|
|
2819
|
+
const coll = db.collection("dag_templates");
|
|
2820
|
+
const docs = await coll.find({}, { projection: { projectId: 1, name: 1, code: 1 } }).toArray();
|
|
2821
|
+
const takenByProject = /* @__PURE__ */ new Map();
|
|
2822
|
+
for (const doc of docs) {
|
|
2823
|
+
const projectId = String(doc.projectId);
|
|
2824
|
+
if (typeof doc.code === "string" && isLegalTemplateCode(doc.code)) {
|
|
2825
|
+
const taken = takenByProject.get(projectId) ?? /* @__PURE__ */ new Set();
|
|
2826
|
+
taken.add(doc.code);
|
|
2827
|
+
takenByProject.set(projectId, taken);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
let migrated = 0;
|
|
2831
|
+
for (const doc of docs) {
|
|
2832
|
+
const projectId = String(doc.projectId);
|
|
2833
|
+
if (typeof doc.code === "string" && isLegalTemplateCode(doc.code)) continue;
|
|
2834
|
+
const taken = takenByProject.get(projectId) ?? /* @__PURE__ */ new Set();
|
|
2835
|
+
const code = buildTemplateCode(String(doc.name ?? ""), taken);
|
|
2836
|
+
taken.add(code);
|
|
2837
|
+
takenByProject.set(projectId, taken);
|
|
2838
|
+
const result = await coll.updateOne(
|
|
2839
|
+
{ _id: doc._id },
|
|
2840
|
+
{ $set: { code } },
|
|
2841
|
+
{ bypassDocumentValidation: true }
|
|
2842
|
+
);
|
|
2843
|
+
if (result.matchedCount === 0) {
|
|
2844
|
+
throw new Error("T202609020002 M13 \u8FC1\u79FB\u5931\u8D25\uFF1Acode \u56DE\u586B\u672A\u547D\u4E2D\uFF08\u542F\u52A8\u671F\u5355\u8FDB\u7A0B\u4E32\u884C\uFF0C\u4E0D\u5E94\u53D1\u751F\uFF09");
|
|
2845
|
+
}
|
|
2846
|
+
migrated += 1;
|
|
2847
|
+
}
|
|
2848
|
+
if (migrated > 0) {
|
|
2849
|
+
console.log(`[siming] T202609020002 M13\uFF1Adag_templates.code \u56DE\u586B ${migrated} \u4E2A`);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
1781
2852
|
async function ensurePostMigrationIndexes(db) {
|
|
1782
2853
|
const tasks = db.collection("tasks");
|
|
1783
2854
|
const taskIndexes = await tasks.listIndexes().toArray();
|
|
@@ -1806,6 +2877,30 @@ async function ensurePostMigrationIndexes(db) {
|
|
|
1806
2877
|
throw new Error(`dag_templates \u5B58\u5728\u540C\u9879\u76EE\u91CD\u540D\u6A21\u677F\uFF0C\u65E0\u6CD5\u5EFA\u7ACB (projectId,name) \u552F\u4E00\u7D22\u5F15\uFF0C\u8BF7\u5148\u4EBA\u5DE5\u5F52\u5E76\uFF1A${detail}`);
|
|
1807
2878
|
}
|
|
1808
2879
|
await templates.createIndex({ projectId: 1, name: 1 }, { unique: true });
|
|
2880
|
+
const seenCodes = /* @__PURE__ */ new Map();
|
|
2881
|
+
const codeDocs = await templates.find(
|
|
2882
|
+
{ code: { $exists: true } },
|
|
2883
|
+
{ projection: { projectId: 1, code: 1 } }
|
|
2884
|
+
).toArray();
|
|
2885
|
+
for (const doc of codeDocs) {
|
|
2886
|
+
if (typeof doc.code !== "string") continue;
|
|
2887
|
+
const key = `${String(doc.projectId)}\0${doc.code}`;
|
|
2888
|
+
const ids = seenCodes.get(key) ?? [];
|
|
2889
|
+
ids.push(doc._id.toString());
|
|
2890
|
+
seenCodes.set(key, ids);
|
|
2891
|
+
}
|
|
2892
|
+
const codeConflicts = [...seenCodes.entries()].filter(([, ids]) => ids.length > 1);
|
|
2893
|
+
if (codeConflicts.length > 0) {
|
|
2894
|
+
const detail = codeConflicts.map(([key, ids]) => {
|
|
2895
|
+
const [projectId, code] = key.split("\0");
|
|
2896
|
+
return `projectId=${projectId} code="${code}" \xD7${ids.length} (_id: ${ids.join(", ")})`;
|
|
2897
|
+
}).join("; ");
|
|
2898
|
+
throw new Error(`dag_templates \u5B58\u5728\u540C\u9879\u76EE\u91CD\u590D code\uFF0C\u65E0\u6CD5\u5EFA\u7ACB (projectId,code) \u552F\u4E00\u7D22\u5F15\uFF0C\u8BF7\u5148\u4EBA\u5DE5\u5F52\u5E76\uFF1A${detail}`);
|
|
2899
|
+
}
|
|
2900
|
+
await templates.createIndex(
|
|
2901
|
+
{ projectId: 1, code: 1 },
|
|
2902
|
+
{ unique: true, partialFilterExpression: { code: { $type: "string" } } }
|
|
2903
|
+
);
|
|
1809
2904
|
await db.collection("agents").createIndex({ model: 1 });
|
|
1810
2905
|
await migrateNameUniquenessIndexes(db);
|
|
1811
2906
|
}
|
|
@@ -1859,7 +2954,7 @@ async function startServer(config) {
|
|
|
1859
2954
|
});
|
|
1860
2955
|
await ensureCollections(client);
|
|
1861
2956
|
const webRoot = resolveWebDistRoot();
|
|
1862
|
-
const app = createApp(client, { webRoot });
|
|
2957
|
+
const app = createApp(client, { webRoot, authEnabled: config.auth.enabled });
|
|
1863
2958
|
let port = config.port;
|
|
1864
2959
|
const server = serve({ fetch: app.fetch, hostname: config.host, port: config.port }, (info) => {
|
|
1865
2960
|
port = info.port;
|
|
@@ -1876,6 +2971,7 @@ async function startServer(config) {
|
|
|
1876
2971
|
}
|
|
1877
2972
|
|
|
1878
2973
|
export {
|
|
2974
|
+
ADMIN_ONLY_ROUTES,
|
|
1879
2975
|
resolveWebDistRoot,
|
|
1880
2976
|
createStaticRoutes,
|
|
1881
2977
|
createApp,
|