@saltcorn/agents 0.7.4 → 0.7.6

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/action.js CHANGED
@@ -90,6 +90,8 @@ module.exports = {
90
90
  run_id,
91
91
  req,
92
92
  is_sub_agent,
93
+ agent_view_config,
94
+ dyn_updates,
93
95
  ...rest
94
96
  }) => {
95
97
  const userinput = interpolate(configuration.prompt, row, user);
@@ -115,9 +117,9 @@ module.exports = {
115
117
  undefined,
116
118
  [],
117
119
  row,
118
- { stream: false },
119
- false,
120
- is_sub_agent
120
+ agent_view_config || { stream: false },
121
+ dyn_updates,
122
+ is_sub_agent,
121
123
  );
122
124
  },
123
125
  };
package/agent-view.js CHANGED
@@ -192,13 +192,15 @@ const realTimeCollabScript = (viewname, rndid, layout) => {
192
192
  ['${view.getRealTimeEventName(
193
193
  "STREAM_CHUNK",
194
194
  )}' + \`?page_load_tag=\${_sc_pageloadtag}\`]: async (data) => {
195
+ $(".agent-waiting-indicator").remove();
195
196
  window['stream scratch ${viewname} ${rndid}'].push(data.content)
196
197
  const rendered = md.render(window['stream scratch ${viewname} ${rndid}'].join(""));
197
- $('form.agent-view div.next_response_scratch').html(
198
+ $('div.next_response_scratch').html(
198
199
  (${JSON.stringify(layout || "")} || "").startsWith("Modern chat")
199
200
  ? '<div class="chat-message chat-assistant"><div class="chat-avatar"><i class="fas fa-robot"></i></div><div class="chat-bubble">' + rendered + '</div></div>'
200
201
  : rendered
201
202
  );
203
+ scrollAgentToBottom();
202
204
  }
203
205
  }
204
206
  };
@@ -434,7 +436,7 @@ const run = async (
434
436
  const rndid = Math.floor(Math.random() * 16777215).toString(16);
435
437
  const input_form = form(
436
438
  {
437
- onsubmit: `event.preventDefault();spin_send_button();view_post('${viewname}', 'interact', new FormData(this), ${dyn_updates ? "null" : "processCopilotResponse"});return false;`,
439
+ onsubmit: `event.preventDefault();const _fd=new FormData(this);spin_send_button();view_post('${viewname}', 'interact', _fd, ${dyn_updates ? "null" : "processCopilotResponse"});return false;`,
438
440
  class: ["form-namespace copilot mt-2 agent-view"],
439
441
  method: "post",
440
442
  },
@@ -492,8 +494,7 @@ const run = async (
492
494
  explainer && small({ class: "explainer" }, i(explainer)),
493
495
  ),
494
496
  stream &&
495
- realTimeCollabScript(viewname, rndid, layout) +
496
- div({ class: "next_response_scratch" }),
497
+ realTimeCollabScript(viewname, rndid, layout),
497
498
  );
498
499
 
499
500
  const isModernSidebar = layout && layout.startsWith("Modern chat");
@@ -612,6 +613,7 @@ const run = async (
612
613
  req.__("Sessions"),
613
614
  ),
614
615
  div({ id: "copilotinteractions" }, runInteractions),
616
+ stream ? div({ class: "next_response_scratch" }) : "",
615
617
  input_form,
616
618
  style(
617
619
  `div.interaction-segment:not(:first-child) {border-top: 1px solid #e7e7e7; }
@@ -685,6 +687,14 @@ const run = async (
685
687
  margin-bottom: 0px;
686
688
  display: block;
687
689
  text-overflow: ellipsis;}
690
+ /* Typing / Waiting Indicator */
691
+ .agent-waiting-indicator { display:flex; align-items:center; padding:0.75rem 1rem; }
692
+ .typing-dots { display:flex; gap:4px; align-items:center; }
693
+ .typing-dots span { width:8px; height:8px; border-radius:50%; background:#6c757d; animation:typingBounce 1.4s infinite ease-in-out both; }
694
+ .typing-dots span:nth-child(1) { animation-delay:-0.32s; }
695
+ .typing-dots span:nth-child(2) { animation-delay:-0.16s; }
696
+ .typing-dots span:nth-child(3) { animation-delay:0s; }
697
+ @keyframes typingBounce { 0%,80%,100%{transform:scale(.6);opacity:.4} 40%{transform:scale(1);opacity:1} }
688
698
  /* Modern Chat Layout */
689
699
  .modern-chat-layout {
690
700
  display: flex;
@@ -881,6 +891,16 @@ const run = async (
881
891
  script(domReady(`$( "#inputuserinput" ).autogrow({paddingBottom: 20});`)),
882
892
  script(
883
893
  `
894
+ function scrollAgentToBottom() {
895
+ const container = document.getElementById('copilotinteractions');
896
+ if (container) {
897
+ if (container.scrollHeight > container.clientHeight) {
898
+ container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
899
+ }
900
+ const inputForm = document.querySelector('form.agent-view');
901
+ if (inputForm) inputForm.scrollIntoView({ behavior: 'smooth', block: 'end' });
902
+ }
903
+ }
884
904
  function close_session_list() {
885
905
  $("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")
886
906
  $("div.open-prev-runs").show()
@@ -916,14 +936,21 @@ const run = async (
916
936
  if(user_input && (!${JSON.stringify(dyn_updates)}))
917
937
  $("#copilotinteractions").append(wrapSegment('<p>'+user_input+'</p>'+fileBadge, "You", true))
918
938
  $("textarea[name=userinput]").val("")
919
- $('form.agent-view div.next_response_scratch').html("")
939
+ $('div.next_response_scratch').html("")
920
940
  window['stream scratch ${viewname} ${rndid}'] = []
921
- if(res.response)
922
- $("#copilotinteractions").append(res.response)
941
+ if(res.response) {
942
+ $(".agent-waiting-indicator").remove();
943
+ $("#copilotinteractions").append(res.response);
944
+ scrollAgentToBottom();
945
+ }
923
946
  }
924
947
  window.processCopilotResponse = processCopilotResponse;
925
948
  window.final_agent_response = () => {
926
949
  $("#sendbuttonicon").attr("class","far fa-paper-plane");
950
+ $(".agent-waiting-indicator").remove();
951
+ $("textarea[name=userinput]").prop("disabled", false).attr("placeholder", ${JSON.stringify(placeholder || "How can I help you?")}).focus();
952
+ $(".copilot-entry .submit-button").css("pointer-events", "");
953
+ scrollAgentToBottom();
927
954
  }
928
955
  window._agentDT = new DataTransfer();
929
956
  function setAgentFiles(files) {
@@ -1046,6 +1073,14 @@ const run = async (
1046
1073
  }
1047
1074
  function spin_send_button() {
1048
1075
  $("#sendbuttonicon").attr("class","fas fa-spinner fa-spin");
1076
+ $("textarea[name=userinput]").prop("disabled", true).attr("placeholder", "Waiting for response...");
1077
+ $(".copilot-entry .submit-button").css("pointer-events", "none");
1078
+ const isModernLayout = ${JSON.stringify((layout || "").startsWith("Modern chat"))};
1079
+ const indicator = isModernLayout
1080
+ ? '<div class="agent-waiting-indicator chat-message chat-assistant"><div class="chat-avatar"><i class="fas fa-robot"></i></div><div class="chat-bubble"><div class="typing-dots"><span></span><span></span><span></span></div></div></div>'
1081
+ : '<div class="agent-waiting-indicator"><div class="typing-dots"><span></span><span></span><span></span></div></div>';
1082
+ $('div.next_response_scratch').before(indicator);
1083
+ scrollAgentToBottom();
1049
1084
  };`,
1050
1085
  stream &&
1051
1086
  domReady(
package/common.js CHANGED
@@ -277,7 +277,9 @@ const process_interaction = async (
277
277
  run.context.interactions[run.context.interactions.length - 1];
278
278
 
279
279
  const answer = await sysState.functions.llm_generate.run(
280
- lastInteract?.role === "user" ? "" : "Continue",
280
+ lastInteract?.role === "user" || lastInteract?.role === "tool"
281
+ ? ""
282
+ : "Continue",
281
283
  complArgs,
282
284
  );
283
285
 
@@ -487,6 +489,8 @@ const process_interaction = async (
487
489
  chat,
488
490
  req,
489
491
  run,
492
+ agent_view_config: agentsViewCfg,
493
+ dyn_updates,
490
494
  async generate(prompt, opts = {}) {
491
495
  generateUsed = true;
492
496
  return await sysState.functions.llm_generate.run(prompt, {
@@ -530,9 +534,7 @@ const process_interaction = async (
530
534
  for (const add_resp of postprocres.add_responses || []) {
531
535
  raw_responses.push(add_resp);
532
536
  const renderedAddResponse =
533
- typeof add_resp === "string"
534
- ? md.render(add_resp)
535
- : add_resp;
537
+ typeof add_resp === "string" ? md.render(add_resp) : add_resp;
536
538
  add_response(
537
539
  wrapSegment(
538
540
  wrapCard(
@@ -635,16 +637,22 @@ const process_interaction = async (
635
637
  return {
636
638
  json: {
637
639
  success: "ok",
638
- ...(is_sub_agent ? { raw_responses } : {}),
640
+ ...(is_sub_agent && !stream ? { raw_responses } : {}),
639
641
  response: [...prevResponses, ...responses].join(""),
640
642
  run_id: run?.id,
641
643
  },
642
644
  };
643
645
  };
644
646
 
647
+ const replaceUserContinue = (chat, newPrompt) => {
648
+ const lastChat = chat[chat.length - 1];
649
+ console.log("lastChat", lastChat);
650
+ };
651
+
645
652
  module.exports = {
646
653
  get_skills,
647
654
  get_skill_class,
655
+ replaceUserContinue,
648
656
  incompleteCfgMsg,
649
657
  find_tool,
650
658
  get_skill_instances,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/agents",
3
- "version": "0.7.4",
3
+ "version": "0.7.6",
4
4
  "description": "AI agents for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -16,6 +16,7 @@ const { button } = require("@saltcorn/markup/tags");
16
16
  const { validID } = require("@saltcorn/markup/layout_utils");
17
17
 
18
18
  const vm = require("vm");
19
+ const { replaceUserContinue } = require("../common");
19
20
 
20
21
  //const { fieldProperties } = require("./helpers");
21
22
 
@@ -100,7 +101,8 @@ class GenerateAndRunJsCodeSkill {
100
101
  },
101
102
  {
102
103
  name: "allow_functions",
103
- label: "Allow calls to functions from codepages and modules",
104
+ label: "Allow system functions",
105
+ sublabel: "Allow calls to functions from codepages and modules",
104
106
  type: "Bool",
105
107
  },
106
108
  ...(Table.subClass
@@ -131,12 +133,14 @@ class GenerateAndRunJsCodeSkill {
131
133
  req,
132
134
  generate,
133
135
  emit_update,
136
+ chat,
134
137
  ...rest
135
138
  }) => {
136
139
  //console.log("postprocess args", { tool_call, ...rest });
137
140
  emit_update("Generating code");
138
- const str = await generate(
139
- `You will now be asked to write JavaScript code.
141
+ const gen_the_code = async (extra) => {
142
+ const str = await generate(
143
+ `You will now be asked to write JavaScript code.
140
144
  ${this.code_description ? "\nSome more information: " + this.code_description : ""}
141
145
  ${this.allow_fetch ? "\nYou can use the standard fetch JavaScript function to make HTTP(S) requests." : ""}
142
146
  ${this.allow_table ? getTablePrompt(this.read_only) : ""}
@@ -165,24 +169,53 @@ const y = await anotherAsyncFunction(x)
165
169
  return { x, y }
166
170
  \`\`\`
167
171
 
172
+ ${extra || ""}
168
173
 
169
174
  Now generate the JavaScript code required by the user.`,
170
- );
171
- getState().log(
172
- 6,
173
- "Generated code:\n--BEGIN CODE--\n" + str + "\n--END CODE--\n",
174
- );
175
- const js_code = str.includes("```javascript")
176
- ? str.split("```javascript")[1].split("```")[0]
177
- : str;
178
- emit_update("Running code");
179
- const res = await this.runCode(js_code, { user: req.user });
180
- //console.log("code response", res);
181
- getState().log(6, "Code answer: " + JSON.stringify(res));
182
- return {
183
- stop: typeof res ==="string",
184
- add_response: res,
175
+ );
176
+ getState().log(
177
+ 6,
178
+ "Generated code:\n--BEGIN CODE--\n" + str + "\n--END CODE--\n",
179
+ );
180
+ const js_code = str.includes("```javascript")
181
+ ? str.split("```javascript")[1].split("```")[0]
182
+ : str;
183
+ return js_code;
185
184
  };
185
+ const js_code = await gen_the_code();
186
+ emit_update("Running code");
187
+ try {
188
+ const res = await this.runCode(js_code, { user: req.user });
189
+ //console.log("code response", res);
190
+ getState().log(6, "Code answer: " + JSON.stringify(res));
191
+ return {
192
+ stop: typeof res === "string",
193
+ add_response: res,
194
+ };
195
+ } catch (err) {
196
+ console.error(err);
197
+ const retry_js_code =
198
+ await gen_the_code(`You were previously asked to complete this task. This was the code generated:
199
+ \`\`\`javascript
200
+ ${js_code}
201
+ \`\`\`
202
+
203
+ this code produced the following error:
204
+
205
+ \`\`\`
206
+ ${err.message}
207
+ \`\`\`
208
+
209
+ Correct this error.
210
+ `);
211
+ const res = await this.runCode(retry_js_code, { user: req.user });
212
+ //console.log("code response", res);
213
+ getState().log(6, "Code retry answer: " + JSON.stringify(res));
214
+ return {
215
+ stop: typeof res === "string",
216
+ add_response: res,
217
+ };
218
+ }
186
219
  },
187
220
  function: {
188
221
  name: this.tool_name,
@@ -34,8 +34,9 @@ class ModelPicker {
34
34
  class: ["form-select form-select-sm w-unset", klass],
35
35
  name: "modelpicker",
36
36
  },
37
- this.placeholder && option({ disabled: true }, this.placeholder),
38
- option({ value: "" }, this.default_label),
37
+ this.placeholder &&
38
+ option({ disabled: true }, this.placeholder || "Select model"),
39
+ option({ value: "" }, this.default_label || "Standard"),
39
40
  alt_config_options.map((o) => option(o)),
40
41
  );
41
42
  }
@@ -8,6 +8,7 @@ const { getState } = require("@saltcorn/data/db/state");
8
8
  const db = require("@saltcorn/data/db");
9
9
  const { fieldProperties } = require("./helpers");
10
10
  const agent_action = require("../action");
11
+ const { replaceUserContinue } = require("../common");
11
12
 
12
13
  class SubagentToSkill {
13
14
  static skill_name = "Subagent";
@@ -45,7 +46,12 @@ class SubagentToSkill {
45
46
  required: true,
46
47
  attributes: { options: actions.map((a) => a.name) },
47
48
  },
48
- // TODO: confirm, show response, show argument
49
+ {
50
+ name: "handover_prompt",
51
+ label: "Handover prompt",
52
+ sublabel: `Optional. The prompt initialising the subagent. Example: "Continue answering my query using the tool now at you disposal"`,
53
+ type: "String",
54
+ },
49
55
  ];
50
56
  }
51
57
 
@@ -65,15 +71,38 @@ class SubagentToSkill {
65
71
  /*renderToolCall({ phrase }, { req }) {
66
72
  return div({ class: "border border-primary p-2 m-2" }, phrase);
67
73
  },*/
68
- postProcess: async ({ tool_call, req, generate, emit_update, run }) => {
74
+ postProcess: async ({
75
+ tool_call,
76
+ req,
77
+ generate,
78
+ emit_update,
79
+ run,
80
+ chat,
81
+ agent_view_config,
82
+ dyn_updates,
83
+ }) => {
84
+ getState().log(6, "Running subagent", this.agent_name);
69
85
  const subres = await agent_action.run({
70
86
  row: {},
71
- configuration: { ...trigger.configuration, prompt: "continue" },
87
+ configuration: {
88
+ ...trigger.configuration,
89
+ prompt:
90
+ this.handover_prompt ||
91
+ "Your instructions and tools have changed. Continue answering my query using the instructions and tools at you disposal, if any",
92
+ },
72
93
  user: req.user,
73
94
  run_id: run.id,
74
95
  is_sub_agent: true,
96
+ agent_view_config,
97
+ dyn_updates,
75
98
  req,
76
99
  });
100
+ getState().log(
101
+ 6,
102
+ "Subagent response",
103
+ subres?.json?.raw_responses || "No response",
104
+ );
105
+
77
106
  if (subres.json.raw_responses)
78
107
  return { add_responses: subres.json.raw_responses };
79
108
  return {
@@ -41,7 +41,7 @@ beforeAll(async () => {
41
41
  //await getState().setConfig("log_level", 6);
42
42
  });
43
43
 
44
- jest.setTimeout(30000);
44
+ jest.setTimeout(40000);
45
45
 
46
46
  const user = { id: 1, role_id: 1 };
47
47
  const action = require("../action");
@@ -128,7 +128,7 @@ for (const nameconfig of require("./configs")) {
128
128
  const result = await action.run({
129
129
  row: {
130
130
  theprompt:
131
- "What is the 16th Fibonacci number (when F1=1 and F2=1)? Consult both the math agent and the oracle and see if they agree. You must call both the OracleAgent and the MathsAgent tools simultaneously",
131
+ "What is the 16th Fibonacci number (when F1=1 and F2=1)? Consult both the math agent and the oracle and see if they agree. Call both the OracleAgent and the MathsAgent tools",
132
132
  },
133
133
  configuration,
134
134
  user,