From 27d5d63240bb184047675cb3871a0b63fedb4354 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Wed, 27 May 2026 14:38:07 -0700 Subject: [PATCH 1/5] 1165: display data type for applicable expressions --- .../ExpressionAssistantAgentAction.java | 175 +++++++++++++----- .../query/controllers/QueryController.java | 15 +- .../labkey/query/controllers/QueryMcp.java | 10 +- .../prompts/ExpressionAssistant.md | 57 +++--- 4 files changed, 187 insertions(+), 70 deletions(-) diff --git a/query/src/org/labkey/query/controllers/ExpressionAssistantAgentAction.java b/query/src/org/labkey/query/controllers/ExpressionAssistantAgentAction.java index 010e3d62783..5aa8f057884 100644 --- a/query/src/org/labkey/query/controllers/ExpressionAssistantAgentAction.java +++ b/query/src/org/labkey/query/controllers/ExpressionAssistantAgentAction.java @@ -40,6 +40,7 @@ import org.springframework.validation.BindException; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -72,7 +73,7 @@ public Object execute(ParseForm form, BindException errors) throws Exception { boolean firstTurn = isBlank(form.getConversationId()); String prompt = form.getPrompt(); - String composedPrompt = composePrompt(firstTurn, prompt, form.getDomainFields(), form.getFieldExpression(), form.getFieldError()); + String composedPrompt = composePrompt(firstTurn, prompt, form.getField(), form.getDomainFields(), form.getFieldExpression(), form.getFieldError()); if (isBlank(composedPrompt)) { @@ -121,7 +122,7 @@ public Object execute(ParseForm form, BindException errors) throws Exception * invalid expression). When {@code firstTurn} is false the context fields are ignored, and the * user's prompt is returned verbatim. */ - static String composePrompt(boolean firstTurn, String userPrompt, JSONArray domainFields, String fieldExpression, String fieldError) + static String composePrompt(boolean firstTurn, String userPrompt, JSONObject field, JSONArray domainFields, String fieldExpression, String fieldError) { if (!firstTurn) return StringUtils.defaultString(userPrompt); @@ -137,6 +138,12 @@ static String composePrompt(boolean firstTurn, String userPrompt, JSONArray doma sb.append(fence(domainFields.toString(), "json")); } + if (field != null) + { + sb.append("The current column that is having its expression evaluated and the one you are assisting with is:\n"); + sb.append(fence(field.toString(), "json")); + } + if (autoEvaluate) { if (isNotBlank(fieldExpression)) @@ -177,6 +184,10 @@ static String fence(String body, String tag) * Each segment is either a rendered-HTML span or a fenced SQL block. SQL blocks fenced as * `expression` are tagged "expression" (the model's assertion that this SQL has been validated * and is safe to apply); blocks fenced as `sql` are tagged "sql" (illustrative / unvalidated). + * For `expression` blocks the body is expected to be the JSON returned by + * validateCalculatedColumnExpression — at minimum {@code {"expression": "..."}}, optionally + * with {@code "jdbcType"}. If the body fails to parse as JSON we fall back to treating it as a + * raw SQL string so the Apply affordance still works. */ private static JSONArray buildSegments(List responses) { @@ -194,36 +205,27 @@ private static JSONArray buildSegments(List response int i = 0; while (i < lines.length) { - String tag = fenceTag(lines[i]); - if ("sql".equals(tag) || "expression".equals(tag)) + Fence f = readFence(lines, i); + if (f != null && f.terminated && ("sql".equals(f.tag) || "expression".equals(f.tag))) { - int j = i + 1; - StringBuilder code = new StringBuilder(); - while (j < lines.length && !"```".equals(lines[j].trim())) - { - if (!code.isEmpty()) code.append("\n"); - code.append(lines[j]); - j++; - } - if (j >= lines.length) - { - // Unterminated fence — fold the body back into prose so we don't drop content, - // but skip the opening fence line itself so the user doesn't see a stray - // "```expression" rendered as a code marker. - if (!htmlBuf.isEmpty()) htmlBuf.append("\n"); - for (int k = i + 1; k < lines.length; k++) - { - htmlBuf.append(lines[k]); - if (k < lines.length - 1) htmlBuf.append("\n"); - } - break; - } flushHtmlSegment(segments, htmlBuf, md); - segments.put(new JSONObject(Map.of("type", tag, "sql", code.toString()))); - i = j + 1; + segments.put(buildCodeSegment(f.tag, f.body)); + i = f.nextIndex; + } + else if (f != null && !f.terminated) + { + // Unterminated fence — fold the body back into prose so we don't drop content, + // but skip the opening fence line itself so the user doesn't see a stray + // "```expression" rendered as a code marker. + if (!htmlBuf.isEmpty()) htmlBuf.append("\n"); + htmlBuf.append(f.body); + break; } else { + // Either not a fence opener or an unknown tag (e.g., python). In the unknown-tag + // case we leave the fence intact in prose so the Markdown renderer turns it into + // a code block. if (!htmlBuf.isEmpty()) htmlBuf.append("\n"); htmlBuf.append(lines[i]); i++; @@ -234,13 +236,59 @@ private static JSONArray buildSegments(List response return segments; } - private static String fenceTag(String line) + /** + * Build a segment JSON object for an `sql` or `expression` fenced block. For `expression` + * blocks the body is expected to be the JSON returned by validateCalculatedColumnExpression; + * we pull "expression" into "sql" and pass through "jdbcType". If the body isn't JSON we treat + * it as raw SQL text so the Apply affordance still works. + */ + private static JSONObject buildCodeSegment(String tag, String body) { - String trimmed = line.trim(); + Map segData = new LinkedHashMap<>(); + segData.put("type", tag); + + if ("expression".equals(tag)) + { + try + { + JSONObject payload = new JSONObject(body); + segData.put("sql", payload.optString("expression", body)); + if (payload.has("jdbcType")) + segData.put("jdbcType", payload.getString("jdbcType")); + } + catch (org.json.JSONException x) + { + segData.put("sql", body); + } + } + else + segData.put("sql", body); + + return new JSONObject(segData); + } + + private record Fence(String tag, String body, int nextIndex, boolean terminated) {} + + private static Fence readFence(String[] lines, int i) + { + String trimmed = lines[i].trim(); if (!trimmed.startsWith("```")) return null; String rest = trimmed.substring(3).trim(); - return rest.isEmpty() ? null : rest.toLowerCase(); + if (rest.isEmpty()) + return null; + String tag = rest.toLowerCase(); + + int j = i + 1; + StringBuilder body = new StringBuilder(); + while (j < lines.length && !"```".equals(lines[j].trim())) + { + if (!body.isEmpty()) body.append("\n"); + body.append(lines[j]); + j++; + } + boolean terminated = j < lines.length; + return new Fence(tag, body.toString(), terminated ? j + 1 : lines.length, terminated); } private static void flushHtmlSegment(JSONArray segments, StringBuilder buf, MarkdownService md) @@ -279,6 +327,16 @@ private static JSONObject segment(JSONArray segments, int i) return segments.getJSONObject(i); } + /** The JSON body the model is expected to echo verbatim from validateCalculatedColumnExpression. */ + private static String expressionPayload(String sql, String jdbcType) + { + JSONObject json = new JSONObject(); + json.put("jdbcType", jdbcType); + json.put("expression", sql); + json.put("success", true); + return json.toString(); + } + @Test public void emptyResponseList() { @@ -305,11 +363,25 @@ public void proseOnlyProducesSingleHtmlSegment() @Test public void expressionFenceProducesExpressionSegment() { + String md = "```expression\n" + expressionPayload("SELECT 1", "INTEGER") + "\n```"; + JSONArray segments = buildSegments(List.of(markdownResponse(md))); + assertEquals(1, segments.length()); + assertEquals("expression", segment(segments, 0).getString("type")); + assertEquals("SELECT 1", segment(segments, 0).getString("sql")); + assertEquals("INTEGER", segment(segments, 0).getString("jdbcType")); + } + + @Test + public void expressionFenceWithRawSqlBodyFallsBackToSqlOnly() + { + // Legacy / model-misbehavior fallback: if the body isn't JSON, treat it as raw SQL and omit jdbcType. String md = "```expression\nSELECT 1\n```"; JSONArray segments = buildSegments(List.of(markdownResponse(md))); assertEquals(1, segments.length()); assertEquals("expression", segment(segments, 0).getString("type")); assertEquals("SELECT 1", segment(segments, 0).getString("sql")); + assertFalse("jdbcType should be absent when body isn't JSON", + segment(segments, 0).has("jdbcType")); } @Test @@ -335,7 +407,7 @@ public void interleavedProseAndFencesProduceOrderedSegments() "", "Option B (ready to apply):", "```expression", - "SELECT b FROM t", + expressionPayload("SELECT b FROM t", "VARCHAR"), "```", "Pick whichever fits." ); @@ -347,6 +419,7 @@ public void interleavedProseAndFencesProduceOrderedSegments() assertEquals("html", segment(segments, 2).getString("type")); assertEquals("expression", segment(segments, 3).getString("type")); assertEquals("SELECT b FROM t", segment(segments, 3).getString("sql")); + assertEquals("VARCHAR", segment(segments, 3).getString("jdbcType")); assertEquals("html", segment(segments, 4).getString("type")); } @@ -355,24 +428,27 @@ public void multipleExpressionFencesEachBecomeOwnSegment() { String md = String.join("\n", "```expression", - "SELECT 1", + expressionPayload("SELECT 1", "INTEGER"), "```", "```expression", - "SELECT 2", + expressionPayload("SELECT 2", "BIGINT"), "```" ); JSONArray segments = buildSegments(List.of(markdownResponse(md))); assertEquals(2, segments.length()); assertEquals("expression", segment(segments, 0).getString("type")); assertEquals("SELECT 1", segment(segments, 0).getString("sql")); + assertEquals("INTEGER", segment(segments, 0).getString("jdbcType")); assertEquals("expression", segment(segments, 1).getString("type")); assertEquals("SELECT 2", segment(segments, 1).getString("sql")); + assertEquals("BIGINT", segment(segments, 1).getString("jdbcType")); } @Test public void unterminatedFenceFallsBackToHtml() { - String md = "Here's an expression:\n```expression\nSELECT 1\n(no closing fence)"; + String payload = expressionPayload("SELECT 1", "INTEGER"); + String md = "Here's an expression:\n```expression\n" + payload + "\n(no closing fence)"; JSONArray segments = buildSegments(List.of(markdownResponse(md))); assertEquals(1, segments.length()); assertEquals("html", segment(segments, 0).getString("type")); @@ -395,32 +471,36 @@ public void unknownFenceLanguageIsTreatedAsProse() @Test public void fenceTagIsCaseInsensitive() { - String md = "```EXPRESSION\nSELECT 1\n```"; + String md = "```EXPRESSION\n" + expressionPayload("SELECT 1", "INTEGER") + "\n```"; JSONArray segments = buildSegments(List.of(markdownResponse(md))); assertEquals(1, segments.length()); assertEquals("expression", segment(segments, 0).getString("type")); assertEquals("SELECT 1", segment(segments, 0).getString("sql")); + assertEquals("INTEGER", segment(segments, 0).getString("jdbcType")); } @Test public void preservesMultilineSqlBody() { - String md = "```expression\nSELECT a,\n b\nFROM t\n```"; + String multiline = "SELECT a,\n b\nFROM t"; + String md = "```expression\n" + expressionPayload(multiline, "VARCHAR") + "\n```"; JSONArray segments = buildSegments(List.of(markdownResponse(md))); assertEquals(1, segments.length()); - assertEquals("SELECT a,\n b\nFROM t", segment(segments, 0).getString("sql")); + assertEquals(multiline, segment(segments, 0).getString("sql")); + assertEquals("VARCHAR", segment(segments, 0).getString("jdbcType")); } @Test public void multipleMessageResponsesAreConcatenatedInOrder() { McpService.MessageResponse r1 = markdownResponse("First response prose."); - McpService.MessageResponse r2 = markdownResponse("```expression\nSELECT 1\n```"); + McpService.MessageResponse r2 = markdownResponse("```expression\n" + expressionPayload("SELECT 1", "INTEGER") + "\n```"); JSONArray segments = buildSegments(List.of(r1, r2)); assertEquals(2, segments.length()); assertEquals("html", segment(segments, 0).getString("type")); assertEquals("expression", segment(segments, 1).getString("type")); assertEquals("SELECT 1", segment(segments, 1).getString("sql")); + assertEquals("INTEGER", segment(segments, 1).getString("jdbcType")); } @Test @@ -453,15 +533,20 @@ private static JSONArray fields(String json) public void composePromptFollowUpTurnIgnoresContextAndReturnsUserPromptVerbatim() { // Once a conversation is underway, the catalog/expression/error are already in chat history. - assertEquals("more please", composePrompt(false, "more please", fields("[{\"name\":\"A\"}]"), "SELECT 1", "boom")); + assertEquals("more please", composePrompt(false, "more please", null, fields("[{\"name\":\"A\"}]"), "SELECT 1", "boom")); } @Test public void composePromptFirstTurnWrapsWithFieldsCatalogAndInstruction() { - String composed = composePrompt(true, "sum A and B", fields("[{\"name\":\"A\"}]"), null, null); + JSONObject field = new JSONObject(Map.of("name", "MyCalc")); + String composed = composePrompt(true, "sum A and B", field, fields("[{\"name\":\"A\"}]"), null, null); assertTrue(composed.contains("available columns")); assertTrue(composed.contains("```json\n[{\"name\":\"A\"}]\n```")); + assertTrue("current-column preamble missing: " + composed, + composed.contains("current column that is having its expression evaluated")); + assertTrue("current-column JSON missing: " + composed, + composed.contains("```json\n" + field + "\n```")); assertTrue(composed.contains("Generate a calculated column expression")); assertTrue(composed.endsWith("sum A and B")); } @@ -469,7 +554,7 @@ public void composePromptFirstTurnWrapsWithFieldsCatalogAndInstruction() @Test public void composePromptAutoEvaluateIncludesExpressionAndError() { - String composed = composePrompt(true, "", fields("[{\"name\":\"A\"}]"), "SELECT bad", "syntax error"); + String composed = composePrompt(true, "", null, fields("[{\"name\":\"A\"}]"), "SELECT bad", "syntax error"); assertTrue(composed.contains("available columns")); assertTrue(composed.contains("```\nSELECT bad\n```")); assertTrue(composed.contains("```\nsyntax error\n```")); @@ -481,7 +566,7 @@ public void composePromptAutoEvaluateIncludesExpressionAndError() @Test public void composePromptAutoEvaluateWithoutExpressionStillIncludesError() { - String composed = composePrompt(true, "", null, null, "boom"); + String composed = composePrompt(true, "", null, null, null, "boom"); assertTrue(composed.contains("```\nboom\n```")); assertFalse(composed.contains("user already has the following")); } @@ -490,15 +575,15 @@ public void composePromptAutoEvaluateWithoutExpressionStillIncludesError() public void composePromptEmptyWhenNothingToSay() { // First turn with no user prompt and no error context — caller renders the no-op shrug response. - assertEquals("", composePrompt(true, "", null, null, null)); - assertEquals("", composePrompt(true, null, fields("[]"), "expr", null)); + assertEquals("", composePrompt(true, "", null, null, null, null)); + assertEquals("", composePrompt(true, null, null, fields("[]"), "expr", null)); } @Test public void composePromptFirstTurnWithoutDomainFieldsSkipsWrapping() { // No catalog to inject — don't bother prepending the "Generate a calculated column" preamble. - assertEquals("just do it", composePrompt(true, "just do it", null, null, null)); + assertEquals("just do it", composePrompt(true, "just do it", null, null, null, null)); } } } diff --git a/query/src/org/labkey/query/controllers/QueryController.java b/query/src/org/labkey/query/controllers/QueryController.java index 8a264aa0022..15c5ef7c53f 100644 --- a/query/src/org/labkey/query/controllers/QueryController.java +++ b/query/src/org/labkey/query/controllers/QueryController.java @@ -7845,8 +7845,9 @@ public static class ParseForm extends PromptForm implements ApiJsonForm Map columnMap = new HashMap<>(); List phiColumns = new ArrayList<>(); JSONArray domainFields; - String fieldExpression; + JSONObject field; String fieldError; + String fieldExpression; Map getColumnMap() { @@ -7883,6 +7884,16 @@ public void setDomainFields(JSONArray domainFields) this.domainFields = domainFields; } + public JSONObject getField() + { + return field; + } + + public void setField(JSONObject field) + { + this.field = field; + } + public String getFieldExpression() { return fieldExpression; @@ -7931,6 +7942,8 @@ public void bindJson(JSONObject json) setConversationId(json.getString("conversationId")); if (json.has("domainFields")) setDomainFields(json.getJSONArray("domainFields")); + if (json.has("field")) + setField(json.getJSONObject("field")); if (json.has("fieldExpression")) setFieldExpression(json.getString("fieldExpression")); if (json.has("fieldError")) diff --git a/query/src/org/labkey/query/controllers/QueryMcp.java b/query/src/org/labkey/query/controllers/QueryMcp.java index cd84391bc37..2ea4a1dae59 100644 --- a/query/src/org/labkey/query/controllers/QueryMcp.java +++ b/query/src/org/labkey/query/controllers/QueryMcp.java @@ -181,14 +181,18 @@ String validateCalculatedColumnExpression( try { - QueryServiceImpl.get().parseCalculatedColumn(context.getContainer(), context.getUser(), expression, columnMap, phiColumns); + QueryServiceImpl.CalculatedColumnParseResult result = QueryServiceImpl.get().parseCalculatedColumn(context.getContainer(), context.getUser(), expression, columnMap, phiColumns); + + JSONObject json = new JSONObject(); + json.put("jdbcType", result.jdbcType().name()); + json.put("expression", expression); + + return json.toString(); } catch (QueryException x) { return "That SQL caused the " + (x instanceof QueryParseWarning ? "warning" : "error") + " below:\n```\n" + x.getMessage() + "\n```"; } - - return "success"; } /* For now, list all schemas. CONSIDER support incremental querying. */ diff --git a/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md b/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md index 4df8d045c76..4c8583619bf 100644 --- a/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md +++ b/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md @@ -5,13 +5,16 @@ aliases. When generating SQL for calculated columns, always use LabKey SQL synta columns in LabKey are defined as SQL expressions that reference columns within the same query context. Never reference columns that do not exist in the provided metadata. -Be brief in your responses unless the user asks otherwise. +Keep conversational filler to a minimum. Be concise but always explicitly state assumptions and explain any +corrections made to invalid SQL. ### Intent Classification #### Requirements -- You shall only operate within the current table context and access metadata required for that table. -- The system shall prevent cross-schema references. +1. Only operate within the current table context and access metadata required for that table. +2. You must prevent cross-schema references. +3. The current column may not be named yet. If so, refer to it as a "new unnamed" calculated column. Do not + spell out how the metadata tells you it is newly-created or unnamed. #### Guidelines Before generating SQL, classify what the user is trying to do — helps route to the right pattern. Given the user's @@ -28,12 +31,13 @@ request, identify which of the following patterns applies: Refer to the "LabKey SQL" documentation resource for how to work with LabKey SQL. #### Requirements -- You shall only reference valid, existing columns from the current table. -- Generate valid LabKey SQL compatible with calculated column rules. -- The system shall prevent the use of disallowed functions and validate what's being used is valid LabKey SQL. -- The system shall prevent unsafe or unsupported SQL constructs. -- You shall not reference the calculated column being created (no circular references). -- You should proactively handle potential issues like empty data and dividing by zero. +1. You must only reference valid, existing columns from the current table. +2. You must generate valid LabKey SQL compatible with calculated column rules. +3. You must prevent the use of disallowed functions and validate what's being used is valid LabKey SQL. +4. You must prevent unsafe or unsupported SQL constructs. +5. Do not reference the calculated column being created (no circular references in the SQL expression). +6. Only refer to the current column's expression and not the expression of other columns unless explicitly asked by the user. +7. You should proactively handle potential issues like empty data and dividing by zero. #### Guidelines - Column references. Only reference columns that exist in the provided schema metadata for the current table. @@ -53,9 +57,9 @@ silently substitute or approximate. Stop, explain the issue clearly and ask the ### Column Validation #### Requirements -- The system shall validate all referenced column names. -- You shall provide suggestions for likely matches and detect and flag typos when a column name is invalid. -- You shall not silently replace invalid column names without notifying the user. +1. The system shall validate all referenced column names. +2. You must provide suggestions for likely matches and detect and flag typos when a column name is invalid. +3. You must not silently replace invalid column names without notifying the user. #### Guidelines Before returning any SQL expression, perform the following validation checks against the provided schema metadata: @@ -75,10 +79,10 @@ If check (1) succeeds, return the SQL and do not return validation information u ### Ambiguity Handling and Assumptions #### Requirements -- You shall detect ambiguous prompts and ask clarifying questions when necessary, avoiding silent guessing when +1. You must detect ambiguous prompts and ask clarifying questions when necessary, avoiding silent guessing when ambiguity materially affects the result. -- The system shall explicitly state assumptions made in the generated SQL. -- You shall ask for clarification if it doesn’t know what a field is. +2. The system shall explicitly state assumptions made in the generated SQL. +3. You must ask for clarification if you don't know what a field is. #### Guidelines If the user's request does not clearly identify which fields to use, ask one clarifying question before generating SQL. @@ -88,10 +92,21 @@ Do not generate SQL based on assumptions about field names. ### Output #### Requirements -- When you produce a SQL expression for the calculated column, you shall validate it using the +1. When you produce a SQL expression for the calculated column, you shall validate it using the validateCalculatedColumnExpression tool. Do not mention this tool to the user. -- When presenting a final SQL expression that the user can apply to their calculated column, place it in a fenced code - block tagged `expression` (e.g., ```expression\\n...\\n```) ONLY AFTER you have successfully validated it using the - validateCalculatedColumnExpression tool. Use a `sql` fence for any illustrative, intermediate or unvalidated SQL that - the user should NOT directly apply. Each `expression` block will be rendered with an \"Apply Expression\" affordance, - so emit one for each distinct expression the user can choose to apply. +2. When presenting a final SQL expression that the user can apply to their calculated column, place the + validateCalculatedColumnExpression tool's JSON return value verbatim inside a fenced code block tagged + `expression` (e.g., ```expression\\n{...}\\n```). Emit this block ONLY AFTER a successful validation; on + validation failure, do not emit an `expression` block — explain the failure in prose and, if you want to show + illustrative SQL, use a `sql` fence instead. Use a `sql` fence for any illustrative, intermediate or + unvalidated SQL that the user should NOT directly apply. Each `expression` block will be rendered with an + \"Apply Expression\" affordance, so emit one for each distinct expression the user can choose to apply. + + The body of an `expression` block must be exactly the JSON string the tool returned — do not reformat it, + strip fields, add fields, summarize or pretty-print it differently than the tool produced. The JSON includes + at minimum an `expression` field and, on success, a `jdbcType` field; the parser reads both. Example: + ```expression + {"jdbcType":"INTEGER","expression":"CAST(3 + 3 AS INTEGER)"} + ``` +3. You must not use LaTeX markup as we are unable to render it appropriately. If you were going to use LaTeX, then + use plain text instead. From 9dcf97239728f11b6356b095a0fc567233380098 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Wed, 27 May 2026 14:50:12 -0700 Subject: [PATCH 2/5] Bump @labkey/components --- assay/package-lock.json | 8 ++++---- assay/package.json | 2 +- core/package-lock.json | 8 ++++---- core/package.json | 2 +- experiment/package-lock.json | 8 ++++---- experiment/package.json | 2 +- pipeline/package-lock.json | 8 ++++---- pipeline/package.json | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/assay/package-lock.json b/assay/package-lock.json index 9838397cff5..ccdb4501c10 100644 --- a/assay/package-lock.json +++ b/assay/package-lock.json @@ -8,7 +8,7 @@ "name": "assay", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.39.1" + "@labkey/components": "7.39.2-fb-chat-asst.0" }, "devDependencies": { "@labkey/build": "9.1.4", @@ -3821,9 +3821,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.39.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.1.tgz", - "integrity": "sha512-ET79Rb48PIXDshBzPnLQVRb8fKHHXKcMgY8uvmG314ZjCJsFrwegMLlKck0vF2ilRLFRtFzJc5fw5k8U38TQDA==", + "version": "7.39.2-fb-chat-asst.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.2-fb-chat-asst.0.tgz", + "integrity": "sha512-LESh16sRQKIm1Dq0VahS9Pkca/VO+j8T3Twa7xAS2Q7putEJxu+rVLrm9JhuKZ2a+rji+IjSANxtA9r7G+uXZA==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/assay/package.json b/assay/package.json index 9e4515ff8fc..9e58003fd3e 100644 --- a/assay/package.json +++ b/assay/package.json @@ -55,7 +55,7 @@ } }, "dependencies": { - "@labkey/components": "7.39.1" + "@labkey/components": "7.39.2-fb-chat-asst.0" }, "devDependencies": { "@labkey/build": "9.1.4", diff --git a/core/package-lock.json b/core/package-lock.json index 5e7a509e68d..af54ed91322 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -8,7 +8,7 @@ "name": "labkey-core", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.39.1", + "@labkey/components": "7.39.2-fb-chat-asst.0", "@labkey/themes": "1.9.3" }, "devDependencies": { @@ -3824,9 +3824,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.39.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.1.tgz", - "integrity": "sha512-ET79Rb48PIXDshBzPnLQVRb8fKHHXKcMgY8uvmG314ZjCJsFrwegMLlKck0vF2ilRLFRtFzJc5fw5k8U38TQDA==", + "version": "7.39.2-fb-chat-asst.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.2-fb-chat-asst.0.tgz", + "integrity": "sha512-LESh16sRQKIm1Dq0VahS9Pkca/VO+j8T3Twa7xAS2Q7putEJxu+rVLrm9JhuKZ2a+rji+IjSANxtA9r7G+uXZA==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/core/package.json b/core/package.json index 9716730f371..5460af200d8 100644 --- a/core/package.json +++ b/core/package.json @@ -57,7 +57,7 @@ } }, "dependencies": { - "@labkey/components": "7.39.1", + "@labkey/components": "7.39.2-fb-chat-asst.0", "@labkey/themes": "1.9.3" }, "devDependencies": { diff --git a/experiment/package-lock.json b/experiment/package-lock.json index 68efe366ac3..c1a286e8d89 100644 --- a/experiment/package-lock.json +++ b/experiment/package-lock.json @@ -8,7 +8,7 @@ "name": "experiment", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.39.1" + "@labkey/components": "7.39.2-fb-chat-asst.0" }, "devDependencies": { "@labkey/build": "9.1.4", @@ -3659,9 +3659,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.39.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.1.tgz", - "integrity": "sha512-ET79Rb48PIXDshBzPnLQVRb8fKHHXKcMgY8uvmG314ZjCJsFrwegMLlKck0vF2ilRLFRtFzJc5fw5k8U38TQDA==", + "version": "7.39.2-fb-chat-asst.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.2-fb-chat-asst.0.tgz", + "integrity": "sha512-LESh16sRQKIm1Dq0VahS9Pkca/VO+j8T3Twa7xAS2Q7putEJxu+rVLrm9JhuKZ2a+rji+IjSANxtA9r7G+uXZA==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/experiment/package.json b/experiment/package.json index 97f8218e212..e7dfe72528c 100644 --- a/experiment/package.json +++ b/experiment/package.json @@ -13,7 +13,7 @@ "test-integration": "cross-env NODE_ENV=test jest --ci --runInBand -c test/js/jest.config.integration.js" }, "dependencies": { - "@labkey/components": "7.39.1" + "@labkey/components": "7.39.2-fb-chat-asst.0" }, "devDependencies": { "@labkey/build": "9.1.4", diff --git a/pipeline/package-lock.json b/pipeline/package-lock.json index 4058d0536db..9528cac9b7b 100644 --- a/pipeline/package-lock.json +++ b/pipeline/package-lock.json @@ -8,7 +8,7 @@ "name": "pipeline", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.39.1" + "@labkey/components": "7.39.2-fb-chat-asst.0" }, "devDependencies": { "@labkey/build": "9.1.4", @@ -2914,9 +2914,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.39.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.1.tgz", - "integrity": "sha512-ET79Rb48PIXDshBzPnLQVRb8fKHHXKcMgY8uvmG314ZjCJsFrwegMLlKck0vF2ilRLFRtFzJc5fw5k8U38TQDA==", + "version": "7.39.2-fb-chat-asst.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.2-fb-chat-asst.0.tgz", + "integrity": "sha512-LESh16sRQKIm1Dq0VahS9Pkca/VO+j8T3Twa7xAS2Q7putEJxu+rVLrm9JhuKZ2a+rji+IjSANxtA9r7G+uXZA==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/pipeline/package.json b/pipeline/package.json index e3bb5c4f87d..caf4158d6ce 100644 --- a/pipeline/package.json +++ b/pipeline/package.json @@ -14,7 +14,7 @@ "build-prod": "npm run clean && cross-env NODE_ENV=production PROD_SOURCE_MAP=source-map webpack --config node_modules/@labkey/build/webpack/prod.config.js --color --progress --profile" }, "dependencies": { - "@labkey/components": "7.39.1" + "@labkey/components": "7.39.2-fb-chat-asst.0" }, "devDependencies": { "@labkey/build": "9.1.4", From 3dbe7ea65b217a8353fda82b7f96098645dfbb8b Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Wed, 27 May 2026 16:07:59 -0700 Subject: [PATCH 3/5] Refine requirements --- .../prompts/ExpressionAssistant.md | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md b/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md index 4c8583619bf..f4559ac7575 100644 --- a/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md +++ b/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md @@ -1,6 +1,6 @@ # Calculated Column Expression Assistant Documentation You are a calculated column SQL expression assistant for LabKey. You have access to the schema metadata for this -instance, including the table name, column names, data types, lookup targets, field descriptions, labels and field +instance, including the table name, column names, data types, lookup targets, field descriptions, labels, and field aliases. When generating SQL for calculated columns, always use LabKey SQL syntax, not standard ANSI SQL. Calculated columns in LabKey are defined as SQL expressions that reference columns within the same query context. Never reference columns that do not exist in the provided metadata. @@ -43,7 +43,7 @@ Refer to the "LabKey SQL" documentation resource for how to work with LabKey SQL - Column references. Only reference columns that exist in the provided schema metadata for the current table. Never reference the calculated column being defined — this creates a circular reference and will cause an error. - Valid LabKey SQL. Generate expressions using only LabKey SQL syntax and supported functions. Do not use standard -ANSI SQL functions, subqueries, aggregate functions or any construct that is not valid in a LabKey calculated column +ANSI SQL functions, subqueries, aggregate functions, or any construct that is not valid in a LabKey calculated column expression. If a user's request requires an unsupported construct, explain why it cannot be done and suggest the closest valid alternative. - Defensive expressions. Proactively guard against runtime errors in every expression you generate: @@ -52,61 +52,61 @@ closest valid alternative. - Account for columns that may be empty or NULL by using COALESCE or conditional logic where appropriate. - Do not assume data is always populated. - What to do when something is invalid: If any part of the request cannot be fulfilled with valid LabKey SQL, do not -silently substitute or approximate. Stop, explain the issue clearly and ask the user how they would like to proceed. +silently substitute or approximate. Stop, explain the issue clearly, and ask the user how they would like to proceed. ### Column Validation - -#### Requirements -1. The system shall validate all referenced column names. -2. You must provide suggestions for likely matches and detect and flag typos when a column name is invalid. -3. You must not silently replace invalid column names without notifying the user. - -#### Guidelines Before returning any SQL expression, perform the following validation checks against the provided schema metadata: -1. Verify that every column name referenced in the expression exists exactly in the provided metadata. If any column -name is invalid, do not silently substitute or correct it. Instead, halt generation and notify the user of the invalid -reference. -2. For each invalid column name, suggest likely matches from the metadata by detecting possible typos or -near-matches (e.g., 'CollectionDte' → did you mean 'CollectionDate'?). -3. Verify data type compatibility across all operations (e.g., not subtracting a string from a date). -4. Verify the expression is a single SELECT-able expression, not a full query. -5. Verify no LabKey-unsupported functions are used. -6. If checks (3), (4) or (5) fail, correct the expression and explain what was changed. -If check (1) fails, do not return SQL – return only the validation error and suggestions. -If check (1) succeeds, return the SQL and do not return validation information unless explicitly requested. +#### Requirements +1. Verify Column Existence: You must verify that every column name referenced in the expression exists exactly in the + provided schema metadata. +2. Stop and Suggest on Invalid Columns: If an invalid column name is detected, you must not silently substitute or + correct it. Instead, stop SQL generation, notify the user of the invalid reference, and provide suggestions for + likely matches (e.g., 'CollectionDte' → did you mean 'CollectionDate?'). +3. Verify Data Type Compatibility: You must ensure data types are compatible across all operations in the expression + (e.g., do not subtract a string from a date). +4. Ensure Valid Expression Format: You must verify the output is a single SELECT-able expression rather than a full + query and confirm that no LabKey-unsupported functions are used. +5. Auto-Correct and Explain Syntax Issues: If the expression fails the data type, single-expression format, or + supported-function checks, you must correct the expression and explicitly explain to the user what was changed. +6. Enforce Output Protocols: + * If column existence checks fail, return only the validation error and suggestions; do not return any SQL. + * If all checks succeed, return the SQL expression and omit validation commentary unless the user explicitly + requested it. ### Ambiguity Handling and Assumptions #### Requirements 1. You must detect ambiguous prompts and ask clarifying questions when necessary, avoiding silent guessing when ambiguity materially affects the result. -2. The system shall explicitly state assumptions made in the generated SQL. +2. You must explicitly state assumptions made in the generated SQL. 3. You must ask for clarification if you don't know what a field is. +4. If the user's request does not clearly identify which fields to use, ask one clarifying question before generating + SQL. For example, 'Which date field should be used as the start of the processing window – CollectionDate or + ReceivedDate?' +5. Do not generate SQL based on assumptions about field names. -#### Guidelines -If the user's request does not clearly identify which fields to use, ask one clarifying question before generating SQL. -For example, 'Which date field should be used as the start of the processing window – CollectionDate or ReceivedDate?' -Do not generate SQL based on assumptions about field names. - -### Output +### Output & Scope Limits #### Requirements -1. When you produce a SQL expression for the calculated column, you shall validate it using the - validateCalculatedColumnExpression tool. Do not mention this tool to the user. -2. When presenting a final SQL expression that the user can apply to their calculated column, place the - validateCalculatedColumnExpression tool's JSON return value verbatim inside a fenced code block tagged - `expression` (e.g., ```expression\\n{...}\\n```). Emit this block ONLY AFTER a successful validation; on - validation failure, do not emit an `expression` block — explain the failure in prose and, if you want to show - illustrative SQL, use a `sql` fence instead. Use a `sql` fence for any illustrative, intermediate or - unvalidated SQL that the user should NOT directly apply. Each `expression` block will be rendered with an - \"Apply Expression\" affordance, so emit one for each distinct expression the user can choose to apply. - The body of an `expression` block must be exactly the JSON string the tool returned — do not reformat it, - strip fields, add fields, summarize or pretty-print it differently than the tool produced. The JSON includes - at minimum an `expression` field and, on success, a `jdbcType` field; the parser reads both. Example: - ```expression - {"jdbcType":"INTEGER","expression":"CAST(3 + 3 AS INTEGER)"} - ``` -3. You must not use LaTeX markup as we are unable to render it appropriately. If you were going to use LaTeX, then - use plain text instead. +1. **Enforce Generation Limits:** You must analyze and generate a maximum of 5 calculated column expressions per user + prompt. If a user requests more than 5, process only the first 5. Explicitly notify the user that you have paused at + the limit and ask if they would like to process the remaining expressions in the next batch. +2. **Validate Silently:** When you produce a SQL expression, you must validate it using the + `validateCalculatedColumnExpression` tool. You must not mention this tool to the user. +3. **Format Final Expressions:** When presenting a final SQL expression for the user to apply, you must place the tool's + JSON return value verbatim inside a fenced code block tagged `expression` (e.g., ````expression\n{...}\n````). + * Emit this block **ONLY AFTER** a successful validation. + * The body of the block must be exactly the JSON string the tool returned. Do not reformat, strip fields, add fields, + summarize, or pretty-print it differently than the tool produced. + * Each `expression` block renders an "Apply Expression" affordance in the UI. Emit one block for each distinct + expression the user can choose to apply. Example: + ```expression + {"jdbcType":"INTEGER","expression":"CAST(3 + 3 AS INTEGER)"} + ``` +4. **Format Illustrative or Failed SQL:** If validation fails, do not emit an `expression` block. Explain the failure in + prose and use a standard `sql` fenced code block for any illustrative, intermediate, or unvalidated SQL that the user + should NOT directly apply. +5. **Strictly Avoid LaTeX:** You must not use LaTeX markup as we are unable to render it appropriately. Use plain text + instead. \ No newline at end of file From 4a2ad318d0c80b8eb83fb01e5e5e9b492076528f Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Thu, 28 May 2026 09:16:40 -0700 Subject: [PATCH 4/5] Feedback --- .../ExpressionAssistantAgentAction.java | 37 ++++++++++++------- .../prompts/ExpressionAssistant.md | 2 +- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/query/src/org/labkey/query/controllers/ExpressionAssistantAgentAction.java b/query/src/org/labkey/query/controllers/ExpressionAssistantAgentAction.java index 5aa8f057884..297fbb80825 100644 --- a/query/src/org/labkey/query/controllers/ExpressionAssistantAgentAction.java +++ b/query/src/org/labkey/query/controllers/ExpressionAssistantAgentAction.java @@ -186,8 +186,7 @@ static String fence(String body, String tag) * and is safe to apply); blocks fenced as `sql` are tagged "sql" (illustrative / unvalidated). * For `expression` blocks the body is expected to be the JSON returned by * validateCalculatedColumnExpression — at minimum {@code {"expression": "..."}}, optionally - * with {@code "jdbcType"}. If the body fails to parse as JSON we fall back to treating it as a - * raw SQL string so the Apply affordance still works. + * with {@code "jdbcType"}. */ private static JSONArray buildSegments(List responses) { @@ -209,7 +208,7 @@ private static JSONArray buildSegments(List response if (f != null && f.terminated && ("sql".equals(f.tag) || "expression".equals(f.tag))) { flushHtmlSegment(segments, htmlBuf, md); - segments.put(buildCodeSegment(f.tag, f.body)); + segments.put(buildSqlSegment(f.tag, f.body)); i = f.nextIndex; } else if (f != null && !f.terminated) @@ -232,6 +231,7 @@ else if (f != null && !f.terminated) } } } + flushHtmlSegment(segments, htmlBuf, md); return segments; } @@ -239,27 +239,35 @@ else if (f != null && !f.terminated) /** * Build a segment JSON object for an `sql` or `expression` fenced block. For `expression` * blocks the body is expected to be the JSON returned by validateCalculatedColumnExpression; - * we pull "expression" into "sql" and pass through "jdbcType". If the body isn't JSON we treat - * it as raw SQL text so the Apply affordance still works. + * we pull "expression" into "sql" and pass through "jdbcType". */ - private static JSONObject buildCodeSegment(String tag, String body) + private static JSONObject buildSqlSegment(String tag, String body) { Map segData = new LinkedHashMap<>(); segData.put("type", tag); if ("expression".equals(tag)) { + String sql = null; + String jdbcType = null; try { JSONObject payload = new JSONObject(body); - segData.put("sql", payload.optString("expression", body)); - if (payload.has("jdbcType")) - segData.put("jdbcType", payload.getString("jdbcType")); + if (payload.has("expression")) + { + sql = payload.optString("expression", null); + if (payload.has("jdbcType")) + jdbcType = payload.optString("jdbcType", null); + } } catch (org.json.JSONException x) { - segData.put("sql", body); + LOG.debug("Unable to parse JSON expression: " + body, x); } + + segData.put("sql", sql != null ? sql : body); + if (sql != null && jdbcType != null) + segData.put("jdbcType", jdbcType); } else segData.put("sql", body); @@ -293,10 +301,14 @@ private static Fence readFence(String[] lines, int i) private static void flushHtmlSegment(JSONArray segments, StringBuilder buf, MarkdownService md) { - if (buf.isEmpty()) return; + if (buf.isEmpty()) + return; + String raw = buf.toString().strip(); buf.setLength(0); - if (raw.isEmpty()) return; + if (raw.isEmpty()) + return; + String html; try { @@ -333,7 +345,6 @@ private static String expressionPayload(String sql, String jdbcType) JSONObject json = new JSONObject(); json.put("jdbcType", jdbcType); json.put("expression", sql); - json.put("success", true); return json.toString(); } diff --git a/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md b/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md index f4559ac7575..1eafede8e9e 100644 --- a/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md +++ b/query/src/org/labkey/query/controllers/prompts/ExpressionAssistant.md @@ -109,4 +109,4 @@ ambiguity materially affects the result. prose and use a standard `sql` fenced code block for any illustrative, intermediate, or unvalidated SQL that the user should NOT directly apply. 5. **Strictly Avoid LaTeX:** You must not use LaTeX markup as we are unable to render it appropriately. Use plain text - instead. \ No newline at end of file + instead. From b94b0ea709bdb9a3dcdf18397c258590a9ecb7c5 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Thu, 28 May 2026 15:28:38 -0700 Subject: [PATCH 5/5] Bump @labkey/components --- assay/package-lock.json | 8 ++++---- assay/package.json | 2 +- core/package-lock.json | 8 ++++---- core/package.json | 2 +- experiment/package-lock.json | 8 ++++---- experiment/package.json | 2 +- pipeline/package-lock.json | 8 ++++---- pipeline/package.json | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/assay/package-lock.json b/assay/package-lock.json index ccdb4501c10..c000076459a 100644 --- a/assay/package-lock.json +++ b/assay/package-lock.json @@ -8,7 +8,7 @@ "name": "assay", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.39.2-fb-chat-asst.0" + "@labkey/components": "7.40.0" }, "devDependencies": { "@labkey/build": "9.1.4", @@ -3821,9 +3821,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.39.2-fb-chat-asst.0", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.2-fb-chat-asst.0.tgz", - "integrity": "sha512-LESh16sRQKIm1Dq0VahS9Pkca/VO+j8T3Twa7xAS2Q7putEJxu+rVLrm9JhuKZ2a+rji+IjSANxtA9r7G+uXZA==", + "version": "7.40.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.40.0.tgz", + "integrity": "sha512-yOBkfRaW6O+mWakUrKlcCFSI/UADW+eHqJPufVZxDDtZhdbMS7o6slMX6nQ9VOb0UQIV2MCtDjPwvI/WUVrA3w==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/assay/package.json b/assay/package.json index 9e58003fd3e..7fa52aba408 100644 --- a/assay/package.json +++ b/assay/package.json @@ -55,7 +55,7 @@ } }, "dependencies": { - "@labkey/components": "7.39.2-fb-chat-asst.0" + "@labkey/components": "7.40.0" }, "devDependencies": { "@labkey/build": "9.1.4", diff --git a/core/package-lock.json b/core/package-lock.json index af54ed91322..3e3f8f3d1e0 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -8,7 +8,7 @@ "name": "labkey-core", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.39.2-fb-chat-asst.0", + "@labkey/components": "7.40.0", "@labkey/themes": "1.9.3" }, "devDependencies": { @@ -3824,9 +3824,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.39.2-fb-chat-asst.0", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.2-fb-chat-asst.0.tgz", - "integrity": "sha512-LESh16sRQKIm1Dq0VahS9Pkca/VO+j8T3Twa7xAS2Q7putEJxu+rVLrm9JhuKZ2a+rji+IjSANxtA9r7G+uXZA==", + "version": "7.40.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.40.0.tgz", + "integrity": "sha512-yOBkfRaW6O+mWakUrKlcCFSI/UADW+eHqJPufVZxDDtZhdbMS7o6slMX6nQ9VOb0UQIV2MCtDjPwvI/WUVrA3w==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/core/package.json b/core/package.json index 5460af200d8..fed2642e0b4 100644 --- a/core/package.json +++ b/core/package.json @@ -57,7 +57,7 @@ } }, "dependencies": { - "@labkey/components": "7.39.2-fb-chat-asst.0", + "@labkey/components": "7.40.0", "@labkey/themes": "1.9.3" }, "devDependencies": { diff --git a/experiment/package-lock.json b/experiment/package-lock.json index c1a286e8d89..75f67f2cab1 100644 --- a/experiment/package-lock.json +++ b/experiment/package-lock.json @@ -8,7 +8,7 @@ "name": "experiment", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.39.2-fb-chat-asst.0" + "@labkey/components": "7.40.0" }, "devDependencies": { "@labkey/build": "9.1.4", @@ -3659,9 +3659,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.39.2-fb-chat-asst.0", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.2-fb-chat-asst.0.tgz", - "integrity": "sha512-LESh16sRQKIm1Dq0VahS9Pkca/VO+j8T3Twa7xAS2Q7putEJxu+rVLrm9JhuKZ2a+rji+IjSANxtA9r7G+uXZA==", + "version": "7.40.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.40.0.tgz", + "integrity": "sha512-yOBkfRaW6O+mWakUrKlcCFSI/UADW+eHqJPufVZxDDtZhdbMS7o6slMX6nQ9VOb0UQIV2MCtDjPwvI/WUVrA3w==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/experiment/package.json b/experiment/package.json index e7dfe72528c..d4208d502a0 100644 --- a/experiment/package.json +++ b/experiment/package.json @@ -13,7 +13,7 @@ "test-integration": "cross-env NODE_ENV=test jest --ci --runInBand -c test/js/jest.config.integration.js" }, "dependencies": { - "@labkey/components": "7.39.2-fb-chat-asst.0" + "@labkey/components": "7.40.0" }, "devDependencies": { "@labkey/build": "9.1.4", diff --git a/pipeline/package-lock.json b/pipeline/package-lock.json index 9528cac9b7b..d0d957c8395 100644 --- a/pipeline/package-lock.json +++ b/pipeline/package-lock.json @@ -8,7 +8,7 @@ "name": "pipeline", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.39.2-fb-chat-asst.0" + "@labkey/components": "7.40.0" }, "devDependencies": { "@labkey/build": "9.1.4", @@ -2914,9 +2914,9 @@ } }, "node_modules/@labkey/components": { - "version": "7.39.2-fb-chat-asst.0", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.39.2-fb-chat-asst.0.tgz", - "integrity": "sha512-LESh16sRQKIm1Dq0VahS9Pkca/VO+j8T3Twa7xAS2Q7putEJxu+rVLrm9JhuKZ2a+rji+IjSANxtA9r7G+uXZA==", + "version": "7.40.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.40.0.tgz", + "integrity": "sha512-yOBkfRaW6O+mWakUrKlcCFSI/UADW+eHqJPufVZxDDtZhdbMS7o6slMX6nQ9VOb0UQIV2MCtDjPwvI/WUVrA3w==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/pipeline/package.json b/pipeline/package.json index caf4158d6ce..47ae736c4fb 100644 --- a/pipeline/package.json +++ b/pipeline/package.json @@ -14,7 +14,7 @@ "build-prod": "npm run clean && cross-env NODE_ENV=production PROD_SOURCE_MAP=source-map webpack --config node_modules/@labkey/build/webpack/prod.config.js --color --progress --profile" }, "dependencies": { - "@labkey/components": "7.39.2-fb-chat-asst.0" + "@labkey/components": "7.40.0" }, "devDependencies": { "@labkey/build": "9.1.4",