@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.
Files changed (45) hide show
  1. package/README.md +71 -20
  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 +34 -0
  7. package/dist/shared/schemas/agent-tools.schema.js +2 -0
  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/requests.js +2 -1
  15. package/dist/src/cli/set-admin.d.ts +1 -0
  16. package/dist/src/cli/set-admin.js +45 -0
  17. package/dist/src/core/accounts.js +2 -1
  18. package/dist/src/core/admin.d.ts +24 -0
  19. package/dist/src/core/admin.js +85 -0
  20. package/dist/src/core/agent-tokens.d.ts +9 -1
  21. package/dist/src/core/agent-tokens.js +25 -2
  22. package/dist/src/core/agent.d.ts +5 -0
  23. package/dist/src/core/agent.js +23 -0
  24. package/dist/src/core/migrations.js +369 -344
  25. package/dist/src/core/notifications.d.ts +7 -1
  26. package/dist/src/core/notifications.js +32 -1
  27. package/dist/src/core/requests.d.ts +1 -1
  28. package/dist/src/core/requests.js +4 -4
  29. package/dist/src/core/store.d.ts +38 -2
  30. package/dist/src/core/store.js +161 -38
  31. package/dist/src/gui-api.d.ts +6 -0
  32. package/dist/src/gui-api.js +25 -4
  33. package/dist/src/index.js +8 -1
  34. package/dist/src/mcp/start.d.ts +1 -1
  35. package/dist/src/mcp/start.js +1 -1
  36. package/dist/src/mcp/tools.js +1 -1
  37. package/dist/src/server/runner.d.ts +51 -0
  38. package/dist/src/server/runner.js +222 -0
  39. package/dist/src/server/start.js +8 -0
  40. package/gui/dist/assets/index-DuoSNpBr.js +70 -0
  41. package/gui/dist/assets/index-duxL0fvO.css +1 -0
  42. package/gui/dist/index.html +2 -2
  43. package/package.json +3 -2
  44. package/gui/dist/assets/index-BVN0agCc.css +0 -1
  45. package/gui/dist/assets/index-DI1_kr81.js +0 -70
@@ -68,6 +68,7 @@ export declare const SettingsResponseSchema: z.ZodObject<{
68
68
  notifyAssignEmail: z.ZodBoolean;
69
69
  notifyCommentApp: z.ZodBoolean;
70
70
  notifyCommentEmail: z.ZodBoolean;
71
+ assistantHistory: z.ZodNumber;
71
72
  email: z.ZodObject<{
72
73
  enabled: z.ZodBoolean;
73
74
  reason: z.ZodNullable<z.ZodString>;
@@ -83,5 +84,35 @@ export declare const UpdateSettingsRequestSchema: z.ZodObject<{
83
84
  notifyAssignEmail: z.ZodOptional<z.ZodBoolean>;
84
85
  notifyCommentApp: z.ZodOptional<z.ZodBoolean>;
85
86
  notifyCommentEmail: z.ZodOptional<z.ZodBoolean>;
87
+ assistantHistory: z.ZodOptional<z.ZodNumber>;
86
88
  }, z.core.$strict>;
87
89
  export type UpdateSettingsRequest = z.infer<typeof UpdateSettingsRequestSchema>;
90
+ /**
91
+ * Notifications de l'utilisateur dans un espace : `custom` dit si l'espace a ses
92
+ * propres préférences ; sinon, les valeurs renvoyées sont celles du compte.
93
+ */
94
+ export declare const SpaceNotificationSettingsResponseSchema: z.ZodObject<{
95
+ notifyAssignApp: z.ZodBoolean;
96
+ notifyAssignEmail: z.ZodBoolean;
97
+ notifyCommentApp: z.ZodBoolean;
98
+ notifyCommentEmail: z.ZodBoolean;
99
+ custom: z.ZodBoolean;
100
+ email: z.ZodObject<{
101
+ enabled: z.ZodBoolean;
102
+ reason: z.ZodNullable<z.ZodString>;
103
+ }, z.core.$strip>;
104
+ }, z.core.$strip>;
105
+ export type SpaceNotificationSettingsResponse = z.infer<typeof SpaceNotificationSettingsResponseSchema>;
106
+ /**
107
+ * Personnalise les notifications d'un espace (`custom: true`, avec les préférences
108
+ * à changer ; les autres partent des préférences en vigueur) ou revient à celles
109
+ * du compte (`custom: false`, sans préférence).
110
+ */
111
+ export declare const UpdateSpaceNotificationSettingsRequestSchema: z.ZodObject<{
112
+ notifyAssignApp: z.ZodOptional<z.ZodBoolean>;
113
+ notifyAssignEmail: z.ZodOptional<z.ZodBoolean>;
114
+ notifyCommentApp: z.ZodOptional<z.ZodBoolean>;
115
+ notifyCommentEmail: z.ZodOptional<z.ZodBoolean>;
116
+ custom: z.ZodBoolean;
117
+ }, z.core.$strict>;
118
+ export type UpdateSpaceNotificationSettingsRequest = z.infer<typeof UpdateSpaceNotificationSettingsRequestSchema>;
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { ActorTypeSchema, NotificationTypeSchema } from "./enums.schema.js";
3
- import { IdSchema, TimestampSchema } from "./entities.schema.js";
3
+ import { AssistantHistorySchema, IdSchema, NotificationPreferencesSchema, TimestampSchema } from "./entities.schema.js";
4
4
  /** Corps des requêtes et des réponses des notifications et des paramètres de la GUI. */
5
5
  /** Nombre de notifications renvoyées par défaut, et au plus, par page. */
6
6
  export const NOTIFICATIONS_PAGE_SIZE = 20;
@@ -42,6 +42,8 @@ export const SettingsResponseSchema = z.object({
42
42
  notifyAssignEmail: z.boolean(),
43
43
  notifyCommentApp: z.boolean(),
44
44
  notifyCommentEmail: z.boolean(),
45
+ /** Nombre d'échanges précédents transmis à l'agent avec un message de l'assistant. */
46
+ assistantHistory: AssistantHistorySchema,
45
47
  /** Envoi des e-mails de notification : `reason` dit pourquoi il n'a pas lieu. */
46
48
  email: z.object({ enabled: z.boolean(), reason: z.string().nullable() }),
47
49
  /** Dictée vocale : `reason` dit pourquoi elle est indisponible. */
@@ -53,6 +55,25 @@ export const UpdateSettingsRequestSchema = z
53
55
  notifyAssignEmail: z.boolean().optional(),
54
56
  notifyCommentApp: z.boolean().optional(),
55
57
  notifyCommentEmail: z.boolean().optional(),
58
+ assistantHistory: AssistantHistorySchema.optional(),
56
59
  })
57
60
  .strict()
58
61
  .refine((patch) => Object.values(patch).some((value) => value !== undefined), "Aucune modification demandée.");
62
+ /**
63
+ * Notifications de l'utilisateur dans un espace : `custom` dit si l'espace a ses
64
+ * propres préférences ; sinon, les valeurs renvoyées sont celles du compte.
65
+ */
66
+ export const SpaceNotificationSettingsResponseSchema = NotificationPreferencesSchema.extend({
67
+ custom: z.boolean(),
68
+ /** Envoi des e-mails de notification : `reason` dit pourquoi il n'a pas lieu. */
69
+ email: z.object({ enabled: z.boolean(), reason: z.string().nullable() }),
70
+ });
71
+ /**
72
+ * Personnalise les notifications d'un espace (`custom: true`, avec les préférences
73
+ * à changer ; les autres partent des préférences en vigueur) ou revient à celles
74
+ * du compte (`custom: false`, sans préférence).
75
+ */
76
+ export const UpdateSpaceNotificationSettingsRequestSchema = NotificationPreferencesSchema.partial()
77
+ .extend({ custom: z.boolean() })
78
+ .strict()
79
+ .refine((patch) => patch.custom || [patch.notifyAssignApp, patch.notifyAssignEmail, patch.notifyCommentApp, patch.notifyCommentEmail].every((value) => value === undefined), "Un espace qui suit les préférences du compte n'a pas de préférences propres.");
@@ -1,5 +1,5 @@
1
1
  import { ClaimRequestBodySchema, ClaimRequestDataSchema, CompleteRequestBodySchema, FailRequestBodySchema, FinishRequestDataSchema, ListRequestsDataSchema, ListRequestsQuerySchema, } from "../../shared/schemas/agent-tools.schema.js";
2
- import { agentActor, ideaData, memberData, recentIdeas, requestContext, requestData, requestLine, requestOfSpace, spaceData, treeData } from "../core/agent.js";
2
+ import { agentActor, ideaData, memberData, recentIdeas, requestContext, requestData, requestHistory, requestLine, requestOfSpace, spaceData, treeData } from "../core/agent.js";
3
3
  import { parseInput, respond } from "./types.js";
4
4
  /** Demandes de l'espace du jeton : liste, prise, fin (réussie ou en échec). */
5
5
  export const requestRoutes = [
@@ -28,6 +28,7 @@ export const requestRoutes = [
28
28
  const request = store.mutate(agentActor(agent, params.requestId), (tx) => tx.claimAgentRequest());
29
29
  return respond(ClaimRequestDataSchema, requestContext(store, agent, request), {
30
30
  request: requestData(store, agent, request),
31
+ history: requestHistory(store, request).map((previous) => requestData(store, agent, previous)),
31
32
  space: spaceData(agent.space),
32
33
  members: memberData(store, agent.space),
33
34
  tasks: treeData(store, agent.space),
@@ -0,0 +1 @@
1
+ export declare function runSetAdmin(argv: string[], dataDir: string): Promise<number>;
@@ -0,0 +1,45 @@
1
+ import { setAdmin } from "../core/admin.js";
2
+ import { Store } from "../core/store.js";
3
+ /**
4
+ * Commande `--set-admin <email> [--revoke]` : accorde le rôle
5
+ * d'administrateur du site à un compte actif, ou le lui retire avec
6
+ * `--revoke`. Le dernier administrateur ne peut pas perdre son rôle.
7
+ */
8
+ const USAGE = "Usage : mcp-organizer --set-admin <email> [--revoke]";
9
+ class UsageError extends Error {
10
+ }
11
+ function parseArgs(argv) {
12
+ const rest = argv.slice(argv.indexOf("--set-admin") + 1);
13
+ const unknown = rest.filter((arg) => arg.startsWith("-") && arg !== "--revoke");
14
+ if (unknown.length > 0)
15
+ throw new UsageError(`Option inconnue : ${unknown.join(", ")}.`);
16
+ const positional = rest.filter((arg) => !arg.startsWith("-"));
17
+ if (positional.length === 0)
18
+ throw new UsageError("--set-admin attend l'adresse e-mail du compte.");
19
+ if (positional.length > 1)
20
+ throw new UsageError(`Une seule adresse à la fois (reçu : ${positional.join(", ")}).`);
21
+ return { email: positional[0], revoke: rest.includes("--revoke") };
22
+ }
23
+ export async function runSetAdmin(argv, dataDir) {
24
+ let args;
25
+ try {
26
+ args = parseArgs(argv);
27
+ }
28
+ catch (err) {
29
+ process.stderr.write(`${err?.message ?? err}\n${USAGE}\n`);
30
+ return 1;
31
+ }
32
+ const store = Store.open(dataDir);
33
+ try {
34
+ const user = setAdmin(store, args.email, !args.revoke);
35
+ process.stdout.write(args.revoke ? `${user.email} n'est plus administrateur du site.\n` : `${user.email} (${user.displayName}) est administrateur du site.\n`);
36
+ return 0;
37
+ }
38
+ catch (err) {
39
+ process.stderr.write(`${err?.message ?? err}\n`);
40
+ return 1;
41
+ }
42
+ finally {
43
+ store.close();
44
+ }
45
+ }
@@ -147,7 +147,8 @@ export async function setAccount(store, input) {
147
147
  tx.discardUnusedAccessLinks(user.id);
148
148
  return { created: false, user };
149
149
  }
150
- const created = tx.createUser({ id: userId, email: normalized, displayName: name, passwordHash, status: "active" });
150
+ // Le premier compte du site en est l'administrateur.
151
+ const created = tx.createUser({ id: userId, email: normalized, displayName: name, passwordHash, status: "active", isAdmin: tx.store.countUsers() === 0 });
151
152
  ensurePersonalSpace(tx, created.id);
152
153
  return { created: true, user: created };
153
154
  });
@@ -0,0 +1,24 @@
1
+ import type { AdminSpaceView, AdminSpacesResponse, RunQueueResponse, RunnerStatus } from "../../shared/schemas/admin-api.schema.js";
2
+ import type { User } from "../../shared/schemas/entities.schema.js";
3
+ import type { Store } from "./store.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 declare function requireAdmin(store: Store, userId: string): User;
11
+ /** Tous les comptes, chacun avec les espaces dont il est propriétaire, et l'état du runner. */
12
+ export declare function listAdminSpaces(store: Store, userId: string, runner: RunnerStatus): AdminSpacesResponse;
13
+ /** Autorise ou interdit le runner sur un espace. */
14
+ export declare function setRunnerAllowed(store: Store, userId: string, spaceId: string, allowed: boolean): AdminSpaceView;
15
+ /**
16
+ * Fait traiter une fois la file d'un espace par le runner, qu'il y soit
17
+ * autorisé ou non ; refus quand aucune demande n'y attend.
18
+ */
19
+ export declare function processQueueNow(store: Store, userId: string, spaceId: string, runNow: (spaceId: string) => void): RunQueueResponse;
20
+ /**
21
+ * Accorde ou retire le rôle d'administrateur au compte actif `email`. Le
22
+ * dernier administrateur garde son rôle : le site n'en reste jamais dépourvu.
23
+ */
24
+ export declare function setAdmin(store: Store, email: string, isAdmin: boolean): User;
@@ -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.");