@saltcorn/copilot 0.8.1 → 0.8.3

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/builder-gen.js CHANGED
@@ -27,13 +27,9 @@ designated as ownership fields (set automatically from the logged-in user). \
27
27
  All other foreign key fields — including parent-context keys like trip_id — MUST appear as \
28
28
  selector inputs; Saltcorn auto-fills them from the URL state when opened in context.`;
29
29
 
30
- const EDIT_FIELDVIEW_SELECTION = `\
31
- For date fields always prefer fieldview "flatpickr" when available — it provides the best user experience \
32
- and works for both regular dates and day-only dates. \
33
- Only use fieldview "edit_day" as a fallback when the field has day_only=true and flatpickr is not installed. \
34
- For String fields that have an options attribute (a comma-separated list of fixed choices), \
35
- use fieldview "select" — this renders a dropdown with those options. \
36
- Do not use "select_by_code" for fields with fixed options.`;
30
+ const {
31
+ fieldview_selection_rules: EDIT_FIELDVIEW_SELECTION,
32
+ } = require("./app-constructor/prompts");
37
33
 
38
34
  const EDIT_LAYOUT_STRUCTURE = `\
39
35
  Every field MUST be preceded by a label. \
@@ -79,7 +75,9 @@ only use "show_with_html" when the task explicitly requires rendering HTML conte
79
75
 
80
76
  const LISTCOLUMNS_VIEW_LINK = `\
81
77
  A view_link that opens the detail of a row MUST point to a Show-type view (viewtemplate "Show"). \
82
- Only include such a view_link if a Show view for this table is listed in the available views.`;
78
+ Only include such a view_link if a Show view for this table is listed in the available views. \
79
+ Every view_link requires a "relation" string — you MUST call get_relation_paths before generating the layout \
80
+ whenever the layout will contain any view_link or embedded view segment.`;
83
81
 
84
82
  // ── Composed MODE_GUIDANCE ────────────────────────────────────────────────────
85
83
 
@@ -805,7 +803,8 @@ const normalizeSegment = (segment, ctx) => {
805
803
  let viewLabel = clone.view_label || clone.view;
806
804
  let isFormula = clone.isFormula || {};
807
805
  const tmplMatch =
808
- typeof viewLabel === "string" && viewLabel.match(/^\{\{\s*(.+?)\s*\}\}$/);
806
+ typeof viewLabel === "string" &&
807
+ viewLabel.match(/^\{\{\s*(.+?)\s*\}\}$/);
809
808
  if (tmplMatch) {
810
809
  viewLabel = tmplMatch[1];
811
810
  isFormula = { ...isFormula, label: true };
@@ -843,7 +842,9 @@ const normalizeSegment = (segment, ctx) => {
843
842
  child == null ? null : normalizeSegment(child, ctx)
844
843
  );
845
844
  if (!besides.some(Boolean)) return null;
846
- return { ...clone, besides, list_columns: true };
845
+ // Drop 'type' — List viewtemplate expects { besides, list_columns: true } with no type field
846
+ const { type: _t, ...rest } = clone;
847
+ return { ...rest, besides, list_columns: true };
847
848
  }
848
849
  case "list_column": {
849
850
  let raw = clone.contents;
@@ -961,7 +962,21 @@ const normalizeLayoutCandidate = (candidate, ctx) => {
961
962
  normalized = convertForeignLayout(candidate, ctx);
962
963
  }
963
964
  if (!normalized) return null;
964
- const layout = Array.isArray(normalized) ? { above: normalized } : normalized;
965
+ let layout = Array.isArray(normalized) ? { above: normalized } : normalized;
966
+
967
+ // For listcolumns mode: if the LLM wrapped the list in a stack, unwrap it
968
+ if (
969
+ ctx.mode === "listcolumns" &&
970
+ layout.type === "stack" &&
971
+ Array.isArray(layout.above)
972
+ ) {
973
+ const listChild = layout.above.find((c) => c?.list_columns);
974
+ if (listChild) {
975
+ const { type: _t, ...rest } = listChild;
976
+ layout = rest;
977
+ }
978
+ }
979
+
965
980
  return sanitizeNoHtmlSegments(layout);
966
981
  };
967
982
 
@@ -1000,6 +1015,11 @@ const buildPromptText = (userPrompt, ctx, schema) => {
1000
1015
  schema
1001
1016
  )}`
1002
1017
  );
1018
+ parts.push(
1019
+ `TOOL CALL REQUIRED: If the layout you are about to generate contains any view_link or embedded view (type "view") segment, ` +
1020
+ `you MUST call get_relation_paths with all required source_table/target_view pairs BEFORE returning JSON. ` +
1021
+ `Do NOT guess or omit the "relation" field — return the tool call first and use the result in the layout.`
1022
+ );
1003
1023
  parts.push(
1004
1024
  `Based on the schema above, process the following user request and generate the layout JSON. Reminder: ONLY output valid JSON starting with { and ending with }, no markdown fences.\nUser request:\n"${userPrompt}"`
1005
1025
  );
@@ -1220,7 +1240,6 @@ const buildBracketObject = (node) => {
1220
1240
  return obj;
1221
1241
  };
1222
1242
 
1223
-
1224
1243
  const buildContext = async (mode, tableName) => {
1225
1244
  const normalizedMode = (mode || "show").toLowerCase();
1226
1245
  const ctx = {
@@ -1286,9 +1305,16 @@ const buildContext = async (mode, tableName) => {
1286
1305
  if (!fieldviews.length && String(typeName).startsWith("Key")) {
1287
1306
  fieldviews = ["select", "show"];
1288
1307
  }
1308
+ // File fields store their fieldviews in state.fileviews, not on the type object
1309
+ let fileFvDefs = {};
1310
+ if (String(typeName) === "File") {
1311
+ fileFvDefs = getState().fileviews || {};
1312
+ fieldviews = Object.keys(fileFvDefs);
1313
+ }
1289
1314
  // editFieldviews: only fieldviews where isEdit is not explicitly false
1315
+ const effectiveFvDefs = String(typeName) === "File" ? fileFvDefs : fvDefs;
1290
1316
  const editFieldviews = fieldviews.filter(
1291
- (fv) => fvDefs[fv]?.isEdit !== false
1317
+ (fv) => effectiveFvDefs[fv]?.isEdit !== false
1292
1318
  );
1293
1319
 
1294
1320
  const isPkName =
@@ -1400,16 +1426,19 @@ const buildErrorLayout = ({ message, mode, table }) => {
1400
1426
  };
1401
1427
  };
1402
1428
 
1403
- const GET_RELATION_PATHS_TOOL = { type: "function", function: GET_RELATION_PATHS_FUNCTION };
1429
+ const GET_RELATION_PATHS_TOOL = {
1430
+ type: "function",
1431
+ function: GET_RELATION_PATHS_FUNCTION,
1432
+ };
1404
1433
 
1405
1434
  /**
1406
- * Run the LLM giving it one opportunity to call get_relation_paths before
1407
- * producing the final layout JSON. Returns the final text response.
1435
+ * Run the LLM allowing up to MAX_TOOL_ROUNDS get_relation_paths calls (depth
1436
+ * escalation: 2 4 6) before producing the final layout JSON.
1408
1437
  */
1438
+ const MAX_TOOL_ROUNDS = 4;
1409
1439
  const runWithRelationTools = async (llm, mainPrompt, opts) => {
1410
1440
  const llm_add_message = getState().functions.llm_add_message;
1411
1441
 
1412
- // Local chat copy with the main prompt pre-loaded so all iterations see it.
1413
1442
  const runChat = Array.isArray(opts.chat) ? [...opts.chat] : [];
1414
1443
  runChat.push({ role: "user", content: mainPrompt });
1415
1444
 
@@ -1418,35 +1447,54 @@ const runWithRelationTools = async (llm, mainPrompt, opts) => {
1418
1447
  delete toolOpts.response_format;
1419
1448
  delete toolOpts.chat;
1420
1449
 
1421
- // Call 1: model either returns JSON directly or calls get_relation_paths.
1422
- const raw = await llm.run(null, { ...toolOpts, chat: runChat });
1423
- if (!raw?.hasToolCalls) {
1424
- return typeof raw === "string" ? raw : raw?.content ?? "";
1425
- }
1450
+ const schemaData = await build_schema_data();
1426
1451
 
1427
- // Append the assistant message so call 2 has full context.
1428
- if (raw.ai_sdk && Array.isArray(raw.messages)) {
1429
- raw.messages.filter((m) => m.role === "assistant").forEach((m) => runChat.push(m));
1430
- } else if (raw.tool_calls) {
1431
- runChat.push({ role: "assistant", content: raw.content || null, tool_calls: raw.tool_calls });
1432
- }
1452
+ for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
1453
+ const raw = await llm.run(null, { ...toolOpts, chat: runChat });
1433
1454
 
1434
- // Resolve the tool call(s) and push results.
1435
- const schemaData = await build_schema_data();
1436
- for (const tc of raw.getToolCalls()) {
1437
- let resultText;
1438
- if (tc.tool_name === "get_relation_paths") {
1439
- const sections = getRelationPathsForPairs(tc.input.pairs || [], schemaData);
1440
- resultText =
1441
- sections.join("\n\n") +
1442
- `\n\nSet the "relation" property to one of the strings listed above for each view_link.`;
1443
- } else {
1444
- resultText = `Unknown tool: ${tc.tool_name}`;
1455
+ if (!raw?.hasToolCalls) {
1456
+ return typeof raw === "string" ? raw : raw?.content ?? "";
1457
+ }
1458
+
1459
+ // Append the assistant message so the next round has full context.
1460
+ if (raw.ai_sdk && Array.isArray(raw.messages)) {
1461
+ raw.messages
1462
+ .filter((m) => m.role === "assistant")
1463
+ .forEach((m) => runChat.push(m));
1464
+ } else if (raw.tool_calls) {
1465
+ runChat.push({
1466
+ role: "assistant",
1467
+ content: raw.content || null,
1468
+ tool_calls: raw.tool_calls,
1469
+ });
1470
+ }
1471
+
1472
+ for (const tc of raw.getToolCalls()) {
1473
+ let resultText;
1474
+ if (tc.tool_name === "get_relation_paths") {
1475
+ const maxDepth = tc.input.max_depth ?? 2;
1476
+ const sections = getRelationPathsForPairs(
1477
+ tc.input.pairs || [],
1478
+ schemaData,
1479
+ maxDepth
1480
+ );
1481
+ resultText =
1482
+ sections.join("\n\n") +
1483
+ `\n\nFor each pair above: analyse whether the listed paths include a suitable one for the intended relation type (ChildList, ParentShow, Own, etc.). ` +
1484
+ `If a suitable path exists, set the "relation" property to the chosen path string for all types including Own. ` +
1485
+ `If no suitable path exists for a pair (no paths listed, or none match the intended type), call get_relation_paths again with a higher max_depth (4, then 6). ` +
1486
+ `Do not escalate just because multiple paths are listed — only escalate when none is appropriate.`;
1487
+ } else {
1488
+ resultText = `Unknown tool: ${tc.tool_name}`;
1489
+ }
1490
+ await llm_add_message.run("tool_response", resultText, {
1491
+ chat: runChat,
1492
+ tool_call: tc,
1493
+ });
1445
1494
  }
1446
- await llm_add_message.run("tool_response", resultText, { chat: runChat, tool_call: tc });
1447
1495
  }
1448
1496
 
1449
- // Call 2: model has relation paths, now generates the layout JSON.
1497
+ // Exhausted tool rounds force final JSON answer.
1450
1498
  const final = await llm.run(null, { ...opts, chat: runChat });
1451
1499
  return typeof final === "string" ? final : final?.content ?? "";
1452
1500
  };
@@ -1564,6 +1612,7 @@ module.exports = {
1564
1612
  },
1565
1613
  isAsync: true,
1566
1614
  description: "Generate a builder layout",
1615
+ hidden: true,
1567
1616
  arguments: [
1568
1617
  { name: "prompt", type: "String" },
1569
1618
  { name: "mode", type: "String" },
package/builder-schema.js CHANGED
@@ -327,7 +327,13 @@ const buildBuilderSchema = ({ mode, ctx }) => {
327
327
  const actionDef = buildSegmentDef({
328
328
  type: "action",
329
329
  description: "Action button segment.",
330
+ required: ["rndid"],
330
331
  properties: {
332
+ rndid: {
333
+ type: "string",
334
+ description:
335
+ "Unique short identifier for this action (e.g. 'act1', 'del2'). REQUIRED — omitting it causes a server crash.",
336
+ },
331
337
  action_name: defs.action_name_enum
332
338
  ? { $ref: "#/$defs/action_name_enum", description: "Action name." }
333
339
  : { type: "string", description: "Action name." },
@@ -62,6 +62,7 @@ const get_agent_view = () => {
62
62
  min_role: 1,
63
63
  configuration: {
64
64
  agent_action,
65
+ show_prev_runs: true,
65
66
  viewname,
66
67
  },
67
68
  });
package/index.js CHANGED
@@ -18,7 +18,6 @@ module.exports = {
18
18
  dependencies: ["@saltcorn/large-language-model", "@saltcorn/agents"],
19
19
  viewtemplates: features.workflows
20
20
  ? [
21
- require("./chat-copilot"),
22
21
  require("./user-copilot"),
23
22
  require("./copilot-as-agent"),
24
23
  require("./app-constructor/view.js"),
package/js-code-gen.js CHANGED
@@ -57,6 +57,7 @@ module.exports = {
57
57
  },
58
58
  isAsync: true,
59
59
  description: "Generate JavaScript code for an action",
60
+ hidden: true,
60
61
  arguments: [
61
62
  { name: "description", type: "String" },
62
63
  { name: "existing_code", type: "String" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/copilot",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "AI assistant for building Saltcorn applications",
5
5
  "main": "index.js",
6
6
  "dependencies": {
package/relation-paths.js CHANGED
@@ -36,66 +36,72 @@ const { RelationsFinder, RelationType } = require("@saltcorn/common-code");
36
36
  const RELATION_PATH_DOC = `
37
37
  ## Relation paths
38
38
 
39
- Relation paths connect a view_link column or embedded view (type "view") segment to its
40
- target view. There are two formats — **new** (preferred) and **legacy** (read-only).
39
+ Every view_link and embedded view segment requires two fields:
40
+ - \`view\`: the view name (plain string, e.g. \`"packing_items_list"\`)
41
+ - \`relation\`: a dot-separated path string (e.g. \`".trips.packing_items$trip_id"\`)
41
42
 
42
- ---
43
+ **Always use this format. Never generate anything else.**
43
44
 
44
- ### New format (always use this when generating or updating)
45
+ ---
45
46
 
46
- Two separate fields:
47
- - \`view\`: just the view name, e.g. \`"packing_items_list"\`
48
- - \`relation\`: a dot-separated path string, e.g. \`".trips.packing_items$trip_id"\`
47
+ ### Relation path format
49
48
 
50
- **Path string format:** \`.sourcetable.segment1.segment2...\`
49
+ \`.sourcetable.segment1.segment2...\`
51
50
 
52
51
  Segment types:
53
- - **Outbound FK** (navigate to a parent): FK field name alone — e.g. \`trip_id\`
54
- - **Inbound FK** (collect child rows): \`childtable$fkfield\` — e.g. \`packing_items$trip_id\`
52
+ - **Outbound FK** (to a parent): FK field name alone — e.g. \`trip_id\`
53
+ - **Inbound FK** (child rows): \`childtable$fkfield\` — e.g. \`packing_items$trip_id\`
54
+ - **Same table** (no FK traversal): just the source table, no segments — e.g. \`".invoice_line_items"\`
55
55
 
56
56
  Examples:
57
- | relation string | type | meaning |
58
- |---|---|---|
59
- | \`.trips.packing_items$trip_id\` | ChildList | all packing_items for a trip |
60
- | \`.packing_items.trip_id\` | ParentShow | the trip that owns a packing_item |
61
- | \`.artists.artist_plays_on_album$artist.album\` | ChildList | albums via join table |
62
- | \`.users.orders$user_id.order_lines$order_id\` | RelationPath | multi-level |
57
+ | relation string | meaning |
58
+ |---|---|
59
+ | \`.invoice_line_items\` | same-table link (no FK traversal) |
60
+ | \`.trips.packing_items$trip_id\` | all packing_items for a trip |
61
+ | \`.packing_items.trip_id\` | the trip that owns a packing_item |
62
+ | \`.artists.artist_plays_on_album$artist.album\` | albums via join table |
63
+ | \`.users.orders$user_id.order_lines$order_id\` | multi-level |
63
64
 
64
65
  ---
65
66
 
66
- ### Legacy format (you may encounter this in existing configs do not generate it)
67
+ ### Legacy format — read-only, never generate
67
68
 
68
- The type and path are encoded together inside the \`view\` field as a colon-prefixed string.
69
- There is no separate \`relation\` field.
69
+ Any \`view\` field value that contains a colon (e.g. \`"ChildList:trips_list.packing_items.trip_id"\`,
70
+ \`"Own:viewname"\`, \`"ParentShow:viewname.table.fkfield"\`) is legacy. You may encounter it in
71
+ existing configs. Parse it to understand the relation, then always write back in the new format.
70
72
 
71
- | legacy view field | equivalent new relation |
73
+ | legacy view field | new format equivalent |
72
74
  |---|---|
73
- | \`"Own:viewname"\` | \`view: "viewname"\`, no relation |
75
+ | \`"Own:viewname"\` | \`relation: ".sourcetable"\` |
76
+ | \`"Independent:viewname"\` | no \`relation\` field needed |
74
77
  | \`"ParentShow:viewname.table.fkfield"\` | \`relation: ".sourcetable.fkfield"\` |
75
78
  | \`"ChildList:viewname.table.inbkey"\` | \`relation: ".sourcetable.childtable$inbkey"\` |
76
79
  | \`"OneToOneShow:viewname.table.inbkey"\` | \`relation: ".sourcetable.childtable$inbkey"\` |
77
- | \`"Independent:viewname"\` | \`view: "viewname"\`, no relation |
78
-
79
- When you read an existing view config and see a legacy \`view\` value like \`"ChildList:trips_list.packing_items.trip_id"\`, parse it as: type=ChildList, view=trips_list, relation=.sourcetable.packing_items$trip_id. When writing back, convert to the new format.
80
80
 
81
81
  ---
82
82
 
83
83
  ### Using get_relation_paths
84
84
 
85
- Call it **once** with all source_table/target_view pairs you need — do not make separate calls per pair. The tool returns paths in the new RelationPath format; always write back in the new format, even if you read legacy strings from an existing config.
85
+ Call it **once** with all source_table/target_view pairs you need. The tool always returns new-format
86
+ path strings — use them as the \`relation\` field directly.
86
87
 
87
- When multiple paths are returned for one pair, pick by matching the relation type to what the target view is meant to show:
88
- - **ChildList** target view shows multiple rows belonging to the current row (e.g. packing items for a trip).
89
- - **ParentShow** target view shows the single parent the current row belongs to (e.g. the trip for a packing item).
88
+ **Depth escalation:** Start with \`max_depth=2\`. After receiving results, analyse each pair: does the
89
+ result contain a path that matches the intended relation type? If yes, use it. If a pair has no
90
+ suitable path (no paths at all, or none of the right type), call again with \`max_depth=4\`, then
91
+ \`max_depth=6\` if still none. Do not escalate just because multiple paths are listed.
92
+
93
+ Selecting among returned paths:
94
+ - **ChildList** — target view shows multiple rows belonging to the current row.
95
+ - **ParentShow** — target view shows the single parent the current row belongs to.
90
96
  - **OneToOneShow** — exactly one related child row via a unique FK.
91
- - **Own** — target view is on the same table (no FK traversal needed).
92
- - If multiple paths of the same type exist (e.g. a table has two FKs pointing to the same target), pick the one whose FK field name best matches the semantic relationship in the task.
97
+ - **Own** — same table, no FK traversal. Relation string is just \`.sourcetable\`.
98
+ - If multiple paths of the same type exist, pick the one whose FK field name best matches the task.
93
99
  - Prefer shorter paths (fewer segments) unless a longer one is clearly more appropriate.
94
100
  `;
95
101
 
96
102
  const typeToLabel = (type) => {
97
103
  if (type === RelationType.OWN)
98
- return "Own – source and target are the same table (no relation needed)";
104
+ return "Own – source and target are the same table. Use this relation string as-is (no extra segments after the table name).";
99
105
  if (type === RelationType.INDEPENDENT)
100
106
  return "Independent – no FK relationship exists";
101
107
  if (type === RelationType.PARENT_SHOW)
@@ -113,10 +119,19 @@ const typeToLabel = (type) => {
113
119
  * @param {{ tables, views }} schemaData pre-fetched via build_schema_data()
114
120
  * @returns {Array<Relation>} raw Relation objects from RelationsFinder
115
121
  */
116
- function getRelationPaths(sourceTableName, targetViewName, schemaData) {
122
+ function getRelationPaths(
123
+ sourceTableName,
124
+ targetViewName,
125
+ schemaData,
126
+ maxDepth = 6
127
+ ) {
117
128
  if (!schemaData) return [];
118
129
  try {
119
- const finder = new RelationsFinder(schemaData.tables, schemaData.views, 6);
130
+ const finder = new RelationsFinder(
131
+ schemaData.tables,
132
+ schemaData.views,
133
+ maxDepth
134
+ );
120
135
  return finder.findRelations(sourceTableName, targetViewName, []);
121
136
  } catch {
122
137
  return [];
@@ -130,13 +145,22 @@ function getRelationPaths(sourceTableName, targetViewName, schemaData) {
130
145
  * @param {{ tables, views }} schemaData
131
146
  * @returns {Array<string>} one formatted result string per pair
132
147
  */
133
- function getRelationPathsForPairs(pairs, schemaData) {
134
- if (!schemaData) return pairs.map(({ source_table, target_view }) =>
135
- formatRelationPathResult(source_table, target_view, { error: "Schema data unavailable" })
148
+ function getRelationPathsForPairs(pairs, schemaData, maxDepth = 6) {
149
+ if (!schemaData)
150
+ return pairs.map(({ source_table, target_view }) =>
151
+ formatRelationPathResult(source_table, target_view, {
152
+ error: "Schema data unavailable",
153
+ })
154
+ );
155
+ const finder = new RelationsFinder(
156
+ schemaData.tables,
157
+ schemaData.views,
158
+ maxDepth
136
159
  );
137
- const finder = new RelationsFinder(schemaData.tables, schemaData.views, 6);
138
160
  return pairs.map(({ source_table, target_view }) => {
139
- const targetView = (schemaData.views || []).find((v) => v.name === target_view);
161
+ const targetView = (schemaData.views || []).find(
162
+ (v) => v.name === target_view
163
+ );
140
164
  if (!targetView)
141
165
  return formatRelationPathResult(source_table, target_view, {
142
166
  error: `View "${target_view}" not found in current schema`,
@@ -198,8 +222,11 @@ const GET_RELATION_PATHS_FUNCTION = {
198
222
  name: "get_relation_paths",
199
223
  description:
200
224
  "Get all valid relation path strings for one or more source_table/target_view pairs. " +
201
- "Call this ONCE with all pairs you need before setting any 'relation' property on " +
202
- "view_link columns or embedded view (type 'view') segments.",
225
+ "Call this before setting any 'relation' property on view_link columns or embedded view segments. " +
226
+ "Always start with max_depth=2 to keep the result compact. " +
227
+ "After receiving the results, analyse whether each pair has a suitable path for its intended relation type. " +
228
+ "If a pair has no suitable path, call again with max_depth=4, then max_depth=6 if still none. " +
229
+ "Stop as soon as every pair has a suitable path.",
203
230
  parameters: {
204
231
  type: "object",
205
232
  required: ["pairs"],
@@ -223,6 +250,12 @@ const GET_RELATION_PATHS_FUNCTION = {
223
250
  },
224
251
  },
225
252
  },
253
+ max_depth: {
254
+ type: "integer",
255
+ description:
256
+ "Maximum join depth to search. Start with 2. Escalate to 4, then 6, only if a pair has no suitable path in the current results — not just because paths exist, but because none match the intended relation type.",
257
+ default: 2,
258
+ },
226
259
  },
227
260
  },
228
261
  };
@@ -73,6 +73,7 @@ ${ds.join("\n")}`);
73
73
  },
74
74
  isAsync: true,
75
75
  description: "Return a standard prompt for writing code",
76
+ hidden: true,
76
77
  arguments: [
77
78
  {
78
79
  name: "options",
package/user-copilot.js CHANGED
@@ -933,5 +933,7 @@ module.exports = {
933
933
  get_state_fields,
934
934
  tableless: true,
935
935
  run,
936
+ deprecated: true,
937
+ description: "Use Agents from the agent module instead",
936
938
  routes: { interact, delprevrun },
937
939
  };
package/workflow-gen.js CHANGED
@@ -41,5 +41,6 @@ module.exports = {
41
41
  },
42
42
  isAsync: true,
43
43
  description: "Generate a workflow",
44
+ hidden: true,
44
45
  arguments: [{ name: "description", type: "String" }],
45
46
  };
@@ -1,120 +0,0 @@
1
- const saltcorn_description = `This application will be implemented in Saltcorn, a database application development
2
- environment.
3
-
4
- Saltcorn applications contain the following entity types:
5
-
6
- * Tables: These are relational database tables and consists of fields of specified types and rows
7
- with a value for each field. Fields optionally can be required and/or unique. Every field has a name,
8
- which is a an identifier that is balid in both JavaScript and SQL, and a label, which is any short
9
- user-friendly string. Every table has a primary key
10
- (composite primary keys are not supported) which by default is an auto-incrementing integer with
11
- name \`id\` and label ID. Fields can also be of Key type (foreign key) referencing a primary key
12
- in another table, or its own table for a self-join. Tables can have
13
- calculated fields, which can be stored or non-stored. Both stored and non-stored fields are
14
- defined by a JavaScript expression, but only stored fields can reference other tables with join
15
- fields and aggregations.
16
-
17
- * Views: Views are elementary user interfaces into a database table. A view is defined by applying a
18
- view template (also sometime called a view pattern, the two are synonymous) to a table with a certain
19
- configuration. The view template defines the fundamental relationship between the UI and the table. For
20
- instance, the Show view template displays a single database row, the Edit view template is a form that
21
- can create a new row or edit an existing row, the List view template displays multiple rows in a grid.
22
- Views can embed views, for instance Show can embed another row through a Key field relationship, or
23
- some views are defined by an underlying view. For instance, the Feed view repeats an underlying view
24
- for multiple tables. New viewtemplates are provided by plugin modules.
25
-
26
- * Triggers: Triggers connect elementary actions (provided by plugin modules) to either a button in the
27
- user interface, or a periodic (hourly, daily etc) or table (for instance insert on specifc table) event.
28
- The elementary action each has a number of configuration fields that must be filled in after connecting
29
- the action to an event, table or button.
30
-
31
- * Page: A page has static content but can also embed views for synamic content. Pages can be either
32
- defined by a Saltcorn layout, for pages that can be edited with drag and drop, or by HTML for more
33
- flexible graphic designs. HTML pages should be used for landing pages.
34
-
35
- * Plugin modules: plugin modules can supply new field types, view templates or actions. Before they can be used,
36
- they need to be installed before they can be used. A plugin may also have a configuration that sets options
37
- for that plugin. Layout themes is Saltcorn are plugin modules.
38
- `;
39
-
40
- const existing_tables_list = (tables) => {
41
- const tableLines = [];
42
- tables.forEach((table) => {
43
- const fieldLines = table.fields.map(
44
- (f) =>
45
- ` * ${f.name} with type: ${f.pretty_type}.${
46
- f.description ? ` ${f.description}` : ""
47
- }`
48
- );
49
- tableLines.push(
50
- `${table.name}${
51
- table.description ? `: ${table.description}.` : "."
52
- } Contains the following fields:\n${fieldLines.join("\n")}`
53
- );
54
- });
55
- return `The database already contains the following tables:
56
-
57
- ${tableLines.join("\n\n")}`;
58
- };
59
-
60
- const existing_entities_list = ({ views, triggers, pages, tableById = {} }) => {
61
- const sections = [];
62
- if (views.length)
63
- sections.push(
64
- `The following views are already implemented — do NOT plan tasks to create them:\n` +
65
- views
66
- .map((v) => {
67
- const tablePart =
68
- v.table?.name ||
69
- (v.table_id && tableById[v.table_id]) ||
70
- v.exttable_name;
71
- return `- ${v.name} (${v.viewtemplate}${
72
- tablePart ? ` on ${tablePart}` : ""
73
- })`;
74
- })
75
- .join("\n")
76
- );
77
- if (triggers.length)
78
- sections.push(
79
- `The following triggers are already implemented — do NOT plan tasks to create them:\n` +
80
- triggers
81
- .map(
82
- (t) =>
83
- `- ${t.name} (${t.action}${
84
- t.when_trigger ? `, ${t.when_trigger}` : ""
85
- })`
86
- )
87
- .join("\n")
88
- );
89
- if (pages.length)
90
- sections.push(
91
- `The following pages are already implemented — do NOT plan tasks to create them:\n` +
92
- pages.map((p) => `- ${p.name}`).join("\n")
93
- );
94
- return sections.join("\n\n");
95
- };
96
-
97
- const available_plugins_list = (storePlugins, installedNames) => {
98
- const uninstalled = storePlugins.filter((p) => !installedNames.has(p.name));
99
- if (!uninstalled.length) return "";
100
- const lines = uninstalled.map((p) => {
101
- let line = `### ${p.name}`;
102
- if (p.description) line += `\n${p.description}`;
103
- if (p.contents) line += `\n${p.contents}`;
104
- return line;
105
- });
106
- return (
107
- `The following plugins are available in the Saltcorn store but not yet installed. ` +
108
- `If a task requires functionality provided by one of these plugins (e.g. a specific view template, field type, or action), ` +
109
- `include an explicit "Install plugin <name>" task before it with the exact plugin name as listed here. ` +
110
- `The executor will use that name directly without needing to look it up.\n\n` +
111
- lines.join("\n\n")
112
- );
113
- };
114
-
115
- module.exports = {
116
- saltcorn_description,
117
- existing_tables_list,
118
- existing_entities_list,
119
- available_plugins_list,
120
- };