Consolidate Odysseus agent harness and tool contracts

This commit is contained in:
pewdiepie-archdaemon
2026-09-17 10:07:40 +00:00
parent 84aa9a91de
commit 218d762427
229 changed files with 28899 additions and 1551 deletions
@@ -0,0 +1,249 @@
const { test, expect } = require('@playwright/test');
const { openBlankEditor, editorState, dragOnCanvas } = require('./helpers');
test.beforeEach(async ({ page }) => {
// The isolated editor server has no logged-in notification owner.
await page.route('**/api/tasks/notification-logs*', route => route.fulfill({
json: { logs: [], notifications: [] },
}));
});
test('top toolbar stays on one row and its menus remain clickable at narrow widths', async ({ page }) => {
await openBlankEditor(page, { width: 320, height: 240 });
const tip = page.getByRole('button', { name: 'Got it', exact: true });
if (await tip.isVisible()) await tip.click();
for (const width of [1280, 900, 600, 390]) {
await page.setViewportSize({ width, height: 850 });
await expect.poll(() => page.locator('.ge-topbar').evaluate(bar => {
const left = bar.querySelector('.ge-topbar-left').getBoundingClientRect();
const right = bar.querySelector('.ge-topbar-right').getBoundingClientRect();
return Math.abs((left.top + left.bottom) / 2 - (right.top + right.bottom) / 2);
})).toBeLessThan(2);
await page.locator('#ge-save-menu-btn').scrollIntoViewIfNeeded();
await expect(page.locator('#ge-save-menu-btn')).toHaveCount(1);
await expect(page.locator('#ge-draft-status')).toHaveCount(1);
const status = page.locator('#ge-save-menu-btn > #ge-draft-status');
await expect(status).toBeVisible();
await expect(page.locator('#ge-save-menu-btn .ge-save-state-icon:not([hidden])')).toHaveCount(1);
await expect(status).toBeVisible();
await expect(page.locator('.ge-topbar-right > #ge-draft-status')).toHaveCount(0);
await page.locator('#ge-image-menu-btn').click();
await expect(page.locator('#ge-image-menu')).toBeVisible();
await page.locator('[data-image-action="rotate-90"]').click();
await expect(page.locator('#ge-image-menu')).toBeHidden();
await page.screenshot({ path: `/tmp/editor-toolbar-${width}.png` });
}
});
test('committed paint refreshes the layer thumbnail and flashes only the edited row', async ({ page }) => {
await openBlankEditor(page, { width: 320, height: 240 });
const activeId = (await editorState(page)).activeLayerId;
const row = page.locator(`.ge-layer-item[data-layer-id="${activeId}"]`);
const before = await row.locator('.ge-layer-inline-thumb').evaluate(canvas => canvas.toDataURL());
await page.keyboard.press('b');
await dragOnCanvas(page, { x: 0.2, y: 0.2 }, { x: 0.7, y: 0.7 });
await expect(row).toHaveClass(/ge-layer-action-flash/);
expect(await row.locator('.ge-layer-inline-thumb').evaluate(canvas => canvas.toDataURL())).not.toBe(before);
await expect(page.locator('.ge-layer-action-flash')).toHaveCount(1);
await expect(row).not.toHaveClass(/ge-layer-action-flash/, { timeout: 2000 });
});
test('filter Escape restores pixels and preserves redo history', async ({ page }) => {
await openBlankEditor(page, { width: 320, height: 240 });
await page.keyboard.press('b');
await dragOnCanvas(page, { x: 0.2, y: 0.2 }, { x: 0.7, y: 0.7 });
await dragOnCanvas(page, { x: 0.7, y: 0.2 }, { x: 0.2, y: 0.7 });
await page.keyboard.press('Control+z');
const before = await editorState(page);
await page.locator('#ge-filter-menu-btn').click();
await page.locator('[data-filter-action="blur-gaussian"]').click();
await expect(page.locator('.ge-filter-modal')).toBeVisible();
await page.keyboard.press('Escape');
await expect(page.locator('.ge-filter-modal')).toHaveCount(0);
const after = await editorState(page);
expect(after.layers).toEqual(before.layers);
expect(after.undo).toBe(before.undo);
expect(after.redo).toBe(before.redo);
});
test('focused fields own undo, selection and clipboard without changing layers', async ({ page }) => {
await openBlankEditor(page);
await page.locator('[data-tool="text"]').click();
const box = await page.locator('.ge-main-canvas').boundingBox();
await page.mouse.click(box.x + 80, box.y + 80);
const input = page.locator('.ge-direct-text-editor');
await input.fill('Editable text');
await input.press('Control+Enter');
// A tool's ordinary text input must also retain native clipboard ownership.
await page.locator('#ge-text-frame-width').focus();
const before = await editorState(page);
await page.keyboard.press('Control+z');
await page.keyboard.press('Control+j');
await page.keyboard.press('Control+a');
await page.keyboard.press('Control+c');
await page.evaluate(() => {
document.activeElement.dispatchEvent(new ClipboardEvent('paste', { bubbles: true, cancelable: true }));
});
const after = await editorState(page);
expect(after.layers).toEqual(before.layers);
expect(after.undo).toBe(before.undo);
expect(after.selection).toEqual(before.selection);
});
test('tool keys never delete a selection and Ctrl J copies only selected pixels', async ({ page }) => {
await openBlankEditor(page, { width: 320, height: 240 });
await page.evaluate(async () => {
const { state } = await import('/static/js/editor/state.js');
const layer = state.layers.find(item => item.id === state.activeLayerId);
layer.ctx.fillStyle = '#f00';
layer.ctx.fillRect(0, 0, layer.canvas.width, layer.canvas.height);
});
await page.keyboard.press('m');
await expect(page.locator('[data-tool="marquee"]')).toHaveClass(/active/);
await dragOnCanvas(page, { x: 0.1, y: 0.1 }, { x: 0.4, y: 0.4 });
const original = await editorState(page);
await page.keyboard.press('c');
await expect(page.locator('[data-tool="crop"]')).toHaveClass(/active/);
await page.keyboard.press('d');
expect((await editorState(page)).layers).toEqual(original.layers);
expect((await editorState(page)).selection).toEqual(original.selection);
await page.keyboard.press('Control+j');
expect((await editorState(page)).layers).toHaveLength(original.layers.length + 1);
const copiedAlpha = await page.evaluate(async () => {
const { state } = await import('/static/js/editor/state.js');
const layer = state.layers.find(item => item.id === state.activeLayerId);
return [layer.ctx.getImageData(50, 50, 1, 1).data[3], layer.ctx.getImageData(250, 180, 1, 1).data[3]];
});
expect(copiedAlpha).toEqual([255, 0]);
await page.keyboard.press('Control+z');
expect((await editorState(page)).layers).toEqual(original.layers);
await page.keyboard.press('Control+d');
expect((await editorState(page)).selection).toBeNull();
await page.keyboard.press('s');
await expect(page.locator('[data-tool="clone"]')).toHaveClass(/active/);
});
test('Shift Alt intersects a marquee and reopening does not duplicate shortcuts', async ({ page }) => {
await openBlankEditor(page, { width: 320, height: 240 });
await page.evaluate(async () => {
const editor = await import('/static/js/galleryEditor.js');
await editor.openEditor(null, null, { w: 320, h: 240 }, 'Reopened');
});
await page.keyboard.press('m');
await dragOnCanvas(page, { x: 0.1, y: 0.1 }, { x: 0.6, y: 0.6 });
await page.keyboard.down('Shift');
await page.keyboard.down('Alt');
await dragOnCanvas(page, { x: 0.4, y: 0.4 }, { x: 0.9, y: 0.9 });
await page.keyboard.up('Alt');
await page.keyboard.up('Shift');
const selection = (await editorState(page)).selection.bounds;
expect(selection.x).toBeGreaterThanOrEqual(127);
expect(selection.width).toBeLessThanOrEqual(65);
const before = await editorState(page);
await page.keyboard.press('Control+j');
expect((await editorState(page)).layers.length).toBe(before.layers.length + 1);
});
test('cut removes selected pixels without creating a layer and undo restores them', async ({ page }) => {
await openBlankEditor(page, { width: 320, height: 240 });
await page.evaluate(async () => {
const { state } = await import('/static/js/editor/state.js');
const layer = state.layers.find(item => item.id === state.activeLayerId);
layer.ctx.fillStyle = '#f00';
layer.ctx.fillRect(0, 0, 320, 240);
});
await page.keyboard.press('m');
await dragOnCanvas(page, { x: 0.1, y: 0.1 }, { x: 0.4, y: 0.4 });
const before = await editorState(page);
await page.keyboard.press('Control+x');
await expect.poll(async () => (await editorState(page)).selection).toBeNull();
const cut = await editorState(page);
expect(cut.layers.length).toBe(before.layers.length);
expect(cut.layers.find(layer => layer.id === before.activeLayerId).pixelHash)
.not.toBe(before.layers.find(layer => layer.id === before.activeLayerId).pixelHash);
await page.keyboard.press('Control+z');
expect((await editorState(page)).layers).toEqual(before.layers);
expect((await editorState(page)).selection).toEqual(before.selection);
});
test('selection delete and fill edit an offset mask, preserving parent pixels', async ({ page }) => {
await openBlankEditor(page, { width: 320, height: 240 });
await page.evaluate(async () => {
const { state } = await import('/static/js/editor/state.js');
const layer = state.layers.find(item => item.id === state.activeLayerId);
layer.ctx.fillStyle = '#f00';
layer.ctx.fillRect(0, 0, 320, 240);
const canvas = document.createElement('canvas');
canvas.width = 200; canvas.height = 160;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, 200, 160);
layer.masks = [{ id: 'test-mask', name: 'Mask', mode: 'layer', space: 'layer',
canvas, ctx, offset: { x: 10, y: 10 }, visible: true, linked: true }];
layer.activeMaskId = 'test-mask';
state.layerOffsets.set(layer.id, { x: 20, y: 10 });
state.color = '#ffffff';
});
await page.keyboard.press('m');
await dragOnCanvas(page, { x: 0.2, y: 0.2 }, { x: 0.4, y: 0.4 });
const before = await editorState(page);
const parentBefore = before.layers.find(layer => layer.id === before.activeLayerId);
await page.keyboard.press('Delete');
await expect.poll(async () => (await editorState(page)).selection).toBeNull();
const erased = (await editorState(page)).layers.find(layer => layer.id === before.activeLayerId);
expect(erased.pixelHash).toBe(parentBefore.pixelHash);
expect(erased.masks[0].pixelHash).not.toBe(parentBefore.masks[0].pixelHash);
await page.locator('#ge-image-menu-btn').click();
await page.locator('[data-image-action="fill"]').click();
await expect.poll(async () => (await editorState(page)).layers.find(layer => layer.id === before.activeLayerId).masks[0].pixelHash)
.toBe(parentBefore.masks[0].pixelHash);
expect((await editorState(page)).layers.find(layer => layer.id === before.activeLayerId).pixelHash).toBe(parentBefore.pixelHash);
await page.keyboard.press('Control+c');
const clipboard = await page.evaluate(async () => {
const { state } = await import('/static/js/editor/state.js');
return { pixel: [...state.internalClipboard.getContext('2d').getImageData(50, 50, 1, 1).data], offset: state.internalClipboardOffset };
});
expect(clipboard).toEqual({ pixel: [255, 255, 255, 255], offset: { x: 30, y: 20 } });
await page.evaluate(() => document.body.dispatchEvent(new ClipboardEvent('paste', { bubbles: true, cancelable: true })));
const pasted = await editorState(page);
expect(pasted.layers).toHaveLength(before.layers.length + 1);
expect(pasted.layers.find(layer => layer.id === pasted.activeLayerId).offset).toEqual({ x: 30, y: 20 });
await page.keyboard.press('Control+z');
expect((await editorState(page)).layers).toHaveLength(before.layers.length);
});
test('focus loss releases a held brush and does not resume it on return', async ({ page }) => {
await openBlankEditor(page, { width: 320, height: 240 });
await page.keyboard.press('b');
const before = await editorState(page);
const canvas = await page.locator('.ge-main-canvas').boundingBox();
await page.mouse.move(canvas.x + 50, canvas.y + 50);
await page.mouse.down();
await page.mouse.move(canvas.x + 90, canvas.y + 70, { steps: 5 });
await page.evaluate(() => window.dispatchEvent(new Event('blur')));
expect(await page.evaluate(async () => (await import('/static/js/editor/state.js')).state.drawing)).toBe(false);
const released = await editorState(page);
await page.mouse.move(canvas.x + 150, canvas.y + 100, { steps: 5 });
await page.mouse.up();
expect((await editorState(page)).layers).toEqual(released.layers);
expect((await editorState(page)).undo).toBe(before.undo + 1);
await page.keyboard.press('Control+z');
expect((await editorState(page)).layers).toEqual(before.layers);
});
test('switching away from a held brush ends one stroke and desktop reselect keeps controls open', async ({ page }) => {
await openBlankEditor(page, { width: 320, height: 240 });
await page.keyboard.press('b');
await page.keyboard.press('b');
await expect(page.locator('.ge-controls')).not.toHaveClass(/dismissed/);
const before = await editorState(page);
const canvas = await page.locator('.ge-main-canvas').boundingBox();
await page.mouse.move(canvas.x + 50, canvas.y + 50);
await page.mouse.down();
await page.mouse.move(canvas.x + 90, canvas.y + 70, { steps: 5 });
await page.keyboard.press('v');
expect(await page.evaluate(async () => (await import('/static/js/editor/state.js')).state.drawing)).toBe(false);
await page.mouse.up();
expect((await editorState(page)).undo).toBe(before.undo + 1);
await page.keyboard.press('Control+z');
expect((await editorState(page)).layers).toEqual(before.layers);
});
@@ -0,0 +1,28 @@
const { test, expect } = require('@playwright/test');
const { openBlankEditor, editorState } = require('./helpers.js');
test('paint tools offer rasterization, cancel preserves text, Enter accepts and undo restores it', async ({ page }) => {
await openBlankEditor(page);
await page.locator('.ge-tool-btn[data-tool="text"]').click();
const canvas = await page.locator('.ge-main-canvas').boundingBox();
await page.mouse.click(canvas.x + 80, canvas.y + 80);
const text = page.locator('.ge-direct-text-editor');
await text.fill('Keep editable');
await text.press('Control+Enter');
const retained = (await editorState(page)).layers.find(layer => layer.kind === 'text');
await page.locator('.ge-tool-btn[data-tool="brush"]').click();
await expect(page.locator('#styled-confirm-ok')).toHaveText('Rasterize');
await page.keyboard.press('Escape');
await expect(page.locator('#styled-confirm-overlay')).toBeHidden();
expect((await editorState(page)).layers.find(layer => layer.id === retained.id).kind).toBe('text');
await page.locator('.ge-tool-btn[data-tool="brush"]').click();
await expect(page.locator('#styled-confirm-ok')).toBeFocused();
await page.keyboard.press('Enter');
await expect(page.locator('#styled-confirm-overlay')).toBeHidden();
expect((await editorState(page)).layers.find(layer => layer.id === retained.id).kind).toBe('raster');
await page.keyboard.press('Control+z');
expect((await editorState(page)).layers.find(layer => layer.id === retained.id).kind).toBe('text');
await page.locator('.ge-tool-btn[data-tool="eraser"]').click();
await expect(page.locator('#styled-confirm-ok')).toBeVisible();
await page.keyboard.press('Escape');
});
+18
View File
@@ -0,0 +1,18 @@
[
{
"id": "hardware_aware_model_recommendation",
"kind": "cookbook_admin",
"user": "find the best model to run on my hardware",
"expect_first_tool_any": [
"app_api",
"list_models"
],
"must_answer_any": [
"Ryzen AI Max+ 395",
"96 GB",
"96GB",
"compatible",
"Qwen/Qwen3-Next-80B-A3B-Thinking"
]
}
]
@@ -0,0 +1,72 @@
from pathlib import Path
import re
ROOT = Path(__file__).resolve().parents[1]
DOCUMENT_JS = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
CHAT_JS = (ROOT / "static/js/chat.js").read_text(encoding="utf-8")
APP_JS = (ROOT / "static/app.js").read_text(encoding="utf-8")
SETTINGS_JS = (ROOT / "static/js/settings.js").read_text(encoding="utf-8")
INDEX_HTML = (ROOT / "static/index.html").read_text(encoding="utf-8")
CHAT_ROUTE = (ROOT / "routes/chat_routes.py").read_text(encoding="utf-8")
def test_visible_or_minimized_linked_document_is_sent_as_chat_context():
function = DOCUMENT_JS.split("export function getChatDocumentId()", 1)[1].split(
"export function getActiveEmailComposerContext()", 1
)[0]
assert "pane?.isConnected" in function
assert "document.body.classList.contains('doc-view')" not in function
assert "style?.display !== 'none'" in function
assert "style?.visibility !== 'hidden'" in function
assert "const id = visiblyOpen ? activeDocId : minimizedId" in function
assert "_minimizedDocId" in function
def test_browser_explicitly_reports_absent_document_context():
assert "? (documentModule?.isPanelOpen?.() ? 'visible' : 'minimized')" in CHAT_JS
assert ": 'none'" in CHAT_JS
def test_chat_and_app_share_one_document_module_instance():
chat_version = re.search(r"from './document\.js\?v=([^']+)'", CHAT_JS).group(1)
app_version = re.search(r"from './js/document\.js\?v=([^']+)'", APP_JS).group(1)
assert chat_version == app_version
def test_all_runtime_document_imports_share_one_module_url():
runtime_files = [
ROOT / "static/app.js",
ROOT / "static/index.html",
ROOT / "static/sw.js",
ROOT / "static/js/chat.js",
ROOT / "static/js/chatStream.js",
ROOT / "static/js/chatRenderer.js",
ROOT / "static/js/emailLibrary.js",
ROOT / "static/js/slashCommands.js",
]
versions = {
match
for path in runtime_files
for match in re.findall(r"document\.js\?v=([A-Za-z0-9_-]+)", path.read_text(encoding="utf-8"))
}
assert versions == {"20260916docctx2"}
def test_server_fallback_is_legacy_only_when_ui_state_is_absent():
assert "legacy_active_doc_fallback = not active_doc_state" in CHAT_ROUTE
assert CHAT_ROUTE.count("if not active_doc and legacy_active_doc_fallback:") == 3
def test_document_writing_action_uses_document_style_not_email_style():
assert "const generalStyle = String(generalData.document_writing_style || '').trim()" in DOCUMENT_JS
assert "emailResponse = await fetch(`/api/email/style${suffix}`" in DOCUMENT_JS
assert "GENERAL WRITING STYLE:" in DOCUMENT_JS
assert "EMAIL CONVENTIONS:" in DOCUMENT_JS
assert 'id="set-document-style"' in INDEX_HTML
assert 'id="set-document-style-extract"' in INDEX_HTML
assert "'/api/auth/settings/document-style/extract'" in SETTINGS_JS
assert "spinner.createWhirlpool(14)" in SETTINGS_JS
assert 'id="set-document-style-save"' in INDEX_HTML
assert 'M17 21v-8H7v8' in INDEX_HTML
assert "document_writing_style: styleEl.value" in SETTINGS_JS
+80
View File
@@ -1,3 +1,5 @@
import json
from src.agent_evidence import (
CompletionRequirements,
CompletionStatus,
@@ -17,6 +19,27 @@ def test_infers_only_explicit_output_or_edit_paths():
assert requirements.verifier_required is True
def test_infers_artifact_from_common_past_participle_request():
requirements = infer_completion_requirements(
"Build a digest saved to /workspace/results/ops_digest.md."
)
assert requirements.required_artifacts == (
"/workspace/results/ops_digest.md",
)
def test_inference_ignores_negated_edits_and_callable_names():
requirements = infer_completion_requirements(
"Write /workspace/results/predictions.json serialized with "
"`json.dumps(..., indent=2)`. Do not modify `segmenter.py` or `cases.jsonl`."
)
assert requirements.required_artifacts == (
"/workspace/results/predictions.json",
)
def test_inference_ignores_prose_abbreviations_that_look_like_paths():
requirements = infer_completion_requirements(
"Generate statistics, e.g. token counts and timing totals."
@@ -25,6 +48,14 @@ def test_inference_ignores_prose_abbreviations_that_look_like_paths():
assert requirements.required_artifacts == ()
def test_inference_ignores_bare_existence_helper_as_artifact():
requirements = infer_completion_requirements(
"Write /workspace/results/answer.json, then verify it with os.path.exists or ls."
)
assert requirements.required_artifacts == ("/workspace/results/answer.json",)
def test_inference_recognizes_named_output_file():
requirements = infer_completion_requirements(
"Put the implementation in a file called /workspace/worker.py."
@@ -33,6 +64,16 @@ def test_inference_recognizes_named_output_file():
assert requirements.required_artifacts == ("/workspace/worker.py",)
def test_inference_recognizes_output_into_a_single_file():
requirements = infer_completion_requirements(
"Reconcile reliable facts into a single file "
"/tmp_workspace/results/profile.md. Save only that file in "
"/tmp_workspace/results/."
)
assert requirements.required_artifacts == ("/tmp_workspace/results/profile.md",)
def test_output_directory_outranks_relative_example_filenames():
requirements = infer_completion_requirements(
"Save the results into `/tmp_workspace/results`. Save each recovered table "
@@ -477,6 +518,45 @@ def test_python_artifact_write_is_detected_against_declared_path():
assert any(event.kind == EvidenceKind.ARTIFACT_MUTATION for event in ledger.events)
def test_json_write_file_then_read_records_required_artifact_mutation():
"""Native compact tool events carry write_file arguments as JSON."""
requirements = CompletionRequirements(
required_artifacts=("/workspace/results/paper_digest.md",)
)
ledger = EvidenceLedger.from_tool_events(
[
{
"round": 1,
"tool": "write_file",
"command": json.dumps({
"path": "/workspace/results/paper_digest.md",
"content": "# Verified digest\n",
}),
"output": "wrote /workspace/results/paper_digest.md",
"exit_code": 0,
},
{
"round": 2,
"tool": "read_file",
"command": json.dumps({
"path": "/workspace/results/paper_digest.md",
}),
"output": "# Verified digest\n",
"exit_code": 0,
},
],
requirements,
)
assert ledger.evaluate().status == CompletionStatus.SATISFIED
assert any(
event.kind == EvidenceKind.ARTIFACT_MUTATION
and event.artifact_path == "/workspace/results/paper_digest.md"
for event in ledger.events
)
def test_inspect_media_export_satisfies_declared_artifact():
requirements = infer_completion_requirements("Save /workspace/frame.png")
ledger = EvidenceLedger.from_tool_events(
+53
View File
@@ -77,6 +77,59 @@ def test_external_tool_schema_is_scoped_into_model_request(monkeypatch):
assert all("reasoning_content" not in message for message in observed_messages)
def test_local_qwen_external_tool_route_preserves_assistant_reasoning_continuity(monkeypatch):
"""Qwen's reasoning parser needs the prior tool-call reasoning on replay."""
observed_messages = []
monkeypatch.setattr(agent_loop, "get_setting", lambda key, default=None: default)
monkeypatch.setattr(agent_loop, "get_mcp_manager", lambda: None)
monkeypatch.setattr(agent_loop, "estimate_tokens", lambda *args, **kwargs: 10)
monkeypatch.setattr(agent_loop, "blocked_tools_for_owner", lambda owner: set())
async def fake_stream(candidates, messages, **kwargs):
observed_messages.extend(messages)
yield 'data: {"delta": "complete"}\n\n'
yield "data: [DONE]\n\n"
monkeypatch.setattr(agent_loop, "stream_llm_with_fallback", fake_stream)
_collect(agent_loop.stream_agent_loop(
"http://127.0.0.1:19200/v1/chat/completions",
"odysseus-qwen3.5-9b-preheretic",
[
{
"role": "assistant",
"content": None,
"reasoning_content": "choose the declared lookup",
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {"name": "inspect_state", "arguments": "{}"},
}],
},
{"role": "tool", "tool_call_id": "call_1", "content": "available"},
{"role": "user", "content": "Summarize the observed state."},
],
max_rounds=1,
owner="pewds",
relevant_tools={"inspect_state"},
forced_tools={"inspect_state"},
fallbacks=[],
fallback_on_empty=False,
external_tool_schemas=[{
"type": "function",
"function": {
"name": "inspect_state",
"description": "Return current state.",
"parameters": {"type": "object", "properties": {}},
},
}],
_is_teacher_run=True,
))
replayed = [m for m in observed_messages if m.get("role") == "assistant"]
assert replayed[0]["reasoning_content"] == "choose the declared lookup"
def test_external_tool_schema_can_use_textual_transport_from_first_request(monkeypatch):
observed_tools = []
observed_messages = []
+107
View File
@@ -48,6 +48,7 @@ try:
_web_search_topic_text,
_parse_qwen_explicit_email_topic_bulk_action_request,
_email_bulk_blocks_from_search_output,
_turn_targets_active_document,
)
_IMPORTED_AGENT_LOOP = sys.modules.get("src.agent_loop")
finally:
@@ -57,6 +58,34 @@ finally:
_drop_module_if_same(_mod, _stub)
def test_what_im_writing_targets_the_visible_document():
active_document = MagicMock(
current_content="A draft that needs verification.",
title="Well,",
language="richtext",
)
def test_unrelated_fact_check_does_not_target_a_stale_visible_document():
active_document = MagicMock(
current_content="A draft left open in the editor.",
title="Old draft",
language="richtext",
)
assert not _turn_targets_active_document(
{"domains": set()},
"fact check today's stock market claim",
active_document,
)
assert _turn_targets_active_document(
{"domains": set()},
"can you fact check what Im writing",
active_document,
)
def test_import_stubs_do_not_leak_into_later_tests():
leaked = [
mod for mod, stub in _INJECTED_IMPORT_STUBS.items()
@@ -205,6 +234,51 @@ def test_web_search_normalizer_trusts_model_chosen_contextual_query():
assert _web_search_query_from_block(out) == "Gustav III Sweden dancing masquerade ball"
def test_web_search_normalizer_removes_leaked_prior_chat_prefix():
block = ToolBlock(
"web_search",
'{"query":"hello looks like testing chat latest AI news","time_filter":"week"}',
)
out = _normalize_web_search_block_query(
block,
"latest ai news?",
current_user_text="latest ai news?",
)
assert json.loads(out.content) == {
"query": "latest AI news",
"time_filter": "week",
}
def test_web_search_normalizer_keeps_refinement_after_leaked_chat_prefix():
block = ToolBlock(
"web_search",
"Hello, it looks like you're testing the chat: OpenAI Anthropic Google model release news",
)
out = _normalize_web_search_block_query(
block,
"latest ai news?",
current_user_text="latest ai news?",
)
assert _web_search_query_from_block(out) == "OpenAI Anthropic Google model release news"
def test_web_search_normalizer_keeps_legitimate_model_enrichment():
block = ToolBlock("web_search", "AI industry announcements September 2026")
out = _normalize_web_search_block_query(
block,
"latest ai news?",
current_user_text="latest ai news?",
)
assert _web_search_query_from_block(out) == "AI industry announcements September 2026"
def test_web_search_normalizer_removes_action_wrapper_and_leads_with_subject():
block = ToolBlock(
"web_search",
@@ -269,6 +343,39 @@ def test_web_search_normalizer_trusts_json_query_and_preserves_time_filter():
}
def test_web_search_followup_keeps_prior_browser_subject_anchor():
block = ToolBlock(
"web_search",
'{"query":"grilled cheese sandwich menu"}',
)
out = _normalize_web_search_block_query(
block,
(
"use google maps and find closest coffee shop in todoroki "
"which one has grilled cheese sandwich on the menu?"
),
current_user_text="which one has grilled cheese sandwich on the menu?",
)
query = json.loads(out.content)["query"].lower()
assert "todoroki" in query
assert "coffee" in query
assert "grilled cheese sandwich" in query
def test_web_search_terse_followup_still_trusts_inferred_entity():
block = ToolBlock("web_search", "Gustav III Sweden dancing masquerade ball")
out = _normalize_web_search_block_query(
block,
"which swedish king liked to dance the most can you search",
current_user_text="can you search",
)
assert _web_search_query_from_block(out) == "Gustav III Sweden dancing masquerade ball"
# ---------------------------------------------------------------------------
# _detect_admin_intent
# ---------------------------------------------------------------------------
+29
View File
@@ -158,6 +158,35 @@ def test_native_codec_records_unmatched_tool_result_as_gap():
assert trace.summary()["observation_gaps"] == ["tool_result_call_unmatched", "trace_end_unavailable"]
def test_native_codec_links_explicit_preexecution_rejection():
trace = decode_native_trace(
[
_sse({
"type": "tool_output",
"tool": "inspect_media",
"command": '{"path":"/workspace/result.png"}',
"output": "This exact successful call already returned evidence.",
"exit_code": 1,
"error": True,
"execution_attempted": False,
"blocked": True,
"round": 4,
}),
{"sse": "data: [DONE]\n\n"},
],
run_id="preexecution-rejection",
)
summary = trace.summary()
assert summary["tool_linkage_valid"] is True
assert summary["observation_gaps"] == []
[call] = [event for event in trace.events if event.kind == TraceKind.TOOL_CALL]
[result] = [event for event in trace.events if event.kind == TraceKind.TOOL_RESULT]
assert call.correlation_id == result.correlation_id
assert call.payload["execution_attempted"] is False
assert call.payload["rejected_before_execution"] is True
def test_native_codec_preserves_model_response_reference():
trace = decode_native_trace(
[
+14 -3
View File
@@ -38,7 +38,9 @@ def test_contract_work_cannot_finish_through_single_action_shortcut(families):
@pytest.mark.parametrize("policy", [None, SimpleNamespace(blocks=lambda _: False)])
def test_contract_rejection_needs_no_legacy_denial(policy):
reason = load_function("_tool_rejection_reason")
assert "outside" in reason("bash", {"bash"}, policy, contract("calendar"))
rejected = reason("bash", {"bash"}, policy, contract("calendar"))
assert "outside" in rejected
assert "Available tool for this turn: manage_calendar." in rejected
assert "disabled" in reason("bash", {"bash"}, policy)
@@ -47,7 +49,7 @@ def test_contract_rejection_needs_no_legacy_denial(policy):
("", False, False, []),
("", True, True, []),
("compact", True, True, ["manage_calendar"]),
("full", True, False, ["manage_calendar"]),
("full", True, False, ["manage_calendar", "bash"]),
])
def test_contract_schema_transport(surface, is_api, router, expected):
fn = schema_function()
@@ -58,17 +60,26 @@ def test_contract_schema_transport(surface, is_api, router, expected):
def route(surface="compact", is_api=True, router=False):
return {"mcp_schemas": [], "relevant_tools": {"bash"},
"qwen38_tool_router": router, "tool_surface": surface,
"is_api_model": is_api}
"is_api_model": is_api, "ody_qwen_finetune_model": False}
def schema_function(force_answer=False, keep_artifacts=False, guide_only=False):
namespace = {
"FUNCTION_TOOL_SCHEMAS": [
{"type": "function", "function": {"name": name}}
for name in ("manage_calendar", "bash")
],
"normalized_external_tool_schemas": [], "disabled_tools": set(),
"_pure_web_turn": False, "_native_artifact_runtime": False,
"_drop_legacy_email_alias_schemas_when_mcp_available": lambda schemas: schemas,
"_filter_route_tool_schemas": lambda schemas: schemas,
"turn_contract": contract("calendar"), "guide_only": guide_only,
"_force_answer": force_answer, "_artifact_recovery_enabled": False,
"_artifact_creation_requested": False, "_artifact_finish_nudge_sent": False,
"_artifact_finish_correction_seen": False,
"_artifact_finish_post_correction_tool_used": False,
"_artifact_finish_post_correction_mutation_seen": False,
"_artifact_finish_convergence_sent": False,
"_post_correction_verification_available": lambda **_: False,
"_force_answer_keeps_artifact_tools": lambda **_: keep_artifacts,
"_normalize_model_tool_surface": lambda value: value,
@@ -0,0 +1,80 @@
import importlib.util
from pathlib import Path
PATH = Path(__file__).parents[1] / "scripts" / "build_historical_harness_queue.py"
SPEC = importlib.util.spec_from_file_location("historical_queue", PATH)
MODULE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(MODULE)
def test_infer_family_handles_variants():
assert MODULE.infer_family("[verify-agent-contract now] typo_notes-00") == "notes"
assert MODULE.infer_family("[verify-agent-contract now] ambiguous_calendar-00") == "calendar"
assert MODULE.infer_family("[verify-agent-contract now] search_ai-11") == "search_browser"
assert MODULE.infer_family("[verify-agent-contract now] email_to_calendar_schedule-00") == "switching"
assert MODULE.infer_family("[verify-agent-contract now] browser_ikea-11") == "search_browser"
def test_build_queue_deduplicates_and_keeps_newest_source():
older = {
"source_session_id": "old", "source_name": "[verify-agent-contract x] notes-00",
"created_at": "1", "family": "notes",
"turns": [{"user": "Show notes", "assistant": "old", "metadata": {}}],
}
newer = {
**older, "source_session_id": "new", "created_at": "2",
"turns": [{"user": "Show notes", "assistant": "new", "metadata": {}}],
}
queue = MODULE.build_queue([newer, older])
assert queue["source_sessions"] == 2
assert queue["unique_flows"] == 1
assert queue["workstreams"]["replay_first"][0]["source_session_id"] == "new"
assert queue["workstreams"]["replay_first"][0]["duplicate_runs"] == 2
assert queue["source_user_turns"] == 2
assert queue["seed_count"] == 2
assert len(queue["seeds"]) == 2
def test_build_seeds_accounts_for_every_turn_and_keeps_context():
sessions = [{
"source_session_id": "s1", "source_name": "Calendar then notes",
"created_at": "1", "family": "switching",
"turns": [
{"user": "Show my calendar", "family": "calendar", "metadata": {}},
{"user": "Now my notes", "family": "notes", "metadata": {}},
{"user": "Open the first one", "family": "notes", "metadata": {}},
],
}]
seeds = MODULE.build_seeds(sessions)
assert len(seeds) == 3
assert seeds[-1]["seed_id"] == "s1:3"
assert [row["user"] for row in seeds[-1]["context"]] == [
"Show my calendar", "Now my notes", "Open the first one",
]
assert seeds[-1]["family"] == "notes"
def test_classifier_separates_harness_sft_and_backend_evidence():
harness = [{"assistant": "There is no preceding answer in this conversation.", "metadata": {}}]
harness.insert(0, {"assistant": "Done", "metadata": {}})
assert MODULE.classify(harness)[0] == "harness"
sft = [{"assistant": "No", "metadata": {"tool_events": [{
"tool": "read_email", "exit_code": 1, "error": True,
"output": "uid must be an exact identifier; placeholders are not executable",
}]}}]
assert MODULE.classify(sft)[0] == "model_sft"
backend = [{"assistant": "No", "metadata": {"tool_events": [{
"tool": "list_emails", "exit_code": 1, "error": True,
"output": "connection refused",
}]}}]
assert MODULE.classify(backend)[0] == "backend"
def test_allowed_policy_decision_is_not_a_harness_failure():
turns = [{"assistant": "Done", "metadata": {"policy_decisions": [{
"allowed": True, "reason": "allowed", "tool": "manage_notes",
}]}}]
assert MODULE.classify(turns)[0] == "replay_first"
@@ -0,0 +1,132 @@
import json
from scripts.build_odysseus_sft_repair_manifest import build_manifest
from scripts.build_odysseus_sft_repair_manifest import requires_native_workspace_tool
def test_generic_shell_version_check_is_native_workspace_only():
assert requires_native_workspace_tool({
"turns": [{"expect": "Run a shell version check and compare it."}],
})
def row(seed, verdict="fail", owner="model_sft", *, offered=True, category="unanswered_followup"):
return {
"source_seed_id": seed,
"family": "notes",
"turns": [{"user": "show notes"}],
"observed": [{
"contract": {"offered": ["manage_notes"] if offered else []},
"tool_calls": [],
}],
"judge": {
"verdict": verdict, "owner": owner, "failure_category": category,
"failed_turns": [1],
},
}
def save(path, rows, routing_experiment=None):
payload = {"results": rows}
if routing_experiment is not None:
payload["routing_experiment"] = routing_experiment
path.write_text(json.dumps(payload), encoding="utf-8")
def test_stochastic_pass_does_not_erase_clean_model_failure(tmp_path):
first, second = tmp_path / "first.json", tmp_path / "second.json"
save(first, [row("fixed"), row("model"), row("routing", owner="harness_routing")])
save(second, [row("fixed", verdict="pass"), row("absent", offered=False)])
manifest = build_manifest([first, second], set())
assert [item["source_seed_id"] for item in manifest["candidates"]] == ["fixed", "model"]
reasons = {item["source_seed_id"]: item["reason"] for item in manifest["exclusions"]}
assert reasons == {
"absent": "no_failed_turn_with_executable_tool_surface",
}
def test_verified_fix_requires_explicit_resolution(tmp_path):
path = tmp_path / "run.json"
save(path, [row("fixed")])
manifest = build_manifest([path], set(), resolved_seeds={"fixed"})
assert manifest["candidate_count"] == 0
assert manifest["exclusions"] == [{
"source_seed_id": "fixed",
"reason": "explicitly_resolved_after_verified_fix",
}]
def test_explicit_ambiguous_seed_is_excluded(tmp_path):
path = tmp_path / "run.json"
save(path, [row("ambiguous")])
manifest = build_manifest([path], {"ambiguous"})
assert manifest["candidate_count"] == 0
assert manifest["exclusions"][0]["reason"] == "explicit_ambiguous_or_defective_seed"
def test_behavior_categories_are_normalized(tmp_path):
path = tmp_path / "run.json"
save(path, [row("wrong-action", category="wrong_tool_action")])
manifest = build_manifest([path], set())
assert manifest["candidates"][0]["behavior_category"] == "tool_action_selection"
def test_behavior_categories_cover_current_reusable_failure_classes():
from scripts.build_odysseus_sft_repair_manifest import behavior_category
expected = {
"missing_required_tool_call": "required_tool_execution",
"missing_required_action": "required_tool_execution",
"unrecovered_command_failure": "tool_error_recovery",
"missing_fallback_after_empty_read": "tool_error_recovery",
"counting_error": "response_constraint_adherence",
"instruction_following": "response_constraint_adherence",
"missing_note_titles": "result_rendering",
"missing_progress_link": "result_rendering",
"suboptimal_command_selection": "tool_action_selection",
"false_success": "evidence_grounding",
"unfaithful_tool_summary": "evidence_grounding",
"skill_content_mismatch": "evidence_grounding",
}
assert {name: behavior_category(name) for name in expected} == expected
def test_uncertain_latest_run_does_not_erase_valid_failure(tmp_path):
first, second = tmp_path / "first.json", tmp_path / "second.json"
save(first, [row("seed")])
save(second, [row("seed", verdict="uncertain", owner="none")])
manifest = build_manifest([first, second], set())
assert [item["source_seed_id"] for item in manifest["candidates"]] == ["seed"]
assert manifest["ignored_nonbehavioral_rows"] == 1
def test_manifest_excludes_failures_from_a_different_routing_runtime(tmp_path):
baseline, exact = tmp_path / "baseline.json", tmp_path / "exact.json"
save(baseline, [row("baseline-only")], "baseline")
save(exact, [row("exact-runtime")], "recent_model_choice")
manifest = build_manifest(
[baseline, exact], set(), routing_experiment="recent_model_choice",
)
assert [item["source_seed_id"] for item in manifest["candidates"]] == ["exact-runtime"]
assert manifest["ignored_runtime_inputs"] == 1
def test_manifest_excludes_webui_failures_that_require_native_workspace_tools(tmp_path):
path = tmp_path / "run.json"
native = row("native-only")
native["turns"] = [{"user": "fix it", "expect": "Use edit_file on readme.md"}]
save(path, [native])
manifest = build_manifest([path], set())
assert manifest["candidate_count"] == 0
assert manifest["exclusions"] == [{
"source_seed_id": "native-only",
"reason": "requires_native_workspace_tool_on_webui_surface",
}]
+30
View File
@@ -57,3 +57,33 @@ async def test_failed_server_discovery_does_not_claim_complete_local_only_invent
result = await do_list_cached_models('{}', owner='alice')
assert result['models'][0]['repo_id'] == 'fixture/local'
assert result['exit_code'] == 1 and result['partial']
@pytest.mark.asyncio
async def test_named_server_retries_its_ssh_alias_when_saved_address_is_stale(monkeypatch):
from src import tool_implementations
monkeypatch.setattr(tool_implementations, '_internal_headers', lambda: {})
client = httpx.AsyncClient
def respond(request):
if request.url.path == '/api/cookbook/state':
return httpx.Response(200, json={'env': {'servers': [
{'name': 'Ajax', 'host': 'pewds@192.168.1.9'},
]}})
if request.url.params.get('host') == 'pewds@192.168.1.9':
return httpx.Response(200, json={
'models': [], 'error': 'ssh: connect to host 192.168.1.9 port 22: timed out',
})
if request.url.params.get('host') == 'Ajax':
return httpx.Response(200, json={'models': [{
'repo_id': 'fixture/model', 'size': '1 GB', 'nb_files': 1,
'has_incomplete': False,
}]})
raise AssertionError(f'unexpected request: {request.url}')
monkeypatch.setattr(httpx, 'AsyncClient', lambda **kwargs: client(
transport=httpx.MockTransport(respond), **kwargs))
result = await do_list_cached_models('{"host":"Ajax"}', owner='alice')
assert result['exit_code'] == 0
assert result['models'][0]['repo_id'] == 'fixture/model'
assert not result.get('scan_errors')
+19
View File
@@ -0,0 +1,19 @@
from src.agent_loop import _parse_ambiguous_calendar_date_ask_user
def test_next_month_ordinal_weekday_is_not_treated_as_missing_date() -> None:
prompt = (
"Create a calendar event on the last Wednesday of next month at "
"3:15 PM."
)
assert _parse_ambiguous_calendar_date_ask_user(prompt) is None
def test_genuinely_missing_next_month_day_still_asks_user() -> None:
prompt = "Create a dinner reservation next month at 7 PM."
tool_call = _parse_ambiguous_calendar_date_ask_user(prompt)
assert tool_call is not None
assert tool_call[0] == "ask_user"
+35
View File
@@ -81,6 +81,41 @@ async def test_update_event_dtstart_anchored_to_user_tz(tokyo_offset):
finally:
db.close()
async def test_update_event_new_start_preserves_duration_when_end_is_omitted(tokyo_offset):
from src.tool_implementations import do_manage_calendar
owner = "move-duration-" + uuid.uuid4().hex[:6]
created = await do_manage_calendar(json.dumps({
"action": "create_event",
"summary": "Dinner",
"dtstart": "2026-10-11T09:30:00",
"dtend": "2026-10-11T11:00:00",
}), owner=owner)
assert created.get("exit_code", 0) == 0, created
updated = await do_manage_calendar(json.dumps({
"action": "update_event",
"uid": created["uid"],
"dtstart": "2026-10-12T21:30:00",
}), owner=owner)
assert updated.get("exit_code", 0) == 0, updated
db = _TS()
try:
event = db.query(CalendarEvent).filter(CalendarEvent.uid == created["uid"]).first()
assert (event.dtend - event.dtstart).total_seconds() == 90 * 60
assert event.dtend > event.dtstart
finally:
db.close()
listed = await do_manage_calendar(json.dumps({
"action": "list_events",
"start": "2026-10-12T18:00:00",
"end": "2026-10-13T00:00:00",
}), owner=owner)
assert listed.get("exit_code", 0) == 0, listed
assert [event["summary"] for event in listed["events"]] == ["Dinner"]
async def test_update_event_with_time_converts_all_day_event_to_timed(tokyo_offset):
from src.tool_implementations import do_manage_calendar
+112 -1
View File
@@ -17,10 +17,17 @@ from src.action_intents import classify_tool_intent
from routes.chat_routes import _is_personal_data_search_without_web_target
from routes.chat_routes import _explicitly_denies_web_lookup
from routes.chat_routes import _contains_explicit_url_target
from routes.chat_routes import _authorizes_exact_url_fetch
from routes.chat_routes import _is_explicit_browser_automation_request
from routes.chat_routes import _is_external_discovery_request
from routes.chat_routes import _prefers_structured_document_tools
from routes.chat_routes import _has_recent_private_browser_success
from routes.chat_routes import (
_has_recent_private_browser_success,
_is_contextual_browser_followup,
)
from routes.chat_routes import _most_recent_successful_web_tool
from routes.chat_routes import _is_contextual_browser_followup
from routes.chat_routes import _is_contextual_web_followup
from src.tool_policy import (
WEB_ACCESS_TOOL_NAMES,
WEB_TOOL_NAMES,
@@ -65,6 +72,37 @@ def test_plain_pdf_url_is_retrieval_not_browser_automation():
assert _is_explicit_browser_automation_request(
"Open the page https://example.com/report and click the details link"
)
assert _is_explicit_browser_automation_request(
"Open https://example.com with the private browesr"
)
def test_authoritative_source_discovery_is_web_intent():
assert _is_external_discovery_request(
"Find the official announcement and tell me the date."
)
assert _is_external_discovery_request("Locate the press release")
assert not _is_external_discovery_request("Find my announcement note")
def test_exact_public_url_authorizes_fetch_without_broad_search():
assert _authorizes_exact_url_fetch(
"Summarize https://example.com/reports/quarterly"
)
assert not _authorizes_exact_url_fetch(
"Open https://example.com and click the details link"
)
assert not _authorizes_exact_url_fetch(
"Summarize https://youtu.be/example"
)
assert not _authorizes_exact_url_fetch(
"Do not search or fetch https://example.com/private"
)
def test_agent_loop_treats_fetch_only_contract_as_web_capable():
source = (Path(__file__).resolve().parent.parent / "src" / "agent_loop.py").read_text(encoding="utf-8")
assert 'and not turn_contract.permits("web_fetch")' in source
def test_external_paper_tables_prefer_structured_tools_over_shell():
@@ -221,6 +259,17 @@ def test_clean_private_browser_warmth_requires_typed_success():
assert not _has_recent_private_browser_success(prose_only)
def test_retry_on_that_page_is_a_contextual_browser_followup():
session = type("Session", (), {"history": [
{"role": "user", "content": "Go to example.com in the browser."},
{"role": "assistant", "content": "Opened the page."},
]})()
assert _is_contextual_browser_followup(
"Try again on that page and compare the prices.", session,
)
def test_contextual_browser_followup_recognizes_current_page_inspection():
session = type("Session", (), {"history": [{
"role": "user",
@@ -248,6 +297,51 @@ def test_clean_preview_only_offers_browser_for_explicit_or_typed_warm_turns():
assert "_native_workspace_contract and _local_browser_render_intent" in source
def test_explicit_web_fetch_is_not_erased_by_generic_browser_intent():
source = _CHAT_ROUTES.read_text(encoding="utf-8")
assert "and not set(_selected_tools or ()).intersection(" in source
assert "{'web_search', 'web_fetch'}" in source
def test_web_followup_grammar_covers_article_detail_questions():
from routes.chat_routes import _WEB_FOLLOWUP_RE
assert _WEB_FOLLOWUP_RE.fullmatch("What else did it say about Miro?")
assert _WEB_FOLLOWUP_RE.fullmatch("What did it say about pricing?")
assert _WEB_FOLLOWUP_RE.fullmatch("grab the top story and read it")
assert _WEB_FOLLOWUP_RE.fullmatch(
"now pull the page title and last-updated date off that link"
)
def test_contextual_web_followup_recognizes_referential_result_open():
session = type("Session", (), {"history": [{
"role": "user",
"content": "look up what's happening in germany rn",
}]})()
assert _is_contextual_web_followup("grab the top story and read it", session)
def test_web_followup_retains_only_latest_successful_public_web_tool():
session = type("Session", (), {"history": [
{"role": "assistant", "metadata": {"tool_events": [
{"tool": "web_search", "exit_code": 0},
{"tool": "web_fetch", "exit_code": 0},
]}},
]})()
assert _most_recent_successful_web_tool(session) == "web_fetch"
def test_failed_web_tool_is_not_retained_for_followup():
session = type("Session", (), {"history": [
{"role": "assistant", "metadata": {"tool_events": [
{"tool": "web_fetch", "exit_code": 1, "error": True},
]}},
]})()
assert _most_recent_successful_web_tool(session) is None
def test_site_navigation_forces_private_browser_with_search_enabled():
source = _CHAT_ROUTES.read_text(encoding="utf-8")
assert "visit|go\\s+to|navigate\\s+to" in source
@@ -450,6 +544,23 @@ def test_explicit_false_disables_web_despite_prompt_web_intent(message):
assert "web_fetch" in disabled
def test_trained_odysseus_route_uses_explicit_web_intent_without_ui_toggle_gate():
"""The exact trained model owns intent routing; other models retain the gate."""
source = _CHAT_ROUTES.read_text(encoding="utf-8")
assert "_clean_v3_web_intent = bool(" in source
assert "_clean_v3_route_requested" in source
assert 'and "search_browser" in _turn_capabilities' in source
assert "or _clean_v3_web_intent" in source
assert "or _contextual_web_turn_followup or _clean_v3_web_intent" in source
assert "and not _explicitly_denies_web_lookup(message)" in source
def test_browser_intent_is_promoted_to_search_browser_capability():
source = _CHAT_ROUTES.read_text(encoding="utf-8")
assert "if _use_turn_contract and _explicit_browser_intent:" in source
assert '_turn_capabilities = _turn_capabilities | {"search_browser"}' in source
def test_prompt_web_intent_enables_web_without_frontend_toggle():
"""Explicit search/web wording should expose private web tools in agent mode."""
intent = classify_tool_intent("look this up and answer with sources")
@@ -156,10 +156,10 @@ def test_successful_url_prefetch_keeps_existing_content_shape(monkeypatch):
monkeypatch.setattr(
chat_processor,
"fetch_webpage_content",
lambda url: {"success": True, "content": "page body"},
lambda url: {"success": True, "title": "Example report", "content": "page body"},
)
preface, _, _ = _processor().build_context_preface(
preface, _, web_sources = _processor().build_context_preface(
message="Read https://example.test/page",
session=SimpleNamespace(endpoint_url="", model="", headers={}),
use_web=False,
@@ -175,3 +175,8 @@ def test_successful_url_prefetch_keeps_existing_content_shape(monkeypatch):
)
assert page["metadata"]["trusted"] is False
assert "page body" in page["content"]
assert web_sources == [{
"url": "https://example.test/page",
"title": "Example report",
"acquisition": "automatic_url_fetch",
}]
@@ -17,11 +17,11 @@ async def test_malformed_text_artifact_write_uses_one_bounded_raw_body_handoff(m
{'choices': [{'delta': {'tool_calls': [{
'index': 0, 'id': 'truncated-write', 'function': {
'name': 'write_file',
'arguments': '{"path": "/workspace/output.html"',
'arguments': '{"content": "# Evidence\\n\\nA long report that was clipped',
},
}]}}]},
{'choices': [{'delta': {
'content': '<!doctype html><html><body>route</body></html>',
'content': '# Evidence\n\nComplete recovered report.',
}}]},
])
@@ -72,7 +72,10 @@ async def test_malformed_text_artifact_write_uses_one_bounded_raw_body_handoff(m
raw = [chunk async for chunk in stream_preview(
endpoint_url='http://test', model='test',
messages=[{'role': 'user', 'content': 'Create /workspace/output.html.'}],
messages=[{'role': 'user', 'content': (
"Save a concise evidence-grounded Markdown report to 'output.md' "
'with write_file and verify it with read_file.'
)}],
headers={}, turn_contract=contract, session_id='test', owner='test',
disabled_tools=set(), tool_policy=ToolPolicy(), workspace='/tmp/workspace',
client_runtime_context={
@@ -88,8 +91,8 @@ async def test_malformed_text_artifact_write_uses_one_bounded_raw_body_handoff(m
assert len(executed) == 1
assert executed[0].tool_type == 'write_file'
assert executed[0].content == (
'/workspace/output.html\n'
'<!doctype html><html><body>route</body></html>'
'output.md\n'
'# Evidence\n\nComplete recovered report.'
)
assert any(event.get('type') == 'artifact_body_handoff' for event in events)
assert any(
File diff suppressed because it is too large Load Diff
+60
View File
@@ -1,6 +1,7 @@
from routes.chat_routes import (
_clean_v3_route_for_model,
_native_runtime_requires_local_browser,
_successful_session_tool_names,
_turn_contract_enabled,
)
@@ -9,6 +10,33 @@ def test_preheretic_model_owns_clean_route_independent_of_endpoint_alias():
assert _clean_v3_route_for_model("odysseus-qwen3.5-tools-pre-heretic")
def test_trial55_base_uses_same_stable_compact_tool_runtime():
assert _clean_v3_route_for_model("odysseus-qwen3.5-heretic-trial55-base")
def test_ajax_c375_uses_same_stable_compact_tool_runtime():
assert _clean_v3_route_for_model("ajax_c375")
assert _clean_v3_route_for_model("openai/ajax_c375")
def test_any_odysseus_or_ajax_token_uses_clean_tool_runtime():
assert _clean_v3_route_for_model("Odysseus")
assert _clean_v3_route_for_model("lab/my-odysseus-experiment")
assert _clean_v3_route_for_model("lab/AJAX-trial-99")
def test_partial_name_matches_do_not_capture_regular_models():
assert not _clean_v3_route_for_model("ajaxian-model")
assert not _clean_v3_route_for_model("myodysseusfoo")
def test_explicit_schema_setting_overrides_model_name_default():
assert not _clean_v3_route_for_model("Odysseus-trial55", "full")
assert not _clean_v3_route_for_model("Ajax_c375", "none")
assert _clean_v3_route_for_model("gpt-5.5", "compact")
assert _clean_v3_route_for_model("gpt-5.5", "odysseus_compact")
def test_other_models_keep_regular_harness():
assert not _clean_v3_route_for_model("qwen35-9b-base")
assert not _clean_v3_route_for_model("")
@@ -32,6 +60,38 @@ def test_other_models_keep_separate_native_workspace_contract():
)
def test_explicit_full_schema_route_bypasses_capability_contract():
assert not _turn_contract_enabled(
exact_tool_approval=None,
runtime_surface="",
native_workspace_contract=False,
clean_v3_route=False,
full_schema_route=True,
)
def test_successfully_used_tools_stay_warm_for_the_session():
class Message:
def __init__(self, metadata):
self.metadata = metadata
class Session:
history = [
Message({"tool_events": [
{"tool": "manage_calendar", "exit_code": 0},
{"tool": "web_fetch", "status": "done"},
]}),
Message({"tool_events": [
{"tool": "manage_notes", "error": True},
{"tool": "private_browser", "status": "failed"},
]}),
]
assert _successful_session_tool_names(Session()) == {
"manage_calendar", "web_fetch",
}
def test_tui_and_exact_approval_still_bypass_routed_contract():
assert not _turn_contract_enabled(
exact_tool_approval=None,
+35 -2
View File
@@ -8,6 +8,7 @@ from src.agent_loop import (
_build_system_prompt, _contract_prompt_domains,
_contract_allows_early_completion,
_contract_allows_single_action_terminal, _contract_mutation_signature,
_request_forbids_execution_retry,
)
@@ -79,7 +80,7 @@ def test_compound_turn_cannot_finish_after_one_family():
assert _contract_allows_single_action_terminal(None)
def test_compound_write_dedupe_preserves_reads_and_distinct_writes():
def test_write_dedupe_preserves_reads_and_distinct_writes():
contract = SimpleNamespace(capabilities={"notes", "calendar"})
def signature(content, tool="manage_notes"):
return _contract_mutation_signature(SimpleNamespace(tool_type=tool, content=content), contract)
@@ -89,7 +90,39 @@ def test_compound_write_dedupe_preserves_reads_and_distinct_writes():
assert first != signature('{"action":"add","title":"Dinner"}')
assert signature('{"action":"list"}') is None
assert signature('{"action":"create","title":"Lunch"}', "manage_calendar") is not None
assert _contract_mutation_signature(SimpleNamespace(tool_type="manage_notes", content='{"action":"add"}'), None) is None
@pytest.mark.parametrize("contract", [
None,
SimpleNamespace(capabilities={"tasks"}),
])
def test_exact_mutation_dedupe_applies_to_legacy_and_single_family_turns(contract):
block = SimpleNamespace(
tool_type="manage_tasks",
content='{"action":"delete","task_id":"11111111-1111-1111-1111-111111111111"}',
)
assert _contract_mutation_signature(block, contract) == (
"manage_tasks",
'{"action":"delete","task_id":"11111111-1111-1111-1111-111111111111"}',
)
read = SimpleNamespace(tool_type="manage_tasks", content='{"action":"list"}')
assert _contract_mutation_signature(read, contract) is None
@pytest.mark.parametrize("text", [
"Run this read-only test once and report the result.",
"Execute the command one time; show stdout.",
"Run this command. Do not retry automatically.",
"Try it, but don't rerun the command again.",
])
def test_explicit_single_execution_bound_is_detected(text):
assert _request_forbids_execution_retry(text)
def test_unbounded_execution_request_does_not_invent_retry_limit():
assert not _request_forbids_execution_retry(
"Run the command and recover if it fails."
)
def test_tool_success_flags_do_not_terminate_compound_turn():
+105
View File
@@ -0,0 +1,105 @@
import importlib.util
from pathlib import Path
PATH = Path(__file__).parents[1] / "scripts" / "cook_sft_alex_conversations.py"
SPEC = importlib.util.spec_from_file_location("cook_sft", PATH)
MODULE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(MODULE)
def test_redact_removes_credentials_but_keeps_request():
text = MODULE.redact("use hf_ABCDEFGHIJKLMNOPQRSTUVWXYZ123456 and api_key=secret on 10.0.0.2")
assert "ABCDEFGHIJKLMNOPQRSTUVWXYZ" not in text
assert "secret" not in text
assert "10.0.0.2" not in text
assert "use" in text
def test_validate_flows_requires_one_known_seed_and_valid_family():
result = {"flows": [{
"source_seed_id": "s:1", "family": "notes",
"turns": [{"user": "Show notes"}, {"user": "Open the first one"}],
}, {
"source_seed_id": "other", "family": "notes",
"turns": [{"user": "x"}, {"user": "y"}],
}]}
valid = MODULE.validate_flows(result, {"s:1"})
assert list(valid) == ["s:1"]
assert valid["s:1"]["id"].startswith("sft-alex-")
def test_grounding_rejects_replacing_a_source_fixture_title():
title = "audit document 20260828_190808-document"
seed = {
"seed_id": "s:2",
"context": [
{"user": f"Create a document titled {title} with one sentence."},
{"user": f"Open {title} in the editor."},
],
"target_user": f"Open {title} in the editor.",
}
flow = {
"source_seed_id": "s:2", "family": "documents",
"turns": [{"user": "open sprint-notes"}, {"user": "is this the one?"}],
}
assert MODULE.validate_flows({"flows": [flow]}, {"s:2"}, {"s:2": seed}) == {}
def test_grounding_requires_source_creation_before_opening_private_object():
title = "audit document 20260828_190808-document"
seed = {
"seed_id": "s:3",
"context": [
{"user": f"Create a document titled {title} with one sentence."},
{"user": f"Open {title} in the editor."},
],
"target_user": f"Open {title} in the editor.",
}
flow = {
"source_seed_id": "s:3", "family": "documents",
"turns": [{"user": f"open {title}"}, {"user": "read the first line"}],
}
assert any(
issue.startswith("unestablished_private_entity:")
for issue in MODULE.grounding_issues(seed, flow)
)
def test_grounding_accepts_preserved_creation_then_open_sequence():
title = "audit document 20260828_190808-document"
seed = {
"seed_id": "s:4",
"context": [
{"user": f"Create a document titled {title} with one sentence."},
{"user": f"Open {title} in the editor."},
],
"target_user": f"Open {title} in the editor.",
}
flow = {
"source_seed_id": "s:4", "family": "documents",
"turns": [
{"user": f"make a document called {title} with one sentence"},
{"user": f"now open {title} in the editor"},
],
}
assert MODULE.grounding_issues(seed, flow) == []
def test_grounding_rejects_lookup_of_new_research_topic_before_start():
seed = {
"seed_id": "s:5",
"context": [
{"user": "List my saved research reports and find the newest SearXNG report."},
{"user": "Start a concise new research report about SearXNG privacy defaults and return its task id."},
],
"target_user": "Start a concise new research report about SearXNG privacy defaults and return its task id.",
}
flow = {
"source_seed_id": "s:5", "family": "research",
"turns": [
{"user": "do I already have anything saved on SearXNG privacy defaults?"},
{"user": "start a short new report on SearXNG privacy defaults"},
],
}
assert "lookup_before_creation:searxng privacy defaults" in MODULE.grounding_issues(seed, flow)
@@ -440,6 +440,20 @@ def test_generate_queries_rejects_meta_only_queries():
assert queries == ["Gustav III Swedish king dancing"]
def test_search_result_prioritization_drops_generic_hits_when_topic_hits_exist():
researcher = _researcher(_ActionNavigator())
results = researcher._prioritize_search_results([
{"url": "https://dictionary.example/best", "title": "Best Definition"},
{
"url": "https://example.com/local-ai",
"title": "Local AI hardware guide",
"snippet": "GPU memory and computer requirements for local models",
},
], limit=10, question="Best computer to run local AI")
assert [item["url"] for item in results] == ["https://example.com/local-ai"]
def test_action_planning_sees_recent_navigation_trace(monkeypatch):
monkeypatch.setattr("src.settings.get_setting", lambda key, default=None: True)
nav = _ActionNavigator()
+27
View File
@@ -66,3 +66,30 @@ def test_plan_prompt_carries_the_current_year():
# The base template itself stays year-agnostic; the year comes from the
# prepended context, proving the wiring (not a hard-coded prompt edit).
assert _this_year() not in RESEARCH_PLAN_PROMPT
def test_generate_queries_falls_back_to_original_question_when_model_returns_empty():
r = DeepResearcher.__new__(DeepResearcher)
r.research_plan = ""
r.queries_used = set()
r._progress = None
async def _fake_llm(messages, **kwargs):
return ""
r._llm = _fake_llm
queries = asyncio.run(r._generate_queries("latest AI news", "", 1))
assert queries == [
"latest AI news",
"latest AI news fact check",
"latest AI news reliable sources",
]
def test_small_model_detection_uses_parameter_size_in_model_title():
assert DeepResearcher._looks_like_small_local_model("Qwen-0.5B-Instruct")
assert DeepResearcher._looks_like_small_local_model("model-9B")
assert DeepResearcher._looks_like_small_local_model("model-10B")
assert not DeepResearcher._looks_like_small_local_model("model-11B")
@@ -51,3 +51,17 @@ def test_doc_update_refreshes_preview_instead_of_hidden_editor_animation():
assert refresh in body
assert body.index(refresh) < body.index(animate)
assert "_refreshMarkdownPreviewIfVisible(docId, newContent);" in body
def test_doc_update_shows_a_plain_text_diff_before_refreshing_rich_text():
body = _function_body("handleDocUpdate")
assert "const isRichTextUpdate = _isRichTextLang(docLang);" in body
assert "if (isRichTextUpdate && updatedDocForRichText)" in body
assert "_animateRichTextEdit(oldContent, newContent, updatedDocForRichText);" in body
rich_diff = _function_body("_animateRichTextEdit")
assert "_richTextContentToPlain(oldContent)" in rich_diff
assert "_richTextContentToPlain(newContent)" in rich_diff
assert "lineDiff(oldText, newText)" in rich_diff
assert "_showRichTextEditor(updatedDoc);" in rich_diff
+52
View File
@@ -0,0 +1,52 @@
"""Regression guards for document-selection references in chat bubbles."""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
RENDERER = (ROOT / "static/js/chatRenderer.js").read_text(encoding="utf-8")
DOCUMENT = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
STYLE = (ROOT / "static/style.css").read_text(encoding="utf-8")
INDEX = (ROOT / "static/index.html").read_text(encoding="utf-8")
APP = (ROOT / "static/app.js").read_text(encoding="utf-8")
def test_live_compact_document_reference_is_rendered_as_an_interactive_tag():
assert r"(?:L|lines?)\s*[\d–\-]+" in RENDERER
assert '<button type="button" class="doc-edit-tag"' in RENDERER
assert "data-doc-edit-ref" in RENDERER
def test_clicking_document_reference_restores_the_editor_selection():
assert "querySelectorAll('[data-doc-edit-ref]')" in RENDERER
assert "restoreSelectionReference" in RENDERER
assert "export async function restoreSelectionReference" in DOCUMENT
assert "restoreSelectionReference," in DOCUMENT
def test_document_reference_looks_clickable_and_has_keyboard_focus_feedback():
rule = STYLE.split(".doc-edit-tag {", 1)[1].split("}", 1)[0]
assert "cursor: pointer" in rule
assert "border:" in rule
assert "display: inline-flex" in rule
assert "border-radius: 999px" in rule
assert "line-height: 1" in rule
assert "font-size: 0.68em" in rule
assert "padding: 1px 5px" in rule
assert ".doc-edit-tag:hover" in STYLE
assert ".doc-edit-tag:focus-visible" in STYLE
def test_document_module_has_one_browser_identity_for_restore_and_chat_send():
"""Different query strings create separate JS module selection stores."""
assert "/static/js/document.js?v=20260916docctx2" in INDEX
assert "./js/document.js?v=20260916docctx2" in APP
assert "document.js?v=20260913dirtysaveicon1" not in INDEX
def test_clearing_a_rich_selection_also_resets_native_selection_stats():
clear_body = DOCUMENT.split("function clearSelection() {", 1)[1].split("\n }", 1)[0]
assert "browserSelection.removeAllRanges()" in clear_body
assert "_scheduleDocumentStats()" in clear_body
@@ -0,0 +1,14 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_document_library_export_submenu_honors_markdown_and_text_formats():
source = (ROOT / "static/js/documentLibrary.js").read_text(encoding="utf-8")
assert "function _documentExport(doc, extMap, format = 'original')" in source
assert "const exportDocumentFile = async (format = 'original')" in source
assert "_documentExport(full, extMap, format)" in source
assert "exportDocumentFile('markdown')" in source
assert "exportDocumentFile('text')" in source
+15
View File
@@ -93,6 +93,21 @@ def test_rich_text_paste_uses_document_allowlist_and_drops_embedded_media():
assert "keepLink" in paste_cleaner
def test_document_image_paste_and_drop_stop_global_chat_attachment_handlers():
rich_handlers = DOC_JS.split("function _wireEmailRichbody", 1)[1].split(
"function _richSelectionElement", 1
)[0]
markdown_handlers = DOC_JS.split("ta.addEventListener('paste'", 1)[1].split(
"ta.addEventListener('scroll'", 1
)[0]
assert "e.stopPropagation();" in rich_handlers
assert "e.stopPropagation();" in markdown_handlers
app_js = (ROOT / "static/app.js").read_text(encoding="utf-8")
assert "e.defaultPrevented || e.target?.closest?.('#doc-editor-pane, [contenteditable=\"true\"]')" in app_js
assert "e.target?.closest?.('#doc-editor-pane')" in app_js
def test_empty_table_is_preserved_and_visually_editable():
assert "rich.querySelector('img, hr, table')" in DOC_JS
assert "clone.insertRow(-1)" in DOC_JS
+69
View File
@@ -7,6 +7,7 @@ from src.agent_tools.document_tools import (
_owned_document_query,
set_active_document,
)
from types import SimpleNamespace
class _Column:
@@ -46,6 +47,7 @@ class _Query:
return self
def limit(self, *args):
self.requested_limit = args[0]
return self
def all(self):
@@ -101,6 +103,35 @@ def test_manage_documents_list_filters_to_calling_owner(monkeypatch):
assert result["documents"] == []
assert ("owner", "eq", "alice") in query.filters
assert query.requested_limit == 50
def test_manage_documents_search_broadens_to_any_term_after_no_strict_match(monkeypatch):
docs = [
SimpleNamespace(
id="tennis-doc", title="Tennis on Tuesday?", language="email",
current_content="Let's play tennis.", updated_at=None, created_at=None,
),
SimpleNamespace(
id="dinner-doc", title="Dinner plans", language="text",
current_content="Choose a dinner date.", updated_at=None, created_at=None,
),
SimpleNamespace(
id="other-doc", title="Quarterly budget", language="text",
current_content="Finance review.", updated_at=None, created_at=None,
),
]
query = _Query(docs=docs)
_install_database_stub(monkeypatch, "core.database", query)
result = asyncio.run(
TOOL_HANDLERS["manage_documents"](
'{"action":"list","search":"tennis dinner"}', {"owner": "alice"}
)
)
ids = {item["id"] for item in result["documents"]}
assert ids == {"tennis-doc", "dinner-doc"}
def test_manage_documents_read_filters_to_calling_owner(monkeypatch):
@@ -135,6 +166,44 @@ def test_update_document_active_id_filters_to_calling_owner(monkeypatch):
assert ("owner", "eq", "alice") in query.filters
def test_update_document_rejects_unchanged_content(monkeypatch):
doc = SimpleNamespace(
id="doc-alice", owner="alice", title="Draft", language="markdown",
current_content="Exactly the same.", version_count=1,
)
_install_database_stub(monkeypatch, "src.database", _Query(first_doc=doc))
set_active_document("doc-alice")
try:
result = asyncio.run(
TOOL_HANDLERS["update_document"]("Exactly the same.", {"owner": "alice"})
)
finally:
set_active_document(None)
assert result["exit_code"] == 1
assert "unchanged" in result["error"]
assert doc.version_count == 1
def test_edit_document_rejects_noop_find_replace(monkeypatch):
doc = SimpleNamespace(
id="doc-alice", owner="alice", title="Draft", language="markdown",
current_content="Exactly the same.", version_count=1,
)
_install_database_stub(monkeypatch, "src.database", _Query(first_doc=doc))
set_active_document("doc-alice")
try:
result = asyncio.run(TOOL_HANDLERS["edit_document"](
"<<<FIND>>>\nExactly the same.\n<<<REPLACE>>>\nExactly the same.\n<<<END>>>",
{"owner": "alice"},
))
finally:
set_active_document(None)
assert "No edits applied" in result["error"]
assert doc.version_count == 1
def test_suggest_document_active_id_filters_to_calling_owner(monkeypatch):
query = _Query()
_install_database_stub(monkeypatch, "src.database", query)
+46 -2
View File
@@ -137,9 +137,9 @@ async def test_scheduled_local_summary_is_preempted_by_foreground_call(monkeypat
monkeypatch.setenv("ODYSSEUS_LOCAL_MODEL_GATE", "true")
monkeypatch.setenv("BACKGROUND_TASK_FOREGROUND_GATE", "false")
monkeypatch.setattr(llm_core, "_LOCAL_MODEL_LOCK", asyncio.Lock())
monkeypatch.setattr(llm_core, "_LOCAL_MODEL_LOCKS", {})
monkeypatch.setattr(llm_core, "_LOCAL_MODEL_CURRENT", {})
monkeypatch.setattr(llm_core, "_LOCAL_MODEL_WAITING_FOREGROUND", 0)
monkeypatch.setattr(llm_core, "_LOCAL_MODEL_WAITING_FOREGROUND", {})
monkeypatch.setattr(
task_endpoint,
"resolve_task_candidates",
@@ -192,6 +192,50 @@ async def test_scheduled_local_summary_is_preempted_by_foreground_call(monkeypat
task.cancel()
@pytest.mark.asyncio
async def test_local_model_gate_serializes_per_endpoint_not_globally(monkeypatch):
import src.llm_core as llm_core
monkeypatch.setenv("ODYSSEUS_LOCAL_MODEL_GATE", "true")
monkeypatch.setattr(llm_core, "_LOCAL_MODEL_LOCKS", {})
monkeypatch.setattr(llm_core, "_LOCAL_MODEL_CURRENT", {})
monkeypatch.setattr(llm_core, "_LOCAL_MODEL_WAITING_FOREGROUND", {})
first_entered = asyncio.Event()
release_first = asyncio.Event()
second_endpoint_entered = asyncio.Event()
same_endpoint_entered = asyncio.Event()
async def hold_first():
async with llm_core._local_model_slot(
"http://127.0.0.1:19200/v1/chat/completions", "model-a"
):
first_entered.set()
await release_first.wait()
async def enter_second_endpoint():
async with llm_core._local_model_slot(
"http://127.0.0.1:19201/v1/chat/completions", "model-b"
):
second_endpoint_entered.set()
async def enter_same_endpoint():
async with llm_core._local_model_slot(
"http://127.0.0.1:19200/v1/completions", "model-a"
):
same_endpoint_entered.set()
first = asyncio.create_task(hold_first())
await asyncio.wait_for(first_entered.wait(), timeout=1)
second = asyncio.create_task(enter_second_endpoint())
same = asyncio.create_task(enter_same_endpoint())
await asyncio.wait_for(second_endpoint_entered.wait(), timeout=1)
await asyncio.sleep(0)
assert not same_endpoint_entered.is_set()
release_first.set()
await asyncio.gather(first, second, same)
assert same_endpoint_entered.is_set()
@pytest.mark.asyncio
async def test_manual_email_summary_uses_shared_helper_and_caches(tmp_path, monkeypatch):
import routes.email_helpers as email_helpers
+25
View File
@@ -0,0 +1,25 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_card_delete_waits_for_durable_success_and_uses_email_identity():
source = (ROOT / "static/js/emailLibrary.js").read_text()
assert "function _emailMutationQuery(em" in source
assert "em?.folder || fallbackFolder" in source
assert "em?.account_id || state._libAccountId" in source
assert "data?.success !== true" in source
assert source.count("await _requireSuccessfulEmailMutation(response, 'Failed to delete email');") >= 3
def test_finished_send_does_not_close_whichever_library_opened_later():
source = (ROOT / "static/js/document.js").read_text()
start = source.index("async function _sendEmail()")
end = source.index("async function _saveDraft()", start)
send = source[start:end]
# Closing the initiating composer when SMTP starts is valid. Completion
# must not close a different document/email opened while SMTP was pending.
assert send.count("if (isLibraryOpen()) closeLibrary();") == 1
success_cleanup = send.index("// Delete the compose document after successful send.")
assert "closeLibrary()" not in send[success_cleanup:]
+20
View File
@@ -0,0 +1,20 @@
import asyncio
import src.event_bus as event_bus
def test_fixture_owners_do_not_run_event_automations():
assert not event_bus._event_automation_enabled_for_owner("sft_alex_creator")
assert not event_bus._event_automation_enabled_for_owner("SFT_MAYA_OPS")
assert event_bus._event_automation_enabled_for_owner("pewds")
assert event_bus._event_automation_enabled_for_owner(None)
def test_fixture_event_returns_before_database_access(monkeypatch):
import core.database
def fail_if_opened():
raise AssertionError("fixture event must not open a database session")
monkeypatch.setattr(core.database, "SessionLocal", fail_if_opened)
asyncio.run(event_bus._handle_event("session_created", "sft_alex_creator"))
@@ -0,0 +1,14 @@
from src.agent_loop import _explicitly_named_personal_tools
def test_extracts_exact_personal_tool_names():
text = "Call manage_calendar once, then use manage_notes."
assert _explicitly_named_personal_tools(text) == {
"manage_calendar",
"manage_notes",
}
def test_does_not_match_substrings_or_generic_calendar_words():
assert _explicitly_named_personal_tools("Show my calendar") == set()
assert _explicitly_named_personal_tools("xmanage_calendar_backup") == set()
@@ -0,0 +1,77 @@
from src.turn_contract import requested_capabilities, selected_tools_for_request
def test_calendar_to_email_draft_offers_complete_read_to_draft_chain():
prompt = (
"Read my calendar Tuesday, identify the earliest free slot, and create "
"only an unsent email draft with the option. Do not change the calendar."
)
assert selected_tools_for_request(prompt) == {"manage_calendar", "draft_email"}
assert requested_capabilities(prompt) == {"calendar", "email"}
def test_email_attachment_to_draft_keeps_complete_evidence_chain():
prompt = (
"Search my inbox for the latest email from Lena, read its attachment, "
"and create an unsent draft with the calculation."
)
assert selected_tools_for_request(prompt) == {
"search_emails", "read_email", "download_attachment", "draft_email",
}
def test_exact_calendar_tool_name_overrides_file_like_event_title():
prompt = (
"Use manage_calendar to create the event 'Q3 Regulatory Filing' "
"on the last Wednesday of next month."
)
assert selected_tools_for_request(prompt) == {"manage_calendar"}
def test_multiple_exact_personal_tool_names_are_preserved():
prompt = "Search with manage_notes, then schedule with manage_tasks."
assert selected_tools_for_request(prompt) == {"manage_notes", "manage_tasks"}
def test_personal_tool_name_substrings_do_not_select_tools():
assert selected_tools_for_request("Explain xmanage_calendar_backup") is None
def test_calendar_email_calendar_chain_gets_complete_request_scoped_path():
prompt = (
"Look at my calendar for this Thursday's launch review with Priya Shah. "
"Then check for her latest email about that meeting and update the calendar "
"event to match exactly what she asks."
)
assert selected_tools_for_request(prompt) == {
"manage_calendar", "search_emails", "read_email",
}
def test_calendar_only_lookup_does_not_gain_email_tools():
selected = selected_tools_for_request(
"Look at my calendar for Thursday's launch review."
) or set()
assert not {"search_emails", "read_email"}.intersection(selected)
def test_calendar_evidence_then_email_draft_keeps_both_capabilities():
prompt = (
"Look at my calendar this week, find a free hour, then create a reviewable "
"email draft to jordan@example.com suggesting that slot. Do not send it or "
"modify my calendar."
)
assert requested_capabilities(prompt) == {"calendar", "email"}
def test_find_read_report_email_request_excludes_mutators():
prompt = (
"Find and read Priya Shah's latest email about the launch review. "
"Report the final logistics. Do not draft or send a reply."
)
assert selected_tools_for_request(prompt) == {"search_emails", "read_email"}
def test_find_read_and_reply_is_not_narrowed_to_read_only():
prompt = "Find and read Priya's latest email, then draft a reply."
assert selected_tools_for_request(prompt) != {"search_emails", "read_email"}
+12
View File
@@ -699,6 +699,18 @@ def test_multiplexed_non_destructive_actions_do_not_claim_destructive_effect(
assert ToolEffect.DESTRUCTIVE not in capabilities.effects
@pytest.mark.parametrize("tool_name,action", [
("manage_endpoints", "list"),
("manage_mcp", "list"),
("manage_mcp", "list_tools"),
("manage_tokens", "list"),
("manage_webhooks", "list"),
])
def test_admin_inventory_reads_are_classified_as_private_reads(tool_name, action):
capabilities = capabilities_for_action(tool_name, {"action": action})
assert capabilities.effects == frozenset({ToolEffect.READ_PRIVATE})
def test_ambiguous_private_manager_action_fails_high():
capabilities = capabilities_for_action("manage_notes", "not json")
+53
View File
@@ -69,6 +69,59 @@ def test_extract_text_is_workspace_confined(monkeypatch, tmp_path: Path):
assert escaped["exit_code"] == 1 and "workspace" in escaped["error"]
def test_extract_text_accepts_workspace_uri_alias_without_weakening_confinement(monkeypatch, tmp_path: Path):
Image.new("RGB", (20, 20), "white").save(tmp_path / "source.png")
monkeypatch.setattr(ocr_engine, "extract_image_text", lambda _path, **_kwargs: {
"lines": [{"t": "VISIBLE", "p": 1.0, "xy": [10.0, 10.0]}],
})
token = _active_workspace.set(str(tmp_path))
try:
result = asyncio.run(ExtractTextTool().execute(json.dumps({
"path": "odysseus://workspace/source.png",
}), {}))
escaped = asyncio.run(ExtractTextTool().execute(json.dumps({
"path": "odysseus://workspace/../outside.png",
}), {}))
finally:
_active_workspace.reset(token)
assert result["exit_code"] == 0
assert result["ocr"]["lines"][0]["t"] == "VISIBLE"
assert escaped["exit_code"] == 1
def test_extract_text_renders_and_ocr_scans_pdf_pages(monkeypatch, tmp_path: Path):
source = tmp_path / "scan.pdf"
pages = [Image.new("RGB", (40, 40), "white") for _ in range(2)]
pages[0].save(source, "PDF", save_all=True, append_images=pages[1:])
calls = []
def fake_ocr(path, **_kwargs):
calls.append(Path(path).name)
return {
"count": 1,
"returned": 1,
"truncated": False,
"lines": [{"t": "VISIBLE", "p": 1.0, "xy": [10.0, 10.0]}],
}
monkeypatch.setattr(ocr_engine, "extract_image_text", fake_ocr)
token = _active_workspace.set(str(tmp_path))
try:
result = asyncio.run(ExtractTextTool().execute(
json.dumps({"path": "/workspace/scan.pdf"}),
{},
))
finally:
_active_workspace.reset(token)
assert result["exit_code"] == 0
assert result["ocr"]["page_count"] == 2
assert result["ocr"]["pages_processed"] == 2
assert [line["page"] for line in result["ocr"]["lines"]] == [1, 2]
assert len(calls) == 2
def test_inspect_media_auto_augments_exact_text_queries_only(monkeypatch, tmp_path: Path):
Image.new("RGB", (20, 20), "white").save(tmp_path / "source.png")
monkeypatch.setattr(ocr_engine, "extract_image_text", lambda _path, **_kwargs: {
@@ -525,6 +525,30 @@ def test_resolve_tool_blocks_maps_legacy_native_email_alias_to_offered_mcp_name(
assert json.loads(blocks[0].content)["max_results"] == 5
def test_resolve_tool_blocks_maps_create_draft_alias_to_reviewable_email_draft():
native_calls = [{
"name": "mcp__email__create_draft",
"arguments": json.dumps({
"to": "review@example.com",
"subject": "Review",
"body": "Please review this draft.",
}),
}]
blocks, used_native, _ = al._resolve_tool_blocks(
"",
native_calls,
round_num=1,
is_api_model=True,
offered_tool_names={"mcp__email__draft_email"},
)
assert used_native is True
assert len(blocks) == 1
assert blocks[0].tool_type == "mcp__email__draft_email"
assert json.loads(blocks[0].content)["to"] == "review@example.com"
def test_resolve_tool_blocks_maps_open_url_to_offered_private_browser():
native_calls = [{
"name": "open_url",
@@ -39,3 +39,54 @@ async def test_filesystem_tools_reject_invalid_structured_arguments_before_disk_
assert result["exit_code"] == 1
assert error_fragment in result["error"]
assert resolved == []
@pytest.mark.asyncio
async def test_write_file_preserves_existing_binary_artifact(tmp_path, monkeypatch):
import src.tool_execution as tool_execution
target = tmp_path / "output.pdf"
original = b"%PDF-1.7\nvalid binary payload\x00\xff"
target.write_bytes(original)
monkeypatch.setattr(tool_execution, "_resolve_tool_path", lambda _path: str(target))
result = await WriteFileTool().execute(
'{"path":"output.pdf","content":"The PDF is already complete."}', {}
)
assert result["exit_code"] == 1
assert result["binary_artifact_preserved"] is True
assert "binary artifact path" in result["error"]
assert target.read_bytes() == original
@pytest.mark.asyncio
async def test_write_file_rejects_new_binary_artifact_path(tmp_path, monkeypatch):
import src.tool_execution as tool_execution
target = tmp_path / "new.pdf"
monkeypatch.setattr(tool_execution, "_resolve_tool_path", lambda _path: str(target))
result = await WriteFileTool().execute(
'{"path":"new.pdf","content":"not really a PDF"}', {}
)
assert result["exit_code"] == 1
assert result["binary_artifact_preserved"] is False
assert not target.exists()
@pytest.mark.asyncio
async def test_write_file_still_rewrites_existing_text_file(tmp_path, monkeypatch):
import src.tool_execution as tool_execution
target = tmp_path / "notes.txt"
target.write_text("old", encoding="utf-8")
monkeypatch.setattr(tool_execution, "_resolve_tool_path", lambda _path: str(target))
result = await WriteFileTool().execute(
'{"path":"notes.txt","content":"new"}', {}
)
assert result["exit_code"] == 0
assert target.read_text(encoding="utf-8") == "new"
+38 -3
View File
@@ -208,6 +208,7 @@ def _chat_stream_endpoint(
captured["agent"] = {
"primary": (endpoint_url, model, kwargs.get("headers")),
"fallbacks": kwargs.get("fallbacks"),
"thinking_mode": kwargs.get("thinking_mode"),
}
if kwargs.get("external_untrusted_context_seen"):
captured["agent_external_untrusted_context_seen"] = True
@@ -311,7 +312,7 @@ async def test_chat_stream_route_keeps_selected_model_strict_with_legacy_data(mo
if mode == "chat":
assert captured == {"chat": [selected]}
else:
assert captured == {"agent": {"primary": selected, "fallbacks": []}}
assert captured == {"agent": {"primary": selected, "fallbacks": [], "thinking_mode": "off"}}
@pytest.mark.asyncio
@@ -566,7 +567,7 @@ async def test_chat_stream_route_uses_only_new_explicit_fallback_policy(monkeypa
if mode == "chat":
assert captured == {"chat": [selected, backup]}
else:
assert captured == {"agent": {"primary": selected, "fallbacks": [backup]}}
assert captured == {"agent": {"primary": selected, "fallbacks": [backup], "thinking_mode": "off"}}
@pytest.mark.asyncio
@@ -1803,6 +1804,36 @@ async def test_chat_stream_threads_form_endpoint_id_to_descriptor_builder(monkey
assert seen == ["account-two"]
@pytest.mark.asyncio
async def test_model_reconciliation_clears_stale_thinking_toggle(monkeypatch):
captured = {}
endpoint = _chat_stream_endpoint(
monkeypatch,
"agent",
captured,
session_model="qwen3-thinking",
)
def switch_to_grok(request, session, session_id, form_data, owner=None):
session.model = "x-ai/grok-4.5"
session.endpoint_url = "https://openrouter.ai/api/v1/chat/completions"
monkeypatch.setattr(
chat_routes,
"_reconcile_selected_route_from_request",
switch_to_grok,
)
request = _RouteRequest("agent")
request._form["thinking_mode"] = "on"
response = await endpoint(request)
async for _chunk in response.body_iterator:
pass
assert captured["agent"]["primary"][1] == "x-ai/grok-4.5"
assert captured["agent"]["thinking_mode"] == "off"
@pytest.mark.asyncio
async def test_nonstream_chat_threads_request_endpoint_id_to_descriptor_builder(
monkeypatch,
@@ -3722,7 +3753,11 @@ def test_skill_activation_reaches_later_fallback_request_and_pinned_round(monkey
for schema in round_two_requests[0]["kwargs"]["tools"]
}
assert "grep" in primary_schema_names
assert round_two_requests[1]["kwargs"]["tools"] is None
fallback_schema_names = {
schema["function"]["name"]
for schema in round_two_requests[1]["kwargs"]["tools"]
}
assert {"grep", "manage_skills"} <= fallback_schema_names
fallback_route_prompt = next(
message.get("content") or ""
for message in round_two_requests[1]["messages"]
+36
View File
@@ -808,6 +808,42 @@ def test_inspect_media_names_unconfined_export_field(tmp_path: Path, monkeypatch
}
def test_inspect_media_falls_back_to_imagemagick_for_svg(tmp_path: Path, monkeypatch):
from src.agent_tools import media_tools
(tmp_path / "source.svg").write_text(
'<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>',
encoding="utf-8",
)
commands = []
def which(name):
return "/usr/bin/convert" if name == "convert" else None
def render(command, timeout):
commands.append(command)
Image.new("RGB", (10, 10), "white").save(command[-1])
return subprocess.CompletedProcess(command, 0, "", "")
monkeypatch.setattr(media_tools.shutil, "which", which)
monkeypatch.setattr(media_tools, "_run", render)
token = _active_workspace.set(str(tmp_path))
try:
result = asyncio.run(InspectMediaTool().execute(json.dumps({
"path": "/workspace/source.svg",
"output_path": "/workspace/rendered.png",
}), {}))
finally:
_active_workspace.reset(token)
assert result["exit_code"] == 0
assert commands == [[
"/usr/bin/convert", str(tmp_path / "source.svg"),
str(tmp_path / "rendered.png"),
]]
assert (tmp_path / "rendered.png").read_bytes().startswith(b"\x89PNG")
@pytest.mark.skipif(not shutil.which("rsvg-convert"), reason="rsvg-convert required")
def test_inspect_media_renders_svg_to_png(tmp_path: Path):
(tmp_path / "floorplan.svg").write_text(
+2 -2
View File
@@ -15,8 +15,8 @@ def _base(monkeypatch, **env):
return cc.internal_api_base()
def test_default_is_legacy_7000(monkeypatch):
assert _base(monkeypatch) == "http://127.0.0.1:7000"
def test_default_matches_app_bind_port(monkeypatch):
assert _base(monkeypatch) == "http://127.0.0.1:7011"
def test_app_port_is_honored(monkeypatch):
+44
View File
@@ -0,0 +1,44 @@
import json
import pytest
from src.agent_tools.model_interaction_tools import list_models
@pytest.mark.asyncio
async def test_recommended_model_filter_uses_hardware_fit_backend(monkeypatch):
observed = {}
async def fake_app_api(content, owner=None):
observed.update({"args": json.loads(content), "owner": owner})
return {
"json": {
"system": {
"gpu_name": "Test GPU", "gpu_count": 2,
"gpu_vram_gb": 48, "backend": "cuda",
"cpu_name": "Test CPU", "total_ram_gb": 64,
},
"models": [{
"name": "org/model", "parameter_count": "30B", "quant": "Q4",
"required_gb": 20, "fit_level": "perfect", "run_mode": "gpu",
"speed_tps": 50, "score": 99, "context": 32768,
}],
},
"exit_code": 0,
}
monkeypatch.setattr("src.tools.system.do_app_api", fake_app_api)
result = await list_models("recommended", owner="pewds")
assert "GPU: Test GPU; count=2; total VRAM=48 GB" in result["output"]
assert "org/model: params=30B, quant=Q4, required=20 GB" in result["output"]
assert observed == {
"owner": "pewds",
"args": {
"action": "call",
"method": "GET",
"path": "/api/hwfit/models",
"query": {"fit_only": "true", "limit": 5, "sort": "fit"},
},
}
@@ -69,3 +69,24 @@ def test_llm_call_async_thinking_only_still_returns_str(monkeypatch):
def test_llm_call_async_plain_string_passthrough(monkeypatch):
out = _call(monkeypatch, "plain answer")
assert out == "plain answer"
def test_llm_call_async_clamps_default_output_to_endpoint_context(monkeypatch):
seen = {}
async def fake_post(client, url, headers, **kwargs):
seen.update(kwargs["json"])
return _FakeResponse(_payload("ok"))
monkeypatch.setattr(llm_core, "httpx_post_kimi_aware_async", fake_post)
monkeypatch.setattr(llm_core, "get_context_length", lambda _url, _model: 16384)
llm_core._response_cache.clear()
result = asyncio.run(llm_core.llm_call_async(
"http://local.test/v1/chat/completions",
"local-model",
[{"role": "user", "content": "brief request"}],
))
assert result == "ok"
assert 0 < seen["max_tokens"] < 16384
@@ -143,6 +143,33 @@ def test_search_matches_meaningful_tokens_when_phrase_skips_words(monkeypatch):
assert "Tokyo dinner reservation" not in result["results"]
def test_search_matches_singular_and_plural_terms_across_label_and_title(monkeypatch):
matching = _note(
id="contractor-existing",
title="Contractor list",
content="Extension status",
note_type="note",
label="forecast",
items=None,
)
other = _note(id="other-note", title="Forecast budget", content="No vendors")
fake_attrs = types.ModuleType("sqlalchemy.orm.attributes")
fake_attrs.flag_modified = lambda *args, **kwargs: None
monkeypatch.setitem(sys.modules, "sqlalchemy.orm.attributes", fake_attrs)
fake_db = types.ModuleType("core.database")
fake_db.SessionLocal = lambda: _Db([matching, other])
fake_db.Note = MagicMock()
monkeypatch.setitem(sys.modules, "core.database", fake_db)
result = asyncio.run(tool_implementations.do_manage_notes(
json.dumps({"action": "search", "query": "Forecast Contractors"}),
owner=None,
))
assert "Contractor list" in result["results"]
assert "Forecast budget" not in result["results"]
def test_list_hides_calendar_reminder_notes_by_default(monkeypatch):
regular = _note(id="abc12345-existing", title="Real user note")
calendar_reminder = _note(
+97
View File
@@ -0,0 +1,97 @@
import importlib.util
import json
import sqlite3
import sys
from pathlib import Path
PATH = Path(__file__).resolve().parents[1] / "scripts" / "manage_sft_fixture_state.py"
SPEC = importlib.util.spec_from_file_location("manage_sft_fixture_state", PATH)
MODULE = importlib.util.module_from_spec(SPEC)
assert SPEC and SPEC.loader
sys.modules[SPEC.name] = MODULE
SPEC.loader.exec_module(MODULE)
def make_db(path: Path) -> None:
db = sqlite3.connect(path)
db.executescript("""
CREATE TABLE notes (id TEXT PRIMARY KEY, owner TEXT, title TEXT);
CREATE TABLE documents (id TEXT PRIMARY KEY, owner TEXT, title TEXT);
CREATE TABLE document_versions (id TEXT PRIMARY KEY, document_id TEXT, content TEXT);
CREATE TABLE sessions (id TEXT PRIMARY KEY, owner TEXT, name TEXT);
""")
db.executemany("INSERT INTO notes VALUES (?,?,?)", [
("a", "alex", "baseline"), ("p", "pewds", "untouched"),
])
db.execute("INSERT INTO documents VALUES ('d', 'alex', 'doc')")
db.execute("INSERT INTO document_versions VALUES ('v', 'd', 'baseline body')")
db.execute("INSERT INTO sessions VALUES ('s', 'alex', 'evidence')")
db.commit()
db.close()
def test_owner_restore_is_scoped_and_preserves_sessions(tmp_path):
path = tmp_path / "app.db"
make_db(path)
snapshot = MODULE.snapshot_owner(path, "alex")
db = sqlite3.connect(path)
db.execute("DELETE FROM notes WHERE id='a'")
db.execute("INSERT INTO notes VALUES ('junk', 'alex', 'audit junk')")
db.execute("INSERT INTO sessions VALUES ('new', 'alex', 'new evidence')")
db.commit()
db.close()
MODULE.restore_owner(path, snapshot, "alex")
db = sqlite3.connect(path)
assert db.execute("SELECT id,title FROM notes WHERE owner='alex'").fetchall() == [("a", "baseline")]
assert db.execute("SELECT title FROM notes WHERE owner='pewds'").fetchone() == ("untouched",)
assert db.execute("SELECT id FROM sessions WHERE owner='alex' ORDER BY id").fetchall() == [("new",), ("s",)]
assert db.execute("SELECT content FROM document_versions").fetchone() == ("baseline body",)
db.close()
def test_restore_rejects_cross_owner_snapshot(tmp_path):
path = tmp_path / "app.db"
make_db(path)
snapshot = MODULE.snapshot_owner(path, "alex")
try:
MODULE.restore_owner(path, snapshot, "pewds")
except ValueError as exc:
assert "owner" in str(exc)
else:
raise AssertionError("cross-owner restore was accepted")
def test_owner_restore_includes_external_fixture_state(tmp_path):
path = tmp_path / "app.db"
make_db(path)
(tmp_path / "user_prefs.json").write_text(
'{"_users":{"alex":{"theme":"dark"},"pewds":{"theme":"light"}}}', encoding="utf-8",
)
(tmp_path / "fixture_email_messages.json").write_text(
'{"messages":[{"owner":"alex","uid":"a"},{"owner":"pewds","uid":"p"}]}', encoding="utf-8",
)
(tmp_path / "email_blocked_senders.json").write_text(
'{"owners":{"alex":["bad@example.com"],"pewds":[]}}', encoding="utf-8",
)
skill_dir = tmp_path / "skills" / "general" / "alex-skill"
skill_dir.mkdir(parents=True)
(skill_dir / "SKILL.md").write_text("---\nname: alex-skill\nowner: alex\n---\nbody", encoding="utf-8")
(tmp_path / "skills" / "_usage.json").write_text(
'{"alex::alex-skill":{"uses":2},"pewds::other":{"uses":1}}', encoding="utf-8",
)
snapshot = MODULE.snapshot_owner(path, "alex", tmp_path)
(tmp_path / "user_prefs.json").write_text('{"_users":{"alex":{"theme":"wrong"}}}', encoding="utf-8")
(tmp_path / "fixture_email_messages.json").write_text('{"messages":[]}', encoding="utf-8")
(skill_dir / "SKILL.md").write_text("---\nname: alex-skill\nowner: alex\n---\nwrong", encoding="utf-8")
MODULE.restore_owner(path, snapshot, "alex", tmp_path)
assert json.loads((tmp_path / "user_prefs.json").read_text())["_users"]["alex"] == {"theme": "dark"}
emails = json.loads((tmp_path / "fixture_email_messages.json").read_text())["messages"]
assert emails == [{"owner": "alex", "uid": "a"}]
assert (skill_dir / "SKILL.md").read_text().endswith("body")
usage = json.loads((tmp_path / "skills" / "_usage.json").read_text())
assert usage["alex::alex-skill"] == {"uses": 2}
+11
View File
@@ -97,3 +97,14 @@ def test_fixture_account_selector_accepts_display_label_with_email(monkeypatch):
}
assert es._fixture_row_matches_account(row, "Research Mail (alex.research@rowan.studio)")
def test_fixture_account_selector_accepts_primary_and_default_aliases():
row = {
"account": "Primary Inbox",
"account_email": "alex@example.com",
"account_id": "primary-inbox",
}
assert es._fixture_row_matches_account(row, "primary")
assert es._fixture_row_matches_account(row, "default")
+87
View File
@@ -4,6 +4,7 @@ from pathlib import Path
from src.agent_loop import (
_classify_agent_request,
_contextual_link_followup_topic,
_is_ambiguous_short_low_signal,
_is_contextual_link_followup,
_is_terse_link_request,
_minimal_recent_notes_tool_context_message,
@@ -169,6 +170,28 @@ def test_tui_bridge_does_not_block_low_signal_clarification_direct_path() -> Non
assert _should_use_direct_low_signal_path(**args)
def test_complete_fresh_domain_free_request_does_not_lose_agent_tools() -> None:
"""A natural request is not a fragment merely because routing found no keyword."""
args = dict(
low_signal_turn=True, casual_low_signal_turn=False,
ambiguous_short_turn=False, standalone_link_fragment_turn=False,
existing_conversation=False, qwen38_tool_router=False,
continuation=False, plan_mode=False, approved_plan=False,
guide_only=False, active_document_relevant=False, active_email=None,
workspace=None, has_domains=False, forced_tools=False,
relevant_tools=None, client_active_skills=False,
terminal_agent_mode=False, has_tui_host_bridge=False,
)
assert not _should_use_direct_low_signal_path(**args)
def test_typo_heavy_product_problem_is_complete_not_ambiguous_fragment() -> None:
text = "I have a miro 3 wiking by hwam and smoke isn't exiting properly its brand new"
assert not _is_ambiguous_short_low_signal(text)
def test_standalone_link_fragment_gets_clarification_path() -> None:
from pathlib import Path
@@ -242,6 +265,8 @@ def test_qwen35_tool_router_uses_broad_compact_map() -> None:
assert _is_qwen38_tool_router("odysseus-qwen3.5-9b-tool-router-v4-q4")
assert _is_qwen38_tool_router("odysseus-qwen3.5-tools-pre-heretic")
assert _is_qwen38_tool_router("ajax_c375")
assert _is_qwen38_tool_router("openai/ajax_c375")
assert "manage_notes: notes/checklists" in _QWEN38_TOOL_ROUTER_PROMPT
assert "mcp__email__list_emails" in _QWEN38_TOOL_ROUTER_PROMPT
assert "mcp__email__search_emails" in _QWEN38_TOOL_ROUTER_PROMPT
@@ -259,6 +284,7 @@ def test_qwen_tool_router_preserves_explicit_artifact_output_budget() -> None:
assert _allow_visual_tool_evidence_for_model(
"odysseus-qwen3.5-tools-pre-heretic"
)
assert _allow_visual_tool_evidence_for_model("ajax_c375")
assert not _allow_visual_tool_evidence_for_model(
"qwen35-9b-tool-router-v4-firstaction-noschema-adapter"
)
@@ -326,6 +352,32 @@ def test_private_browser_product_catalog_requires_multiple_comparable_items() ->
)
def test_interactive_browser_has_room_for_navigation_and_overlay_recovery() -> None:
from src.clean_agent_preview import (
INTERACTIVE_BROWSER_TOOL_CALL_LIMIT,
INTERACTIVE_TOOL_CALL_LIMIT,
)
assert INTERACTIVE_BROWSER_TOOL_CALL_LIMIT > INTERACTIVE_TOOL_CALL_LIMIT
assert INTERACTIVE_TOOL_CALL_LIMIT == 18
assert INTERACTIVE_BROWSER_TOOL_CALL_LIMIT == 30
def test_email_account_transport_outage_is_not_treated_as_empty_inbox() -> None:
from src.clean_agent_preview import email_account_backend_unavailable
assert email_account_backend_unavailable({
"stdout": (
"[EMAIL ACCOUNT ERRORS: Primary: [Errno 111] Connection refused]\n"
"No unread/unresponded emails found."
),
"exit_code": 0,
})
assert not email_account_backend_unavailable({
"stdout": "No unread/unresponded emails found.", "exit_code": 0,
})
def test_private_browser_open_without_dom_refs_queues_snapshot() -> None:
from src.agent_loop import _private_browser_open_needs_snapshot
@@ -917,6 +969,16 @@ def test_late_tool_summary_fallback_preserves_synthesized_answers() -> None:
assert "if _visible_response_text(full_response):\n break" in src
def test_read_only_shell_is_a_canonical_terminal_renderer() -> None:
from src.agent_loop import _ody_qwen_terminal_tool_summary
assert _ody_qwen_terminal_tool_summary({
"tool": "bash",
"command": '{"command":"pwd"}',
"output": "/workspace",
}) == "```text\n/workspace\n```"
def test_calendar_list_relative_range_args_become_iso_dates() -> None:
from src.agent_loop import _normalize_calendar_list_range_args
@@ -933,6 +995,28 @@ def test_calendar_list_relative_range_args_become_iso_dates() -> None:
}
def test_broad_calendar_list_discards_model_invented_range_and_query() -> None:
from src.agent_loop import _normalize_calendar_list_range_args
normalized, changed = _normalize_calendar_list_range_args(
{
"action": "list_events",
"start": "2026-09-11T15:00:00Z",
"end": "2026-09-11T18:00:00Z",
"query": "Today's schedule: 15:30 Meeting, 16:00 Call",
},
today="2026-09-11",
user_text="whats on my calendar? just three titles and times",
)
assert changed
assert normalized == {
"action": "list_events",
"start": "2026-09-11",
"end": "2026-10-11",
}
def test_calendar_create_strips_accidental_utc_suffix_for_local_wall_time() -> None:
from src.agent_loop import _normalize_calendar_create_relative_args
@@ -1244,6 +1328,9 @@ def test_qwen_explicit_note_delete_extracts_exact_title() -> None:
assert _parse_qwen_explicit_calendar_delete(
"Delete only the temporary calendar event titled Fixture-123."
) == "Fixture-123"
assert _parse_qwen_explicit_calendar_delete(
"Delete the second event from that list."
) is None
assert _parse_qwen_explicit_calendar_absence_verify(
"Verify that calendar event Fixture-123 is absent. Search the 2030-01-02 range; do not create anything."
) == {
+3
View File
@@ -175,6 +175,9 @@ class TestLookupKnown:
def test_deepseek_r1(self):
assert _lookup_known("deepseek-r1") == 64000
def test_deepseek_flash_provider_alias(self):
assert _lookup_known("deepseek-flash") == 64000
def test_gemini_pro(self):
assert _lookup_known("gemini-2.5-pro") == 1048576
+70 -1
View File
@@ -1,7 +1,13 @@
from types import SimpleNamespace
from routes import model_routes
from src import agent_loop
from src import agent_loop, llm_core
from src.tool_schemas import FUNCTION_TOOL_SCHEMAS
from src.model_profiles import (
GENERIC_TOOL_SCHEMA_PROFILE,
ODYSSEUS_COMPACT_TOOL_SCHEMA_PROFILE,
tool_schema_profile,
)
def test_model_tool_modes_filters_to_supported_values():
@@ -73,3 +79,66 @@ def test_preheretic_tools_model_forces_thinking_off():
tool_surface="compact",
domains={"web"},
) == "off"
def test_ajax_c375_defaults_to_compact_and_forces_thinking_off(monkeypatch):
class BrokenSession:
def __init__(self):
raise RuntimeError("endpoint metadata unavailable")
import core.database
monkeypatch.setattr(core.database, "SessionLocal", BrokenSession)
assert agent_loop._configured_model_tool_surface(
"http://100.67.207.85:19187/v1", "ajax_c375"
) == "compact"
assert agent_loop._thinking_mode_for_route(
model="ajax_c375",
tool_surface="compact",
domains=set(),
) == "off"
payload = {}
monkeypatch.setattr(llm_core, "is_local_endpoint", lambda _url: True)
llm_core._apply_local_qwen_thinking_mode(
payload,
"http://ajax.invalid/v1",
"ajax_c375",
"off",
)
assert payload["chat_template_kwargs"] == {"enable_thinking": False}
def test_named_schema_profile_aliases_are_accepted_by_settings_api():
assert model_routes._normalize_model_tool_mode("regular") == "full"
assert model_routes._normalize_model_tool_mode("odysseus_compact") == "compact"
def test_odysseus_ajax_names_force_native_tool_transport():
assert agent_loop._agent_route_tool_mode(
"http://model.invalid/v1", "Odysseus-experiment"
) == (True, False, False)
assert agent_loop._agent_route_tool_mode(
"http://model.invalid/v1", "provider/Ajax_trial_99"
) == (True, False, False)
def test_builtin_function_schemas_are_accepted_by_openai_top_level_contract():
forbidden = {"oneOf", "anyOf", "allOf", "enum", "const", "not"}
for schema in FUNCTION_TOOL_SCHEMAS:
parameters = schema.get("function", {}).get("parameters", {})
assert parameters.get("type") == "object"
assert forbidden.isdisjoint(parameters)
def test_models_select_one_of_two_tool_schema_profiles():
assert tool_schema_profile("gpt-5.5") == GENERIC_TOOL_SCHEMA_PROFILE
assert tool_schema_profile("claude-sonnet-5") == GENERIC_TOOL_SCHEMA_PROFILE
assert (
tool_schema_profile("lab/Odysseus-trial55")
== ODYSSEUS_COMPACT_TOOL_SCHEMA_PROFILE
)
assert (
tool_schema_profile("provider/Ajax_c375")
== ODYSSEUS_COMPACT_TOOL_SCHEMA_PROFILE
)
@@ -12,6 +12,18 @@ what is threaded back.
import src.agent_loop as al
def test_email_backend_error_is_not_rendered_as_empty_inbox():
raw = (
"[EMAIL ACCOUNT ERRORS: Primary: [Errno 111] Connection refused]\n"
"No unread/unresponded emails found."
)
summary = al._email_list_summary_from_tool_output(raw)
assert "currently unavailable" in summary
assert "No emails found" not in summary
def test_private_browser_product_query_extracts_short_storefront_term():
assert al._private_browser_product_query(
"Browse IKEA and find the best chair."
@@ -22,6 +34,66 @@ def test_private_browser_product_query_extracts_short_storefront_term():
assert al._private_browser_product_query("Where is IKEA?") == ""
def test_unrequested_browser_placeholder_is_rejected():
block = al.ToolBlock(
"private_browser",
'{"action":"batch","commands":[["open","https://www.example.com"],["snapshot"]]}',
)
assert al._private_browser_uses_unrequested_placeholder(
block, "Open the best IKEA chair option"
)
assert not al._private_browser_uses_unrequested_placeholder(
block, "Open https://www.example.com"
)
def test_browser_placeholder_named_by_prior_user_turn_remains_allowed():
block = al.ToolBlock(
"private_browser",
'{"action":"batch","commands":[["open","https://example.com"],["snapshot"]]}',
)
history = [
{"role": "user", "content": "Open https://example.com and report its heading."},
{"role": "assistant", "content": "The heading is Example Domain."},
{"role": "user", "content": "Return to that browser page and open Learn more."},
]
assert not al._private_browser_uses_unrequested_placeholder(
block, history[-1]["content"], history,
)
def test_ordinal_task_mutation_binds_to_prior_list_order():
first = "11111111-1111-4111-8111-111111111111"
second = "22222222-2222-4222-8222-222222222222"
history = [{
"role": "assistant",
"content": "Found two tasks.",
"metadata": {"tool_events": [{
"tool": "manage_tasks", "command": '{"action":"list"}', "exit_code": 0,
"output": f"AI: Found 2 tasks:\n1. Beta ({first}) — active\n2. Alpha ({second}) — active",
}]},
}]
assert al._ordinal_collection_mutation_target(
"Delete the second task from that list.", history, None, "tasks",
) == second
def test_ordinal_calendar_mutation_binds_to_prior_list_order():
first = "33333333-3333-4333-8333-333333333333"
second = "44444444-4444-4444-8444-444444444444"
history = [{
"role": "assistant",
"content": "Two calendar events.",
"metadata": {"tool_events": [{
"tool": "manage_calendar", "command": '{"action":"list_events"}', "exit_code": 0,
"output": f"- [Alpha](#event-{first})\n- [Beta](#event-{second})",
}]},
}]
assert al._ordinal_collection_mutation_target(
"Delete the second event from that list.", history, None, "calendar",
) == second
def test_resolve_returns_converted_calls_aligned():
native = [
{"name": "bogus_unknown_tool", "arguments": "{}", "id": "A"},
@@ -0,0 +1,14 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_notification_copy_supports_insecure_lan_contexts():
source = (ROOT / "static/js/admin.js").read_text(encoding="utf-8")
assert "async function copyNotificationText(value)" in source
assert "navigator.clipboard?.writeText && window.isSecureContext" in source
assert "document.execCommand('copy')" in source
assert "await copyNotificationText(note.body)" in source
assert "event.stopPropagation();" in source
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_pdf_backed_documents_do_not_offer_destructive_html_pdf_export():
source = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
assert "if (!isForm) {" in source
assert "label: _isDocxLang(lang) ? 'Convert to PDF' : 'Print as PDF'" in source
assert "destroy the original page layout, images, and form structure" in source
@@ -0,0 +1,12 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_preview_hides_import_action_and_restores_it_for_empty_editor():
source = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
assert "const emptyImport = document.getElementById('doc-rich-empty-import');" in source
assert "if (emptyImport) emptyImport.style.display = 'none';" in source
assert "_syncRichEmptyImport(richEmailBody);" in source
+41
View File
@@ -0,0 +1,41 @@
"""Sampling boundary regression checks; live request replay is also required."""
import ast
import unittest
from pathlib import Path
from src.generation_sampling import validate_temperature
ROOT = Path(__file__).resolve().parents[1]
class SamplingContractTests(unittest.TestCase):
def test_requested_sampling_and_greedy_values_preserved(self):
for value in (0, .2, 1., 1.5):
self.assertEqual(validate_temperature(value), value)
def test_invalid_values_rejected(self):
for value in (None, True, '1.0', -1., float('nan'), float('inf')):
with self.subTest(value=value), self.assertRaises(ValueError):
validate_temperature(value)
def test_preview_wrapper_forwards_temperature(self):
tree = ast.parse((ROOT / 'src/agent_loop.py').read_text())
calls = [n for n in ast.walk(tree) if isinstance(n, ast.Call)
and isinstance(n.func, ast.Name) and n.func.id == 'stream_preview']
self.assertTrue(calls)
for call in calls:
values = [k.value for k in call.keywords if k.arg == 'temperature']
self.assertEqual(len(values), 1)
self.assertIsInstance(values[0], ast.Name)
self.assertEqual(values[0].id, 'temperature')
def test_preview_request_uses_validated_temperature(self):
tree = ast.parse((ROOT / 'src/clean_agent_preview.py').read_text())
function = next(n for n in tree.body if isinstance(n, ast.AsyncFunctionDef) and n.name == 'stream_preview')
values = [v for n in ast.walk(function) if isinstance(n, ast.Dict)
for k, v in zip(n.keys, n.values) if isinstance(k, ast.Constant) and k.value == 'temperature']
self.assertTrue(values)
self.assertTrue(all(isinstance(v, ast.Name) and v.id == 'temperature' for v in values))
if __name__ == '__main__':
unittest.main()
+42 -3
View File
@@ -192,6 +192,34 @@ def test_private_browser_batch_normalizes_stable_inspection_action_names() -> No
]
def test_private_browser_batch_read_selector_matches_top_level_read_semantics() -> None:
commands, paths = PrivateBrowserTool()._normalize_batch_screenshots([
["open", "https://example.com"],
["read", "h1"],
])
assert paths == []
assert commands == [
["open", "https://example.com"],
["get", "text", "h1"],
]
def test_private_browser_batch_recovers_omitted_wait_selector_with_timeout() -> None:
commands, paths = PrivateBrowserTool()._normalize_batch_screenshots([
["fill", "@e2", "orange"],
["wait", None, 2500],
["snapshot"],
])
assert paths == []
assert commands == [
["fill", "@e2", "orange"],
["wait", "2500"],
["snapshot"],
]
def test_private_browser_batch_normalizes_object_commands_to_cli_arrays() -> None:
commands, paths = PrivateBrowserTool()._normalize_batch_screenshots([
{"action": "open", "url": "https://example.com"},
@@ -1453,7 +1481,7 @@ def test_private_browser_retries_transient_local_browser_bootstrap_failure(
assert sum(1 for call in calls if call[-1] == page.as_uri()) == 2
def test_private_browser_open_defers_screenshot_until_snapshot(monkeypatch, tmp_path) -> None:
def test_private_browser_open_captures_visual_preview(monkeypatch, tmp_path) -> None:
png_bytes = b"\x89PNG\r\n\x1a\nauto-browser"
monkeypatch.setattr(web_tools.shutil, "which", lambda name: "/usr/bin/agent-browser")
@@ -1492,8 +1520,19 @@ def test_private_browser_open_defers_screenshot_until_snapshot(monkeypatch, tmp_
"open",
"https://example.com",
]
assert len(calls) == 1
assert "images" not in result
assert len(calls) == 2
assert calls[1][-2] == "screenshot"
assert result["images"] == [{
"data": base64.b64encode(png_bytes).decode("ascii"),
"mimeType": "image/png",
}]
def test_private_browser_visual_preview_covers_state_changing_actions() -> None:
assert {'open', 'batch', 'snapshot', 'click', 'fill', 'press', 'scroll'} <= (
PrivateBrowserTool._AUTO_SCREENSHOT_ACTIONS
)
assert {'read', 'find', 'evaluate', 'wait'} - PrivateBrowserTool._AUTO_SCREENSHOT_ACTIONS
def test_youtube_tool_comments_falls_back_to_ytdlp(monkeypatch) -> None:
+14 -3
View File
@@ -37,9 +37,18 @@ def test_openrouter_does_not_disable_mandatory_grok_45_reasoning():
assert "reasoning" not in payload
def test_stream_transport_suppresses_reasoning_when_thinking_is_off():
def test_stream_transport_only_keeps_required_protocol_reasoning_when_off():
import ast
source = open(llm_core.__file__, encoding="utf-8").read()
assert 'if reasoning and _normalize_thinking_mode(thinking_mode) != "off":' in source
condition = next(node.test for node in ast.walk(ast.parse(source))
if isinstance(node, ast.If) and isinstance(node.test, ast.BoolOp)
and isinstance(node.test.values[0], ast.Name)
and node.test.values[0].id == "reasoning")
expression = compile(ast.Expression(condition), "reasoning-policy", "eval")
for model, mode, expected in [("generic", "off", False), ("generic", "on", True),
("deepseek-v4", "off", True)]:
assert bool(eval(expression, {"reasoning": "analysis", "thinking_mode": mode,
"model": model, "_normalize_thinking_mode": llm_core._normalize_thinking_mode})) is expected
def test_response_cache_is_partitioned_by_thinking_mode():
@@ -55,8 +64,10 @@ def test_chat_backend_reads_thinking_mode_from_active_prompt_preset():
def test_email_writing_style_is_agent_manageable_and_routable():
assert settings.DEFAULT_SETTINGS["email_writing_style"] == ""
assert settings.DEFAULT_SETTINGS["document_writing_style"] == ""
source = open(admin_tools.__file__, encoding="utf-8").read()
assert '"writing style": "email_writing_style"' in source
assert '"writing style": "document_writing_style"' in source
assert '"email writing style": "email_writing_style"' in source
assert any(
"writing style" in triggers and "manage_settings" in tools
for triggers, tools in agent_loop._QWEN38_ROUTER_KEYWORD_TOOLS
+17
View File
@@ -0,0 +1,17 @@
from src.agent_tools.subprocess_tools import _python_with_configured_import_paths
def test_python_tool_import_paths_are_opt_in():
source = "print('ok')"
assert _python_with_configured_import_paths(source, {}) == source
def test_python_tool_import_paths_include_only_absolute_configured_roots():
wrapped = _python_with_configured_import_paths(
"print('ok')",
{"ODYSSEUS_PYTHON_TOOL_SITE_PACKAGES": "/vetted/one:relative:/vetted/two"},
)
assert "'/vetted/one'" in wrapped
assert "'/vetted/two'" in wrapped
assert "relative" not in wrapped
assert "exec(compile(" in wrapped
+214
View File
@@ -0,0 +1,214 @@
from email.message import EmailMessage
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import core.database as cdb
from routes.email_pollers import _import_calendar_attachments
@pytest.fixture
def invitation_db(tmp_path, monkeypatch):
monkeypatch.setattr("src.constants.DATA_DIR", str(tmp_path))
engine = create_engine(f"sqlite:///{tmp_path / 'calendar.db'}")
cdb.Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine)
monkeypatch.setattr(cdb, "SessionLocal", factory)
import routes.calendar_routes as calendar
monkeypatch.setattr(calendar, "SessionLocal", factory)
async def no_push(*args, **kwargs):
return None
monkeypatch.setattr(calendar, "_push_caldav_event_after_commit", no_push)
yield factory
engine.dispose()
def message(method="REQUEST", sequence=0, start="20261001T100000Z", source_uid="meeting-1", recurrence=None, rule=None):
lines = ["BEGIN:VCALENDAR", "VERSION:2.0", f"METHOD:{method}", "BEGIN:VEVENT",
f"UID:{source_uid}", f"SEQUENCE:{sequence}", "DTSTAMP:20260916T100000Z",
"SUMMARY:Meeting"]
if start:
lines.append(f"DTSTART:{start}")
if method == "CANCEL":
lines.append("STATUS:CANCELLED")
if recurrence:
lines.append(f"RECURRENCE-ID:{recurrence}")
if rule:
lines.append(f"RRULE:{rule}")
lines.extend(["END:VEVENT", "END:VCALENDAR", ""])
msg = EmailMessage()
msg.set_content("Invitation")
msg.add_attachment("\r\n".join(lines).encode(), maintype="text", subtype="calendar", filename="invite.ics")
return msg
async def apply(msg, owner="alice", sender="organizer@example.test"):
return await _import_calendar_attachments(msg, owner=owner, sender=sender, subject="Meeting")
@pytest.mark.asyncio
async def test_reschedule_and_cancellation_target_same_event(invitation_db):
uids, created = await apply(message())
assert created == 1
updated, created = await apply(message(sequence=1, start="20261002T100000Z"))
assert updated == uids and created == 0
with invitation_db() as db:
events = db.query(cdb.CalendarEvent).all()
assert len(events) == 1
assert events[0].dtstart.day == 2
await apply(message(method="CANCEL", sequence=2, start=None))
await apply(message(sequence=1))
with invitation_db() as db:
event = db.get(cdb.CalendarEvent, uids[0])
assert event.status == "cancelled"
assert event.dtstart.day == 2
@pytest.mark.asyncio
async def test_cancellation_before_invite_does_not_create_event(invitation_db):
assert await apply(message(method="CANCEL", sequence=2, start=None)) == ([], 0)
assert await apply(message(sequence=1)) == ([], 0)
with invitation_db() as db:
assert db.query(cdb.CalendarEvent).count() == 0
@pytest.mark.asyncio
async def test_same_ics_uid_is_scoped_to_owner(invitation_db):
alice, _ = await apply(message())
bob, _ = await apply(message(), owner="bob")
assert alice != bob
await apply(message(method="CANCEL", sequence=2, start=None), owner="bob")
with invitation_db() as db:
assert db.get(cdb.CalendarEvent, alice[0]).status == "confirmed"
assert db.get(cdb.CalendarEvent, bob[0]).status == "cancelled"
@pytest.mark.asyncio
async def test_attendee_reply_does_not_create_event(invitation_db):
assert await apply(message(method="REPLY")) == ([], 0)
@pytest.mark.asyncio
async def test_overlapping_revisions_do_not_race(invitation_db, monkeypatch):
import asyncio
from src import tool_implementations
original = tool_implementations.do_manage_calendar
entered, release = asyncio.Event(), asyncio.Event()
calls = 0
async def delayed(*args, **kwargs):
nonlocal calls
calls += 1
if calls == 1:
entered.set()
await release.wait()
return await original(*args, **kwargs)
monkeypatch.setattr(tool_implementations, "do_manage_calendar", delayed)
first = asyncio.create_task(apply(message(sequence=1)))
await asyncio.wait_for(entered.wait(), 2)
later = asyncio.create_task(apply(message(sequence=2, start="20261003T100000Z")))
await asyncio.sleep(0.05)
assert calls == 1
release.set()
await asyncio.wait_for(asyncio.gather(first, later), 3)
with invitation_db() as db:
assert db.query(cdb.CalendarEvent).count() == 1
assert db.query(cdb.CalendarEvent).one().dtstart.day == 3
assert db.query(cdb.EmailCalendarInvitation).one().sequence == 2
@pytest.mark.asyncio
async def test_invitation_lock_released_when_holder_cancelled(invitation_db):
import asyncio
from src.email_calendar_import import _invitation_lock
entered = asyncio.Event()
async def holder():
async with _invitation_lock("alice", "sender@example.test", "meeting"):
entered.set()
await asyncio.Event().wait()
task = asyncio.create_task(holder())
await asyncio.wait_for(entered.wait(), 2)
task.cancel()
with pytest.raises(asyncio.CancelledError):
await task
async def reacquire():
async with _invitation_lock("alice", "sender@example.test", "meeting"):
return True
assert await asyncio.wait_for(reacquire(), 2)
@pytest.mark.asyncio
async def test_invitation_lock_excludes_another_process(invitation_db, tmp_path):
import asyncio
import os
import subprocess
import sys
if os.name == "nt":
pytest.skip("POSIX cross-process probe; Windows uses msvcrt")
from src.email_calendar_import import _invitation_lock
probe = """
import fcntl, os, sys
fd = os.open(sys.argv[1], os.O_RDWR)
try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
sys.exit(73)
finally:
os.close(fd)
"""
async with _invitation_lock("alice", "sender@example.test", "meeting"):
lock_path = next((tmp_path / ".calendar-import-locks").glob("*.lock"))
result = await asyncio.to_thread(subprocess.run, [sys.executable, "-c", probe, str(lock_path)], timeout=5)
assert result.returncode == 73
result = await asyncio.to_thread(subprocess.run, [sys.executable, "-c", probe, str(lock_path)], timeout=5)
assert result.returncode == 0
@pytest.mark.asyncio
async def test_same_title_time_does_not_link_different_senders(invitation_db):
alice, _ = await apply(message(), sender="alice@example.test")
bob, _ = await apply(message(), sender="bob@example.test")
assert alice != bob
await apply(message(method="CANCEL", sequence=2, start=None), sender="bob@example.test")
with invitation_db() as db:
assert db.get(cdb.CalendarEvent, alice[0]).status == "confirmed"
@pytest.mark.asyncio
async def test_occurrence_reschedule_excludes_original_without_moving_series(invitation_db):
import json
master, _ = await apply(message(rule="FREQ=DAILY;COUNT=3"))
detached, _ = await apply(message(sequence=1, start="20261002T120000Z", recurrence="20261002T100000Z"))
with invitation_db() as db:
parent = db.get(cdb.CalendarEvent, master[0])
child = db.get(cdb.CalendarEvent, detached[0])
assert parent.dtstart.day == 1
assert "2026-10-02T10:00" in json.loads(parent.recurrence_exdates)
assert child.dtstart.hour == 12 and not child.rrule
await apply(message(method="CANCEL", sequence=2, start=None, recurrence="20261002T100000Z"))
with invitation_db() as db:
assert db.get(cdb.CalendarEvent, master[0]).status == "confirmed"
assert db.get(cdb.CalendarEvent, detached[0]).status == "cancelled"
@pytest.mark.asyncio
async def test_occurrence_cancellation_before_series_is_preserved(invitation_db):
import json
await apply(message(method="CANCEL", sequence=2, start=None, recurrence="20261002T100000Z"))
master, _ = await apply(message(rule="FREQ=DAILY;COUNT=3"))
with invitation_db() as db:
assert "2026-10-02T10:00" in json.loads(db.get(cdb.CalendarEvent, master[0]).recurrence_exdates)
@pytest.mark.asyncio
async def test_series_cancellation_also_cancels_detached_events(invitation_db):
master, _ = await apply(message(rule="FREQ=DAILY;COUNT=3"))
detached, _ = await apply(message(sequence=1, start="20261002T120000Z", recurrence="20261002T100000Z"))
await apply(message(method="CANCEL", sequence=2, start=None))
with invitation_db() as db:
assert db.get(cdb.CalendarEvent, master[0]).status == "cancelled"
assert db.get(cdb.CalendarEvent, detached[0]).status == "cancelled"
@@ -0,0 +1,29 @@
import json
import subprocess
from pathlib import Path
def test_calendar_location_escapes_markup_surrounding_links():
source = (Path(__file__).resolve().parents[1] / "static/js/calendar.js").read_text()
function = source[source.index("function _locHTML("):source.index("// ── Open / Close", source.index("function _locHTML("))]
script = r'''
const _e = value => String(value).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
''' + function + r'''
const location = '<img src=x onerror=alert(1)> https://example.test/meeting <svg onload=alert(2)>';
console.log(JSON.stringify(_locHTML(location)));
'''
result = subprocess.run(["node", "--input-type=module", "-e", script], capture_output=True, text=True, check=True)
html = json.loads(result.stdout)
assert "<img" not in html
assert "<svg" not in html
assert "&lt;img" in html
assert 'href="https://example.test/meeting"' in html
def test_source_email_is_visible_with_only_one_calendar():
source = (Path(__file__).resolve().parents[1] / "static/js/calendar.js").read_text()
function = source[source.index("function _eventSourceHtml("):source.index("async function _fetchEventByUid(")]
script = "const _e = String; const _calendars = [];\n" + function + "\nconsole.log(_eventSourceHtml({source_email_uid:'42', source_email_folder:'INBOX'}));"
result = subprocess.run(["node", "-e", script], capture_output=True, text=True, check=True)
assert '<svg' in result.stdout
assert 'href="#email=INBOX:42"' in result.stdout
+137
View File
@@ -0,0 +1,137 @@
import asyncio
from pathlib import Path
import subprocess
import threading
from types import SimpleNamespace
from unittest.mock import MagicMock
import pytest
from fastapi import HTTPException
@pytest.fixture
def conversion(monkeypatch, tmp_path):
import routes.document.document_routes as routes
import src.auth_helpers as auth
monkeypatch.setattr(auth, "require_privilege", lambda *a: "alice")
doc = SimpleNamespace(owner="alice", session_id=None, current_content='<!-- docx_source upload_id="' + 'a'*32 + '" -->')
db = MagicMock()
db.query.return_value.filter.return_value.first.return_value = doc
monkeypatch.setattr(routes, "SessionLocal", lambda: db)
source = tmp_path / "original.docx"
source.write_bytes(b"original-file")
handler = MagicMock()
handler.upload_dir = tmp_path
handler.resolve_upload.return_value = {"path": str(source)}
monkeypatch.setattr("shutil.which", lambda executable: "/fake/soffice")
router = routes.setup_document_routes(MagicMock(), handler)
endpoint = next(r.endpoint for r in router.routes if r.path.endswith("/convert-original/{target}"))
request = SimpleNamespace(app=SimpleNamespace(state=SimpleNamespace(auth_manager=None)))
return endpoint, request, doc, handler
@pytest.mark.asyncio
async def test_conversion_does_not_block_loop_and_cleans_output(monkeypatch, conversion):
endpoint, request, _, _ = conversion
entered, release = threading.Event(), threading.Event()
outputs = []
loop_thread = threading.get_ident()
def run(command, **kwargs):
assert threading.get_ident() != loop_thread
output_dir = Path(command[command.index("--outdir") + 1])
outputs.append(output_dir)
assert any(arg.startswith("-env:UserInstallation=") for arg in command)
assert Path(command[-1]).read_bytes() == b"original-file"
entered.set()
assert release.wait(3)
(output_dir / "original.pdf").write_bytes(b"%PDF-test")
return SimpleNamespace(returncode=0)
monkeypatch.setattr(subprocess, "run", run)
task = asyncio.create_task(endpoint("doc", "pdf", request))
try:
assert await asyncio.to_thread(entered.wait, 2)
# This coroutine executes while conversion is still blocked in a worker.
assert not task.done()
finally:
release.set()
response = await task
assert response.body == b"%PDF-test"
assert all(not path.exists() for path in outputs)
@pytest.mark.asyncio
async def test_timeout_returns_504_and_cleans_worker_directory(monkeypatch, conversion):
endpoint, request, _, _ = conversion
outputs = []
def run(command, **kwargs):
outputs.append(Path(command[command.index("--outdir") + 1]))
raise subprocess.TimeoutExpired(command, 120)
monkeypatch.setattr(subprocess, "run", run)
with pytest.raises(HTTPException) as error:
await endpoint("doc", "pdf", request)
assert error.value.status_code == 504
assert all(not path.exists() for path in outputs)
@pytest.mark.asyncio
async def test_form_pdf_with_fields_reaches_original_conversion(monkeypatch, conversion, tmp_path):
endpoint, request, doc, handler = conversion
doc.current_content = '<!-- pdf_form_source upload_id="' + 'a'*32 + '" fields="3" -->'
source = tmp_path / "original.pdf"
source.write_bytes(b"%PDF-original")
handler.resolve_upload.return_value = {"path": str(source)}
def run(command, **kwargs):
assert command[-1] == str(source)
output = Path(command[command.index("--outdir") + 1]) / "original.docx"
output.write_bytes(b"PK-converted")
return SimpleNamespace(returncode=0)
monkeypatch.setattr(subprocess, "run", run)
assert (await endpoint("doc", "docx", request)).body == b"PK-converted"
@pytest.mark.asyncio
async def test_docx_preview_runs_off_loop_and_checks_owner(monkeypatch, conversion):
import sys
import routes.document.document_routes as routes
_, request, doc, handler = conversion
main_thread = threading.get_ident()
rendered = []
def render(path):
assert threading.get_ident() != main_thread
rendered.append(path)
return SimpleNamespace(value="<p>Preview</p>", messages=[])
monkeypatch.setitem(sys.modules, "mammoth", SimpleNamespace(convert_to_html=render))
router = routes.setup_document_routes(MagicMock(), handler)
endpoint = next(r.endpoint for r in router.routes if r.path.endswith("/render-docx"))
assert (await endpoint("doc", request))["html"] == "<p>Preview</p>"
doc.owner = "bob"
with pytest.raises(HTTPException) as error:
await endpoint("doc", request)
assert error.value.status_code in {403, 404}
assert len(rendered) == 1
def test_imported_office_document_is_owned_at_first_commit(monkeypatch, tmp_path):
from sqlalchemy import create_engine, event
from sqlalchemy.orm import sessionmaker
import src.database as database
from src.office_doc import create_office_document
engine = create_engine(f"sqlite:///{tmp_path / 'documents.db'}")
database.Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine)
monkeypatch.setattr(database, "SessionLocal", factory)
monkeypatch.setattr("src.agent_tools.document_tools.set_active_document", lambda doc_id: None)
owners_at_commit = []
def inspect_new_rows(session):
owners_at_commit.extend(row.owner for row in session.new if isinstance(row, database.Document))
event.listen(factory, "before_commit", inspect_new_rows)
try:
doc_id = create_office_document(None, "upload", "Standalone", "Content", owner="alice")
assert doc_id
assert owners_at_commit == ["alice"]
with factory() as db:
assert db.get(database.Document, doc_id).owner == "alice"
finally:
engine.dispose()
+55
View File
@@ -0,0 +1,55 @@
"""Execute the actual DOCX handlers with deferred network responses."""
import subprocess
from pathlib import Path
def test_docx_responses_do_not_overwrite_new_tabs_or_hidden_previews():
source = (Path(__file__).resolve().parents[1] / "static/js/document.js").read_text()
handlers = source.split(" let _docxPreviewRequest = 0;", 1)[1].split(" /** Parse CSV", 1)[0]
script = r'''
import assert from 'node:assert/strict';
let _docxPreviewRequest = 0;
let activeDocId = 'a';
const original = {language: 'docx', content: 'original'};
const docs = new Map([['a', original], ['b', {content: 'untouched'}]]);
const preview = {style: {}, innerHTML: '', replaceChildren() {this.innerHTML = '';}};
const wrap = {style: {}};
const textarea = {value: 'untouched'};
const document = {getElementById(id) { return id === 'doc-docx-preview' ? preview : id === 'doc-editor-wrap' ? wrap : textarea; }};
const API_BASE = '';
const _syncHeaderActions = () => {};
const _escHtml = String;
const markdownModule = {sanitizeAllowedHtml: x => x};
const _isDocxLang = x => x === 'docx';
let saves = 0;
const saveDocument = async () => {saves++;};
const switchToDoc = () => {};
const uiModule = {};
let respond;
const fetch = () => new Promise(resolve => { respond = () => resolve({ok: true, json: async () => ({html: '<p>converted</p>'})}); });
''' + handlers + r'''
let pending = _convertDocxToRichText();
activeDocId = 'b'; respond(); await pending;
assert.equal(original.content, 'original');
assert.equal(textarea.value, 'untouched');
assert.equal(saves, 0);
activeDocId = 'a';
pending = _convertDocxToRichText();
original.content = 'new edit'; respond(); await pending;
assert.equal(original.content, 'new edit');
assert.equal(saves, 0);
pending = _setDocxPreviewActive(true);
await _setDocxPreviewActive(false);
respond(); await pending;
assert.equal(preview.innerHTML, '');
assert.equal(original._docxPreviewActive, false);
pending = _setDocxPreviewActive(true);
activeDocId = 'b'; preview.innerHTML = 'other tab';
respond(); await pending;
assert.equal(preview.innerHTML, 'other tab');
activeDocId = 'a';
pending = _convertDocxToRichText(); respond(); await pending;
assert.equal(original.content, '<p>converted</p>');
assert.equal(saves, 1);
'''
subprocess.run(["node", "--input-type=module", "-e", script], check=True, capture_output=True, text=True)
+38
View File
@@ -0,0 +1,38 @@
from unittest.mock import MagicMock
import pytest
from routes.email_routes import _resolve_current_email_uid
def test_disconnect_is_not_confirmed_absence():
conn = MagicMock()
conn.uid.side_effect = OSError("IMAP disconnected")
with pytest.raises(OSError):
_resolve_current_email_uid(conn, "123")
def test_rejected_search_is_not_confirmed_absence():
conn = MagicMock()
conn.uid.side_effect = [("OK", [None]), ("NO", [b"search failed"])]
with pytest.raises(RuntimeError):
_resolve_current_email_uid(conn, "123")
def test_confirmed_absence_can_be_idempotent():
conn = MagicMock()
conn.uid.side_effect = [("OK", [None]), ("OK", [b""])]
assert _resolve_current_email_uid(conn, "123") == ""
def test_message_id_search_failure_is_not_confirmed_absence():
conn = MagicMock()
conn.uid.side_effect = [("OK", [None]), ("OK", [b""]), ("NO", [b"failed"])]
with pytest.raises(RuntimeError):
_resolve_current_email_uid(conn, "123", "<id@example.test>")
def test_stale_uid_resolves_by_message_id():
conn = MagicMock()
conn.uid.side_effect = [("OK", [None]), ("OK", [b""]), ("OK", [b"456"])]
assert _resolve_current_email_uid(conn, "123", "<id@example.test>") == "456"
+82
View File
@@ -0,0 +1,82 @@
from types import SimpleNamespace
from unittest.mock import MagicMock
import pytest
from src.task_endpoint import _same_endpoint_base, resolve_task_candidates
@pytest.mark.parametrize("url", [
"https://untrusted.test/https://api.example.test/v1/chat/completions",
"https://api.example.test.evil.test/v1",
"http://api.example.test/v1",
"https://api.example.test:444/v1",
"https://api.example.test/v10",
"https://api.example.test/v1?redirect=elsewhere",
"https://user@api.example.test/v1",
])
def test_unrelated_override_receives_no_saved_credentials(monkeypatch, url):
import src.database as database
import src.endpoint_resolver as resolver
import src.task_endpoint as tasks
db = MagicMock()
db.query.return_value.filter.return_value.all.return_value = [
SimpleNamespace(base_url="https://api.example.test/v1")
]
monkeypatch.setattr(database, "SessionLocal", lambda: db)
runtime = MagicMock(return_value=("https://api.example.test/v1", "dummy-secret"))
monkeypatch.setattr(resolver, "resolve_endpoint_runtime", runtime)
monkeypatch.setattr(tasks, "resolve_task_endpoint", lambda *a, **k: (None, None, {}))
monkeypatch.setattr(tasks, "resolve_endpoint", lambda *a, **k: (None, None, {}))
monkeypatch.setattr(tasks, "resolve_utility_fallback_candidates", lambda **k: [])
candidates = resolve_task_candidates(override_url=url, override_model="test")
assert candidates == [(url, "test", {})]
runtime.assert_not_called()
def test_exact_api_base_allows_normalized_chat_path():
assert _same_endpoint_base("https://API.example.test:443/v1/chat/completions", "https://api.example.test/v1/")
assert not _same_endpoint_base("https://api.example.test/v1", "")
@pytest.mark.parametrize("resolver_kind", ["task", "skill"])
@pytest.mark.parametrize("owner,url,expected", [
("alice", "https://api.example.test/v1/chat/completions", "Bearer alice-secret"),
("bob", "https://api.example.test/v1/chat/completions", None),
("alice", "https://api.example.test.evil.test/v1", None),
("alice", "https://evil.test/https://api.example.test/v1", None),
])
def test_credential_resolution_is_exact_and_owner_scoped(monkeypatch, tmp_path, resolver_kind, owner, url, expected):
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import src.database as database
import src.endpoint_resolver as resolver
import src.task_endpoint as tasks
import src.llm_core as llm
import src.settings as settings
engine = create_engine(f"sqlite:///{tmp_path / 'endpoints.db'}")
database.ModelEndpoint.__table__.create(engine)
factory = sessionmaker(bind=engine)
with factory() as db:
db.add(database.ModelEndpoint(id="private", name="Private", owner="alice",
base_url="https://api.example.test/v1",
api_key="alice-secret", is_enabled=True))
db.commit()
monkeypatch.setattr(database, "SessionLocal", factory)
monkeypatch.setattr(resolver, "resolve_endpoint_runtime", lambda ep, **kw: (ep.base_url, ep.api_key))
monkeypatch.setattr(tasks, "resolve_task_endpoint", lambda *a, **kw: (None, None, {}))
monkeypatch.setattr(tasks, "resolve_endpoint", lambda *a, **kw: (None, None, {}))
monkeypatch.setattr(tasks, "resolve_utility_fallback_candidates", lambda **kw: [])
monkeypatch.setattr(llm, "list_model_ids", lambda *a, **kw: [])
monkeypatch.setattr(settings, "get_setting", lambda key, default=None: default)
try:
if resolver_kind == "task":
headers = tasks.resolve_task_candidates(override_url=url, override_model="test", owner=owner)[0][2]
else:
from routes.skills_routes import _resolve_audit_models
headers = _resolve_audit_models(owner=owner, endpoint_url=url, model_spec="test")[2]
assert headers.get("Authorization") == expected
finally:
engine.dispose()
+50
View File
@@ -0,0 +1,50 @@
"""The fixture capability exception cannot grant a denied tool."""
import ast
from pathlib import Path
import pytest
def test_explicit_fixture_selection_respects_all_denials():
tree = ast.parse((Path(__file__).resolve().parents[1] / "routes/chat_routes.py").read_text())
assignment = next(node for node in ast.walk(tree) if isinstance(node, ast.Assign)
and any(isinstance(t, ast.Name) and t.id == "_explicit_fixture_personal_tools" for t in node.targets))
expression = compile(ast.Expression(assignment.value), "fixture-selection", "eval")
for disabled, blocked, expected in [
(set(), set(), {"manage_calendar"}),
({"manage_calendar"}, set(), set()),
(set(), {"manage_calendar"}, set()),
]:
assert eval(expression, {"_selected_tools": {"manage_calendar"},
"disabled_tools": disabled, "_owner_blocked": blocked}) == expected
source = ast.unparse(tree)
assert '_owner_blocked.difference_update(_explicit_fixture_personal_tools)' not in source
assert 'disabled_tools.difference_update(_explicit_fixture_personal_tools)' not in source
@pytest.mark.asyncio
@pytest.mark.parametrize("gate", ["disabled", "guide_only"])
async def test_bound_contract_cannot_override_execution_restrictions(monkeypatch, gate):
from types import SimpleNamespace
from unittest.mock import AsyncMock
from src import tool_execution, tool_implementations
from src.tool_policy import build_effective_tool_policy
from src.tool_schemas import FUNCTION_TOOL_SCHEMAS
from src.turn_contract import bind_turn_contract, resolve_turn_contract
handler = AsyncMock(return_value={"exit_code": 0})
monkeypatch.setattr(tool_implementations, "do_manage_calendar", handler)
monkeypatch.setattr(tool_execution, "_owner_is_admin", lambda owner: True)
contract = resolve_turn_contract(capabilities={"calendar"}, schemas=FUNCTION_TOOL_SCHEMAS,
policy=build_effective_tool_policy())
assert contract.permits("manage_calendar")
with bind_turn_contract(contract):
desc, result = await tool_execution.execute_tool_block(
SimpleNamespace(tool_type="manage_calendar", content='{"action":"list"}'),
disabled_tools={"manage_calendar"} if gate == "disabled" else set(),
tool_policy=build_effective_tool_policy(last_user_message="Do not use tools.") if gate == "guide_only" else None,
security_context=tool_execution.NO_TOOL_SECURITY_CONTEXT,
)
assert desc == "manage_calendar: BLOCKED"
assert result["exit_code"] == 1
handler.assert_not_awaited()
+30
View File
@@ -0,0 +1,30 @@
import asyncio
import pytest
import src.llm_core as core
@pytest.mark.asyncio
async def test_exiting_foreground_does_not_decrement_other_waiters(monkeypatch):
monkeypatch.setattr(core, "_LOCAL_MODEL_LOCKS", {})
monkeypatch.setattr(core, "_LOCAL_MODEL_CURRENT", {})
monkeypatch.setattr(core, "_LOCAL_MODEL_WAITING_FOREGROUND", {})
monkeypatch.setattr(core, "_local_model_gate_enabled", lambda: True)
monkeypatch.setattr(core, "is_local_endpoint", lambda url: True)
url = "http://local.test/v1"
key = core._local_model_gate_key(url)
entered, release = asyncio.Event(), asyncio.Event()
async def waiter():
async with core._local_model_slot(url, "test"):
entered.set()
await release.wait()
async with core._local_model_slot(url, "test"):
task = asyncio.create_task(waiter())
await asyncio.sleep(0)
assert core._LOCAL_MODEL_WAITING_FOREGROUND[key] == 1
# The queued request has not resumed yet, so it must still be counted.
assert core._LOCAL_MODEL_WAITING_FOREGROUND[key] == 1
await entered.wait()
assert core._LOCAL_MODEL_WAITING_FOREGROUND[key] == 0
release.set()
await task
+44
View File
@@ -0,0 +1,44 @@
import json
import pytest
from src.deep_research import DeepResearcher
from src.research_navigator import ResearchPage
@pytest.mark.parametrize("question,text", [
("日本の人工知能", "日本の人工知能の研究について"),
("日本の人工知能", "Artificial intelligence research in Japan"),
("artificial intelligence", "人工知能の研究について"),
("???", "A source that the model can assess"),
])
def test_lexical_filter_defers_when_it_cannot_assess_language(question, text):
assert DeepResearcher._topic_relevant(question, text)
def test_small_model_filter_still_rejects_unrelated_english():
assert not DeepResearcher._topic_relevant("Boston Terrier neurology", "Boston tourism and hotels")
@pytest.mark.asyncio
async def test_small_model_can_recover_topic_from_browser(monkeypatch):
monkeypatch.setattr("src.settings.get_setting", lambda key, default=None: True)
researcher = DeepResearcher(llm_endpoint="http://local.test/v1", llm_model="test-9b")
calls = []
async def fetch(url, **kwargs):
return ResearchPage(url=url, title="Welcome", content="Sign in and accept cookies", success=True, retrieval="fetch")
async def browser_read(url, **kwargs):
calls.append(url)
return ResearchPage(url=url, title="Boston Terrier neurology", content="Boston Terrier neurological research findings", success=True, retrieval="browser")
async def llm(*args, **kwargs):
return json.dumps({"summary": "Boston Terrier neurological research findings", "evidence": "Boston Terrier neurological research findings"})
researcher.navigator.fetch = fetch
researcher.navigator.browser_read = browser_read
researcher._llm = llm
result = await researcher._fetch_and_extract("https://example.test/article", "Boston Terrier neurology", "Welcome")
assert calls == ["https://example.test/article"]
assert result and result["retrieval"] == "browser"
@@ -0,0 +1,19 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_richtext_toolbar_preserves_selection_before_formatting():
source = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
assert "let _savedFormatTextareaSelection = null;" in source
assert "let _savedFormatRichRange = null;" in source
assert "toolbar.addEventListener('pointerdown', (e) =>" in source
assert "_saveFormatSelection(e);" in source
assert "textarea.selectionStart !== textarea.selectionEnd" in source
assert "ta.selectionStart = _savedFormatTextareaSelection.start;" in source
assert "const _selection = window.getSelection?.();" in source
assert "_richFormatRange = _range.cloneRange();" in source
assert "_selection.addRange(_richFormatRange.cloneRange());" in source
assert "collapse the browser range before execCommand runs" in source
@@ -0,0 +1,13 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_richtext_preview_returns_to_contenteditable_editor():
source = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
assert "const currentLang = document.getElementById('doc-language-select')?.value || '';" in source
assert "if (richMode) {" in source
assert "wrap.style.display = 'none';" in source
assert "_syncRichEmptyImport(richEmailBody);" in source
@@ -0,0 +1,20 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_selection_overlays_have_individual_clear_controls():
js = (ROOT / "static/js/document.js").read_text(encoding="utf-8")
css = (ROOT / "static/style.css").read_text(encoding="utf-8")
assert "function clearSelectionAt(index)" in js
assert "className = 'doc-selection-overlay-clear'" in js
assert "clearSelectionAt(selectionIndex);" in js
assert "const removed = _selections[index];" in js
assert "browserSelection.removeAllRanges();" in js
assert "Delete the persistent CSS highlight before checking whether any" in js
assert "doc-selection-rich-clear" in js
assert ".doc-selection-overlay-clear" in css
assert ".doc-selection-rich-clear" in css
assert "pointer-events: auto;" in css
+60 -1
View File
@@ -3,7 +3,7 @@ import json
import pytest
from scripts.generate_sft_environment_expansion import validate_case
from scripts.run_sft_environment_expansion import score_turn
from scripts.run_sft_environment_expansion import has_unrecovered_tool_failure, score_turn
def tool_start(action: str) -> dict:
@@ -57,6 +57,65 @@ def test_mcp_expected_tool_name_matches_runtime_short_name():
assert score_turn(turn, events, "Found it.") == []
@pytest.mark.parametrize("expected,observed", [
("edit_document", "update_document"),
("update_document", "edit_document"),
])
def test_active_document_writers_are_scored_by_function_not_name(expected, observed):
turn = {"prompt": "Expand this active draft.", "expected_tools": [expected]}
events = [{"type": "tool_start", "tool": observed, "command": "{}"}]
assert score_turn(turn, events, "Updated.") == []
def test_corrected_tool_retry_does_not_remain_a_functional_failure():
events = [
{"type": "tool_output", "tool": "edit_document", "exit_code": 1,
"error": True, "output": "Error: malformed arguments"},
{"type": "tool_output", "tool": "edit_document", "exit_code": 0,
"error": False, "output": '{"action":"edit"}'},
]
assert not has_unrecovered_tool_failure(events)
assert has_unrecovered_tool_failure(list(reversed(events)))
def test_isolated_replay_materializes_inventory_email_rows(tmp_path, monkeypatch):
from scripts import run_sft_environment_expansion as runner
from core.database import Base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
inventory = tmp_path / "environments.json"
inventory.write_text(json.dumps({"environments": [{
"owner": "sft_maya_ops",
"profile": {
"primary": "maya@example.test",
"secondary": "maya-research@example.test",
"primary_account": "Primary Inbox",
"secondary_account": "Ops Research",
},
"emails": [
{"uid": "1001", "account": "Primary Inbox", "subject": "Review"},
{"uid": "1010", "account": "Ops Research", "subject": "Research"},
],
}]}), encoding="utf-8")
monkeypatch.setattr(runner, "DATA_DIR", tmp_path / "isolated-data")
engine = create_engine(f"sqlite:///{tmp_path / 'fixture.db'}")
Base.metadata.create_all(engine)
monkeypatch.setattr(runner, "SessionLocal", sessionmaker(bind=engine))
installed = runner.install_fixture_environments(inventory)
assert installed["emails"] == 2
rows = json.loads(
(runner.DATA_DIR / "fixture_email_messages.json").read_text(encoding="utf-8")
)["messages"]
assert rows[0]["owner"] == "sft_maya_ops"
assert rows[0]["account_id"] == "primary-inbox"
assert rows[0]["account_email"] == "maya@example.test"
assert rows[1]["account_id"] == "secondary-inbox"
assert rows[1]["account_email"] == "maya-research@example.test"
assert rows[0]["body"].startswith("Fixture message for: Review")
def test_generated_case_rejects_multiple_tool_families_in_one_turn():
seed = {
"seed_family_id": "seed-1", "source_session_id": "session-1",
+3 -1
View File
@@ -125,8 +125,10 @@ def test_task_audit_resolves_the_owners_model_settings(monkeypatch):
seen = {}
def fake_resolve(owner=None):
def fake_resolve(owner=None, model_spec=None, endpoint_url=None):
seen["owner"] = owner
assert model_spec is None
assert endpoint_url is None
return "http://example.test", "audit-model", None, None
async def fake_run(key, skills_manager, names, url, model, headers, teacher, owner, workload="foreground"):
@@ -48,6 +48,47 @@ def test_scroll_restoration_cancels_the_stale_smooth_scroll_target():
assert "overflow-anchor: none" in STYLE
def test_large_tool_output_does_not_abort_enabled_auto_scroll():
"""Expanded browser/tool cards may add far more than 300px at once.
User intent is represented by ``autoScrollEnabled`` (wheel/touch/scroll
handlers turn it off). Geometry growth is not evidence that the user
scrolled away, so synthesis below a large tool timeline must still be
brought into view.
"""
smooth_step = UI.split("function _smoothScrollStep()", 1)[1].split(
"/**\n * Instant scroll to bottom", 1
)[0]
assert "!autoScrollEnabled" in smooth_step
assert "diff > 300" not in smooth_step
assert "box.scrollTop = current + diff * factor" in smooth_step
def test_programmatic_smooth_scroll_does_not_disable_itself():
"""The scroll event emitted by the lerp is not user intent."""
app = (ROOT / "static/app.js").read_text(encoding="utf-8")
listener = app.split(
"el('chat-history').addEventListener('scroll', uiModule.debounce", 1
)[1].split("}, 100));", 1)[0]
assert "uiModule.isAutoScrolling?.()" in listener
assert listener.index("uiModule.isAutoScrolling?.()") < listener.index(
"uiModule.setAutoScroll(atBottom)"
)
assert "export function isAutoScrolling()" in UI
def test_large_tool_scroll_fix_is_served_under_a_fresh_chat_module_key():
"""The fixed ui module is imported by chat.js, so stale chat.js is stale UI."""
app = (ROOT / "static/app.js").read_text(encoding="utf-8")
index = (ROOT / "static/index.html").read_text(encoding="utf-8")
key = "chat.js?v=20260916largetoolscroll2"
assert key in app
assert index.count(key) == 2
def test_stream_completion_does_not_focus_behind_open_document():
assert "else if (!document.getElementById('doc-editor-pane'))" in CHAT
assert "messageInput.focus({ preventScroll: true })" in CHAT
@@ -0,0 +1,11 @@
from src.task_scheduler import _is_sft_fixture_owner
def test_sft_accounts_are_background_scheduler_fixtures():
assert _is_sft_fixture_owner("sft_alex_creator")
assert _is_sft_fixture_owner("SFT_MAYA_OPS")
def test_real_and_ownerless_accounts_remain_scheduler_eligible():
assert not _is_sft_fixture_owner("pewds")
assert not _is_sft_fixture_owner(None)
@@ -51,3 +51,12 @@ def test_tasks_completed_view_exposes_active_paused_shortcuts():
assert "_taskStatusFilter = value;" in src
assert "_switchTab('tasks');" in src
assert "if (_activeTab === 'completed') _renderCompletedTaskStatusShortcuts();" in src
def test_completed_task_preview_links_research_runs_to_visual_report():
src = (ROOT / "static/js/tasks.js").read_text()
assert "entry.researchId" in src
assert "task-completed-report-btn" in src
assert "api/research/report/${encodeURIComponent(entry.researchId)}" in src
assert "task-completed-report-btn').forEach" in src
@@ -62,3 +62,12 @@ def test_find_info_online_forces_web_search_tools():
tools = ti.get_tools_for_query("find info online about crow box designs")
assert "web_search" in tools
assert "web_fetch" in tools
def test_hardware_aware_model_recommendation_includes_app_api():
ti = _index()
for query in (
"find the best model to run on my hardware",
"recommend a compatible model for this GPU",
):
assert "app_api" in ti.get_tools_for_query(query), query
+13
View File
@@ -0,0 +1,13 @@
from pathlib import Path
CHAT_JS = Path(__file__).parents[1] / "static" / "js" / "chat.js"
def test_tool_start_stops_initial_ttft_before_tool_execution():
source = CHAT_JS.read_text()
branch = source.index("} else if (json.type === 'tool_start') {")
mark_output = source.index("markFirstVisibleOutput();", branch)
close_thinking = source.index("_closeOpenThinkingMarkup(_isBg);", branch)
assert branch < mark_output < close_thinking
+241 -1
View File
@@ -4,6 +4,8 @@ import sys
from pathlib import Path
from types import SimpleNamespace
import pytest
import src.agent_loop as al
from src.agent_tools import ToolBlock
from src.tool_execution import NO_TOOL_SECURITY_CONTEXT, execute_tool_block
@@ -14,6 +16,7 @@ from src.tool_policy import (
detect_guide_only_turn,
web_search_enabled_for_turn,
)
from src.turn_contract import requested_capabilities
def _collect(gen):
@@ -116,6 +119,13 @@ def test_natural_browser_request_is_not_blocked_by_web_search_toggle():
)
def test_exact_url_fetch_only_contract_is_not_reported_as_web_disabled():
assert not al._web_search_unavailable_for_turn(
{"web"}, {"web_search"},
"Summarize https://example.com/report", None, None,
)
def test_unsubscribe_url_token_does_not_trigger_token_listing():
assert al._parse_qwen_explicit_admin_request(
"Use private_browser to open https://example.test/unsubscribe?token=abc123"
@@ -193,6 +203,19 @@ def test_sft_workspace_clamp_does_not_strip_private_web_tools():
assert "read_file" not in stripped
def test_sft_web_session_keeps_workspace_clamp_but_native_terminal_is_exempt():
assert al._workspace_tools_disabled_for_request(
"sft_alex_creator", {"surface": "web"}
)
assert not al._workspace_tools_disabled_for_request(
"sft_alex_creator",
{"surface": "odysseus-native", "terminal_agent": True},
)
assert not al._workspace_tools_disabled_for_request(
"pewds", {"surface": "web"}
)
def test_compact_prompt_says_current_turn_tools_override_stale_history():
prompt = al._assemble_prompt({"web_search", "ask_user"}, set(), compact=True)
@@ -266,6 +289,62 @@ def test_contextual_web_followup_trusts_topical_model_query():
assert "vram" in block.content.lower()
def test_contextual_browser_discovery_cannot_drop_prior_subject():
block = al._contextual_browser_opens_to_web_search(
ToolBlock(
"private_browser",
json.dumps({
"action": "batch",
"commands": [
["open", "https://example.com/grilled-cheese-recipe"],
["open", "https://example.org/best-grilled-cheese"],
],
}),
),
(
"use google maps and find closest coffee shop in todoroki "
"which one has grilled cheese sandwich on the menu?"
),
"which one has grilled cheese sandwich on the menu?",
)
assert block.tool_type == "web_search"
query = json.loads(block.content)["query"].lower()
assert "todoroki" in query
assert "coffee" in query
assert "grilled cheese" in query
def test_contextual_browser_current_page_interaction_is_preserved():
block = ToolBlock(
"private_browser",
json.dumps({"action": "click", "target": "@e14"}),
)
assert al._contextual_browser_opens_to_web_search(
block,
"browse coffee shops in todoroki open the second result",
"open the second result",
) == block
def test_contextual_browser_open_is_not_rewritten_to_unoffered_web_search():
block = ToolBlock(
"private_browser",
json.dumps({
"action": "open",
"url": "http://127.0.0.1:7011/static/test-fixtures/browser-catalog.html",
}),
)
assert al._contextual_browser_opens_to_web_search(
block,
"find orange sofas on the catalog page",
"try again on that page and compare the prices",
allow_web_search=False,
) == block
def test_contextual_web_followup_matrix_restores_missing_subject_anchor():
cases = [
(
@@ -587,6 +666,41 @@ def test_web_disabled_request_returns_feedback_without_calling_model(monkeypatch
assert chunks[-1] == "data: [DONE]\n\n"
def test_web_disabled_context_comparison_uses_prior_result_without_new_lookup(monkeypatch):
_patch_loop_basics(monkeypatch)
model_calls = []
async def _fake_stream(_candidates, messages, **kwargs):
model_calls.append((messages, kwargs.get("tools")))
yield _delta_chunk("Cedar costs less.")
yield "data: [DONE]\n\n"
monkeypatch.setattr(al, "stream_llm_with_fallback", _fake_stream, raising=False)
chunks = _collect(
al.stream_agent_loop(
"https://api.openai.com/v1",
"gpt-test",
[
{"role": "user", "content": "Find the orange sofas and their prices."},
{"role": "assistant", "content": "Cedar is $219 and Harbor is $349.", "metadata": {
"tool_events": [{"tool": "private_browser", "exit_code": 0}],
}},
{"role": "user", "content": "Which of those costs less?"},
],
max_rounds=1,
disabled_tools=set(WEB_ACCESS_TOOL_NAMES),
)
)
assert model_calls
assert any(event.get("delta") == "Cedar costs less." for event in _events(chunks))
assert not any(
event.get("content", "").startswith("Web access is disabled")
for event in _events(chunks)
)
def test_web_disabled_mixed_file_intent_still_calls_model(monkeypatch):
_patch_loop_basics(monkeypatch)
model_calls = []
@@ -2183,6 +2297,49 @@ def test_recurring_event_lookup_routes_calendar_not_tasks():
assert "manage_tasks" not in tools
def test_recurring_automation_lifecycle_routes_tasks_not_calendar():
tools = al._qwen38_router_tool_names(
"Set up a recurring task that runs every Monday morning to summarize "
"my open notes, then pause it, resume it later, and finally remove it."
)
assert tools == {"manage_tasks"}
def test_recurring_meeting_routes_calendar_not_tasks():
tools = al._qwen38_router_tool_names(
"Schedule a recurring team meeting every Monday morning."
)
assert "manage_calendar" in tools
assert "manage_tasks" not in tools
def test_document_title_containing_notes_does_not_offer_notes_product():
prompt = (
"Create a document titled 'Q3 planning feedback notes' with exactly one sentence. "
"Then search the document library, suggest a replacement, and delete that document."
)
assert requested_capabilities(prompt) == frozenset({"documents"})
def test_compound_document_lifecycle_is_not_single_action_terminal():
assert al._request_has_compound_actions(
"Create a document, search for it, suggest a revision, then delete it."
)
assert not al._request_has_compound_actions(
"Create a document titled Weekly status."
)
def test_two_source_comparison_is_not_single_fetch_terminal():
assert al._request_has_compound_actions(
"Open https://example.com/a and https://example.org/b, compare their "
"evidence, and cite both sources."
)
def test_calendar_lookup_requires_fresh_tool_even_before_schema_is_added():
assert al._calendar_lookup_requires_fresh_tool(
"show my recurring trash events",
@@ -2496,6 +2653,21 @@ def test_show_skill_note_memory_requests_do_not_open_panels():
)
@pytest.mark.parametrize("message,theme", [
("set the theme to dark", "dark"),
("go dark mode pls", "dark"),
("hmm actually switch it back to light", "light"),
])
def test_explicit_theme_change_parser_binds_set_action(message, theme):
assert al._parse_explicit_theme_change_request(message) == (
"ui_control", json.dumps({"action": "set_theme", "name": theme})
)
def test_explicit_theme_change_parser_does_not_steal_discussion():
assert al._parse_explicit_theme_change_request("is dark mode easier on the eyes?") is None
def test_personal_task_list_routes_to_task_manager():
intent = al._classify_agent_request([], "show me my tasks")
assert "notes_calendar_tasks" in intent["domains"]
@@ -3113,6 +3285,12 @@ def test_explicit_email_search_uses_named_account():
}
def test_email_inventory_projection_is_not_parsed_as_search_query():
assert al._parse_explicit_email_search_tool(
"List my latest three inbox emails with sender and subject."
) is None
def test_email_immediate_send_recognizes_explicit_email_and_reply_wording():
assert al._email_immediate_send_requested("Send an email now to alex@example.com.")
assert al._email_immediate_send_requested("Send a reply now to UID 10.")
@@ -3127,6 +3305,37 @@ def test_email_mixed_send_and_review_policy_preserves_drafts():
assert al._email_draft_review_requested(request)
def test_explicit_read_only_email_request_forbids_mutation_tools_only():
request = (
"Find the most recent inbox message and report its sender. "
"Do not send, draft, or modify anything."
)
for tool in (
"mcp__email__draft_email_reply",
"mcp__email__send_email",
"mcp__email__mark_email_read",
"mcp__email__delete_email",
):
assert al._email_mutation_forbidden(request, tool)
assert not al._email_mutation_forbidden(request, "mcp__email__list_emails")
assert not al._email_mutation_forbidden(request, "mcp__email__read_email")
assert not al._email_mutation_forbidden(
"Find UID 702 and draft a reply saying thanks; do not send it.",
"mcp__email__draft_email_reply",
)
assert not al._email_mutation_forbidden(
"Draft an email suggesting the free slot. Do not modify any calendar event.",
"mcp__email__draft_email",
)
assert not al._email_mutation_forbidden(
"Draft an email suggesting the slot. Do not send the email and do not "
"create, delete, or modify any calendar event.",
"mcp__email__draft_email",
)
def test_qwen_explicit_session_current_chat_actions_use_manage_session():
assert al._parse_qwen_explicit_session_action(
"Rename this current audit chat to manage-session-audit-abc Use the tool directly and report the result.",
@@ -3517,6 +3726,20 @@ def test_qwen_model_registry_still_uses_list_models():
) == ("list_models", "")
def test_qwen_hardware_model_recommendation_uses_hwfit_scan():
tool, content = al._parse_qwen_explicit_admin_request(
"Find the best model to run on my hardware"
)
assert tool == "app_api"
assert json.loads(content) == {
"action": "call",
"method": "GET",
"path": "/api/hwfit/models",
"query": {"fit_only": "true", "limit": 10, "sort": "fit"},
}
def test_model_endpoints_take_precedence_over_model_catalog():
assert al._parse_qwen_explicit_admin_request(
"List configured model endpoints and summarize which ones are enabled."
@@ -3681,7 +3904,8 @@ def test_native_media_workspace_uses_bounded_non_coding_guidance():
rules = al._native_media_workspace_rules("/workspace")
assert "Workspace media mode" in rules
assert "make `inspect_media` your first inspection call" in rules
assert "use `extract_text` first" in rules
assert "use `inspect_media` first" in rules
assert "Do not use bash/Python/ffprobe/OpenCV/ffmpeg" in rules
assert "one bounded overview" in rules
assert "never call it inaccessible without a failed tool result" in rules
@@ -3704,6 +3928,22 @@ def test_compact_native_media_analysis_removes_coding_noise():
assert selected == {"bash", "inspect_media", "ls", "python", "read_file"}
def test_compact_native_media_analysis_clamps_explicit_ocr_to_native_tool():
selected = al._compact_native_media_analysis_tools(
{
"apply_patch", "extract_text", "inspect_media", "ls", "python",
"read_file", "transcribe_media", "write_file",
},
text=(
"Use local OCR to extract the exact visible text from "
"/workspace/tests/fixtures/vl/quarterly-dashboard.png."
),
media_inputs=["/workspace/tests/fixtures/vl/quarterly-dashboard.png"],
)
assert selected == {"extract_text"}
def test_native_coding_turn_keeps_coding_workspace_guidance():
messages = [{"role": "user", "content": "Fix the parser in this repository"}]
context = {"surface": "odysseus-native", "terminal_agent": True}
+178 -3
View File
@@ -1,5 +1,6 @@
from src.tool_routing_experiment import experiment_mode, select_experiment_inventory
from src.turn_contract import resolve_turn_contract, resolve_full_inventory_contract
from src.turn_contract import RequiredReadOperation, resolve_turn_contract, resolve_full_inventory_contract
from dataclasses import replace
from src.tool_schemas import FUNCTION_TOOL_SCHEMAS
from src.tool_policy import ToolPolicy
from src.clean_agent_preview import sealed_read_arguments
@@ -58,6 +59,172 @@ def test_url_offers_page_and_video_readers_without_forcing_a_call(prompt):
assert not result.permits('web_fetch') and not result.permits('youtube_tool')
def test_model_choice_preserves_router_sealed_safe_read_only():
from src.tool_routing_experiment import MODEL_CHOICE_MODE
from src.turn_contract import RequiredReadOperation
policy = ToolPolicy()
inventory = resolve_full_inventory_contract(
schemas=FUNCTION_TOOL_SCHEMAS, policy=policy,
)
operation = RequiredReadOperation(
'manage_skills', {'action': 'search', 'query': 'email or docs'}, 3,
)
routed = resolve_turn_contract(
capabilities={'skills'}, schemas=FUNCTION_TOOL_SCHEMAS, policy=policy,
required_read_operation=operation,
)
model_choice = select_experiment_inventory(
inventory, routed, [], MODEL_CHOICE_MODE,
user_text='any skills about email or docs?',
)
assert model_choice.required_read_operation == operation
assert sealed_read_arguments(
model_choice,
'manage_skills',
{'action': 'list', 'command': 'limit 3'},
) == {'action': 'search', 'query': 'email or docs'}
# Other ablation modes retain their original unforced semantics.
recent = select_experiment_inventory(inventory, routed, [], 'recent')
assert recent.required_read_operation is None
def test_model_choice_preserves_explicit_single_image_editor_requirement():
from src.tool_routing_experiment import MODEL_CHOICE_MODE
policy = ToolPolicy()
inventory = resolve_full_inventory_contract(
schemas=FUNCTION_TOOL_SCHEMAS, policy=policy,
)
routed = resolve_turn_contract(
capabilities={'image_editing'}, schemas=FUNCTION_TOOL_SCHEMAS, policy=policy,
)
gallery_history = [{'role': 'assistant', 'metadata': {'tool_events': [{
'tool': 'app_api',
'command': {'action': 'call', 'method': 'GET', 'path': '/api/gallery/library'},
'exit_code': 0,
}]}}]
result = select_experiment_inventory(
inventory, routed, gallery_history, MODEL_CHOICE_MODE,
user_text='upscale that image 2x',
)
assert result.required == {'edit_image'}
assert result.permits('edit_image')
def test_model_choice_preserves_explicit_cookbook_lifecycle_requirement():
from src.tool_routing_experiment import MODEL_CHOICE_MODE
policy = ToolPolicy()
inventory = resolve_full_inventory_contract(
schemas=FUNCTION_TOOL_SCHEMAS, policy=policy,
)
routed = resolve_turn_contract(
capabilities={'cookbook_admin'}, schemas=FUNCTION_TOOL_SCHEMAS,
policy=policy, selected_tools={'serve_preset'},
required_tools={'serve_preset'},
)
result = select_experiment_inventory(
inventory, routed, [], MODEL_CHOICE_MODE,
user_text='Launch my SD3.5 preset.',
)
assert result.required == {'serve_preset'}
assert result.permits('serve_preset')
@pytest.mark.parametrize('tool,prompt', [
('web_fetch', 'open that link and tell me the main heading'),
('web_search', 'cross-check it with another credible source and cite the URL'),
])
def test_model_choice_preserves_resolved_web_read_requirement(tool, prompt):
from src.tool_routing_experiment import MODEL_CHOICE_MODE
policy = ToolPolicy()
inventory = resolve_full_inventory_contract(
schemas=FUNCTION_TOOL_SCHEMAS, policy=policy,
)
routed = resolve_turn_contract(
capabilities={'search_browser'}, schemas=FUNCTION_TOOL_SCHEMAS,
policy=policy, selected_tools={tool}, required_tools={tool},
)
result = select_experiment_inventory(
inventory, routed, [], MODEL_CHOICE_MODE, user_text=prompt,
)
assert result.required == {tool}
expected = {tool, 'private_browser'}
if tool == 'web_search':
expected.add('web_fetch')
assert result.offered == expected
def test_model_choice_preserves_explicit_cross_model_call_requirement():
from src.tool_routing_experiment import MODEL_CHOICE_MODE
policy = ToolPolicy()
inventory = resolve_full_inventory_contract(
schemas=FUNCTION_TOOL_SCHEMAS, policy=policy,
)
routed = resolve_turn_contract(
capabilities={'sessions'}, schemas=FUNCTION_TOOL_SCHEMAS,
policy=policy, selected_tools={'chat_with_model'},
required_tools={'chat_with_model'},
)
result = select_experiment_inventory(
inventory, routed, [], MODEL_CHOICE_MODE,
user_text='ask model anthropic/claude-sonnet-4.5 to review this',
)
assert result.required == {'chat_with_model'}
assert result.offered == {'chat_with_model'}
def test_model_choice_preserves_explicit_chat_history_search_requirement():
from src.tool_routing_experiment import MODEL_CHOICE_MODE
policy = ToolPolicy()
inventory = resolve_full_inventory_contract(
schemas=FUNCTION_TOOL_SCHEMAS, policy=policy,
)
routed = resolve_turn_contract(
capabilities={'memory'}, schemas=FUNCTION_TOOL_SCHEMAS,
policy=policy, selected_tools={'search_chats'},
required_tools={'search_chats'},
)
result = select_experiment_inventory(
inventory, routed, [], MODEL_CHOICE_MODE,
user_text='search my old chats for tool grounding',
)
assert result.required == {'search_chats'}
assert result.offered == {'search_chats'}
def test_gallery_recheck_does_not_confuse_upscaled_noun_with_a_new_edit():
from src.tool_routing_experiment import MODEL_CHOICE_MODE
policy = ToolPolicy()
inventory = resolve_full_inventory_contract(
schemas=FUNCTION_TOOL_SCHEMAS, policy=policy,
)
operation = RequiredReadOperation('app_api', {
'action': 'call', 'method': 'GET', 'path': '/api/gallery/library',
})
routed = resolve_turn_contract(
capabilities={'cookbook_admin'}, schemas=FUNCTION_TOOL_SCHEMAS, policy=policy,
required_read_operation=operation,
)
routed = replace(
routed, capabilities=frozenset({'image_editing'}),
active_capabilities=frozenset({'image_editing'}),
)
history = [{'role': 'assistant', 'metadata': {'tool_events': [{
'tool': 'app_api',
'command': {'action': 'call', 'method': 'GET', 'path': '/api/gallery/library'},
'exit_code': 0,
}]}}]
result = select_experiment_inventory(
inventory, routed, history, MODEL_CHOICE_MODE,
user_text='list the gallery again and confirm the new upscaled record shows up',
)
assert result.required_read_operation == operation
assert result.required == {'app_api'}
assert result.permits('app_api')
assert 'edit_image' not in result.required
def test_recognized_browser_request_survives_empty_family_classification():
from src.tool_routing_experiment import MODEL_CHOICE_MODE
policy = ToolPolicy(disabled_tools=frozenset({'web_search', 'web_fetch'}))
@@ -75,7 +242,10 @@ def test_recognized_browser_request_survives_empty_family_classification():
browser_requested=True).permits('private_browser')
@pytest.mark.parametrize('text', ['person@example.org', '/workspace/local/report.pdf', '3.14159'])
@pytest.mark.parametrize('text', [
'person@example.org', '/workspace/local/report.pdf', '3.14159',
'shellcheck.txt', 'notes.md', 'results.json',
])
def test_web_reference_does_not_match_email_local_path_or_decimal(text):
from src.tool_routing_experiment import WEB_REFERENCE
assert not WEB_REFERENCE.search(text)
@@ -256,7 +426,12 @@ async def test_experiment_request_uses_compact_tools_auto_choice_and_no_thinking
)]
assert len(requests) == 1
assert requests[0]['chat_template_kwargs'] == {'enable_thinking': False}
assert 'tool_choice' not in requests[0]
if mode == 'recent_model_choice':
assert requests[0]['tool_choice'] == {
'type': 'function', 'function': {'name': 'manage_notes'},
}
else:
assert 'tool_choice' not in requests[0]
instruction = requests[0]['messages'][0]['content']
assert ('URL words and titles are not page evidence' in instruction) == (mode == 'recent_model_choice')
expected = {'manage_notes', 'manage_calendar'} if mode == 'all' else {'manage_notes'}
+25
View File
@@ -51,6 +51,31 @@ def test_web_search_native_command_arg_repairs_to_query_before_required_check()
assert block.content == "PISA 2022 OECD report Japan Sweden scores mathematics reading science"
def test_web_search_native_query_wins_over_redundant_tool_name_command() -> None:
block = function_call_to_tool_block(
"web_search",
'{"command":"web_search","query":"gpt-4 official source site:openai.com","time_filter":"year"}',
)
assert block is not None
assert block.tool_type == "web_search"
assert json.loads(block.content) == {
"query": "gpt-4 official source site:openai.com",
"time_filter": "year",
}
def test_create_document_human_language_code_does_not_pollute_body() -> None:
block = function_call_to_tool_block(
"create_document",
'{"title":"Repair Notes","language":"en","content":"The harness is fixed."}',
)
assert block is not None
assert block.tool_type == "create_document"
assert block.content == "Repair Notes\nThe harness is fixed."
def test_web_search_native_q_arg_repairs_to_query_before_required_check() -> None:
block = function_call_to_tool_block("web_search", '{"q":"latest Apple Mac mini specs"}')
File diff suppressed because it is too large Load Diff
+20 -16
View File
@@ -40,7 +40,7 @@ def forbidden_inference_and_execution(monkeypatch):
@pytest.mark.asyncio
@pytest.mark.parametrize("case", ["calendar", "catalog", "unknown"])
@pytest.mark.parametrize("case", ["calendar", "catalog"])
async def test_actual_generator_reports_unavailable_before_inference(
case, forbidden_inference_and_execution,
):
@@ -54,10 +54,6 @@ async def test_actual_generator_reports_unavailable_before_inference(
selected = contract({"cookbook_admin"}, required_tools={"list_models"},
policy=ToolPolicy(disabled_tools=frozenset({"list_models"})))
missing = "list_models"
else:
selected = contract({"unknown"})
missing = "capability:unknown"
chunks = [chunk async for chunk in stream_agent_loop(
"https://inference.invalid", "unused-model",
[{"role": "user", "content": "Perform the requested action"}],
@@ -72,18 +68,23 @@ async def test_actual_generator_reports_unavailable_before_inference(
}
failure = json.loads(chunks[1].removeprefix("data: "))
assert set(failure) == {"delta"}
if case == "unknown":
assert "Which action" in failure["delta"]
assert "haven’t called any tools" in failure["delta"]
assert missing in selected.audit()["unavailable"]
else:
assert "can’t perform" in failure["delta"]
assert "unavailable" in failure["delta"]
assert missing in failure["delta"]
assert "haven’t substituted another tool" in failure["delta"]
assert "can’t perform" in failure["delta"]
assert "unavailable" in failure["delta"]
assert missing in failure["delta"]
assert "haven’t substituted another tool" in failure["delta"]
assert chunks[2] == "data: [DONE]\n\n"
def test_unknown_contract_reaches_model_instead_of_forced_clarification():
from src.agent_loop import _blocks_before_inference
assert not _blocks_before_inference(contract({"unknown"}))
assert _blocks_before_inference(contract(
{"calendar"},
policy=ToolPolicy(disabled_tools=frozenset({"manage_calendar"})),
))
@pytest.mark.asyncio
@pytest.mark.parametrize("use_bridge", [False, True])
async def test_stream_bridge_binds_contract_for_actual_generator_and_resets(
@@ -98,7 +99,9 @@ async def test_stream_bridge_binds_contract_for_actual_generator_and_resets(
outer_bridge = AgentExecutionBridge(transport, frozenset({"bash"}), name="outer")
inner_bridge = AgentExecutionBridge(transport, frozenset({"web_search"}), name="inner")
outer = contract({"notes"})
selected = contract({"unknown"})
# Use an actually unavailable capability to stop before inference.
# Unknown intent is deliberately allowed to reach the model.
selected = contract({"calendar"}, policy=ToolPolicy(disabled_tools=frozenset({"manage_calendar"})))
previous = active_turn_contract(), get_active_execution_bridge()
with bind_turn_contract(outer), bind_execution_bridge(outer_bridge):
chunks = []
@@ -233,7 +236,8 @@ async def test_direct_agent_caller_binds_contract_and_restores_context(
):
from src.agent_loop import stream_agent_loop
outer, selected = contract({"notes"}), contract({"unknown"})
outer = contract({"notes"})
selected = contract({"calendar"}, policy=ToolPolicy(disabled_tools=frozenset({"manage_calendar"})))
previous = active_turn_contract()
with bind_turn_contract(outer):
stream = stream_agent_loop(
+453
View File
@@ -10,6 +10,7 @@ from src.tool_schemas import FUNCTION_TOOL_SCHEMAS
from src.turn_contract import (
RequiredReadOperation, TurnContract, requested_capabilities,
required_read_operation_for_request, resolve_turn_contract,
selected_tools_for_request,
)
@@ -21,6 +22,357 @@ def resolve(message, *, history=(), **kwargs):
)
@pytest.mark.parametrize(
"message,tool,action,maximum",
[
("can i see what documents are in the editor? a few titles is enough", "manage_documents", "list", 3),
("pull up my noes, just a quick look", "manage_notes", "list", None),
("I need a quick look at my calendar events. Three titles max, read-only. Don't modify or message anyone.", "manage_calendar", "list_events", 3),
("cookbook servers?", "list_cookbook_servers", None, None),
("Quick calendar listing: max three titles. Read-only inspection.", "manage_calendar", "list_events", 3),
("hey can u glance at my calendar? just want a few event titles, nothing else", "manage_calendar", "list_events", 3),
("morning — anythin on my calendar today? just the headlines please", "manage_calendar", "list_events", None),
("What have you got stored in memory for me? Three bullets, no changes.", "manage_memory", "list", 3),
("gimme my notes, top three titles pls", "manage_notes", "list", 3),
("show me my calendar — couple titles, no edits", "manage_calendar", "list_events", 3),
("List stored mems, max 3, no changes.", "manage_memory", "list", 3),
("pull my calendar events, cap at three, read only", "manage_calendar", "list_events", 3),
("rundown my calendar, three at most", "manage_calendar", "list_events", 3),
],
)
def test_natural_safe_inventory_requests(message, tool, action, maximum):
operation = required_read_operation_for_request(message)
assert operation is not None
assert operation.tool == tool
assert operation.args.get("action") == action
assert operation.max_items == maximum
assert requested_capabilities(message) == {
"cookbook_admin" if tool == "list_cookbook_servers" else {
"manage_documents": "documents",
"manage_notes": "notes",
"manage_calendar": "calendar",
"manage_memory": "memory",
}[tool]
}
def test_natural_document_prefix_search_seals_query():
message = (
"im trying to find a doc i made earlier, title starts with "
"'Suggestion audit 20260829_220912-177730bb'"
)
operation = required_read_operation_for_request(message)
assert operation == RequiredReadOperation(
"manage_documents",
{"action": "list", "search": "Suggestion audit 20260829_220912-177730bb"},
)
assert requested_capabilities(message) == {"documents"}
def test_read_only_unsubscribe_scan_seals_account_scope():
message = (
"go through my Primary inbox and flag newsletters or mailing lists that "
"give an unsubscribe option. dont change anything yet."
)
operation = required_read_operation_for_request(message)
assert operation == RequiredReadOperation(
"scan_email_unsubscribes",
{"folder": "INBOX", "account": "Primary Inbox"},
)
assert requested_capabilities(message) == {"email"}
def test_unsubscribe_scan_accepts_inbox_for_newsletters_wording():
message = (
"scan my primary inbox for newsletters or mailing lists that offer an "
"unsubscribe option"
)
operation = required_read_operation_for_request(message)
assert operation == RequiredReadOperation(
"scan_email_unsubscribes",
{"folder": "INBOX", "account": "Primary Inbox"},
)
assert requested_capabilities(message) == {"email"}
@pytest.mark.parametrize(("message", "expected"), [
(
"what notes do i have? keep it to a handful of titles",
RequiredReadOperation("manage_notes", {"action": "list"}, 3),
),
(
"what events do i have? keep it to a few titles max",
RequiredReadOperation("manage_calendar", {"action": "list_events"}, 3),
),
])
def test_natural_personal_inventory_inherits_approximate_limit(message, expected):
assert required_read_operation_for_request(message) == expected
@pytest.mark.parametrize("message", [
"show me whats scheduled, only a handful of titles pls",
"whats coming up? short answer, 3 titles tops",
"what have I got coming up over the next seven days?",
])
def test_natural_schedule_inventory_is_a_bounded_calendar_read(message):
operation = required_read_operation_for_request(message)
assert operation is not None
assert operation.tool == "manage_calendar"
assert operation.args == {"action": "list_events"}
if "next seven" not in message:
assert operation.max_items == 3
def test_read_only_contact_resolution_ignores_presentation_suffix():
assert required_read_operation_for_request(
"Resolve Priya Shah in my contacts, read only please",
) == RequiredReadOperation(
"manage_contact", {"action": "search", "query": "Priya Shah"},
)
def test_exact_read_repeat_inherits_same_limit_clause():
history = [
{"role": "user", "content": "list my calendar events, three max"},
{"role": "assistant", "content": "Three events", "metadata": {
"tool_events": [{
"tool": "manage_calendar", "command": {"action": "list_events"},
"exit_code": 0,
}],
}},
]
assert required_read_operation_for_request(
"do that again, same limit", history,
) == RequiredReadOperation("manage_calendar", {"action": "list_events"}, 3)
def test_exact_skill_repeat_accepts_for_me_clause_and_new_limit():
history = [
{"role": "user", "content": "list my skills, only the first three"},
{"role": "assistant", "content": "Three skills", "metadata": {
"tool_events": [{
"tool": "manage_skills", "command": {"action": "list"},
"exit_code": 0,
}],
}},
]
assert required_read_operation_for_request(
"k, list those again for me, max three", history,
) == RequiredReadOperation("manage_skills", {"action": "list"}, 3)
@pytest.mark.parametrize(("message", "expected"), [
(
"which search backend am i on right now?",
RequiredReadOperation(
"manage_settings", {"action": "get", "key": "search_provider"},
),
),
(
"what time filter is my search set to by default?",
RequiredReadOperation("manage_settings", {"action": "list"}),
),
(
"show me the whole search group",
RequiredReadOperation("manage_settings", {"action": "list"}),
),
])
def test_search_configuration_questions_seal_settings_reads(message, expected):
assert required_read_operation_for_request(message) == expected
assert requested_capabilities(message) == {"cookbook_admin"}
def test_common_official_typo_keeps_web_lookup():
message = "i need an offical link for GPT-4, look it up on the web"
assert requested_capabilities(message) == {"search_browser"}
assert selected_tools_for_request(message) == {"web_search"}
def test_calendar_repeat_preserves_executed_range_and_new_limit():
history = [{
"role": "assistant",
"metadata": {"tool_events": [{
"tool": "manage_calendar",
"command": {
"action": "list_events",
"start": "2026-09-12T00:00:00",
"end": "2026-09-13T00:00:00",
},
"exit_code": 0,
}]},
}]
operation = required_read_operation_for_request(
"thanks, do that again but stick to three and don't modify anything",
history,
)
assert operation == RequiredReadOperation(
"manage_calendar",
{
"action": "list_events",
"start": "2026-09-12T00:00:00",
"end": "2026-09-13T00:00:00",
},
3,
)
@pytest.mark.parametrize(
"message,query",
[
("hey can you search my skill library for anything about email workflows?", "email workflows"),
("look thru my skills for email workflow guidance", "email workflow guidance"),
],
)
def test_skill_library_search_owns_incidental_email_word(message, query):
assert required_read_operation_for_request(message) == RequiredReadOperation(
"manage_skills", {"action": "search", "query": query},
)
assert requested_capabilities(message) == {"skills"}
def test_approximate_calendar_count_is_a_contract_limit():
assert required_read_operation_for_request(
"can u check my calendar and gimme like three event titles"
) == RequiredReadOperation(
"manage_calendar", {"action": "list_events"}, 3,
)
def test_explicit_email_attachment_read_is_sealed_and_available():
message = "open attachment 0 on email UID 112 and tell me what it is"
assert selected_tools_for_request(message) == {"download_attachment"}
assert requested_capabilities(message) == {"email"}
assert required_read_operation_for_request(message) == RequiredReadOperation(
"download_attachment", {"uid": "112", "index": 0},
)
contract = resolve(
message,
selected_tools=selected_tools_for_request(message),
)
assert {
name.rsplit("__", 1)[-1] for name in contract.offered
if name not in {"ask_user", "update_plan"}
} == {
"download_attachment"
}
assert {name.rsplit("__", 1)[-1] for name in contract.required} == {
"download_attachment"
}
@pytest.mark.parametrize("message", [
"pull up attachment 0 from message 112",
"theres a creator payout sample attached to email 112, can u read it for me",
])
def test_natural_email_attachment_references_are_sealed(message):
assert selected_tools_for_request(message) == {"download_attachment"}
assert requested_capabilities(message) == {"email"}
assert required_read_operation_for_request(message) == RequiredReadOperation(
"download_attachment", {"uid": "112", "index": 0},
)
def test_quick_web_lookup_with_official_link_selects_search():
message = "quick web lookup for gpt-4, one official link is enough."
assert selected_tools_for_request(message) == {"web_search"}
assert requested_capabilities(message) == {"search_browser"}
@pytest.mark.parametrize(("message", "expected"), [
(
"whats on my agenda today? just the titles, dont change anything",
RequiredReadOperation("manage_calendar", {"action": "list_events"}),
),
(
"wich agent tools are switched off right now?",
RequiredReadOperation("manage_settings", {"action": "list_tools"}),
),
(
"show me who is on my blocked senders list",
RequiredReadOperation("manage_email_state", {"action": "list_blocked"}),
),
(
"quick check — who am i blocking in email",
RequiredReadOperation("manage_email_state", {"action": "list_blocked"}),
),
(
"something feels off in my inbox — can u check for spam?",
RequiredReadOperation("scan_spam", {}),
),
(
"wots the state of my cookbook model servers — anything crashed or stuck?",
RequiredReadOperation("list_served_models", {}),
),
(
"can u list my notes? just a cpl titles, dont change antyhing",
RequiredReadOperation("manage_notes", {"action": "list"}, 2),
),
(
"what notes do i have? keep it short — few titles",
RequiredReadOperation("manage_notes", {"action": "list"}, 3),
),
(
"show me my saved cookbook serve presets — but dont launch anything",
RequiredReadOperation("list_serve_presets", {}),
),
(
"find me the skill about email workflows, however you label it",
RequiredReadOperation("manage_skills", {"action": "search", "query": "email workflows"}),
),
(
"can u look through my skills and see if anything covers email workflows?",
RequiredReadOperation("manage_skills", {"action": "search", "query": "email workflows"}),
),
(
"can you look thru my skils and see if any of them cover email workflows?",
RequiredReadOperation("manage_skills", {"action": "search", "query": "email workflows"}),
),
(
"list my cal events please — three titles, read only",
RequiredReadOperation("manage_calendar", {"action": "list_events"}, 3),
),
(
"resolve priya shah in my contacts",
RequiredReadOperation("manage_contact", {"action": "search", "query": "priya shah"}),
),
])
def test_unseen_safe_inventory_wording_is_sealed(message, expected):
assert required_read_operation_for_request(message) == expected
assert requested_capabilities(message)
def test_explicit_teacher_review_uses_teacher_tool():
message = "teacher review pls, check this for tool-grouding: the action worked"
assert selected_tools_for_request(message) == {"ask_teacher"}
assert requested_capabilities(message) == {"cookbook_admin"}
contract = resolve(message, selected_tools=selected_tools_for_request(message))
assert "ask_teacher" in contract.offered
def test_sent_mail_followup_switches_from_contact_to_email():
history = [{"role": "assistant", "metadata": {"tool_events": [{
"tool": "manage_contact", "exit_code": 0, "error": False,
}]}}]
assert requested_capabilities(
"did i ever actually send them anything?", history,
) == {"email"}
def test_titled_document_editor_request_keeps_data_and_ui_tools():
message = (
"i had a doc going earlier called 'SFT Harness Repair Notes' — "
"pull it up in the editor for me"
)
assert selected_tools_for_request(message) == {
"manage_documents", "ui_control",
}
assert requested_capabilities(message) == {"documents", "ui"}
assert required_read_operation_for_request(message) == RequiredReadOperation(
"manage_documents",
{"action": "list", "search": "SFT Harness Repair Notes"},
)
@pytest.mark.parametrize("message,tool,args", [
("List my notes", "manage_notes", {"action": "list"}),
("Show my calendar events", "manage_calendar", {"action": "list_events"}),
@@ -43,6 +395,15 @@ def resolve(message, *, history=(), **kwargs):
("List my saved research reports", "manage_research", {"action": "list"}),
("List my chat sessions", "list_sessions", {}),
("List my contacts", "manage_contact", {"action": "list"}),
(
"Find the best model to run on my hardware",
"app_api",
{
"action": "call",
"method": "GET",
"path": "/api/hwfit/models?fit_only=true&limit=10&sort=fit",
},
),
("Read note id abcd1234", "manage_notes", {"action": "view", "id": "abcd1234"}),
("Read document id doc-123", "manage_documents", {"action": "read", "document_id": "doc-123"}),
("View skill id release-check", "manage_skills", {"action": "view", "name": "release-check"}),
@@ -63,6 +424,21 @@ def test_explicit_reads_resolve_real_schema_operations(message, tool, args):
assert contract.audit()["required_read_operation"] == operation.audit()
def test_research_completion_followup_lists_reports_from_recent_research_context():
history = [
{"role": "assistant", "metadata": {"tool_events": [{
"tool": "trigger_research", "exit_code": 0, "error": False,
}]}},
]
operation = required_read_operation_for_request(
"when its done, how do i find it again?", history,
)
assert operation == RequiredReadOperation("manage_research", {"action": "list"})
assert resolve(
"when its done, how do i find it again?", history=history,
).required == {"manage_research"}
def test_explicit_limit_is_presentation_bound_and_survives_exact_repeat():
message = "Show my first 3 notes"
operation = required_read_operation_for_request(message)
@@ -72,6 +448,13 @@ def test_explicit_limit_is_presentation_bound_and_survives_exact_repeat():
assert resolve("Show them again", history=history).required_read_operation == operation
def test_app_api_cannot_be_sealed_as_an_arbitrary_admin_read():
with pytest.raises(ValueError, match="limited to declared GET endpoints"):
RequiredReadOperation("app_api", {
"action": "call", "method": "POST", "path": "/api/cookbook/state",
})
@pytest.mark.parametrize("followup", ["Do that again", "Repeat it", "Same list again", "Again!"])
def test_exact_repeat_resolves_nearest_user_read_and_ignores_assistant_suggestions(followup):
history = [SimpleNamespace(role="user", content="Read document id doc-7"),
@@ -127,6 +510,23 @@ def test_repeat_chain_accepts_history_including_current_turn():
assert required_read_operation_for_request("Do that again", iter(history)) == RequiredReadOperation("list_email_accounts")
def test_typoed_return_to_latest_email_inventory_after_another_family():
history = [
{"role": "user", "content": "whats my emaol adress?"},
{"role": "assistant", "content": "Two accounts.", "metadata": {
"tool_events": [{"tool": "list_email_accounts", "exit_code": 0}],
}},
{"role": "user", "content": "show my notse now"},
{"role": "assistant", "content": "Your notes.", "metadata": {
"tool_events": [{"tool": "manage_notes", "exit_code": 0}],
}},
]
assert required_read_operation_for_request(
"back to emaol show 2 latest", history
) == RequiredReadOperation("list_emails", {"max_results": 2}, 2)
@pytest.mark.parametrize("intervening", ["Delete my notes", "What is a prime number?", "Search the web for notes"])
def test_repeat_does_not_reach_past_a_new_user_intent(intervening):
history = [{"role": "user", "content": "List my notes"},
@@ -289,6 +689,37 @@ def test_safe_suffix_limit_is_not_lost_when_repeat_inherits_plain_request():
assert required_read_operation_for_request("List my notes. Return at most 5 titles. Return at most three titles.").max_items == 3
@pytest.mark.parametrize("message,tool", [
("Can you show my notes? I only need three titles. Read-only, keep it short.", "manage_notes"),
("what notes have i got? 3 titles tops", "manage_notes"),
("list my memorries, three short ones, read-only please", "manage_memory"),
])
def test_natural_bounded_read_wording_keeps_three_item_contract(message, tool):
assert required_read_operation_for_request(message) == RequiredReadOperation(
tool, {"action": "list"}, max_items=3,
)
def test_natural_bounded_repeat_inherits_three_item_contract():
history = [{"role": "user", "content": "Show my scheduled tasks"}]
assert required_read_operation_for_request(
"list those again, three tops", history,
) == RequiredReadOperation("manage_tasks", {"action": "list"}, max_items=3)
@pytest.mark.parametrize("message", [
"list those again, same three",
"same again but cap it at three, read only",
"again but only show 3",
"same as before, three tops",
])
def test_additional_natural_bounded_repeats_inherit_contract(message):
history = [{"role": "user", "content": "Show my scheduled tasks"}]
assert required_read_operation_for_request(message, history) == RequiredReadOperation(
"manage_tasks", {"action": "list"}, max_items=3,
)
@pytest.mark.parametrize("message,tool,args,limit", [
("List my scheduled tasks. Return at most three names and statuses. Read-only inspection; do not change data or send messages. Keep the answer concise.",
"manage_tasks", {"action": "list"}, 3),
@@ -301,3 +732,25 @@ def test_exact_live_output_wording_remains_a_safe_read(message, tool, args, limi
assert required_read_operation_for_request(message) == RequiredReadOperation(
tool, args, max_items=limit,
)
def test_latest_inbox_projection_is_inventory_not_topic_search():
assert required_read_operation_for_request(
"List my latest three inbox emails with sender and subject."
) == RequiredReadOperation(
"list_emails", {"folder": "INBOX", "max_results": 3}, max_items=3,
)
def test_bounded_calendar_inventory_preserves_dates_and_query():
assert required_read_operation_for_request(
"List calendar events from 2030-02-01 through 2030-02-02 containing fixture-marker."
) == RequiredReadOperation(
"manage_calendar",
{
"action": "list_events",
"start": "2030-02-01",
"end": "2030-02-02",
"query": "fixture-marker",
},
)
+40
View File
@@ -9,6 +9,7 @@ toggle" error - the advertised capability was dead.
import asyncio
from src.ai_interaction import do_ui_control
from routes import prefs_routes
def test_toggle_rag_on_is_accepted():
@@ -50,3 +51,42 @@ def test_open_calendar_panel_accepts_view_and_target_date():
assert r.get("view") == "month"
assert r.get("target_date") == "2026-09"
assert "error" not in r
def test_models_panel_alias_opens_cookbook_models_view():
r = asyncio.run(do_ui_control("open_panel models"))
assert r.get("panel") == "cookbook"
assert r.get("view") == "Search"
assert r.get("view_label") == "models"
assert "models view" in r.get("results", "")
def test_cookbook_panel_accepts_named_subview():
r = asyncio.run(do_ui_control("open_panel cookbook serve"))
assert r.get("panel") == "cookbook"
assert r.get("view") == "Serve"
assert r.get("view_label") == "launch"
def test_set_theme_persists_owner_scoped_name_for_later_verification(monkeypatch):
stores = {"alice": {}}
monkeypatch.setattr(prefs_routes, "_load_for_user", lambda owner: dict(stores.get(owner, {})))
monkeypatch.setattr(prefs_routes, "_save_for_user", lambda owner, prefs: stores.__setitem__(owner, dict(prefs)))
changed = asyncio.run(do_ui_control("set_theme dark", owner="alice"))
current = asyncio.run(do_ui_control("get_theme", owner="alice"))
assert changed.get("ui_event") == "set_theme"
assert stores["alice"]["theme"] == {"name": "dark"}
assert current == {
"results": "Current theme: dark",
"current_theme": "dark",
"theme_known": True,
}
def test_get_theme_does_not_invent_unsynchronized_client_state(monkeypatch):
monkeypatch.setattr(prefs_routes, "_load_for_user", lambda owner: {})
result = asyncio.run(do_ui_control("get_theme", owner="alice"))
assert result["theme_known"] is False
assert "not been synchronized" in result["results"].lower()
+31 -3
View File
@@ -4,6 +4,7 @@ import json
from src.agent_tools.web_tools import WebFetchTool
from src.search import content as content_mod
from src.tool_schemas import FUNCTION_TOOL_SCHEMAS, function_call_to_tool_block
from src.clean_agent_preview import normalize_preview_function_args
from src.tool_execution import _active_workspace
@@ -92,11 +93,38 @@ def test_web_fetch_schema_and_native_parser_accept_urls_batch():
assert block.tool_type == "web_fetch"
def test_web_fetch_parser_normalizes_empty_label_batch_pairs_only():
def test_web_fetch_parser_normalizes_labeled_url_batch_pairs():
block = function_call_to_tool_block(
"web_fetch",
json.dumps({"urls": [["https://example.com/a", ""]]}),
json.dumps({"urls": [
["https://example.com/a", "Primary documentation"],
["https://example.com/b", ""],
]}),
)
assert block is not None
assert json.loads(block.content)["urls"] == ["https://example.com/a"]
assert json.loads(block.content)["urls"] == [
"https://example.com/a",
"https://example.com/b",
]
def test_web_fetch_parser_rejects_ambiguous_nested_batch_shapes():
assert function_call_to_tool_block(
"web_fetch",
json.dumps({"urls": [["https://example.com/a", "label", "extra"]]}),
) is None
def test_clean_preview_normalizes_labeled_urls_before_schema_validation():
tool_type, args = normalize_preview_function_args(
"web_fetch",
{"urls": [["https://example.com/a", "Primary docs"]]},
)
assert tool_type == "web_fetch"
assert args == {"urls": ["https://example.com/a"]}
assert function_call_to_tool_block(
"web_fetch",
json.dumps({"urls": [["not-a-url", "label"]]}),
) is None
+27
View File
@@ -141,3 +141,30 @@ def test_general_web_search_keeps_comprehensive_fetch(monkeypatch):
assert result["exit_code"] == 0
assert seen[0][0] == "best way to repair a bicycle tire"
assert "full fetched answer" in result["output"]
def test_general_web_search_degrades_to_metadata_when_content_fetch_times_out(monkeypatch):
monkeypatch.setattr(
search,
"comprehensive_web_search",
lambda *args, **kwargs: (_ for _ in ()).throw(asyncio.TimeoutError()),
)
monkeypatch.setattr(
search,
"searxng_search_results",
lambda query, count: [{
"title": "HTTP Semantics",
"url": "https://www.rfc-editor.org/rfc/rfc9110.html",
"snippet": "The Retry-After field indicates how long to wait.",
}],
)
result = asyncio.run(WebSearchTool().execute(
json.dumps({"query": "HTTP Retry-After semantics", "max_pages": 3}),
{},
))
assert result["exit_code"] == 0
assert result["evidence_status"] == "available"
assert result["degraded_mode"] == "metadata_after_content_timeout"
assert "https://www.rfc-editor.org/rfc/rfc9110.html" in result["output"]
+10 -8
View File
@@ -1024,6 +1024,9 @@ def test_visual_text_extraction_is_distinct_from_speech_transcription():
assert _visual_text_extraction_requested(
"Extract all flashing English words shown on screen from 0:25 to 0:30."
)
assert _visual_text_extraction_requested(
"Run OCR on that same image again, returning only numbers."
)
assert _visual_text_extraction_requested("识别视频画面中的文字。")
assert not _visual_text_extraction_requested(
"Transcribe everything the speaker says from 0:25 to 0:30."
@@ -1033,16 +1036,15 @@ def test_visual_text_extraction_is_distinct_from_speech_transcription():
)
def test_local_media_routes_remove_transcriber_for_visual_only_text():
import re
def test_local_media_routes_select_dedicated_ocr_for_visual_text():
source = (Path(__file__).parents[1] / "src" / "agent_loop.py").read_text()
assert len(re.findall(
r'if _visual_text_extraction_requested\(_last_user\):\n'
r'\s+_local_media_tools\.discard\("transcribe_media"\)',
source,
)) == 3
assert source.count(
"_ocr_requested = _visual_text_extraction_requested(_last_user)"
) == 3
assert source.count(
'{"extract_text"}\n if _ocr_requested'
) == 3
def test_workspace_paths_split_on_chinese_list_punctuation():
+26
View File
@@ -0,0 +1,26 @@
from routes.chat_helpers import youtube_prefetch_sources
def test_successful_youtube_preprocessing_has_visible_provenance():
sources = youtube_prefetch_sources(
"Summarize https://youtu.be/jNQXAC9IVRw",
[
"instructions",
"[YOUTUBE VIDEO TRANSCRIPT]\nTitle: Me at the zoo\n[END TRANSCRIPT]",
"[YOUTUBE VIDEO COMMENTS — Top 2 by popularity]\n[END COMMENTS]",
],
)
assert sources == [{
"url": "https://youtu.be/jNQXAC9IVRw",
"title": "Me at the zoo",
"acquisition": "automatic_youtube_context",
"evidence": "transcript+comments",
}]
def test_failed_youtube_preprocessing_does_not_claim_evidence():
assert youtube_prefetch_sources(
"Summarize https://youtu.be/jNQXAC9IVRw",
["Transcript unavailable; use comments if available."],
) == []