@saltcorn/agents 0.7.10 → 0.7.12

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
@@ -61,6 +61,14 @@ const configuration_workflow = (req) =>
61
61
  {
62
62
  name: "Agent action",
63
63
  form: async (context) => {
64
+ let run_id_field_opts;
65
+ if (context.table_id) {
66
+ const table = Table.findOne({ id: context.table_id });
67
+ run_id_field_opts = table.fields
68
+ .filter((f) => f.type?.name === "Integer" && !f.primary_key)
69
+ .map((f) => f.name);
70
+ }
71
+
64
72
  const agent_actions = await Trigger.find({ action: "Agent" });
65
73
  return new Form({
66
74
  fields: [
@@ -114,13 +122,29 @@ const configuration_workflow = (req) =>
114
122
  sublabel:
115
123
  "Appears below the input box. Use for additional instructions.",
116
124
  },
125
+ ...(run_id_field_opts
126
+ ? [
127
+ {
128
+ name: "run_id_field",
129
+ type: "String",
130
+ label: "Run ID field",
131
+ sublabel: "Set this field to the run ID",
132
+ attributes: { options: run_id_field_opts },
133
+ },
134
+ ]
135
+ : []),
117
136
  {
118
137
  name: "layout",
119
138
  label: "Layout",
120
139
  type: "String",
121
140
  required: true,
122
141
  attributes: {
123
- options: ["Standard", "No card", "Modern chat", "Modern chat - no card"],
142
+ options: [
143
+ "Standard",
144
+ "No card",
145
+ "Modern chat",
146
+ "Modern chat - no card",
147
+ ],
124
148
  },
125
149
  },
126
150
  {
@@ -289,9 +313,7 @@ const run = async (
289
313
  if (interact.content?.[0]?.type === "image_url") {
290
314
  const image_url = interact.content[0].image_url.url;
291
315
  if (image_url.startsWith("data"))
292
- interactMarkups.push(
293
- wrapSegment("File", "You", true, layout),
294
- );
316
+ interactMarkups.push(wrapSegment("File", "You", true, layout));
295
317
  else
296
318
  interactMarkups.push(
297
319
  wrapSegment(
@@ -303,12 +325,7 @@ const run = async (
303
325
  );
304
326
  } else
305
327
  interactMarkups.push(
306
- wrapSegment(
307
- md.render(interact.content),
308
- "You",
309
- true,
310
- layout,
311
- ),
328
+ wrapSegment(md.render(interact.content), "You", true, layout),
312
329
  );
313
330
  break;
314
331
  case "assistant":
@@ -494,8 +511,7 @@ const run = async (
494
511
  ),
495
512
  explainer && small({ class: "explainer" }, i(explainer)),
496
513
  ),
497
- stream &&
498
- realTimeCollabScript(viewname, rndid, layout),
514
+ stream && realTimeCollabScript(viewname, rndid, layout),
499
515
  );
500
516
 
501
517
  const isModernSidebar = layout && layout.startsWith("Modern chat");
@@ -527,7 +543,8 @@ const run = async (
527
543
  )
528
544
  : div(
529
545
  {
530
- class: "d-flex flex-wrap justify-content-between align-middle mb-2",
546
+ class:
547
+ "d-flex flex-wrap justify-content-between align-middle mb-2",
531
548
  },
532
549
  div(
533
550
  { class: "d-flex" },
@@ -564,7 +581,10 @@ const run = async (
564
581
  (isActive ? " active-session" : ""),
565
582
  },
566
583
  div(
567
- { class: "d-flex justify-content-between align-items-center mb-1" },
584
+ {
585
+ class:
586
+ "d-flex justify-content-between align-items-center mb-1",
587
+ },
568
588
  small(
569
589
  { class: "text-muted text-truncate", style: "min-width:0" },
570
590
  localeDateTime(run.started_at),
@@ -592,10 +612,7 @@ const run = async (
592
612
  onclick: `delprevrun(event, ${run.id})`,
593
613
  }),
594
614
  ),
595
- p(
596
- { class: "prevrun_content" },
597
- preview,
598
- ),
615
+ p({ class: "prevrun_content" }, preview),
599
616
  );
600
617
  }),
601
618
  )
@@ -1108,7 +1125,10 @@ const run = async (
1108
1125
  const isModern = layout && layout.startsWith("Modern chat");
1109
1126
  const main_chat =
1110
1127
  layout === "Modern chat"
1111
- ? div({ class: "card" }, div({ class: "card-body modern-chat-layout" }, main_inner))
1128
+ ? div(
1129
+ { class: "card" },
1130
+ div({ class: "card-body modern-chat-layout" }, main_inner),
1131
+ )
1112
1132
  : layout === "Modern chat - no card"
1113
1133
  ? div({ class: "modern-chat-layout" }, main_inner)
1114
1134
  : layout === "No card"
@@ -1157,6 +1177,14 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
1157
1177
  triggering_row_id,
1158
1178
  },
1159
1179
  });
1180
+ if (table_id && config.run_id_field && triggering_row_id) {
1181
+ const table = Table.findOne(table_id);
1182
+ await table.updateRow(
1183
+ { [config.run_id_field]: run.id },
1184
+ triggering_row_id,
1185
+ );
1186
+ if (triggering_row) triggering_row[config.run_id_field] = run.id;
1187
+ }
1160
1188
  } else {
1161
1189
  run = await WorkflowRun.findOne({ id: +run_id });
1162
1190
  }
@@ -1437,6 +1465,57 @@ const execute_user_action = async (
1437
1465
  ...uadata.tool_call.input,
1438
1466
  ...uadata.input,
1439
1467
  });
1468
+ if (result.generate_prompt) {
1469
+ const action =
1470
+ config.agent_action || (await Trigger.findOne({ id: config.action_id }));
1471
+ run.context.interactions.push({
1472
+ role: "user",
1473
+ content: result.generate_prompt,
1474
+ });
1475
+ const dyn_updates = getState().getConfig("enable_dynamic_updates", true);
1476
+
1477
+ if (dyn_updates && uadata.click_replace_text) {
1478
+ getState().emitDynamicUpdate(
1479
+ db.getTenantSchema(),
1480
+ {
1481
+ eval_js: `spin_send_button();${`$("button[data-useraction-id=${uadata.rndid}]").replaceWith("${uadata.click_replace_text}")`}`,
1482
+ page_load_tag: req?.headers?.["page-load-tag"],
1483
+ },
1484
+ [req.user.id],
1485
+ );
1486
+ // remove from html_interactions
1487
+ run.context.html_interactions = run.context.html_interactions.map(
1488
+ (hi) => {
1489
+ if (hi.includes(`button data-useraction-id="${uadata.rndid}"`))
1490
+ return wrapSegment(
1491
+ uadata.click_replace_text,
1492
+ "You",
1493
+ true,
1494
+ config.layout,
1495
+ );
1496
+ return hi;
1497
+ },
1498
+ );
1499
+ await run.update({ context: run.context });
1500
+ }
1501
+ await process_interaction(
1502
+ run,
1503
+ action.configuration,
1504
+ req,
1505
+ action.name,
1506
+ [],
1507
+ {}, //row?
1508
+ config,
1509
+ dyn_updates,
1510
+ );
1511
+ const { generate_prompt, ...restResult } = result;
1512
+ return {
1513
+ json: {
1514
+ success: "ok",
1515
+ ...restResult,
1516
+ },
1517
+ };
1518
+ }
1440
1519
  return {
1441
1520
  json: {
1442
1521
  success: "ok",
package/common.js CHANGED
@@ -41,6 +41,7 @@ const get_skills = () => {
41
41
  require("./skills/WebSearch"),
42
42
  require("./skills/Subagent"),
43
43
  require("./skills/ExternalSkill"),
44
+ require("./skills/PlanApproval"),
44
45
  //require("./skills/AdaptiveFeedback"),
45
46
  ...exchange_skills,
46
47
  ];
@@ -416,17 +417,49 @@ const process_interaction = async (
416
417
  myHasResult = true;
417
418
  let result = await tool.tool.process(tool_call.input, {
418
419
  req,
419
- });
420
+ });
421
+ const tool_response = result.add_response || result;
420
422
  toolResults[tool_call.tool_call_id] = result;
421
423
  if (result?.stop) stop = true;
424
+ if (result?.add_user_action && viewname) {
425
+ const user_actions = Array.isArray()
426
+ ? result.add_user_action
427
+ : [result.add_user_action];
428
+ for (const uact of user_actions) {
429
+ uact.rndid = Math.floor(Math.random() * 16777215).toString(16);
430
+ uact.tool_call = tool_call;
431
+ }
432
+ await addToContext(run, {
433
+ user_actions,
434
+ });
435
+ add_response(
436
+ div(
437
+ { class: "d-flex mb-2" },
438
+ user_actions.map((ua) =>
439
+ button(
440
+ {
441
+ "data-useraction-id": ua.rndid,
442
+ class: "btn btn-primary", //press_store_button(this, true);
443
+ onclick: `view_post('${viewname}', 'execute_user_action', {uaname: "${ua.name}",rndid: "${ua.rndid}", run_id: ${run.id}}, processExecuteResponse)`,
444
+ },
445
+ ua.label,
446
+ ),
447
+ ),
448
+ ),
449
+ );
450
+ }
422
451
  if (
423
- (typeof result === "object" && Object.keys(result || {}).length) ||
424
- typeof result === "string"
452
+ (typeof tool_response === "object" &&
453
+ Object.keys(tool_response || {}).length) ||
454
+ typeof tool_response === "string"
425
455
  ) {
426
456
  if (tool.tool.renderToolResponse) {
427
- const rendered = await tool.tool.renderToolResponse(result, {
428
- req,
429
- });
457
+ const rendered = await tool.tool.renderToolResponse(
458
+ tool_response,
459
+ {
460
+ req,
461
+ },
462
+ );
430
463
  if (rendered)
431
464
  add_response(
432
465
  wrapSegment(
@@ -441,14 +474,14 @@ const process_interaction = async (
441
474
  }
442
475
  await sysState.functions.llm_add_message.run(
443
476
  "tool_response",
444
- !result || typeof result === "string"
477
+ !tool_response || typeof tool_response === "string"
445
478
  ? {
446
479
  type: "text",
447
- value: result || "Action run",
480
+ value: tool_response || "Action run",
448
481
  }
449
482
  : {
450
483
  type: "json",
451
- value: JSON.parse(JSON.stringify(result)),
484
+ value: JSON.parse(JSON.stringify(tool_response)),
452
485
  },
453
486
  {
454
487
  chat: run.context.interactions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/agents",
3
- "version": "0.7.10",
3
+ "version": "0.7.12",
4
4
  "description": "AI agents for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -0,0 +1,103 @@
1
+ const { div, pre, a } = require("@saltcorn/markup/tags");
2
+ const Workflow = require("@saltcorn/data/models/workflow");
3
+ const Form = require("@saltcorn/data/models/form");
4
+ const Table = require("@saltcorn/data/models/table");
5
+ const User = require("@saltcorn/data/models/user");
6
+ const File = require("@saltcorn/data/models/file");
7
+ const View = require("@saltcorn/data/models/view");
8
+ const Trigger = require("@saltcorn/data/models/trigger");
9
+ const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
10
+ const { getState } = require("@saltcorn/data/db/state");
11
+ const db = require("@saltcorn/data/db");
12
+ const { eval_expression } = require("@saltcorn/data/models/expression");
13
+ const { interpolate, sleep } = require("@saltcorn/data/utils");
14
+ const { features } = require("@saltcorn/data/db/state");
15
+ const { button } = require("@saltcorn/markup/tags");
16
+ const { validID } = require("@saltcorn/markup/layout_utils");
17
+
18
+ const vm = require("vm");
19
+
20
+ //const { fieldProperties } = require("./helpers");
21
+
22
+ class PlanApprovalSkill {
23
+ static skill_name = "Plan approval";
24
+
25
+ get skill_label() {
26
+ return "Plan approval";
27
+ }
28
+
29
+ constructor(cfg) {
30
+ Object.assign(this, cfg);
31
+ }
32
+
33
+ static async configFields() {
34
+ return [
35
+ {
36
+ name: "ini_sys_prompt",
37
+ label: "Initial system prompt",
38
+ type: "String",
39
+ fieldview: "textarea",
40
+ sublabel: "Refer to the tool as <code>submit_plan_for_approval</code>",
41
+ },
42
+ {
43
+ name: "approval_prompt",
44
+ label: "Prompt on approval",
45
+ type: "String",
46
+ fieldview: "textarea",
47
+ sublabel: "If the user approves the plan, what should the agent do?",
48
+ },
49
+ ];
50
+ }
51
+ systemPrompt() {
52
+ return this.ini_sys_prompt;
53
+ }
54
+
55
+ get userActions() {
56
+ return {
57
+ approve_plan: async () => {
58
+ return { generate_prompt: this.approval_prompt };
59
+ },
60
+ };
61
+ }
62
+
63
+ provideTools = () => {
64
+ return {
65
+ type: "function",
66
+ process: async (row) => {
67
+ console.log("process plan row", row);
68
+
69
+ return {
70
+ stop: true,
71
+ add_response: row.plan,
72
+ add_user_action: {
73
+ name: "approve_plan",
74
+ type: "button",
75
+ label: `Approve`,
76
+ click_replace_text: "Approved",
77
+ input: {},
78
+ },
79
+ };
80
+ },
81
+ renderToolCall({ plan }) {
82
+ return plan;
83
+ },
84
+ function: {
85
+ name: "submit_plan_for_approval",
86
+ description:
87
+ "Submit a plan for approval by the user. If the plan is approved, further instructions about executing it may be given",
88
+ parameters: {
89
+ type: "object",
90
+ required: ["plan"],
91
+ properties: {
92
+ plan: {
93
+ description: "The plan",
94
+ type: "string",
95
+ },
96
+ },
97
+ },
98
+ },
99
+ };
100
+ };
101
+ }
102
+
103
+ module.exports = PlanApprovalSkill;