mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-14 15:16:35 +03:00
Release: CLI 4.0.2 and engine 0.1.1
Ship signed skill-bundle verification, fix annotated-session checkpoint ordering, and pin all published engine platform packages. Validated with Rust, Node, browser, and provider-backed end-to-end tests. AI assistance: prepared and validated with Codex under Paul Bakaus direction.
This commit is contained in:
@@ -57,6 +57,12 @@
|
||||
"readyPattern": "Local:\\s+https?://[^:]+:(\\d+)",
|
||||
"readyTimeoutMs": 120000,
|
||||
"steer": false,
|
||||
"liveChrome": {
|
||||
"annotations": {
|
||||
"selector": "h1.hero-title",
|
||||
"uploadDelayMs": 300
|
||||
}
|
||||
},
|
||||
"pickSelector": "ul.expense-list",
|
||||
"pickPosition": {
|
||||
"x": 10,
|
||||
|
||||
@@ -2,12 +2,53 @@ import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { runInNewContext } from 'node:vm';
|
||||
|
||||
const SOURCE = readFileSync(join(process.cwd(), 'skill/scripts/live-browser.js'), 'utf-8');
|
||||
const PENDING_DOCK_POSITION_SOURCE = SOURCE.match(/function positionPendingDock\(\) \{[\s\S]*?\n \}/)?.[0] || '';
|
||||
const CAPTURE_AND_EMIT_SOURCE = SOURCE.match(/async function captureAndEmit\([\s\S]*?\n \}/)?.[0] || '';
|
||||
|
||||
describe('live-browser source contracts', () => {
|
||||
it('does not checkpoint a generation before captureAndEmit creates its session', () => {
|
||||
for (const name of ['handleGo', 'handleInsertCreate']) {
|
||||
const body = SOURCE.match(new RegExp(`function ${name}\\(\\) \\{[\\s\\S]*?\\n \\}`))?.[0];
|
||||
assert.ok(body);
|
||||
assert.doesNotMatch(body, /sendCheckpoint\('generate_started'\)/);
|
||||
}
|
||||
});
|
||||
|
||||
for (const annotated of [false, true]) {
|
||||
for (const outcome of ['created', 'failed', 'superseded']) {
|
||||
it(`${annotated ? 'annotated' : 'plain'} generation checkpoints only its acknowledged current session (${outcome})`, async () => {
|
||||
const capture = Promise.withResolvers();
|
||||
const creation = Promise.withResolvers();
|
||||
const events = [];
|
||||
const context = {
|
||||
currentSessionId: 'session-a', state: 'GENERATING', PORT: 1234, TOKEN: 'test',
|
||||
console, Date,
|
||||
captureElementToBlob: () => capture.promise,
|
||||
showShaderOverlay() {},
|
||||
fetch: async () => ({ ok: true, json: async () => ({ path: '/annotation.png' }) }),
|
||||
sendEvent: async (payload) => { events.push(payload.type); return creation.promise; },
|
||||
sendCheckpoint: (reason) => events.push(reason),
|
||||
};
|
||||
const emit = runInNewContext(`(${CAPTURE_AND_EMIT_SOURCE})`, context);
|
||||
const pending = emit({}, { type: 'generate', id: 'session-a' }, {
|
||||
comments: annotated ? [{ text: 'change title' }] : [], strokes: [],
|
||||
}, {});
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
assert.deepEqual(events, annotated ? [] : ['generate']);
|
||||
capture.resolve({ blob: {}, paper: 'white' });
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
assert.deepEqual(events, ['generate'], 'capture/upload must not checkpoint before creation is acknowledged');
|
||||
if (outcome === 'superseded') context.currentSessionId = 'session-b';
|
||||
creation.resolve(outcome === 'failed' ? null : { ok: true });
|
||||
await pending;
|
||||
assert.deepEqual(events, outcome === 'created' ? ['generate', 'generate_started'] : ['generate']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
it('reports foreground poll connectivity without a background worker dependency', () => {
|
||||
assert.match(
|
||||
SOURCE,
|
||||
@@ -29,7 +70,7 @@ describe('live-browser source contracts', () => {
|
||||
);
|
||||
assert.match(
|
||||
CAPTURE_AND_EMIT_SOURCE,
|
||||
/if \(hasAnnotations\) \{[\s\S]*?basePayload\.clientSentAt = Date\.now\(\);\s*sendEvent\(screenshotPath \? \{ \.\.\.basePayload, screenshotPath \} : basePayload\);\s*\}/,
|
||||
/if \(hasAnnotations\) \{[\s\S]*?basePayload\.clientSentAt = Date\.now\(\);\s*const created = await sendEvent\(screenshotPath \? \{ \.\.\.basePayload, screenshotPath \} : basePayload\);/,
|
||||
'annotated generation should dispatch exactly after capture and upload resolve',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
MANUAL_EDIT_SYSTEM_INSTRUCTIONS,
|
||||
VARIANT_SYSTEM_INSTRUCTIONS,
|
||||
createLlmAgent,
|
||||
llmRequestSettings,
|
||||
parseManualEditResponse,
|
||||
parseVariantResponse,
|
||||
progressiveVariantGuidance,
|
||||
@@ -19,6 +20,19 @@ import {
|
||||
validateVariantVisibleCopy,
|
||||
} from './live-e2e/agents/llm-agent.mjs';
|
||||
|
||||
describe('live-e2e LLM request settings', () => {
|
||||
it('explicitly selects low-effort DeepSeek thinking for bounded JSON edit requests', () => {
|
||||
assert.deepEqual(llmRequestSettings('deepseek'), {
|
||||
thinking: { type: 'enabled' }, output_config: { effort: 'low' },
|
||||
});
|
||||
});
|
||||
|
||||
it('leaves other providers unchanged', () => {
|
||||
assert.deepEqual(llmRequestSettings('anthropic'), {});
|
||||
assert.deepEqual(llmRequestSettings('openai'), {});
|
||||
});
|
||||
});
|
||||
|
||||
describe('live-e2e LLM agent provider config', () => {
|
||||
it('defaults to OpenAI gpt-5.6-terra at medium reasoning effort', () => {
|
||||
const config = resolveLlmAgentConfig({}, {});
|
||||
|
||||
+10
-6
@@ -1283,17 +1283,21 @@ for (const { name, fixture } of fixtures) {
|
||||
const pickSelector = annotation.selector || fixture.runtime.pickSelector || 'h1.hero-title';
|
||||
try {
|
||||
await waitForHandshake(page);
|
||||
if (annotation.uploadDelayMs) {
|
||||
await page.route('**/annotation?*', async (route) => {
|
||||
await new Promise(resolve => setTimeout(resolve, annotation.uploadDelayMs));
|
||||
await route.continue();
|
||||
});
|
||||
}
|
||||
if (fixture.runtime.preActions) await runPreActions(page, fixture.runtime.preActions);
|
||||
await pickElement(page, pickSelector, { resetPickMode: true });
|
||||
await drawAnnotationPinAndStroke(page, {
|
||||
comment: annotation.comment || 'Make this selected element easier to scan',
|
||||
});
|
||||
await clickGo(page);
|
||||
await waitForCyclingRobust(page, 3, {
|
||||
agentMode,
|
||||
preActions: fixture.runtime.preActions,
|
||||
log: (m) => t.diagnostic(m),
|
||||
});
|
||||
// A reload would mask a checkpoint-before-creation race by adopting
|
||||
// the session again. Annotated generation must complete in this tab.
|
||||
await waitForCycling(page, 3, { timeout: agentMode === 'llm' ? 180_000 : 30_000 });
|
||||
|
||||
const generateEvent = recordedGenerateEvents.at(-1);
|
||||
await assertAnnotationUploadEvent(generateEvent);
|
||||
@@ -1302,7 +1306,7 @@ for (const { name, fixture } of fixtures) {
|
||||
|
||||
const sourceFile = await locateSessionFile(session.appRoot);
|
||||
const svelteComponentTarget = svelteComponentTargetFor(sourceFile);
|
||||
await clickNext(page);
|
||||
await cycleToVariant(page, 2, 3);
|
||||
assert.equal(await getVisibleVariant(page), 2, 'variant 2 visible after annotated generate');
|
||||
await clickAccept(page, { expectedVariant: 2 });
|
||||
await waitForBarHidden(page);
|
||||
|
||||
@@ -248,6 +248,17 @@ function resolveProvider(opts, env) {
|
||||
return 'openai';
|
||||
}
|
||||
|
||||
export function llmRequestSettings(provider) {
|
||||
// DeepSeek defaults to high-effort thinking, which can consume the entire
|
||||
// bounded response before emitting the JSON these edit tests exercise.
|
||||
// Low effort retains planning for the full live spec without inheriting
|
||||
// the provider's high-effort default.
|
||||
// https://api-docs.deepseek.com/guides/thinking_mode/
|
||||
return provider === 'deepseek'
|
||||
? { thinking: { type: 'enabled' }, output_config: { effort: 'low' } }
|
||||
: {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Anthropic-SDK-shaped shim over the `ai` SDK for OpenAI models, so the
|
||||
* three text-only call sites in this file stay provider-agnostic. system
|
||||
@@ -331,6 +342,7 @@ export async function createLlmAgent(opts = {}) {
|
||||
try {
|
||||
response = await client.messages.create(
|
||||
{
|
||||
...llmRequestSettings(provider),
|
||||
model,
|
||||
temperature: 0,
|
||||
max_tokens: 16000,
|
||||
@@ -476,6 +488,7 @@ export async function createLlmAgent(opts = {}) {
|
||||
try {
|
||||
response = await client.messages.create(
|
||||
{
|
||||
...llmRequestSettings(provider),
|
||||
model,
|
||||
temperature: 0,
|
||||
max_tokens: 16000,
|
||||
@@ -605,11 +618,15 @@ export async function createLlmAgent(opts = {}) {
|
||||
].join('\n');
|
||||
|
||||
const response = await client.messages.create({
|
||||
...llmRequestSettings(provider),
|
||||
model,
|
||||
max_tokens: 4096,
|
||||
system: systemBlocks(STEER_SYSTEM_INSTRUCTIONS),
|
||||
messages: [{ role: 'user', content: userMessage }],
|
||||
});
|
||||
}, provider === 'deepseek' ? {
|
||||
maxRetries: LLM_REQUEST_MAX_RETRIES,
|
||||
timeout: MANUAL_EDIT_REQUEST_TIMEOUT_MS,
|
||||
} : {});
|
||||
|
||||
const cacheRead = response.usage?.cache_read_input_tokens ?? 0;
|
||||
const inputTokens = response.usage?.input_tokens ?? 0;
|
||||
|
||||
@@ -26,8 +26,7 @@ import {
|
||||
clickEditCopy,
|
||||
clickExitLiveMode,
|
||||
clickGo,
|
||||
clickNext,
|
||||
clickPrev,
|
||||
cycleToVariant,
|
||||
clickSaveEdit,
|
||||
drawAnnotationPinAndStroke,
|
||||
editTextLeaf,
|
||||
@@ -40,6 +39,7 @@ import {
|
||||
waitForBarHidden,
|
||||
waitForCycling,
|
||||
waitForHandshake,
|
||||
waitForVariantSettled,
|
||||
} from './live-e2e/ui.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -281,7 +281,7 @@ async function runAnnotationGenerateFlow({ page, tmp, evidence }) {
|
||||
const generateEvent = latestJournalEvent(tmp, (event) => event.type === 'generate' && event.screenshotPath);
|
||||
await assertAnnotationUploadEvent(generateEvent);
|
||||
assert.ok(existsSync(generateEvent.screenshotPath), 'annotation screenshot file exists');
|
||||
await clickNext(page);
|
||||
await cycleTo(page, 2);
|
||||
await assertVariantCounter(page, 2, 3);
|
||||
await evidence.capture('annotation-cycle');
|
||||
await clickDiscard(page);
|
||||
@@ -514,14 +514,10 @@ async function clickPendingTrash(page) {
|
||||
}
|
||||
|
||||
async function cycleTo(page, target) {
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const visible = await getVisibleVariant(page);
|
||||
if (visible === target) return;
|
||||
if (visible == null) await page.waitForTimeout(250);
|
||||
else if (visible < target) await clickNext(page);
|
||||
else await clickPrev(page);
|
||||
}
|
||||
assert.equal(await getVisibleVariant(page), target, `variant ${target} visible`);
|
||||
// Component imports finish after the counter changes. Do not send the next
|
||||
// click (or reload) while the previous variant is still mounting.
|
||||
await cycleToVariant(page, target, 3);
|
||||
await waitForVariantSettled(page, target, 3);
|
||||
}
|
||||
|
||||
async function waitForVisibleCycling(page, count, { timeout }) {
|
||||
|
||||
Reference in New Issue
Block a user