@imenam/mcp-organizer 1.0.2 → 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.
Files changed (50) hide show
  1. package/README.md +609 -548
  2. package/dist/shared/schemas/admin-api.schema.d.ts +101 -0
  3. package/dist/shared/schemas/admin-api.schema.js +39 -0
  4. package/dist/shared/schemas/agent-api.schema.d.ts +2 -0
  5. package/dist/shared/schemas/agent-api.schema.js +4 -0
  6. package/dist/shared/schemas/agent-tools.schema.d.ts +40 -1
  7. package/dist/shared/schemas/agent-tools.schema.js +8 -1
  8. package/dist/shared/schemas/api.schema.d.ts +2 -0
  9. package/dist/shared/schemas/api.schema.js +2 -0
  10. package/dist/shared/schemas/entities.schema.d.ts +27 -1
  11. package/dist/shared/schemas/entities.schema.js +21 -2
  12. package/dist/shared/schemas/notifications-api.schema.d.ts +31 -0
  13. package/dist/shared/schemas/notifications-api.schema.js +22 -1
  14. package/dist/src/agent-api/notifications.js +1 -0
  15. package/dist/src/agent-api/requests.js +2 -1
  16. package/dist/src/cli/set-admin.d.ts +1 -0
  17. package/dist/src/cli/set-admin.js +45 -0
  18. package/dist/src/cli/watch.d.ts +0 -3
  19. package/dist/src/cli/watch.js +1 -9
  20. package/dist/src/core/accounts.js +2 -1
  21. package/dist/src/core/admin.d.ts +24 -0
  22. package/dist/src/core/admin.js +85 -0
  23. package/dist/src/core/agent-tokens.d.ts +9 -1
  24. package/dist/src/core/agent-tokens.js +25 -2
  25. package/dist/src/core/agent.d.ts +5 -0
  26. package/dist/src/core/agent.js +23 -0
  27. package/dist/src/core/migrations.js +369 -344
  28. package/dist/src/core/notifications.d.ts +7 -1
  29. package/dist/src/core/notifications.js +32 -1
  30. package/dist/src/core/requests.d.ts +1 -1
  31. package/dist/src/core/requests.js +4 -4
  32. package/dist/src/core/store.d.ts +38 -2
  33. package/dist/src/core/store.js +161 -38
  34. package/dist/src/gui-api.d.ts +6 -0
  35. package/dist/src/gui-api.js +25 -4
  36. package/dist/src/index.js +8 -1
  37. package/dist/src/mcp/notifications.d.ts +22 -5
  38. package/dist/src/mcp/notifications.js +95 -9
  39. package/dist/src/mcp/start.d.ts +1 -1
  40. package/dist/src/mcp/start.js +39 -32
  41. package/dist/src/mcp/tools.js +1 -1
  42. package/dist/src/server/runner.d.ts +51 -0
  43. package/dist/src/server/runner.js +222 -0
  44. package/dist/src/server/start.js +8 -0
  45. package/gui/dist/assets/index-DuoSNpBr.js +70 -0
  46. package/gui/dist/assets/index-duxL0fvO.css +1 -0
  47. package/gui/dist/index.html +2 -2
  48. package/package.json +3 -2
  49. package/gui/dist/assets/index-BVN0agCc.css +0 -1
  50. package/gui/dist/assets/index-DI1_kr81.js +0 -70
@@ -0,0 +1,85 @@
1
+ import { normalizeEmail } from "./accounts.js";
2
+ import { userLabel } from "./agent.js";
3
+ import { ForbiddenError, InvariantError, NotFoundError, OrganizerError } from "./errors.js";
4
+ /**
5
+ * Administration du site : rôle d'administrateur des comptes, et
6
+ * autorisation du runner par espace. Seul un administrateur voit tous les
7
+ * comptes et tous les espaces, et décide des espaces où le runner travaille.
8
+ */
9
+ /** L'utilisateur est administrateur ; sinon, refus. */
10
+ export function requireAdmin(store, userId) {
11
+ const user = store.getUser(userId);
12
+ if (!user.isAdmin)
13
+ throw new ForbiddenError("Réservé aux administrateurs du site.");
14
+ return user;
15
+ }
16
+ function spaceView(store, space) {
17
+ return {
18
+ id: space.id,
19
+ name: space.name,
20
+ kind: space.kind,
21
+ keyPrefix: space.keyPrefix,
22
+ runnerAllowed: space.runnerAllowed,
23
+ members: store.listMemberships(space.id).map((membership) => {
24
+ const member = store.getUser(membership.userId);
25
+ return { id: member.id, name: userLabel(member), email: member.email };
26
+ }),
27
+ };
28
+ }
29
+ /** Tous les comptes, chacun avec les espaces dont il est propriétaire, et l'état du runner. */
30
+ export function listAdminSpaces(store, userId, runner) {
31
+ requireAdmin(store, userId);
32
+ const spaces = store.listAllSpaces();
33
+ const users = store.listUsers().map((user) => ({
34
+ id: user.id,
35
+ email: user.email,
36
+ displayName: user.displayName,
37
+ status: user.status,
38
+ isAdmin: user.isAdmin,
39
+ spaces: spaces.filter((space) => space.ownerId === user.id).map((space) => spaceView(store, space)),
40
+ }));
41
+ return { users, allowedCount: spaces.filter((space) => space.runnerAllowed).length, spaceCount: spaces.length, runner };
42
+ }
43
+ /** Autorise ou interdit le runner sur un espace. */
44
+ export function setRunnerAllowed(store, userId, spaceId, allowed) {
45
+ return store.mutate({ type: "user", userId }, (tx) => {
46
+ requireAdmin(tx.store, userId);
47
+ if (!tx.store.findSpace(spaceId))
48
+ throw new NotFoundError("Espace introuvable.");
49
+ return spaceView(tx.store, tx.setSpaceRunnerAllowed(spaceId, allowed));
50
+ });
51
+ }
52
+ /**
53
+ * Fait traiter une fois la file d'un espace par le runner, qu'il y soit
54
+ * autorisé ou non ; refus quand aucune demande n'y attend.
55
+ */
56
+ export function processQueueNow(store, userId, spaceId, runNow) {
57
+ requireAdmin(store, userId);
58
+ const space = store.findSpace(spaceId);
59
+ if (!space)
60
+ throw new NotFoundError("Espace introuvable.");
61
+ const pendingCount = store.countPendingRequestsOfSpace(spaceId);
62
+ if (pendingCount === 0)
63
+ throw new InvariantError(`Aucune demande en attente dans « ${space.name} ».`);
64
+ runNow(spaceId);
65
+ return { pendingCount };
66
+ }
67
+ /**
68
+ * Accorde ou retire le rôle d'administrateur au compte actif `email`. Le
69
+ * dernier administrateur garde son rôle : le site n'en reste jamais dépourvu.
70
+ */
71
+ export function setAdmin(store, email, isAdmin) {
72
+ const normalized = normalizeEmail(email);
73
+ const target = store.findUserByEmail(normalized);
74
+ if (!target)
75
+ throw new NotFoundError(`Aucun compte pour ${normalized} : créez-le d'abord (--set-user ${normalized}).`);
76
+ return store.mutate({ type: "user", userId: target.id }, (tx) => {
77
+ const user = tx.store.getUser(target.id);
78
+ if (isAdmin && user.status !== "active")
79
+ throw new OrganizerError(`Le compte ${normalized} n'est pas actif : activez-le d'abord (--set-user ${normalized}).`);
80
+ if (!isAdmin && user.isAdmin && tx.store.countAdmins() === 1) {
81
+ throw new InvariantError(`${normalized} est le seul administrateur : désignez-en un autre (--set-admin <email>) avant de lui retirer ce rôle.`);
82
+ }
83
+ return tx.setUserAdmin(user.id, isAdmin);
84
+ });
85
+ }
@@ -7,7 +7,7 @@ export declare function generateAgentToken(): string;
7
7
  export declare function hashAgentToken(token: string): string;
8
8
  /** État d'un jeton à l'instant `now` ; la révocation l'emporte sur l'expiration, l'expiration sur la perte d'accès. */
9
9
  export declare function tokenState(store: Store, token: AgentToken, now: Date): TokenState;
10
- /** Jetons de l'utilisateur : actifs d'abord, puis du plus récent au plus ancien. */
10
+ /** Jetons de l'utilisateur, sans ceux du runner : actifs d'abord, puis du plus récent au plus ancien. */
11
11
  export declare function listAgentTokens(store: Store, userId: string, now?: Date): TokenListResponse;
12
12
  /**
13
13
  * Crée un jeton de l'utilisateur sur un espace dont il est membre ; renvoie
@@ -15,6 +15,14 @@ export declare function listAgentTokens(store: Store, userId: string, now?: Date
15
15
  * relié au site `publicUrl`.
16
16
  */
17
17
  export declare function createAgentToken(store: Store, userId: string, input: CreateTokenRequest, publicUrl: string, now?: Date): CreatedTokenResponse;
18
+ /** Nom du jeton du runner, affiché dans la page Agent et l'historique. */
19
+ export declare const RUNNER_TOKEN_NAME = "Runner";
20
+ /**
21
+ * Donne une nouvelle valeur au jeton du runner de l'espace, en le créant au
22
+ * nom du propriétaire de l'espace s'il n'existe pas, et la renvoie. L'ancienne
23
+ * valeur est aussitôt refusée.
24
+ */
25
+ export declare function issueRunnerToken(store: Store, spaceId: string): string;
18
26
  /** Révoque un jeton de l'utilisateur ; un jeton d'un autre est introuvable. */
19
27
  export declare function revokeAgentToken(store: Store, userId: string, tokenId: string, now?: Date): AgentTokenView;
20
28
  /** Jeton résolu par l'API agent : le jeton, son propriétaire et l'espace couvert. */
@@ -49,9 +49,12 @@ function tokenView(store, token, now) {
49
49
  };
50
50
  }
51
51
  const STATE_ORDER = { active: 0, inactive: 1, expired: 2, revoked: 3 };
52
- /** Jetons de l'utilisateur : actifs d'abord, puis du plus récent au plus ancien. */
52
+ /** Jetons de l'utilisateur, sans ceux du runner : actifs d'abord, puis du plus récent au plus ancien. */
53
53
  export function listAgentTokens(store, userId, now = new Date()) {
54
- const tokens = store.listAgentTokensOf(userId).map((token) => tokenView(store, token, now));
54
+ const tokens = store
55
+ .listAgentTokensOf(userId)
56
+ .filter((token) => !token.isRunner)
57
+ .map((token) => tokenView(store, token, now));
55
58
  tokens.sort((a, b) => STATE_ORDER[a.state] - STATE_ORDER[b.state] || b.createdAt.localeCompare(a.createdAt));
56
59
  return { tokens };
57
60
  }
@@ -74,6 +77,26 @@ export function createAgentToken(store, userId, input, publicUrl, now = new Date
74
77
  });
75
78
  return { token: tokenView(store, token, now), value, mcpJson: mcpJsonText(buildAgentEntry({ url: publicUrl, token: value })) };
76
79
  }
80
+ /** Nom du jeton du runner, affiché dans la page Agent et l'historique. */
81
+ export const RUNNER_TOKEN_NAME = "Runner";
82
+ /**
83
+ * Donne une nouvelle valeur au jeton du runner de l'espace, en le créant au
84
+ * nom du propriétaire de l'espace s'il n'existe pas, et la renvoie. L'ancienne
85
+ * valeur est aussitôt refusée.
86
+ */
87
+ export function issueRunnerToken(store, spaceId) {
88
+ const value = generateAgentToken();
89
+ const prefix = value.slice(0, AGENT_TOKEN_PREFIX.length + PREFIX_RANDOM_CHARS);
90
+ const space = store.getSpace(spaceId);
91
+ store.mutate(asUser(space.ownerId), (tx) => {
92
+ const existing = tx.store.findRunnerToken(spaceId);
93
+ if (existing)
94
+ tx.rotateRunnerToken(existing.id, prefix, hashAgentToken(value));
95
+ else
96
+ tx.createAgentToken({ spaceId, name: RUNNER_TOKEN_NAME, prefix, tokenHash: hashAgentToken(value), expiresAt: null, isRunner: true });
97
+ });
98
+ return value;
99
+ }
77
100
  /** Révoque un jeton de l'utilisateur ; un jeton d'un autre est introuvable. */
78
101
  export function revokeAgentToken(store, userId, tokenId, now = new Date()) {
79
102
  const token = store.mutate(asUser(userId), (tx) => {
@@ -44,6 +44,11 @@ export declare function memberLines(store: Store, space: Space): string[];
44
44
  export declare function ago(iso: string, now?: Date): string;
45
45
  /** Résumé d'une demande pour list_requests. */
46
46
  export declare function requestLine(store: Store, agent: AgentCaller, request: AgentRequest): string;
47
+ /**
48
+ * Échanges précédents de l'auteur d'un message dans l'espace visé, du plus ancien
49
+ * au plus récent, au nombre fixé dans ses paramètres ; aucun pour les autres types.
50
+ */
51
+ export declare function requestHistory(store: Store, request: AgentRequest): AgentRequest[];
47
52
  /** Contexte complet d'une demande, renvoyé par claim_request. */
48
53
  export declare function requestContext(store: Store, agent: AgentCaller, request: AgentRequest): string;
49
54
  /** Idées actives récentes de la boîte à idées d'un espace. */
@@ -168,6 +168,23 @@ const KIND_GUIDANCE = {
168
168
  test_review: "A test review written by a tester of the context task: feed it back into the existing tasks (status, checklist, comments, priorities) or create a task for each problem found.",
169
169
  idea_promotion: "Promote the context idea into a well-formed task (clear title, description, type, priority, parent if relevant) with promote_idea.",
170
170
  };
171
+ /**
172
+ * Échanges précédents de l'auteur d'un message dans l'espace visé, du plus ancien
173
+ * au plus récent, au nombre fixé dans ses paramètres ; aucun pour les autres types.
174
+ */
175
+ export function requestHistory(store, request) {
176
+ if (request.kind !== "message")
177
+ return [];
178
+ const limit = store.getUserSettings(request.authorId).assistantHistory;
179
+ return store.listClosedRequestsOfAuthorBefore(request.targetSpaceId, request.authorId, request, limit);
180
+ }
181
+ /** Lignes d'un échange précédent : demande de l'auteur, puis réponse ou raison de l'échec. */
182
+ function historyLines(store, request) {
183
+ const task = request.contextTaskId === null ? null : store.findTask(request.contextTaskId);
184
+ const about = [request.kind !== "message" ? request.kind : null, task ? `context ${taskKey(store, task)}` : null].filter(Boolean).join(", ");
185
+ const answer = request.status === "done" ? `Reply: ${request.reply ?? ""}` : `Failed: ${request.failureReason ?? ""}`;
186
+ return [`- ${ago(request.createdAt)}${about ? ` (${about})` : ""}: ${request.text.trim()}`, ` ${answer.trim()}`];
187
+ }
171
188
  /** Contexte complet d'une demande, renvoyé par claim_request. */
172
189
  export function requestContext(store, agent, request) {
173
190
  const author = store.getUser(request.authorId);
@@ -201,6 +218,12 @@ export function requestContext(store, agent, request) {
201
218
  if (idea)
202
219
  lines.push(`Context idea: ${idea.id} "${idea.text}".`);
203
220
  }
221
+ const history = requestHistory(store, request);
222
+ if (history.length > 0) {
223
+ lines.push("", `Previous exchanges of the author in this space (oldest first, ${history.length} most recent) — use them to understand the message:`);
224
+ for (const previous of history)
225
+ lines.push(...historyLines(store, previous));
226
+ }
204
227
  lines.push("", "Text:", request.text, "");
205
228
  lines.push(...spaceContextLines(store, target));
206
229
  lines.push("", "Next: read with get_task / search_tasks, act with the write tools (pass this request_id), then call complete_request with a short reply in the author's language.");