115 lines
5.1 KiB
JavaScript
115 lines
5.1 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createRequire } from 'node:module';
|
|
import os from 'node:os';
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const tools = require('../vendor/agent-harness/agent/tools.js');
|
|
const permRules = require('../vendor/agent-harness/agent/perm-rules.js');
|
|
const toolBudget = require('../vendor/agent-harness/agent/tool-budget.js');
|
|
|
|
test('runShell captures stdout after the process closes', async () => {
|
|
const result = await tools.runShell(os.tmpdir(), 'printf hello-jarvis');
|
|
assert.equal(result.exitCode, 0);
|
|
assert.match(result.stdout, /hello-jarvis/);
|
|
});
|
|
|
|
test('formatShellResult puts command output in front of the exit code', () => {
|
|
assert.equal(tools.formatShellResult({ exitCode: 0, stdout: 'Linux 6.8\n', stderr: '' }), 'Linux 6.8\nexit 0');
|
|
assert.equal(tools.formatShellResult({ exitCode: 0, stdout: '', stderr: '' }), '(no output)\nexit 0');
|
|
});
|
|
|
|
test('Always allow wildcard matches later shell commands', () => {
|
|
const rules = permRules.addRule([], 'run_terminal_cmd', { command: '*' }, 'allow');
|
|
assert.equal(permRules.resolve(rules, 'run_terminal_cmd', { command: 'uname -a' }), 'allow');
|
|
assert.equal(permRules.resolve(rules, 'run_terminal_cmd', { command: 'ls -la /tmp' }), 'allow');
|
|
});
|
|
|
|
test('run_terminal_cmd description still exists for the model', () => {
|
|
const def = tools.SCHEMAS.find((item) => item.name === 'run_terminal_cmd');
|
|
assert.ok(def);
|
|
assert.match(def.description, /shell command/i);
|
|
});
|
|
|
|
test('voice unfinished tool talk nudges another call', () => {
|
|
const voice = toolBudget.fromPayload({}, 'jarvis-qvac');
|
|
assert.equal(toolBudget.shouldNudgeToolCall('Let me fetch a specific lookup result for the IP address.', voice), true);
|
|
assert.equal(toolBudget.shouldNudgeToolCall('Let me search again more specifically.', voice), true);
|
|
assert.equal(toolBudget.shouldNudgeToolCall('I should search more specifically for Honeypier LLC.', voice), true);
|
|
assert.equal(toolBudget.shouldNudgeToolCall('Your public I P is 203 0 113 8.', voice), false);
|
|
assert.equal(toolBudget.shouldNudgeToolCall('Let me know if you need more.', voice), false);
|
|
toolBudget.forceAnswer(voice);
|
|
assert.equal(toolBudget.shouldNudgeToolCall('Let me fetch it.', voice), false);
|
|
});
|
|
|
|
test('DuckDuckGo search hits decode to real page URLs', () => {
|
|
assert.equal(
|
|
tools.decodeSearchUrl('//duckduckgo.com/l/?uddg=https%3A%2F%2Fwhatismyipaddress.com%2Fip-lookup&rut=abc'),
|
|
'https://whatismyipaddress.com/ip-lookup'
|
|
);
|
|
});
|
|
|
|
test('complete watch aborts after idle silence', async () => {
|
|
const watch = require('../vendor/agent-harness/lib/complete-watch.js');
|
|
const hits = [];
|
|
const w = watch.attachCompleteWatch({ idleMs: 25, abort: () => hits.push('abort') });
|
|
w.bump();
|
|
await new Promise((resolve) => setTimeout(resolve, 70));
|
|
assert.equal(w.timedOut(), true);
|
|
assert.deepEqual(hits, ['abort']);
|
|
});
|
|
|
|
test('complete watch idle is reset by bump', async () => {
|
|
const watch = require('../vendor/agent-harness/lib/complete-watch.js');
|
|
const w = watch.attachCompleteWatch({ idleMs: 80, abort() {} });
|
|
w.bump();
|
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
w.bump();
|
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
assert.equal(w.timedOut(), false);
|
|
w.clear();
|
|
});
|
|
|
|
test('repeat-loop stops a cloned spoken tail and keeps one copy', () => {
|
|
const repeatLoop = require('../vendor/agent-harness/lib/repeat-loop.js');
|
|
const unit = 'The public address is 203 0 113 8. ';
|
|
const looping = unit.repeat(6);
|
|
assert.equal(repeatLoop.isRepeating('Short answer.'), false);
|
|
assert.equal(repeatLoop.isRepeating(looping), true);
|
|
const collapsed = repeatLoop.collapseRepeats(looping);
|
|
assert.ok(collapsed.startsWith('The public address is 203 0 113 8.'));
|
|
assert.ok(collapsed.length < looping.length / 2);
|
|
});
|
|
|
|
test('html pages are stripped to text for web_fetch', () => {
|
|
const text = tools.htmlToText('<!DOCTYPE html><html><head><title>Hi</title></head><body><p>Honey peer</p></body></html>');
|
|
assert.match(text, /Honey peer/);
|
|
assert.doesNotMatch(text, /DOCTYPE|<html/i);
|
|
});
|
|
|
|
test('Jarvis voice budget runs one shell then forces an answer', () => {
|
|
const budget = toolBudget.fromPayload({}, 'jarvis-qvac');
|
|
assert.equal(budget.maxShellCalls, 1);
|
|
assert.equal(budget.maxTurns, 6);
|
|
assert.equal(budget.completeIdleMs, 10000);
|
|
assert.equal(budget.completeTimeoutMs, 45000);
|
|
assert.equal(toolBudget.shouldSkipShell(budget), false);
|
|
toolBudget.markShell(budget);
|
|
assert.equal(budget.answerOnly, true);
|
|
assert.equal(toolBudget.shouldSkipShell(budget), true);
|
|
assert.match(toolBudget.skipShellMessage(), /already ran/i);
|
|
assert.equal(
|
|
toolBudget.lastToolText([{ role: 'tool', content: 'Static hostname: nest\nexit 0' }], 80),
|
|
'Static hostname: nest exit 0'
|
|
);
|
|
});
|
|
|
|
test('coding-agent origin does not cap shell chaining', () => {
|
|
const budget = toolBudget.fromPayload({}, 'local');
|
|
assert.equal(budget.maxShellCalls, 0);
|
|
toolBudget.markShell(budget);
|
|
toolBudget.markShell(budget);
|
|
assert.equal(toolBudget.shouldSkipShell(budget), false);
|
|
assert.equal(budget.answerOnly, false);
|
|
});
|