From 9aa00b031723e85d2d95d3382da3025524e3484b Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Mon, 26 Oct 2026 05:04:19 -0700 Subject: [PATCH] feat(scout): post Linear list results as a kind:'ui' card (TASK-573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scout answered Linear list reads as bullet prose, and nothing in its prompt told it the card path existed. The prompt now has a LINEAR LIST RESULTS rule: one card with a plain-text fallback in content, the catalogue tuple taken from uiCardCatalogueRevision.json, and an example that passes the real card validator. The test pins the tuple to the live revision file and the example to the validator. Written by Tern, a Commonly agent — https://commonly.me/v2/pods/6a692a1be833c668acdb84cf Co-Authored-By: Claude Haiku 5.5 Co-authored-by: tern Agent-Model: claude/claude-haiku-5-5/default --- .../nativeAgents.scoutLinearCard.test.js | 39 +++++++++++++++++++ backend/config/native-agents/scout.ts | 16 +++++++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 backend/__tests__/unit/config/nativeAgents.scoutLinearCard.test.js diff --git a/backend/__tests__/unit/config/nativeAgents.scoutLinearCard.test.js b/backend/__tests__/unit/config/nativeAgents.scoutLinearCard.test.js new file mode 100644 index 0000000000..44213c81fe --- /dev/null +++ b/backend/__tests__/unit/config/nativeAgents.scoutLinearCard.test.js @@ -0,0 +1,39 @@ +/** + * Scout posts Linear list results as a kind:'ui' card (TASK-573, D98). + * + * The prompt is the only thing that tells Scout the card exists, so these tests + * pin three things: the catalogue tuple it quotes is the live revision file (not + * a copy that can drift), the example source it teaches passes the same + * validator a real write goes through, and the rule is present at all. + */ +const { scoutApp } = require('../../../config/native-agents/scout'); +const revision = require('../../../config/uiCardCatalogueRevision.json'); +const { validateAgentCardPayload } = require('../../../services/uiCardValidation'); + +const prompt = scoutApp.systemPrompt; + +// The example copied from the prompt, so a changed example fails here too. +const EXAMPLE_SOURCE = 'root = Card("Linear issues", [Table([Col("Issue"), Col("Status")], ' + + '[["LIN-12 Fix login", "In progress"]])])'; + +describe('Scout Linear list results as a card', () => { + it('teaches the rule with the live catalogue tuple', () => { + expect(prompt).toContain('LINEAR LIST RESULTS'); + expect(prompt).toContain(`"catalogId":"${revision.catalogId}"`); + expect(prompt).toContain(`"catalogRev":${revision.catalogRev}`); + expect(prompt).toContain(`"catalogHash":"${revision.catalogHash}"`); + }); + + it('teaches an example the card validator accepts', () => { + expect(prompt).toContain(EXAMPLE_SOURCE); + const payload = { + ...revision, kind: 'ui', v: 1, lang: 'openui@1', source: EXAMPLE_SOURCE, + }; + expect(() => validateAgentCardPayload('card', payload, 'LIN-12 Fix login (In progress)')).not.toThrow(); + }); + + it('names the card as the path for list results, with a text fallback', () => { + expect(prompt).toContain('messageType "card"'); + expect(prompt).toContain('fallback every reader sees'); + }); +}); diff --git a/backend/config/native-agents/scout.ts b/backend/config/native-agents/scout.ts index 3b63a15c02..afe953b422 100644 --- a/backend/config/native-agents/scout.ts +++ b/backend/config/native-agents/scout.ts @@ -1,5 +1,6 @@ import type { NativeAgentDefinition } from './types'; import { composePrompt } from './housePreamble'; +import UI_CARD_REVISION from '../uiCardCatalogueRevision.json'; /** * Scout — the per-user onboarding admin agent (retention plan D4; named @@ -294,7 +295,20 @@ export const scoutApp = { + '- They want Commonly to run an agent for them, or would rather not ' + 'install anything → commonly_offer_hosted_agent.\n' + '- commonly_read_context before answering questions about what happened ' - + 'here.', + + 'here.\n' + + '\n' + + 'LINEAR LIST RESULTS: when a linear.* list tool (for example linear.list_issues) ' + + 'returns issues, answer with one card, not bullet prose. Call commonly_post_message ' + + 'with messageType "card". Its content is a short plain-text list of the same ' + + 'issues, which is the fallback every reader sees. Its payload is ' + + `{"kind":"ui","v":1,"lang":"openui@1","catalogId":"${UI_CARD_REVISION.catalogId}",` + + `"catalogRev":${UI_CARD_REVISION.catalogRev},"catalogHash":"${UI_CARD_REVISION.catalogHash}",` + + '"source":""}. The source uses only the catalogue components ' + + 'Card, Table, Col and Text, in one statement set, for example: ' + + 'root = Card("Linear issues", [Table([Col("Issue"), Col("Status")], ' + + '[["LIN-12 Fix login", "In progress"]])]). Every row needs one cell per Col. ' + + 'Show at most 10 rows and say how many more there are in a Text line. Keep each ' + + 'cell under 80 characters. Copy the catalogue fields exactly.', 'Never claim the user\'s own agent is connected or working — the ' + 'connect page verifies that, not you. Point them there instead. And ' + 'remember you are the product\'s first impression: an invented product '