@plandesk/api 0.20.0 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-control.d.ts +150 -0
- package/dist/access-control.js +63 -0
- package/dist/agent-keys.d.ts +81 -0
- package/dist/agent-keys.js +181 -0
- package/dist/auth-context.d.ts +60 -0
- package/dist/auth-context.js +41 -0
- package/dist/auth.d.ts +33 -0
- package/dist/auth.js +357 -0
- package/dist/better-auth.d.ts +66 -0
- package/dist/better-auth.js +106 -0
- package/dist/github.d.ts +57 -0
- package/dist/github.js +115 -0
- package/dist/hosted-auth.d.ts +23 -0
- package/dist/hosted-auth.js +49 -0
- package/dist/identity.d.ts +38 -0
- package/dist/identity.js +163 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +12 -2
- package/dist/invitations.d.ts +94 -0
- package/dist/invitations.js +211 -0
- package/dist/organizations.d.ts +32 -0
- package/dist/organizations.js +63 -0
- package/dist/permissions.d.ts +19 -0
- package/dist/permissions.js +42 -0
- package/dist/projection.d.ts +1 -1
- package/dist/projection.js +5 -5
- package/dist/routes/agent-runs.js +3 -3
- package/dist/routes/artifacts.js +6 -6
- package/dist/routes/auth.d.ts +13 -0
- package/dist/routes/auth.js +83 -0
- package/dist/routes/canvas.js +5 -5
- package/dist/routes/comments.js +11 -11
- package/dist/routes/documents.js +10 -10
- package/dist/routes/folders.js +8 -8
- package/dist/routes/goals.js +11 -11
- package/dist/routes/notes.js +8 -8
- package/dist/routes/orgs.d.ts +17 -0
- package/dist/routes/orgs.js +241 -0
- package/dist/routes/projects.js +13 -13
- package/dist/routes/shares.js +92 -3
- package/dist/routes/submissions.js +7 -8
- package/dist/routes/tags.js +6 -6
- package/dist/routes/tasks.js +14 -3
- package/dist/routes/tokens.js +4 -4
- package/dist/serialize.d.ts +3 -3
- package/dist/server.d.ts +20 -2
- package/dist/server.js +57 -9
- package/dist/services/agent-runs.d.ts +13 -14
- package/dist/services/agent-runs.js +39 -41
- package/dist/services/artifacts.d.ts +6 -7
- package/dist/services/artifacts.js +30 -26
- package/dist/services/canvas.d.ts +11 -12
- package/dist/services/canvas.js +58 -41
- package/dist/services/comments.d.ts +11 -12
- package/dist/services/comments.js +44 -64
- package/dist/services/documents.d.ts +10 -11
- package/dist/services/documents.js +74 -55
- package/dist/services/files.d.ts +2 -1
- package/dist/services/files.js +13 -5
- package/dist/services/folders.d.ts +7 -8
- package/dist/services/folders.js +45 -33
- package/dist/services/goals.d.ts +24 -25
- package/dist/services/goals.js +60 -53
- package/dist/services/index.d.ts +2 -5
- package/dist/services/index.js +15 -20
- package/dist/services/notes.d.ts +7 -8
- package/dist/services/notes.js +35 -23
- package/dist/services/org-scope.d.ts +22 -0
- package/dist/services/org-scope.js +38 -0
- package/dist/services/projects.d.ts +12 -13
- package/dist/services/projects.js +90 -58
- package/dist/services/scope.d.ts +12 -0
- package/dist/services/scope.js +29 -0
- package/dist/services/share.d.ts +74 -10
- package/dist/services/share.js +213 -23
- package/dist/services/sync.d.ts +7 -29
- package/dist/services/sync.js +37 -142
- package/dist/services/tags.d.ts +6 -7
- package/dist/services/tags.js +34 -22
- package/dist/services/tasks.d.ts +24 -17
- package/dist/services/tasks.js +97 -58
- package/dist/services/tokens.d.ts +6 -5
- package/dist/services/tokens.js +13 -6
- package/dist/session.d.ts +5 -0
- package/dist/session.js +8 -0
- package/dist/storage/local.js +16 -10
- package/dist/storage/s3.js +11 -6
- package/dist/test-ba-org.d.ts +16 -0
- package/dist/test-ba-org.js +35 -0
- package/dist/test-helpers.d.ts +21 -5
- package/dist/test-helpers.js +45 -8
- package/dist/vercel.d.ts +3 -0
- package/dist/vercel.js +62 -0
- package/dist/worker.d.ts +44 -0
- package/dist/worker.js +101 -0
- package/package.json +8 -5
- package/web/assets/index-D29SyYFS.css +2 -0
- package/web/assets/index-tBJ_K3ur.js +358 -0
- package/web/index.html +2 -2
- package/web/assets/index-CeBvZ-aq.css +0 -2
- package/web/assets/index-CuRAxImC.js +0 -358
package/dist/routes/shares.js
CHANGED
|
@@ -26,7 +26,7 @@ export function createSharesRouter(shareService) {
|
|
|
26
26
|
return c.json({ error: 'invalid_expires' }, 400);
|
|
27
27
|
}
|
|
28
28
|
const origin = new URL(c.req.url).origin;
|
|
29
|
-
const result = shareService.createResourceShare({ resource: { kind, id }, expiresAt: resolveExpiresAt(body.expires) }, origin);
|
|
29
|
+
const result = await shareService.createResourceShare({ resource: { kind, id }, expiresAt: resolveExpiresAt(body.expires) }, origin);
|
|
30
30
|
if (!result) {
|
|
31
31
|
return c.json({ error: 'not_found' }, 404);
|
|
32
32
|
}
|
|
@@ -36,14 +36,14 @@ export function createSharesRouter(shareService) {
|
|
|
36
36
|
router.post('/documents/:id/share', createShareHandler('document'));
|
|
37
37
|
// Hono doesn't match a literal `.md` suffix inside a param, so the route
|
|
38
38
|
// takes the raw segment and the handler enforces + strips the extension.
|
|
39
|
-
router.get('/share/:tokenWithExt', (c) => {
|
|
39
|
+
router.get('/share/:tokenWithExt', async (c) => {
|
|
40
40
|
const raw = c.req.param('tokenWithExt');
|
|
41
41
|
if (!raw.endsWith('.md')) {
|
|
42
42
|
return c.notFound();
|
|
43
43
|
}
|
|
44
44
|
const token = raw.slice(0, -'.md'.length);
|
|
45
45
|
const origin = new URL(c.req.url).origin;
|
|
46
|
-
const result = shareService.getResourceMarkdown(token, origin);
|
|
46
|
+
const result = await shareService.getResourceMarkdown(token, origin);
|
|
47
47
|
if (result.status === 'not_found') {
|
|
48
48
|
return c.json({ error: 'not_found' }, 404);
|
|
49
49
|
}
|
|
@@ -52,6 +52,95 @@ export function createSharesRouter(shareService) {
|
|
|
52
52
|
}
|
|
53
53
|
return c.body(result.markdown, 200, { 'Content-Type': 'text/markdown; charset=utf-8' });
|
|
54
54
|
});
|
|
55
|
+
// Pre-join: audience name + mode so the portal can render the join gate.
|
|
56
|
+
router.get('/share/:token/meta', async (c) => {
|
|
57
|
+
const token = c.req.param('token');
|
|
58
|
+
const result = await shareService.getShareMeta(token);
|
|
59
|
+
if (result.status === 'not_found') {
|
|
60
|
+
return c.json({ error: 'not_found' }, 404);
|
|
61
|
+
}
|
|
62
|
+
return c.json({ audience_name: result.audienceName, mode: result.mode });
|
|
63
|
+
});
|
|
64
|
+
// Named join: gate by invite allow-list when mode=invite; mint guest session.
|
|
65
|
+
router.post('/share/:token/join', async (c) => {
|
|
66
|
+
const token = c.req.param('token');
|
|
67
|
+
let body;
|
|
68
|
+
try {
|
|
69
|
+
body = await c.req.json();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return c.json({ error: 'invalid_json' }, 400);
|
|
73
|
+
}
|
|
74
|
+
const result = await shareService.joinShare(token, {
|
|
75
|
+
name: body.name ?? '',
|
|
76
|
+
email: body.email,
|
|
77
|
+
});
|
|
78
|
+
if (result.status === 'unauthorized') {
|
|
79
|
+
return c.json({ error: 'unauthorized' }, 401);
|
|
80
|
+
}
|
|
81
|
+
if (result.status === 'name_required') {
|
|
82
|
+
return c.json({ error: 'name_required' }, 400);
|
|
83
|
+
}
|
|
84
|
+
if (result.status === 'email_not_invited') {
|
|
85
|
+
return c.json({ error: 'email_not_invited' }, 403);
|
|
86
|
+
}
|
|
87
|
+
return c.json({
|
|
88
|
+
session_token: result.sessionToken,
|
|
89
|
+
participant: result.participant,
|
|
90
|
+
share: {
|
|
91
|
+
audience_name: result.share.audienceName,
|
|
92
|
+
permissions: result.share.permissions,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
// Guest-session-gated portal view (middleware sets AuthContext kind guest).
|
|
97
|
+
// Live projection per request; uniform 404 on any failure shape.
|
|
98
|
+
router.get('/share/:token/view', async (c) => {
|
|
99
|
+
const token = c.req.param('token');
|
|
100
|
+
const view = await shareService.getClientView(token);
|
|
101
|
+
if (view === undefined) {
|
|
102
|
+
return c.json({ error: 'not_found' }, 404);
|
|
103
|
+
}
|
|
104
|
+
return c.json(view);
|
|
105
|
+
});
|
|
106
|
+
// Guest moderated inbox — same guest session as view (BA6b single-server).
|
|
107
|
+
router.post('/share/:token/submissions', async (c) => {
|
|
108
|
+
const token = c.req.param('token');
|
|
109
|
+
let body;
|
|
110
|
+
try {
|
|
111
|
+
body = await c.req.json();
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return c.json({ error: 'invalid_json' }, 400);
|
|
115
|
+
}
|
|
116
|
+
const result = await shareService.submitIssue(token, {
|
|
117
|
+
title: body.title ?? '',
|
|
118
|
+
body: body.body,
|
|
119
|
+
severity: body.severity,
|
|
120
|
+
task_ref: body.task_ref,
|
|
121
|
+
});
|
|
122
|
+
if (result.status === 'unauthorized') {
|
|
123
|
+
return c.json({ error: 'unauthorized' }, 401);
|
|
124
|
+
}
|
|
125
|
+
if (result.status === 'submit_not_permitted') {
|
|
126
|
+
return c.json({ error: 'submit_not_permitted' }, 403);
|
|
127
|
+
}
|
|
128
|
+
if (result.status === 'title_required') {
|
|
129
|
+
return c.json({ error: 'title_required' }, 400);
|
|
130
|
+
}
|
|
131
|
+
if (result.status === 'rate_limited') {
|
|
132
|
+
return c.json({ error: 'rate_limited' }, 429);
|
|
133
|
+
}
|
|
134
|
+
return c.json({ submission: result.submission }, 201);
|
|
135
|
+
});
|
|
136
|
+
router.get('/share/:token/submissions', async (c) => {
|
|
137
|
+
const token = c.req.param('token');
|
|
138
|
+
const result = await shareService.listMySubmissions(token);
|
|
139
|
+
if (result.status === 'unauthorized') {
|
|
140
|
+
return c.json({ error: 'unauthorized' }, 401);
|
|
141
|
+
}
|
|
142
|
+
return c.json(result.submissions);
|
|
143
|
+
});
|
|
55
144
|
return router;
|
|
56
145
|
}
|
|
57
146
|
//# sourceMappingURL=shares.js.map
|
|
@@ -6,16 +6,16 @@ function isSubmissionStatus(value) {
|
|
|
6
6
|
}
|
|
7
7
|
export function createSubmissionsRouter(syncService, projectService) {
|
|
8
8
|
const router = new Hono();
|
|
9
|
-
router.get('/projects/:id/submissions', (c) => {
|
|
9
|
+
router.get('/projects/:id/submissions', async (c) => {
|
|
10
10
|
const projectId = c.req.param('id');
|
|
11
|
-
if (!projectService.get(projectId)) {
|
|
11
|
+
if (!(await projectService.get(projectId))) {
|
|
12
12
|
return c.json({ error: 'not_found' }, 404);
|
|
13
13
|
}
|
|
14
14
|
const statusParam = c.req.query('status') ?? 'pending';
|
|
15
15
|
if (!isSubmissionStatus(statusParam)) {
|
|
16
16
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
17
17
|
}
|
|
18
|
-
return c.json(syncService.listTriage(projectId, statusParam));
|
|
18
|
+
return c.json(await syncService.listTriage(projectId, statusParam));
|
|
19
19
|
});
|
|
20
20
|
router.post('/submissions/:id/triage', async (c) => {
|
|
21
21
|
const submissionId = c.req.param('id');
|
|
@@ -23,14 +23,13 @@ export function createSubmissionsRouter(syncService, projectService) {
|
|
|
23
23
|
if (body.action !== 'accept' && body.action !== 'reject') {
|
|
24
24
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
25
25
|
}
|
|
26
|
-
const submission = syncService.getSubmission(submissionId);
|
|
26
|
+
const submission = await syncService.getSubmission(submissionId);
|
|
27
27
|
if (submission === undefined) {
|
|
28
28
|
return c.json({ error: 'not_found' }, 404);
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
30
|
+
// Optional remote: single-server guest submit has no cross-server ack target.
|
|
31
|
+
// Legacy local→remote pull still stores a remote when present.
|
|
32
|
+
const remote = await syncService.getRemote(submission.project_id);
|
|
34
33
|
try {
|
|
35
34
|
// Triage never creates a `todo` task — the scope->todo release is the human's own
|
|
36
35
|
// board action, enforced in syncService.triage(). A merge (link_task_id) links to
|
package/dist/routes/tags.js
CHANGED
|
@@ -2,8 +2,8 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import { InvalidTagError } from '../services/tags.js';
|
|
3
3
|
export function createTagsRouter(tagService) {
|
|
4
4
|
const router = new Hono();
|
|
5
|
-
router.get('/projects/:id/tags', (c) => {
|
|
6
|
-
const tags = tagService.list(c.req.param('id'));
|
|
5
|
+
router.get('/projects/:id/tags', async (c) => {
|
|
6
|
+
const tags = await tagService.list(c.req.param('id'));
|
|
7
7
|
if (!tags) {
|
|
8
8
|
return c.json({ error: 'not_found' }, 404);
|
|
9
9
|
}
|
|
@@ -15,7 +15,7 @@ export function createTagsRouter(tagService) {
|
|
|
15
15
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
16
16
|
}
|
|
17
17
|
try {
|
|
18
|
-
const tag = tagService.create(c.req.param('id'), {
|
|
18
|
+
const tag = await tagService.create(c.req.param('id'), {
|
|
19
19
|
name: body.name,
|
|
20
20
|
color: body.color,
|
|
21
21
|
});
|
|
@@ -34,7 +34,7 @@ export function createTagsRouter(tagService) {
|
|
|
34
34
|
router.patch('/tags/:id', async (c) => {
|
|
35
35
|
const body = await c.req.json();
|
|
36
36
|
try {
|
|
37
|
-
const tag = tagService.update(c.req.param('id'), {
|
|
37
|
+
const tag = await tagService.update(c.req.param('id'), {
|
|
38
38
|
...(body.name !== undefined ? { name: body.name } : {}),
|
|
39
39
|
...(body.color !== undefined ? { color: body.color } : {}),
|
|
40
40
|
});
|
|
@@ -50,8 +50,8 @@ export function createTagsRouter(tagService) {
|
|
|
50
50
|
throw error;
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
|
-
router.delete('/tags/:id', (c) => {
|
|
54
|
-
const deleted = tagService.delete(c.req.param('id'));
|
|
53
|
+
router.delete('/tags/:id', async (c) => {
|
|
54
|
+
const deleted = await tagService.delete(c.req.param('id'));
|
|
55
55
|
if (!deleted) {
|
|
56
56
|
return c.json({ error: 'not_found' }, 404);
|
|
57
57
|
}
|
package/dist/routes/tasks.js
CHANGED
|
@@ -15,7 +15,7 @@ export function createTasksRouter(taskService) {
|
|
|
15
15
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
16
16
|
}
|
|
17
17
|
try {
|
|
18
|
-
const task = taskService.update(c.req.param('id'), {
|
|
18
|
+
const task = await taskService.update(c.req.param('id'), {
|
|
19
19
|
...(body.label !== undefined ? { label: body.label } : {}),
|
|
20
20
|
...(body.status !== undefined ? { status: body.status } : {}),
|
|
21
21
|
...(body.description !== undefined ? { description: body.description } : {}),
|
|
@@ -35,13 +35,24 @@ export function createTasksRouter(taskService) {
|
|
|
35
35
|
throw error;
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
|
-
router.delete('/tasks/:id', (c) => {
|
|
39
|
-
const deleted = taskService.delete(c.req.param('id'));
|
|
38
|
+
router.delete('/tasks/:id', async (c) => {
|
|
39
|
+
const deleted = await taskService.delete(c.req.param('id'));
|
|
40
40
|
if (!deleted) {
|
|
41
41
|
return c.json({ error: 'not_found' }, 404);
|
|
42
42
|
}
|
|
43
43
|
return c.body(null, 204);
|
|
44
44
|
});
|
|
45
|
+
router.post('/tasks/:id/claim', async (c) => {
|
|
46
|
+
const body = await c.req.json();
|
|
47
|
+
if (typeof body.agent_ref !== 'string' || body.agent_ref.length === 0) {
|
|
48
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
49
|
+
}
|
|
50
|
+
const result = await taskService.claim(c.req.param('id'), body.agent_ref);
|
|
51
|
+
if (!result.claimed) {
|
|
52
|
+
return c.json(result, 409);
|
|
53
|
+
}
|
|
54
|
+
return c.json(result);
|
|
55
|
+
});
|
|
45
56
|
return router;
|
|
46
57
|
}
|
|
47
58
|
//# sourceMappingURL=tasks.js.map
|
package/dist/routes/tokens.js
CHANGED
|
@@ -6,12 +6,12 @@ export function createTokensRouter(tokenService) {
|
|
|
6
6
|
if (typeof body.name !== 'string' || body.name.trim() === '') {
|
|
7
7
|
return c.json({ error: 'invalid_argument' }, 400);
|
|
8
8
|
}
|
|
9
|
-
const created = tokenService.create(body.name.trim());
|
|
9
|
+
const created = await tokenService.create(body.name.trim());
|
|
10
10
|
return c.json(created, 201);
|
|
11
11
|
});
|
|
12
|
-
router.get('/mcp-tokens', (c) => c.json(tokenService.list()));
|
|
13
|
-
router.delete('/mcp-tokens/:id', (c) => {
|
|
14
|
-
const revoked = tokenService.revoke(c.req.param('id'));
|
|
12
|
+
router.get('/mcp-tokens', async (c) => c.json(await tokenService.list()));
|
|
13
|
+
router.delete('/mcp-tokens/:id', async (c) => {
|
|
14
|
+
const revoked = await tokenService.revoke(c.req.param('id'));
|
|
15
15
|
if (!revoked) {
|
|
16
16
|
return c.json({ error: 'not_found' }, 404);
|
|
17
17
|
}
|
package/dist/serialize.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export declare function serializeGoal(goal: Goal): {
|
|
|
33
33
|
id: string;
|
|
34
34
|
project_id: string;
|
|
35
35
|
objective: string;
|
|
36
|
-
status: "
|
|
36
|
+
status: "paused" | "active" | "complete" | "blocked";
|
|
37
37
|
verification_surface: string | null;
|
|
38
38
|
constraints: string | null;
|
|
39
39
|
boundaries: string | null;
|
|
@@ -55,7 +55,7 @@ export declare function serializeTask(task: Task, tags?: Tag[]): {
|
|
|
55
55
|
project_id: string;
|
|
56
56
|
goal_id: string;
|
|
57
57
|
label: string;
|
|
58
|
-
status: "
|
|
58
|
+
status: "in_progress" | "done" | "todo" | "scope" | "backlog";
|
|
59
59
|
description: string | null;
|
|
60
60
|
x: number;
|
|
61
61
|
y: number;
|
|
@@ -150,7 +150,7 @@ export declare function serializeEdge(edge: Edge): {
|
|
|
150
150
|
export declare function serializeAgentRun(run: AgentRun): {
|
|
151
151
|
id: string;
|
|
152
152
|
project_id: string;
|
|
153
|
-
status: "
|
|
153
|
+
status: "completed" | "running" | "failed";
|
|
154
154
|
label: string | null;
|
|
155
155
|
started_at: string;
|
|
156
156
|
completed_at: string | null;
|
package/dist/server.d.ts
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import type { Db } from '@plandesk/db';
|
|
3
|
-
import type {
|
|
3
|
+
import type { GithubConfig } from './github.js';
|
|
4
|
+
import type { BetterAuthInstance } from './better-auth.js';
|
|
4
5
|
import { type Services } from './services/index.js';
|
|
5
6
|
export type AppDeps = {
|
|
6
7
|
db: Db;
|
|
7
|
-
eventBus?: EventBus;
|
|
8
8
|
services?: Services;
|
|
9
9
|
mcp?: Hono;
|
|
10
10
|
authPassword?: string;
|
|
11
|
+
/** Server bind host — loopback default-org only when this is loopback. Default 127.0.0.1. */
|
|
12
|
+
bindHost?: string;
|
|
13
|
+
/**
|
|
14
|
+
* GitHub app for better-auth social sign-in. Omit it and the instance simply
|
|
15
|
+
* has no GitHub sign-in; /auth/methods reports githubEnabled:false.
|
|
16
|
+
* Self-hosting must never require registering an app (REQ-20).
|
|
17
|
+
*/
|
|
18
|
+
github?: GithubConfig;
|
|
19
|
+
/**
|
|
20
|
+
* better-auth (GitHub sign-in + session + apiKey). Omit and /api/auth/* 404s;
|
|
21
|
+
* when set, org middleware recognizes better-auth session cookies and API keys.
|
|
22
|
+
*/
|
|
23
|
+
betterAuth?: {
|
|
24
|
+
secret: string;
|
|
25
|
+
baseURL: string;
|
|
26
|
+
};
|
|
27
|
+
/** Reuse a better-auth instance owned by an edge entry across requests. */
|
|
28
|
+
betterAuthInstance?: BetterAuthInstance;
|
|
11
29
|
};
|
|
12
30
|
export declare function createApp(deps: AppDeps): Hono;
|
|
13
31
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { createAuthMiddleware } from './auth.js';
|
|
2
|
+
import { createAuthMiddleware, createOrgAuthMiddleware, createWriteGuardMiddleware, } from './auth.js';
|
|
3
3
|
import { healthRouter } from './routes/health.js';
|
|
4
|
+
import { createAuthRouter } from './routes/auth.js';
|
|
5
|
+
import { createBetterAuth } from './better-auth.js';
|
|
4
6
|
import { createProjectsRouter } from './routes/projects.js';
|
|
5
7
|
import { createTasksRouter } from './routes/tasks.js';
|
|
6
8
|
import { createTagsRouter } from './routes/tags.js';
|
|
@@ -12,21 +14,60 @@ import { createFilesRouter } from './routes/files.js';
|
|
|
12
14
|
import { createFoldersRouter } from './routes/folders.js';
|
|
13
15
|
import { createNotesRouter } from './routes/notes.js';
|
|
14
16
|
import { createSharesRouter } from './routes/shares.js';
|
|
15
|
-
import { createEventsRouter } from './routes/events.js';
|
|
16
|
-
import { createTokensRouter } from './routes/tokens.js';
|
|
17
17
|
import { createAgentRunsRouter } from './routes/agent-runs.js';
|
|
18
18
|
import { createGoalsRouter } from './routes/goals.js';
|
|
19
19
|
import { createSubmissionsRouter } from './routes/submissions.js';
|
|
20
|
-
import {
|
|
20
|
+
import { createOrgsRouter } from './routes/orgs.js';
|
|
21
21
|
import { createServices } from './services/index.js';
|
|
22
|
+
import { ProjectNotInOrgError } from './services/scope.js';
|
|
23
|
+
import { ReadOnlyTokenError } from './auth-context.js';
|
|
24
|
+
import { PermissionDeniedError } from './permissions.js';
|
|
22
25
|
export function createApp(deps) {
|
|
23
|
-
const services = deps.services ?? createServices({ db: deps.db
|
|
24
|
-
const {
|
|
26
|
+
const services = deps.services ?? createServices({ db: deps.db });
|
|
27
|
+
const { projectService, goalService, taskService, tagService, canvasService, documentService, folderService, noteService, commentService, agentRunService, syncService, fileService, artifactService, shareService, } = services;
|
|
28
|
+
const bindHost = deps.bindHost ?? '127.0.0.1';
|
|
25
29
|
const app = new Hono();
|
|
30
|
+
// Create better-auth before org middleware so the same instance is recognized
|
|
31
|
+
// by AuthContext and mounted at /api/auth/* (BA4a session recognition).
|
|
32
|
+
const betterAuthInstance = deps.betterAuthInstance ??
|
|
33
|
+
(deps.betterAuth !== undefined
|
|
34
|
+
? createBetterAuth({
|
|
35
|
+
client: deps.db.$client,
|
|
36
|
+
db: deps.db,
|
|
37
|
+
secret: deps.betterAuth.secret,
|
|
38
|
+
baseURL: deps.betterAuth.baseURL,
|
|
39
|
+
github: deps.github,
|
|
40
|
+
})
|
|
41
|
+
: undefined);
|
|
42
|
+
// Always-on org resolution (better-auth apiKey/session, loopback, or guest).
|
|
43
|
+
app.use('*', createOrgAuthMiddleware({
|
|
44
|
+
db: deps.db,
|
|
45
|
+
bindHost,
|
|
46
|
+
betterAuth: betterAuthInstance,
|
|
47
|
+
}));
|
|
48
|
+
app.use('*', createWriteGuardMiddleware());
|
|
26
49
|
if (deps.authPassword !== undefined && deps.authPassword.length > 0) {
|
|
27
50
|
app.use('*', createAuthMiddleware(deps.authPassword));
|
|
28
51
|
}
|
|
52
|
+
app.onError((err, c) => {
|
|
53
|
+
if (err instanceof ProjectNotInOrgError) {
|
|
54
|
+
return c.json({ error: 'not_found' }, 404);
|
|
55
|
+
}
|
|
56
|
+
if (err instanceof ReadOnlyTokenError || err instanceof PermissionDeniedError) {
|
|
57
|
+
return c.json({ error: 'forbidden' }, 403);
|
|
58
|
+
}
|
|
59
|
+
throw err;
|
|
60
|
+
});
|
|
29
61
|
app.route('/api/v1', healthRouter);
|
|
62
|
+
app.route('/api/v1', createAuthRouter({
|
|
63
|
+
db: deps.db,
|
|
64
|
+
github: deps.github,
|
|
65
|
+
betterAuth: betterAuthInstance,
|
|
66
|
+
}));
|
|
67
|
+
app.route('/api/v1', createOrgsRouter(deps.db, {
|
|
68
|
+
betterAuth: betterAuthInstance,
|
|
69
|
+
baseURL: deps.betterAuth?.baseURL,
|
|
70
|
+
}));
|
|
30
71
|
app.route('/api/v1', createProjectsRouter(projectService, taskService));
|
|
31
72
|
app.route('/api/v1', createGoalsRouter(goalService));
|
|
32
73
|
app.route('/api/v1', createTasksRouter(taskService));
|
|
@@ -39,10 +80,8 @@ export function createApp(deps) {
|
|
|
39
80
|
app.route('/api/v1', createNotesRouter(noteService));
|
|
40
81
|
app.route('/api/v1', createSharesRouter(shareService));
|
|
41
82
|
app.route('/api/v1', createCommentsRouter(commentService));
|
|
42
|
-
app.route('/api/v1', createTokensRouter(tokenService));
|
|
43
83
|
app.route('/api/v1', createAgentRunsRouter(agentRunService));
|
|
44
84
|
app.route('/api/v1', createSubmissionsRouter(syncService, projectService));
|
|
45
|
-
app.route('/api/v1', createEventsRouter(eventBus));
|
|
46
85
|
// Mount the MCP router BEFORE the static/SPA handler. The MCP transport uses
|
|
47
86
|
// GET /mcp/ for its server->client SSE stream; if the SPA catch-all (app.get('*'))
|
|
48
87
|
// is registered first it shadows that GET and the stream never reaches the
|
|
@@ -50,7 +89,16 @@ export function createApp(deps) {
|
|
|
50
89
|
if (deps.mcp) {
|
|
51
90
|
app.route('/mcp', deps.mcp);
|
|
52
91
|
}
|
|
53
|
-
|
|
92
|
+
// Mounted at /api/auth/*, one segment away from the existing /api/v1/auth/*
|
|
93
|
+
// (routes/auth.ts) so it can never shadow it. /api/auth/* is public so a
|
|
94
|
+
// stranger can reach sign-in without a plandesk credential first (BA4a).
|
|
95
|
+
if (betterAuthInstance) {
|
|
96
|
+
app.on(['GET', 'POST'], '/api/auth/*', (c) => betterAuthInstance.handler(c.req.raw));
|
|
97
|
+
}
|
|
98
|
+
// SPA static files are NOT mounted here. Node mounts them via `mountStatic`
|
|
99
|
+
// in the serve entry; Workers/Vercel serve the SPA through platform assets.
|
|
100
|
+
// Keeping static out of createApp prevents node:fs and @hono/node-server
|
|
101
|
+
// from entering the edge bundle graph.
|
|
54
102
|
app.notFound((c) => c.json({ error: 'not_found' }, 404));
|
|
55
103
|
return app;
|
|
56
104
|
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { type Db } from '@plandesk/db';
|
|
2
|
-
import type { EventBus } from '../events.js';
|
|
3
2
|
import { type PaginationParams } from '../serialize.js';
|
|
4
|
-
|
|
3
|
+
import { type OrgScopedDeps } from './org-scope.js';
|
|
4
|
+
export type AgentRunServiceDeps = OrgScopedDeps & {
|
|
5
5
|
db: Db;
|
|
6
|
-
eventBus: EventBus;
|
|
7
6
|
};
|
|
8
7
|
export declare class InvalidAgentRunError extends Error {
|
|
9
8
|
constructor(message: string);
|
|
10
9
|
}
|
|
11
10
|
export declare function createAgentRunService(deps: AgentRunServiceDeps): {
|
|
12
|
-
listForProject(projectId: string, pagination?: PaginationParams): {
|
|
11
|
+
listForProject(projectId: string, pagination?: PaginationParams): Promise<{
|
|
13
12
|
events: {
|
|
14
13
|
id: string;
|
|
15
14
|
message: string;
|
|
@@ -17,33 +16,33 @@ export declare function createAgentRunService(deps: AgentRunServiceDeps): {
|
|
|
17
16
|
}[];
|
|
18
17
|
id: string;
|
|
19
18
|
project_id: string;
|
|
20
|
-
status: "
|
|
19
|
+
status: "completed" | "running" | "failed";
|
|
21
20
|
label: string | null;
|
|
22
21
|
started_at: string;
|
|
23
22
|
completed_at: string | null;
|
|
24
|
-
}[] | undefined
|
|
25
|
-
start(projectId: string, label?: string | null): {
|
|
23
|
+
}[] | undefined>;
|
|
24
|
+
start(projectId: string, label?: string | null): Promise<{
|
|
26
25
|
id: string;
|
|
27
26
|
project_id: string;
|
|
28
|
-
status: "
|
|
27
|
+
status: "completed" | "running" | "failed";
|
|
29
28
|
label: string | null;
|
|
30
29
|
started_at: string;
|
|
31
30
|
completed_at: string | null;
|
|
32
|
-
} | undefined
|
|
33
|
-
recordProgress(runId: string, message: string): {
|
|
31
|
+
} | undefined>;
|
|
32
|
+
recordProgress(runId: string, message: string): Promise<{
|
|
34
33
|
id: string;
|
|
35
34
|
run_id: string;
|
|
36
35
|
message: string;
|
|
37
36
|
created_at: string;
|
|
38
|
-
} | undefined
|
|
39
|
-
complete(runId: string, status: "completed" | "failed"): {
|
|
37
|
+
} | undefined>;
|
|
38
|
+
complete(runId: string, status: "completed" | "failed"): Promise<{
|
|
40
39
|
id: string;
|
|
41
40
|
project_id: string;
|
|
42
|
-
status: "
|
|
41
|
+
status: "completed" | "running" | "failed";
|
|
43
42
|
label: string | null;
|
|
44
43
|
started_at: string;
|
|
45
44
|
completed_at: string | null;
|
|
46
|
-
} | undefined
|
|
45
|
+
} | undefined>;
|
|
47
46
|
};
|
|
48
47
|
export type AgentRunService = ReturnType<typeof createAgentRunService>;
|
|
49
48
|
//# sourceMappingURL=agent-runs.d.ts.map
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createAgentRun, createAgentRunEvent, getAgentRun, getProject, listAgentRunEvents, listAgentRuns, updateAgentRunStatus, } from '@plandesk/db';
|
|
2
2
|
import { serializeAgentRun, serializeAgentRunEvent } from '../serialize.js';
|
|
3
|
+
import { assertPermission, resolveOrgId } from './org-scope.js';
|
|
4
|
+
import { assertProjectInOrg, ProjectNotInOrgError } from './scope.js';
|
|
3
5
|
export class InvalidAgentRunError extends Error {
|
|
4
6
|
constructor(message) {
|
|
5
7
|
super(message);
|
|
@@ -8,84 +10,80 @@ export class InvalidAgentRunError extends Error {
|
|
|
8
10
|
}
|
|
9
11
|
const terminalStatuses = new Set(['completed', 'failed']);
|
|
10
12
|
export function createAgentRunService(deps) {
|
|
11
|
-
const { db
|
|
13
|
+
const { db } = deps;
|
|
12
14
|
return {
|
|
13
|
-
listForProject(projectId, pagination = {}) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
async listForProject(projectId, pagination = {}) {
|
|
16
|
+
try {
|
|
17
|
+
await assertProjectInOrg(db, projectId, resolveOrgId(deps));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error instanceof ProjectNotInOrgError) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
throw error;
|
|
17
24
|
}
|
|
18
|
-
const runs = listAgentRuns(db, projectId, pagination)
|
|
19
|
-
.slice()
|
|
20
|
-
.sort((a, b) => {
|
|
25
|
+
const runs = (await listAgentRuns(db, projectId, pagination)).slice().sort((a, b) => {
|
|
21
26
|
const timeDiff = b.startedAt.getTime() - a.startedAt.getTime();
|
|
22
27
|
if (timeDiff !== 0) {
|
|
23
28
|
return timeDiff;
|
|
24
29
|
}
|
|
25
30
|
return b.id.localeCompare(a.id);
|
|
26
31
|
});
|
|
27
|
-
return runs.map((run) => {
|
|
32
|
+
return Promise.all(runs.map(async (run) => {
|
|
28
33
|
const serialized = serializeAgentRun(run);
|
|
29
|
-
const events = listAgentRunEvents(db, run.id).map((event) => {
|
|
30
|
-
const
|
|
34
|
+
const events = (await listAgentRunEvents(db, run.id)).map((event) => {
|
|
35
|
+
const serializedEvent = serializeAgentRunEvent(event);
|
|
31
36
|
return {
|
|
32
|
-
id:
|
|
33
|
-
message:
|
|
34
|
-
created_at:
|
|
37
|
+
id: serializedEvent.id,
|
|
38
|
+
message: serializedEvent.message,
|
|
39
|
+
created_at: serializedEvent.created_at,
|
|
35
40
|
};
|
|
36
41
|
});
|
|
37
42
|
return { ...serialized, events };
|
|
38
|
-
});
|
|
43
|
+
}));
|
|
39
44
|
},
|
|
40
|
-
start(projectId, label) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
async start(projectId, label) {
|
|
46
|
+
assertPermission(deps, 'agent_run', 'create');
|
|
47
|
+
try {
|
|
48
|
+
await assertProjectInOrg(db, projectId, resolveOrgId(deps));
|
|
44
49
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof ProjectNotInOrgError) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
const run = await createAgentRun(db, { projectId, label });
|
|
51
57
|
return serializeAgentRun(run);
|
|
52
58
|
},
|
|
53
|
-
recordProgress(runId, message) {
|
|
54
|
-
|
|
59
|
+
async recordProgress(runId, message) {
|
|
60
|
+
assertPermission(deps, 'agent_run', 'update');
|
|
61
|
+
const run = await getAgentRun(db, runId);
|
|
55
62
|
if (!run) {
|
|
56
63
|
return undefined;
|
|
57
64
|
}
|
|
58
65
|
if (terminalStatuses.has(run.status)) {
|
|
59
66
|
throw new InvalidAgentRunError('Agent run is already complete');
|
|
60
67
|
}
|
|
61
|
-
const event = createAgentRunEvent(db, { runId, message });
|
|
62
|
-
eventBus.emit({
|
|
63
|
-
type: 'agent_run_progress',
|
|
64
|
-
runId,
|
|
65
|
-
projectId: run.projectId,
|
|
66
|
-
});
|
|
68
|
+
const event = await createAgentRunEvent(db, { runId, message });
|
|
67
69
|
return serializeAgentRunEvent(event);
|
|
68
70
|
},
|
|
69
|
-
complete(runId, status) {
|
|
70
|
-
|
|
71
|
+
async complete(runId, status) {
|
|
72
|
+
assertPermission(deps, 'agent_run', 'update');
|
|
73
|
+
const run = await getAgentRun(db, runId);
|
|
71
74
|
if (!run) {
|
|
72
75
|
return undefined;
|
|
73
76
|
}
|
|
74
77
|
if (terminalStatuses.has(run.status)) {
|
|
75
78
|
throw new InvalidAgentRunError('Agent run is already complete');
|
|
76
79
|
}
|
|
77
|
-
const updated = updateAgentRunStatus(db, runId, {
|
|
80
|
+
const updated = await updateAgentRunStatus(db, runId, {
|
|
78
81
|
status,
|
|
79
82
|
completedAt: new Date(),
|
|
80
83
|
});
|
|
81
84
|
if (!updated) {
|
|
82
85
|
return undefined;
|
|
83
86
|
}
|
|
84
|
-
eventBus.emit({
|
|
85
|
-
type: 'agent_run_completed',
|
|
86
|
-
runId,
|
|
87
|
-
projectId: run.projectId,
|
|
88
|
-
});
|
|
89
87
|
return serializeAgentRun(updated);
|
|
90
88
|
},
|
|
91
89
|
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { type ArtifactKind, type Db } from '@plandesk/db';
|
|
2
2
|
import { type SerializedArtifact, type SerializedArtifactSummary } from '../serialize.js';
|
|
3
|
-
import type
|
|
4
|
-
export type ArtifactServiceDeps = {
|
|
3
|
+
import { type OrgScopedDeps } from './org-scope.js';
|
|
4
|
+
export type ArtifactServiceDeps = OrgScopedDeps & {
|
|
5
5
|
db: Db;
|
|
6
|
-
eventBus: EventBus;
|
|
7
6
|
};
|
|
8
7
|
export type CreateArtifactInput = {
|
|
9
8
|
title: string;
|
|
@@ -19,10 +18,10 @@ export declare class InvalidArtifactError extends Error {
|
|
|
19
18
|
constructor(message: string);
|
|
20
19
|
}
|
|
21
20
|
export declare function createArtifactService(deps: ArtifactServiceDeps): {
|
|
22
|
-
listByProject(projectId: string): SerializedArtifactSummary[] | undefined
|
|
23
|
-
create(projectId: string, input: CreateArtifactInput): SerializedArtifact | undefined
|
|
24
|
-
get(id: string): SerializedArtifact | undefined
|
|
25
|
-
update(id: string, input: UpdateArtifactInput): SerializedArtifact | undefined
|
|
21
|
+
listByProject(projectId: string): Promise<SerializedArtifactSummary[] | undefined>;
|
|
22
|
+
create(projectId: string, input: CreateArtifactInput): Promise<SerializedArtifact | undefined>;
|
|
23
|
+
get(id: string): Promise<SerializedArtifact | undefined>;
|
|
24
|
+
update(id: string, input: UpdateArtifactInput): Promise<SerializedArtifact | undefined>;
|
|
26
25
|
};
|
|
27
26
|
export type ArtifactService = ReturnType<typeof createArtifactService>;
|
|
28
27
|
//# sourceMappingURL=artifacts.d.ts.map
|