@dotdrelle/wiki-manager 0.15.34 → 0.15.38

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 (41) hide show
  1. package/.env.example +8 -5
  2. package/README.md +42 -0
  3. package/agents.docker-compose.yml +11 -16
  4. package/docker-compose.yml +9 -8
  5. package/package.json +2 -2
  6. package/src/agent/graph.js +20 -0
  7. package/src/cli/wiki-manager.js +20 -0
  8. package/src/commands/slash.js +85 -13
  9. package/src/commands/slash.test.js +85 -3
  10. package/src/core/agentsCompose.js +86 -1
  11. package/src/core/buildInfo.json +2 -2
  12. package/src/core/compose.js +24 -2
  13. package/src/core/dockerCompose.test.js +18 -0
  14. package/src/core/env.js +2 -1
  15. package/src/core/env.test.js +14 -0
  16. package/src/core/googleGrants.js +38 -0
  17. package/src/core/googleGrants.test.js +59 -0
  18. package/src/core/mcp.js +1 -1
  19. package/src/core/mcp.test.js +1 -0
  20. package/src/core/mcpEndpoints.js +96 -0
  21. package/src/core/mcpEndpoints.test.js +126 -0
  22. package/src/core/modelFetch.js +241 -27
  23. package/src/core/modelFetch.test.js +213 -0
  24. package/src/core/profileServiceStatus.test.js +146 -0
  25. package/src/core/wikiSetup.js +11 -2
  26. package/src/core/wikiWorkspace.test.js +16 -3
  27. package/src/orchestrator/agentRegistry.js +13 -1
  28. package/src/orchestrator/agentRegistry.test.js +20 -0
  29. package/src/runtime/lifecycle.js +105 -3
  30. package/src/runtime/lifecycle.test.js +68 -0
  31. package/src/runtime/server.js +28 -0
  32. package/src/shell/LeftPane.tsx +55 -41
  33. package/src/shell/SetupWizard.tsx +416 -116
  34. package/src/shell/repl.js +61 -12
  35. package/src/shell/repl.test.js +48 -19
  36. package/src/shell/setupWizardDiscovery.test.js +48 -0
  37. package/src/shell/setupWizardPlaceholders.test.js +15 -2
  38. package/src/shell/setupWizardSuggestions.test.js +36 -2
  39. package/src/shell/wrapText.js +57 -0
  40. package/src/shell/wrapText.test.js +48 -0
  41. package/wiki-workspace +122 -32
@@ -139,20 +139,52 @@ function WelcomeHelpPanels(props: { width: number }) {
139
139
  );
140
140
  }
141
141
 
142
- const COPY_BTN = ' [ copy ]';
142
+ const COPY_BTN = ' copy';
143
+
144
+ /**
145
+ * Barre de gouttière, préfixée à chaque ligne d'un message.
146
+ *
147
+ * Remplace le filet horizontal pleine largeur qui ouvrait chaque message.
148
+ * Celui-ci était l'élément le plus encré de l'écran, répété à chaque tour, et
149
+ * il séparait là où il fallait grouper : rien n'indiquait où un bloc finissait,
150
+ * seulement où le suivant commençait. Une barre colorée porte la même
151
+ * information — à qui est le tour, où le bloc commence ET où il s'arrête — pour
152
+ * un caractère par ligne au lieu de toute la largeur.
153
+ */
154
+ const GUTTER = '▌ ';
155
+
156
+ function clockLabel(at?: number): string {
157
+ if (!Number.isFinite(at)) return '';
158
+ const date = new Date(at as number);
159
+ return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
160
+ }
143
161
 
144
- function messageHeaderSegments(role: string, columns: number): Segment[] {
145
- const label = `[${roleLabel(role)}]`;
146
- const left = '── ';
147
- const actionsLength = COPY_BTN.length;
148
- const rightLength = Math.max(2, columns - left.length - label.length - 1 - actionsLength);
162
+ /**
163
+ * En-tête d'un message : gouttière, locuteur, heure, et `copy` à droite.
164
+ *
165
+ * Le bouton de copie était collé au libellé, précédé d'un filet de soixante-dix
166
+ * tirets. Poussé au bord droit et dégrisé, il reste atteignable sans peser sur
167
+ * la ligne. L'heure était reléguée dans une colonne latérale, détachée de ce
168
+ * qu'elle datait ; elle coûte cinq caractères ici et devient lisible.
169
+ */
170
+ function messageHeaderSegments(role: string, columns: number, at?: number): Segment[] {
171
+ const time = clockLabel(at);
172
+ const label = roleLabel(role);
173
+ const used = GUTTER.length + label.length + (time ? time.length + 2 : 0);
174
+ const pad = Math.max(1, columns - used - COPY_BTN.length);
149
175
  return [
150
- { text: left, color: '#4B5563' },
176
+ { text: GUTTER, color: roleColor(role) },
151
177
  { text: label, color: roleColor(role) },
152
- { text: ' ' + '─'.repeat(rightLength), color: '#4B5563' },
178
+ ...(time ? [{ text: ` ${time}`, color: '#4B5563' }] : []),
179
+ { text: ' '.repeat(pad), color: '#4B5563' },
153
180
  ];
154
181
  }
155
182
 
183
+ /** Préfixe la gouttière à une ligne de corps de message. */
184
+ function withGutter(line: RenderedLine, role: string): RenderedLine {
185
+ return { ...line, segments: [{ text: GUTTER, color: roleColor(role) }, ...line.segments] };
186
+ }
187
+
156
188
  // Split " /cmd [<arg>...] description" into two segments.
157
189
  // Uses non-greedy to stop at the first double-space separator.
158
190
  function splitCmdDesc(line: string): [string, string] | null {
@@ -413,21 +445,24 @@ function headerGroupKey(role: string) {
413
445
  return isDonnaRole(role) ? 'donna' : roleLabel(role);
414
446
  }
415
447
 
416
- function conversationLines(messages: Array<{ role: string; content: string }>, columns: number): RenderedLine[] {
448
+ function conversationLines(messages: Array<{ role: string; content: string; at?: number }>, columns: number): RenderedLine[] {
417
449
  return messages.flatMap((message, index) => {
418
450
  const raw = String(message.content || '');
419
451
  const previous = index > 0 ? messages[index - 1] : null;
420
452
  const showHeader = !previous || headerGroupKey(previous.role) !== headerGroupKey(message.role);
453
+ // Plus de ligne vide sous l'en-tête : le filet et son rembourrage
454
+ // coûtaient deux lignes par message, en plus de la ligne de séparation.
455
+ // La gouttière rattache visuellement l'en-tête à son corps, la respiration
456
+ // entre tours suffit.
421
457
  const headerLines: RenderedLine[] = showHeader
422
- ? [
423
- {
424
- segments: messageHeaderSegments(message.role, columns),
425
- copyContent: raw,
426
- },
427
- { segments: [{ text: ' ', color: '#D6DEE8' }] },
428
- ]
458
+ ? [{ segments: messageHeaderSegments(message.role, columns, message.at), copyContent: raw }]
429
459
  : [];
430
- const contentColumns = message.role === 'user' ? Math.max(12, Math.floor(columns * 0.6) - 2) : columns;
460
+ // Les messages utilisateur étaient repliés à 60 % de la largeur pour tenir
461
+ // dans une bulle alignée à droite. Cela coupait les phrases en plein milieu
462
+ // et faisait repartir l'œil de deux colonnes différentes selon le locuteur.
463
+ // La couleur de gouttière distingue les tours ; la colonne de lecture reste
464
+ // unique. On retire aussi la largeur de la gouttière du texte disponible.
465
+ const contentColumns = Math.max(12, columns - GUTTER.length);
431
466
  if (isStatusOutput(message)) {
432
467
  const statusLines: RenderedLine[] = [
433
468
  ...headerLines,
@@ -454,15 +489,9 @@ function conversationLines(messages: Array<{ role: string; content: string }>, c
454
489
  lines.push({ text: line, isCode: inFence });
455
490
  }
456
491
  const renderedBody = renderMarkdownLines(lines, message.role, contentColumns);
457
- const userBubbleWidth = message.role === 'user'
458
- ? Math.min(
459
- Math.max(1, ...renderedBody.map((line) => line.segments.reduce((sum, segment) => sum + segment.text.length, 0))) + 2,
460
- Math.max(12, Math.floor(columns * 0.6)),
461
- )
462
- : undefined;
463
492
  const bodyLines: RenderedLine[] = [
464
493
  ...headerLines,
465
- ...renderedBody.map((line) => ({ ...line, userBubbleWidth })),
494
+ ...renderedBody.map((line) => withGutter(line, message.role)),
466
495
  { segments: [{ text: ' ', color: '#D6DEE8' }] },
467
496
  ];
468
497
  return bodyLines.map((line) => ({ ...line, role: message.role }));
@@ -470,7 +499,7 @@ function conversationLines(messages: Array<{ role: string; content: string }>, c
470
499
  }
471
500
 
472
501
  export function ConversationView(props: {
473
- messages: Array<{ role: string; content: string }>;
502
+ messages: Array<{ role: string; content: string; at?: number }>;
474
503
  rows: number;
475
504
  columns: number;
476
505
  scroll: number;
@@ -536,21 +565,6 @@ export function ConversationView(props: {
536
565
  </For>
537
566
  <text fg="#4B5563" content={COPY_BTN} onMouseUp={() => props.onCopy?.(line.copyContent!)} />
538
567
  </box>
539
- ) : line.role === 'user' && line.userBubbleWidth ? (
540
- <box height={1} flexDirection="row" overflow="hidden">
541
- <box flexGrow={1} />
542
- <box
543
- width={line.userBubbleWidth}
544
- height={1}
545
- flexDirection="row"
546
- flexShrink={1}
547
- justifyContent="flex-start"
548
- paddingX={1}
549
- overflow="hidden"
550
- >
551
- <text>{styledSegments(line.segments)}</text>
552
- </box>
553
- </box>
554
568
  ) : line.status ? (
555
569
  <box height={1} flexDirection="row" gap={2} overflow="hidden">
556
570
  <box
@@ -786,7 +800,7 @@ export function LeftPane(props: {
786
800
  statusLine: string;
787
801
  hintLine?: string | null;
788
802
  showWelcome: boolean;
789
- messages: Array<{ role: string; content: string }>;
803
+ messages: Array<{ role: string; content: string; at?: number }>;
790
804
  prompt: string;
791
805
  input: string;
792
806
  busy: boolean;