Allow to change the agents name + Workspace files
Rolling release / release (push) Successful in 8m2s

This commit is contained in:
2026-09-12 15:30:11 -04:00
parent 874154f39a
commit 5317576087
30 changed files with 338 additions and 174 deletions
+34
View File
@@ -0,0 +1,34 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, readFileSync, existsSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { ensureAgentWorkspace, applyAssistantName, normalizeAssistantName, templateWorkspaceDir } from '../daemon/agent-workspace.js';
const previous = process.env.XDG_DATA_HOME;
const data = mkdtempSync(path.join(tmpdir(), 'jarvis-workspace-'));
process.env.XDG_DATA_HOME = data;
test('workspace seed copies SOUL and bootstrap, then name writes IDENTITY.md', () => {
const dir = ensureAgentWorkspace({ name: 'Jarvis' });
assert.equal(existsSync(path.join(dir, 'SOUL.md')), true);
assert.equal(existsSync(path.join(dir, 'AGENTS.md')), true);
assert.equal(existsSync(path.join(dir, 'BOOTSTRAP.md')), true);
assert.match(readFileSync(path.join(dir, 'SOUL.md'), 'utf8'), /You are \*\*Jarvis\*\*/);
assert.match(readFileSync(path.join(dir, 'BOOTSTRAP.md'), 'utf8'), /first-run ritual/);
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, 'SOUL.md'), 'utf8'), /# SOUL\.md: Ada/);
const again = ensureAgentWorkspace({ name: 'Ada' });
assert.equal(again, dir);
assert.match(readFileSync(path.join(dir, 'IDENTITY.md'), 'utf8'), /\*\*Name:\*\* Ada/);
assert.equal(normalizeAssistantName(' '), 'Jarvis');
assert.match(templateWorkspaceDir(), /agent-workspace$/);
});
test('cleanup', () => {
if (previous === undefined) delete process.env.XDG_DATA_HOME;
else process.env.XDG_DATA_HOME = previous;
rmSync(data, { recursive: true, force: true });
});
+11 -4
View File
@@ -55,17 +55,24 @@ test('voice compaction does not auto-continue a new greeting', () => {
assert.match(compaction.compactReminder({ voice: true }), /Do not greet again/);
});
test('voice assemble uses only the Jarvis prompt', () => {
test('voice assemble inlines workspace identity files and skips a completed bootstrap', () => {
const sys = prompts.assemble({
personality: 'voice',
extra: VOICE_SYSTEM_PROMPT,
cwd: '/home/raven/.local/share/jarvis-qvac',
cwd: '/home/raven/.local/share/jarvis/workspace',
hostWorkspace: true,
fsRead: () => 'You are a local coding agent. Read AGENTS.md.',
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 === 'BOOTSTRAP.md') return '# completed';
return '';
},
});
assert.match(sys, /You are Jarvis/);
assert.match(sys, /SOUL\.md/);
assert.match(sys, /AGENTS\.md/);
assert.doesNotMatch(sys, /You are a local coding agent/);
assert.doesNotMatch(sys, /AGENTS.md/);
assert.doesNotMatch(sys, /## BOOTSTRAP\.md/);
});
test('LLM compact skips a two-turn voice chat', async () => {
+6
View File
@@ -2,6 +2,7 @@ import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
process.env.XDG_STATE_HOME = mkdtempSync(path.join(tmpdir(), 'jarvis-daemon-test-'));
process.env.XDG_DATA_HOME = mkdtempSync(path.join(tmpdir(), 'jarvis-daemon-data-'));
import test from 'node:test';
import assert from 'node:assert/strict';
import { VoiceStateMachine } from '../daemon/voice-state.js';
@@ -74,8 +75,13 @@ test('harness bridge caps voice shell chaining', () => {
assert.equal(bridge.options.maxTurns, 6);
assert.deepEqual(bridge.options.roots, []);
assert.deepEqual(harnessRoots('filesystem'), ['/']);
assert.match(bridge.options.cwd, /jarvis\/workspace$/);
assert.match(bridge.options.system, /You are Jarvis/);
assert.match(bridge.options.system, /SOUL\.md/);
assert.deepEqual(bridge.options.builtinTools, [
'read_file',
'write_file',
'search_replace',
'list_dir',
'grep',
'run_terminal_cmd',
+3 -2
View File
@@ -227,7 +227,7 @@ test('GNOME GI imports expose default namespaces and panel has a real menu', ()
assert.doesNotMatch(extensionSource, /DO_NOT_AUTO_START/);
assert.match(extensionSource, /DBusProxyFlags\.NONE/);
assert.match(extensionSource, /_keepSyncing/);
assert.match(extensionSource, /Jarvis is starting/);
assert.match(extensionSource, /is starting/);
assert.match(extensionSource, /\['Thinking'/);
assert.match(extensionSource, /\['ToolCall'/);
assert.match(extensionSource, /_openPopup/);
@@ -240,7 +240,8 @@ test('GNOME GI imports expose default namespaces and panel has a real menu', ()
assert.match(extensionSource, /brand\/icons\/jarvis-mark\.svg/);
assert.match(uiSource, /jarvis-panel-state/);
assert.doesNotMatch(uiSource, /style_class: 'jarvis-osd'/);
assert.match(extensionSource, /_openSettings/);
assert.match(uiSource, /setAssistantName/);
assert.match(extensionSource, /_applyAssistantName/);
assert.match(extensionSource, /PopupMenuItem\('Settings'\)/);
assert.match(extensionSource, /PopupMenuItem\('Grant desktop'\)/);
assert.doesNotMatch(uiSource, /button-press-event', \(\) => Clutter\.EVENT_STOP/);
+4
View File
@@ -157,7 +157,11 @@ test('public web search and fetch do not require confirmation', () => {
assert.equal(policy.needsPermission('hn_search', 'ask'), false);
assert.equal(policy.needsPermission('code_search', 'ask'), false);
assert.equal(policy.needsPermission('run_terminal_cmd', 'ask'), true);
assert.match(VOICE_SYSTEM_PROMPT, /Follow SOUL\.md, IDENTITY\.md, AGENTS\.md/);
assert.equal(policy.needsPermission('write_file', 'ask'), true);
assert.equal(policy.isIdentityPath('USER.md'), true);
assert.equal(policy.isIdentityPath('memory/2026-09-12.md'), true);
assert.equal(policy.isIdentityPath('/tmp/secret.txt'), false);
});
test('voice prompt tells the model not to chain extra terminal commands', () => {
+6
View File
@@ -5,6 +5,7 @@ import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { SETTINGS_FIELDS, voiceSettings } from '../daemon/voice-settings.js';
import { mkdtempSync } from 'node:fs';
import { normalizeSettings, mergeSettings, settingVisible } from '../apps/gnome-extension/[email protected]/settings-values.js';
import { TTS_PRESETS, ttsConfiguration, scaleSpeech } from '../daemon/tts-config.js';
import { QvacVoiceAdapter } from '../daemon/voice-adapters.js';
@@ -18,6 +19,8 @@ import { VoiceLoop } from '../daemon/voice-loop.js';
import { ttsLoadConfigSchema } from '../node_modules/@qvac/inference/dist/schemas/text-to-speech.js';
import * as registry from '../node_modules/@qvac/inference/dist/models/registry/models.js';
process.env.XDG_DATA_HOME ||= mkdtempSync(path.join(tmpdir(), 'jarvis-settings-data-'));
test('settings migrate aliases, retain explicit disables, and reject invalid numeric input', () => {
const settings = voiceSettings({ tts_enabled: false, voice_id: 'M3', language: 'es-MX', computer_step_budget: '50', tts_speed: 1.4 });
assert.equal(settings.ttsEnabled, false); assert.equal(settings.voiceId, 'M3'); assert.equal(settings.asrLanguage, 'es');
@@ -181,4 +184,7 @@ test('catalog defaults enable CPU wake, workspace files, and idle VRAM unload',
assert.equal(settings.fsAccess, 'workspace');
assert.equal(settings.freeVramOnIdle, true);
assert.equal(settings.wakeCommand, 'jarvis-wake-bridge');
assert.equal(settings.assistantName, 'Jarvis');
assert.equal(voiceSettings({ assistantName: ' Ada ' }).assistantName, 'Ada');
assert.equal(voiceSettings({ assistantName: '<script>' }).assistantName, 'Jarvis');
});