@saltcorn/agents 0.7.6 → 0.7.7
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 +16 -14
- package/agent-view.js +88 -4
- package/common.js +22 -22
- package/package.json +1 -1
- package/skills/GenerateAndRunJsCode.js +61 -13
- package/skills/Subagent.js +14 -9
- package/tests/action.test.js +14 -3
package/action.js
CHANGED
|
@@ -96,20 +96,22 @@ module.exports = {
|
|
|
96
96
|
}) => {
|
|
97
97
|
const userinput = interpolate(configuration.prompt, row, user);
|
|
98
98
|
|
|
99
|
-
const run =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
99
|
+
const run =
|
|
100
|
+
rest.run ||
|
|
101
|
+
(run_id
|
|
102
|
+
? await WorkflowRun.findOne({ id: run_id })
|
|
103
|
+
: await WorkflowRun.create({
|
|
104
|
+
status: "Running",
|
|
105
|
+
started_by: user?.id,
|
|
106
|
+
trigger_id: trigger_id || undefined,
|
|
107
|
+
context: {
|
|
108
|
+
implemented_fcall_ids: [],
|
|
109
|
+
interactions: [],
|
|
110
|
+
funcalls: {},
|
|
111
|
+
},
|
|
112
|
+
}));
|
|
113
|
+
|
|
114
|
+
run.context.interactions.push({ role: "user", content: userinput });
|
|
113
115
|
return await process_interaction(
|
|
114
116
|
run,
|
|
115
117
|
configuration,
|
package/agent-view.js
CHANGED
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
input,
|
|
19
19
|
h4,
|
|
20
20
|
h3,
|
|
21
|
+
h2,
|
|
21
22
|
style,
|
|
22
23
|
h5,
|
|
23
24
|
button,
|
|
@@ -821,6 +822,21 @@ const run = async (
|
|
|
821
822
|
.modern-chat-layout .chat-user .chat-bubble table th {
|
|
822
823
|
background: rgba(255,255,255,0.1);
|
|
823
824
|
}
|
|
825
|
+
/* Skill attribution badge */
|
|
826
|
+
.modern-chat-layout .chat-bubble .badge.bg-info {
|
|
827
|
+
display: inline-block;
|
|
828
|
+
margin-bottom: 6px;
|
|
829
|
+
font-size: 0.7rem;
|
|
830
|
+
font-weight: 600;
|
|
831
|
+
letter-spacing: 0.3px;
|
|
832
|
+
text-transform: uppercase;
|
|
833
|
+
opacity: 0.85;
|
|
834
|
+
}
|
|
835
|
+
.modern-chat-layout .chat-bubble .card.bg-secondary-subtle {
|
|
836
|
+
border: none;
|
|
837
|
+
background-color: rgba(0,0,0,0.03) !important;
|
|
838
|
+
margin-bottom: 0.5rem;
|
|
839
|
+
}
|
|
824
840
|
/* Input area for modern chat */
|
|
825
841
|
.modern-chat-layout .copilot-entry {
|
|
826
842
|
border-top: 1px solid var(--tblr-border-color, var(--bs-border-color, #dee2e6));
|
|
@@ -1273,12 +1289,80 @@ const debug_info = async (table_id, viewname, config, body, { req, res }) => {
|
|
|
1273
1289
|
);
|
|
1274
1290
|
sysPrompt = complArgs.systemPrompt;
|
|
1275
1291
|
}
|
|
1292
|
+
const apiJson = JSON.stringify(run.context.api_interactions, null, 2);
|
|
1276
1293
|
const debug_html = div(
|
|
1277
|
-
|
|
1294
|
+
{ class: "accordion", id: "debugAccordion" },
|
|
1278
1295
|
div(
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1296
|
+
{ class: "accordion-item" },
|
|
1297
|
+
h2(
|
|
1298
|
+
{ class: "accordion-header", id: "debugHeadPrompt" },
|
|
1299
|
+
button(
|
|
1300
|
+
{
|
|
1301
|
+
class: "accordion-button collapsed",
|
|
1302
|
+
type: "button",
|
|
1303
|
+
"data-bs-toggle": "collapse",
|
|
1304
|
+
"data-bs-target": "#debugCollapsePrompt",
|
|
1305
|
+
"aria-expanded": "false",
|
|
1306
|
+
"aria-controls": "debugCollapsePrompt",
|
|
1307
|
+
},
|
|
1308
|
+
"System prompt",
|
|
1309
|
+
),
|
|
1310
|
+
),
|
|
1311
|
+
div(
|
|
1312
|
+
{
|
|
1313
|
+
id: "debugCollapsePrompt",
|
|
1314
|
+
class: "accordion-collapse collapse",
|
|
1315
|
+
"aria-labelledby": "debugHeadPrompt",
|
|
1316
|
+
"data-bs-parent": "#debugAccordion",
|
|
1317
|
+
},
|
|
1318
|
+
div(
|
|
1319
|
+
{ class: "accordion-body" },
|
|
1320
|
+
pre({ style: "white-space:pre-wrap" }, text(escapeHtml(sysPrompt))),
|
|
1321
|
+
),
|
|
1322
|
+
),
|
|
1323
|
+
),
|
|
1324
|
+
div(
|
|
1325
|
+
{ class: "accordion-item" },
|
|
1326
|
+
h2(
|
|
1327
|
+
{ class: "accordion-header", id: "debugHeadAPI" },
|
|
1328
|
+
button(
|
|
1329
|
+
{
|
|
1330
|
+
class: "accordion-button",
|
|
1331
|
+
type: "button",
|
|
1332
|
+
"data-bs-toggle": "collapse",
|
|
1333
|
+
"data-bs-target": "#debugCollapseAPI",
|
|
1334
|
+
"aria-expanded": "true",
|
|
1335
|
+
"aria-controls": "debugCollapseAPI",
|
|
1336
|
+
},
|
|
1337
|
+
"API interactions",
|
|
1338
|
+
),
|
|
1339
|
+
),
|
|
1340
|
+
div(
|
|
1341
|
+
{
|
|
1342
|
+
id: "debugCollapseAPI",
|
|
1343
|
+
class: "accordion-collapse collapse show",
|
|
1344
|
+
"aria-labelledby": "debugHeadAPI",
|
|
1345
|
+
"data-bs-parent": "#debugAccordion",
|
|
1346
|
+
},
|
|
1347
|
+
div(
|
|
1348
|
+
{ class: "accordion-body" },
|
|
1349
|
+
button(
|
|
1350
|
+
{
|
|
1351
|
+
class: "btn btn-sm btn-outline-secondary mb-2",
|
|
1352
|
+
onclick: `
|
|
1353
|
+
var t=document.getElementById('debugApiPre').textContent;
|
|
1354
|
+
navigator.clipboard.writeText(t).then(function(){
|
|
1355
|
+
var b=event.target;b.textContent='Copied!';
|
|
1356
|
+
setTimeout(function(){b.textContent='Copy to clipboard'},1500)
|
|
1357
|
+
})`,
|
|
1358
|
+
},
|
|
1359
|
+
"Copy to clipboard",
|
|
1360
|
+
),
|
|
1361
|
+
pre(
|
|
1362
|
+
{ id: "debugApiPre", style: "white-space:pre-wrap" },
|
|
1363
|
+
text(escapeHtml(apiJson)),
|
|
1364
|
+
),
|
|
1365
|
+
),
|
|
1282
1366
|
),
|
|
1283
1367
|
),
|
|
1284
1368
|
);
|
package/common.js
CHANGED
|
@@ -515,7 +515,7 @@ const process_interaction = async (
|
|
|
515
515
|
});
|
|
516
516
|
if (generateUsed)
|
|
517
517
|
await addToContext(run, {
|
|
518
|
-
interactions:
|
|
518
|
+
interactions: run.context.interactions,
|
|
519
519
|
});
|
|
520
520
|
if (postprocres.stop) stop = true;
|
|
521
521
|
if (postprocres.add_system_prompt)
|
|
@@ -546,35 +546,35 @@ const process_interaction = async (
|
|
|
546
546
|
layout,
|
|
547
547
|
),
|
|
548
548
|
);
|
|
549
|
-
|
|
550
|
-
// run.context.interactions.forEach((ic) => {});
|
|
549
|
+
|
|
551
550
|
const result = add_resp;
|
|
552
551
|
await sysState.functions.llm_add_message.run(
|
|
553
552
|
"assistant",
|
|
553
|
+
|
|
554
554
|
!result || typeof result === "string"
|
|
555
|
-
?
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
559
|
-
: {
|
|
560
|
-
type: "json",
|
|
561
|
-
value: JSON.parse(JSON.stringify(result)),
|
|
562
|
-
},
|
|
555
|
+
? result || "Action run"
|
|
556
|
+
: JSON.stringify(result),
|
|
557
|
+
|
|
563
558
|
{
|
|
564
559
|
chat: run.context.interactions,
|
|
565
560
|
},
|
|
566
561
|
);
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
562
|
+
await addToContext(run, {
|
|
563
|
+
interactions: run.context.interactions,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
if (!postprocres.stop) {
|
|
567
|
+
await sysState.functions.llm_add_message.run(
|
|
568
|
+
"user",
|
|
569
|
+
postprocres.follow_up_prompt || "Continue with the query",
|
|
570
|
+
{
|
|
571
|
+
chat: run.context.interactions,
|
|
572
|
+
},
|
|
573
|
+
);
|
|
574
|
+
await addToContext(run, {
|
|
575
|
+
interactions: run.context.interactions,
|
|
576
|
+
});
|
|
577
|
+
myHasResult = true;
|
|
578
578
|
}
|
|
579
579
|
if (postprocres.add_user_action && viewname) {
|
|
580
580
|
const user_actions = Array.isArray()
|
package/package.json
CHANGED
|
@@ -18,6 +18,24 @@ const { validID } = require("@saltcorn/markup/layout_utils");
|
|
|
18
18
|
const vm = require("vm");
|
|
19
19
|
const { replaceUserContinue } = require("../common");
|
|
20
20
|
|
|
21
|
+
function extractCode(str) {
|
|
22
|
+
// Try ```javascript fence
|
|
23
|
+
if (str.includes("```javascript")) {
|
|
24
|
+
return str.split("```javascript")[1].split("```")[0];
|
|
25
|
+
}
|
|
26
|
+
// Try ```js fence
|
|
27
|
+
if (str.includes("```js\n") || str.includes("```js\r")) {
|
|
28
|
+
return str.split(/```js\s/)[1].split("```")[0];
|
|
29
|
+
}
|
|
30
|
+
// Try generic ``` fence (code is between first and second ```)
|
|
31
|
+
const fenceMatch = str.match(/```\s*\n([\s\S]*?)```/);
|
|
32
|
+
if (fenceMatch) {
|
|
33
|
+
return fenceMatch[1];
|
|
34
|
+
}
|
|
35
|
+
// No fences found - return raw string
|
|
36
|
+
return str;
|
|
37
|
+
}
|
|
38
|
+
|
|
21
39
|
//const { fieldProperties } = require("./helpers");
|
|
22
40
|
|
|
23
41
|
class GenerateAndRunJsCodeSkill {
|
|
@@ -105,6 +123,14 @@ class GenerateAndRunJsCodeSkill {
|
|
|
105
123
|
sublabel: "Allow calls to functions from codepages and modules",
|
|
106
124
|
type: "Bool",
|
|
107
125
|
},
|
|
126
|
+
{
|
|
127
|
+
name: "follow_up_prompt",
|
|
128
|
+
label: "Follow-up prompt",
|
|
129
|
+
sublabel:
|
|
130
|
+
"If set, the agent will continue processing after code execution with this prompt. Leave empty to stop after code result.",
|
|
131
|
+
type: "String",
|
|
132
|
+
fieldview: "textarea",
|
|
133
|
+
},
|
|
108
134
|
...(Table.subClass
|
|
109
135
|
? [
|
|
110
136
|
{
|
|
@@ -171,26 +197,34 @@ return { x, y }
|
|
|
171
197
|
|
|
172
198
|
${extra || ""}
|
|
173
199
|
|
|
200
|
+
CRITICAL: Your response must contain ONLY a single JavaScript code block wrapped in \`\`\`javascript ... \`\`\` fences. Do not include any text, explanation, or commentary before or after the code block.
|
|
201
|
+
|
|
174
202
|
Now generate the JavaScript code required by the user.`,
|
|
175
203
|
);
|
|
176
204
|
getState().log(
|
|
177
205
|
6,
|
|
178
206
|
"Generated code:\n--BEGIN CODE--\n" + str + "\n--END CODE--\n",
|
|
179
207
|
);
|
|
180
|
-
const js_code = str
|
|
181
|
-
? str.split("```javascript")[1].split("```")[0]
|
|
182
|
-
: str;
|
|
208
|
+
const js_code = extractCode(str);
|
|
183
209
|
return js_code;
|
|
184
210
|
};
|
|
185
211
|
const js_code = await gen_the_code();
|
|
186
212
|
emit_update("Running code");
|
|
213
|
+
const ensureResult = (res) => {
|
|
214
|
+
if (res && typeof res === "object") return JSON.stringify(res);
|
|
215
|
+
if (res !== undefined && res !== null && res !== "") return res;
|
|
216
|
+
return "Code executed successfully but returned no output.";
|
|
217
|
+
};
|
|
187
218
|
try {
|
|
188
219
|
const res = await this.runCode(js_code, { user: req.user });
|
|
189
|
-
//console.log("code response", res);
|
|
190
220
|
getState().log(6, "Code answer: " + JSON.stringify(res));
|
|
221
|
+
const effectiveRes = ensureResult(res);
|
|
191
222
|
return {
|
|
192
|
-
stop: typeof res === "string",
|
|
193
|
-
add_response:
|
|
223
|
+
stop: typeof res === "string" && !this.follow_up_prompt,
|
|
224
|
+
add_response: effectiveRes,
|
|
225
|
+
...(this.follow_up_prompt
|
|
226
|
+
? { follow_up_prompt: this.follow_up_prompt }
|
|
227
|
+
: {}),
|
|
194
228
|
};
|
|
195
229
|
} catch (err) {
|
|
196
230
|
console.error(err);
|
|
@@ -208,13 +242,27 @@ ${err.message}
|
|
|
208
242
|
|
|
209
243
|
Correct this error.
|
|
210
244
|
`);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
245
|
+
try {
|
|
246
|
+
const res = await this.runCode(retry_js_code, {
|
|
247
|
+
user: req.user,
|
|
248
|
+
});
|
|
249
|
+
getState().log(6, "Code retry answer: " + JSON.stringify(res));
|
|
250
|
+
const effectiveRes = ensureResult(res);
|
|
251
|
+
return {
|
|
252
|
+
stop: typeof res === "string" && !this.follow_up_prompt,
|
|
253
|
+
add_response: effectiveRes,
|
|
254
|
+
...(this.follow_up_prompt
|
|
255
|
+
? { follow_up_prompt: this.follow_up_prompt }
|
|
256
|
+
: {}),
|
|
257
|
+
};
|
|
258
|
+
} catch (retryErr) {
|
|
259
|
+
console.error(retryErr);
|
|
260
|
+
return {
|
|
261
|
+
add_response:
|
|
262
|
+
"Error: code generation failed after retry: " +
|
|
263
|
+
retryErr.message,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
218
266
|
}
|
|
219
267
|
},
|
|
220
268
|
function: {
|
package/skills/Subagent.js
CHANGED
|
@@ -52,6 +52,12 @@ class SubagentToSkill {
|
|
|
52
52
|
sublabel: `Optional. The prompt initialising the subagent. Example: "Continue answering my query using the tool now at you disposal"`,
|
|
53
53
|
type: "String",
|
|
54
54
|
},
|
|
55
|
+
{
|
|
56
|
+
name: "handoff_prompt",
|
|
57
|
+
label: "Handoff prompt",
|
|
58
|
+
sublabel: `Optional. A prompt to process the results of the subagent. Example: "Analyze this response in relation to my query"`,
|
|
59
|
+
type: "String",
|
|
60
|
+
},
|
|
55
61
|
];
|
|
56
62
|
}
|
|
57
63
|
|
|
@@ -91,21 +97,20 @@ class SubagentToSkill {
|
|
|
91
97
|
"Your instructions and tools have changed. Continue answering my query using the instructions and tools at you disposal, if any",
|
|
92
98
|
},
|
|
93
99
|
user: req.user,
|
|
94
|
-
|
|
100
|
+
run,
|
|
95
101
|
is_sub_agent: true,
|
|
96
102
|
agent_view_config,
|
|
97
103
|
dyn_updates,
|
|
98
104
|
req,
|
|
99
105
|
});
|
|
100
|
-
getState().log(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
subres?.json?.raw_responses || "No response",
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
if (subres.json.raw_responses)
|
|
107
|
-
return { add_responses: subres.json.raw_responses };
|
|
106
|
+
getState().log(6, "Subagent response", JSON.stringify(subres, null, 2));
|
|
107
|
+
//if (subres.json.raw_responses)
|
|
108
|
+
// return { add_responses: subres.json.raw_responses };
|
|
108
109
|
return {
|
|
110
|
+
...(this.handoff_prompt
|
|
111
|
+
? { follow_up_prompt: this.handoff_prompt }
|
|
112
|
+
: { stop: true }),
|
|
113
|
+
|
|
109
114
|
//stop: true,
|
|
110
115
|
//add_response: result,
|
|
111
116
|
};
|
package/tests/action.test.js
CHANGED
|
@@ -38,7 +38,7 @@ beforeAll(async () => {
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
await getState().refresh_triggers(false);
|
|
41
|
-
|
|
41
|
+
await getState().setConfig("log_level", 6);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
jest.setTimeout(40000);
|
|
@@ -46,6 +46,13 @@ jest.setTimeout(40000);
|
|
|
46
46
|
const user = { id: 1, role_id: 1 };
|
|
47
47
|
const action = require("../action");
|
|
48
48
|
|
|
49
|
+
const getLastInteraction = async ({ run_id }) => {
|
|
50
|
+
const run = await WorkflowRuns.findOne({ id: run_id });
|
|
51
|
+
return JSON.stringify(
|
|
52
|
+
run.context.interactions[run.context.interactions.length - 1],
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
49
56
|
for (const nameconfig of require("./configs")) {
|
|
50
57
|
const { name, ...config } = nameconfig;
|
|
51
58
|
describe("agent action with " + name, () => {
|
|
@@ -114,7 +121,10 @@ for (const nameconfig of require("./configs")) {
|
|
|
114
121
|
user,
|
|
115
122
|
req: { user },
|
|
116
123
|
});
|
|
117
|
-
|
|
124
|
+
|
|
125
|
+
const lastInteraction = await getLastInteraction(result.json);
|
|
126
|
+
|
|
127
|
+
expect(result.json.response || lastInteraction).toContain("987");
|
|
118
128
|
});
|
|
119
129
|
it("run multiple subagents concurrenty", async () => {
|
|
120
130
|
const configuration = { ...require("./agentcfg").agent1 };
|
|
@@ -134,7 +144,8 @@ for (const nameconfig of require("./configs")) {
|
|
|
134
144
|
user,
|
|
135
145
|
req: { user },
|
|
136
146
|
});
|
|
137
|
-
|
|
147
|
+
const lastInteraction = await getLastInteraction(result.json);
|
|
148
|
+
expect(lastInteraction).toContain("987");
|
|
138
149
|
});
|
|
139
150
|
});
|
|
140
151
|
|