@saltcorn/copilot 0.8.0 → 0.8.2
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/actions/generate-tables.js +14 -0
- package/actions/generate-trigger.js +61 -0
- package/actions/generate-workflow.js +6 -1
- package/agent-skills/pagegen.js +39 -12
- package/agent-skills/registry-editor.js +15 -0
- package/agent-skills/triggergen.js +259 -0
- package/agent-skills/viewgen.js +248 -22
- package/app-constructor/prompts.js +65 -3
- package/app-constructor/requirements.js +98 -36
- package/app-constructor/run_task.js +116 -68
- package/app-constructor/schema.js +227 -49
- package/app-constructor/tasks.js +622 -83
- package/app-constructor/tools.js +5 -4
- package/app-constructor/view.js +7 -0
- package/builder-gen.js +135 -35
- package/copilot-as-agent.js +2 -0
- package/index.js +1 -2
- package/js-code-gen.js +1 -0
- package/package.json +1 -1
- package/relation-paths.js +269 -0
- package/standard-prompt.js +1 -0
- package/user-copilot.js +2 -0
- package/workflow-gen.js +1 -0
- package/agent-skills/app-constructor-context.js +0 -25
- package/chat-copilot.js +0 -769
|
@@ -163,6 +163,13 @@ class GenerateTables {
|
|
|
163
163
|
},
|
|
164
164
|
},
|
|
165
165
|
},
|
|
166
|
+
reused_table_names: {
|
|
167
|
+
type: "array",
|
|
168
|
+
items: { type: "string" },
|
|
169
|
+
description:
|
|
170
|
+
"Names of existing tables that are already complete and require no changes. " +
|
|
171
|
+
"List them here so the caller knows which tables were reused as-is. Do NOT repeat their field definitions in the tables array.",
|
|
172
|
+
},
|
|
166
173
|
},
|
|
167
174
|
};
|
|
168
175
|
}
|
|
@@ -202,6 +209,12 @@ class GenerateTables {
|
|
|
202
209
|
want to add or update. The system will automatically add new fields and update the settings of
|
|
203
210
|
existing fields — it will not recreate or drop the table.
|
|
204
211
|
|
|
212
|
+
## Reused tables
|
|
213
|
+
|
|
214
|
+
If an existing table is used by the application as-is (no new fields needed), do NOT repeat it
|
|
215
|
+
in the tables array. Instead, add its name to the reused_table_names array. This tells the
|
|
216
|
+
system to include it in the schema diagram without attempting to modify it.
|
|
217
|
+
|
|
205
218
|
If a user requests creating a table with certain fields and the table already exists, automatically add any missing fields to that table. Do not ask the user for confirmation or prompt them again—just proceed with the table update.
|
|
206
219
|
|
|
207
220
|
If a table has a ForeignKey field that references another table which does not yet exist in the
|
|
@@ -451,6 +464,7 @@ const buildMermaidMarkup = (tables) => {
|
|
|
451
464
|
};
|
|
452
465
|
|
|
453
466
|
module.exports = GenerateTables;
|
|
467
|
+
module.exports.buildMermaidMarkup = buildMermaidMarkup;
|
|
454
468
|
|
|
455
469
|
/* todo
|
|
456
470
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
2
|
+
const Table = require("@saltcorn/data/models/table");
|
|
3
|
+
const { a, div, pre } = require("@saltcorn/markup/tags");
|
|
4
|
+
|
|
5
|
+
class GenerateTrigger {
|
|
6
|
+
static title = "Generate Trigger";
|
|
7
|
+
static function_name = "generate_trigger";
|
|
8
|
+
static description =
|
|
9
|
+
"Generate a Saltcorn trigger with any available action type";
|
|
10
|
+
|
|
11
|
+
static render_html({
|
|
12
|
+
action_name,
|
|
13
|
+
action_type,
|
|
14
|
+
when_trigger,
|
|
15
|
+
trigger_table,
|
|
16
|
+
action_config,
|
|
17
|
+
}) {
|
|
18
|
+
const summary =
|
|
19
|
+
`<strong>${action_name}</strong> — ${action_type}` +
|
|
20
|
+
(when_trigger ? `: ${when_trigger}` : "") +
|
|
21
|
+
(trigger_table ? ` on ${trigger_table}` : "");
|
|
22
|
+
const configSection =
|
|
23
|
+
action_config && Object.keys(action_config).length
|
|
24
|
+
? pre({ class: "mt-2" }, JSON.stringify(action_config, null, 2))
|
|
25
|
+
: "";
|
|
26
|
+
return div({ class: "mb-3" }, summary) + configSection;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static async execute(
|
|
30
|
+
{ action_name, action_type, when_trigger, trigger_table, action_config },
|
|
31
|
+
req,
|
|
32
|
+
) {
|
|
33
|
+
let table_id;
|
|
34
|
+
if (trigger_table) {
|
|
35
|
+
const table = Table.findOne({ name: trigger_table });
|
|
36
|
+
if (!table) return { postExec: `Table not found: ${trigger_table}` };
|
|
37
|
+
table_id = table.id;
|
|
38
|
+
}
|
|
39
|
+
const trigger = await Trigger.create({
|
|
40
|
+
name: action_name,
|
|
41
|
+
when_trigger: when_trigger || "Never",
|
|
42
|
+
table_id,
|
|
43
|
+
action: action_type,
|
|
44
|
+
configuration: action_config || {},
|
|
45
|
+
});
|
|
46
|
+
Trigger.emitEvent("AppChange", `Trigger ${trigger.name}`, req?.user, {
|
|
47
|
+
entity_type: "Trigger",
|
|
48
|
+
entity_name: trigger.name,
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
postExec:
|
|
52
|
+
"Trigger created. " +
|
|
53
|
+
a(
|
|
54
|
+
{ target: "_blank", href: `/actions/configure/${trigger.id}` },
|
|
55
|
+
"Configure trigger.",
|
|
56
|
+
),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = GenerateTrigger;
|
|
@@ -215,7 +215,8 @@ class GenerateWorkflow {
|
|
|
215
215
|
workflow_steps: await steps(),
|
|
216
216
|
workflow_name: {
|
|
217
217
|
description:
|
|
218
|
-
"The name of the workflow. Can include spaces and mixed case, should be 1-5 words."
|
|
218
|
+
"The name of the workflow. Can include spaces and mixed case, should be 1-5 words. " +
|
|
219
|
+
"Must be unique across all workflows. When creating variants for different events on the same table (e.g. insert, update, delete), include the event in each name — e.g. 'Recalc trip packing insert', 'Recalc trip packing update'.",
|
|
219
220
|
type: "string",
|
|
220
221
|
},
|
|
221
222
|
when_trigger: {
|
|
@@ -254,6 +255,10 @@ class GenerateWorkflow {
|
|
|
254
255
|
return `Use the generate_workflow tool to construct computational workflows according to specifications. You must create
|
|
255
256
|
the workflow by calling the generate_workflow tool, with the step required to implement the specification.
|
|
256
257
|
|
|
258
|
+
**Trigger vs workflow:** If the task can be completed in a single step (e.g. updating one field with modify_row, sending a single notification), use the generate_trigger tool instead — a workflow is only appropriate when multiple steps, branching, or looping are required. If the task requires several independent single-step actions (e.g. "mark complete" and "mark incomplete"), create a separate trigger for each — do NOT bundle them into one workflow.
|
|
259
|
+
|
|
260
|
+
**Naming:** Workflow names must be unique. When creating variants for different events on the same table (e.g. insert, update, delete), include the event in each name — e.g. "Recalc trip packing insert", "Recalc trip packing update", "Recalc trip packing delete".
|
|
261
|
+
|
|
257
262
|
${contextBlocks}
|
|
258
263
|
|
|
259
264
|
The steps are specified as JSON objects. Each step has a name, specified in the step_name key in the JSON object.
|
package/agent-skills/pagegen.js
CHANGED
|
@@ -52,20 +52,27 @@ class GeneratePageSkill {
|
|
|
52
52
|
}
|
|
53
53
|
get userActions() {
|
|
54
54
|
return {
|
|
55
|
-
async build_copilot_page_gen({
|
|
55
|
+
async build_copilot_page_gen({
|
|
56
|
+
user,
|
|
57
|
+
name,
|
|
58
|
+
title,
|
|
59
|
+
description,
|
|
60
|
+
html,
|
|
61
|
+
min_role = 100,
|
|
62
|
+
}) {
|
|
56
63
|
const file = await File.from_contents(
|
|
57
64
|
`${name}.html`,
|
|
58
65
|
"text/html",
|
|
59
66
|
html,
|
|
60
|
-
user
|
|
61
|
-
|
|
67
|
+
user?.id,
|
|
68
|
+
min_role
|
|
62
69
|
);
|
|
63
70
|
|
|
64
71
|
await Page.create({
|
|
65
72
|
name,
|
|
66
73
|
title,
|
|
67
74
|
description,
|
|
68
|
-
min_role
|
|
75
|
+
min_role,
|
|
69
76
|
layout: { html_file: file.path_to_serve },
|
|
70
77
|
});
|
|
71
78
|
setTimeout(() => getState().refresh_pages(), 200);
|
|
@@ -96,7 +103,7 @@ class GeneratePageSkill {
|
|
|
96
103
|
);
|
|
97
104
|
} else return "Metadata recieved";
|
|
98
105
|
},
|
|
99
|
-
postProcess: async ({ tool_call, generate }) => {
|
|
106
|
+
postProcess: async ({ tool_call, generate, req }) => {
|
|
100
107
|
const str = await generate(
|
|
101
108
|
`Now generate the contents of the ${tool_call.input.name} HTML page. If I asked you to embed a view,
|
|
102
109
|
use the <embed-view> self-closing tag to do so, setting the view name in the viewname attribute. For example,
|
|
@@ -114,12 +121,26 @@ class GeneratePageSkill {
|
|
|
114
121
|
|
|
115
122
|
<script src="/static_assets/js/saltcorn-common.js"></script>
|
|
116
123
|
<script src="/static_assets/js/saltcorn.js">
|
|
117
|
-
|
|
124
|
+
`
|
|
118
125
|
);
|
|
119
126
|
const html = str.includes("```html")
|
|
120
127
|
? str.split("```html")[1].split("```")[0]
|
|
121
128
|
: str;
|
|
122
129
|
|
|
130
|
+
if (this.yoloMode) {
|
|
131
|
+
await this.userActions.build_copilot_page_gen({
|
|
132
|
+
user: req?.user,
|
|
133
|
+
name: tool_call.input.name,
|
|
134
|
+
title: tool_call.input.title,
|
|
135
|
+
description: tool_call.input.description,
|
|
136
|
+
min_role: tool_call.input.min_role ?? 100,
|
|
137
|
+
html,
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
stop: true,
|
|
141
|
+
add_response: `Page ${tool_call.input.name} created.`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
123
144
|
return {
|
|
124
145
|
stop: true,
|
|
125
146
|
add_response: iframe({
|
|
@@ -127,15 +148,15 @@ class GeneratePageSkill {
|
|
|
127
148
|
width: 500,
|
|
128
149
|
height: 800,
|
|
129
150
|
}),
|
|
130
|
-
add_system_prompt: `If the user asks you to regenerate the page,
|
|
131
|
-
you must run the generate_page tool again. After running this tool
|
|
132
|
-
you will be prompted to generate the html again. You should repeat
|
|
133
|
-
the html from the previous answer except for the changes the user
|
|
151
|
+
add_system_prompt: `If the user asks you to regenerate the page,
|
|
152
|
+
you must run the generate_page tool again. After running this tool
|
|
153
|
+
you will be prompted to generate the html again. You should repeat
|
|
154
|
+
the html from the previous answer except for the changes the user
|
|
134
155
|
is requesting.`,
|
|
135
156
|
add_user_action: {
|
|
136
157
|
name: "build_copilot_page_gen",
|
|
137
158
|
type: "button",
|
|
138
|
-
label: "Save page "+tool_call.input.name,
|
|
159
|
+
label: "Save page " + tool_call.input.name,
|
|
139
160
|
input: { html },
|
|
140
161
|
},
|
|
141
162
|
};
|
|
@@ -150,7 +171,7 @@ class GeneratePageSkill {
|
|
|
150
171
|
response.includes("Unable to provide HTML for this page")
|
|
151
172
|
)
|
|
152
173
|
return response;
|
|
153
|
-
|
|
174
|
+
if (
|
|
154
175
|
typeof response === "string" &&
|
|
155
176
|
response.includes("The HTML code for the ")
|
|
156
177
|
)
|
|
@@ -181,6 +202,12 @@ class GeneratePageSkill {
|
|
|
181
202
|
"A longer description that is not visible but appears in the page header and is indexed by search engines",
|
|
182
203
|
type: "string",
|
|
183
204
|
},
|
|
205
|
+
min_role: {
|
|
206
|
+
description:
|
|
207
|
+
"Minimum role required to access this page. Use 1 for admin-only, 40 for staff and above, 80 for logged-in users and above, 100 for public. Set this based on the intended audience described in the task.",
|
|
208
|
+
type: "integer",
|
|
209
|
+
enum: [1, 40, 80, 100],
|
|
210
|
+
},
|
|
184
211
|
page_type: {
|
|
185
212
|
description:
|
|
186
213
|
"The type of page to generate: a Marketing page if for promotional purposes, such as a landing page or a brouchure, with an appealing design. An Application page is simpler and an integrated part of the application",
|
|
@@ -6,6 +6,7 @@ const Field = require("@saltcorn/data/models/field");
|
|
|
6
6
|
const User = require("@saltcorn/data/models/user");
|
|
7
7
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
8
8
|
const Role = require("@saltcorn/data/models/role");
|
|
9
|
+
const File = require("@saltcorn/data/models/file");
|
|
9
10
|
const WorkflowStep = require("@saltcorn/data/models/workflow_step");
|
|
10
11
|
const { getState } = require("@saltcorn/data/db/state");
|
|
11
12
|
|
|
@@ -167,6 +168,7 @@ with both the entity type and name, and the new JSON definition as a string as a
|
|
|
167
168
|
"trigger",
|
|
168
169
|
"plugin",
|
|
169
170
|
"system-configuration-value",
|
|
171
|
+
"file",
|
|
170
172
|
"type",
|
|
171
173
|
],
|
|
172
174
|
},
|
|
@@ -317,6 +319,11 @@ with both the entity type and name, and the new JSON definition as a string as a
|
|
|
317
319
|
}
|
|
318
320
|
return v;
|
|
319
321
|
}
|
|
322
|
+
case "file": {
|
|
323
|
+
const file = await File.findOne({ filename: input.entity_name });
|
|
324
|
+
if (!file) return `file not found`;
|
|
325
|
+
return { filename: file.filename, min_role_read: file.min_role_read };
|
|
326
|
+
}
|
|
320
327
|
case "plugin": {
|
|
321
328
|
const plugin = await Plugin.findOne({ name: input.entity_name });
|
|
322
329
|
if (!plugin) return `plugin not found`;
|
|
@@ -473,6 +480,7 @@ with both the entity type and name, and the new JSON definition as a string as a
|
|
|
473
480
|
"system-configuration-value",
|
|
474
481
|
"module-configuration",
|
|
475
482
|
"role",
|
|
483
|
+
"file",
|
|
476
484
|
],
|
|
477
485
|
},
|
|
478
486
|
entity_name: {
|
|
@@ -897,6 +905,13 @@ with both the entity type and name, and the new JSON definition as a string as a
|
|
|
897
905
|
await getState().refresh_roles();
|
|
898
906
|
return "Role created";
|
|
899
907
|
}
|
|
908
|
+
|
|
909
|
+
case "file": {
|
|
910
|
+
const file = await File.findOne({ filename: input.entity_name });
|
|
911
|
+
if (!file) return `file not found: ${input.entity_name}`;
|
|
912
|
+
await file.set_role(entityValue.min_role_read);
|
|
913
|
+
return "Done";
|
|
914
|
+
}
|
|
900
915
|
}
|
|
901
916
|
return "Done";
|
|
902
917
|
} catch (e) {
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
2
|
+
const Table = require("@saltcorn/data/models/table");
|
|
3
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
4
|
+
const { getActionConfigFields } = require("@saltcorn/data/plugin-helper");
|
|
5
|
+
const { fieldProperties } = require("../common");
|
|
6
|
+
const GenerateAnyAction = require("../actions/generate-trigger");
|
|
7
|
+
|
|
8
|
+
const flattenOptionGroups = (options = []) =>
|
|
9
|
+
options.flatMap((opt) =>
|
|
10
|
+
opt?.optgroup && Array.isArray(opt.options) ? opt.options : [opt],
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
class AnyActionSkill {
|
|
14
|
+
static skill_name = "Generate trigger";
|
|
15
|
+
|
|
16
|
+
get skill_label() {
|
|
17
|
+
return "Generate trigger";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
constructor(cfg) {
|
|
21
|
+
Object.assign(this, cfg);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async systemPrompt() {
|
|
25
|
+
return (
|
|
26
|
+
`If the user asks to create an action or trigger, use the generate_trigger tool. ` +
|
|
27
|
+
`Pick the most appropriate action_type from the available options. ` +
|
|
28
|
+
`Only set when_trigger and trigger_table if the user has specified them. ` +
|
|
29
|
+
`Trigger names must be unique — when creating variants for different events on the same table, include the event in each name (e.g. "Recalc trip packing insert", "Recalc trip packing update", "Recalc trip packing delete").\n\n` +
|
|
30
|
+
`**Trigger vs workflow:** Use a trigger (this tool) when the action is a single step — ` +
|
|
31
|
+
`for example, setting a field value with modify_row, sending a notification, or calling an API. ` +
|
|
32
|
+
`Only use a workflow when the logic requires multiple steps, branching, or looping. ` +
|
|
33
|
+
`If the task requires several independent single-step actions (e.g. "mark complete" and "mark incomplete"), call this tool once per action — do NOT bundle them into one workflow.\n\n` +
|
|
34
|
+
`**Navigation is a view concern:** If a task description says "return the user to X" or "navigate back", ` +
|
|
35
|
+
`do NOT add a navigation step inside the trigger. Triggers only handle data operations. ` +
|
|
36
|
+
`Navigation (GoBack) is configured on the button in the list view, not inside the trigger itself.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get userActions() {
|
|
41
|
+
return {
|
|
42
|
+
async build_copilot_any_action(input) {
|
|
43
|
+
const {
|
|
44
|
+
name,
|
|
45
|
+
action_type,
|
|
46
|
+
when_trigger,
|
|
47
|
+
trigger_table,
|
|
48
|
+
action_config,
|
|
49
|
+
user,
|
|
50
|
+
} = input;
|
|
51
|
+
if (!name || !action_type) {
|
|
52
|
+
return { notify: "Action name and type are required." };
|
|
53
|
+
}
|
|
54
|
+
const result = await GenerateAnyAction.execute(
|
|
55
|
+
{
|
|
56
|
+
action_name: name,
|
|
57
|
+
action_type,
|
|
58
|
+
when_trigger,
|
|
59
|
+
trigger_table,
|
|
60
|
+
action_config,
|
|
61
|
+
},
|
|
62
|
+
{ user },
|
|
63
|
+
);
|
|
64
|
+
return { notify: result?.postExec || `Action saved: ${name}` };
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
provideTools = () => {
|
|
70
|
+
const state = getState();
|
|
71
|
+
const allActionOptions = (() => {
|
|
72
|
+
try {
|
|
73
|
+
return (
|
|
74
|
+
Trigger.action_options({ notRequireRow: false, workflow: false }) ||
|
|
75
|
+
[]
|
|
76
|
+
);
|
|
77
|
+
} catch (_) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
const stateActions = state?.actions || {};
|
|
82
|
+
const stateActionNames = Object.keys(stateActions);
|
|
83
|
+
const catalogNames = flattenOptionGroups(allActionOptions);
|
|
84
|
+
const actionEnum = Array.from(
|
|
85
|
+
new Set([...catalogNames, ...stateActionNames]),
|
|
86
|
+
).sort();
|
|
87
|
+
|
|
88
|
+
const tables = (state.tables || []).map((t) => t.name);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
type: "function",
|
|
92
|
+
function: {
|
|
93
|
+
name: GenerateAnyAction.function_name,
|
|
94
|
+
description: GenerateAnyAction.description,
|
|
95
|
+
parameters: {
|
|
96
|
+
type: "object",
|
|
97
|
+
required: ["name", "action_type"],
|
|
98
|
+
properties: {
|
|
99
|
+
name: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description:
|
|
102
|
+
"A human-readable name for the trigger/action (1–5 words). " +
|
|
103
|
+
"Must be unique across all triggers. When creating multiple triggers for different events on the same table (e.g. insert, update, delete), include the event in the name — e.g. 'Recalc trip packing insert', 'Recalc trip packing update'.",
|
|
104
|
+
},
|
|
105
|
+
action_type: {
|
|
106
|
+
type: "string",
|
|
107
|
+
enum: actionEnum.length ? actionEnum : undefined,
|
|
108
|
+
description: "The action to run when the trigger fires.",
|
|
109
|
+
},
|
|
110
|
+
when_trigger: {
|
|
111
|
+
type: "string",
|
|
112
|
+
enum: Trigger.when_options,
|
|
113
|
+
description:
|
|
114
|
+
"When to fire this trigger. Only set if the user has specified. Leave unset for manual/API-call triggers.",
|
|
115
|
+
},
|
|
116
|
+
trigger_table: {
|
|
117
|
+
type: "string",
|
|
118
|
+
enum: tables,
|
|
119
|
+
description:
|
|
120
|
+
"Table for row-level triggers (Insert/Update/Delete/Validate). Only set when when_trigger requires a table.",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
process: async (input) => {
|
|
126
|
+
const { name, action_type, when_trigger, trigger_table } = input || {};
|
|
127
|
+
return [
|
|
128
|
+
`Generating ${action_type} action: ${name}.`,
|
|
129
|
+
when_trigger ? `Trigger: ${when_trigger}` : null,
|
|
130
|
+
trigger_table ? `Table: ${trigger_table}` : null,
|
|
131
|
+
]
|
|
132
|
+
.filter(Boolean)
|
|
133
|
+
.join("\n");
|
|
134
|
+
},
|
|
135
|
+
postProcess: async ({ tool_call, generate }) => {
|
|
136
|
+
const { name, action_type, when_trigger, trigger_table } =
|
|
137
|
+
tool_call.input || {};
|
|
138
|
+
if (!name || !action_type) {
|
|
139
|
+
return {
|
|
140
|
+
stop: true,
|
|
141
|
+
add_response: "Action name and type are required.",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const stateAction = getState()?.actions?.[action_type];
|
|
146
|
+
let action_config = {};
|
|
147
|
+
|
|
148
|
+
if (stateAction) {
|
|
149
|
+
const table = trigger_table
|
|
150
|
+
? Table.findOne({ name: trigger_table })
|
|
151
|
+
: null;
|
|
152
|
+
let cfgFields = [];
|
|
153
|
+
try {
|
|
154
|
+
cfgFields = await getActionConfigFields(stateAction, table, {
|
|
155
|
+
copilot: true,
|
|
156
|
+
});
|
|
157
|
+
} catch (_) {}
|
|
158
|
+
|
|
159
|
+
const configurable = cfgFields.filter(
|
|
160
|
+
(f) => f.input_type !== "section_header",
|
|
161
|
+
);
|
|
162
|
+
if (configurable.length > 0) {
|
|
163
|
+
const properties = {};
|
|
164
|
+
for (const f of configurable) {
|
|
165
|
+
properties[f.name] = {
|
|
166
|
+
description: f.sublabel || f.label || f.name,
|
|
167
|
+
...fieldProperties(f),
|
|
168
|
+
};
|
|
169
|
+
if (!properties[f.name].type) properties[f.name].type = "string";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let actionPrompt = "";
|
|
173
|
+
if (stateAction.copilot_generate_trigger_prompt) {
|
|
174
|
+
if (
|
|
175
|
+
typeof stateAction.copilot_generate_trigger_prompt === "string"
|
|
176
|
+
)
|
|
177
|
+
actionPrompt = stateAction.copilot_generate_trigger_prompt;
|
|
178
|
+
else if (
|
|
179
|
+
typeof stateAction.copilot_generate_trigger_prompt ===
|
|
180
|
+
"function"
|
|
181
|
+
)
|
|
182
|
+
actionPrompt =
|
|
183
|
+
await stateAction.copilot_generate_trigger_prompt(
|
|
184
|
+
tool_call.input,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const llm = getState().functions.llm_generate;
|
|
189
|
+
const answer = await llm.run(
|
|
190
|
+
`${actionPrompt ? actionPrompt + "\n\n" : ""}Configure the "${action_type}" action named "${name}". ` +
|
|
191
|
+
`Fill in the configuration by calling the generate_action_config tool.`,
|
|
192
|
+
{
|
|
193
|
+
tools: [
|
|
194
|
+
{
|
|
195
|
+
type: "function",
|
|
196
|
+
function: {
|
|
197
|
+
name: "generate_action_config",
|
|
198
|
+
description: `Provide configuration fields for the ${action_type} action`,
|
|
199
|
+
parameters: { type: "object", properties },
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
tool_choice: {
|
|
204
|
+
type: "function",
|
|
205
|
+
function: { name: "generate_action_config" },
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const tc = answer.getToolCalls()[0];
|
|
211
|
+
if (tc?.input) action_config = tc.input;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (this.yoloMode) {
|
|
216
|
+
const result = await GenerateAnyAction.execute(
|
|
217
|
+
{
|
|
218
|
+
action_name: name,
|
|
219
|
+
action_type,
|
|
220
|
+
when_trigger,
|
|
221
|
+
trigger_table,
|
|
222
|
+
action_config,
|
|
223
|
+
},
|
|
224
|
+
{},
|
|
225
|
+
);
|
|
226
|
+
return {
|
|
227
|
+
stop: true,
|
|
228
|
+
add_response: result?.postExec || `Action ${name} created.`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
stop: true,
|
|
234
|
+
add_response: GenerateAnyAction.render_html({
|
|
235
|
+
action_name: name,
|
|
236
|
+
action_type,
|
|
237
|
+
when_trigger,
|
|
238
|
+
trigger_table,
|
|
239
|
+
action_config,
|
|
240
|
+
}),
|
|
241
|
+
add_user_action: {
|
|
242
|
+
name: "build_copilot_any_action",
|
|
243
|
+
type: "button",
|
|
244
|
+
label: `Save action (${name})`,
|
|
245
|
+
input: {
|
|
246
|
+
name,
|
|
247
|
+
action_type,
|
|
248
|
+
when_trigger,
|
|
249
|
+
trigger_table,
|
|
250
|
+
action_config,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = AnyActionSkill;
|