Compare commits

..

3 Commits

Author SHA1 Message Date
elffuss-bot
f0bf4465c7 Apply item 0x054ca82f for issue #7
Squash of the patch of item 0x054ca82fe89ff53664b84304b335f216bd1dfbeca76588ee9d0169e905dac9ef, final in the elffuss ledger.
Solver 0xa472b6bac297911f562335db561c6cbf33e5c32e; 2 approvals of round 0.

T2T-Item: 0x054ca82fe89ff53664b84304b335f216bd1dfbeca76588ee9d0169e905dac9ef
T2T-Issue: 0x7bf84a053d2bd6ce83a7ce9d2799aadf280de622b31dbf03026d0e69d76d3a9a
T2T-Event: 0xc4d14ab5f340035493bca3a09c233287a16a2fd49c720b5d91e1fae2301ccfd7
2026-10-05 17:59:10 +00:00
elffuss-bot
e2c294fb43 Apply item 0x4ac69e55 for issue #10
Squash of the patch of item 0x4ac69e554cfa29c1fb7819e9835c17153233e73e72ecb0b821628233c820eb3c, final in the elffuss ledger.
Solver 0xdd8075ba73c2173b9f05957b5f1a7561600e5494; 2 approvals of round 0.

T2T-Item: 0x4ac69e554cfa29c1fb7819e9835c17153233e73e72ecb0b821628233c820eb3c
T2T-Issue: 0xb8d7c6d9815eb6af5efae391d136467cda8c028200c2b5ec5a36fa6620196c53
T2T-Event: 0x96cb4689693dca2b068c24579b325bee24f431c0e6c31a1d7f0b39f8810f534f
2026-10-03 17:32:55 +00:00
elffuss-bot
ede2250e96 Apply item 0xb6340749 for issue #9
Squash of the patch of item 0xb63407496b05c7ed5580852a8b11084877e719e1f4a9a6c4ae367c9b15f22b59, final in the elffuss ledger.
Solver 0xdd8075ba73c2173b9f05957b5f1a7561600e5494; 2 approvals of round 0.

T2T-Item: 0xb63407496b05c7ed5580852a8b11084877e719e1f4a9a6c4ae367c9b15f22b59
T2T-Issue: 0x76d6986733689a0daf7f538f2068e36d2e4d9363bd01d5e0e69704ee39f48fd3
T2T-Event: 0x80803f1aa05fcaa0131d6bf26b5ee90528884780d17a0475a07c12922da3f8ca
2026-10-03 14:16:35 +00:00
11 changed files with 118 additions and 22 deletions

View File

@@ -1,17 +1,17 @@
{
"schema": "elffuss-t2t/criteria@2",
"issue": 8,
"issue": 10,
"criteria": [
{
"index": 1,
"tests": [
"tests/acceptance/8/criteria.test.js::criterion 1"
"tests/acceptance/10/criteria.test.js::criterion 1"
]
},
{
"index": 2,
"tests": [
"tests/acceptance/8/criteria.test.js::criterion 2"
"tests/acceptance/10/criteria.test.js::criterion 2"
]
}
]

View File

@@ -0,0 +1,31 @@
import test from 'node:test'
import assert from 'node:assert/strict'
const mod = await import('../../../web/js/model-cache.js')
function setNavigator(value) {
Object.defineProperty(globalThis, 'navigator', { value, configurable: true, writable: true })
}
test('criterion 1', async () => {
const removed = []
const deletedCaches = []
const root = {
async removeEntry(name, opts) { removed.push([name, opts]) },
}
setNavigator({ storage: { getDirectory: async () => root } })
globalThis.caches = { delete: async (n) => { deletedCaches.push(n); return true } }
await mod.clearModelCache()
assert.ok(removed.some(([n]) => n === 'elffuss-models'))
assert.deepEqual(deletedCaches, ['elffuss-models-v1'])
})
test('criterion 2', async () => {
let calls = 0
setNavigator({ storage: { getDirectory: async () => { calls++; throw new Error('sin OPFS') } } })
globalThis.caches = { delete: async () => true }
await assert.doesNotReject(mod.clearModelCache())
assert.equal(calls, 1)
})

View File

@@ -0,0 +1,12 @@
{
"schema": "elffuss-t2t/criteria@2",
"issue": 7,
"criteria": [
{
"index": 1,
"tests": [
"tests/acceptance/7/criteria.test.js::criterion 1"
]
}
]
}

View File

@@ -0,0 +1,36 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { MODELS } from '../../../web/js/providers/litert.js'
// Minimal stand-in for the page so the lab module can run and render its lists.
class Node {
constructor(tag) { this.tag = tag; this.children = []; this.style = {}; this.className = ''; this.textContent = ''; this.innerHTML = ''; this.scrollTop = 0; this.scrollHeight = 0 }
appendChild(c) { this.children.push(c); return c }
append(...cs) { cs.forEach(c => this.children.push(c)) }
querySelector(s) { return this.slot(s) }
slot(s) { return slots.get(s) }
}
const slots = new Map()
const pick = s => { if (!slots.has(s)) { const n = new Node('slot'); n.id = s.slice(1); slots.set(s, n) } return slots.get(s) }
Node.prototype.querySelector = function (s) { return pick(s) }
globalThis.document = { querySelector: pick, createElement: t => new Node(t) }
const store = new Map()
globalThis.localStorage = { getItem: k => (store.has(k) ? store.get(k) : null), setItem: (k, v) => store.set(k, String(v)), removeItem: k => store.delete(k) }
globalThis.window = { open() {} }
await import('../../../web/js/lab.js')
function titlesOf(node, out = []) {
if (node.className === 't') out.push(node.textContent)
node.children.forEach(c => titlesOf(c, out))
return out
}
test('criterion 1', () => {
const titles = titlesOf(pick('#brains'))
assert.equal(titles.length, 4)
assert.equal(titles[2], MODELS['gemma-e2b'].label)
assert.equal(titles[3], MODELS['gemma-e4b'].label)
assert.ok(titles[0].startsWith('Reglas'))
assert.ok(titles[1].startsWith('Elffuss LM'))
})

View File

@@ -1,15 +0,0 @@
import test from 'node:test'
import assert from 'node:assert/strict'
const mod = await import('../../../web/js/tools/clone.js')
test('criterion 1', () => {
const host = 'github.com'
assert.deepEqual(mod.parseRepoUrl(`https://${host}/o/r.git/`), { host: 'github', owner: 'o', repo: 'r', branch: null })
assert.equal(mod.parseRepoUrl(`https://${host}/o/r/tree/dev`).branch, 'dev')
})
test('criterion 2', () => {
const host = 'bitbucket.org'
assert.deepEqual(mod.parseRepoUrl(`https://${host}/o/r.git/`), { host: 'bitbucket', owner: 'o', repo: 'r', branch: null })
assert.throws(() => mod.parseRepoUrl('https://example.org/o/r'), Error)
})

View File

@@ -0,0 +1,12 @@
{
"schema": "elffuss-t2t/criteria@2",
"issue": 9,
"criteria": [
{
"index": 1,
"tests": [
"tests/acceptance/9/criteria.test.js::criterion 1"
]
}
]
}

View File

@@ -0,0 +1,15 @@
import test from 'node:test'
import assert from 'node:assert/strict'
const mod = await import('../../../web/js/conversations.js')
const convOf = content => ({ title: null, agent: { history: [{ role: 'user', content }] } })
test('criterion 1', () => {
const emoji = '\u{1F600}'
const a39 = 'a'.repeat(39)
const title = mod.titleFor(convOf(a39 + emoji + 'bcd'))
assert.equal(title, a39 + emoji + '…')
assert.equal(title.isWellFormed(), true)
assert.equal(mod.titleFor(convOf('a'.repeat(60))), 'a'.repeat(40) + '…')
})

View File

@@ -41,7 +41,8 @@ export function titleFor(conv) {
// no debe filtrarse al título de la pestaña — se muestra el texto limpio.
const raw = firstUser.content.startsWith(GOAL_PREFIX) ? firstUser.content.slice(GOAL_PREFIX.length) : firstUser.content;
const t = cleanTitle(raw);
return t.length > 40 ? t.slice(0, 40) + '…' : t || 'Nueva conversación';
const chars = Array.from(t);
return chars.length > 40 ? chars.slice(0, 40).join('') + '…' : t || 'Nueva conversación';
}
export function setProvider(mod) {

View File

@@ -12,6 +12,7 @@
// el de juegos ejecuta el juego generado y lo puntúa mirándolo correr.
import { DEFAULTS, packHistoryACER, estimateTokens } from './acer-core.js';
import { TASKS } from './swe-tasks.js';
import { MODELS } from './providers/litert.js';
const $ = s => document.querySelector(s);
const el = (t, c, x) => { const n = document.createElement(t); if (c) n.className = c; if (x != null) n.textContent = x; return n; };
@@ -68,8 +69,7 @@ const BENCHES = [
const BRAINS = [
['rules', 'Reglas (sin GPU)', 'Determinista. Valida el arnés sin bajar pesos.'],
['onnx', 'Elffuss LM (CPU/wasm)', 'LFM2.5-1.2B por transformers.js.'],
['litert:gemma-e2b', 'Gemma-4 E2B (~2 GB)', 'WebGPU.'],
['litert:gemma-e4b', 'Gemma-4 E4B (~3 GB)', 'WebGPU. El mejor.'],
...['gemma-e2b', 'gemma-e4b'].map(k => ['litert:' + k, MODELS[k].label, MODELS[k].tag]),
];
// ─────────────────────────── consola ───────────────────────────

View File

@@ -48,4 +48,8 @@ export async function clearModelCache() {
if (navigator.serviceWorker && navigator.serviceWorker.controller)
navigator.serviceWorker.controller.postMessage('clear-models');
try { await caches.delete('elffuss-models-v1'); } catch { /* */ }
try {
const { clearAll } = await import('./runtime/model-store.js');
await clearAll();
} catch { /* sin almacén OPFS: nada que vaciar */ }
}

View File

@@ -12,7 +12,7 @@ const MAX_FILE_BYTES = 4 * 1024 * 1024; // ficheros más grandes se saltan (bina
const CONCURRENCY = 10;
export function parseRepoUrl(raw) {
const url = String(raw || '').trim().replace(/\/+$/, '').replace(/\.git$/, '');
const url = String(raw || '').trim().replace(/\.git$/, '').replace(/\/+$/, '');
let m = url.match(/^(?:https?:\/\/)?(?:www\.)?github\.com\/([\w.-]+)\/([\w.-]+)(?:\/tree\/([\w./-]+))?/i);
if (m) return { host: 'github', owner: m[1], repo: m[2], branch: m[3] || null };
m = url.match(/^(?:https?:\/\/)?(?:www\.)?bitbucket\.org\/([\w.-]+)\/([\w.-]+)(?:\/src\/([\w./-]+))?/i);