sync: update from private repo (dfadcd5f)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-06-23 06:38:48 +00:00
parent 6a2f2cc736
commit 29ccaf1e92
377 changed files with 31028 additions and 8994 deletions
+187
View File
@@ -162,6 +162,193 @@ describe('web tools', () => {
});
});
describe('source library routing', () => {
let workspacePath = '';
afterEach(() => {
if (workspacePath) {
fs.rmSync(workspacePath, { recursive: true, force: true });
workspacePath = '';
}
vi.unstubAllGlobals();
vi.restoreAllMocks();
});
it('DownloadFile section=source saves under source/ and appends index.jsonl', async () => {
workspacePath = makeWorkspace();
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('PNGDATA', {
status: 200,
headers: { 'content-type': 'image/png' },
})));
const result = await executeTool(
'DownloadFile',
{ url: 'https://example.com/chart.png', filename: 'chart.png', section: 'source' },
makeContext(workspacePath),
);
expect(result?.isError).toBe(false);
expect(fs.existsSync(path.join(workspacePath, 'source', 'chart.png'))).toBe(true);
const idx = fs.readFileSync(path.join(workspacePath, 'source', 'index.jsonl'), 'utf-8')
.trim().split('\n').map((l) => JSON.parse(l) as Record<string, unknown>);
expect(idx).toHaveLength(1);
expect(idx[0]?.url).toBe('https://example.com/chart.png');
expect(idx[0]?.tool).toBe('DownloadFile');
expect(idx[0]?.file).toBe('chart.png');
expect(idx[0]?.bytes).toBe(Buffer.byteLength('PNGDATA'));
});
it('DownloadFile section=input does NOT write source/index.jsonl', async () => {
workspacePath = makeWorkspace();
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('DATA', {
status: 200,
headers: { 'content-type': 'application/octet-stream' },
})));
const result = await executeTool(
'DownloadFile',
{ url: 'https://example.com/file.bin', filename: 'file.bin' },
makeContext(workspacePath),
);
expect(result?.isError).toBe(false);
expect(fs.existsSync(path.join(workspacePath, 'input', 'file.bin'))).toBe(true);
expect(fs.existsSync(path.join(workspacePath, 'source', 'index.jsonl'))).toBe(false);
});
it('WebFetch persists source .md + index entry when ctx.spaceId is set', async () => {
workspacePath = makeWorkspace();
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(
'<html><body><h1>Title</h1><p>Body content here</p></body></html>',
{ status: 200, headers: { 'content-type': 'text/html; charset=utf-8' } },
)));
const ctx: ToolContext = {
workspacePath,
editAllowed: false,
spaceId: 'space-123',
toolsConfig: { webfetchAllowedHosts: ['example.com'] },
};
const result = await executeTool('WebFetch', { url: 'https://example.com/article' }, ctx);
expect(result?.isError).toBe(false);
const idx = fs.readFileSync(path.join(workspacePath, 'source', 'index.jsonl'), 'utf-8')
.trim().split('\n').map((l) => JSON.parse(l) as Record<string, unknown>);
expect(idx).toHaveLength(1);
expect(idx[0]?.url).toBe('https://example.com/article');
expect(idx[0]?.tool).toBe('WebFetch');
const savedFile = idx[0]?.file as string;
expect(savedFile.endsWith('.md')).toBe(true);
const body = fs.readFileSync(path.join(workspacePath, 'source', savedFile), 'utf-8');
expect(body).toContain('Body content here');
});
it('WebFetch dedupes the same URL within a space (no second source file)', async () => {
workspacePath = makeWorkspace();
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(
'<html><body><p>Stuff</p></body></html>',
{ status: 200, headers: { 'content-type': 'text/html' } },
)));
const ctx: ToolContext = {
workspacePath,
editAllowed: false,
spaceId: 'space-1',
toolsConfig: { webfetchAllowedHosts: ['example.com'] },
};
await executeTool('WebFetch', { url: 'https://example.com/dup' }, ctx);
await executeTool('WebFetch', { url: 'https://example.com/dup' }, ctx);
const idx = fs.readFileSync(path.join(workspacePath, 'source', 'index.jsonl'), 'utf-8')
.trim().split('\n').filter(Boolean);
expect(idx).toHaveLength(1);
});
it('WebFetch does NOT create source/ when ctx.spaceId is undefined (legacy)', async () => {
workspacePath = makeWorkspace();
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(
'<html><body><p>Non-space content</p></body></html>',
{ status: 200, headers: { 'content-type': 'text/html' } },
)));
const result = await executeTool('WebFetch', { url: 'https://example.com/page' }, makeContext(workspacePath));
expect(result?.isError).toBe(false);
expect(result?.output).toContain('Non-space content');
expect(fs.existsSync(path.join(workspacePath, 'source'))).toBe(false);
});
it('WebFetch saves FULL clean markdown to source/ but returns TRUNCATED text to the agent', async () => {
workspacePath = makeWorkspace();
// 本文を 10k 文字を大きく超える長さにして、source(全文) と agent(切り詰め) の差を作る。
const longBody = 'word '.repeat(6000); // ~30k chars
const html = `<html><head><title>Long Article</title></head><body>
<nav>NAV MENU LINKS</nav>
<article><h1>Long Article Heading</h1><p>${longBody}</p></article>
<footer>FOOTER JUNK</footer>
</body></html>`;
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(
html, { status: 200, headers: { 'content-type': 'text/html; charset=utf-8' } },
)));
const ctx: ToolContext = {
workspacePath,
editAllowed: false,
spaceId: 'space-long',
toolsConfig: { webfetchAllowedHosts: ['example.com'] },
};
const result = await executeTool('WebFetch', { url: 'https://example.com/long' }, ctx);
expect(result?.isError).toBe(false);
// agent への返却は切り詰め + source 案内ノート
expect(result!.output.length).toBeLessThan(11000);
expect(result!.output).toContain('full version saved to source/');
// クリーン抽出されているので boilerplate は出ない
expect(result!.output).not.toContain('NAV MENU LINKS');
expect(result!.output).not.toContain('FOOTER JUNK');
// source/ には全文(agent 返却より長い)が保存される
const idx = fs.readFileSync(path.join(workspacePath, 'source', 'index.jsonl'), 'utf-8')
.trim().split('\n').map((l) => JSON.parse(l) as Record<string, unknown>);
expect(idx).toHaveLength(1);
const savedFile = idx[0]?.file as string;
const saved = fs.readFileSync(path.join(workspacePath, 'source', savedFile), 'utf-8');
expect(saved.length).toBeGreaterThan(20000);
expect(saved.length).toBeGreaterThan(result!.output.length);
expect(saved).not.toContain('NAV MENU LINKS');
});
it('WebFetch (non-space) returns clean truncated text without a source note', async () => {
workspacePath = makeWorkspace();
const longBody = 'lorem '.repeat(6000);
const html = `<html><head><title>NS</title></head><body>
<article><h1>Heading</h1><p>${longBody}</p></article></body></html>`;
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(
html, { status: 200, headers: { 'content-type': 'text/html' } },
)));
const result = await executeTool('WebFetch', { url: 'https://example.com/ns' }, makeContext(workspacePath));
expect(result?.isError).toBe(false);
expect(result!.output.length).toBeLessThan(11000);
expect(result!.output).toContain('... (truncated)');
expect(result!.output).not.toContain('saved to source');
expect(fs.existsSync(path.join(workspacePath, 'source'))).toBe(false);
});
it('WebFetch falls back to naive extraction on non-article HTML (never hard-fails)', async () => {
workspacePath = makeWorkspace();
// readability が本文を見つけられない最小 HTML
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(
'<html><body>bare text with no article structure</body></html>',
{ status: 200, headers: { 'content-type': 'text/html' } },
)));
const result = await executeTool('WebFetch', { url: 'https://example.com/bare' }, makeContext(workspacePath));
expect(result?.isError).toBe(false);
expect(result!.output).toContain('bare text');
});
});
describe('parseSearchResultsFromText', () => {
it('extracts URLs with surrounding text', () => {
const text = [