Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 53 additions & 3 deletions backend/__tests__/unit/config/nativeAgents.scoutKnowledge.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
Expand All @@ -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/);
});
});
5 changes: 3 additions & 2 deletions backend/__tests__/unit/scripts/docsSiteFacts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
Expand Down
29 changes: 26 additions & 3 deletions backend/config/native-agents/scout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 '
Expand Down Expand Up @@ -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 '
Expand Down
9 changes: 8 additions & 1 deletion backend/services/nativeRuntimeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> = { 'deepseek-v4-flash': 'DeepSeek V4 Flash' };
export const HOSTED_MODEL_NAME = MODEL_DISPLAY_NAMES[DEFAULT_MODEL] || DEFAULT_MODEL;

// --- tool schema (OpenAI function-calling format) -------------------------
//
Expand Down Expand Up @@ -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.',
Expand Down
Loading