living-ai-documentation 2.1.0 → 2.3.0

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.
@@ -29,12 +29,30 @@
29
29
  "nav.export_all_pdf": "Exporter tous les documents en PDF",
30
30
  "nav.new_folder": "Nouveau dossier",
31
31
  "nav.new_document": "Nouveau document",
32
+ "nav.ai_agents": "Agents IA",
32
33
  "nav.word_cloud": "☁ Nuage de mots",
33
34
  "nav.diagram": "◇ Diagramme",
34
35
  "nav.ai_context": "Contexte IA",
35
36
  "nav.files": "📁 Fichiers joints",
36
37
  "nav.admin": "⚙ Admin",
37
38
 
39
+ "agents.title": "Agents IA",
40
+ "agents.subtitle": "Lancer un agent configuré dans le workspace.",
41
+ "agents.empty": "Aucun agent workspace n'est encore configuré.",
42
+ "agents.ready": "Prêt",
43
+ "agents.requires_input": "Input requis",
44
+ "agents.not_ready": "Configuration incomplète",
45
+ "agents.no_provider": "Aucun provider LLM",
46
+ "agents.input_title": "Input agent",
47
+ "agents.input_hint": "Renseignez l'input nécessaire à cette exécution.",
48
+ "agents.run": "Lancer l'agent",
49
+ "agents.loading": "Exécution de l'agent…",
50
+ "agents.success": "Exécution terminée",
51
+ "agents.failure": "Échec de l'exécution",
52
+ "agents.open_document": "Ouvrir le document",
53
+ "agents.load_failed": "Impossible de charger les agents workspace.",
54
+ "agents.run_failed": "Échec de l'exécution de l'agent.",
55
+
38
56
  "files.title": "📁 Fichiers joints",
39
57
  "files.empty": "Aucun fichier — téléversez-en un depuis un document (trombone, glisser-déposer ou coller).",
40
58
  "files.replace": "Remplacer",
@@ -591,7 +609,26 @@
591
609
  "diagram.toolbar.shape_editor": "Éditeur de formes personnalisées",
592
610
  "diagram.toolbar.arrow": "Flèche (F)",
593
611
  "diagram.toolbar.delete": "Supprimer (Suppr)",
612
+ "diagram.toolbar.defaults": "Valeurs par défaut — configurer les tailles, couleurs et styles par défaut",
594
613
  "diagram.toolbar.align_guides": "Guides d'alignement — indique l'alignement entre formes du même type",
614
+ "diagram.defaults.title": "Valeurs par défaut",
615
+ "diagram.defaults.section.arrows": "Flèches",
616
+ "diagram.defaults.section.shapes": "Formes",
617
+ "diagram.defaults.field.direction": "Direction",
618
+ "diagram.defaults.field.style": "Style",
619
+ "diagram.defaults.field.font_size": "Taille police",
620
+ "diagram.defaults.field.width": "L",
621
+ "diagram.defaults.field.height": "H",
622
+ "diagram.defaults.field.color": "Couleur",
623
+ "diagram.defaults.style.solid": "Plein",
624
+ "diagram.defaults.style.dashed": "Pointillé",
625
+ "diagram.defaults.shape.box": "Rectangle",
626
+ "diagram.defaults.shape.ellipse": "Ellipse",
627
+ "diagram.defaults.shape.circle": "Cercle",
628
+ "diagram.defaults.shape.database": "Base de données",
629
+ "diagram.defaults.shape.actor": "Acteur",
630
+ "diagram.defaults.shape.postit": "Post-it",
631
+ "diagram.defaults.shape.text_free": "Texte libre",
595
632
  "diagram.toolbar.grid": "Grille / Magnétisme (G)",
596
633
  "diagram.toolbar.edge_style": "Basculer flèches courbes / droites",
597
634
  "diagram.toolbar.resize_corner": "Mode redimensionnement : coin fixe (cliquer pour centre fixe)",
@@ -609,6 +646,8 @@
609
646
  "diagram.sidebar.empty": "Aucun diagramme",
610
647
  "diagram.sidebar.delete_title": "Supprimer",
611
648
 
649
+ "diagram.node_panel.color": "Couleur de la forme",
650
+ "diagram.node_panel.save_as_default": "Enregistrer comme valeur par défaut pour ce type de forme",
612
651
  "diagram.node_panel.lock": "Verrouiller la sélection",
613
652
  "diagram.node_panel.unlock": "Déverrouiller la sélection",
614
653
  "diagram.node_panel.edit_label": "Modifier le texte (double-clic)",
@@ -648,6 +687,9 @@
648
687
  "diagram.link_panel.save_btn": "Enregistrer",
649
688
  "diagram.link_panel.remove_btn": "Retirer",
650
689
 
690
+ "diagram.edge_panel.font_size_value": "Taille de police",
691
+ "diagram.edge_panel.color": "Couleur de la flèche",
692
+ "diagram.edge_panel.save_as_default": "Enregistrer comme valeur par défaut pour les flèches",
651
693
  "diagram.edge_panel.lock": "Verrouiller la flèche",
652
694
  "diagram.edge_panel.unlock": "Déverrouiller la flèche",
653
695
  "diagram.edge_panel.no_arrow": "Trait simple",
@@ -58,6 +58,7 @@
58
58
  <script defer src="/accuracy-gauge.js"></script>
59
59
  <script defer src="/validate.js"></script>
60
60
  <script defer src="/export.js"></script>
61
+ <script defer src="/agents.js"></script>
61
62
  <script defer src="/modals/diagram-link-modal.js"></script>
62
63
  <script defer src="/modals/new-folder-modal.js"></script>
63
64
  <script defer src="/modals/new-doc-modal.js"></script>
@@ -557,6 +558,16 @@
557
558
  Workspace
558
559
  </a>
559
560
 
561
+ <!-- AI Agents -->
562
+ <button
563
+ id="ai-agents-btn"
564
+ type="button"
565
+ data-i18n="nav.ai_agents"
566
+ class="h-[34px] px-3 rounded-[9px] border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 text-[13px] font-bold text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors inline-flex items-center"
567
+ >
568
+ Agents IA
569
+ </button>
570
+
560
571
  <!-- Word Cloud -->
561
572
  <button
562
573
  onclick="openWordCloud()"
@@ -3111,6 +3122,117 @@
3111
3122
  </div>
3112
3123
  </div>
3113
3124
 
3125
+ <!-- ── AI agents launch modal ── -->
3126
+ <div
3127
+ id="ai-agents-modal"
3128
+ class="hidden fixed inset-0 z-[55] bg-black/45 flex items-center justify-center p-4"
3129
+ onclick="if(event.target===this)closeAiAgentsModal()"
3130
+ >
3131
+ <div
3132
+ class="w-full max-w-4xl max-h-[82vh] bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-2xl flex flex-col overflow-hidden"
3133
+ onclick="event.stopPropagation()"
3134
+ >
3135
+ <div class="px-5 py-4 border-b border-gray-200 dark:border-gray-800 flex items-start justify-between gap-4">
3136
+ <div>
3137
+ <h2 class="font-semibold text-base text-gray-900 dark:text-gray-100" data-i18n="agents.title">Agents IA</h2>
3138
+ <p class="text-xs text-gray-500 dark:text-gray-400 mt-1" data-i18n="agents.subtitle">
3139
+ Launch a configured workspace agent.
3140
+ </p>
3141
+ </div>
3142
+ <button
3143
+ type="button"
3144
+ onclick="closeAiAgentsModal()"
3145
+ data-i18n-title="common.close"
3146
+ class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 text-xl leading-none px-2"
3147
+ >
3148
+ &times;
3149
+ </button>
3150
+ </div>
3151
+ <div id="ai-agents-grid" class="p-5 overflow-y-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
3152
+ <p class="text-sm text-gray-400" data-i18n="common.loading">Loading…</p>
3153
+ </div>
3154
+ </div>
3155
+ </div>
3156
+
3157
+ <!-- ── AI agent input modal ── -->
3158
+ <div
3159
+ id="ai-agent-input-modal"
3160
+ class="hidden fixed inset-0 z-[56] bg-black/45 flex items-center justify-center p-4"
3161
+ onclick="if(event.target===this)closeAiAgentInputModal()"
3162
+ >
3163
+ <div
3164
+ class="w-full max-w-2xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-2xl flex flex-col overflow-hidden"
3165
+ onclick="event.stopPropagation()"
3166
+ >
3167
+ <div class="px-5 py-4 border-b border-gray-200 dark:border-gray-800 flex items-start justify-between gap-4">
3168
+ <div>
3169
+ <h2 id="ai-agent-input-title" class="font-semibold text-base text-gray-900 dark:text-gray-100" data-i18n="agents.input_title">Agent input</h2>
3170
+ <p id="ai-agent-input-hint" class="text-xs text-gray-500 dark:text-gray-400 mt-1" data-i18n="agents.input_hint">
3171
+ Provide the input required for this run.
3172
+ </p>
3173
+ </div>
3174
+ <button
3175
+ type="button"
3176
+ onclick="closeAiAgentInputModal()"
3177
+ data-i18n-title="common.close"
3178
+ class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 text-xl leading-none px-2"
3179
+ >
3180
+ &times;
3181
+ </button>
3182
+ </div>
3183
+ <div class="p-5 flex flex-col gap-4">
3184
+ <textarea
3185
+ id="ai-agent-user-input"
3186
+ rows="8"
3187
+ class="w-full rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-950 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500/25 focus:border-blue-500 resize-y"
3188
+ ></textarea>
3189
+ <div class="flex justify-end gap-3">
3190
+ <button
3191
+ type="button"
3192
+ onclick="closeAiAgentInputModal()"
3193
+ data-i18n="common.cancel"
3194
+ class="text-sm px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
3195
+ >Cancel</button>
3196
+ <button
3197
+ id="ai-agent-input-run"
3198
+ type="button"
3199
+ data-i18n="agents.run"
3200
+ class="text-sm px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
3201
+ >Run agent</button>
3202
+ </div>
3203
+ </div>
3204
+ </div>
3205
+ </div>
3206
+
3207
+ <!-- ── AI agent persistent toast ── -->
3208
+ <div
3209
+ id="ai-agent-toast"
3210
+ class="hidden fixed right-5 bottom-5 z-[70] w-[min(420px,calc(100vw-2.5rem))] rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-2xl px-4 py-3"
3211
+ role="status"
3212
+ >
3213
+ <div class="flex items-start gap-3">
3214
+ <span id="ai-agent-toast-icon" class="mt-0.5 h-5 w-5 rounded-full border-2 border-blue-500 border-t-transparent animate-spin shrink-0"></span>
3215
+ <div class="min-w-0 flex-1">
3216
+ <p id="ai-agent-toast-title" class="text-sm font-semibold text-gray-900 dark:text-gray-100" data-i18n="agents.loading">Running agent…</p>
3217
+ <p id="ai-agent-toast-message" class="text-xs text-gray-500 dark:text-gray-400 mt-1 break-words"></p>
3218
+ <button
3219
+ id="ai-agent-toast-open"
3220
+ type="button"
3221
+ data-i18n="agents.open_document"
3222
+ class="hidden mt-3 text-xs px-3 py-1.5 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
3223
+ >Open document</button>
3224
+ </div>
3225
+ <button
3226
+ id="ai-agent-toast-close"
3227
+ type="button"
3228
+ data-i18n-title="common.close"
3229
+ class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 text-lg leading-none px-1 shrink-0"
3230
+ >
3231
+ &times;
3232
+ </button>
3233
+ </div>
3234
+ </div>
3235
+
3114
3236
  <!-- ── Generic confirmation modal (used by showConfirm) ── -->
3115
3237
  <div
3116
3238
  id="confirm-modal"
@@ -51,9 +51,7 @@ const addButton = document.getElementById("addButton");
51
51
  const fitButton = document.getElementById("fitButton");
52
52
  const testNodeButton = document.getElementById("testNodeButton");
53
53
  const loadModelsButton = document.getElementById("loadModelsButton");
54
- const runAgentButton = document.getElementById("runAgentButton");
55
- const clearAgentButton = document.getElementById("clearAgentButton");
56
- const agentResponses = document.getElementById("agentResponses");
54
+ const closePanelButton = document.getElementById("closePanelButton");
57
55
  const deleteNodeButton = document.getElementById("deleteNodeButton");
58
56
  const renameConfirmOverlay = document.getElementById("renameConfirmOverlay");
59
57
  const renameConfirmMessage = document.getElementById("renameConfirmMessage");
@@ -63,6 +61,14 @@ const deleteConfirmOverlay = document.getElementById("deleteConfirmOverlay");
63
61
  const deleteConfirmMessage = document.getElementById("deleteConfirmMessage");
64
62
  const cancelDeleteButton = document.getElementById("cancelDeleteButton");
65
63
  const confirmDeleteButton = document.getElementById("confirmDeleteButton");
64
+ const agentRunOverlay = document.getElementById("agentRunOverlay");
65
+ const agentRunTitle = document.getElementById("agentRunTitle");
66
+ const agentRunHint = document.getElementById("agentRunHint");
67
+ const agentRunInputField = document.getElementById("agentRunInputField");
68
+ const agentRunInput = document.getElementById("agentRunInput");
69
+ const agentRunResult = document.getElementById("agentRunResult");
70
+ const cancelAgentRunButton = document.getElementById("cancelAgentRunButton");
71
+ const executeAgentRunButton = document.getElementById("executeAgentRunButton");
66
72
  const fallbackPanelHost = document.getElementById("fallbackPanelHost");
67
73
  const configSurface = document.getElementById("configSurface");
68
74
  const form = configSurface;
@@ -72,6 +78,7 @@ const workspaceToastMessage = document.getElementById("workspaceToastMessage");
72
78
  let toastTimerId = null;
73
79
  let savedAgentLabel = null;
74
80
  let savedAgentFolder = null;
81
+ let agentRunTargetId = null;
75
82
  const fields = {
76
83
  name: document.getElementById("nodeName"),
77
84
  kind: document.getElementById("nodeKind"),
@@ -87,7 +94,10 @@ const fields = {
87
94
  mcpInventory: document.getElementById("mcpInventory"),
88
95
  agentSection: document.getElementById("agentSection"),
89
96
  systemPrompt: document.getElementById("nodeSystemPrompt"),
90
- userInput: document.getElementById("nodeUserInput"),
97
+ requiresUserInput: document.getElementById("nodeRequiresUserInput"),
98
+ userInputDescriptionField: document.getElementById("agentUserInputDescriptionField"),
99
+ userInputDescription: document.getElementById("nodeUserInputDescription"),
100
+ expectedOutputMarker: document.getElementById("nodeExpectedOutputMarker"),
91
101
  };
92
102
  const initialEntities = [
93
103
  createEntity("root", "Living AI Documentation", "system", null),
@@ -236,6 +246,9 @@ form.addEventListener("input", (event) => {
236
246
  }
237
247
  scheduleRender();
238
248
  });
249
+ closePanelButton.addEventListener("click", () => {
250
+ selectEntity(null);
251
+ });
239
252
  deleteNodeButton.addEventListener("click", (event) => {
240
253
  event.preventDefault();
241
254
  event.stopPropagation();
@@ -259,6 +272,11 @@ confirmDeleteButton.addEventListener("click", () => {
259
272
  closeDeleteConfirmation();
260
273
  });
261
274
  testNodeButton.addEventListener("click", () => {
275
+ const selected = selectedEntity();
276
+ if (selected?.kind === "agent") {
277
+ openAgentRunDialog();
278
+ return;
279
+ }
262
280
  void testSelectedLlmConnection();
263
281
  });
264
282
  loadModelsButton.addEventListener("click", () => {
@@ -269,13 +287,15 @@ fields.name.addEventListener("blur", () => {
269
287
  if (!selected || selected.kind !== "agent")
270
288
  return;
271
289
  const newName = fields.name.value.trim();
272
- if (!newName || !savedAgentLabel || !savedAgentFolder || newName === savedAgentLabel)
290
+ if (!newName ||
291
+ !savedAgentLabel ||
292
+ !savedAgentFolder ||
293
+ newName === savedAgentLabel)
273
294
  return;
274
295
  const newFolder = workspaceFolderForProvider(newName);
275
296
  if (newFolder === savedAgentFolder)
276
297
  return; // slug identique, pas de renommage nécessaire
277
- renameConfirmMessage.textContent =
278
- `Renommer le dossier "${savedAgentFolder}" en "${newFolder}" ?`;
298
+ renameConfirmMessage.textContent = `Renommer le dossier "${savedAgentFolder}" en "${newFolder}" ?`;
279
299
  renameConfirmOverlay.hidden = false;
280
300
  cancelRenameButton.focus();
281
301
  });
@@ -310,12 +330,16 @@ fields.model.addEventListener("change", () => {
310
330
  selected.config.model = fields.model.value;
311
331
  }
312
332
  });
313
- runAgentButton.addEventListener("click", () => {
314
- void runAgent();
333
+ cancelAgentRunButton.addEventListener("click", () => {
334
+ closeAgentRunDialog();
315
335
  });
316
- clearAgentButton.addEventListener("click", () => {
317
- agentResponses.innerHTML = "";
318
- agentResponses.hidden = true;
336
+ executeAgentRunButton.addEventListener("click", () => {
337
+ void executeAgentRunFromDialog();
338
+ });
339
+ agentRunOverlay.addEventListener("click", (event) => {
340
+ if (event.target === agentRunOverlay) {
341
+ closeAgentRunDialog();
342
+ }
319
343
  });
320
344
  void initializeWorkspace();
321
345
  function isolatePanelEvents() {
@@ -328,6 +352,7 @@ function isolateConfirmEvents() {
328
352
  for (const type of PANEL_EVENT_TYPES) {
329
353
  deleteConfirmOverlay.addEventListener(type, stopPanelEventPropagation);
330
354
  renameConfirmOverlay.addEventListener(type, stopPanelEventPropagation);
355
+ agentRunOverlay.addEventListener(type, stopPanelEventPropagation);
331
356
  }
332
357
  }
333
358
  function stopPanelEventPropagation(event) {
@@ -419,6 +444,9 @@ function hydrateEntity(input) {
419
444
  workspaceFolder: stringValue(config.workspaceFolder) ||
420
445
  (kind === "agent" ? workspaceFolderForProvider(label) : ""),
421
446
  systemPrompt: stringValue(config.systemPrompt),
447
+ requiresUserInput: config.requiresUserInput === true,
448
+ userInputDescription: stringValue(config.userInputDescription),
449
+ expectedOutputMarker: stringValue(config.expectedOutputMarker),
422
450
  },
423
451
  };
424
452
  }
@@ -535,6 +563,9 @@ function createEntity(id, label, kind, parentId) {
535
563
  description: defaultDescription(kind),
536
564
  workspaceFolder: kind === "agent" ? workspaceFolderForProvider(label) : "",
537
565
  systemPrompt: "",
566
+ requiresUserInput: false,
567
+ userInputDescription: "",
568
+ expectedOutputMarker: "",
538
569
  },
539
570
  };
540
571
  }
@@ -546,7 +577,7 @@ function cloneEntities(entities) {
546
577
  }
547
578
  function defaultEndpoint(id, kind) {
548
579
  if (kind === "llm") {
549
- return "http://localhost:1234/v1";
580
+ return "http://localhost:11434";
550
581
  }
551
582
  if (kind === "mcp") {
552
583
  return "http://localhost:4321/mcp";
@@ -769,7 +800,11 @@ function selectEntity(id) {
769
800
  state.selectedId = id;
770
801
  const entity = id ? entityById(id) : null;
771
802
  savedAgentLabel = entity?.kind === "agent" ? entity.label : null;
772
- savedAgentFolder = entity?.kind === "agent" ? (entity.config.workspaceFolder || workspaceFolderForProvider(entity.label)) : null;
803
+ savedAgentFolder =
804
+ entity?.kind === "agent"
805
+ ? entity.config.workspaceFolder ||
806
+ workspaceFolderForProvider(entity.label)
807
+ : null;
773
808
  layoutGraph();
774
809
  syncCameraForPanelChange(hadSelection, Boolean(id), true);
775
810
  syncPanelFromSelection();
@@ -813,6 +848,7 @@ function syncPanelFromSelection() {
813
848
  fallbackPanelHost.hidden = !hasSelection || supportsHtmlInCanvas;
814
849
  if (!selected) {
815
850
  addButton.title = "Add LLM provider";
851
+ testNodeButton.textContent = "Test";
816
852
  testNodeButton.hidden = true;
817
853
  testNodeButton.disabled = true;
818
854
  return;
@@ -828,14 +864,22 @@ function syncPanelFromSelection() {
828
864
  fields.description.value = selected.config.description;
829
865
  fields.typeBadge.textContent = labelForBadge(selected.kind);
830
866
  fields.kind.disabled = true;
831
- fields.llmFields.hidden = selected.kind === "mcp" || selected.kind === "agent";
867
+ fields.llmFields.hidden =
868
+ selected.kind === "mcp" || selected.kind === "agent";
832
869
  fields.mcpInventory.hidden = selected.kind !== "mcp";
833
870
  fields.agentSection.hidden = selected.kind !== "agent";
834
871
  fields.systemPrompt.value = selected.config.systemPrompt;
872
+ fields.requiresUserInput.checked = selected.config.requiresUserInput;
873
+ syncUserInputDescriptionVisibility();
874
+ fields.userInputDescription.value = selected.config.userInputDescription;
875
+ fields.expectedOutputMarker.value = selected.config.expectedOutputMarker;
835
876
  deleteNodeButton.hidden = isProtectedEntity(selected.id);
836
- deleteNodeButton.disabled = isProtectedEntity(selected.id);
837
- testNodeButton.hidden = selected.kind !== "llm";
838
- testNodeButton.disabled = selected.kind !== "llm" || !selected.config.model;
877
+ testNodeButton.textContent = "Test";
878
+ testNodeButton.hidden = selected.kind !== "llm" && selected.kind !== "agent";
879
+ testNodeButton.disabled =
880
+ selected.kind === "llm"
881
+ ? !selected.config.model
882
+ : selected.kind !== "agent";
839
883
  addButton.title =
840
884
  selected.kind === "llm" || entityById(selected.parentId)?.kind === "llm"
841
885
  ? "Add agent"
@@ -854,7 +898,25 @@ function syncSelectedFromForm() {
854
898
  selected.config.timeout = Number(fields.timeout.value || 30);
855
899
  selected.config.description = fields.description.value;
856
900
  selected.config.systemPrompt = fields.systemPrompt.value;
901
+ selected.config.requiresUserInput = fields.requiresUserInput.checked;
902
+ selected.config.userInputDescription = fields.userInputDescription.value;
903
+ selected.config.expectedOutputMarker = fields.expectedOutputMarker.value;
904
+ syncUserInputDescriptionVisibility();
857
905
  fields.typeBadge.textContent = labelForBadge(selected.kind);
906
+ // Propagate model + timeout to child agents
907
+ if (selected.kind === "llm") {
908
+ for (const entity of state.entities) {
909
+ if (entity.parentId === selected.id && entity.kind === "agent") {
910
+ entity.config.model = selected.config.model;
911
+ entity.config.timeout = selected.config.timeout;
912
+ }
913
+ }
914
+ }
915
+ }
916
+ function syncUserInputDescriptionVisibility() {
917
+ const shouldShow = selectedEntity()?.kind === "agent" && fields.requiresUserInput.checked;
918
+ fields.userInputDescriptionField.hidden = !shouldShow;
919
+ fields.userInputDescription.required = shouldShow;
858
920
  }
859
921
  function restoreModelSelect(savedModel) {
860
922
  const existing = Array.from(fields.model.options).map((o) => o.value);
@@ -867,7 +929,7 @@ function restoreModelSelect(savedModel) {
867
929
  }
868
930
  fields.model.value = savedModel || (existing[0] ?? "");
869
931
  }
870
- async function runAgent() {
932
+ function openAgentRunDialog() {
871
933
  const selected = selectedEntity();
872
934
  if (!selected || selected.kind !== "agent")
873
935
  return;
@@ -885,38 +947,80 @@ async function runAgent() {
885
947
  showSaveToast("Write a system prompt first.", "error");
886
948
  return;
887
949
  }
888
- runAgentButton.disabled = true;
889
- showLoadingToast("Running agent…");
950
+ if (selected.config.requiresUserInput &&
951
+ !selected.config.userInputDescription.trim()) {
952
+ showSaveToast("Describe the required user input first.", "error");
953
+ return;
954
+ }
955
+ agentRunTargetId = selected.id;
956
+ agentRunTitle.textContent = `Test ${selected.label}`;
957
+ agentRunHint.textContent = selected.config.requiresUserInput
958
+ ? selected.config.userInputDescription
959
+ : "Run this agent with its configured system prompt.";
960
+ agentRunInputField.hidden = !selected.config.requiresUserInput;
961
+ agentRunInput.required = selected.config.requiresUserInput;
962
+ agentRunInput.value = "";
963
+ agentRunResult.hidden = true;
964
+ agentRunResult.textContent = "";
965
+ executeAgentRunButton.disabled = false;
966
+ executeAgentRunButton.textContent = "Run";
967
+ agentRunOverlay.hidden = false;
968
+ if (selected.config.requiresUserInput) {
969
+ window.setTimeout(() => agentRunInput.focus(), 0);
970
+ }
971
+ else {
972
+ window.setTimeout(() => executeAgentRunButton.focus(), 0);
973
+ }
974
+ }
975
+ function closeAgentRunDialog() {
976
+ agentRunOverlay.hidden = true;
977
+ agentRunTargetId = null;
978
+ }
979
+ async function executeAgentRunFromDialog() {
980
+ if (agentRunInput.required && !agentRunInput.value.trim()) {
981
+ agentRunInput.reportValidity();
982
+ return;
983
+ }
984
+ const selected = agentRunTargetId ? entityById(agentRunTargetId) : null;
985
+ if (!selected || selected.kind !== "agent") {
986
+ showAgentRunResult("error", "The selected agent is no longer available.");
987
+ return;
988
+ }
989
+ const llm = entityById(selected.parentId ?? "");
990
+ if (!llm || llm.kind !== "llm") {
991
+ showAgentRunResult("error", "No parent LLM found for this agent.");
992
+ return;
993
+ }
994
+ executeAgentRunButton.disabled = true;
995
+ executeAgentRunButton.textContent = "Running…";
996
+ showAgentRunResult("loading", "Running agent…");
890
997
  const mcpEntity = state.entities.find((e) => e.kind === "mcp");
891
998
  const result = await runAgentPrompt({
892
999
  endpoint: llm.config.endpoint,
893
1000
  token: llm.config.token,
894
1001
  model: llm.config.model,
895
1002
  systemPrompt: selected.config.systemPrompt,
896
- userInput: fields.userInput.value.trim() || undefined,
1003
+ userInput: agentRunInput.value.trim() || undefined,
897
1004
  timeout: llm.config.timeout,
898
1005
  mcpEndpoint: mcpEntity?.config.endpoint || undefined,
1006
+ expectedOutputMarker: selected.config.expectedOutputMarker || undefined,
899
1007
  });
900
- runAgentButton.disabled = false;
1008
+ executeAgentRunButton.disabled = false;
1009
+ executeAgentRunButton.textContent = "Run";
901
1010
  if (result.ok && result.content) {
902
- agentResponses.hidden = false;
903
- const item = document.createElement("div");
904
- item.className = "agent-response-item";
905
- const meta = document.createElement("div");
906
- meta.className = "agent-response-meta";
907
- meta.textContent = new Date().toLocaleTimeString();
908
- const content = document.createElement("div");
909
- content.textContent = result.content;
910
- item.appendChild(meta);
911
- item.appendChild(content);
912
- agentResponses.appendChild(item);
913
- agentResponses.scrollTop = agentResponses.scrollHeight;
1011
+ showAgentRunResult("success", result.content);
914
1012
  showSaveToast("Agent responded.", "success");
915
1013
  }
916
1014
  else {
1015
+ showAgentRunResult("error", result.error ?? "Agent failed.");
917
1016
  showSaveToast(result.error ?? "Agent failed.", "error");
918
1017
  }
919
1018
  }
1019
+ function showAgentRunResult(state, message) {
1020
+ agentRunResult.hidden = false;
1021
+ agentRunResult.dataset.state = state;
1022
+ agentRunResult.textContent = message;
1023
+ }
920
1024
  async function loadModelsForSelect() {
921
1025
  const selected = selectedEntity();
922
1026
  if (!selected || selected.kind !== "llm")
@@ -930,7 +1034,10 @@ async function loadModelsForSelect() {
930
1034
  loadModelsButton.classList.add("loading");
931
1035
  loadModelsButton.disabled = true;
932
1036
  fields.model.disabled = true;
933
- const result = await listLlmModels({ endpoint, token: selected.config.token });
1037
+ const result = await listLlmModels({
1038
+ endpoint,
1039
+ token: selected.config.token,
1040
+ });
934
1041
  loadModelsButton.classList.remove("loading");
935
1042
  loadModelsButton.disabled = false;
936
1043
  fields.model.disabled = false;