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.
@@ -32,6 +32,9 @@ interface EntityConfig {
32
32
  description: string;
33
33
  workspaceFolder: string;
34
34
  systemPrompt: string;
35
+ requiresUserInput: boolean;
36
+ userInputDescription: string;
37
+ expectedOutputMarker: string;
35
38
  }
36
39
 
37
40
  interface Entity {
@@ -144,13 +147,9 @@ const testNodeButton = document.getElementById(
144
147
  const loadModelsButton = document.getElementById(
145
148
  "loadModelsButton",
146
149
  ) as HTMLButtonElement;
147
- const runAgentButton = document.getElementById(
148
- "runAgentButton",
150
+ const closePanelButton = document.getElementById(
151
+ "closePanelButton",
149
152
  ) as HTMLButtonElement;
150
- const clearAgentButton = document.getElementById(
151
- "clearAgentButton",
152
- ) as HTMLButtonElement;
153
- const agentResponses = document.getElementById("agentResponses") as HTMLElement;
154
153
  const deleteNodeButton = document.getElementById(
155
154
  "deleteNodeButton",
156
155
  ) as HTMLButtonElement;
@@ -178,6 +177,24 @@ const cancelDeleteButton = document.getElementById(
178
177
  const confirmDeleteButton = document.getElementById(
179
178
  "confirmDeleteButton",
180
179
  ) as HTMLButtonElement;
180
+ const agentRunOverlay = document.getElementById(
181
+ "agentRunOverlay",
182
+ ) as HTMLElement;
183
+ const agentRunTitle = document.getElementById("agentRunTitle") as HTMLElement;
184
+ const agentRunHint = document.getElementById("agentRunHint") as HTMLElement;
185
+ const agentRunInputField = document.getElementById(
186
+ "agentRunInputField",
187
+ ) as HTMLElement;
188
+ const agentRunInput = document.getElementById(
189
+ "agentRunInput",
190
+ ) as HTMLTextAreaElement;
191
+ const agentRunResult = document.getElementById("agentRunResult") as HTMLElement;
192
+ const cancelAgentRunButton = document.getElementById(
193
+ "cancelAgentRunButton",
194
+ ) as HTMLButtonElement;
195
+ const executeAgentRunButton = document.getElementById(
196
+ "executeAgentRunButton",
197
+ ) as HTMLButtonElement;
181
198
  const fallbackPanelHost = document.getElementById(
182
199
  "fallbackPanelHost",
183
200
  ) as HTMLElement;
@@ -186,13 +203,16 @@ const configSurface = document.getElementById(
186
203
  ) as HTMLFormElement;
187
204
  const form = configSurface;
188
205
  const workspaceToast = document.getElementById("workspaceToast") as HTMLElement;
189
- const workspaceToastIcon = document.getElementById("workspaceToastIcon") as HTMLElement;
206
+ const workspaceToastIcon = document.getElementById(
207
+ "workspaceToastIcon",
208
+ ) as HTMLElement;
190
209
  const workspaceToastMessage = document.getElementById(
191
210
  "workspaceToastMessage",
192
211
  ) as HTMLElement;
193
212
  let toastTimerId: number | null = null;
194
213
  let savedAgentLabel: string | null = null;
195
214
  let savedAgentFolder: string | null = null;
215
+ let agentRunTargetId: string | null = null;
196
216
 
197
217
  const fields = {
198
218
  name: document.getElementById("nodeName") as HTMLInputElement,
@@ -200,9 +220,7 @@ const fields = {
200
220
  endpoint: document.getElementById("nodeEndpoint") as HTMLInputElement,
201
221
  token: document.getElementById("nodeToken") as HTMLInputElement,
202
222
  model: document.getElementById("nodeModel") as HTMLSelectElement,
203
- workspaceField: document.getElementById(
204
- "agentWorkspaceField",
205
- ) as HTMLElement,
223
+ workspaceField: document.getElementById("agentWorkspaceField") as HTMLElement,
206
224
  workspaceFolder: document.getElementById(
207
225
  "nodeWorkspaceFolder",
208
226
  ) as HTMLInputElement,
@@ -214,8 +232,21 @@ const fields = {
214
232
  llmFields: document.getElementById("llmFields") as HTMLElement,
215
233
  mcpInventory: document.getElementById("mcpInventory") as HTMLElement,
216
234
  agentSection: document.getElementById("agentSection") as HTMLElement,
217
- systemPrompt: document.getElementById("nodeSystemPrompt") as HTMLTextAreaElement,
218
- userInput: document.getElementById("nodeUserInput") as HTMLTextAreaElement,
235
+ systemPrompt: document.getElementById(
236
+ "nodeSystemPrompt",
237
+ ) as HTMLTextAreaElement,
238
+ requiresUserInput: document.getElementById(
239
+ "nodeRequiresUserInput",
240
+ ) as HTMLInputElement,
241
+ userInputDescriptionField: document.getElementById(
242
+ "agentUserInputDescriptionField",
243
+ ) as HTMLElement,
244
+ userInputDescription: document.getElementById(
245
+ "nodeUserInputDescription",
246
+ ) as HTMLTextAreaElement,
247
+ expectedOutputMarker: document.getElementById(
248
+ "nodeExpectedOutputMarker",
249
+ ) as HTMLInputElement,
219
250
  };
220
251
 
221
252
  const initialEntities: Entity[] = [
@@ -293,7 +324,6 @@ fitButton.addEventListener("click", () => {
293
324
  scheduleRender();
294
325
  });
295
326
 
296
-
297
327
  canvas.addEventListener("pointerdown", (event) => {
298
328
  if (event.target !== canvas) {
299
329
  return;
@@ -398,6 +428,10 @@ form.addEventListener("input", (event) => {
398
428
  scheduleRender();
399
429
  });
400
430
 
431
+ closePanelButton.addEventListener("click", () => {
432
+ selectEntity(null);
433
+ });
434
+
401
435
  deleteNodeButton.addEventListener("click", (event) => {
402
436
  event.preventDefault();
403
437
  event.stopPropagation();
@@ -425,6 +459,12 @@ confirmDeleteButton.addEventListener("click", () => {
425
459
  });
426
460
 
427
461
  testNodeButton.addEventListener("click", () => {
462
+ const selected = selectedEntity();
463
+ if (selected?.kind === "agent") {
464
+ openAgentRunDialog();
465
+ return;
466
+ }
467
+
428
468
  void testSelectedLlmConnection();
429
469
  });
430
470
 
@@ -436,13 +476,18 @@ fields.name.addEventListener("blur", () => {
436
476
  const selected = selectedEntity();
437
477
  if (!selected || selected.kind !== "agent") return;
438
478
  const newName = fields.name.value.trim();
439
- if (!newName || !savedAgentLabel || !savedAgentFolder || newName === savedAgentLabel) return;
479
+ if (
480
+ !newName ||
481
+ !savedAgentLabel ||
482
+ !savedAgentFolder ||
483
+ newName === savedAgentLabel
484
+ )
485
+ return;
440
486
 
441
487
  const newFolder = workspaceFolderForProvider(newName);
442
488
  if (newFolder === savedAgentFolder) return; // slug identique, pas de renommage nécessaire
443
489
 
444
- renameConfirmMessage.textContent =
445
- `Renommer le dossier "${savedAgentFolder}" en "${newFolder}" ?`;
490
+ renameConfirmMessage.textContent = `Renommer le dossier "${savedAgentFolder}" en "${newFolder}" ?`;
446
491
  renameConfirmOverlay.hidden = false;
447
492
  cancelRenameButton.focus();
448
493
  });
@@ -479,13 +524,18 @@ fields.model.addEventListener("change", () => {
479
524
  }
480
525
  });
481
526
 
482
- runAgentButton.addEventListener("click", () => {
483
- void runAgent();
527
+ cancelAgentRunButton.addEventListener("click", () => {
528
+ closeAgentRunDialog();
529
+ });
530
+
531
+ executeAgentRunButton.addEventListener("click", () => {
532
+ void executeAgentRunFromDialog();
484
533
  });
485
534
 
486
- clearAgentButton.addEventListener("click", () => {
487
- agentResponses.innerHTML = "";
488
- agentResponses.hidden = true;
535
+ agentRunOverlay.addEventListener("click", (event) => {
536
+ if (event.target === agentRunOverlay) {
537
+ closeAgentRunDialog();
538
+ }
489
539
  });
490
540
 
491
541
  void initializeWorkspace();
@@ -501,6 +551,7 @@ function isolateConfirmEvents() {
501
551
  for (const type of PANEL_EVENT_TYPES) {
502
552
  deleteConfirmOverlay.addEventListener(type, stopPanelEventPropagation);
503
553
  renameConfirmOverlay.addEventListener(type, stopPanelEventPropagation);
554
+ agentRunOverlay.addEventListener(type, stopPanelEventPropagation);
504
555
  }
505
556
  }
506
557
 
@@ -612,6 +663,9 @@ function hydrateEntity(input: unknown): Entity | null {
612
663
  stringValue(config.workspaceFolder) ||
613
664
  (kind === "agent" ? workspaceFolderForProvider(label) : ""),
614
665
  systemPrompt: stringValue(config.systemPrompt),
666
+ requiresUserInput: config.requiresUserInput === true,
667
+ userInputDescription: stringValue(config.userInputDescription),
668
+ expectedOutputMarker: stringValue(config.expectedOutputMarker),
615
669
  },
616
670
  };
617
671
  }
@@ -664,7 +718,10 @@ function showLoadingToast(message: string) {
664
718
  workspaceToast.hidden = false;
665
719
  }
666
720
 
667
- function showSaveToast(message: string, state: "success" | "error" = "success") {
721
+ function showSaveToast(
722
+ message: string,
723
+ state: "success" | "error" = "success",
724
+ ) {
668
725
  if (toastTimerId) {
669
726
  clearTimeout(toastTimerId);
670
727
  }
@@ -736,8 +793,12 @@ function createEntity(id, label, kind, parentId) {
736
793
  model: "",
737
794
  timeout: 180,
738
795
  description: defaultDescription(kind),
739
- workspaceFolder: kind === "agent" ? workspaceFolderForProvider(label) : "",
796
+ workspaceFolder:
797
+ kind === "agent" ? workspaceFolderForProvider(label) : "",
740
798
  systemPrompt: "",
799
+ requiresUserInput: false,
800
+ userInputDescription: "",
801
+ expectedOutputMarker: "",
741
802
  },
742
803
  };
743
804
  }
@@ -751,7 +812,7 @@ function cloneEntities(entities) {
751
812
 
752
813
  function defaultEndpoint(id, kind) {
753
814
  if (kind === "llm") {
754
- return "http://localhost:1234/v1";
815
+ return "http://localhost:11434";
755
816
  }
756
817
  if (kind === "mcp") {
757
818
  return "http://localhost:4321/mcp";
@@ -1080,7 +1141,11 @@ function selectEntity(id) {
1080
1141
  state.selectedId = id;
1081
1142
  const entity = id ? entityById(id) : null;
1082
1143
  savedAgentLabel = entity?.kind === "agent" ? entity.label : null;
1083
- savedAgentFolder = entity?.kind === "agent" ? (entity.config.workspaceFolder || workspaceFolderForProvider(entity.label)) : null;
1144
+ savedAgentFolder =
1145
+ entity?.kind === "agent"
1146
+ ? entity.config.workspaceFolder ||
1147
+ workspaceFolderForProvider(entity.label)
1148
+ : null;
1084
1149
  layoutGraph();
1085
1150
  syncCameraForPanelChange(hadSelection, Boolean(id), true);
1086
1151
  syncPanelFromSelection();
@@ -1133,6 +1198,7 @@ function syncPanelFromSelection() {
1133
1198
 
1134
1199
  if (!selected) {
1135
1200
  addButton.title = "Add LLM provider";
1201
+ testNodeButton.textContent = "Test";
1136
1202
  testNodeButton.hidden = true;
1137
1203
  testNodeButton.disabled = true;
1138
1204
  return;
@@ -1149,14 +1215,22 @@ function syncPanelFromSelection() {
1149
1215
  fields.description.value = selected.config.description;
1150
1216
  fields.typeBadge.textContent = labelForBadge(selected.kind);
1151
1217
  fields.kind.disabled = true;
1152
- fields.llmFields.hidden = selected.kind === "mcp" || selected.kind === "agent";
1218
+ fields.llmFields.hidden =
1219
+ selected.kind === "mcp" || selected.kind === "agent";
1153
1220
  fields.mcpInventory.hidden = selected.kind !== "mcp";
1154
1221
  fields.agentSection.hidden = selected.kind !== "agent";
1155
1222
  fields.systemPrompt.value = selected.config.systemPrompt;
1223
+ fields.requiresUserInput.checked = selected.config.requiresUserInput;
1224
+ syncUserInputDescriptionVisibility();
1225
+ fields.userInputDescription.value = selected.config.userInputDescription;
1226
+ fields.expectedOutputMarker.value = selected.config.expectedOutputMarker;
1156
1227
  deleteNodeButton.hidden = isProtectedEntity(selected.id);
1157
- deleteNodeButton.disabled = isProtectedEntity(selected.id);
1158
- testNodeButton.hidden = selected.kind !== "llm";
1159
- testNodeButton.disabled = selected.kind !== "llm" || !selected.config.model;
1228
+ testNodeButton.textContent = "Test";
1229
+ testNodeButton.hidden = selected.kind !== "llm" && selected.kind !== "agent";
1230
+ testNodeButton.disabled =
1231
+ selected.kind === "llm"
1232
+ ? !selected.config.model
1233
+ : selected.kind !== "agent";
1160
1234
  addButton.title =
1161
1235
  selected.kind === "llm" || entityById(selected.parentId)?.kind === "llm"
1162
1236
  ? "Add agent"
@@ -1177,7 +1251,28 @@ function syncSelectedFromForm() {
1177
1251
  selected.config.timeout = Number(fields.timeout.value || 30);
1178
1252
  selected.config.description = fields.description.value;
1179
1253
  selected.config.systemPrompt = fields.systemPrompt.value;
1254
+ selected.config.requiresUserInput = fields.requiresUserInput.checked;
1255
+ selected.config.userInputDescription = fields.userInputDescription.value;
1256
+ selected.config.expectedOutputMarker = fields.expectedOutputMarker.value;
1257
+ syncUserInputDescriptionVisibility();
1180
1258
  fields.typeBadge.textContent = labelForBadge(selected.kind);
1259
+
1260
+ // Propagate model + timeout to child agents
1261
+ if (selected.kind === "llm") {
1262
+ for (const entity of state.entities) {
1263
+ if (entity.parentId === selected.id && entity.kind === "agent") {
1264
+ entity.config.model = selected.config.model;
1265
+ entity.config.timeout = selected.config.timeout;
1266
+ }
1267
+ }
1268
+ }
1269
+ }
1270
+
1271
+ function syncUserInputDescriptionVisibility() {
1272
+ const shouldShow =
1273
+ selectedEntity()?.kind === "agent" && fields.requiresUserInput.checked;
1274
+ fields.userInputDescriptionField.hidden = !shouldShow;
1275
+ fields.userInputDescription.required = shouldShow;
1181
1276
  }
1182
1277
 
1183
1278
  function restoreModelSelect(savedModel: string) {
@@ -1192,7 +1287,7 @@ function restoreModelSelect(savedModel: string) {
1192
1287
  fields.model.value = savedModel || (existing[0] ?? "");
1193
1288
  }
1194
1289
 
1195
- async function runAgent() {
1290
+ function openAgentRunDialog() {
1196
1291
  const selected = selectedEntity();
1197
1292
  if (!selected || selected.kind !== "agent") return;
1198
1293
 
@@ -1211,9 +1306,60 @@ async function runAgent() {
1211
1306
  showSaveToast("Write a system prompt first.", "error");
1212
1307
  return;
1213
1308
  }
1309
+ if (
1310
+ selected.config.requiresUserInput &&
1311
+ !selected.config.userInputDescription.trim()
1312
+ ) {
1313
+ showSaveToast("Describe the required user input first.", "error");
1314
+ return;
1315
+ }
1316
+
1317
+ agentRunTargetId = selected.id;
1318
+ agentRunTitle.textContent = `Test ${selected.label}`;
1319
+ agentRunHint.textContent = selected.config.requiresUserInput
1320
+ ? selected.config.userInputDescription
1321
+ : "Run this agent with its configured system prompt.";
1322
+ agentRunInputField.hidden = !selected.config.requiresUserInput;
1323
+ agentRunInput.required = selected.config.requiresUserInput;
1324
+ agentRunInput.value = "";
1325
+ agentRunResult.hidden = true;
1326
+ agentRunResult.textContent = "";
1327
+ executeAgentRunButton.disabled = false;
1328
+ executeAgentRunButton.textContent = "Run";
1329
+ agentRunOverlay.hidden = false;
1330
+ if (selected.config.requiresUserInput) {
1331
+ window.setTimeout(() => agentRunInput.focus(), 0);
1332
+ } else {
1333
+ window.setTimeout(() => executeAgentRunButton.focus(), 0);
1334
+ }
1335
+ }
1336
+
1337
+ function closeAgentRunDialog() {
1338
+ agentRunOverlay.hidden = true;
1339
+ agentRunTargetId = null;
1340
+ }
1341
+
1342
+ async function executeAgentRunFromDialog() {
1343
+ if (agentRunInput.required && !agentRunInput.value.trim()) {
1344
+ agentRunInput.reportValidity();
1345
+ return;
1346
+ }
1214
1347
 
1215
- runAgentButton.disabled = true;
1216
- showLoadingToast("Running agent");
1348
+ const selected = agentRunTargetId ? entityById(agentRunTargetId) : null;
1349
+ if (!selected || selected.kind !== "agent") {
1350
+ showAgentRunResult("error", "The selected agent is no longer available.");
1351
+ return;
1352
+ }
1353
+
1354
+ const llm = entityById(selected.parentId ?? "");
1355
+ if (!llm || llm.kind !== "llm") {
1356
+ showAgentRunResult("error", "No parent LLM found for this agent.");
1357
+ return;
1358
+ }
1359
+
1360
+ executeAgentRunButton.disabled = true;
1361
+ executeAgentRunButton.textContent = "Running…";
1362
+ showAgentRunResult("loading", "Running agent…");
1217
1363
 
1218
1364
  const mcpEntity = state.entities.find((e) => e.kind === "mcp");
1219
1365
 
@@ -1222,32 +1368,33 @@ async function runAgent() {
1222
1368
  token: llm.config.token,
1223
1369
  model: llm.config.model,
1224
1370
  systemPrompt: selected.config.systemPrompt,
1225
- userInput: fields.userInput.value.trim() || undefined,
1371
+ userInput: agentRunInput.value.trim() || undefined,
1226
1372
  timeout: llm.config.timeout,
1227
1373
  mcpEndpoint: mcpEntity?.config.endpoint || undefined,
1374
+ expectedOutputMarker: selected.config.expectedOutputMarker || undefined,
1228
1375
  });
1229
1376
 
1230
- runAgentButton.disabled = false;
1377
+ executeAgentRunButton.disabled = false;
1378
+ executeAgentRunButton.textContent = "Run";
1231
1379
 
1232
1380
  if (result.ok && result.content) {
1233
- agentResponses.hidden = false;
1234
- const item = document.createElement("div");
1235
- item.className = "agent-response-item";
1236
- const meta = document.createElement("div");
1237
- meta.className = "agent-response-meta";
1238
- meta.textContent = new Date().toLocaleTimeString();
1239
- const content = document.createElement("div");
1240
- content.textContent = result.content;
1241
- item.appendChild(meta);
1242
- item.appendChild(content);
1243
- agentResponses.appendChild(item);
1244
- agentResponses.scrollTop = agentResponses.scrollHeight;
1381
+ showAgentRunResult("success", result.content);
1245
1382
  showSaveToast("Agent responded.", "success");
1246
1383
  } else {
1384
+ showAgentRunResult("error", result.error ?? "Agent failed.");
1247
1385
  showSaveToast(result.error ?? "Agent failed.", "error");
1248
1386
  }
1249
1387
  }
1250
1388
 
1389
+ function showAgentRunResult(
1390
+ state: "loading" | "success" | "error",
1391
+ message: string,
1392
+ ) {
1393
+ agentRunResult.hidden = false;
1394
+ agentRunResult.dataset.state = state;
1395
+ agentRunResult.textContent = message;
1396
+ }
1397
+
1251
1398
  async function loadModelsForSelect() {
1252
1399
  const selected = selectedEntity();
1253
1400
  if (!selected || selected.kind !== "llm") return;
@@ -1263,7 +1410,10 @@ async function loadModelsForSelect() {
1263
1410
  loadModelsButton.disabled = true;
1264
1411
  fields.model.disabled = true;
1265
1412
 
1266
- const result = await listLlmModels({ endpoint, token: selected.config.token });
1413
+ const result = await listLlmModels({
1414
+ endpoint,
1415
+ token: selected.config.token,
1416
+ });
1267
1417
 
1268
1418
  loadModelsButton.classList.remove("loading");
1269
1419
  loadModelsButton.disabled = false;
@@ -1287,7 +1437,9 @@ async function loadModelsForSelect() {
1287
1437
  : result.models[0];
1288
1438
  selected.config.model = fields.model.value;
1289
1439
  testNodeButton.disabled = !fields.model.value;
1290
- showSaveToast(`${result.models.length} model${result.models.length === 1 ? "" : "s"} loaded.`);
1440
+ showSaveToast(
1441
+ `${result.models.length} model${result.models.length === 1 ? "" : "s"} loaded.`,
1442
+ );
1291
1443
  scheduleRender();
1292
1444
  }
1293
1445
 
@@ -1313,7 +1465,10 @@ async function testSelectedLlmConnection() {
1313
1465
  if (result.ok) {
1314
1466
  showSaveToast(result.detail ?? "Connection OK", "success");
1315
1467
  } else {
1316
- showSaveToast(result.error || `Connection failed (${result.status || "no status"})`, "error");
1468
+ showSaveToast(
1469
+ result.error || `Connection failed (${result.status || "no status"})`,
1470
+ "error",
1471
+ );
1317
1472
  }
1318
1473
  scheduleRender();
1319
1474
  }
@@ -1587,7 +1742,8 @@ function syncCameraForPanelChange(hadSelection, hasSelection, animate) {
1587
1742
  // Panel was closed → center selected node in the left area
1588
1743
  const selected = selectedEntity();
1589
1744
  if (selected) {
1590
- const panelReserve = panelWidthForViewport(canvas.clientWidth) + PANEL_GAP;
1745
+ const panelReserve =
1746
+ panelWidthForViewport(canvas.clientWidth) + PANEL_GAP;
1591
1747
  const availableWidth = canvas.clientWidth - panelReserve;
1592
1748
  const targetX = availableWidth / 2 - selected.x * state.zoom;
1593
1749
  const appliedDelta = targetX - state.cameraX;
@@ -4,7 +4,10 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>HTML-in-Canvas Configuration Experiment</title>
7
- <link rel="stylesheet" href="./styles.css?v=workspace-ts-1" />
7
+ <link
8
+ rel="stylesheet"
9
+ href="/workspace/styles.css?v=workspace-agent-panel-1"
10
+ />
8
11
  </head>
9
12
  <body>
10
13
  <main class="app-shell">
@@ -50,10 +53,10 @@
50
53
  <div class="panel-kicker">
51
54
  <span id="nodeTypeBadge">Provider</span>
52
55
  <button
53
- id="deleteNodeButton"
54
- class="icon-button danger"
56
+ id="closePanelButton"
57
+ class="icon-button close"
55
58
  type="button"
56
- title="Delete node"
59
+ title="Close panel"
57
60
  >
58
61
  ×
59
62
  </button>
@@ -87,7 +90,7 @@
87
90
  id="nodeEndpoint"
88
91
  name="nodeEndpoint"
89
92
  type="url"
90
- placeholder="http://localhost:1234/v1"
93
+ placeholder="http://localhost:11434"
91
94
  />
92
95
  </label>
93
96
 
@@ -163,33 +166,36 @@
163
166
  placeholder="You are a helpful assistant…"
164
167
  ></textarea>
165
168
  </label>
166
- <label class="field wide">
167
- <span>User input</span>
169
+ <label class="field wide checkbox-field">
170
+ <input
171
+ id="nodeRequiresUserInput"
172
+ name="nodeRequiresUserInput"
173
+ type="checkbox"
174
+ />
175
+ <span>Require a user input</span>
176
+ </label>
177
+ <label
178
+ id="agentUserInputDescriptionField"
179
+ class="field wide"
180
+ hidden
181
+ >
182
+ <span>Describe User input</span>
168
183
  <textarea
169
- id="nodeUserInput"
170
- name="nodeUserInput"
171
- rows="2"
172
- placeholder="Document ID, question, or any input for the agent"
184
+ id="nodeUserInputDescription"
185
+ name="nodeUserInputDescription"
186
+ rows="3"
187
+ placeholder="Describe exactly what the user must provide before running this agent."
173
188
  ></textarea>
174
189
  </label>
175
-
176
- <div class="agent-run-row">
177
- <button
178
- id="runAgentButton"
179
- class="secondary-button"
180
- type="button"
181
- >
182
- Test
183
- </button>
184
- <button
185
- id="clearAgentButton"
186
- class="ghost-button"
187
- type="button"
188
- >
189
- Clear
190
- </button>
191
- </div>
192
- <div id="agentResponses" class="agent-responses" hidden></div>
190
+ <label class="field wide">
191
+ <span>Required output marker</span>
192
+ <input
193
+ id="nodeExpectedOutputMarker"
194
+ name="nodeExpectedOutputMarker"
195
+ type="text"
196
+ placeholder="Optional, e.g. ```mermaid"
197
+ />
198
+ </label>
193
199
  </section>
194
200
 
195
201
  <section id="mcpInventory" class="mcp-inventory" hidden>
@@ -235,13 +241,24 @@
235
241
  </section>
236
242
 
237
243
  <footer class="panel-actions">
238
- <button
239
- id="testNodeButton"
240
- class="secondary-button"
241
- type="button"
242
- >
243
- Test
244
- </button>
244
+ <div class="panel-actions-left">
245
+ <button
246
+ id="testNodeButton"
247
+ class="secondary-button"
248
+ type="button"
249
+ >
250
+ Test
251
+ </button>
252
+ </div>
253
+ <div class="panel-actions-right">
254
+ <button
255
+ id="deleteNodeButton"
256
+ class="danger-button"
257
+ type="button"
258
+ >
259
+ Delete
260
+ </button>
261
+ </div>
245
262
  </footer>
246
263
  </form>
247
264
  </canvas>
@@ -262,8 +279,20 @@
262
279
  <p id="renameConfirmMessage" class="confirm-message"></p>
263
280
  </div>
264
281
  <footer class="confirm-actions">
265
- <button id="cancelRenameButton" class="secondary-button" type="button">Annuler</button>
266
- <button id="confirmRenameButton" class="primary-button" type="button">Renommer</button>
282
+ <button
283
+ id="cancelRenameButton"
284
+ class="secondary-button"
285
+ type="button"
286
+ >
287
+ Annuler
288
+ </button>
289
+ <button
290
+ id="confirmRenameButton"
291
+ class="primary-button"
292
+ type="button"
293
+ >
294
+ Renommer
295
+ </button>
267
296
  </footer>
268
297
  </section>
269
298
  </div>
@@ -298,6 +327,53 @@
298
327
  </footer>
299
328
  </section>
300
329
  </div>
330
+
331
+ <div id="agentRunOverlay" class="confirm-overlay" hidden>
332
+ <section
333
+ class="agent-run-dialog"
334
+ role="dialog"
335
+ aria-modal="true"
336
+ aria-labelledby="agentRunTitle"
337
+ >
338
+ <div>
339
+ <p class="confirm-kicker">Agent test</p>
340
+ <h2 id="agentRunTitle">Run agent</h2>
341
+ <p id="agentRunHint" class="confirm-message"></p>
342
+ </div>
343
+
344
+ <label
345
+ id="agentRunInputField"
346
+ class="field wide agent-run-input-field"
347
+ >
348
+ <span>User input</span>
349
+ <textarea
350
+ id="agentRunInput"
351
+ name="agentRunInput"
352
+ rows="6"
353
+ placeholder="Document ID, question, or any input for the agent…"
354
+ ></textarea>
355
+ </label>
356
+
357
+ <div id="agentRunResult" class="agent-run-result" hidden></div>
358
+
359
+ <footer class="confirm-actions">
360
+ <button
361
+ id="cancelAgentRunButton"
362
+ class="secondary-button"
363
+ type="button"
364
+ >
365
+ Close
366
+ </button>
367
+ <button
368
+ id="executeAgentRunButton"
369
+ class="primary-button"
370
+ type="button"
371
+ >
372
+ Run
373
+ </button>
374
+ </footer>
375
+ </section>
376
+ </div>
301
377
  </main>
302
378
 
303
379
  <div
@@ -313,6 +389,9 @@
313
389
  <span id="workspaceToastMessage">Workspace saved</span>
314
390
  </div>
315
391
 
316
- <script type="module" src="./app.js?v=workspace-persistence-1"></script>
392
+ <script
393
+ type="module"
394
+ src="/workspace/app.js?v=workspace-agent-panel-1"
395
+ ></script>
317
396
  </body>
318
397
  </html>