diff --git a/CHANGELOG.md b/CHANGELOG.md index 28c3212..0e924c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,11 @@ All notable changes to LLeMbas Desktop. The format follows - **The release your server recommends.** Settings › About names the Desktop release that goes with the server's version when it is newer than this one (a beta only to a beta install), with a link to its release page. +- **Scheduled tasks on a linked computer.** A task's page says where it runs: on the server, or + on a computer, in a folder and mode, with how long an approval waits, and whether that computer + is away or does not accept scheduled work. A run waiting for its computer says so. The form + offers "Where it runs" where the server can: this computer or another linked one that accepts + scheduled work, its folder (this computer's agent folder to begin with), mode and wait. ### Fixed diff --git a/scripts/fetch-fixtures.sh b/scripts/fetch-fixtures.sh index b133734..90a2a7d 100755 --- a/scripts/fetch-fixtures.sh +++ b/scripts/fetch-fixtures.sh @@ -16,7 +16,7 @@ OUT="tests/ui/fixtures" FILES="chat_agent.json event_ask.json event_status.json event_status_idle.json event_status_person.json event_status_stopped.json event_tree.json models_agent.json subagent_findings.json subagent_transcript.json tree.json me.json folders.json folder.json search.json memories.json memory.json scope.json push_schedule.json push_schedule_failed.json -apps.json apps_stable.json event_schedules.json schedule.json schedule_run.json chat.json chats_listed.json files_listing.json" +apps.json apps_stable.json event_schedules.json schedule.json schedule_on_device.json schedule_run.json chat.json chats_listed.json files_listing.json" [ $# -eq 1 ] || { echo "usage: scripts/fetch-fixtures.sh " >&2; exit 2; } commit="$(git -C "$SERVER" rev-parse --verify --quiet "$1^{commit}")" \ diff --git a/scripts/gen-i18n.py b/scripts/gen-i18n.py index ec4a7ec..5c0a9eb 100755 --- a/scripts/gen-i18n.py +++ b/scripts/gen-i18n.py @@ -524,6 +524,23 @@ KEYS = ( "Your server recommends LLeMbas Desktop %(version)s", "The release that goes with the server's version. This is %(current)s.", "Open the release page", + # scheduled.js: where a task runs + "Where it runs", + "On this server", + "does not accept scheduled work", + "not linked now", + "A run on a computer is an agent chat there. The computer must allow it: “Accept scheduled work” in its LLeMbas CLI (lembas service scheduled on).", + "Folder on the computer", + "A full path, inside a folder the computer allows remote work in.", + "Edit unless you choose otherwise; Auto runs commands without asking. The computer may lower it.", + "An approval waits (minutes)", + "A command the run needs approved comes to your phone as a card. Unanswered by then, it counts as denied.", + "A linked computer", + "Away now: a run waits for it, then counts as missed.", + "It does not accept scheduled work: turn on Accept scheduled work there.", + "This computer (%(name)s)", + "Waiting for the computer", + "approvals wait %(n)s min", ) # The app's own sentences. The mode names (Manual, Edit, Auto, Plan) stay English, as the @@ -797,6 +814,12 @@ OWN: dict[str, dict[str, str]] = { "Your server recommends LLeMbas Desktop %(version)s": "Váš server doporučuje LLeMbas Desktop %(version)s", "The release that goes with the server's version. This is %(current)s.": "Vydání, které patří k verzi serveru. Toto je %(current)s.", "Open the release page": "Otevřít stránku vydání", + "A linked computer": "Propojený počítač", + "Away now: a run waits for it, then counts as missed.": "Teď je pryč: spuštění na něj počká, pak se počítá jako zmeškané.", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Nepřijímá plánovanou práci: zapněte tam Accept scheduled work.", + "This computer (%(name)s)": "Tento počítač (%(name)s)", + "Waiting for the computer": "Čeká na počítač", + "approvals wait %(n)s min": "schválení čeká %(n)s min", }, "de": { "Sign in again: the server no longer accepts this app's sign-in.": "Melden Sie sich erneut an: Der Server nimmt die Anmeldung dieser App nicht mehr an.", @@ -1065,6 +1088,12 @@ OWN: dict[str, dict[str, str]] = { "Your server recommends LLeMbas Desktop %(version)s": "Ihr Server empfiehlt LLeMbas Desktop %(version)s", "The release that goes with the server's version. This is %(current)s.": "Die Version, die zur Version des Servers passt. Dies ist %(current)s.", "Open the release page": "Release-Seite öffnen", + "A linked computer": "Ein verknüpfter Computer", + "Away now: a run waits for it, then counts as missed.": "Gerade nicht da: Ein Lauf wartet auf ihn und zählt dann als verpasst.", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Er nimmt keine geplante Arbeit an: Schalten Sie dort Accept scheduled work ein.", + "This computer (%(name)s)": "Dieser Computer (%(name)s)", + "Waiting for the computer": "Wartet auf den Computer", + "approvals wait %(n)s min": "Freigaben warten %(n)s Min.", }, "es": { "Sign in again: the server no longer accepts this app's sign-in.": "Vuelva a iniciar sesión: el servidor ya no acepta el inicio de sesión de esta aplicación.", @@ -1333,6 +1362,12 @@ OWN: dict[str, dict[str, str]] = { "Your server recommends LLeMbas Desktop %(version)s": "Su servidor recomienda LLeMbas Desktop %(version)s", "The release that goes with the server's version. This is %(current)s.": "La versión que corresponde a la del servidor. Esta es %(current)s.", "Open the release page": "Abrir la página de la versión", + "A linked computer": "Un ordenador vinculado", + "Away now: a run waits for it, then counts as missed.": "Ausente ahora: una ejecución lo espera y luego cuenta como perdida.", + "It does not accept scheduled work: turn on Accept scheduled work there.": "No acepta trabajo programado: active allí Accept scheduled work.", + "This computer (%(name)s)": "Este ordenador (%(name)s)", + "Waiting for the computer": "Esperando al ordenador", + "approvals wait %(n)s min": "las aprobaciones esperan %(n)s min", }, "fr": { "Sign in again: the server no longer accepts this app's sign-in.": "Reconnectez-vous : le serveur n’accepte plus la connexion de cette application.", @@ -1601,6 +1636,12 @@ OWN: dict[str, dict[str, str]] = { "Your server recommends LLeMbas Desktop %(version)s": "Votre serveur recommande LLeMbas Desktop %(version)s", "The release that goes with the server's version. This is %(current)s.": "La version qui va avec celle du serveur. Celle-ci est la %(current)s.", "Open the release page": "Ouvrir la page de la version", + "A linked computer": "Un ordinateur lié", + "Away now: a run waits for it, then counts as missed.": "Absent pour l’instant : une exécution l’attend, puis compte comme manquée.", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Il n’accepte pas le travail planifié : activez-y Accept scheduled work.", + "This computer (%(name)s)": "Cet ordinateur (%(name)s)", + "Waiting for the computer": "En attente de l’ordinateur", + "approvals wait %(n)s min": "les approbations attendent %(n)s min", }, "hu": { "Sign in again: the server no longer accepts this app's sign-in.": "Jelentkezzen be újra: a szerver már nem fogadja el ennek az alkalmazásnak a bejelentkezését.", @@ -1869,6 +1910,12 @@ OWN: dict[str, dict[str, str]] = { "Your server recommends LLeMbas Desktop %(version)s": "A szervere a LLeMbas Desktop %(version)s verziót ajánlja", "The release that goes with the server's version. This is %(current)s.": "A szerver verziójához tartozó kiadás. Ez a %(current)s.", "Open the release page": "Kiadás oldalának megnyitása", + "A linked computer": "Egy összekapcsolt számítógép", + "Away now: a run waits for it, then counts as missed.": "Most nem elérhető: a futás megvárja, aztán kihagyottnak számít.", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Nem fogad ütemezett munkát: kapcsolja be ott az Accept scheduled work beállítást.", + "This computer (%(name)s)": "Ez a számítógép (%(name)s)", + "Waiting for the computer": "A számítógépre vár", + "approvals wait %(n)s min": "a jóváhagyások %(n)s percig várnak", }, "it": { "Sign in again: the server no longer accepts this app's sign-in.": "Accedi di nuovo: il server non accetta più l’accesso di questa app.", @@ -2137,6 +2184,12 @@ OWN: dict[str, dict[str, str]] = { "Your server recommends LLeMbas Desktop %(version)s": "Il tuo server consiglia LLeMbas Desktop %(version)s", "The release that goes with the server's version. This is %(current)s.": "La versione che va con quella del server. Questa è la %(current)s.", "Open the release page": "Apri la pagina della versione", + "A linked computer": "Un computer collegato", + "Away now: a run waits for it, then counts as missed.": "Assente ora: un’esecuzione lo aspetta, poi conta come persa.", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Non accetta lavoro pianificato: attiva lì Accept scheduled work.", + "This computer (%(name)s)": "Questo computer (%(name)s)", + "Waiting for the computer": "In attesa del computer", + "approvals wait %(n)s min": "le approvazioni attendono %(n)s min", }, "pl": { "Sign in again: the server no longer accepts this app's sign-in.": "Zaloguj się ponownie: serwer nie przyjmuje już logowania tej aplikacji.", @@ -2405,6 +2458,12 @@ OWN: dict[str, dict[str, str]] = { "Your server recommends LLeMbas Desktop %(version)s": "Twój serwer zaleca LLeMbas Desktop %(version)s", "The release that goes with the server's version. This is %(current)s.": "Wydanie pasujące do wersji serwera. To jest %(current)s.", "Open the release page": "Otwórz stronę wydania", + "A linked computer": "Połączony komputer", + "Away now: a run waits for it, then counts as missed.": "Teraz nieobecny: uruchomienie na niego czeka, potem liczy się jako pominięte.", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Nie przyjmuje zaplanowanej pracy: włącz tam Accept scheduled work.", + "This computer (%(name)s)": "Ten komputer (%(name)s)", + "Waiting for the computer": "Czeka na komputer", + "approvals wait %(n)s min": "zatwierdzenia czekają %(n)s min", }, "sk": { "Sign in again: the server no longer accepts this app's sign-in.": "Prihláste sa znova: server už neprijíma prihlásenie tejto aplikácie.", @@ -2673,6 +2732,12 @@ OWN: dict[str, dict[str, str]] = { "Your server recommends LLeMbas Desktop %(version)s": "Váš server odporúča LLeMbas Desktop %(version)s", "The release that goes with the server's version. This is %(current)s.": "Vydanie, ktoré patrí k verzii servera. Toto je %(current)s.", "Open the release page": "Otvoriť stránku vydania", + "A linked computer": "Prepojený počítač", + "Away now: a run waits for it, then counts as missed.": "Teraz nie je k dispozícii: spustenie naň počká, potom sa počíta ako zmeškané.", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Neprijíma plánovanú prácu: zapnite tam Accept scheduled work.", + "This computer (%(name)s)": "Tento počítač (%(name)s)", + "Waiting for the computer": "Čaká na počítač", + "approvals wait %(n)s min": "schválenia čakajú %(n)s min", }, } diff --git a/tests/ui/fixtures/schedule_on_device.json b/tests/ui/fixtures/schedule_on_device.json new file mode 100644 index 0000000..a2e197f --- /dev/null +++ b/tests/ui/fixtures/schedule_on_device.json @@ -0,0 +1,98 @@ +{ + "schedule": { + "id": "sched3", + "title": "Nightly tests", + "instruction": "Run the tests.", + "request": "every night at 02:00", + "rule": { + "every": { + "minutes": 1440 + } + }, + "description": "Every 1 day", + "timezone": "UTC", + "enabled": true, + "next_run": "2026-01-03T02:00:00+00:00", + "next_runs": [], + "target": "chat", + "mode": "fresh", + "notify": "matters", + "catch_up": "one", + "grace_minutes": null, + "model": null, + "effort": null, + "agents": null, + "tools": [ + "files", + "recall", + "search", + "fetch", + "memory", + "skills", + "plan" + ], + "origin": "user", + "chat_id": "chat2", + "fired_count": 3, + "failure_streak": 0, + "watch": { + "kind": "command", + "command": "git log -1 --format=%H", + "timeout_s": 30, + "target": "git log -1 --format=%H", + "changed_at": "2026-01-02T03:04:05+00:00" + }, + "runs_on": { + "device": "tok1", + "device_name": "bag-end", + "cwd": "/home/frodo/waybread", + "mode": "edit", + "approval_wait_minutes": 30 + }, + "last_error": null, + "last_run": { + "id": "run5", + "schedule_id": "sched3", + "status": "waiting", + "timing": "on_time", + "missed": 0, + "due_at": "2026-01-02T03:04:05+00:00", + "started_at": null, + "finished_at": null, + "waiting_for": "device", + "retry_at": "2026-01-02T03:09:05+00:00", + "progress": { + "phase": "retrying", + "since": 1767323045000, + "reason": "Waiting for bag-end to be linked.", + "in": 0, + "attempt": 1 + }, + "attempts": 1, + "model": { + "id": null, + "name": null, + "reason": null, + "switched_from": null + }, + "tokens": { + "prompt": 0, + "completion": 0 + }, + "cost": null, + "chat_id": null, + "message_id": null, + "report_path": null, + "preview": "", + "error": { + "class": "device_away", + "text": "Waiting for bag-end to be linked." + }, + "notified": false, + "notification": null, + "watch": null + }, + "running": false, + "created_at": "2026-01-02T03:04:05+00:00" + } +} diff --git a/tests/ui/run.mjs b/tests/ui/run.mjs index a36468a..30f5b3b 100644 --- a/tests/ui/run.mjs +++ b/tests/ui/run.mjs @@ -1586,6 +1586,102 @@ test("K8: nothing is offered that is not newer, a beta to a stable install, or a } }) +// L8: where a scheduled task runs (runs_on, fixture schedule_on_device.json; /devices `scheduled`). + +function l8(devices) { + const on = fixture("schedule_on_device") + const id = on.schedule.id + return { + status: status({ host: "workbench" }), + routes: baseRoutes({ + "GET /devices": { devices }, + "GET /schedules": { schedules: [on.schedule] }, + [`GET /schedules/${id}`]: on, + [`GET /schedules/${id}/runs`]: { runs: [on.schedule.last_run], more: false }, + "GET /schedules/models": { models: [], instance: [], mine: [] }, + "POST /schedules/compile": { ok: true, reason: "", title: "Build check", instruction: "Check the build.", target: "chat", rule: { at: { times: ["07:30"] } }, description: "Every day at 07:30", next_runs: [], timezone: "UTC" }, + "POST /schedules": { schedule: { ...on.schedule, id: "sched9" } }, + "GET /schedules/sched9": { schedule: { ...on.schedule, id: "sched9" } }, + "GET /schedules/sched9/runs": { runs: [], more: false }, + }), + streams: {}, + } +} + +const bagEnd = (over = {}) => ({ id: "tok1", name: "bag-end", host: "bag-end", platform: "linux", terminal: true, scheduled: true, max_mode: "", ...over }) + +async function openTask(p) { + await p.waitForSelector("#composer") + await p.evaluate(() => window.__mock.emit("open-schedule", { schedule: "sched3", run: "run5" })) + await p.waitForSelector(".fact >> text=Where it runs") +} + +test("L8: a task on a linked computer says the computer, the folder and mode; a run waits for it", async () => { + const p = await page(l8([bagEnd()])) + await openTask(p) + const where = await p.textContent(".fact:has-text('Where it runs')") + assert.match(where, /bag-end/) + assert.match(where, /\/home\/frodo\/waybread/) + assert.match(where, /Edit — asks before commands · approvals wait 30 min/) + assert.equal(await p.locator(".fact.is-warning").count(), 0) + assert.match(await p.textContent(".run[data-id='run5']"), /Waiting for the computer/) + assert.match(await p.textContent(".run[data-id='run5']"), /Waiting for bag-end to be linked\./) + await shot(p, "l8-task-1280") + assert.deepEqual(p.errors, []) +}) + +test("L8: a computer away, or not accepting scheduled work, is said on the task", async () => { + for (const [devices, words] of [ + [[], /Away now/], + [[bagEnd({ scheduled: false })], /does not accept scheduled work/], + ]) { + const p = await page(l8(devices)) + await openTask(p) + assert.match(await p.textContent(".fact.is-warning"), words) + assert.deepEqual(p.errors, []) + await p.context().close() + } +}) + +test("L8: a new task can run on this computer, in a folder and mode; one not accepting cannot be chosen", async () => { + const p = await page(l8([bagEnd({ scheduled: false }), bagEnd({ id: "token1", name: "workbench", host: "workbench" })])) + await p.waitForSelector("#composer") + await p.evaluate(() => window.__mock.emit("open-schedule", { schedule: "sched3", run: "run5" })) + await p.waitForSelector("#page-scheduled:not([hidden])") + await p.keyboard.press("Control+k") + await p.fill(".palette__input", "New scheduled") + await p.keyboard.press("Enter") + await p.fill("#sched-words", "every day at 7:30 check the build") + await p.click(".sched--new button[type=submit]") + await p.waitForSelector("#sched-where") + const options = await p.$$eval("#sched-where option", (l) => l.map((o) => [o.textContent, o.disabled])) + assert.deepEqual(options, [["On this server", false], ["bag-end — does not accept scheduled work", true], ["This computer (workbench)", false]]) + await p.selectOption("#sched-where", "token1") + assert.equal(await p.inputValue("#sched-cwd"), "/home/dev/project") + await p.selectOption("#sched-runmode", "auto") + await p.fill("#sched-wait", "45") + await shot(p, "l8-form-1280") + await p.click("#sched-save") + await p.waitForFunction(() => window.__mock.api("POST", "/schedules").some((c) => c.args.path === "/schedules")) + const body = (await p.evaluate(() => window.__mock.api("POST", "/schedules").find((c) => c.args.path === "/schedules"))).args.body + assert.deepEqual(body.runs_on, { device: "token1", cwd: "/home/dev/project", mode: "auto", approval_wait_minutes: 45 }) + assert.deepEqual(p.errors, []) +}) + +test("L8: at 390 px the form with a computer chosen stays inside the window", async () => { + const p = await page(l8([bagEnd()]), { width: 390, height: 844 }) + await p.waitForSelector("#composer") + await p.evaluate(() => window.__mock.emit("open-schedule", { schedule: "sched3", run: "run5" })) + await p.waitForSelector(".fact >> text=Where it runs") + assert.ok(await noSideways(p, 390), "nothing scrolls sideways") + await shot(p, "l8-task-390") + await p.click("#sched-edit") + await p.waitForSelector("#sched-cwd") + assert.ok(await noSideways(p, 390), "nothing scrolls sideways") + await shot(p, "l8-form-390") + assert.deepEqual(p.errors, []) +}) + // --- Run ---------------------------------------------------------------------------------------- const only = process.argv[2] || "" diff --git a/ui/app.css b/ui/app.css index 90eef9c..3cba0be 100644 --- a/ui/app.css +++ b/ui/app.css @@ -1550,6 +1550,9 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); } .sched + .sched__history { margin-top: var(--sp-6); } .sched__trouble { padding: var(--sp-3) var(--sp-4); color: var(--warning); background: var(--warning-soft); border-radius: var(--radius-soft); overflow-wrap: anywhere; } .fact__hint { font: var(--text-xs) var(--font-ui); color: var(--ink-faint); } +/* Where it runs: the computer over its folder; a computer that cannot take the run says so in colour. */ +.fact__path { display: block; font: var(--text-xs) var(--font-mono); color: var(--ink-muted); overflow-wrap: anywhere; } +.fact.is-warning .fact__hint { color: var(--warning); } /* The form: the When field's words on the control's track, one line, the time zone as its hint. */ .sched__desc { align-self: center; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: var(--text-sm); } .sched__desc.is-error { color: var(--warning); } diff --git a/ui/i18n.js b/ui/i18n.js index ef8c2ca..969b588 100644 --- a/ui/i18n.js +++ b/ui/i18n.js @@ -34,8 +34,12 @@ const LEMBAS_I18N = { "A busy model is waited for, then the next free one is used.": "Na zaneprázdněný model se čeká, pak se použije další volný.", "A chat asking for more is lowered to this.": "Konverzace, která chce víc, je snížena na tohle.", "A chat on %(server)s, with your account's models and files.": "Konverzace na %(server)s s modely a soubory vašeho účtu.", + "A command the run needs approved comes to your phone as a card. Unanswered by then, it counts as denied.": "Příkaz, který spuštění potřebuje schválit, přijde do telefonu jako karta. Pokud do té doby nebude zodpovězen, počítá se jako zamítnutý.", + "A full path, inside a folder the computer allows remote work in.": "Úplná cesta ve složce, ve které počítač povoluje vzdálenou práci.", + "A linked computer": "Propojený počítač", "A new chat for each run": "Nová konverzace pro každé spuštění", "A result, but not a run with nothing to report.": "Výsledek, ale ne spuštění, které nemá co hlásit.", + "A run on a computer is an agent chat there. The computer must allow it: “Accept scheduled work” in its LLeMbas CLI (lembas service scheduled on).": "Spuštění na počítači je tam agentní chat. Počítač to musí povolit: „Accept scheduled work“ v jeho LLeMbas CLI (lembas service scheduled on).", "A scheduled task failed 3 times in a row. Pause it?": "Naplánovaná úloha selhala 3krát za sebou. Pozastavit ji?", "A scheduled task failed.": "Naplánovaná úloha selhala.", "A scheduled task has a result.": "Naplánovaná úloha má výsledek.", @@ -56,6 +60,7 @@ const LEMBAS_I18N = { "Always allowed here": "Tady vždy povoleno", "An agent chat works in a folder on %(device)s, through the LLeMbas agent there.": "Agentní konverzace pracuje ve složce na zařízení %(device)s přes tamního agenta LLeMbas.", "An agent chat works in a folder on this computer, through the agent built into this app.": "Agentní konverzace pracuje ve složce na tomto počítači přes agenta vestavěného v této aplikaci.", + "An approval waits (minutes)": "Schválení čeká (minuty)", "Another device": "Jiné zařízení", "Answer": "Odpovědět", "Appearance": "Vzhled", @@ -66,6 +71,7 @@ const LEMBAS_I18N = { "Attach files": "Přikládat soubory", "Auto — asks nothing": "Auto — na nic se neptá", "Automatic compaction is off.": "Automatické shrnutí je vypnuté.", + "Away now: a run waits for it, then counts as missed.": "Teď je pryč: spuštění na něj počká, pak se počítá jako zmeškané.", "Back": "Zpět", "Background command": "Příkaz na pozadí", "Background tasks": "Úlohy na pozadí", @@ -132,6 +138,7 @@ const LEMBAS_I18N = { "Edit": "Upravit", "Edit %(title)s": "Upravit %(title)s", "Edit message": "Upravit zprávu", + "Edit unless you choose otherwise; Auto runs commands without asking. The computer may lower it.": "Edit, pokud nezvolíte jinak; Auto spouští příkazy bez ptaní. Počítač ho může snížit.", "Edit — asks before commands": "Edit — ptá se před příkazy", "Empty folder": "Prázdná složka", "Ended": "Skončené", @@ -147,6 +154,7 @@ const LEMBAS_I18N = { "Folder": "Složka", "Folder name": "Název složky", "Folder on %(device)s": "Složka na zařízení %(device)s", + "Folder on the computer": "Složka na počítači", "Free space": "Volné místo", "Hello": "Dobrý den", "Hello, %(name)s": "Dobrý den, %(name)s", @@ -155,6 +163,7 @@ const LEMBAS_I18N = { "In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Vlastními slovy. Server z nich vytvoří pravidlo a pokyn a před uložením vám ukáže další spuštění.", "Install LLeMbas %(version)s…": "Nainstalovat LLeMbas %(version)s…", "Instruction files": "Soubory s pokyny", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Nepřijímá plánovanou práci: zapněte tam Accept scheduled work.", "It has not run yet.": "Ještě neběžel.", "It says: %(purpose)s": "Model uvádí: %(purpose)s", "Kept in this app on this computer. The window opens again in the language chosen.": "Uloženo v této aplikaci na tomto počítači. Okno se znovu otevře ve zvoleném jazyce.", @@ -226,6 +235,7 @@ const LEMBAS_I18N = { "Nothing to switch in this chat.": "V této konverzaci není co přepnout.", "Nothing yet.": "Zatím nic.", "Notifications": "Upozornění", + "On this server": "Na tomto serveru", "One chat that grows": "Jedna konverzace, která roste", "Only in its history.": "Jen v její historii.", "Open in the viewer": "Otevřít v prohlížeči souborů", @@ -379,6 +389,7 @@ const LEMBAS_I18N = { "Think before answering": "Přemýšlet před odpovědí", "Thinking": "Přemýšlí", "This computer": "Tento počítač", + "This computer (%(name)s)": "Tento počítač (%(name)s)", "This computer is not linked yet.": "Tento počítač ještě není propojený.", "This computer's (%(language)s)": "Podle tohoto počítače (%(language)s)", "This device is not linked right now.": "Toto zařízení teď není připojené.", @@ -407,6 +418,7 @@ const LEMBAS_I18N = { "Waiting for a free model": "Čeká na volný model", "Waiting for subagents: {{running}} running": "Čeká na podagenty: pracuje {{running}}", "Waiting for the browser…": "Čeká se na prohlížeč…", + "Waiting for the computer": "Čeká na počítač", "Waiting for the reply before it": "Čeká na odpověď před ní", "Waiting for you: {{what}}": "Čeká na vás: {{what}}", "Waiting for your answer": "Čeká na vaši odpověď", @@ -417,6 +429,7 @@ const LEMBAS_I18N = { "What your models remember about you, across chats. Change or remove anything here.": "Co si o vás vaše modely pamatují napříč konverzacemi. Cokoli tu můžete změnit nebo odstranit.", "When": "Kdy", "When it matters": "Když na tom záleží", + "Where it runs": "Kde se spouští", "Wide": "Široký", "Writing": "Píše", "Writing down…": "Zapisuji…", @@ -440,12 +453,14 @@ const LEMBAS_I18N = { "all its slots are in use": "všechny jeho sloty jsou obsazené", "allow {{tool}}?": "povolit {{tool}}?", "an approval": "povolení", + "approvals wait %(n)s min": "schválení čeká %(n)s min", "budget": "rozpočet", "busy": "zaneprázdněný", "by you": "vámi", "caught up": "dohnáno", "dark": "tmavý", "default": "výchozí", + "does not accept scheduled work": "nepřijímá plánovanou práci", "done": "hotovo", "failed": "selhalo", "findings: %(n)s": "zjištění: %(n)s", @@ -458,6 +473,7 @@ const LEMBAS_I18N = { "light": "světlý", "messages: %(n)s": "zprávy: %(n)s", "not linked": "nepropojeno", + "not linked now": "teď není propojený", "off": "vypnuto", "out of context": "bez kontextu", "paused: context full": "pozastavený: plný kontext", @@ -502,8 +518,12 @@ const LEMBAS_I18N = { "A busy model is waited for, then the next free one is used.": "Auf ein belegtes Modell wird gewartet, dann wird das nächste freie genommen.", "A chat asking for more is lowered to this.": "Ein Chat, der mehr verlangt, wird hierauf begrenzt.", "A chat on %(server)s, with your account's models and files.": "Ein Chat auf %(server)s, mit den Modellen und Dateien Ihres Kontos.", + "A command the run needs approved comes to your phone as a card. Unanswered by then, it counts as denied.": "Ein Befehl, den der Lauf genehmigt braucht, kommt als Karte auf Ihr Telefon. Bis dahin unbeantwortet, gilt er als abgelehnt.", + "A full path, inside a folder the computer allows remote work in.": "Ein vollständiger Pfad in einem Ordner, in dem der Computer Fernarbeit erlaubt.", + "A linked computer": "Ein verknüpfter Computer", "A new chat for each run": "Ein neuer Chat für jeden Lauf", "A result, but not a run with nothing to report.": "Ein Ergebnis, aber kein Lauf ohne Neuigkeiten.", + "A run on a computer is an agent chat there. The computer must allow it: “Accept scheduled work” in its LLeMbas CLI (lembas service scheduled on).": "Ein Lauf auf einem Computer ist dort ein Agenten-Chat. Der Computer muss es erlauben: „Accept scheduled work“ in seinem LLeMbas CLI (lembas service scheduled on).", "A scheduled task failed 3 times in a row. Pause it?": "Eine geplante Aufgabe ist 3-mal hintereinander fehlgeschlagen. Pausieren?", "A scheduled task failed.": "Eine geplante Aufgabe ist fehlgeschlagen.", "A scheduled task has a result.": "Eine geplante Aufgabe hat ein Ergebnis.", @@ -524,6 +544,7 @@ const LEMBAS_I18N = { "Always allowed here": "Hier immer erlaubt", "An agent chat works in a folder on %(device)s, through the LLeMbas agent there.": "Ein Agenten-Chat arbeitet in einem Ordner auf %(device)s, über den LLeMbas-Agenten dort.", "An agent chat works in a folder on this computer, through the agent built into this app.": "Ein Agenten-Chat arbeitet in einem Ordner auf diesem Computer, über den in diese App eingebauten Agenten.", + "An approval waits (minutes)": "Eine Genehmigung wartet (Minuten)", "Another device": "Ein anderes Gerät", "Answer": "Antworten", "Appearance": "Darstellung", @@ -534,6 +555,7 @@ const LEMBAS_I18N = { "Attach files": "Dateien anhängen", "Auto — asks nothing": "Auto — fragt nichts", "Automatic compaction is off.": "Die automatische Zusammenfassung ist aus.", + "Away now: a run waits for it, then counts as missed.": "Gerade nicht da: Ein Lauf wartet auf ihn und zählt dann als verpasst.", "Back": "Zurück", "Background command": "Hintergrundbefehl", "Background tasks": "Hintergrundaufgaben", @@ -600,6 +622,7 @@ const LEMBAS_I18N = { "Edit": "Bearbeiten", "Edit %(title)s": "%(title)s bearbeiten", "Edit message": "Nachricht bearbeiten", + "Edit unless you choose otherwise; Auto runs commands without asking. The computer may lower it.": "Edit, sofern Sie nichts anderes wählen; Auto führt Befehle ohne Nachfrage aus. Der Computer kann ihn herabsetzen.", "Edit — asks before commands": "Edit — fragt vor Befehlen", "Empty folder": "Leerer Ordner", "Ended": "Beendet", @@ -615,6 +638,7 @@ const LEMBAS_I18N = { "Folder": "Ordner", "Folder name": "Ordnername", "Folder on %(device)s": "Ordner auf %(device)s", + "Folder on the computer": "Ordner auf dem Computer", "Free space": "Freier Platz", "Hello": "Hallo", "Hello, %(name)s": "Hallo, %(name)s", @@ -623,6 +647,7 @@ const LEMBAS_I18N = { "In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "In Ihren eigenen Worten. Der Server macht daraus eine Regel und eine Anweisung und zeigt Ihnen die nächsten Läufe, bevor etwas gespeichert wird.", "Install LLeMbas %(version)s…": "LLeMbas %(version)s installieren…", "Instruction files": "Anweisungsdateien", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Er nimmt keine geplante Arbeit an: Schalten Sie dort Accept scheduled work ein.", "It has not run yet.": "Er ist noch nicht gelaufen.", "It says: %(purpose)s": "Es sagt: %(purpose)s", "Kept in this app on this computer. The window opens again in the language chosen.": "In dieser App auf diesem Computer gespeichert. Das Fenster öffnet sich in der gewählten Sprache neu.", @@ -694,6 +719,7 @@ const LEMBAS_I18N = { "Nothing to switch in this chat.": "In diesem Chat gibt es nichts umzuschalten.", "Nothing yet.": "Noch nichts.", "Notifications": "Benachrichtigungen", + "On this server": "Auf diesem Server", "One chat that grows": "Ein Chat, der wächst", "Only in its history.": "Nur in ihrem Verlauf.", "Open in the viewer": "Im Betrachter öffnen", @@ -847,6 +873,7 @@ const LEMBAS_I18N = { "Think before answering": "Vor der Antwort nachdenken", "Thinking": "Denkt nach", "This computer": "Dieser Computer", + "This computer (%(name)s)": "Dieser Computer (%(name)s)", "This computer is not linked yet.": "Dieser Computer ist noch nicht verknüpft.", "This computer's (%(language)s)": "Wie dieser Computer (%(language)s)", "This device is not linked right now.": "Dieses Gerät ist gerade nicht verbunden.", @@ -875,6 +902,7 @@ const LEMBAS_I18N = { "Waiting for a free model": "Wartet auf ein freies Modell", "Waiting for subagents: {{running}} running": "Wartet auf Subagenten: {{running}} laufen", "Waiting for the browser…": "Warte auf den Browser…", + "Waiting for the computer": "Wartet auf den Computer", "Waiting for the reply before it": "Wartet auf die Antwort davor", "Waiting for you: {{what}}": "Wartet auf dich: {{what}}", "Waiting for your answer": "Wartet auf Ihre Antwort", @@ -885,6 +913,7 @@ const LEMBAS_I18N = { "What your models remember about you, across chats. Change or remove anything here.": "Was Ihre Modelle über Sie behalten, über alle Chats hinweg. Hier können Sie alles ändern oder entfernen.", "When": "Wann", "When it matters": "Wenn es zählt", + "Where it runs": "Wo es läuft", "Wide": "Breit", "Writing": "Schreibe", "Writing down…": "Wird aufgeschrieben…", @@ -908,12 +937,14 @@ const LEMBAS_I18N = { "all its slots are in use": "alle seine Plätze sind belegt", "allow {{tool}}?": "{{tool}} erlauben?", "an approval": "eine Freigabe", + "approvals wait %(n)s min": "Freigaben warten %(n)s Min.", "budget": "Budget", "busy": "belegt", "by you": "von dir", "caught up": "nachgeholt", "dark": "dunkel", "default": "Standard", + "does not accept scheduled work": "nimmt keine geplante Arbeit an", "done": "fertig", "failed": "fehlgeschlagen", "findings: %(n)s": "Erkenntnisse: %(n)s", @@ -926,6 +957,7 @@ const LEMBAS_I18N = { "light": "hell", "messages: %(n)s": "Nachrichten: %(n)s", "not linked": "nicht verbunden", + "not linked now": "gerade nicht verbunden", "off": "aus", "out of context": "Kontext erschöpft", "paused: context full": "pausiert: Kontext voll", @@ -970,8 +1002,12 @@ const LEMBAS_I18N = { "A busy model is waited for, then the next free one is used.": "Se espera a un modelo ocupado y luego se usa el siguiente libre.", "A chat asking for more is lowered to this.": "Un chat que pida más se limita a esto.", "A chat on %(server)s, with your account's models and files.": "Un chat en %(server)s, con los modelos y archivos de su cuenta.", + "A command the run needs approved comes to your phone as a card. Unanswered by then, it counts as denied.": "Un comando que la ejecución necesita aprobar llega a tu teléfono como tarjeta. Sin respuesta para entonces, cuenta como denegado.", + "A full path, inside a folder the computer allows remote work in.": "Una ruta completa, dentro de una carpeta en la que el ordenador permite trabajo remoto.", + "A linked computer": "Un ordenador vinculado", "A new chat for each run": "Un chat nuevo en cada ejecución", "A result, but not a run with nothing to report.": "Un resultado, pero no una ejecución sin nada que informar.", + "A run on a computer is an agent chat there. The computer must allow it: “Accept scheduled work” in its LLeMbas CLI (lembas service scheduled on).": "Una ejecución en un ordenador es allí un chat de agente. El ordenador debe permitirlo: «Accept scheduled work» en su LLeMbas CLI (lembas service scheduled on).", "A scheduled task failed 3 times in a row. Pause it?": "Una tarea programada falló 3 veces seguidas. ¿Pausarla?", "A scheduled task failed.": "Una tarea programada ha fallado.", "A scheduled task has a result.": "Una tarea programada tiene un resultado.", @@ -992,6 +1028,7 @@ const LEMBAS_I18N = { "Always allowed here": "Siempre permitido aquí", "An agent chat works in a folder on %(device)s, through the LLeMbas agent there.": "Un chat de agente trabaja en una carpeta de %(device)s, a través del agente LLeMbas de allí.", "An agent chat works in a folder on this computer, through the agent built into this app.": "Un chat de agente trabaja en una carpeta de este equipo, a través del agente integrado en esta aplicación.", + "An approval waits (minutes)": "Una aprobación espera (minutos)", "Another device": "Otro dispositivo", "Answer": "Responder", "Appearance": "Apariencia", @@ -1002,6 +1039,7 @@ const LEMBAS_I18N = { "Attach files": "Adjuntar archivos", "Auto — asks nothing": "Auto — no pregunta nada", "Automatic compaction is off.": "La compactación automática está desactivada.", + "Away now: a run waits for it, then counts as missed.": "Ausente ahora: una ejecución lo espera y luego cuenta como perdida.", "Back": "Volver", "Background command": "Comando en segundo plano", "Background tasks": "Tareas en segundo plano", @@ -1026,7 +1064,7 @@ const LEMBAS_I18N = { "Compaction buffer": "Reserva para la compactación", "Compacts before the next reply.": "Se compacta antes de la próxima respuesta.", "Compacts by itself once the window is known.": "Se compacta sola en cuanto se conozca la ventana.", - "Computer": "Equipo", + "Computer": "Ordenador", "Connect": "Conectar", "Connected.": "Conectado.", "Connected. This shell is also open in another tab, and they share a size.": "Conectado. Este shell también está abierto en otra pestaña, y comparten tamaño.", @@ -1068,6 +1106,7 @@ const LEMBAS_I18N = { "Edit": "Editar", "Edit %(title)s": "Editar %(title)s", "Edit message": "Editar mensaje", + "Edit unless you choose otherwise; Auto runs commands without asking. The computer may lower it.": "Edit salvo que elijas otro; Auto ejecuta comandos sin preguntar. El ordenador puede rebajarlo.", "Edit — asks before commands": "Edit — pregunta antes de los comandos", "Empty folder": "Carpeta vacía", "Ended": "Terminados", @@ -1083,6 +1122,7 @@ const LEMBAS_I18N = { "Folder": "Carpeta", "Folder name": "Nombre de la carpeta", "Folder on %(device)s": "Carpeta en %(device)s", + "Folder on the computer": "Carpeta en el ordenador", "Free space": "Espacio libre", "Hello": "Hola", "Hello, %(name)s": "Hola, %(name)s", @@ -1091,6 +1131,7 @@ const LEMBAS_I18N = { "In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Con sus propias palabras. El servidor las convierte en una regla y una instrucción, y le muestra las próximas ejecuciones antes de guardar nada.", "Install LLeMbas %(version)s…": "Instalar LLeMbas %(version)s…", "Instruction files": "Archivos de instrucciones", + "It does not accept scheduled work: turn on Accept scheduled work there.": "No acepta trabajo programado: active allí Accept scheduled work.", "It has not run yet.": "Todavía no se ha ejecutado.", "It says: %(purpose)s": "Dice: %(purpose)s", "Kept in this app on this computer. The window opens again in the language chosen.": "Se guarda en esta aplicación en este equipo. La ventana se vuelve a abrir en el idioma elegido.", @@ -1162,6 +1203,7 @@ const LEMBAS_I18N = { "Nothing to switch in this chat.": "No hay nada que activar o desactivar en este chat.", "Nothing yet.": "Aún nada.", "Notifications": "Notificaciones", + "On this server": "En este servidor", "One chat that grows": "Un chat que va creciendo", "Only in its history.": "Solo en su historial.", "Open in the viewer": "Abrir en el visor", @@ -1315,6 +1357,7 @@ const LEMBAS_I18N = { "Think before answering": "Razonar antes de responder", "Thinking": "Pensando", "This computer": "Este equipo", + "This computer (%(name)s)": "Este ordenador (%(name)s)", "This computer is not linked yet.": "Este equipo aún no está vinculado.", "This computer's (%(language)s)": "La de este equipo (%(language)s)", "This device is not linked right now.": "Este dispositivo no está vinculado en este momento.", @@ -1343,6 +1386,7 @@ const LEMBAS_I18N = { "Waiting for a free model": "Esperando un modelo libre", "Waiting for subagents: {{running}} running": "Esperando a los subagentes: {{running}} en marcha", "Waiting for the browser…": "Esperando al navegador…", + "Waiting for the computer": "Esperando al ordenador", "Waiting for the reply before it": "Esperando la respuesta anterior", "Waiting for you: {{what}}": "Esperándote: {{what}}", "Waiting for your answer": "Esperando su respuesta", @@ -1353,6 +1397,7 @@ const LEMBAS_I18N = { "What your models remember about you, across chats. Change or remove anything here.": "Lo que sus modelos recuerdan de usted entre chats. Aquí puede cambiar o quitar cualquier cosa.", "When": "Cuándo", "When it matters": "Cuando importa", + "Where it runs": "Dónde se ejecuta", "Wide": "Ancha", "Writing": "Escribiendo", "Writing down…": "Transcribiendo…", @@ -1376,12 +1421,14 @@ const LEMBAS_I18N = { "all its slots are in use": "todas sus plazas están ocupadas", "allow {{tool}}?": "¿permitir {{tool}}?", "an approval": "una aprobación", + "approvals wait %(n)s min": "las aprobaciones esperan %(n)s min", "budget": "presupuesto", "busy": "ocupado", "by you": "por ti", "caught up": "recuperada", "dark": "oscuro", "default": "predeterminado", + "does not accept scheduled work": "no acepta trabajo programado", "done": "terminado", "failed": "falló", "findings: %(n)s": "hallazgos: %(n)s", @@ -1394,6 +1441,7 @@ const LEMBAS_I18N = { "light": "claro", "messages: %(n)s": "mensajes: %(n)s", "not linked": "no vinculado", + "not linked now": "no vinculado ahora", "off": "desactivado", "out of context": "sin contexto", "paused: context full": "en pausa: contexto lleno", @@ -1438,8 +1486,12 @@ const LEMBAS_I18N = { "A busy model is waited for, then the next free one is used.": "Un modèle occupé est attendu, puis le suivant libre est utilisé.", "A chat asking for more is lowered to this.": "Une conversation qui en demande plus est ramenée à ceci.", "A chat on %(server)s, with your account's models and files.": "Une conversation sur %(server)s, avec les modèles et les fichiers de votre compte.", + "A command the run needs approved comes to your phone as a card. Unanswered by then, it counts as denied.": "Une commande que l’exécution doit faire approuver arrive sur votre téléphone sous forme de carte. Sans réponse d’ici là, elle compte comme refusée.", + "A full path, inside a folder the computer allows remote work in.": "Un chemin complet, dans un dossier où l’ordinateur autorise le travail à distance.", + "A linked computer": "Un ordinateur lié", "A new chat for each run": "Une nouvelle conversation à chaque exécution", "A result, but not a run with nothing to report.": "Un résultat, mais pas une exécution sans rien à signaler.", + "A run on a computer is an agent chat there. The computer must allow it: “Accept scheduled work” in its LLeMbas CLI (lembas service scheduled on).": "Une exécution sur un ordinateur y est un chat d’agent. L’ordinateur doit l’autoriser : « Accept scheduled work » dans son LLeMbas CLI (lembas service scheduled on).", "A scheduled task failed 3 times in a row. Pause it?": "Une tâche planifiée a échoué 3 fois de suite. La mettre en pause ?", "A scheduled task failed.": "Une tâche planifiée a échoué.", "A scheduled task has a result.": "Une tâche planifiée a un résultat.", @@ -1460,6 +1512,7 @@ const LEMBAS_I18N = { "Always allowed here": "Toujours autorisé ici", "An agent chat works in a folder on %(device)s, through the LLeMbas agent there.": "Une discussion d'agent travaille dans un dossier de %(device)s, via l'agent LLeMbas qui s'y trouve.", "An agent chat works in a folder on this computer, through the agent built into this app.": "Une discussion d'agent travaille dans un dossier de cet ordinateur, via l'agent intégré à cette application.", + "An approval waits (minutes)": "Une approbation attend (minutes)", "Another device": "Un autre appareil", "Answer": "Répondre", "Appearance": "Apparence", @@ -1470,6 +1523,7 @@ const LEMBAS_I18N = { "Attach files": "Joindre des fichiers", "Auto — asks nothing": "Auto — ne demande rien", "Automatic compaction is off.": "Le compactage automatique est désactivé.", + "Away now: a run waits for it, then counts as missed.": "Absent pour l’instant : une exécution l’attend, puis compte comme manquée.", "Back": "Retour", "Background command": "Commande en arrière-plan", "Background tasks": "Tâches en arrière-plan", @@ -1536,6 +1590,7 @@ const LEMBAS_I18N = { "Edit": "Modifier", "Edit %(title)s": "Modifier %(title)s", "Edit message": "Modifier le message", + "Edit unless you choose otherwise; Auto runs commands without asking. The computer may lower it.": "Edit sauf choix contraire ; Auto exécute les commandes sans demander. L’ordinateur peut l’abaisser.", "Edit — asks before commands": "Edit — demande avant les commandes", "Empty folder": "Dossier vide", "Ended": "Terminés", @@ -1551,6 +1606,7 @@ const LEMBAS_I18N = { "Folder": "Dossier", "Folder name": "Nom du dossier", "Folder on %(device)s": "Dossier sur %(device)s", + "Folder on the computer": "Dossier sur l’ordinateur", "Free space": "Espace libre", "Hello": "Bonjour", "Hello, %(name)s": "Bonjour, %(name)s", @@ -1559,6 +1615,7 @@ const LEMBAS_I18N = { "In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Avec vos propres mots. Le serveur en fait une règle et une instruction, et vous montre les prochaines exécutions avant que rien ne soit enregistré.", "Install LLeMbas %(version)s…": "Installer LLeMbas %(version)s…", "Instruction files": "Fichiers d’instructions", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Il n’accepte pas le travail planifié : activez-y Accept scheduled work.", "It has not run yet.": "Elle ne s'est pas encore exécutée.", "It says: %(purpose)s": "Il dit : %(purpose)s", "Kept in this app on this computer. The window opens again in the language chosen.": "Enregistré dans cette application sur cet ordinateur. La fenêtre se rouvre dans la langue choisie.", @@ -1630,6 +1687,7 @@ const LEMBAS_I18N = { "Nothing to switch in this chat.": "Rien à activer ou désactiver dans cette conversation.", "Nothing yet.": "Rien pour l'instant.", "Notifications": "Notifications", + "On this server": "Sur ce serveur", "One chat that grows": "Une conversation qui grandit", "Only in its history.": "Seulement dans son historique.", "Open in the viewer": "Ouvrir dans la visionneuse", @@ -1783,6 +1841,7 @@ const LEMBAS_I18N = { "Think before answering": "Réfléchir avant de répondre", "Thinking": "Réflexion", "This computer": "Cet ordinateur", + "This computer (%(name)s)": "Cet ordinateur (%(name)s)", "This computer is not linked yet.": "Cet ordinateur n’est pas encore lié.", "This computer's (%(language)s)": "Celle de cet ordinateur (%(language)s)", "This device is not linked right now.": "Cet appareil n'est pas relié pour le moment.", @@ -1811,6 +1870,7 @@ const LEMBAS_I18N = { "Waiting for a free model": "En attente d’un modèle libre", "Waiting for subagents: {{running}} running": "En attente des sous-agents : {{running}} en cours", "Waiting for the browser…": "En attente du navigateur…", + "Waiting for the computer": "En attente de l’ordinateur", "Waiting for the reply before it": "En attente de la réponse précédente", "Waiting for you: {{what}}": "En attente de vous : {{what}}", "Waiting for your answer": "En attente de votre réponse", @@ -1821,6 +1881,7 @@ const LEMBAS_I18N = { "What your models remember about you, across chats. Change or remove anything here.": "Ce que vos modèles retiennent de vous, d’une conversation à l’autre. Modifiez ou supprimez ce que vous voulez ici.", "When": "Quand", "When it matters": "Quand c'est important", + "Where it runs": "Où elle s’exécute", "Wide": "Large", "Writing": "Écriture", "Writing down…": "Transcription…", @@ -1844,12 +1905,14 @@ const LEMBAS_I18N = { "all its slots are in use": "tous ses emplacements sont occupés", "allow {{tool}}?": "autoriser {{tool}} ?", "an approval": "une approbation", + "approvals wait %(n)s min": "les approbations attendent %(n)s min", "budget": "budget", "busy": "occupé", "by you": "par vous", "caught up": "rattrapée", "dark": "sombre", "default": "par défaut", + "does not accept scheduled work": "n’accepte pas le travail planifié", "done": "terminé", "failed": "échoué", "findings: %(n)s": "constats : %(n)s", @@ -1862,6 +1925,7 @@ const LEMBAS_I18N = { "light": "clair", "messages: %(n)s": "messages : %(n)s", "not linked": "non relié", + "not linked now": "non relié pour l’instant", "off": "désactivé", "out of context": "contexte épuisé", "paused: context full": "en pause : contexte plein", @@ -1906,8 +1970,12 @@ const LEMBAS_I18N = { "A busy model is waited for, then the next free one is used.": "A foglalt modellre vár, majd a következő szabadot használja.", "A chat asking for more is lowered to this.": "Az ennél többet kérő csevegés erre korlátozódik.", "A chat on %(server)s, with your account's models and files.": "Beszélgetés itt: %(server)s, a fiókja modelljeivel és fájljaival.", + "A command the run needs approved comes to your phone as a card. Unanswered by then, it counts as denied.": "A futás által jóváhagyást igénylő parancs kártyaként érkezik a telefonjára. Ha addig nem válaszol, elutasítottnak számít.", + "A full path, inside a folder the computer allows remote work in.": "Teljes elérési út, olyan mappán belül, ahol a számítógép engedi a távoli munkát.", + "A linked computer": "Egy összekapcsolt számítógép", "A new chat for each run": "Minden futáshoz új beszélgetés", "A result, but not a run with nothing to report.": "Eredmény, de nem olyan futás, amelynek nincs mit jelentenie.", + "A run on a computer is an agent chat there. The computer must allow it: “Accept scheduled work” in its LLeMbas CLI (lembas service scheduled on).": "A számítógépen futás ott egy ügynök-csevegés. A számítógépnek engedélyeznie kell: „Accept scheduled work” a LLeMbas CLI-jében (lembas service scheduled on).", "A scheduled task failed 3 times in a row. Pause it?": "Egy ütemezett feladat 3-szor egymás után sikertelen volt. Szünetelteti?", "A scheduled task failed.": "Egy ütemezett feladat sikertelen volt.", "A scheduled task has a result.": "Egy ütemezett feladatnak eredménye van.", @@ -1928,6 +1996,7 @@ const LEMBAS_I18N = { "Always allowed here": "Itt mindig engedélyezett", "An agent chat works in a folder on %(device)s, through the LLeMbas agent there.": "Az ügynökcsevegés egy mappában dolgozik ezen: %(device)s, az ottani LLeMbas-ügynökön keresztül.", "An agent chat works in a folder on this computer, through the agent built into this app.": "Az ügynökcsevegés egy mappában dolgozik ezen a számítógépen, az alkalmazásba épített ügynökön keresztül.", + "An approval waits (minutes)": "Egy jóváhagyás vár (perc)", "Another device": "Másik eszköz", "Answer": "Válasz", "Appearance": "Megjelenés", @@ -1938,6 +2007,7 @@ const LEMBAS_I18N = { "Attach files": "Fájlok csatolása", "Auto — asks nothing": "Auto — semmit nem kérdez", "Automatic compaction is off.": "Az automatikus tömörítés ki van kapcsolva.", + "Away now: a run waits for it, then counts as missed.": "Most nem elérhető: a futás megvárja, aztán kihagyottnak számít.", "Back": "Vissza", "Background command": "Háttérparancs", "Background tasks": "Háttérfeladatok", @@ -2004,6 +2074,7 @@ const LEMBAS_I18N = { "Edit": "Szerkesztés", "Edit %(title)s": "%(title)s szerkesztése", "Edit message": "Üzenet szerkesztése", + "Edit unless you choose otherwise; Auto runs commands without asking. The computer may lower it.": "Edit, hacsak mást nem választ; az Auto kérdezés nélkül futtat parancsokat. A számítógép csökkentheti.", "Edit — asks before commands": "Edit — parancsok előtt kérdez", "Empty folder": "Üres mappa", "Ended": "Befejezett", @@ -2019,6 +2090,7 @@ const LEMBAS_I18N = { "Folder": "Mappa", "Folder name": "Mappa neve", "Folder on %(device)s": "Mappa ezen: %(device)s", + "Folder on the computer": "Mappa a számítógépen", "Free space": "Szabad hely", "Hello": "Üdv", "Hello, %(name)s": "Üdv, %(name)s", @@ -2027,6 +2099,7 @@ const LEMBAS_I18N = { "In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "A saját szavaival. A szerver szabályt és utasítást készít belőlük, és mentés előtt megmutatja a következő futásokat.", "Install LLeMbas %(version)s…": "LLeMbas %(version)s telepítése…", "Instruction files": "Utasításfájlok", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Nem fogad ütemezett munkát: kapcsolja be ott az Accept scheduled work beállítást.", "It has not run yet.": "Még nem futott.", "It says: %(purpose)s": "Ezt mondja: %(purpose)s", "Kept in this app on this computer. The window opens again in the language chosen.": "Ebben az alkalmazásban, ezen a számítógépen tárolva. Az ablak a választott nyelven nyílik meg újra.", @@ -2098,6 +2171,7 @@ const LEMBAS_I18N = { "Nothing to switch in this chat.": "Ebben a beszélgetésben nincs mit átkapcsolni.", "Nothing yet.": "Még semmi.", "Notifications": "Értesítések", + "On this server": "Ezen a szerveren", "One chat that grows": "Egy beszélgetés, amely bővül", "Only in its history.": "Csak az előzményeiben.", "Open in the viewer": "Megnyitás a megjelenítőben", @@ -2251,6 +2325,7 @@ const LEMBAS_I18N = { "Think before answering": "Gondolkodjon válasz előtt", "Thinking": "Gondolkodás", "This computer": "Ez a számítógép", + "This computer (%(name)s)": "Ez a számítógép (%(name)s)", "This computer is not linked yet.": "Ez a számítógép még nincs összekapcsolva.", "This computer's (%(language)s)": "A számítógépé (%(language)s)", "This device is not linked right now.": "Ez az eszköz jelenleg nincs összekapcsolva.", @@ -2279,6 +2354,7 @@ const LEMBAS_I18N = { "Waiting for a free model": "Szabad modellre vár", "Waiting for subagents: {{running}} running": "Az alügynökökre vár: {{running}} dolgozik", "Waiting for the browser…": "Várakozás a böngészőre…", + "Waiting for the computer": "A számítógépre vár", "Waiting for the reply before it": "Az előző válaszra vár", "Waiting for you: {{what}}": "Rád vár: {{what}}", "Waiting for your answer": "Az Ön válaszára vár", @@ -2289,6 +2365,7 @@ const LEMBAS_I18N = { "What your models remember about you, across chats. Change or remove anything here.": "Amit a modelljei megjegyeznek Önről a beszélgetéseken át. Itt bármit módosíthat vagy eltávolíthat.", "When": "Mikor", "When it matters": "Ha számít", + "Where it runs": "Hol fut", "Wide": "Széles", "Writing": "Írás", "Writing down…": "Leírás…", @@ -2312,12 +2389,14 @@ const LEMBAS_I18N = { "all its slots are in use": "minden helye foglalt", "allow {{tool}}?": "engedélyezed ezt: {{tool}}?", "an approval": "egy jóváhagyás", + "approvals wait %(n)s min": "a jóváhagyások %(n)s percig várnak", "budget": "keret", "busy": "foglalt", "by you": "általad", "caught up": "pótolva", "dark": "sötét", "default": "alapértelmezett", + "does not accept scheduled work": "nem fogad ütemezett munkát", "done": "kész", "failed": "sikertelen", "findings: %(n)s": "megállapítások: %(n)s", @@ -2330,6 +2409,7 @@ const LEMBAS_I18N = { "light": "világos", "messages: %(n)s": "üzenetek: %(n)s", "not linked": "nincs összekapcsolva", + "not linked now": "most nincs összekapcsolva", "off": "ki", "out of context": "elfogyott a kontextus", "paused: context full": "szüneteltetve: tele a kontextus", @@ -2374,8 +2454,12 @@ const LEMBAS_I18N = { "A busy model is waited for, then the next free one is used.": "Si attende un modello occupato, poi si usa il successivo libero.", "A chat asking for more is lowered to this.": "Una chat che chiede di più viene limitata a questo.", "A chat on %(server)s, with your account's models and files.": "Una chat su %(server)s, con i modelli e i file del tuo account.", + "A command the run needs approved comes to your phone as a card. Unanswered by then, it counts as denied.": "Un comando che l'esecuzione deve far approvare arriva al tuo telefono come scheda. Senza risposta entro allora, conta come negato.", + "A full path, inside a folder the computer allows remote work in.": "Un percorso completo, dentro una cartella in cui il computer consente il lavoro remoto.", + "A linked computer": "Un computer collegato", "A new chat for each run": "Una nuova chat per ogni esecuzione", "A result, but not a run with nothing to report.": "Un risultato, ma non un’esecuzione senza nulla da segnalare.", + "A run on a computer is an agent chat there. The computer must allow it: “Accept scheduled work” in its LLeMbas CLI (lembas service scheduled on).": "Un'esecuzione su un computer è lì una chat agente. Il computer deve consentirlo: «Accept scheduled work» nel suo LLeMbas CLI (lembas service scheduled on).", "A scheduled task failed 3 times in a row. Pause it?": "Un'attività pianificata è fallita 3 volte di fila. Metterla in pausa?", "A scheduled task failed.": "Un'attività pianificata è fallita.", "A scheduled task has a result.": "Un’attività pianificata ha un risultato.", @@ -2396,6 +2480,7 @@ const LEMBAS_I18N = { "Always allowed here": "Sempre consentito qui", "An agent chat works in a folder on %(device)s, through the LLeMbas agent there.": "Una chat con agente lavora in una cartella su %(device)s, tramite l’agente LLeMbas che vi si trova.", "An agent chat works in a folder on this computer, through the agent built into this app.": "Una chat con agente lavora in una cartella di questo computer, tramite l'agente integrato in questa app.", + "An approval waits (minutes)": "Un'approvazione attende (minuti)", "Another device": "Un altro dispositivo", "Answer": "Rispondi", "Appearance": "Aspetto", @@ -2406,6 +2491,7 @@ const LEMBAS_I18N = { "Attach files": "Allegare file", "Auto — asks nothing": "Auto — non chiede nulla", "Automatic compaction is off.": "La compattazione automatica è disattivata.", + "Away now: a run waits for it, then counts as missed.": "Assente ora: un’esecuzione lo aspetta, poi conta come persa.", "Back": "Indietro", "Background command": "Comando in background", "Background tasks": "Attività in background", @@ -2472,6 +2558,7 @@ const LEMBAS_I18N = { "Edit": "Modifica", "Edit %(title)s": "Modifica %(title)s", "Edit message": "Modifica messaggio", + "Edit unless you choose otherwise; Auto runs commands without asking. The computer may lower it.": "Edit, salvo diversa scelta; Auto esegue i comandi senza chiedere. Il computer può abbassarla.", "Edit — asks before commands": "Edit — chiede prima dei comandi", "Empty folder": "Cartella vuota", "Ended": "Terminati", @@ -2487,6 +2574,7 @@ const LEMBAS_I18N = { "Folder": "Cartella", "Folder name": "Nome della cartella", "Folder on %(device)s": "Cartella su %(device)s", + "Folder on the computer": "Cartella sul computer", "Free space": "Spazio libero", "Hello": "Ciao", "Hello, %(name)s": "Ciao, %(name)s", @@ -2495,6 +2583,7 @@ const LEMBAS_I18N = { "In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Con parole tue. Il server ne ricava una regola e un’istruzione e ti mostra le prossime esecuzioni prima di salvare.", "Install LLeMbas %(version)s…": "Installa LLeMbas %(version)s…", "Instruction files": "File di istruzioni", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Non accetta lavoro pianificato: attiva lì Accept scheduled work.", "It has not run yet.": "Non è ancora stata eseguita.", "It says: %(purpose)s": "Dice: %(purpose)s", "Kept in this app on this computer. The window opens again in the language chosen.": "Salvato in questa app su questo computer. La finestra si riapre nella lingua scelta.", @@ -2566,6 +2655,7 @@ const LEMBAS_I18N = { "Nothing to switch in this chat.": "Non c’è niente da attivare o disattivare in questa chat.", "Nothing yet.": "Ancora niente.", "Notifications": "Notifiche", + "On this server": "Su questo server", "One chat that grows": "Una chat che cresce", "Only in its history.": "Solo nella sua cronologia.", "Open in the viewer": "Apri nel visualizzatore", @@ -2719,6 +2809,7 @@ const LEMBAS_I18N = { "Think before answering": "Ragiona prima di rispondere", "Thinking": "Ragionamento", "This computer": "Questo computer", + "This computer (%(name)s)": "Questo computer (%(name)s)", "This computer is not linked yet.": "Questo computer non è ancora collegato.", "This computer's (%(language)s)": "Quella di questo computer (%(language)s)", "This device is not linked right now.": "Questo dispositivo non è collegato in questo momento.", @@ -2747,6 +2838,7 @@ const LEMBAS_I18N = { "Waiting for a free model": "In attesa di un modello libero", "Waiting for subagents: {{running}} running": "In attesa dei subagenti: {{running}} al lavoro", "Waiting for the browser…": "In attesa del browser…", + "Waiting for the computer": "In attesa del computer", "Waiting for the reply before it": "In attesa della risposta precedente", "Waiting for you: {{what}}": "In attesa di te: {{what}}", "Waiting for your answer": "In attesa della sua risposta", @@ -2757,6 +2849,7 @@ const LEMBAS_I18N = { "What your models remember about you, across chats. Change or remove anything here.": "Ciò che i tuoi modelli ricordano di te tra una chat e l’altra. Qui puoi cambiare o rimuovere qualsiasi cosa.", "When": "Quando", "When it matters": "Quando conta", + "Where it runs": "Dove viene eseguito", "Wide": "Larga", "Writing": "Scrittura", "Writing down…": "Trascrizione…", @@ -2780,12 +2873,14 @@ const LEMBAS_I18N = { "all its slots are in use": "tutti i suoi posti sono occupati", "allow {{tool}}?": "consentire {{tool}}?", "an approval": "un’approvazione", + "approvals wait %(n)s min": "le approvazioni attendono %(n)s min", "budget": "budget", "busy": "occupato", "by you": "da te", "caught up": "recuperata", "dark": "scuro", "default": "predefinito", + "does not accept scheduled work": "non accetta lavoro pianificato", "done": "finito", "failed": "fallito", "findings: %(n)s": "risultati: %(n)s", @@ -2798,6 +2893,7 @@ const LEMBAS_I18N = { "light": "chiaro", "messages: %(n)s": "messaggi: %(n)s", "not linked": "non collegato", + "not linked now": "non collegato ora", "off": "disattivato", "out of context": "senza contesto", "paused: context full": "in pausa: contesto pieno", @@ -2842,8 +2938,12 @@ const LEMBAS_I18N = { "A busy model is waited for, then the next free one is used.": "Na zajęty model się czeka, potem używany jest następny wolny.", "A chat asking for more is lowered to this.": "Czat, który prosi o więcej, zostaje ograniczony do tego.", "A chat on %(server)s, with your account's models and files.": "Czat na %(server)s, z modelami i plikami Twojego konta.", + "A command the run needs approved comes to your phone as a card. Unanswered by then, it counts as denied.": "Polecenie, które uruchomienie musi zatwierdzić, trafia na Twój telefon jako karta. Bez odpowiedzi do tego czasu liczy się jako odrzucone.", + "A full path, inside a folder the computer allows remote work in.": "Pełna ścieżka, w folderze, w którym komputer pozwala na pracę zdalną.", + "A linked computer": "Połączony komputer", "A new chat for each run": "Nowy czat dla każdego uruchomienia", "A result, but not a run with nothing to report.": "Wynik, ale nie uruchomienie bez niczego do zgłoszenia.", + "A run on a computer is an agent chat there. The computer must allow it: “Accept scheduled work” in its LLeMbas CLI (lembas service scheduled on).": "Uruchomienie na komputerze jest tam czatem agenta. Komputer musi na to pozwolić: „Accept scheduled work” w jego LLeMbas CLI (lembas service scheduled on).", "A scheduled task failed 3 times in a row. Pause it?": "Zaplanowane zadanie nie powiodło się 3 razy z rzędu. Wstrzymać je?", "A scheduled task failed.": "Zaplanowane zadanie nie powiodło się.", "A scheduled task has a result.": "Zaplanowane zadanie ma wynik.", @@ -2864,6 +2964,7 @@ const LEMBAS_I18N = { "Always allowed here": "Tutaj zawsze dozwolone", "An agent chat works in a folder on %(device)s, through the LLeMbas agent there.": "Czat z agentem pracuje w folderze na %(device)s, przez działającego tam agenta LLeMbas.", "An agent chat works in a folder on this computer, through the agent built into this app.": "Czat z agentem pracuje w folderze na tym komputerze, przez agenta wbudowanego w tę aplikację.", + "An approval waits (minutes)": "Zatwierdzenie czeka (minuty)", "Another device": "Inne urządzenie", "Answer": "Odpowiedź", "Appearance": "Wygląd", @@ -2874,6 +2975,7 @@ const LEMBAS_I18N = { "Attach files": "Załączaj pliki", "Auto — asks nothing": "Auto — o nic nie pyta", "Automatic compaction is off.": "Automatyczne streszczanie jest wyłączone.", + "Away now: a run waits for it, then counts as missed.": "Teraz nieobecny: uruchomienie na niego czeka, potem liczy się jako pominięte.", "Back": "Wstecz", "Background command": "Polecenie w tle", "Background tasks": "Zadania w tle", @@ -2940,6 +3042,7 @@ const LEMBAS_I18N = { "Edit": "Edytuj", "Edit %(title)s": "Edytuj %(title)s", "Edit message": "Edytuj wiadomość", + "Edit unless you choose otherwise; Auto runs commands without asking. The computer may lower it.": "Edit, chyba że wybierzesz inaczej; Auto uruchamia polecenia bez pytania. Komputer może go obniżyć.", "Edit — asks before commands": "Edit — pyta przed poleceniami", "Empty folder": "Pusty folder", "Ended": "Zakończone", @@ -2955,6 +3058,7 @@ const LEMBAS_I18N = { "Folder": "Folder", "Folder name": "Nazwa folderu", "Folder on %(device)s": "Folder na %(device)s", + "Folder on the computer": "Folder na komputerze", "Free space": "Wolne miejsce", "Hello": "Cześć", "Hello, %(name)s": "Cześć, %(name)s", @@ -2963,6 +3067,7 @@ const LEMBAS_I18N = { "In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Własnymi słowami. Serwer zamieni je w regułę i polecenie i pokaże następne uruchomienia, zanim cokolwiek zostanie zapisane.", "Install LLeMbas %(version)s…": "Zainstaluj LLeMbas %(version)s…", "Instruction files": "Pliki z instrukcjami", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Nie przyjmuje zaplanowanej pracy: włącz tam Accept scheduled work.", "It has not run yet.": "Jeszcze się nie uruchomił.", "It says: %(purpose)s": "Mówi: %(purpose)s", "Kept in this app on this computer. The window opens again in the language chosen.": "Zapisane w tej aplikacji na tym komputerze. Okno otworzy się ponownie w wybranym języku.", @@ -3034,6 +3139,7 @@ const LEMBAS_I18N = { "Nothing to switch in this chat.": "W tym czacie nie ma czego przełączać.", "Nothing yet.": "Jeszcze nic.", "Notifications": "Powiadomienia", + "On this server": "Na tym serwerze", "One chat that grows": "Jeden czat, który rośnie", "Only in its history.": "Tylko w jego historii.", "Open in the viewer": "Otwórz w podglądzie", @@ -3187,6 +3293,7 @@ const LEMBAS_I18N = { "Think before answering": "Rozumuj przed odpowiedzią", "Thinking": "Rozumowanie", "This computer": "Ten komputer", + "This computer (%(name)s)": "Ten komputer (%(name)s)", "This computer is not linked yet.": "Ten komputer nie jest jeszcze połączony.", "This computer's (%(language)s)": "Jak na tym komputerze (%(language)s)", "This device is not linked right now.": "To urządzenie nie jest teraz połączone.", @@ -3215,6 +3322,7 @@ const LEMBAS_I18N = { "Waiting for a free model": "Czeka na wolny model", "Waiting for subagents: {{running}} running": "Czeka na podagentów: pracuje {{running}}", "Waiting for the browser…": "Czekam na przeglądarkę…", + "Waiting for the computer": "Czeka na komputer", "Waiting for the reply before it": "Czeka na odpowiedź przed nią", "Waiting for you: {{what}}": "Czeka na ciebie: {{what}}", "Waiting for your answer": "Czeka na Twoją odpowiedź", @@ -3225,6 +3333,7 @@ const LEMBAS_I18N = { "What your models remember about you, across chats. Change or remove anything here.": "Co Twoje modele pamiętają o Tobie we wszystkich czatach. Tutaj możesz wszystko zmienić lub usunąć.", "When": "Kiedy", "When it matters": "Gdy to ważne", + "Where it runs": "Gdzie działa", "Wide": "Szeroki", "Writing": "Pisanie", "Writing down…": "Zapisywanie…", @@ -3248,12 +3357,14 @@ const LEMBAS_I18N = { "all its slots are in use": "wszystkie jego miejsca są zajęte", "allow {{tool}}?": "zezwolić na {{tool}}?", "an approval": "zgoda", + "approvals wait %(n)s min": "zatwierdzenia czekają %(n)s min", "budget": "budżet", "busy": "zajęty", "by you": "przez ciebie", "caught up": "nadrobiony", "dark": "ciemny", "default": "domyślna", + "does not accept scheduled work": "nie przyjmuje zaplanowanej pracy", "done": "gotowe", "failed": "nie powiodło się", "findings: %(n)s": "ustalenia: %(n)s", @@ -3266,6 +3377,7 @@ const LEMBAS_I18N = { "light": "jasny", "messages: %(n)s": "wiadomości: %(n)s", "not linked": "niepołączony", + "not linked now": "teraz niepołączony", "off": "wył.", "out of context": "bez kontekstu", "paused: context full": "wstrzymany: pełny kontekst", @@ -3310,8 +3422,12 @@ const LEMBAS_I18N = { "A busy model is waited for, then the next free one is used.": "Na zaneprázdnený model sa čaká, potom sa použije ďalší voľný.", "A chat asking for more is lowered to this.": "Konverzácia, ktorá chce viac, je znížená na toto.", "A chat on %(server)s, with your account's models and files.": "Konverzácia na %(server)s s modelmi a súbormi vášho účtu.", + "A command the run needs approved comes to your phone as a card. Unanswered by then, it counts as denied.": "Príkaz, ktorý spustenie potrebuje schváliť, príde do telefónu ako karta. Ak do tej doby nebude zodpovedaný, počíta sa ako zamietnutý.", + "A full path, inside a folder the computer allows remote work in.": "Úplná cesta v priečinku, v ktorom počítač povoľuje vzdialenú prácu.", + "A linked computer": "Prepojený počítač", "A new chat for each run": "Nová konverzácia pre každé spustenie", "A result, but not a run with nothing to report.": "Výsledok, ale nie spustenie, ktoré nemá čo hlásiť.", + "A run on a computer is an agent chat there. The computer must allow it: “Accept scheduled work” in its LLeMbas CLI (lembas service scheduled on).": "Spustenie na počítači je tam agentový chat. Počítač to musí povoliť: „Accept scheduled work“ v jeho LLeMbas CLI (lembas service scheduled on).", "A scheduled task failed 3 times in a row. Pause it?": "Naplánovaná úloha zlyhala 3-krát za sebou. Pozastaviť ju?", "A scheduled task failed.": "Naplánovaná úloha zlyhala.", "A scheduled task has a result.": "Naplánovaná úloha má výsledok.", @@ -3332,6 +3448,7 @@ const LEMBAS_I18N = { "Always allowed here": "Tu vždy povolené", "An agent chat works in a folder on %(device)s, through the LLeMbas agent there.": "Agentská konverzácia pracuje v priečinku na zariadení %(device)s cez tamojšieho agenta LLeMbas.", "An agent chat works in a folder on this computer, through the agent built into this app.": "Agentská konverzácia pracuje v priečinku na tomto počítači cez agenta vstavaného v tejto aplikácii.", + "An approval waits (minutes)": "Schválenie čaká (minúty)", "Another device": "Iné zariadenie", "Answer": "Odpovedať", "Appearance": "Vzhľad", @@ -3342,6 +3459,7 @@ const LEMBAS_I18N = { "Attach files": "Prikladať súbory", "Auto — asks nothing": "Auto — na nič sa nepýta", "Automatic compaction is off.": "Automatické zhrnutie je vypnuté.", + "Away now: a run waits for it, then counts as missed.": "Teraz nie je k dispozícii: spustenie naň počká, potom sa počíta ako zmeškané.", "Back": "Späť", "Background command": "Príkaz na pozadí", "Background tasks": "Úlohy na pozadí", @@ -3408,6 +3526,7 @@ const LEMBAS_I18N = { "Edit": "Upraviť", "Edit %(title)s": "Upraviť %(title)s", "Edit message": "Upraviť správu", + "Edit unless you choose otherwise; Auto runs commands without asking. The computer may lower it.": "Edit, ak nevyberiete inak; Auto spúšťa príkazy bez pýtania. Počítač ho môže znížiť.", "Edit — asks before commands": "Edit — pýta sa pred príkazmi", "Empty folder": "Prázdny priečinok", "Ended": "Skončené", @@ -3423,6 +3542,7 @@ const LEMBAS_I18N = { "Folder": "Priečinok", "Folder name": "Názov zložky", "Folder on %(device)s": "Priečinok na zariadení %(device)s", + "Folder on the computer": "Priečinok na počítači", "Free space": "Voľné miesto", "Hello": "Dobrý deň", "Hello, %(name)s": "Dobrý deň, %(name)s", @@ -3431,6 +3551,7 @@ const LEMBAS_I18N = { "In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Vlastnými slovami. Server z nich vytvorí pravidlo a pokyn a pred uložením vám ukáže ďalšie spustenia.", "Install LLeMbas %(version)s…": "Nainštalovať LLeMbas %(version)s…", "Instruction files": "Súbory s pokynmi", + "It does not accept scheduled work: turn on Accept scheduled work there.": "Neprijíma plánovanú prácu: zapnite tam Accept scheduled work.", "It has not run yet.": "Ešte nebežal.", "It says: %(purpose)s": "Model uvádza: %(purpose)s", "Kept in this app on this computer. The window opens again in the language chosen.": "Uložené v tejto aplikácii na tomto počítači. Okno sa znova otvorí vo zvolenom jazyku.", @@ -3502,6 +3623,7 @@ const LEMBAS_I18N = { "Nothing to switch in this chat.": "V tejto konverzácii nie je čo prepnúť.", "Nothing yet.": "Zatiaľ nič.", "Notifications": "Upozornenia", + "On this server": "Na tomto serveri", "One chat that grows": "Jedna konverzácia, ktorá rastie", "Only in its history.": "Len v jej histórii.", "Open in the viewer": "Otvoriť v prehliadači súborov", @@ -3655,6 +3777,7 @@ const LEMBAS_I18N = { "Think before answering": "Premýšľať pred odpoveďou", "Thinking": "Premýšľa", "This computer": "Tento počítač", + "This computer (%(name)s)": "Tento počítač (%(name)s)", "This computer is not linked yet.": "Tento počítač ešte nie je prepojený.", "This computer's (%(language)s)": "Podľa tohto počítača (%(language)s)", "This device is not linked right now.": "Toto zariadenie teraz nie je pripojené.", @@ -3683,6 +3806,7 @@ const LEMBAS_I18N = { "Waiting for a free model": "Čaká na voľný model", "Waiting for subagents: {{running}} running": "Čaká na podagentov: pracujú {{running}}", "Waiting for the browser…": "Čaká sa na prehliadač…", + "Waiting for the computer": "Čaká na počítač", "Waiting for the reply before it": "Čaká na odpoveď pred ňou", "Waiting for you: {{what}}": "Čaká na vás: {{what}}", "Waiting for your answer": "Čaká na vašu odpoveď", @@ -3693,6 +3817,7 @@ const LEMBAS_I18N = { "What your models remember about you, across chats. Change or remove anything here.": "Čo si o vás vaše modely pamätajú naprieč konverzáciami. Čokoľvek tu môžete zmeniť alebo odstrániť.", "When": "Kedy", "When it matters": "Keď na tom záleží", + "Where it runs": "Kde sa spúšťa", "Wide": "Široký", "Writing": "Píše", "Writing down…": "Zapisujem…", @@ -3716,12 +3841,14 @@ const LEMBAS_I18N = { "all its slots are in use": "všetky jeho sloty sú obsadené", "allow {{tool}}?": "povoliť {{tool}}?", "an approval": "povolenie", + "approvals wait %(n)s min": "schválenia čakajú %(n)s min", "budget": "rozpočet", "busy": "zaneprázdnený", "by you": "vami", "caught up": "dohnané", "dark": "tmavá", "default": "predvolené", + "does not accept scheduled work": "neprijíma plánovanú prácu", "done": "hotovo", "failed": "zlyhalo", "findings: %(n)s": "zistenia: %(n)s", @@ -3734,6 +3861,7 @@ const LEMBAS_I18N = { "light": "svetlá", "messages: %(n)s": "správy: %(n)s", "not linked": "neprepojené", + "not linked now": "teraz nie je prepojený", "off": "vypnuté", "out of context": "bez kontextu", "paused: context full": "pozastavený: plný kontext", diff --git a/ui/js/main.js b/ui/js/main.js index d8ea96b..f278b11 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -320,7 +320,7 @@ async function boot() { openFile: (path, name) => openServerFile(path, name), openMemory: (id) => showMemoryPage(id), }) - initScheduled({ status: () => status, models: () => models, me: () => me, openChat: (id) => showChat(id), show: () => showPage("scheduled") }) + initScheduled({ status: () => status, models: () => models, me: () => me, thisDevices, openChat: (id) => showChat(id), show: () => showPage("scheduled") }) onViewer((on) => { $("shell").dataset.viewer = String(on) }) diff --git a/ui/js/scheduled.js b/ui/js/scheduled.js index f58f098..24ef5a0 100644 --- a/ui/js/scheduled.js +++ b/ui/js/scheduled.js @@ -27,6 +27,15 @@ // by the app (Rust, watch.rs), handed here as the // `schedules` event // +// GET /devices { devices: [{ id, name, host, scheduled, max_mode }] }: +// the linked computers, each saying whether it +// accepts scheduled work +// +// Where it runs (`runs_on`, server 2.0.0-beta.9): null on the server, else `{ device, device_name, +// cwd, mode, approval_wait_minutes }` -- an agent chat on that linked computer, in that folder and +// mode (never Plan). A run waiting for the computer says `waiting_for: "device"`; one it refused +// (it does not accept scheduled work) fails with the computer's own words. +// // A server without them answers 404: the page says this server has no scheduled tasks yet. // Everything a task says (its title, instruction, a run's result or error) is the person's or a // model's words, set as text; a result's Markdown goes through markdown.js. @@ -37,7 +46,7 @@ import { ask, menu, popover } from "./popup.js" import { avatar, levelsOf, levelWord } from "./models.js" import { markdown } from "./markdown.js" -let ctx = null // { status(), models(), me(), openChat(id), show() } +let ctx = null // { status(), models(), me(), thisDevices(), openChat(id), show() } let view = { name: "list" } // { name: "list" } | { name: "task", id } | { name: "form", task? } let tasks = [] let missing = false // the server has no scheduling routes @@ -83,7 +92,7 @@ const RUNS_PAGE = 20 function statusChip(run, running) { if (running) return h("span", { class: "schip is-running", text: STATUS.running() }) if (!run) return h("span", { class: "schip", text: t("Not run yet") }) - const word = STATUS[run.status]?.() || run.status + const word = run.status === "waiting" && run.waiting_for === "device" ? t("Waiting for the computer") : STATUS[run.status]?.() || run.status // A missed run counts the slots that passed unrun; any other run says how many it followed. const n = run.status === "missed" ? (run.missed > 1 ? ` ×${run.missed}` : "") : run.missed > 0 ? ` · ${t("%(n)s missed", { n: run.missed })}` : "" return h("span", { class: `schip is-${run.status}`, title: `${word}${n}`, text: `${word}${n}` }) @@ -326,7 +335,8 @@ async function drawTask(id) { icon(ICONS.more), ), ) - const models = (await schedulingModels())?.models || ctx.models() + const [smodels, devices] = await Promise.all([schedulingModels(), linkedDevices()]) + const models = smodels?.models || ctx.models() const body = clear($("scheduled-body")) body.append( h( @@ -344,6 +354,7 @@ async function drawTask(id) { fact(t("Subagents"), h("span", { text: task.agents ? t("Up to %(n)s at once", { n: task.agents }) : t("None") })), fact(t("Tell me"), h("span", { text: NOTIFY[task.notify]?.() || NOTIFY.matters() })), fact(t("Runs"), h("span", { text: MODE[task.mode]?.() || MODE.fresh() })), + "runs_on" in task ? whereFact(task.runs_on, devices) : null, ), h("div", { class: "sched__what" }, h("h2", { class: "sched__h", text: t("What it does") }), h("p", { class: "sched__instruction", text: task.instruction || task.request || "" }), task.request && task.request !== task.instruction ? h("p", { class: "sched__asked", text: t("You asked: “%(text)s”", { text: task.request }) }) : null), upcoming(task.next_runs || []), @@ -352,6 +363,46 @@ async function drawTask(id) { ) } +// --- Where it runs ------------------------------------------------------------------------------ + +const RUN_MODES = { + manual: () => t("Manual — asks first"), + edit: () => t("Edit — asks before commands"), + auto: () => t("Auto — asks nothing"), +} + +/** The account's linked computers (`GET /devices`), or null where the server cannot say. */ +async function linkedDevices() { + try { + return (await get("/devices")).devices || [] + } catch { + return null + } +} + +function deviceLabel(d) { + const name = d.name || d.host || d.id + return (ctx.thisDevices?.() || []).includes(d.id) ? t("This computer (%(name)s)", { name }) : name +} + +/** "Where it runs": the server, or a computer and a folder, with what it is doing about it now. */ +function whereFact(on, devices) { + if (!on) return fact(t("Where it runs"), h("span", { text: t("On this server") })) + const d = (devices || []).find((x) => x.id === on.device) + const name = d ? deviceLabel(d) : on.device_name || t("A linked computer") + let state = "" + if (devices && !d) state = t("Away now: a run waits for it, then counts as missed.") + else if (d && d.scheduled === false) state = t("It does not accept scheduled work: turn on Accept scheduled work there.") + const hint = [RUN_MODES[on.mode]?.() || on.mode, t("approvals wait %(n)s min", { n: on.approval_wait_minutes }), state].filter(Boolean).join(" · ") + return h( + "div", + { class: `fact${state ? " is-warning" : ""}` }, + h("span", { class: "fact__label", text: t("Where it runs") }), + h("span", { class: "fact__value" }, h("bdi", { text: name }), h("span", { class: "fact__path", title: on.cwd }, h("bdi", { text: on.cwd }))), + h("span", { class: "fact__hint", text: hint }), + ) +} + function fact(label, value, hint) { return h("div", { class: "fact" }, h("span", { class: "fact__label", text: label }), h("span", { class: "fact__value" }, value), hint ? h("span", { class: "fact__hint", text: hint }) : null) } @@ -448,7 +499,7 @@ function runRow(task, run) { h("span", { text: stamp(run.started_at || run.due_at) }), TIMING[run.timing] ? h("span", { class: "run__late", text: TIMING[run.timing]() }) : null, ), - h("span", { class: "run__model", title: switched }, h("span", { text: model.name || "" }), switched ? h("span", { class: "run__switched", text: t("switched") }) : null), + h("span", { class: "run__model", title: switched }, h("span", { text: model.name || (task.runs_on ? task.runs_on.device_name || "" : "") }), switched ? h("span", { class: "run__switched", text: t("switched") }) : null), open || h("span", { class: "run__error", title: note, text: note }), detail, ) @@ -488,7 +539,10 @@ async function drawForm(task) { const editing = !!task header(editing ? t("Edit %(title)s", { title: task.title }) : t("New scheduled task"), () => go(editing ? { name: "task", id: task.id } : { name: "list" })) clear($("scheduled-tools")) - const smodels = (await schedulingModels())?.models || [] + const [sm, devices] = await Promise.all([schedulingModels(), linkedDevices()]) + const smodels = sm?.models || [] + // Offered where the server can run a schedule on a computer: its devices say whether they accept. + const canPlace = !!devices && (devices.some((d) => "scheduled" in d) || !!task?.runs_on) // What will be saved: the rule only from the server's reading of words (or the task's own). const draft = { request: task?.request || "", @@ -501,6 +555,7 @@ async function drawForm(task) { agents: task?.agents || null, notify: task?.notify || "matters", mode: task?.mode || "fresh", + runs_on: task?.runs_on ? { device: task.runs_on.device, cwd: task.runs_on.cwd, mode: task.runs_on.mode, approval_wait_minutes: task.runs_on.approval_wait_minutes } : null, } let reading = editing ? await post("/schedules/preview", { rule: task.rule }).catch(() => null) : null @@ -555,6 +610,47 @@ async function drawForm(task) { saveBtn.disabled = !(draft.rule && Object.keys(draft.rule).length) } + /** Where it runs: the server, or a linked computer that accepts scheduled work, its folder and mode. */ + const whereFields = () => { + const on = draft.runs_on + const listed = devices || [] + const where = h( + "select", + { + id: "sched-where", + onchange: (e) => { + const id = e.target.value + if (!id) draft.runs_on = null + else { + const mine = (ctx.thisDevices?.() || []).includes(id) + const roots = ctx.status?.()?.remote?.roots || [] + draft.runs_on = { device: id, cwd: draft.runs_on?.cwd || (mine ? roots[0] || "" : ""), mode: draft.runs_on?.mode || "edit", approval_wait_minutes: draft.runs_on?.approval_wait_minutes || 30 } + } + drawSettings() + settings.querySelector(draft.runs_on ? "#sched-cwd" : "#sched-where")?.focus() + }, + }, + h("option", { value: "", text: t("On this server"), selected: !on }), + listed.map((d) => h("option", { value: d.id, text: d.scheduled === false ? `${deviceLabel(d)} — ${t("does not accept scheduled work")}` : deviceLabel(d), disabled: d.scheduled === false && on?.device !== d.id, selected: on?.device === d.id })), + on && !listed.some((d) => d.id === on.device) ? h("option", { value: on.device, text: `${task?.runs_on?.device_name || on.device} — ${t("not linked now")}`, selected: true }) : null, + ) + const out = [field(t("Where it runs"), where, on ? t("A run on a computer is an agent chat there. The computer must allow it: “Accept scheduled work” in its LLeMbas CLI (lembas service scheduled on).") : "", "sched-where")] + if (!on) return out + const cwd = h("input", { type: "text", id: "sched-cwd", value: on.cwd, placeholder: "/home/…", spellcheck: "false", oninput: (e) => (on.cwd = e.target.value.trim()) }) + const mode = h( + "select", + { id: "sched-runmode", onchange: (e) => (on.mode = e.target.value) }, + Object.entries(RUN_MODES).map(([v, word]) => h("option", { value: v, text: word(), selected: on.mode === v })), + ) + const wait = h("input", { type: "number", id: "sched-wait", min: "1", max: "1440", step: "1", value: String(on.approval_wait_minutes || 30), oninput: (e) => (on.approval_wait_minutes = Number(e.target.value) || 30) }) + out.push( + field(t("Folder on the computer"), cwd, t("A full path, inside a folder the computer allows remote work in."), "sched-cwd"), + field(t("Mode"), mode, t("Edit unless you choose otherwise; Auto runs commands without asking. The computer may lower it."), "sched-runmode"), + field(t("An approval waits (minutes)"), wait, t("A command the run needs approved comes to your phone as a card. Unanswered by then, it counts as denied."), "sched-wait"), + ) + return out + } + const drawSettings = () => { const model = smodels.find((m) => m.id === draft.model) || ctx.models().find((m) => m.id === draft.model) const select = (id, table, key, onChange) => @@ -586,6 +682,7 @@ async function drawForm(task) { Array.from({ length: MAX_AGENTS - 1 }, (_, i) => i + 2).map((n) => h("option", { value: String(n), text: t("Up to %(n)s at once", { n }), selected: draft.agents === n })), ) settings.replaceChildren( + ...(canPlace ? whereFields() : []), field(t("Tell me"), select("sched-notify", NOTIFY, "notify", () => (settings.querySelector("#sched-notify-hint").textContent = NOTIFY_HINT[draft.notify]())), NOTIFY_HINT[draft.notify]?.() || "", "sched-notify", "sched-notify-hint"), field(t("Runs"), select("sched-mode", MODE, "mode"), draft.mode === "continue" ? t("Each run sees the ones before it.") : t("Each run starts with nothing but the instruction."), "sched-mode"), field(t("Model"), modelSelect, t("A busy model is waited for, then the next free one is used."), "sched-model"), @@ -656,6 +753,7 @@ async function drawForm(task) { notify: draft.notify, mode: draft.mode, } + if (canPlace) body.runs_on = draft.runs_on ? { ...draft.runs_on } : null try { const r = editing ? await patch(`/schedules/${enc(task.id)}`, body) : await post("/schedules", body) go({ name: "task", id: r.schedule.id })