@saltcorn/agents 0.5.8 → 0.6.1

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.
package/agent-view.js CHANGED
@@ -30,6 +30,7 @@ const {
30
30
  textarea,
31
31
  label,
32
32
  a,
33
+ br,
33
34
  } = require("@saltcorn/markup/tags");
34
35
  const { getState } = require("@saltcorn/data/db/state");
35
36
  const {
@@ -107,6 +108,15 @@ const configuration_workflow = (req) =>
107
108
  sublabel:
108
109
  "Appears below the input box. Use for additional instructions.",
109
110
  },
111
+ {
112
+ name: "layout",
113
+ label: "Layout",
114
+ type: "String",
115
+ required: true,
116
+ attributes: {
117
+ options: ["Standard", "No card"],
118
+ },
119
+ },
110
120
  {
111
121
  name: "image_upload",
112
122
  label: "Upload images",
@@ -219,17 +229,21 @@ const run = async (
219
229
  image_upload,
220
230
  stream,
221
231
  audio_recorder,
232
+ layout,
222
233
  },
223
234
  state,
224
235
  { res, req },
225
236
  ) => {
226
237
  const action = await Trigger.findOne({ id: action_id });
227
- const prevRuns = (
228
- await WorkflowRun.find(
229
- { trigger_id: action.id, started_by: req.user?.id },
230
- { orderBy: "started_at", orderDesc: true, limit: 30 },
231
- )
232
- ).filter((r) => r.context.interactions);
238
+ if (!action) throw new Error(`Action not found: ${action_id}`);
239
+ const prevRuns = show_prev_runs
240
+ ? (
241
+ await WorkflowRun.find(
242
+ { trigger_id: action.id, started_by: req.user?.id },
243
+ { orderBy: "started_at", orderDesc: true, limit: 30 },
244
+ )
245
+ ).filter((r) => r.context.interactions)
246
+ : null;
233
247
 
234
248
  const cfgMsg = incompleteCfgMsg();
235
249
  if (cfgMsg) return cfgMsg;
@@ -244,137 +258,146 @@ const run = async (
244
258
  }
245
259
  const initial_q = state.run_id ? undefined : state._q;
246
260
  if (state.run_id) {
247
- const run = prevRuns.find((r) => r.id == state.run_id);
261
+ const run = prevRuns
262
+ ? prevRuns.find((r) => r.id == state.run_id)
263
+ : await WorkflowRun.findOne({
264
+ trigger_id: action.id,
265
+ started_by: req.user?.id,
266
+ id: state.run_id,
267
+ });
248
268
  const interactMarkups = [];
249
- for (const interact of run.context.interactions) {
250
- switch (interact.role) {
251
- case "user":
252
- if (interact.content?.[0]?.type === "image_url") {
253
- const image_url = interact.content[0].image_url.url;
254
- if (image_url.startsWith("data"))
269
+ if (run.context.html_interactions) {
270
+ interactMarkups.push(...run.context.html_interactions);
271
+ } else
272
+ for (const interact of run.context.interactions) {
273
+ switch (interact.role) {
274
+ case "user":
275
+ if (interact.content?.[0]?.type === "image_url") {
276
+ const image_url = interact.content[0].image_url.url;
277
+ if (image_url.startsWith("data"))
278
+ interactMarkups.push(
279
+ div(
280
+ { class: "interaction-segment" },
281
+ span({ class: "badge bg-secondary" }, "You"),
282
+ "File",
283
+ ),
284
+ );
285
+ else
286
+ interactMarkups.push(
287
+ div(
288
+ { class: "interaction-segment" },
289
+ span({ class: "badge bg-secondary" }, "You"),
290
+ a({ href: image_url, target: "_blank" }, "File"),
291
+ ),
292
+ );
293
+ } else
255
294
  interactMarkups.push(
256
295
  div(
257
296
  { class: "interaction-segment" },
258
297
  span({ class: "badge bg-secondary" }, "You"),
259
- "File",
298
+ md.render(interact.content),
260
299
  ),
261
300
  );
262
- else
263
- interactMarkups.push(
264
- div(
265
- { class: "interaction-segment" },
266
- span({ class: "badge bg-secondary" }, "You"),
267
- a({ href: image_url, target: "_blank" }, "File"),
268
- ),
301
+ break;
302
+ case "assistant":
303
+ case "system":
304
+ for (const tool_call of interact.tool_calls || []) {
305
+ const toolSkill = find_tool(
306
+ tool_call.function?.name,
307
+ action.configuration,
269
308
  );
270
- } else
271
- interactMarkups.push(
272
- div(
273
- { class: "interaction-segment" },
274
- span({ class: "badge bg-secondary" }, "You"),
275
- md.render(interact.content),
276
- ),
277
- );
278
- break;
279
- case "assistant":
280
- case "system":
281
- for (const tool_call of interact.tool_calls || []) {
282
- const toolSkill = find_tool(
283
- tool_call.function?.name,
284
- action.configuration,
285
- );
286
- if (toolSkill) {
287
- const row = JSON.parse(tool_call.function.arguments);
288
- if (toolSkill.tool.renderToolCall) {
289
- const rendered = await toolSkill.tool.renderToolCall(row, {
290
- req,
291
- });
292
- if (rendered)
293
- interactMarkups.push(
294
- wrapSegment(
295
- wrapCard(
296
- toolSkill.skill.skill_label ||
297
- toolSkill.skill.constructor.skill_name,
298
- rendered,
309
+ if (toolSkill) {
310
+ const row = JSON.parse(tool_call.function.arguments);
311
+ if (toolSkill.tool.renderToolCall) {
312
+ const rendered = await toolSkill.tool.renderToolCall(row, {
313
+ req,
314
+ });
315
+ if (rendered)
316
+ interactMarkups.push(
317
+ wrapSegment(
318
+ wrapCard(
319
+ toolSkill.skill.skill_label ||
320
+ toolSkill.skill.constructor.skill_name,
321
+ rendered,
322
+ ),
323
+ action.name,
299
324
  ),
300
- action.name,
301
- ),
302
- );
325
+ );
326
+ }
303
327
  }
304
328
  }
305
- }
306
- for (const image_call of interact.content?.image_calls || []) {
307
- const toolSkill = find_image_tool(action.configuration);
308
- if (toolSkill) {
309
- if (toolSkill.tool.renderToolResponse) {
310
- const rendered = await toolSkill.tool.renderToolResponse(
311
- image_call,
312
- {
313
- req,
314
- },
315
- );
329
+ for (const image_call of interact.content?.image_calls || []) {
330
+ const toolSkill = find_image_tool(action.configuration);
331
+ if (toolSkill) {
332
+ if (toolSkill.tool.renderToolResponse) {
333
+ const rendered = await toolSkill.tool.renderToolResponse(
334
+ image_call,
335
+ {
336
+ req,
337
+ },
338
+ );
316
339
 
317
- if (rendered)
318
- interactMarkups.push(
319
- wrapSegment(
320
- wrapCard(
321
- toolSkill.skill.skill_label ||
322
- toolSkill.skill.constructor.skill_name,
323
- rendered,
340
+ if (rendered)
341
+ interactMarkups.push(
342
+ wrapSegment(
343
+ wrapCard(
344
+ toolSkill.skill.skill_label ||
345
+ toolSkill.skill.constructor.skill_name,
346
+ rendered,
347
+ ),
348
+ action.name,
324
349
  ),
325
- action.name,
326
- ),
327
- );
350
+ );
351
+ }
328
352
  }
329
353
  }
330
- }
331
- if (
332
- typeof interact.content === "string" ||
333
- typeof interact.content?.content === "string"
334
- )
335
- interactMarkups.push(
336
- div(
337
- { class: "interaction-segment" },
338
- span({ class: "badge bg-secondary" }, action.name),
339
- typeof interact.content === "string"
340
- ? md.render(interact.content)
341
- : typeof interact.content?.content === "string"
342
- ? md.render(interact.content.content)
343
- : interact.content,
344
- ),
345
- );
346
- break;
347
- case "tool":
348
- if (interact.content !== "Action run") {
349
- let markupContent;
350
- const toolSkill = find_tool(interact.name, action.configuration);
351
- try {
352
- if (toolSkill?.tool?.renderToolResponse)
353
- markupContent = await toolSkill?.tool?.renderToolResponse?.(
354
- JSON.parse(interact.content),
355
- {
356
- req,
357
- },
358
- );
359
- } catch {
360
- markupContent = pre(interact.content);
361
- }
362
- if (markupContent)
354
+ if (
355
+ typeof interact.content === "string" ||
356
+ typeof interact.content?.content === "string"
357
+ )
363
358
  interactMarkups.push(
364
- wrapSegment(
365
- wrapCard(
366
- toolSkill?.skill?.skill_label ||
367
- toolSkill?.skill?.constructor.skill_name ||
368
- interact.name,
369
- markupContent,
370
- ),
371
- action.name,
359
+ div(
360
+ { class: "interaction-segment" },
361
+ span({ class: "badge bg-secondary" }, action.name),
362
+ typeof interact.content === "string"
363
+ ? md.render(interact.content)
364
+ : typeof interact.content?.content === "string"
365
+ ? md.render(interact.content.content)
366
+ : interact.content,
372
367
  ),
373
368
  );
374
- }
375
- break;
369
+ break;
370
+ case "tool":
371
+ if (interact.content !== "Action run") {
372
+ let markupContent;
373
+ const toolSkill = find_tool(interact.name, action.configuration);
374
+ try {
375
+ if (toolSkill?.tool?.renderToolResponse)
376
+ markupContent = await toolSkill?.tool?.renderToolResponse?.(
377
+ JSON.parse(interact.content),
378
+ {
379
+ req,
380
+ },
381
+ );
382
+ } catch {
383
+ markupContent = pre(interact.content);
384
+ }
385
+ if (markupContent)
386
+ interactMarkups.push(
387
+ wrapSegment(
388
+ wrapCard(
389
+ toolSkill?.skill?.skill_label ||
390
+ toolSkill?.skill?.constructor.skill_name ||
391
+ interact.name,
392
+ markupContent,
393
+ ),
394
+ action.name,
395
+ ),
396
+ );
397
+ }
398
+ break;
399
+ }
376
400
  }
377
- }
378
401
  runInteractions = interactMarkups.join("");
379
402
  }
380
403
  const skill_form_widgets = [];
@@ -390,10 +413,12 @@ const run = async (
390
413
  }
391
414
 
392
415
  const debugMode = is_debug_mode(action.configuration, req.user);
416
+ const dyn_updates = getState().getConfig("enable_dynamic_updates", true);
417
+
393
418
  const rndid = Math.floor(Math.random() * 16777215).toString(16);
394
419
  const input_form = form(
395
420
  {
396
- onsubmit: `event.preventDefault();spin_send_button();view_post('${viewname}', 'interact', new FormData(this), processCopilotResponse);return false;`,
421
+ onsubmit: `event.preventDefault();spin_send_button();view_post('${viewname}', 'interact', new FormData(this), ${dyn_updates ? "null" : "processCopilotResponse"});return false;`,
397
422
  class: ["form-namespace copilot mt-2 agent-view"],
398
423
  method: "post",
399
424
  },
@@ -455,68 +480,73 @@ const run = async (
455
480
  div({ class: "next_response_scratch" }),
456
481
  );
457
482
 
458
- const prev_runs_side_bar = div(
459
- div(
460
- {
461
- class: "d-flex justify-content-between align-middle mb-2",
462
- },
463
- div(
464
- { class: "d-flex" },
465
- i({
466
- class: "fas fa-caret-down me-1 session-open-sessions",
467
- onclick: "close_session_list()",
468
- }),
469
- h5(req.__("Sessions")),
470
- ),
471
- button(
472
- {
473
- type: "button",
474
- class: "btn btn-secondary btn-sm py-0",
475
- style: "font-size: 0.9em;height:1.5em",
476
- onclick: "unset_state_field('run_id')",
477
- title: "New session",
478
- },
479
- i({ class: "fas fa-redo fa-sm" }),
480
- ),
481
- ),
482
- prevRuns.map((run) =>
483
- div(
484
- {
485
- onclick: `set_state_field('run_id',${run.id})`,
486
- class: "prevcopilotrun border p-2",
487
- },
483
+ const prev_runs_side_bar = show_prev_runs
484
+ ? div(
488
485
  div(
489
- { class: "d-flex justify-content-between" },
490
- localeDateTime(run.started_at),
491
- i({
492
- class: "far fa-trash-alt",
493
- onclick: `delprevrun(event, ${run.id})`,
494
- }),
486
+ {
487
+ class: "d-flex justify-content-between align-middle mb-2",
488
+ },
489
+ div(
490
+ { class: "d-flex" },
491
+ i({
492
+ class: "fas fa-caret-down me-1 session-open-sessions",
493
+ onclick: "close_session_list()",
494
+ }),
495
+ h5(req.__("Sessions")),
496
+ ),
497
+ button(
498
+ {
499
+ type: "button",
500
+ class: "btn btn-secondary btn-sm py-0",
501
+ style: "font-size: 0.9em;height:1.5em",
502
+ onclick: "unset_state_field('run_id')",
503
+ title: "New session",
504
+ },
505
+ i({ class: "fas fa-redo fa-sm" }),
506
+ ),
495
507
  ),
508
+ prevRuns.map((run) =>
509
+ div(
510
+ {
511
+ onclick: `set_state_field('run_id',${run.id})`,
512
+ class: "prevcopilotrun border p-2",
513
+ },
514
+ div(
515
+ { class: "d-flex justify-content-between" },
516
+ localeDateTime(run.started_at),
517
+ i({
518
+ class: "far fa-trash-alt",
519
+ onclick: `delprevrun(event, ${run.id})`,
520
+ }),
521
+ ),
496
522
 
497
- p({ class: "prevrun_content" }, run.context.interactions[0]?.content),
498
- ),
499
- ),
500
- );
501
- const main_chat = div(
502
- { class: "card" },
523
+ p(
524
+ { class: "prevrun_content" },
525
+ run.context.interactions
526
+ .find((i) => typeof i?.content === "string")
527
+ ?.content?.substring?.(0, 80),
528
+ ),
529
+ ),
530
+ ),
531
+ )
532
+ : "";
533
+
534
+ const main_inner = div(
503
535
  div(
504
- { class: "card-body" },
505
- div(
506
- {
507
- class: "open-prev-runs",
508
- style: prev_runs_closed ? {} : { display: "none" },
509
- onclick: "open_session_list()",
510
- },
511
- i({
512
- class: "fas fa-caret-right me-1",
513
- }),
514
- req.__("Sessions"),
515
- ),
516
- div({ id: "copilotinteractions" }, runInteractions),
517
- input_form,
518
- style(
519
- `div.interaction-segment:not(:first-child) {border-top: 1px solid #e7e7e7; }
536
+ {
537
+ class: "open-prev-runs",
538
+ style: prev_runs_closed ? {} : { display: "none" },
539
+ onclick: "open_session_list()",
540
+ },
541
+ i({
542
+ class: "fas fa-caret-right me-1",
543
+ }),
544
+ req.__("Sessions"),
545
+ ),
546
+ div({ id: "copilotinteractions" }, runInteractions),
547
+ input_form,
548
+ style(
549
+ `div.interaction-segment:not(:first-child) {border-top: 1px solid #e7e7e7; }
520
550
  div.interaction-segment {padding-top: 5px;padding-bottom: 5px;}
521
551
  div.interaction-segment p {margin-bottom: 0px;}
522
552
  div.interaction-segment div.card {margin-top: 0.5rem;}
@@ -573,9 +603,10 @@ const run = async (
573
603
  margin-bottom: 0px;
574
604
  display: block;
575
605
  text-overflow: ellipsis;}`,
576
- ),
577
- script(
578
- `
606
+ ),
607
+ script(domReady(`$( "#inputuserinput" ).autogrow({paddingBottom: 20});`)),
608
+ script(
609
+ `
579
610
  function close_session_list() {
580
611
  $("div.prev-runs-list").hide().parents(".col-3").removeClass("col-3").addClass("was-col-3").parent().children(".col-9").removeClass("col-9").addClass("col-12")
581
612
  $("div.open-prev-runs").show()
@@ -587,8 +618,10 @@ const run = async (
587
618
  function get_run_id(elem) {
588
619
  return $("input[name=run_id").val()
589
620
  }
590
- function processCopilotResponse(res) {
621
+ function processCopilotResponse(res) {
622
+ console.log("processCopilotResponse", res)
591
623
  const hadFile = $("input#attach_agent_image").val();
624
+ let fileBadge = hadFile ? '<span class="badge text-bg-info"><i class="fas fa-image me-1"></i>'+$("input#attach_agent_image")[0].files?.item?.(0)?.name||"File"+'</span>': ""
592
625
  $("span.filename-label").text("");
593
626
  $("input#attach_agent_image").val(null);
594
627
  $("#sendbuttonicon").attr("class","far fa-paper-plane");
@@ -596,15 +629,14 @@ const run = async (
596
629
  if(res.run_id && (!$runidin.val() || $runidin.val()=="undefined"))
597
630
  $runidin.val(res.run_id);
598
631
  const wrapSegment = (html, who) => '<div class="interaction-segment"><span class="badge bg-secondary">'+who+'</span>'+html+'</div>'
599
- $("#copilotinteractions").append(wrapSegment('<p>'+$("textarea[name=userinput]").val()+'</p>', "You"))
600
- if(hadFile)
601
- $("#copilotinteractions").append(wrapSegment('File', "You"))
632
+ $("#copilotinteractions").append(wrapSegment('<p>'+$("textarea[name=userinput]").val()+'</p>'+fileBadge, "You"))
602
633
  $("textarea[name=userinput]").val("")
603
634
  $('form.agent-view div.next_response_scratch').html("")
604
635
  window['stream scratch ${viewname} ${rndid}'] = []
605
636
  if(res.response)
606
637
  $("#copilotinteractions").append(res.response)
607
638
  }
639
+ window.processCopilotResponse = processCopilotResponse;
608
640
  function agent_file_attach(e) {
609
641
  $(".attach_agent_image_wrap span.filename-label").text(e.target.files[0].name)
610
642
  }
@@ -648,7 +680,7 @@ const run = async (
648
680
  }
649
681
  }
650
682
  function submitOnEnter(event) {
651
- if (event.which === 13) {
683
+ if (event.which === 13 && !event.shiftKey) {
652
684
  if (!event.repeat) {
653
685
  const newEvent = new Event("submit", {cancelable: true});
654
686
  event.target.form.dispatchEvent(newEvent);
@@ -661,14 +693,17 @@ const run = async (
661
693
  function spin_send_button() {
662
694
  $("#sendbuttonicon").attr("class","fas fa-spinner fa-spin");
663
695
  };`,
664
- stream &&
665
- domReady(
666
- `$('form.agent-view input[name=page_load_tag]').val(window._sc_pageloadtag)`,
667
- ),
668
- initial_q && domReady("$('form.copilot').submit()"),
669
- ),
696
+ stream &&
697
+ domReady(
698
+ `$('form.agent-view input[name=page_load_tag]').val(window._sc_pageloadtag)`,
699
+ ),
700
+ initial_q && domReady("$('form.copilot').submit()"),
670
701
  ),
671
702
  );
703
+ const main_chat =
704
+ layout === "No card"
705
+ ? div({ class: "mx-1" }, main_inner)
706
+ : div({ class: "card" }, div({ class: "card-body" }, main_inner));
672
707
 
673
708
  return show_prev_runs
674
709
  ? div(
@@ -705,17 +740,16 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
705
740
  trigger_id: config.action_id,
706
741
  context: {
707
742
  implemented_fcall_ids: [],
708
- interactions: [...ini_interacts, { role: "user", content: userinput }],
743
+ interactions: [...ini_interacts],
744
+ html_interactions: [],
709
745
  funcalls: {},
710
746
  triggering_row_id,
711
747
  },
712
748
  });
713
749
  } else {
714
750
  run = await WorkflowRun.findOne({ id: +run_id });
715
- await addToContext(run, {
716
- interactions: [{ role: "user", content: userinput }],
717
- });
718
751
  }
752
+ let fileBadges = "";
719
753
  if (config.image_upload && req.files?.file) {
720
754
  const file = await File.from_req_files(
721
755
  req.files.file,
@@ -723,6 +757,11 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
723
757
  100,
724
758
  // file_field?.attributes?.folder
725
759
  );
760
+ fileBadges = span(
761
+ { class: "badge text-bg-info" },
762
+ i({ class: "fas fa-image me-1" }),
763
+ file.filename,
764
+ );
726
765
  const baseUrl = getState().getConfig("base_url").replace(/\/$/, "");
727
766
  let imageurl;
728
767
  if (
@@ -735,24 +774,22 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
735
774
  const b64 = await file.get_contents("base64");
736
775
  imageurl = `data:${file.mimetype};base64,${b64}`;
737
776
  }
777
+ await getState().functions.llm_add_message.run("image", imageurl, {
778
+ chat: run.context.interactions || [],
779
+ });
738
780
  await addToContext(run, {
739
- interactions: [
740
- {
741
- role: "user",
742
- content: [
743
- {
744
- type: "image_url",
745
- image_url: {
746
- url: imageurl,
747
- },
748
- },
749
- ],
750
- },
751
- ],
781
+ interactions: run.context.interactions || [],
752
782
  });
753
783
  }
754
-
755
- return await process_interaction(
784
+ await addToContext(run, {
785
+ interactions: [
786
+ ...(run.context.interactions || []),
787
+ { role: "user", content: userinput },
788
+ ],
789
+ html_interactions: [wrapSegment(p(userinput) + fileBadges, "You")],
790
+ });
791
+ const dyn_updates = getState().getConfig("enable_dynamic_updates", true);
792
+ const process_promise = process_interaction(
756
793
  run,
757
794
  action.configuration,
758
795
  req,
@@ -760,7 +797,21 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
760
797
  [],
761
798
  triggering_row,
762
799
  config,
800
+ dyn_updates,
763
801
  );
802
+ if (dyn_updates) {
803
+ process_promise.catch((e) => {
804
+ console.error(e);
805
+ getState().emitDynamicUpdate(
806
+ db.getTenantSchema(),
807
+ {
808
+ error: e?.message || e,
809
+ },
810
+ [req.user.id],
811
+ );
812
+ });
813
+ return;
814
+ } else return await process_promise;
764
815
  };
765
816
 
766
817
  const delprevrun = async (table_id, viewname, config, body, { req, res }) => {
@@ -845,36 +896,42 @@ const skillroute = async (table_id, viewname, config, body, { req, res }) => {
845
896
  },
846
897
  };
847
898
  };
848
- const wrapAction = (
849
- inner_markup,
899
+
900
+ const execute_user_action = async (
901
+ table_id,
850
902
  viewname,
851
- tool_call,
852
- actionClass,
853
- implemented,
854
- run,
855
- ) =>
856
- wrapCard(
857
- actionClass.title,
858
- inner_markup + implemented
859
- ? button(
860
- {
861
- type: "button",
862
- class: "btn btn-secondary d-block mt-3 float-end",
863
- disabled: true,
864
- },
865
- i({ class: "fas fa-check me-1" }),
866
- "Applied",
867
- )
868
- : button(
869
- {
870
- type: "button",
871
- id: "exec-" + tool_call.id,
872
- class: "btn btn-primary d-block mt-3 float-end",
873
- onclick: `press_store_button(this, true);view_post('${viewname}', 'execute', {fcall_id: '${tool_call.id}', run_id: ${run.id}}, processExecuteResponse)`,
874
- },
875
- "Apply",
876
- ) + div({ id: "postexec-" + tool_call.id }),
903
+ config,
904
+ body,
905
+ { req, res },
906
+ ) => {
907
+ const { run_id, rndid, uaname } = body;
908
+
909
+ const action = await Trigger.findOne({ id: config.action_id });
910
+ const run = await WorkflowRun.findOne({ id: +run_id });
911
+ //console.log("run uas",run.context.user_actions );
912
+
913
+ if (!run) return;
914
+ const instances = get_skill_instances(action.configuration);
915
+ const instance = instances.find((i) => i.userActions?.[uaname]);
916
+ //console.log({ instance });
917
+
918
+ if (!instance) return;
919
+ const uadata = (run.context.user_actions || []).find(
920
+ (ua) => ua.rndid === rndid,
877
921
  );
922
+ if (!uadata) return;
923
+ const result = await instance.userActions[uaname]({
924
+ user: req.user,
925
+ ...uadata.tool_call.input,
926
+ ...uadata.input,
927
+ });
928
+ return {
929
+ json: {
930
+ success: "ok",
931
+ ...result,
932
+ },
933
+ };
934
+ };
878
935
 
879
936
  module.exports = {
880
937
  name: "Agent Chat",
@@ -884,5 +941,5 @@ module.exports = {
884
941
  //tableless: true,
885
942
  table_optional: true,
886
943
  run,
887
- routes: { interact, delprevrun, debug_info, skillroute },
944
+ routes: { interact, delprevrun, debug_info, skillroute, execute_user_action },
888
945
  };