From 071bdbefff9bc783663a4de330f4d9542693e9b0 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Mon, 19 Oct 2026 12:34:38 -0700 Subject: [PATCH] fix(scout): say yes to DeepSeek models, name both paths, and point OpenCode at its guide A new person asked Scout whether DeepSeek Harness could connect and heard that DeepSeek models could not be used here. They left within two minutes. Hosted agents already run on DeepSeek V4 Flash, and OpenCode on a DeepSeek model was walked end to end the same day. - The hosted offer's description names the model a hosted turn runs on, built from the same default the runtime uses (LITELLM_CHAT_MODEL), so it follows the deployment the way the limits line does. Scout names it only when asked about models or runtime. - Scout says yes to DeepSeek models two ways (Run it here, or OpenCode on DeepSeek's API through docs/cli/opencode.md), says dsh itself can't connect yet without promising when, and stops claiming the only tools are Claude Code and Codex. Qwen Code and pi are named as not ready. - Gemini stays "can't join yet". Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Wkz5axAzCvoLEFkZ5KtkKG --- .../nativeAgents.scoutKnowledge.test.js | 56 ++++++++++++++++++- .../unit/scripts/docsSiteFacts.test.js | 5 +- backend/config/native-agents/scout.ts | 29 +++++++++- backend/services/nativeRuntimeService.ts | 9 ++- 4 files changed, 90 insertions(+), 9 deletions(-) diff --git a/backend/__tests__/unit/config/nativeAgents.scoutKnowledge.test.js b/backend/__tests__/unit/config/nativeAgents.scoutKnowledge.test.js index b5393fb465..7746c45e37 100644 --- a/backend/__tests__/unit/config/nativeAgents.scoutKnowledge.test.js +++ b/backend/__tests__/unit/config/nativeAgents.scoutKnowledge.test.js @@ -66,8 +66,25 @@ describe('Scout onboarding knowledge', () => { // Nothing on the board backs a Gemini promise. expect(prompt).not.toMatch(/coming/i); // The hosted agent's model is whatever the runtime defaults to, so the - // prompt names none. - expect(prompt).not.toMatch(/deepseek/i); + // prompt names none; the hosted offer's description carries it. + expect(prompt).not.toMatch(/v4[ -]?flash/i); + }); + + // 2026-10-19: a new person asked to connect DeepSeek Harness, heard that + // DeepSeek models could not be used here, and left within two minutes. + test('says yes to DeepSeek models and names both paths, without promising dsh', () => { + expect(prompt).toMatch(/DeepSeek models: yes, two ways\. "Run it here"/); + expect(prompt).toMatch(/their own OpenCode pointed at DeepSeek's API, through the OpenCode guide/); + expect(prompt).toMatch(/DeepSeek Harness \(dsh\), cannot connect yet; do not say when or whether it will/); + expect(prompt).toMatch(/lead with the yes: its models can work here today/); + expect(prompt).toMatch(/name it only when someone asks which model or runtime it uses/); + }); + + test('connects OpenCode through its guide, never through the connect card', () => { + expect(prompt).toMatch(/OpenCode, on a model provider they choose .* connects from the command line, not from this page/); + expect(prompt).toMatch(/do not propose a connect card for it: the card sets up Claude Code or Codex/); + expect(prompt).toMatch(/Qwen Code and pi are not ready for outside use/); + expect(prompt).toMatch(/Do not claim any other tool connects/); }); // Linux run 38982219348: a Claude Code listener cannot run on Linux, WSL or @@ -92,7 +109,7 @@ describe('Scout onboarding knowledge', () => { test('cites developer docs that exist in the repository', () => { const repo = path.resolve(__dirname, '../../../..'); - for (const doc of ['docs/agents/BUILDING_AN_AGENT.md', 'docs/api/openapi.yaml']) { + for (const doc of ['docs/agents/BUILDING_AN_AGENT.md', 'docs/api/openapi.yaml', 'docs/cli/opencode.md']) { expect(prompt).toContain(doc); expect(fs.existsSync(path.join(repo, doc))).toBe(true); } @@ -115,3 +132,36 @@ describe('Scout onboarding knowledge', () => { expect(prompt).toMatch(/with no invitation code/); }); }); + +// The hosted model reaches Scout through the offer's description, built from +// the same default a hosted turn runs on, so it follows the deployment the way +// the limits line does (#2237) instead of drifting in a prompt. +describe('the hosted offer names the model a hosted turn runs on', () => { + const offerDescription = (model) => { + const saved = process.env.LITELLM_CHAT_MODEL; + if (model === undefined) delete process.env.LITELLM_CHAT_MODEL; + else process.env.LITELLM_CHAT_MODEL = model; + let tools; + try { + jest.isolateModules(() => { + ({ TOOLS: tools } = require('../../../services/nativeRuntimeService')); + }); + } finally { + if (saved === undefined) delete process.env.LITELLM_CHAT_MODEL; + else process.env.LITELLM_CHAT_MODEL = saved; + } + return tools.find((tool) => tool.function.name === 'commonly_offer_hosted_agent').function.description; + }; + + test('names DeepSeek V4 Flash for the default model, and says it cannot be changed', () => { + const description = offerDescription(undefined); + expect(description).toMatch(/A hosted agent runs on DeepSeek V4 Flash, and the person cannot choose another model today/); + expect(description).toMatch(/name the model only when they ask which model or runtime it uses/); + }); + + test('follows LITELLM_CHAT_MODEL when the deployment changes it', () => { + const description = offerDescription('some-other-model'); + expect(description).toMatch(/A hosted agent runs on some-other-model,/); + expect(description).not.toMatch(/DeepSeek/); + }); +}); diff --git a/backend/__tests__/unit/scripts/docsSiteFacts.test.js b/backend/__tests__/unit/scripts/docsSiteFacts.test.js index 2d9196146a..6bad439e41 100644 --- a/backend/__tests__/unit/scripts/docsSiteFacts.test.js +++ b/backend/__tests__/unit/scripts/docsSiteFacts.test.js @@ -75,8 +75,9 @@ describe('docs.commonly.me says what the product does', () => { }); test('the listener claims only the adapters Scout is allowed to name', () => { - // Scout's verified facts: the listener runs Claude Code or Codex. Other - // adapters exist in the CLI but are not verified for public seats yet. + // Scout's verified facts: the connect page's listener runs Claude Code or + // Codex. OpenCode connects through its own guide (docs/cli/opencode.md), + // and the other CLI adapters are not ready for outside use. [quickstart, connect].forEach((page) => { expect(page).toMatch(/Claude Code if it\s+is\s+installed,\s+otherwise\s+Codex/); expect(page).not.toMatch(/--adapter (qwen|opencode|pi)\b/); diff --git a/backend/config/native-agents/scout.ts b/backend/config/native-agents/scout.ts index 0b9f3ae6d9..908ba3b322 100644 --- a/backend/config/native-agents/scout.ts +++ b/backend/config/native-agents/scout.ts @@ -86,7 +86,9 @@ export const scoutApp = { + 'seconds after the click. When someone wants it, answer with ' + 'commonly_offer_hosted_agent: your reply arrives with a button that opens ' + 'this choice already made. Its limits are in that tool\'s description; ' - + 'quote them from there, never from memory.\n' + + 'quote them from there, never from memory. The model it runs on is named ' + + 'there too: name it only when someone asks which model or runtime it uses, ' + + 'or asks for that model, and never lead with it.\n' + ' * "Use my own agent": their own Claude Code or Codex, on their own ' + 'computer. The page\'s "Get my connect command" button shows one command ' + 'to run in Terminal, with what it needs written above it:\n' @@ -106,6 +108,16 @@ export const scoutApp = { + 'sessions. MCP alone does NOT answer @mentions; only the listener does. ' + 'Say exactly this when someone set up MCP and asks why their agent is ' + 'silent.\n' + // Walked 2026-10-19 (CLI 0.1.108, OpenCode 1.19.3, macOS, a non-admin + // account): an attached OpenCode agent on a DeepSeek model answered a + // mention through the Commonly tool. The page offers no OpenCode path, so + // the guide is the only route. + + ' * OpenCode, on a model provider they choose (for example DeepSeek, with ' + + 'their own API key), connects from the command line, not from this page. ' + + 'The guide is docs/cli/opencode.md in the github.com/Team-Commonly/commonly ' + + 'repository; it has been walked on a Mac only. Point them to the guide and ' + + 'do not propose a connect card for it: the card sets up Claude Code or ' + + 'Codex.\n' + ' * "Add a computer": `npm i -g @commonlyai/cli@latest && commonly login ' + '&& commonly daemon register && commonly daemon install` registers their ' + 'machine once; after that they add agents to it from this page with no ' @@ -133,8 +145,19 @@ export const scoutApp = { + 'that Commonly runs, on a model Commonly picks. Gemini can\'t join yet; do ' + 'not say when or whether it will. So when someone asks for Claude and GPT ' + 'together, the answer is yes: Claude Code plus Codex.\n' - + '- The listener runs Claude Code or Codex. Do not claim it runs any other ' - + 'tool.\n' + // 2026-10-19: a new person asked to connect DeepSeek Harness and heard + // that DeepSeek models could not be used here; hosted agents already ran + // on DeepSeek, and OpenCode on DeepSeek had just been walked. + + '- DeepSeek models: yes, two ways. "Run it here" (the model is named in ' + + 'commonly_offer_hosted_agent\'s description), or their own OpenCode pointed ' + + 'at DeepSeek\'s API, through the OpenCode guide. DeepSeek\'s own command-line ' + + 'tool, DeepSeek Harness (dsh), cannot connect yet; do not say when or ' + + 'whether it will. When someone asks for it, lead with the yes: its models ' + + 'can work here today.\n' + + '- What connects as their own agent: Claude Code or Codex from the Bring ' + + 'your own page, and OpenCode through its guide. Qwen Code and pi are not ' + + 'ready for outside use; say so if asked. Do not claim any other tool ' + + 'connects.\n' + '- Developers building their own agent: Commonly agents use a small HTTP ' + 'protocol (read context, post messages, memory, events). In the ' + 'github.com/Team-Commonly/commonly repository, the guide is ' diff --git a/backend/services/nativeRuntimeService.ts b/backend/services/nativeRuntimeService.ts index 3065597f40..7872ed0dbf 100644 --- a/backend/services/nativeRuntimeService.ts +++ b/backend/services/nativeRuntimeService.ts @@ -145,6 +145,11 @@ export const HOSTED_LIMITS_LINE = `${HOSTED_FREE_CAPS.agentsPerUser} hosted ` + `agent${HOSTED_FREE_CAPS.agentsPerUser === 1 ? '' : 's'} per account and ` + `${HOSTED_FREE_CAPS.turnsPerDay} turns a day (${HOSTED_PRO_CAPS.turnsPerDay} on Pro)`; export const HOSTED_OFFER_LABEL = 'Run it here'; +// The model a hosted turn runs on. hostedNativeRunConfig leaves `model` unset, +// so it is DEFAULT_MODEL. Named here for the same reason as the limits: Scout +// answers "can I use DeepSeek?" from the deployment, not from its own copy. +const MODEL_DISPLAY_NAMES: Record = { 'deepseek-v4-flash': 'DeepSeek V4 Flash' }; +export const HOSTED_MODEL_NAME = MODEL_DISPLAY_NAMES[DEFAULT_MODEL] || DEFAULT_MODEL; // --- tool schema (OpenAI function-calling format) ------------------------- // @@ -319,7 +324,9 @@ export const TOOLS = [ + `"${HOSTED_OFFER_LABEL}" button that opens Bring your own with hosting already chosen; ` + 'one more click there and their agent is live in a few seconds. Open to every ' + 'signed-in account during beta, no invitation code needed. The current limits are ' - + `${HOSTED_LIMITS_LINE}; quote these, never a number from memory. The content is your ` + + `${HOSTED_LIMITS_LINE}; quote these, never a number from memory. A hosted agent runs ` + + `on ${HOSTED_MODEL_NAME}, and the person cannot choose another model today; name the ` + + 'model only when they ask which model or runtime it uses. The content is your ' + 'whole reply: do not post another message about it. If they already have a hosted ' + 'agent, or this instance cannot host, nothing is posted and the result says why; ' + 'tell them in your own words.',