@imenam/mcp-organizer 1.0.3 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -20
- package/dist/shared/schemas/admin-api.schema.d.ts +101 -0
- package/dist/shared/schemas/admin-api.schema.js +39 -0
- package/dist/shared/schemas/agent-api.schema.d.ts +2 -0
- package/dist/shared/schemas/agent-api.schema.js +4 -0
- package/dist/shared/schemas/agent-tools.schema.d.ts +34 -0
- package/dist/shared/schemas/agent-tools.schema.js +2 -0
- package/dist/shared/schemas/api.schema.d.ts +2 -0
- package/dist/shared/schemas/api.schema.js +2 -0
- package/dist/shared/schemas/entities.schema.d.ts +27 -1
- package/dist/shared/schemas/entities.schema.js +21 -2
- package/dist/shared/schemas/notifications-api.schema.d.ts +31 -0
- package/dist/shared/schemas/notifications-api.schema.js +22 -1
- package/dist/src/agent-api/requests.js +2 -1
- package/dist/src/cli/set-admin.d.ts +1 -0
- package/dist/src/cli/set-admin.js +45 -0
- package/dist/src/core/accounts.js +2 -1
- package/dist/src/core/admin.d.ts +24 -0
- package/dist/src/core/admin.js +85 -0
- package/dist/src/core/agent-tokens.d.ts +9 -1
- package/dist/src/core/agent-tokens.js +25 -2
- package/dist/src/core/agent.d.ts +5 -0
- package/dist/src/core/agent.js +23 -0
- package/dist/src/core/migrations.js +369 -344
- package/dist/src/core/notifications.d.ts +7 -1
- package/dist/src/core/notifications.js +32 -1
- package/dist/src/core/requests.d.ts +1 -1
- package/dist/src/core/requests.js +4 -4
- package/dist/src/core/store.d.ts +38 -2
- package/dist/src/core/store.js +161 -38
- package/dist/src/gui-api.d.ts +6 -0
- package/dist/src/gui-api.js +25 -4
- package/dist/src/index.js +8 -1
- package/dist/src/mcp/start.d.ts +1 -1
- package/dist/src/mcp/start.js +1 -1
- package/dist/src/mcp/tools.js +1 -1
- package/dist/src/server/runner.d.ts +51 -0
- package/dist/src/server/runner.js +222 -0
- package/dist/src/server/start.js +8 -0
- package/gui/dist/assets/index-DuoSNpBr.js +70 -0
- package/gui/dist/assets/index-duxL0fvO.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -2
- package/gui/dist/assets/index-BVN0agCc.css +0 -1
- package/gui/dist/assets/index-DI1_kr81.js +0 -70
package/dist/src/core/store.js
CHANGED
|
@@ -4,7 +4,7 @@ import { positionBetween } from "../../shared/logic/positions.js";
|
|
|
4
4
|
import { newId } from "./ids.js";
|
|
5
5
|
import { WatcherRegistry } from "./watchers.js";
|
|
6
6
|
import { MAX_TASK_DEPTH, } from "../../shared/schemas/enums.schema.js";
|
|
7
|
-
import { AccessLinkSchema, AgentActionSchema, AgentTokenSchema, AgentRequestSchema, AttachmentSchema, ChangeSchema, ChecklistItemSchema, CommentSchema, EntityVersionSchema, IdeaSchema, IdeaSnapshotSchema, InvitationSchema, MembershipSchema, NotificationSchema, SessionSchema, SpaceSchema, TagSchema, TaskSchema, TaskSnapshotSchema, TestCheckpointSchema, TestResultSchema, TestReviewSchema, UserSchema, UserSettingsSchema, } from "../../shared/schemas/entities.schema.js";
|
|
7
|
+
import { AccessLinkSchema, AgentActionSchema, AgentTokenSchema, AgentRequestSchema, AttachmentSchema, ChangeSchema, ChecklistItemSchema, CommentSchema, EntityVersionSchema, IdeaSchema, IdeaSnapshotSchema, InvitationSchema, MembershipSchema, NotificationSchema, SessionSchema, SpaceSchema, TagSchema, TaskSchema, TaskSnapshotSchema, TestCheckpointSchema, TestResultSchema, TestReviewSchema, UserSchema, UserSettingsSchema, SpaceNotificationSettingsSchema, } from "../../shared/schemas/entities.schema.js";
|
|
8
8
|
/** Valide `value` avec `schema` ; une donnée invalide est une erreur métier qui liste les champs fautifs. */
|
|
9
9
|
function validate(schema, value, what) {
|
|
10
10
|
const parsed = schema.safeParse(value);
|
|
@@ -24,9 +24,25 @@ function userFromRow(row) {
|
|
|
24
24
|
displayName: row.display_name,
|
|
25
25
|
passwordHash: row.password_hash,
|
|
26
26
|
status: row.status,
|
|
27
|
+
isAdmin: bool(row.is_admin),
|
|
27
28
|
createdAt: row.created_at,
|
|
28
29
|
});
|
|
29
30
|
}
|
|
31
|
+
function spaceNotificationSettingsFromRow(row) {
|
|
32
|
+
return SpaceNotificationSettingsSchema.parse({
|
|
33
|
+
userId: row.user_id,
|
|
34
|
+
spaceId: row.space_id,
|
|
35
|
+
notifyAssignApp: bool(row.notify_assign_app),
|
|
36
|
+
notifyAssignEmail: bool(row.notify_assign_email),
|
|
37
|
+
notifyCommentApp: bool(row.notify_comment_app),
|
|
38
|
+
notifyCommentEmail: bool(row.notify_comment_email),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Les quatre préférences de notification d'un objet qui les porte. */
|
|
42
|
+
function preferencesOf(source) {
|
|
43
|
+
const { notifyAssignApp, notifyAssignEmail, notifyCommentApp, notifyCommentEmail } = source;
|
|
44
|
+
return { notifyAssignApp, notifyAssignEmail, notifyCommentApp, notifyCommentEmail };
|
|
45
|
+
}
|
|
30
46
|
function settingsFromRow(row) {
|
|
31
47
|
return UserSettingsSchema.parse({
|
|
32
48
|
userId: row.user_id,
|
|
@@ -34,6 +50,7 @@ function settingsFromRow(row) {
|
|
|
34
50
|
notifyAssignEmail: bool(row.notify_assign_email),
|
|
35
51
|
notifyCommentApp: bool(row.notify_comment_app),
|
|
36
52
|
notifyCommentEmail: bool(row.notify_comment_email),
|
|
53
|
+
assistantHistory: row.assistant_history,
|
|
37
54
|
});
|
|
38
55
|
}
|
|
39
56
|
function sessionFromRow(row) {
|
|
@@ -63,6 +80,7 @@ function agentTokenFromRow(row) {
|
|
|
63
80
|
lastUsedAt: row.last_used_at,
|
|
64
81
|
expiresAt: row.expires_at,
|
|
65
82
|
revokedAt: row.revoked_at,
|
|
83
|
+
isRunner: bool(row.is_runner),
|
|
66
84
|
});
|
|
67
85
|
}
|
|
68
86
|
function invitationFromRow(row) {
|
|
@@ -86,6 +104,7 @@ function spaceFromRow(row) {
|
|
|
86
104
|
ownerId: row.owner_id,
|
|
87
105
|
keyPrefix: row.key_prefix,
|
|
88
106
|
nextTaskNumber: row.next_task_number,
|
|
107
|
+
runnerAllowed: bool(row.runner_allowed),
|
|
89
108
|
createdAt: row.created_at,
|
|
90
109
|
});
|
|
91
110
|
}
|
|
@@ -278,6 +297,7 @@ export class Store {
|
|
|
278
297
|
activeMutation = null;
|
|
279
298
|
emailListener = null;
|
|
280
299
|
pendingRequestListeners = new Set();
|
|
300
|
+
runnerAllowedListeners = new Set();
|
|
281
301
|
/** Présence des veilleurs de notifications, par jeton d'agent. */
|
|
282
302
|
watchers = new WatcherRegistry();
|
|
283
303
|
constructor(db) {
|
|
@@ -308,6 +328,22 @@ export class Store {
|
|
|
308
328
|
countUsers() {
|
|
309
329
|
return this.db.prepare("SELECT COUNT(*) AS n FROM users").get().n;
|
|
310
330
|
}
|
|
331
|
+
/** Tous les comptes, du plus ancien au plus récent. */
|
|
332
|
+
listUsers() {
|
|
333
|
+
return this.db.prepare("SELECT * FROM users ORDER BY created_at, id").all().map(userFromRow);
|
|
334
|
+
}
|
|
335
|
+
countAdmins() {
|
|
336
|
+
return this.db.prepare("SELECT COUNT(*) AS n FROM users WHERE is_admin = 1").get().n;
|
|
337
|
+
}
|
|
338
|
+
/** Préférences de notification propres à un espace, ou null si l'utilisateur y suit celles de son compte. */
|
|
339
|
+
findSpaceNotificationSettings(userId, spaceId) {
|
|
340
|
+
const row = this.db.prepare("SELECT * FROM space_notification_settings WHERE user_id = ? AND space_id = ?").get(userId, spaceId);
|
|
341
|
+
return row ? spaceNotificationSettingsFromRow(row) : null;
|
|
342
|
+
}
|
|
343
|
+
/** Préférences de notification qui s'appliquent à un utilisateur dans un espace : celles de l'espace, sinon celles du compte. */
|
|
344
|
+
notificationPreferences(userId, spaceId) {
|
|
345
|
+
return preferencesOf(this.findSpaceNotificationSettings(userId, spaceId) ?? this.getUserSettings(userId));
|
|
346
|
+
}
|
|
311
347
|
getUserSettings(userId) {
|
|
312
348
|
const row = this.db.prepare("SELECT * FROM user_settings WHERE user_id = ?").get(userId);
|
|
313
349
|
if (!row)
|
|
@@ -338,6 +374,11 @@ export class Store {
|
|
|
338
374
|
return row ? agentTokenFromRow(row) : null;
|
|
339
375
|
}
|
|
340
376
|
/** Jetons qui couvrent un espace, tous propriétaires confondus, du plus récemment utilisé au moins récemment. */
|
|
377
|
+
/** Jeton du runner de l'espace, s'il a déjà servi. */
|
|
378
|
+
findRunnerToken(spaceId) {
|
|
379
|
+
const row = this.db.prepare("SELECT * FROM agent_tokens WHERE space_id = ? AND is_runner = 1 AND revoked_at IS NULL").get(spaceId);
|
|
380
|
+
return row ? agentTokenFromRow(row) : null;
|
|
381
|
+
}
|
|
341
382
|
listAgentTokensOfSpace(spaceId) {
|
|
342
383
|
const rows = this.db
|
|
343
384
|
.prepare("SELECT * FROM agent_tokens WHERE space_id = ? ORDER BY last_used_at IS NULL, last_used_at DESC, created_at DESC, id DESC")
|
|
@@ -392,11 +433,16 @@ export class Store {
|
|
|
392
433
|
/** Espaces dont l'utilisateur est membre : son espace personnel d'abord, puis les espaces communs par nom. */
|
|
393
434
|
listSpacesOf(userId) {
|
|
394
435
|
const rows = this.db
|
|
395
|
-
.prepare(`SELECT s.* FROM spaces s JOIN memberships m ON m.space_id = s.id
|
|
436
|
+
.prepare(`SELECT s.* FROM spaces s JOIN memberships m ON m.space_id = s.id
|
|
396
437
|
WHERE m.user_id = ? ORDER BY s.kind = 'shared', s.name COLLATE NOCASE`)
|
|
397
438
|
.all(userId);
|
|
398
439
|
return rows.map(spaceFromRow);
|
|
399
440
|
}
|
|
441
|
+
/** Tous les espaces : les espaces personnels d'abord, puis les espaces communs par nom. */
|
|
442
|
+
listAllSpaces() {
|
|
443
|
+
const rows = this.db.prepare("SELECT * FROM spaces ORDER BY kind = 'shared', name COLLATE NOCASE, id").all();
|
|
444
|
+
return rows.map(spaceFromRow);
|
|
445
|
+
}
|
|
400
446
|
listMemberships(spaceId) {
|
|
401
447
|
const rows = this.db.prepare("SELECT * FROM memberships WHERE space_id = ? ORDER BY joined_at, user_id").all(spaceId);
|
|
402
448
|
return rows.map(membershipFromRow);
|
|
@@ -542,7 +588,7 @@ export class Store {
|
|
|
542
588
|
/** Résultats saisis sur les points des tâches d'un espace. */
|
|
543
589
|
listTestResultsInSpace(spaceId) {
|
|
544
590
|
const rows = this.db
|
|
545
|
-
.prepare(`SELECT c.task_id, r.checkpoint_id, r.tester_id, r.result FROM test_results r
|
|
591
|
+
.prepare(`SELECT c.task_id, r.checkpoint_id, r.tester_id, r.result FROM test_results r
|
|
546
592
|
JOIN test_checkpoints c ON c.id = r.checkpoint_id JOIN tasks t ON t.id = c.task_id WHERE t.space_id = ?`)
|
|
547
593
|
.all(spaceId);
|
|
548
594
|
return rows.map((row) => ({
|
|
@@ -587,7 +633,7 @@ export class Store {
|
|
|
587
633
|
/** Pièces jointes d'une tâche et de toutes ses sous-tâches. */
|
|
588
634
|
listAttachmentsInSubtree(taskId) {
|
|
589
635
|
const rows = this.db
|
|
590
|
-
.prepare(`WITH RECURSIVE subtree(id) AS (SELECT ? UNION ALL SELECT t.id FROM tasks t JOIN subtree s ON t.parent_id = s.id)
|
|
636
|
+
.prepare(`WITH RECURSIVE subtree(id) AS (SELECT ? UNION ALL SELECT t.id FROM tasks t JOIN subtree s ON t.parent_id = s.id)
|
|
591
637
|
SELECT a.* FROM attachments a JOIN subtree s ON a.task_id = s.id`)
|
|
592
638
|
.all(taskId);
|
|
593
639
|
return rows.map(attachmentFromRow);
|
|
@@ -617,7 +663,7 @@ export class Store {
|
|
|
617
663
|
/** Demandes de promotion (`idea_promotion`) en attente ou en cours sur les idées d'un espace. */
|
|
618
664
|
listOpenPromotionRequests(spaceId) {
|
|
619
665
|
const rows = this.db
|
|
620
|
-
.prepare(`SELECT r.* FROM agent_requests r JOIN ideas i ON i.id = r.context_idea_id
|
|
666
|
+
.prepare(`SELECT r.* FROM agent_requests r JOIN ideas i ON i.id = r.context_idea_id
|
|
621
667
|
WHERE i.space_id = ? AND r.kind = 'idea_promotion' AND r.status IN ('pending', 'processing') ORDER BY r.created_at, r.id`)
|
|
622
668
|
.all(spaceId);
|
|
623
669
|
return rows.map(requestFromRow);
|
|
@@ -650,6 +696,21 @@ export class Store {
|
|
|
650
696
|
.all({ spaceId, status, limit });
|
|
651
697
|
return rows.map(requestFromRow);
|
|
652
698
|
}
|
|
699
|
+
/**
|
|
700
|
+
* Échanges clos (traités ou en échec) d'un auteur dans un espace, antérieurs à
|
|
701
|
+
* la demande `before`, du plus ancien au plus récent : les `limit` derniers.
|
|
702
|
+
*/
|
|
703
|
+
listClosedRequestsOfAuthorBefore(spaceId, authorId, before, limit) {
|
|
704
|
+
if (limit === 0)
|
|
705
|
+
return [];
|
|
706
|
+
const rows = this.db
|
|
707
|
+
.prepare(`SELECT * FROM agent_requests
|
|
708
|
+
WHERE target_space_id = @spaceId AND author_id = @authorId AND status IN ('done', 'failed')
|
|
709
|
+
AND (created_at < @createdAt OR (created_at = @createdAt AND id < @id))
|
|
710
|
+
ORDER BY created_at DESC, id DESC LIMIT @limit`)
|
|
711
|
+
.all({ spaceId, authorId, createdAt: before.createdAt, id: before.id, limit });
|
|
712
|
+
return rows.map(requestFromRow).reverse();
|
|
713
|
+
}
|
|
653
714
|
/** Demandes d'un espace, de la plus récente à la plus ancienne, au plus `limit`. */
|
|
654
715
|
listAgentRequestsOfSpace(spaceId, limit) {
|
|
655
716
|
const rows = this.db
|
|
@@ -712,9 +773,9 @@ export class Store {
|
|
|
712
773
|
*/
|
|
713
774
|
listNotifications(userId, limit, offset) {
|
|
714
775
|
const rows = this.db
|
|
715
|
-
.prepare(`SELECT n.* FROM notifications n JOIN tasks t ON t.id = n.task_id
|
|
716
|
-
JOIN memberships m ON m.space_id = t.space_id AND m.user_id = n.user_id
|
|
717
|
-
WHERE n.user_id = ? AND n.in_app = 1
|
|
776
|
+
.prepare(`SELECT n.* FROM notifications n JOIN tasks t ON t.id = n.task_id
|
|
777
|
+
JOIN memberships m ON m.space_id = t.space_id AND m.user_id = n.user_id
|
|
778
|
+
WHERE n.user_id = ? AND n.in_app = 1
|
|
718
779
|
ORDER BY n.read_at IS NOT NULL, n.created_at DESC, n.id DESC LIMIT ? OFFSET ?`)
|
|
719
780
|
.all(userId, limit, offset);
|
|
720
781
|
return rows.map(notificationFromRow);
|
|
@@ -722,8 +783,8 @@ export class Store {
|
|
|
722
783
|
/** Nombre de notifications non lues affichées dans l'application d'un utilisateur. */
|
|
723
784
|
countUnreadNotifications(userId) {
|
|
724
785
|
const row = this.db
|
|
725
|
-
.prepare(`SELECT COUNT(*) AS n FROM notifications n JOIN tasks t ON t.id = n.task_id
|
|
726
|
-
JOIN memberships m ON m.space_id = t.space_id AND m.user_id = n.user_id
|
|
786
|
+
.prepare(`SELECT COUNT(*) AS n FROM notifications n JOIN tasks t ON t.id = n.task_id
|
|
787
|
+
JOIN memberships m ON m.space_id = t.space_id AND m.user_id = n.user_id
|
|
727
788
|
WHERE n.user_id = ? AND n.in_app = 1 AND n.read_at IS NULL`)
|
|
728
789
|
.get(userId);
|
|
729
790
|
return row.n;
|
|
@@ -752,8 +813,8 @@ export class Store {
|
|
|
752
813
|
*/
|
|
753
814
|
listChangesForUser(userId, since, limit) {
|
|
754
815
|
const rows = this.db
|
|
755
|
-
.prepare(`SELECT * FROM changes WHERE seq > ?
|
|
756
|
-
AND (user_id = ? OR space_id IN (SELECT space_id FROM memberships WHERE user_id = ?))
|
|
816
|
+
.prepare(`SELECT * FROM changes WHERE seq > ?
|
|
817
|
+
AND (user_id = ? OR space_id IN (SELECT space_id FROM memberships WHERE user_id = ?))
|
|
757
818
|
ORDER BY seq LIMIT ?`)
|
|
758
819
|
.all(since, userId, userId, limit);
|
|
759
820
|
return rows.map(changeFromRow);
|
|
@@ -780,6 +841,11 @@ export class Store {
|
|
|
780
841
|
this.pendingRequestListeners.add(listener);
|
|
781
842
|
return () => this.pendingRequestListeners.delete(listener);
|
|
782
843
|
}
|
|
844
|
+
/** Abonne `listener` aux espaces où le runner vient d'être autorisé, après la validation de la mutation. */
|
|
845
|
+
onRunnerAllowed(listener) {
|
|
846
|
+
this.runnerAllowedListeners.add(listener);
|
|
847
|
+
return () => this.runnerAllowedListeners.delete(listener);
|
|
848
|
+
}
|
|
783
849
|
/**
|
|
784
850
|
* Point unique d'écriture. `fn` reçoit une `Mutation` et s'exécute dans une
|
|
785
851
|
* transaction exclusive ; à sa sortie, les invariants sont vérifiés sur l'état
|
|
@@ -791,6 +857,7 @@ export class Store {
|
|
|
791
857
|
throw new Error("Store.mutate is not reentrant: use the Mutation passed to the running mutation.");
|
|
792
858
|
let emails = [];
|
|
793
859
|
let pending = [];
|
|
860
|
+
let allowed = [];
|
|
794
861
|
const run = this.db.transaction(() => {
|
|
795
862
|
const tx = new Mutation(this, actor);
|
|
796
863
|
this.activeMutation = tx;
|
|
@@ -799,6 +866,7 @@ export class Store {
|
|
|
799
866
|
tx.finish();
|
|
800
867
|
emails = tx.emailNotificationIds;
|
|
801
868
|
pending = tx.pendingRequestIds;
|
|
869
|
+
allowed = tx.runnerAllowedSpaceIds;
|
|
802
870
|
return result;
|
|
803
871
|
}
|
|
804
872
|
finally {
|
|
@@ -815,6 +883,11 @@ export class Store {
|
|
|
815
883
|
listener(request);
|
|
816
884
|
}
|
|
817
885
|
}
|
|
886
|
+
for (const id of allowed) {
|
|
887
|
+
const space = this.getSpace(id);
|
|
888
|
+
for (const listener of [...this.runnerAllowedListeners])
|
|
889
|
+
listener(space);
|
|
890
|
+
}
|
|
818
891
|
return result;
|
|
819
892
|
}
|
|
820
893
|
}
|
|
@@ -833,6 +906,8 @@ export class Mutation {
|
|
|
833
906
|
emailNotificationIds = [];
|
|
834
907
|
/** Demandes passées en attente par la mutation : créées, ou libérées. */
|
|
835
908
|
pendingRequestIds = [];
|
|
909
|
+
/** Espaces où la mutation a autorisé le runner. */
|
|
910
|
+
runnerAllowedSpaceIds = [];
|
|
836
911
|
/** Demande pour laquelle agit l'agent, lue au début de la mutation. */
|
|
837
912
|
request;
|
|
838
913
|
/** Jeton avec lequel agit l'agent. */
|
|
@@ -917,6 +992,7 @@ export class Mutation {
|
|
|
917
992
|
displayName: input.displayName ?? null,
|
|
918
993
|
passwordHash: input.passwordHash ?? null,
|
|
919
994
|
status: input.status,
|
|
995
|
+
isAdmin: input.isAdmin ?? false,
|
|
920
996
|
createdAt: this.now,
|
|
921
997
|
}, "user");
|
|
922
998
|
if (user.status !== "invited" && user.displayName === null) {
|
|
@@ -925,8 +1001,8 @@ export class Mutation {
|
|
|
925
1001
|
if (this.store.findUserByEmail(user.email))
|
|
926
1002
|
throw new InvariantError(`An account already exists for ${user.email}.`);
|
|
927
1003
|
this.db
|
|
928
|
-
.prepare("INSERT INTO users (id, email, display_name, password_hash, status, created_at) VALUES (?, ?, ?, ?, ?, ?)")
|
|
929
|
-
.run(user.id, user.email, user.displayName, user.passwordHash, user.status, user.createdAt);
|
|
1004
|
+
.prepare("INSERT INTO users (id, email, display_name, password_hash, status, is_admin, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)")
|
|
1005
|
+
.run(user.id, user.email, user.displayName, user.passwordHash, user.status, user.isAdmin ? 1 : 0, user.createdAt);
|
|
930
1006
|
this.db.prepare("INSERT INTO user_settings (user_id) VALUES (?)").run(user.id);
|
|
931
1007
|
this.touch("user", user.id, null, user.id);
|
|
932
1008
|
return user;
|
|
@@ -943,6 +1019,13 @@ export class Mutation {
|
|
|
943
1019
|
this.touch("user", id, null, id);
|
|
944
1020
|
return user;
|
|
945
1021
|
}
|
|
1022
|
+
/** Accorde ou retire le rôle d'administrateur du site. */
|
|
1023
|
+
setUserAdmin(id, isAdmin) {
|
|
1024
|
+
const user = { ...this.store.getUser(id), isAdmin };
|
|
1025
|
+
this.db.prepare("UPDATE users SET is_admin = ? WHERE id = ?").run(isAdmin ? 1 : 0, id);
|
|
1026
|
+
this.touch("user", id, null, id);
|
|
1027
|
+
return user;
|
|
1028
|
+
}
|
|
946
1029
|
// --- Sessions et liens d'accès ---
|
|
947
1030
|
createSession(session) {
|
|
948
1031
|
const valid = validate(SessionSchema, session, "session");
|
|
@@ -1009,13 +1092,13 @@ export class Mutation {
|
|
|
1009
1092
|
createAgentToken(input) {
|
|
1010
1093
|
if (this.actor.type !== "user")
|
|
1011
1094
|
throw new InvariantError("Only a user can create an agent token.");
|
|
1012
|
-
const token = validate(AgentTokenSchema, { id: newId(), userId: this.actor.userId, ...input, createdAt: this.now, lastUsedAt: null, revokedAt: null }, "agent token");
|
|
1095
|
+
const token = validate(AgentTokenSchema, { id: newId(), userId: this.actor.userId, ...input, isRunner: input.isRunner ?? false, createdAt: this.now, lastUsedAt: null, revokedAt: null }, "agent token");
|
|
1013
1096
|
if (!this.store.isMember(token.spaceId, token.userId))
|
|
1014
1097
|
throw new ForbiddenError("An agent token can only cover a space its owner is a member of.");
|
|
1015
1098
|
this.db
|
|
1016
|
-
.prepare(`INSERT INTO agent_tokens (id, user_id, space_id, name, prefix, token_hash, created_at, expires_at)
|
|
1017
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1018
|
-
.run(token.id, token.userId, token.spaceId, token.name, token.prefix, token.tokenHash, token.createdAt, token.expiresAt);
|
|
1099
|
+
.prepare(`INSERT INTO agent_tokens (id, user_id, space_id, name, prefix, token_hash, created_at, expires_at, is_runner)
|
|
1100
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1101
|
+
.run(token.id, token.userId, token.spaceId, token.name, token.prefix, token.tokenHash, token.createdAt, token.expiresAt, token.isRunner ? 1 : 0);
|
|
1019
1102
|
this.touch("agent_token", token.id, null, token.userId);
|
|
1020
1103
|
return token;
|
|
1021
1104
|
}
|
|
@@ -1030,6 +1113,14 @@ export class Mutation {
|
|
|
1030
1113
|
this.touch("agent_token", id, null, token.userId);
|
|
1031
1114
|
return { ...token, revokedAt: this.now };
|
|
1032
1115
|
}
|
|
1116
|
+
/** Remplace la valeur d'un jeton du runner : l'ancienne valeur est aussitôt refusée. */
|
|
1117
|
+
rotateRunnerToken(id, prefix, tokenHash) {
|
|
1118
|
+
const token = this.store.findAgentToken(id);
|
|
1119
|
+
if (!token || !token.isRunner)
|
|
1120
|
+
throw new NotFoundError(`Runner token ${id} not found.`);
|
|
1121
|
+
this.db.prepare("UPDATE agent_tokens SET prefix = ?, token_hash = ? WHERE id = ?").run(prefix, tokenHash, id);
|
|
1122
|
+
return { ...token, prefix, tokenHash };
|
|
1123
|
+
}
|
|
1033
1124
|
/** Date du dernier usage d'un jeton ; le journal des changements n'en garde pas trace. */
|
|
1034
1125
|
recordAgentTokenUse(id, at) {
|
|
1035
1126
|
this.db.prepare("UPDATE agent_tokens SET last_used_at = ? WHERE id = ?").run(at, id);
|
|
@@ -1105,7 +1196,7 @@ export class Mutation {
|
|
|
1105
1196
|
// --- Espaces et membres ---
|
|
1106
1197
|
/** Crée un espace et en fait de son propriétaire le membre `owner`. */
|
|
1107
1198
|
createSpace(input) {
|
|
1108
|
-
const space = validate(SpaceSchema, { id: newId(), name: input.name, kind: input.kind, ownerId: input.ownerId, keyPrefix: input.keyPrefix, nextTaskNumber: 1, createdAt: this.now }, "space");
|
|
1199
|
+
const space = validate(SpaceSchema, { id: newId(), name: input.name, kind: input.kind, ownerId: input.ownerId, keyPrefix: input.keyPrefix, nextTaskNumber: 1, runnerAllowed: false, createdAt: this.now }, "space");
|
|
1109
1200
|
this.store.getUser(space.ownerId);
|
|
1110
1201
|
if (space.kind === "personal" && this.store.findPersonalSpace(space.ownerId)) {
|
|
1111
1202
|
throw new InvariantError(`User ${space.ownerId} already has a personal space.`);
|
|
@@ -1117,6 +1208,15 @@ export class Mutation {
|
|
|
1117
1208
|
this.insertMembership(space.id, space.ownerId, "owner");
|
|
1118
1209
|
return space;
|
|
1119
1210
|
}
|
|
1211
|
+
/** Autorise ou interdit le runner sur un espace. */
|
|
1212
|
+
setSpaceRunnerAllowed(id, allowed) {
|
|
1213
|
+
const space = { ...this.store.getSpace(id), runnerAllowed: allowed };
|
|
1214
|
+
this.db.prepare("UPDATE spaces SET runner_allowed = ? WHERE id = ?").run(allowed ? 1 : 0, id);
|
|
1215
|
+
if (allowed)
|
|
1216
|
+
this.runnerAllowedSpaceIds.push(id);
|
|
1217
|
+
this.touch("space", id, id, null);
|
|
1218
|
+
return space;
|
|
1219
|
+
}
|
|
1120
1220
|
renameSpace(id, name) {
|
|
1121
1221
|
const space = validate(SpaceSchema, { ...this.store.getSpace(id), name }, "space");
|
|
1122
1222
|
this.db.prepare("UPDATE spaces SET name = ? WHERE id = ?").run(space.name, id);
|
|
@@ -1132,7 +1232,7 @@ export class Mutation {
|
|
|
1132
1232
|
this.touch("membership", `${id}:${membership.userId}`, id, membership.userId);
|
|
1133
1233
|
}
|
|
1134
1234
|
this.db
|
|
1135
|
-
.prepare(`DELETE FROM entity_versions WHERE (entity_type = 'task' AND entity_id IN (SELECT id FROM tasks WHERE space_id = ?))
|
|
1235
|
+
.prepare(`DELETE FROM entity_versions WHERE (entity_type = 'task' AND entity_id IN (SELECT id FROM tasks WHERE space_id = ?))
|
|
1136
1236
|
OR (entity_type = 'idea' AND entity_id IN (SELECT id FROM ideas WHERE space_id = ?))`)
|
|
1137
1237
|
.run(id, id);
|
|
1138
1238
|
this.db.prepare("DELETE FROM spaces WHERE id = ?").run(id);
|
|
@@ -1158,6 +1258,7 @@ export class Mutation {
|
|
|
1158
1258
|
if (!this.store.findMembership(spaceId, userId))
|
|
1159
1259
|
throw new NotFoundError(`User ${userId} is not a member of space ${spaceId}.`);
|
|
1160
1260
|
this.db.prepare("DELETE FROM memberships WHERE space_id = ? AND user_id = ?").run(spaceId, userId);
|
|
1261
|
+
this.db.prepare("DELETE FROM space_notification_settings WHERE space_id = ? AND user_id = ?").run(spaceId, userId);
|
|
1161
1262
|
this.touch("membership", `${spaceId}:${userId}`, spaceId, userId);
|
|
1162
1263
|
const tokenIds = this.db.prepare("SELECT id FROM agent_tokens WHERE space_id = ? AND user_id = ? AND revoked_at IS NULL").pluck().all(spaceId, userId);
|
|
1163
1264
|
this.db.prepare("UPDATE agent_tokens SET revoked_at = ? WHERE space_id = ? AND user_id = ? AND revoked_at IS NULL").run(this.now, spaceId, userId);
|
|
@@ -1195,8 +1296,8 @@ export class Mutation {
|
|
|
1195
1296
|
version: 1,
|
|
1196
1297
|
}, "task");
|
|
1197
1298
|
this.db
|
|
1198
|
-
.prepare(`INSERT INTO tasks (id, space_id, number, parent_id, depth, type, title, description, status, priority, position,
|
|
1199
|
-
created_by, created_at, updated_at, version)
|
|
1299
|
+
.prepare(`INSERT INTO tasks (id, space_id, number, parent_id, depth, type, title, description, status, priority, position,
|
|
1300
|
+
created_by, created_at, updated_at, version)
|
|
1200
1301
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1201
1302
|
.run(task.id, task.spaceId, task.number, task.parentId, task.depth, task.type, task.title, task.description, task.status, task.priority, task.position, task.createdBy, task.createdAt, task.updatedAt, task.version);
|
|
1202
1303
|
this.db.prepare("UPDATE spaces SET next_task_number = next_task_number + 1 WHERE id = ?").run(space.id);
|
|
@@ -1230,7 +1331,7 @@ export class Mutation {
|
|
|
1230
1331
|
const testedTaskId = fields.testedTaskId !== undefined ? fields.testedTaskId : type === "standard" ? null : current.testedTaskId;
|
|
1231
1332
|
const task = validate(TaskSchema, { ...current, ...fields, parentId, depth, status, position, testedTaskId, updatedAt: this.now }, "task");
|
|
1232
1333
|
this.db
|
|
1233
|
-
.prepare(`UPDATE tasks SET parent_id = ?, depth = ?, type = ?, title = ?, description = ?, status = ?, priority = ?,
|
|
1334
|
+
.prepare(`UPDATE tasks SET parent_id = ?, depth = ?, type = ?, title = ?, description = ?, status = ?, priority = ?,
|
|
1234
1335
|
position = ?, tested_task_id = ?, updated_at = ? WHERE id = ?`)
|
|
1235
1336
|
.run(task.parentId, task.depth, task.type, task.title, task.description, task.status, task.priority, task.position, task.testedTaskId, task.updatedAt, id);
|
|
1236
1337
|
this.touch("task", id, task.spaceId, null);
|
|
@@ -1489,7 +1590,7 @@ export class Mutation {
|
|
|
1489
1590
|
updatedAt: this.now,
|
|
1490
1591
|
}, "test result");
|
|
1491
1592
|
this.db
|
|
1492
|
-
.prepare(`INSERT INTO test_results (checkpoint_id, tester_id, result, comment, updated_at) VALUES (?, ?, ?, ?, ?)
|
|
1593
|
+
.prepare(`INSERT INTO test_results (checkpoint_id, tester_id, result, comment, updated_at) VALUES (?, ?, ?, ?, ?)
|
|
1493
1594
|
ON CONFLICT (checkpoint_id, tester_id) DO UPDATE SET result = excluded.result, comment = excluded.comment, updated_at = excluded.updated_at`)
|
|
1494
1595
|
.run(result.checkpointId, result.testerId, result.result, result.comment, result.updatedAt);
|
|
1495
1596
|
this.touch("test_result", `${checkpointId}:${testerId}`, task.spaceId, null, task.id);
|
|
@@ -1652,7 +1753,7 @@ export class Mutation {
|
|
|
1652
1753
|
createdAt: this.now,
|
|
1653
1754
|
}, "idea");
|
|
1654
1755
|
this.db
|
|
1655
|
-
.prepare(`INSERT INTO ideas (id, space_id, author_id, text, origin, request_id, promoted_task_id, archived_at, created_at)
|
|
1756
|
+
.prepare(`INSERT INTO ideas (id, space_id, author_id, text, origin, request_id, promoted_task_id, archived_at, created_at)
|
|
1656
1757
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1657
1758
|
.run(idea.id, idea.spaceId, idea.authorId, idea.text, idea.origin, idea.requestId, idea.promotedTaskId, idea.archivedAt, idea.createdAt);
|
|
1658
1759
|
this.touch("idea", idea.id, idea.spaceId, null);
|
|
@@ -1709,8 +1810,8 @@ export class Mutation {
|
|
|
1709
1810
|
}, "request");
|
|
1710
1811
|
this.store.getSpace(request.targetSpaceId);
|
|
1711
1812
|
this.db
|
|
1712
|
-
.prepare(`INSERT INTO agent_requests (id, author_id, target_space_id, kind, context_task_id, context_idea_id,
|
|
1713
|
-
text, status, created_at, updated_at)
|
|
1813
|
+
.prepare(`INSERT INTO agent_requests (id, author_id, target_space_id, kind, context_task_id, context_idea_id,
|
|
1814
|
+
text, status, created_at, updated_at)
|
|
1714
1815
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1715
1816
|
.run(request.id, request.authorId, request.targetSpaceId, request.kind, request.contextTaskId, request.contextIdeaId, request.text, request.status, request.createdAt, request.updatedAt);
|
|
1716
1817
|
this.createdRequestIds.add(request.id);
|
|
@@ -1835,7 +1936,7 @@ export class Mutation {
|
|
|
1835
1936
|
tokenId: token.id,
|
|
1836
1937
|
}, "agent action");
|
|
1837
1938
|
this.db
|
|
1838
|
-
.prepare(`INSERT INTO agent_actions (id, request_id, seq, entity_type, entity_id, operation, before, after, summary, created_at, token_id)
|
|
1939
|
+
.prepare(`INSERT INTO agent_actions (id, request_id, seq, entity_type, entity_id, operation, before, after, summary, created_at, token_id)
|
|
1839
1940
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1840
1941
|
.run(action.id, action.requestId, action.seq, action.entityType, action.entityId, action.operation, action.before === null ? null : JSON.stringify(action.before), action.after === null ? null : JSON.stringify(action.after), action.summary, action.createdAt, action.tokenId);
|
|
1841
1942
|
this.touch("agent_action", action.id, request.targetSpaceId, null, input.taskId);
|
|
@@ -1858,13 +1959,14 @@ export class Mutation {
|
|
|
1858
1959
|
return this.actor.type === "user" && this.actor.userId === userId;
|
|
1859
1960
|
}
|
|
1860
1961
|
/**
|
|
1861
|
-
* Notifie un utilisateur selon ses préférences
|
|
1962
|
+
* Notifie un utilisateur selon ses préférences dans l'espace de la tâche
|
|
1963
|
+
* (celles de l'espace, sinon celles du compte) : la notification apparaît
|
|
1862
1964
|
* dans l'application si elle y est activée, et son identifiant est retenu
|
|
1863
1965
|
* pour l'envoi de l'e-mail si celui-ci est activé. Rien n'est créé quand les
|
|
1864
1966
|
* deux sont désactivés.
|
|
1865
1967
|
*/
|
|
1866
1968
|
notify(type, userId, taskId, commentId) {
|
|
1867
|
-
const settings = this.store.
|
|
1969
|
+
const settings = this.store.notificationPreferences(userId, this.store.getTask(taskId).spaceId);
|
|
1868
1970
|
const inApp = type === "assigned" ? settings.notifyAssignApp : settings.notifyCommentApp;
|
|
1869
1971
|
const email = type === "assigned" ? settings.notifyAssignEmail : settings.notifyCommentEmail;
|
|
1870
1972
|
if (!inApp && !email)
|
|
@@ -1885,7 +1987,7 @@ export class Mutation {
|
|
|
1885
1987
|
emailError: null,
|
|
1886
1988
|
}, "notification");
|
|
1887
1989
|
this.db
|
|
1888
|
-
.prepare(`INSERT INTO notifications (id, user_id, type, task_id, actor_type, actor_id, request_id, comment_id, in_app, created_at)
|
|
1990
|
+
.prepare(`INSERT INTO notifications (id, user_id, type, task_id, actor_type, actor_id, request_id, comment_id, in_app, created_at)
|
|
1889
1991
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1890
1992
|
.run(notification.id, notification.userId, notification.type, notification.taskId, notification.actorType, notification.actorId, notification.requestId, notification.commentId, inApp ? 1 : 0, notification.createdAt);
|
|
1891
1993
|
if (inApp)
|
|
@@ -1927,12 +2029,33 @@ export class Mutation {
|
|
|
1927
2029
|
updateUserSettings(userId, patch) {
|
|
1928
2030
|
const settings = validate(UserSettingsSchema, { ...this.store.getUserSettings(userId), ...definedFields(patch) }, "settings");
|
|
1929
2031
|
this.db
|
|
1930
|
-
.prepare(`UPDATE user_settings SET notify_assign_app = ?, notify_assign_email = ?, notify_comment_app = ?, notify_comment_email =
|
|
1931
|
-
WHERE user_id = ?`)
|
|
1932
|
-
.run(settings.notifyAssignApp ? 1 : 0, settings.notifyAssignEmail ? 1 : 0, settings.notifyCommentApp ? 1 : 0, settings.notifyCommentEmail ? 1 : 0, userId);
|
|
2032
|
+
.prepare(`UPDATE user_settings SET notify_assign_app = ?, notify_assign_email = ?, notify_comment_app = ?, notify_comment_email = ?,
|
|
2033
|
+
assistant_history = ? WHERE user_id = ?`)
|
|
2034
|
+
.run(settings.notifyAssignApp ? 1 : 0, settings.notifyAssignEmail ? 1 : 0, settings.notifyCommentApp ? 1 : 0, settings.notifyCommentEmail ? 1 : 0, settings.assistantHistory, userId);
|
|
1933
2035
|
this.touch("user_settings", userId, null, userId);
|
|
1934
2036
|
return settings;
|
|
1935
2037
|
}
|
|
2038
|
+
/**
|
|
2039
|
+
* Fixe les préférences de notification d'un membre propres à un espace ;
|
|
2040
|
+
* null les retire, et il y suit de nouveau celles de son compte.
|
|
2041
|
+
*/
|
|
2042
|
+
setSpaceNotificationSettings(userId, spaceId, preferences) {
|
|
2043
|
+
if (!this.store.findMembership(spaceId, userId))
|
|
2044
|
+
throw new NotFoundError(`User ${userId} is not a member of space ${spaceId}.`);
|
|
2045
|
+
this.touch("user_settings", userId, null, userId);
|
|
2046
|
+
if (preferences === null) {
|
|
2047
|
+
this.db.prepare("DELETE FROM space_notification_settings WHERE user_id = ? AND space_id = ?").run(userId, spaceId);
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
const settings = validate(SpaceNotificationSettingsSchema, { userId, spaceId, ...preferences }, "space notification settings");
|
|
2051
|
+
this.db
|
|
2052
|
+
.prepare(`INSERT INTO space_notification_settings (user_id, space_id, notify_assign_app, notify_assign_email, notify_comment_app, notify_comment_email)
|
|
2053
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2054
|
+
ON CONFLICT (user_id, space_id) DO UPDATE SET notify_assign_app = excluded.notify_assign_app, notify_assign_email = excluded.notify_assign_email,
|
|
2055
|
+
notify_comment_app = excluded.notify_comment_app, notify_comment_email = excluded.notify_comment_email`)
|
|
2056
|
+
.run(userId, spaceId, settings.notifyAssignApp ? 1 : 0, settings.notifyAssignEmail ? 1 : 0, settings.notifyCommentApp ? 1 : 0, settings.notifyCommentEmail ? 1 : 0);
|
|
2057
|
+
return settings;
|
|
2058
|
+
}
|
|
1936
2059
|
// --- Fin de mutation ---
|
|
1937
2060
|
/** Vérifie les invariants sur l'état final, puis écrit les versions et le journal. */
|
|
1938
2061
|
finish() {
|
|
@@ -2009,8 +2132,8 @@ export class Mutation {
|
|
|
2009
2132
|
}
|
|
2010
2133
|
}
|
|
2011
2134
|
const outsiders = this.db
|
|
2012
|
-
.prepare(`SELECT t.number AS number, x.user_id AS user_id FROM task_assignees x JOIN tasks t ON t.id = x.task_id
|
|
2013
|
-
WHERE t.space_id = ? AND NOT EXISTS (SELECT 1 FROM memberships m WHERE m.space_id = t.space_id AND m.user_id = x.user_id)
|
|
2135
|
+
.prepare(`SELECT t.number AS number, x.user_id AS user_id FROM task_assignees x JOIN tasks t ON t.id = x.task_id
|
|
2136
|
+
WHERE t.space_id = ? AND NOT EXISTS (SELECT 1 FROM memberships m WHERE m.space_id = t.space_id AND m.user_id = x.user_id)
|
|
2014
2137
|
ORDER BY t.number LIMIT 1`)
|
|
2015
2138
|
.get(space.id);
|
|
2016
2139
|
if (outsiders) {
|
|
@@ -2082,7 +2205,7 @@ export class Mutation {
|
|
|
2082
2205
|
const actorId = this.actingUserId;
|
|
2083
2206
|
const requestId = this.request?.id ?? null;
|
|
2084
2207
|
const tokenId = this.token?.id ?? null;
|
|
2085
|
-
const insert = this.db.prepare(`INSERT INTO entity_versions (entity_type, entity_id, version, snapshot, actor_type, actor_id, request_id, created_at, token_id)
|
|
2208
|
+
const insert = this.db.prepare(`INSERT INTO entity_versions (entity_type, entity_id, version, snapshot, actor_type, actor_id, request_id, created_at, token_id)
|
|
2086
2209
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2087
2210
|
for (const entry of this.touched.values()) {
|
|
2088
2211
|
if (!VERSIONED.has(entry.entityType))
|
|
@@ -2104,7 +2227,7 @@ export class Mutation {
|
|
|
2104
2227
|
}
|
|
2105
2228
|
/** Une ligne du journal par entité modifiée, avec l'auteur de la mutation. */
|
|
2106
2229
|
writeChanges() {
|
|
2107
|
-
const insert = this.db.prepare(`INSERT INTO changes (space_id, user_id, entity_type, entity_id, task_id, at, actor_type, actor_id, request_id)
|
|
2230
|
+
const insert = this.db.prepare(`INSERT INTO changes (space_id, user_id, entity_type, entity_id, task_id, at, actor_type, actor_id, request_id)
|
|
2108
2231
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2109
2232
|
const actorId = this.actingUserId;
|
|
2110
2233
|
const requestId = this.request?.id ?? null;
|
package/dist/src/gui-api.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ApiRequestData, type ApiResponseData, type ApiErrorKind } from "../shared/schemas/gui-api.schema.js";
|
|
2
|
+
import { type RunnerStatus } from "../shared/schemas/admin-api.schema.js";
|
|
2
3
|
import type { AttachmentStorage } from "./core/attachments.js";
|
|
3
4
|
import type { Mailer } from "./core/mail.js";
|
|
4
5
|
import type { Store } from "./core/store.js";
|
|
@@ -26,6 +27,11 @@ export interface GuiApiContext {
|
|
|
26
27
|
attachments: AttachmentStorage;
|
|
27
28
|
/** Dictée vocale (API Groq). */
|
|
28
29
|
transcriber: Transcriber;
|
|
30
|
+
/** Runner du site : son état, et le lancement d'une session ponctuelle (null quand il est désactivé). */
|
|
31
|
+
runner: {
|
|
32
|
+
status: RunnerStatus;
|
|
33
|
+
runNow: ((spaceId: string) => void) | null;
|
|
34
|
+
};
|
|
29
35
|
/** Horloge, imposée par les tests. */
|
|
30
36
|
now?: () => Date;
|
|
31
37
|
}
|
package/dist/src/gui-api.js
CHANGED
|
@@ -6,8 +6,8 @@ import { acceptReceivedInvitation, createSharedSpace, declineReceivedInvitation,
|
|
|
6
6
|
import { addChecklistItem, addComment, createTask, deleteAttachment, deleteChecklistItem, deleteComment, deleteTask, downloadAttachment, getBoard, getTaskDetail, listChanges, moveChecklistItem, moveTask, setAssignees, setTags, updateChecklistItem, updateComment, updateTask, uploadAttachment, } from "./core/tasks.js";
|
|
7
7
|
import { addCheckpoint, deleteCheckpoint, moveCheckpoint, saveReviewDraft, setTestResult, submitReview, updateCheckpoint } from "./core/test-tasks.js";
|
|
8
8
|
import { AddChecklistItemRequestSchema, CheckpointTextRequestSchema, CommentRequestSchema, CreateTaskRequestSchema, MoveAfterRequestSchema, MoveTaskRequestSchema, ReviewDraftRequestSchema, SetAssigneesRequestSchema, SetTagsRequestSchema, SetTestResultRequestSchema, SubmitReviewRequestSchema, TaskFiltersSchema, UpdateChecklistItemRequestSchema, UpdateTaskRequestSchema, UploadAttachmentRequestSchema, } from "../shared/schemas/tasks-api.schema.js";
|
|
9
|
-
import { getSettings, listNotifications, markAllNotificationsRead, markNotificationRead, unreadCount, updateSettings } from "./core/notifications.js";
|
|
10
|
-
import { NotificationListQuerySchema, UpdateSettingsRequestSchema } from "../shared/schemas/notifications-api.schema.js";
|
|
9
|
+
import { getSettings, getSpaceNotificationSettings, listNotifications, markAllNotificationsRead, markNotificationRead, unreadCount, updateSettings, updateSpaceNotificationSettings } from "./core/notifications.js";
|
|
10
|
+
import { NotificationListQuerySchema, UpdateSettingsRequestSchema, UpdateSpaceNotificationSettingsRequestSchema } from "../shared/schemas/notifications-api.schema.js";
|
|
11
11
|
import { cancelRequest, createRequest, getRequest, listRequests, releaseRequest, undoAction, undoRequest, updateRequest } from "./core/requests.js";
|
|
12
12
|
import { listTaskVersions, restoreTaskVersion } from "./core/task-history.js";
|
|
13
13
|
import { CreateRequestSchema, UpdateRequestSchema } from "../shared/schemas/agent-api.schema.js";
|
|
@@ -15,6 +15,8 @@ import { createIdea, delegatePromotion, listIdeas, promoteIdea, setIdeaArchived
|
|
|
15
15
|
import { CreateIdeaRequestSchema, PromoteIdeaRequestSchema, UpdateIdeaRequestSchema } from "../shared/schemas/ideas-api.schema.js";
|
|
16
16
|
import { createAgentToken, listAgentTokens, revokeAgentToken } from "./core/agent-tokens.js";
|
|
17
17
|
import { CreateTokenRequestSchema } from "../shared/schemas/tokens-api.schema.js";
|
|
18
|
+
import { listAdminSpaces, processQueueNow, requireAdmin, setRunnerAllowed } from "./core/admin.js";
|
|
19
|
+
import { SetRunnerAllowedRequestSchema } from "../shared/schemas/admin-api.schema.js";
|
|
18
20
|
import { TranscriptionError } from "./core/transcription.js";
|
|
19
21
|
import { MAX_TRANSCRIPTION_BYTES, TranscribeRequestSchema } from "../shared/schemas/transcription-api.schema.js";
|
|
20
22
|
/** Erreur d'une requête de la GUI, avec la nature qui fixe son code HTTP. */
|
|
@@ -89,7 +91,7 @@ function parseBody(schema, body) {
|
|
|
89
91
|
return parsed.data;
|
|
90
92
|
}
|
|
91
93
|
function publicUser(user) {
|
|
92
|
-
return { id: user.id, email: user.email, displayName: user.displayName, status: user.status };
|
|
94
|
+
return { id: user.id, email: user.email, displayName: user.displayName, status: user.status, isAdmin: user.isAdmin };
|
|
93
95
|
}
|
|
94
96
|
const ok = { ok: true };
|
|
95
97
|
/** Taille lisible d'un nombre d'octets, en Mo. */
|
|
@@ -287,7 +289,7 @@ route("POST", "/ideas/:ideaId/promote", (ctx, { user, request, params }) => ({
|
|
|
287
289
|
}));
|
|
288
290
|
route("POST", "/ideas/:ideaId/delegate", (ctx, { user, params }) => ({ body: delegatePromotion(ctx.store, user.id, params.ideaId) }));
|
|
289
291
|
// --- File de l'agent et historique -------------------------------------------------
|
|
290
|
-
route("GET", "/spaces/:spaceId/requests", (ctx, { user, params }) => ({ body: listRequests(ctx.store, user.id, params.spaceId, now(ctx)) }));
|
|
292
|
+
route("GET", "/spaces/:spaceId/requests", (ctx, { user, params }) => ({ body: listRequests(ctx.store, user.id, params.spaceId, ctx.runner.status.disabledReason, now(ctx)) }));
|
|
291
293
|
route("POST", "/spaces/:spaceId/requests", (ctx, { user, request, params }) => ({
|
|
292
294
|
body: createRequest(ctx.store, user.id, params.spaceId, parseBody(CreateRequestSchema, request.body)),
|
|
293
295
|
}));
|
|
@@ -322,6 +324,12 @@ route("GET", "/settings", (ctx, { user }) => ({ body: getSettings(ctx.store, ctx
|
|
|
322
324
|
route("PUT", "/settings", (ctx, { user, request }) => ({
|
|
323
325
|
body: updateSettings(ctx.store, ctx, user.id, parseBody(UpdateSettingsRequestSchema, request.body)),
|
|
324
326
|
}));
|
|
327
|
+
route("GET", "/spaces/:spaceId/notification-settings", (ctx, { user, params }) => ({
|
|
328
|
+
body: getSpaceNotificationSettings(ctx.store, ctx, user.id, params.spaceId),
|
|
329
|
+
}));
|
|
330
|
+
route("PUT", "/spaces/:spaceId/notification-settings", (ctx, { user, request, params }) => ({
|
|
331
|
+
body: updateSpaceNotificationSettings(ctx.store, ctx, user.id, params.spaceId, parseBody(UpdateSpaceNotificationSettingsRequestSchema, request.body)),
|
|
332
|
+
}));
|
|
325
333
|
// --- Jetons d'agent ---------------------------------------------------------------
|
|
326
334
|
route("GET", "/tokens", (ctx, { user }) => ({ body: listAgentTokens(ctx.store, user.id, now(ctx)) }));
|
|
327
335
|
route("POST", "/tokens", (ctx, { user, request }) => {
|
|
@@ -331,6 +339,19 @@ route("POST", "/tokens", (ctx, { user, request }) => {
|
|
|
331
339
|
return { body: createAgentToken(ctx.store, user.id, input, ctx.publicUrl, now(ctx)) };
|
|
332
340
|
});
|
|
333
341
|
route("POST", "/tokens/:tokenId/revoke", (ctx, { user, params }) => ({ body: revokeAgentToken(ctx.store, user.id, params.tokenId, now(ctx)) }));
|
|
342
|
+
// --- Administration ----------------------------------------------------------------
|
|
343
|
+
route("GET", "/admin/spaces", (ctx, { user }) => ({ body: listAdminSpaces(ctx.store, user.id, ctx.runner.status) }));
|
|
344
|
+
route("POST", "/spaces/:spaceId/runner/run", (ctx, { user, params }) => {
|
|
345
|
+
requireAdmin(ctx.store, user.id);
|
|
346
|
+
const runNow = ctx.runner.runNow;
|
|
347
|
+
if (!runNow)
|
|
348
|
+
throw new ApiError("unavailable", `Runner désactivé : ${ctx.runner.status.disabledReason}`);
|
|
349
|
+
return { body: processQueueNow(ctx.store, user.id, params.spaceId, runNow) };
|
|
350
|
+
});
|
|
351
|
+
route("PUT", "/admin/spaces/:spaceId/runner", (ctx, { user, request, params }) => {
|
|
352
|
+
const input = parseBody(SetRunnerAllowedRequestSchema, request.body);
|
|
353
|
+
return { body: setRunnerAllowed(ctx.store, user.id, params.spaceId, input.allowed) };
|
|
354
|
+
});
|
|
334
355
|
// --- Dictée ------------------------------------------------------------------------
|
|
335
356
|
route("POST", "/transcribe", async (ctx, { request }) => {
|
|
336
357
|
const input = parseBody(TranscribeRequestSchema, request.body);
|
package/dist/src/index.js
CHANGED
|
@@ -12,6 +12,9 @@ Usage :
|
|
|
12
12
|
mcp-organizer --server Site web : interface, API et base de données, sur HOST:PORT.
|
|
13
13
|
mcp-organizer --set-user [email] [--name="Prénom Nom"] [--non-interactive]
|
|
14
14
|
Crée un compte ou remplace son mot de passe.
|
|
15
|
+
mcp-organizer --set-admin <email> [--revoke]
|
|
16
|
+
Accorde (ou retire) le rôle d'administrateur du site à un compte actif.
|
|
17
|
+
Le premier compte créé est administrateur d'office.
|
|
15
18
|
mcp-organizer --setup [options] Installe le site : .env, unité systemd, bloc nginx, premier compte
|
|
16
19
|
(interactif, ou --non-interactive ; --setup --help pour les options).
|
|
17
20
|
mcp-organizer --backup <fichier> [--force]
|
|
@@ -33,7 +36,7 @@ Le mode MCP exige ORGANIZER_URL (URL publique du site, en https hors de la machi
|
|
|
33
36
|
et ORGANIZER_TOKEN (jeton d'agent), fournis par son environnement.
|
|
34
37
|
Voir le README pour la liste complète des variables.`;
|
|
35
38
|
/** Commandes qui ouvrent la base du site ; toute autre invocation démarre le mode MCP. */
|
|
36
|
-
const SITE_COMMANDS = ["--server", "--set-user", "--setup", "--backup"];
|
|
39
|
+
const SITE_COMMANDS = ["--server", "--set-user", "--set-admin", "--setup", "--backup"];
|
|
37
40
|
async function main() {
|
|
38
41
|
const argv = process.argv.slice(2);
|
|
39
42
|
if (argv.includes("--setup") && (argv.includes("--help") || argv.includes("-h"))) {
|
|
@@ -87,6 +90,10 @@ async function main() {
|
|
|
87
90
|
const [{ runSetUser }, { resolveDataDir }] = await Promise.all([import("./cli/set-user.js"), import("./core/data-dir.js")]);
|
|
88
91
|
process.exit(await runSetUser(args, resolveDataDir(rootDir)));
|
|
89
92
|
}
|
|
93
|
+
if (args.includes("--set-admin")) {
|
|
94
|
+
const [{ runSetAdmin }, { resolveDataDir }] = await Promise.all([import("./cli/set-admin.js"), import("./core/data-dir.js")]);
|
|
95
|
+
process.exit(await runSetAdmin(args, resolveDataDir(rootDir)));
|
|
96
|
+
}
|
|
90
97
|
if (args.includes("--backup")) {
|
|
91
98
|
const [{ runBackup }, { resolveDataDir }] = await Promise.all([import("./cli/backup.js"), import("./core/data-dir.js")]);
|
|
92
99
|
process.exit(await runBackup(args, resolveDataDir(rootDir)));
|