@@ -19,7 +19,10 @@ test('workspace seed copies SOUL and bootstrap, then name writes IDENTITY.md', (
|
||||
assert.doesNotMatch(readFileSync(path.join(dir, 'AGENTS.md'), 'utf8'), /Pip/);
|
||||
applyAssistantName(dir, 'Ada');
|
||||
assert.match(readFileSync(path.join(dir, 'IDENTITY.md'), 'utf8'), /\*\*Name:\*\* Ada/);
|
||||
assert.match(readFileSync(path.join(dir, 'IDENTITY.md'), 'utf8'), /Ada lives on this Ubuntu/);
|
||||
assert.match(readFileSync(path.join(dir, 'SOUL.md'), 'utf8'), /# SOUL\.md: Ada/);
|
||||
assert.match(readFileSync(path.join(dir, 'SOUL.md'), 'utf8'), /You are \*\*Ada\*\*/);
|
||||
assert.doesNotMatch(readFileSync(path.join(dir, 'SOUL.md'), 'utf8'), /You are \*\*Jarvis\*\*/);
|
||||
const again = ensureAgentWorkspace({ name: 'Ada' });
|
||||
assert.equal(again, dir);
|
||||
assert.match(readFileSync(path.join(dir, 'IDENTITY.md'), 'utf8'), /\*\*Name:\*\* Ada/);
|
||||
|
||||
+30
-1
@@ -1,7 +1,7 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { VOICE_SYSTEM_PROMPT } from '../skills/voice-prompt.js';
|
||||
import { VOICE_SYSTEM_PROMPT, voiceSystemPrompt } from '../skills/voice-prompt.js';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const compaction = require('../vendor/agent-harness/agent/compaction.js');
|
||||
@@ -64,6 +64,7 @@ test('voice assemble inlines workspace identity files and skips a completed boot
|
||||
fsRead: (_c, n) => {
|
||||
if (n === 'SOUL.md') return '# SOUL.md: Jarvis\nYou are Jarvis on this desktop.';
|
||||
if (n === 'AGENTS.md') return '# AGENTS.md\nFollow the workspace ritual.';
|
||||
if (n === 'PERSONA.md') return '# PERSONA.md\nBe a roast comedian.';
|
||||
if (n === 'BOOTSTRAP.md') return '# completed';
|
||||
return '';
|
||||
},
|
||||
@@ -71,10 +72,38 @@ test('voice assemble inlines workspace identity files and skips a completed boot
|
||||
assert.match(sys, /You are Jarvis/);
|
||||
assert.match(sys, /SOUL\.md/);
|
||||
assert.match(sys, /AGENTS\.md/);
|
||||
assert.doesNotMatch(sys, /## PERSONA\.md/);
|
||||
assert.doesNotMatch(sys, /Be a roast comedian/);
|
||||
assert.doesNotMatch(sys, /You are a local coding agent/);
|
||||
assert.doesNotMatch(sys, /## BOOTSTRAP\.md/);
|
||||
});
|
||||
|
||||
test('voice assemble keeps acting notes in the system prompt and skips SOUL identity files', () => {
|
||||
const extra = voiceSystemPrompt('Tony', 'Jarvis, you are acting as Tony Hinchcliffe. Never call yourself Jarvis.');
|
||||
const sys = prompts.assemble({
|
||||
personality: 'voice',
|
||||
extra,
|
||||
cwd: '/home/raven/.local/share/jarvis/workspace',
|
||||
hostWorkspace: true,
|
||||
fsRead: (_c, n) => {
|
||||
if (n === 'SOUL.md') return '# SOUL.md: Jarvis\nYou are **Jarvis**.';
|
||||
if (n === 'IDENTITY.md') return '# IDENTITY.md\nJarvis lives here.';
|
||||
if (n === 'PERSONA.md') return '# PERSONA.md\nDuplicate roast notes.';
|
||||
if (n === 'AGENTS.md') return '# AGENTS.md\nFollow the workspace ritual.';
|
||||
return '';
|
||||
},
|
||||
});
|
||||
assert.match(sys, /Your name is Tony/);
|
||||
assert.match(sys, /## Acting/);
|
||||
assert.match(sys, /Tony Hinchcliffe/);
|
||||
assert.match(sys, /AGENTS\.md/);
|
||||
assert.doesNotMatch(sys, /## SOUL\.md/);
|
||||
assert.doesNotMatch(sys, /## IDENTITY\.md/);
|
||||
assert.doesNotMatch(sys, /## PERSONA\.md/);
|
||||
assert.doesNotMatch(sys, /Duplicate roast notes/);
|
||||
assert.doesNotMatch(sys, /You are \*\*Jarvis\*\*/);
|
||||
});
|
||||
|
||||
test('LLM compact skips a two-turn voice chat', async () => {
|
||||
let called = false;
|
||||
const hist = [
|
||||
|
||||
@@ -40,6 +40,12 @@ test('typed questions barge in from SPEAKING', () => {
|
||||
test('spoken replies use harness text and strip HUD sidecars', () => {
|
||||
assert.equal(spokenReply({ ok: true, text: 'Hello there.', reason: 'stop' }), 'Hello there.');
|
||||
assert.equal(spokenReply({ text: 'Spoken. <jarvis_hud>{"title":"x","chips":[]}</jarvis_hud>' }), 'Spoken.');
|
||||
assert.equal(
|
||||
spokenReply({
|
||||
text: 'I will look that up. <tool_call><function=web_search><parameter=query>weather</parameter></function></tool_call>',
|
||||
}),
|
||||
'I will look that up.',
|
||||
);
|
||||
assert.equal(spokenReply({}), '');
|
||||
assert.equal(spokenReply('[object Object]'), '');
|
||||
});
|
||||
|
||||
@@ -160,9 +160,17 @@ test('public web search and fetch do not require confirmation', () => {
|
||||
assert.match(VOICE_SYSTEM_PROMPT, /Follow SOUL\.md, IDENTITY\.md, AGENTS\.md/);
|
||||
assert.match(VOICE_SYSTEM_PROMPT, /PERSONA\.md/);
|
||||
assert.doesNotMatch(VOICE_SYSTEM_PROMPT, /User personality notes/);
|
||||
assert.match(voiceSystemPrompt('Ada', 'Be dry. Skip filler.'), /You are Ada/);
|
||||
assert.match(voiceSystemPrompt('Ada', 'Be dry. Skip filler.'), /User personality notes/);
|
||||
assert.match(voiceSystemPrompt('Ada', 'Be dry. Skip filler.'), /Be dry\. Skip filler\./);
|
||||
assert.doesNotMatch(VOICE_SYSTEM_PROMPT, /\n## Acting\n/);
|
||||
assert.match(voiceSystemPrompt('Ada', 'Be dry. Skip filler.'), /Your name is Ada/);
|
||||
assert.match(voiceSystemPrompt('Ada', 'Be dry. Skip filler.'), /\n## Acting\nBe dry\. Skip filler\./);
|
||||
assert.match(voiceSystemPrompt('Ada', 'Be dry. Skip filler.'), /override SOUL\.md/);
|
||||
assert.doesNotMatch(voiceSystemPrompt('Ada', 'Be dry. Skip filler.'), /User personality notes/);
|
||||
assert.doesNotMatch(voiceSystemPrompt('Tony', 'Jarvis, you are acting as Tony Hinchcliffe.'), /You are Tony, a local Ubuntu GNOME voice assistant/);
|
||||
const roast = voiceSystemPrompt('Tony', 'Jarvis, you are acting as Tony Hinchcliffe, roast comedian.\nYou must never call yourself Jarvis.');
|
||||
assert.match(roast, /Your name is Tony/);
|
||||
assert.match(roast, /## Acting/);
|
||||
assert.match(roast, /never call yourself Jarvis/);
|
||||
assert.match(roast, /still speak as Tony/);
|
||||
assert.equal(policy.needsPermission('write_file', 'ask'), true);
|
||||
assert.equal(policy.isIdentityPath('USER.md'), true);
|
||||
assert.equal(policy.isIdentityPath('PERSONA.md'), true);
|
||||
@@ -207,6 +215,8 @@ test('QVAC utility tools are exposed only through the master adapter', () => {
|
||||
});
|
||||
|
||||
test('model profiles select one master model without creating another runtime', () => {
|
||||
assert.equal(profile('laptop-4gb-mm').model, 'qwen3.5-0.8b');
|
||||
assert.equal(profile('laptop-4gb-mm').vision, true);
|
||||
assert.equal(profile('laptop-8gb-mm').model, 'qwen3.5-2b');
|
||||
assert.equal(profile('laptop-8gb-mm').vision, true);
|
||||
assert.equal(profile('desktop-gpu').model, 'qwen3.5-9b');
|
||||
|
||||
@@ -36,6 +36,7 @@ test('saving one window preserves unrelated changes from another and removes con
|
||||
assert.deepEqual(merged, { ttsSpeed: 1.5, unrelated: 'kept', ttsEnabled: true });
|
||||
assert.equal(normalizeSettings(merged, SETTINGS_FIELDS).ttsSpeed, 1.5);
|
||||
assert.equal(voiceSettings({ modelProfile: 'laptop-8gb-mm' }).modelProfile, 'laptop-8gb-mm');
|
||||
assert.equal(voiceSettings({ modelProfile: 'laptop-4gb-mm' }).modelProfile, 'laptop-4gb-mm');
|
||||
});
|
||||
|
||||
test('every speech preset uses a real registry asset and passes the installed SDK schema', () => {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { profile } from '../daemon/model-profiles.js';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const catalog = require('../vendor/agent-harness/lib/catalog.js');
|
||||
const toolParse = require('../vendor/agent-harness/lib/tool-parse.js');
|
||||
|
||||
test('Qwen3.5 0.8B and 2B use the qwen35 tool dialect and compact-tool reminder', () => {
|
||||
assert.equal(profile('laptop-4gb-mm').model, 'qwen3.5-0.8b');
|
||||
assert.equal(profile('laptop-8gb-mm').model, 'qwen3.5-2b');
|
||||
for (const id of ['qwen3.5-0.8b', 'qwen3.5-2b']) {
|
||||
assert.equal(catalog.toolDialectFor(id), 'qwen35');
|
||||
assert.equal(catalog.findCatalogEntry(id).tools, true);
|
||||
assert.equal(catalog.isCompactToolModel(id), true);
|
||||
}
|
||||
assert.match(toolParse.FORMAT_REMINDER, /<function=TOOL_NAME>/);
|
||||
const tiny = catalog.filterToolsForModel(
|
||||
[{ name: 'web_search' }, { name: 'qvac_capability' }, { name: 'cu_drag' }],
|
||||
'qwen3.5-0.8b',
|
||||
);
|
||||
assert.deepEqual(tiny.map((t) => t.name), ['web_search']);
|
||||
});
|
||||
|
||||
test('Qwen3.5 compact models recover tool calls nested in think tags', () => {
|
||||
const tools = [{ name: 'web_search' }, { name: 'web_fetch' }];
|
||||
const thinking = `<think>
|
||||
Need a current answer.
|
||||
<tool_call>
|
||||
<function=web_search>
|
||||
<parameter=query>Ubuntu 26.04 release</parameter>
|
||||
</function>
|
||||
</tool_call>
|
||||
</think>`;
|
||||
const recovered = toolParse.recover({ thinking, text: '', tools });
|
||||
assert.equal(recovered.calls.length, 1);
|
||||
assert.equal(recovered.calls[0].name, 'web_search');
|
||||
assert.equal(recovered.calls[0].arguments.query, 'Ubuntu 26.04 release');
|
||||
});
|
||||
Reference in New Issue
Block a user