fix(security): GitHub broker, drop sudo, DE awareness i18n, CI image tools
Route git push and gh pr through the sandbox supervisor broker so project computers never receive GITHUB_TOKEN/GH_TOKEN. Remove NOPASSWD sudo from the computer image. Localize awareness strip copy via Lingui and wire computer-image-tools.test.sh into the validate-computer-image job. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
This commit is contained in:
parent
35ac96fed3
commit
0c9e6d7c58
7
.github/workflows/publish-server-image.yml
vendored
7
.github/workflows/publish-server-image.yml
vendored
@ -83,10 +83,12 @@ jobs:
|
||||
images: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }}
|
||||
tags: type=sha,prefix=sha-
|
||||
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
||||
id: build
|
||||
with:
|
||||
context: ${{ matrix.context }}
|
||||
file: ${{ matrix.dockerfile }}
|
||||
push: false
|
||||
load: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: ${{ matrix.name == 'app' && format('GIT_SHA={0}', github.sha) || '' }}
|
||||
@ -94,6 +96,11 @@ jobs:
|
||||
cache-to: type=gha,mode=max,scope=pr-${{ github.event.pull_request.number }}-${{ matrix.name }}
|
||||
provenance: false
|
||||
sbom: false
|
||||
- name: Verify computer image tooling
|
||||
if: matrix.name == 'computer'
|
||||
env:
|
||||
RAKAZO_COMPUTER_IMAGE: ghcr.io/${{ steps.ns.outputs.repo }}/computer:sha-${{ github.sha }}
|
||||
run: bash infra/sandboxes/computer/computer-image-tools.test.sh
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.name }} ${{ matrix.arch }}
|
||||
|
||||
@ -2,10 +2,10 @@ import { useLingui } from "@lingui/react/macro";
|
||||
import type { ThreadSnapshot } from "@rakazo/contracts";
|
||||
import { BotAvatar } from "@rakazo/ui-web";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { narrateAwarenessDescriptor } from "../../lib/awareness-persona-copy";
|
||||
import {
|
||||
cadencePulseMs,
|
||||
extractVoiceAwareness,
|
||||
narrateAwarenessForPersona,
|
||||
narrateGroupSpark,
|
||||
personaBloomGlow,
|
||||
personaTextPose,
|
||||
@ -122,7 +122,7 @@ export function OoStage({
|
||||
}, [phase, persona.cadence, thinkingLines.length]);
|
||||
|
||||
const awarenessLabel = awarenessEvent
|
||||
? narrateAwarenessForPersona(persona, awarenessEvent)
|
||||
? t(narrateAwarenessDescriptor(persona.archetype, awarenessEvent))
|
||||
: null;
|
||||
|
||||
const subtitle = useMemo(() => {
|
||||
@ -397,7 +397,9 @@ export function OoGroupStage({
|
||||
}, [phase, persona?.cadence, thinkingLines.length]);
|
||||
|
||||
const awarenessLabel =
|
||||
awarenessEvent && persona ? narrateAwarenessForPersona(persona, awarenessEvent) : null;
|
||||
awarenessEvent && persona
|
||||
? t(narrateAwarenessDescriptor(persona.archetype, awarenessEvent))
|
||||
: null;
|
||||
const sparkLabel = groupSpark ? narrateGroupSpark(groupSpark, persona?.archetype) : null;
|
||||
|
||||
const subtitle = useMemo(() => {
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { BotAvatar } from "@rakazo/ui-web";
|
||||
import { ArrowRightLeft, GitBranch, MessageCircle, Users } from "lucide-react";
|
||||
import { awarenessKindLabel } from "../../lib/awareness-persona-copy";
|
||||
import type { VoiceAwarenessEvent } from "../../lib/bot-persona";
|
||||
|
||||
export function VoiceAwarenessStrip({
|
||||
@ -13,6 +15,7 @@ export function VoiceAwarenessStrip({
|
||||
botColor: string;
|
||||
botId: string;
|
||||
}) {
|
||||
const { t } = useLingui();
|
||||
const Icon =
|
||||
event.kind === "subagent"
|
||||
? Users
|
||||
@ -24,19 +27,6 @@ export function VoiceAwarenessStrip({
|
||||
? MessageCircle
|
||||
: Users;
|
||||
|
||||
const kindLabel =
|
||||
event.kind === "subagent"
|
||||
? "Collaborating"
|
||||
: event.kind === "handoff"
|
||||
? "Handoff"
|
||||
: event.kind === "delegation"
|
||||
? "Delegated"
|
||||
: event.kind === "peer-reply"
|
||||
? "Peer reply"
|
||||
: event.kind === "working"
|
||||
? "In progress"
|
||||
: "Aware";
|
||||
|
||||
return (
|
||||
<div
|
||||
role="status"
|
||||
@ -47,7 +37,7 @@ export function VoiceAwarenessStrip({
|
||||
<div className="min-w-0 flex-1 text-start">
|
||||
<div className="flex items-center gap-2 text-ui-label font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
<Icon strokeWidth={1.8} className="size-icon-sm shrink-0 opacity-80" />
|
||||
<span>{kindLabel}</span>
|
||||
<span>{t(awarenessKindLabel(event.kind))}</span>
|
||||
</div>
|
||||
<p dir="auto" className="mt-1 text-ui-body leading-relaxed text-foreground">
|
||||
{label}
|
||||
|
||||
139
apps/web/src/lib/awareness-persona-copy.ts
Normal file
139
apps/web/src/lib/awareness-persona-copy.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import type { MessageDescriptor } from "@lingui/core";
|
||||
import { msg } from "@lingui/core/macro";
|
||||
import type { PersonaArchetype } from "./bot-persona";
|
||||
import type { VoiceAwarenessEvent } from "./bot-persona";
|
||||
|
||||
export function awarenessKindLabel(kind: VoiceAwarenessEvent["kind"]): MessageDescriptor {
|
||||
switch (kind) {
|
||||
case "subagent":
|
||||
return msg`Collaborating`;
|
||||
case "handoff":
|
||||
return msg`Handoff`;
|
||||
case "delegation":
|
||||
return msg`Delegated`;
|
||||
case "peer-reply":
|
||||
return msg`Peer reply`;
|
||||
case "working":
|
||||
return msg`In progress`;
|
||||
default:
|
||||
return msg`Aware`;
|
||||
}
|
||||
}
|
||||
|
||||
function truncate(text: string, max: number): string {
|
||||
const trimmed = text.trim();
|
||||
if (trimmed.length <= max) return trimmed;
|
||||
return `${trimmed.slice(0, max - 1)}…`;
|
||||
}
|
||||
|
||||
export function narrateAwarenessDescriptor(
|
||||
archetype: PersonaArchetype,
|
||||
event: VoiceAwarenessEvent,
|
||||
): MessageDescriptor {
|
||||
const task = event.detail ? truncate(event.detail, 96) : "";
|
||||
const fallbackName = "a specialist";
|
||||
switch (event.kind) {
|
||||
case "subagent":
|
||||
return narrateSubagentDescriptor(archetype, event.targetName ?? fallbackName, task);
|
||||
case "handoff":
|
||||
return narrateHandoffDescriptor(archetype, event.targetName ?? fallbackName, task);
|
||||
case "delegation":
|
||||
return narrateDelegationDescriptor(archetype, event.targetName ?? fallbackName, task);
|
||||
case "peer-reply":
|
||||
return narratePeerReplyDescriptor(archetype, event.actorName, task);
|
||||
case "working":
|
||||
return narrateWorkingDescriptor(archetype, event.actorName);
|
||||
default:
|
||||
return msg`${task}`;
|
||||
}
|
||||
}
|
||||
|
||||
function narrateSubagentDescriptor(
|
||||
archetype: PersonaArchetype,
|
||||
name: string,
|
||||
task: string,
|
||||
): MessageDescriptor {
|
||||
switch (archetype) {
|
||||
case "cto-executive":
|
||||
return task
|
||||
? msg`I'm casting ${name} on this — ${task}`
|
||||
: msg`I'm bringing ${name} into the room.`;
|
||||
case "chief-operator":
|
||||
return task ? msg`Routing to ${name}: ${task}` : msg`Looping in ${name}.`;
|
||||
case "builder":
|
||||
return task ? msg`Spinning up ${name} for ${task}` : msg`Assembling ${name}.`;
|
||||
case "quant":
|
||||
return task ? msg`Running ${name} on the numbers — ${task}` : msg`${name} is on the data.`;
|
||||
case "engineer":
|
||||
return task ? msg`Handing off to ${name}: ${task}` : msg`${name} is taking the keyboard.`;
|
||||
default:
|
||||
return task ? msg`${name} is helping with ${task}` : msg`${name} joined the thread.`;
|
||||
}
|
||||
}
|
||||
|
||||
function narrateHandoffDescriptor(
|
||||
archetype: PersonaArchetype,
|
||||
target: string,
|
||||
text: string,
|
||||
): MessageDescriptor {
|
||||
const line = text ? truncate(text, 72) : "";
|
||||
switch (archetype) {
|
||||
case "cto-executive":
|
||||
return line ? msg`Passing the baton to ${target} — ${line}` : msg`This goes to ${target} now.`;
|
||||
case "chief-operator":
|
||||
return line ? msg`Handoff to ${target}: ${line}` : msg`Transferring to ${target}.`;
|
||||
default:
|
||||
return line ? msg`Handing over to ${target}: ${line}` : msg`Connecting you with ${target}.`;
|
||||
}
|
||||
}
|
||||
|
||||
function narrateDelegationDescriptor(
|
||||
archetype: PersonaArchetype,
|
||||
target: string,
|
||||
text: string,
|
||||
): MessageDescriptor {
|
||||
const line = truncate(text, 72);
|
||||
switch (archetype) {
|
||||
case "cto-executive":
|
||||
return msg`I asked ${target} to ${line}`;
|
||||
case "chief-operator":
|
||||
return msg`Delegated to ${target}: ${line}`;
|
||||
default:
|
||||
return msg`Asked ${target}: ${line}`;
|
||||
}
|
||||
}
|
||||
|
||||
function narratePeerReplyDescriptor(
|
||||
archetype: PersonaArchetype,
|
||||
name: string,
|
||||
task: string,
|
||||
): MessageDescriptor {
|
||||
const line = task ? truncate(task, 72) : "";
|
||||
switch (archetype) {
|
||||
case "cto-executive":
|
||||
return line ? msg`${name} pushed back — ${line}` : msg`${name} jumped in with a take.`;
|
||||
case "chief-operator":
|
||||
return line ? msg`${name} riffed: ${line}` : msg`${name} is weighing in.`;
|
||||
case "builder":
|
||||
return line ? msg`${name} geeked out: ${line}` : msg`${name} couldn't resist chiming in.`;
|
||||
case "quant":
|
||||
return line ? msg`${name} on the numbers: ${line}` : msg`${name} has a counter-read.`;
|
||||
case "engineer":
|
||||
return line ? msg`${name} fired back: ${line}` : msg`${name} joined the thread.`;
|
||||
default:
|
||||
return line ? msg`${name} replied — ${line}` : msg`${name} is riffing back…`;
|
||||
}
|
||||
}
|
||||
|
||||
function narrateWorkingDescriptor(archetype: PersonaArchetype, name: string): MessageDescriptor {
|
||||
switch (archetype) {
|
||||
case "cto-executive":
|
||||
return msg`${name} is in the work — give me a moment.`;
|
||||
case "quant":
|
||||
return msg`${name} is crunching — one beat.`;
|
||||
case "engineer":
|
||||
return msg`${name} is heads-down on this.`;
|
||||
default:
|
||||
return msg`${name} is working on your request.`;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
extractVoiceAwareness,
|
||||
narrateAwarenessForPersona,
|
||||
narrateGroupSpark,
|
||||
personaTextPose,
|
||||
resolveBotPersona,
|
||||
@ -86,24 +85,6 @@ describe("extractVoiceAwareness", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("narrateAwarenessForPersona", () => {
|
||||
it("uses executive casting language for CTO archetype", () => {
|
||||
const profile = resolveBotPersona({
|
||||
name: "Steve Jobs - Autonomous CTO",
|
||||
title: "CTO",
|
||||
description: "",
|
||||
});
|
||||
const line = narrateAwarenessForPersona(profile, {
|
||||
kind: "subagent",
|
||||
actorName: profile.roleLine,
|
||||
targetName: "Code Architect",
|
||||
detail: "Harden login flow",
|
||||
});
|
||||
expect(line).toContain("Code Architect");
|
||||
expect(line.toLowerCase()).toContain("casting");
|
||||
});
|
||||
});
|
||||
|
||||
describe("narrateGroupSpark", () => {
|
||||
it("uses playful peer-reply language for builder archetype", () => {
|
||||
const line = narrateGroupSpark(
|
||||
|
||||
@ -242,6 +242,10 @@ msgstr "{memberCount, plural, one {# Agent eingeladen} other {# Agenten eingelad
|
||||
msgid "{minutes}m ago"
|
||||
msgstr "vor {minutes} Min."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} couldn't resist chiming in."
|
||||
msgstr "{name} konnte nicht widerstehen."
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} failed"
|
||||
@ -252,6 +256,78 @@ msgstr "{name} ist fehlgeschlagen"
|
||||
msgid "{name} finished"
|
||||
msgstr "{name} ist fertig"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} fired back: {line}"
|
||||
msgstr "{name} kontert: {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} geeked out: {line}"
|
||||
msgstr "{name} schwärmt: {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} has a counter-read."
|
||||
msgstr "{name} hat eine Gegenmeinung."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is crunching — one beat."
|
||||
msgstr "{name} rechnet — kurz."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is heads-down on this."
|
||||
msgstr "{name} arbeitet konzentriert daran."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is helping with {task}"
|
||||
msgstr "{name} hilft bei {task}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is in the work — give me a moment."
|
||||
msgstr "{name} arbeitet daran — einen Moment."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is on the data."
|
||||
msgstr "{name} arbeitet an den Daten."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is riffing back…"
|
||||
msgstr "{name} antwortet…"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is taking the keyboard."
|
||||
msgstr "{name} übernimmt."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is weighing in."
|
||||
msgstr "{name} äußert sich."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is working on your request."
|
||||
msgstr "{name} arbeitet an deiner Anfrage."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} joined the thread."
|
||||
msgstr "{name} ist im Thread."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} jumped in with a take."
|
||||
msgstr "{name} mischt sich ein."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} on the numbers: {line}"
|
||||
msgstr "{name} zu den Zahlen: {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} pushed back — {line}"
|
||||
msgstr "{name} widerspricht — {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} replied — {line}"
|
||||
msgstr "{name} antwortet — {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} riffed: {line}"
|
||||
msgstr "{name}: {line}"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} stopped"
|
||||
@ -274,6 +350,10 @@ msgstr "{size} B"
|
||||
msgid "{steps, plural, one {# step} other {# steps}}"
|
||||
msgstr "{steps, plural, one {# Schritt} other {# Schritte}}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{task}"
|
||||
msgstr "{task}"
|
||||
|
||||
#. placeholder {0}: runActivityLabel(run.status)
|
||||
#: src/pages/HeuteBrief.tsx
|
||||
msgid "{title}, {0}"
|
||||
@ -721,6 +801,14 @@ msgstr "Bitte den Serverbesitzer, diesen Anbieter zu konfigurieren."
|
||||
msgid "Ask within my scope — I won't just yes you."
|
||||
msgstr "Frag in meinem Fachgebiet — ich sage nicht einfach Ja."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Asked {target}: {line}"
|
||||
msgstr "An {target}: {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Assembling {name}."
|
||||
msgstr "{name} wird hinzugezogen."
|
||||
|
||||
#. placeholder {0}: preset.time
|
||||
#: src/pages/RoutineSchedule.tsx
|
||||
msgid "at {0}"
|
||||
@ -788,6 +876,10 @@ msgstr "Autopilot gestoppt"
|
||||
msgid "Available after the routine is saved"
|
||||
msgstr "Verfügbar, sobald die Routine gespeichert ist"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Aware"
|
||||
msgstr "Hinweis"
|
||||
|
||||
#: src/components/browser/InAppBrowser.tsx
|
||||
#: src/components/CookingMode.tsx
|
||||
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||
@ -1256,6 +1348,10 @@ msgstr "Code"
|
||||
msgid "Coder"
|
||||
msgstr "Coder"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Collaborating"
|
||||
msgstr "Zusammenarbeit"
|
||||
|
||||
#. placeholder {0}: group.title
|
||||
#: src/pages/shell/sidebar-group-header.tsx
|
||||
msgid "Collapse {0}"
|
||||
@ -1412,6 +1508,10 @@ msgstr "Verbundene App"
|
||||
msgid "Connected. Its tools are available from your next message."
|
||||
msgstr "Verbunden. Die Tools sind ab deiner nächsten Nachricht verfügbar."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Connecting you with {target}."
|
||||
msgstr "Ich verbinde dich mit {target}."
|
||||
|
||||
#: src/components/integrations/IntegrationSetup.tsx
|
||||
#: src/pages/McpServersOverlay.tsx
|
||||
#: src/pages/memory-providers/BuiltinSettingsForm.tsx
|
||||
@ -1977,6 +2077,14 @@ msgstr "Standard"
|
||||
msgid "Default ({0})"
|
||||
msgstr "Standard ({0})"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated"
|
||||
msgstr "Delegiert"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated to {target}: {line}"
|
||||
msgstr "Delegiert an {target}: {line}"
|
||||
|
||||
#: src/components/UserProfileSettings.tsx
|
||||
#: src/pages/BotContextMenu.tsx
|
||||
#: src/pages/KnowledgeSection.tsx
|
||||
@ -2623,6 +2731,22 @@ msgstr "Gruppen"
|
||||
msgid "Habits"
|
||||
msgstr "Gewohnheiten"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing off to {name}: {task}"
|
||||
msgstr "Übergabe an {name}: {task}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing over to {target}: {line}"
|
||||
msgstr "Übergabe an {target}: {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff"
|
||||
msgstr "Übergabe"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff to {target}: {line}"
|
||||
msgstr "Übergabe an {target}: {line}"
|
||||
|
||||
#: src/components/voice/VoiceStage.tsx
|
||||
msgid "Hang up"
|
||||
msgstr "Auflegen"
|
||||
@ -2726,6 +2850,18 @@ msgstr "Wie soll dein Oo klingen, wenn es Antworten vorliest?"
|
||||
msgid "How to check"
|
||||
msgstr "So wird geprüft"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I asked {target} to {line}"
|
||||
msgstr "Ich habe {target} gebeten: {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm bringing {name} into the room."
|
||||
msgstr "Ich hole {name} dazu."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm casting {name} on this — {task}"
|
||||
msgstr "Ich setze {name} auf das an — {task}"
|
||||
|
||||
#: src/pages/shell/computer-screen.tsx
|
||||
msgid "I’m done"
|
||||
msgstr "Ich bin fertig"
|
||||
@ -2766,6 +2902,7 @@ msgstr "OpenAPI-JSON importieren"
|
||||
msgid "In my lane — happy to geek out or just execute, your call."
|
||||
msgstr "In meinem Fachgebiet — ich gehe gern ins Detail oder setze einfach um, du entscheidest."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
#: src/pages/ProjectSessionTimeline.tsx
|
||||
msgid "In progress"
|
||||
msgstr "In Arbeit"
|
||||
@ -3047,6 +3184,10 @@ msgstr "Look-Varianten"
|
||||
msgid "Looking"
|
||||
msgstr "Suche läuft"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Looping in {name}."
|
||||
msgstr "{name} ist mit dabei."
|
||||
|
||||
#: src/pages/ModelSettingsOverlay.tsx
|
||||
#: src/pages/shell/bot-panel.tsx
|
||||
msgid "Low"
|
||||
@ -3748,6 +3889,10 @@ msgstr "Zurückstellen"
|
||||
msgid "Party hat"
|
||||
msgstr "Partyhut"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Passing the baton to {target} — {line}"
|
||||
msgstr "Stafette an {target} — {line}"
|
||||
|
||||
#: src/components/AskCard.tsx
|
||||
#: src/pages/AccountSettingsOverlay.tsx
|
||||
#: src/pages/Auth.tsx
|
||||
@ -3789,6 +3934,10 @@ msgstr "Pfirsich"
|
||||
msgid "Peaks too high for {name} (max {max} dBTP)"
|
||||
msgstr "Pegelspitzen zu hoch für {name} (maximal {max} dBTP)"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Peer reply"
|
||||
msgstr "Antwort"
|
||||
|
||||
#: src/components/OoAtelier.tsx
|
||||
msgid "Penguin"
|
||||
msgstr "Pinguin"
|
||||
@ -4273,6 +4422,10 @@ msgstr "Routine"
|
||||
msgid "Routines"
|
||||
msgstr "Routinen"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Routing to {name}: {task}"
|
||||
msgstr "Weiter an {name}: {task}"
|
||||
|
||||
#: src/pages/RoutineEditor.tsx
|
||||
msgid "Run at"
|
||||
msgstr "Ausführen um"
|
||||
@ -4298,6 +4451,10 @@ msgstr "Läuft"
|
||||
msgid "Running · Stop"
|
||||
msgstr "Wird ausgeführt · Stoppen"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Running {name} on the numbers — {task}"
|
||||
msgstr "{name} prüft die Zahlen — {task}"
|
||||
|
||||
#: src/lib/voice-persona-copy.ts
|
||||
msgid "Running the numbers…"
|
||||
msgstr "Rechne nach…"
|
||||
@ -4704,6 +4861,10 @@ msgstr "Wesen"
|
||||
msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings."
|
||||
msgstr "Die Spracherkennung ist blockiert. Erlaube das Mikrofon und schalte das Diktieren in den Systemeinstellungen ein."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Spinning up {name} for {task}"
|
||||
msgstr "Starte {name} für {task}"
|
||||
|
||||
#: src/lib/model-tier-label.ts
|
||||
msgid "Standard"
|
||||
msgstr "Standard"
|
||||
@ -4956,6 +5117,10 @@ msgstr "dieser Computer"
|
||||
msgid "This file is not valid UTF-8 Markdown."
|
||||
msgstr "Diese Datei ist kein gültiges UTF-8-Markdown."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "This goes to {target} now."
|
||||
msgstr "Das geht jetzt an {target}."
|
||||
|
||||
#: src/pages/HostComputerPrompt.tsx
|
||||
msgid "this Mac"
|
||||
msgstr "dieser Mac"
|
||||
@ -5078,6 +5243,10 @@ msgstr "Transkription"
|
||||
msgid "Transcription request timed out."
|
||||
msgstr "Die Transkription hat zu lange gedauert."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Transferring to {target}."
|
||||
msgstr "Weiterleitung an {target}."
|
||||
|
||||
#: src/pages/ExternalConversationSettings.tsx
|
||||
msgid "Treat like a person"
|
||||
msgstr "Wie eine Person behandeln"
|
||||
|
||||
@ -242,6 +242,10 @@ msgstr "{memberCount, plural, one {# agent invited} other {# agents invited}}"
|
||||
msgid "{minutes}m ago"
|
||||
msgstr "{minutes}m ago"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} couldn't resist chiming in."
|
||||
msgstr "{name} couldn't resist chiming in."
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} failed"
|
||||
@ -252,6 +256,78 @@ msgstr "{name} failed"
|
||||
msgid "{name} finished"
|
||||
msgstr "{name} finished"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} fired back: {line}"
|
||||
msgstr "{name} fired back: {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} geeked out: {line}"
|
||||
msgstr "{name} geeked out: {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} has a counter-read."
|
||||
msgstr "{name} has a counter-read."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is crunching — one beat."
|
||||
msgstr "{name} is crunching — one beat."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is heads-down on this."
|
||||
msgstr "{name} is heads-down on this."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is helping with {task}"
|
||||
msgstr "{name} is helping with {task}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is in the work — give me a moment."
|
||||
msgstr "{name} is in the work — give me a moment."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is on the data."
|
||||
msgstr "{name} is on the data."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is riffing back…"
|
||||
msgstr "{name} is riffing back…"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is taking the keyboard."
|
||||
msgstr "{name} is taking the keyboard."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is weighing in."
|
||||
msgstr "{name} is weighing in."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is working on your request."
|
||||
msgstr "{name} is working on your request."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} joined the thread."
|
||||
msgstr "{name} joined the thread."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} jumped in with a take."
|
||||
msgstr "{name} jumped in with a take."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} on the numbers: {line}"
|
||||
msgstr "{name} on the numbers: {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} pushed back — {line}"
|
||||
msgstr "{name} pushed back — {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} replied — {line}"
|
||||
msgstr "{name} replied — {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} riffed: {line}"
|
||||
msgstr "{name} riffed: {line}"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} stopped"
|
||||
@ -274,6 +350,10 @@ msgstr "{size} B"
|
||||
msgid "{steps, plural, one {# step} other {# steps}}"
|
||||
msgstr "{steps, plural, one {# step} other {# steps}}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{task}"
|
||||
msgstr "{task}"
|
||||
|
||||
#. placeholder {0}: runActivityLabel(run.status)
|
||||
#: src/pages/HeuteBrief.tsx
|
||||
msgid "{title}, {0}"
|
||||
@ -721,6 +801,14 @@ msgstr "Ask the server owner to configure this provider."
|
||||
msgid "Ask within my scope — I won't just yes you."
|
||||
msgstr "Ask within my scope — I won't just yes you."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Asked {target}: {line}"
|
||||
msgstr "Asked {target}: {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Assembling {name}."
|
||||
msgstr "Assembling {name}."
|
||||
|
||||
#. placeholder {0}: preset.time
|
||||
#: src/pages/RoutineSchedule.tsx
|
||||
msgid "at {0}"
|
||||
@ -788,6 +876,10 @@ msgstr "Autopilot stopped"
|
||||
msgid "Available after the routine is saved"
|
||||
msgstr "Available after the routine is saved"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Aware"
|
||||
msgstr "Aware"
|
||||
|
||||
#: src/components/browser/InAppBrowser.tsx
|
||||
#: src/components/CookingMode.tsx
|
||||
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||
@ -1256,6 +1348,10 @@ msgstr "Code"
|
||||
msgid "Coder"
|
||||
msgstr "Coder"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Collaborating"
|
||||
msgstr "Collaborating"
|
||||
|
||||
#. placeholder {0}: group.title
|
||||
#: src/pages/shell/sidebar-group-header.tsx
|
||||
msgid "Collapse {0}"
|
||||
@ -1412,6 +1508,10 @@ msgstr "Connected app"
|
||||
msgid "Connected. Its tools are available from your next message."
|
||||
msgstr "Connected. Its tools are available from your next message."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Connecting you with {target}."
|
||||
msgstr "Connecting you with {target}."
|
||||
|
||||
#: src/components/integrations/IntegrationSetup.tsx
|
||||
#: src/pages/McpServersOverlay.tsx
|
||||
#: src/pages/memory-providers/BuiltinSettingsForm.tsx
|
||||
@ -1977,6 +2077,14 @@ msgstr "Default"
|
||||
msgid "Default ({0})"
|
||||
msgstr "Default ({0})"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated"
|
||||
msgstr "Delegated"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated to {target}: {line}"
|
||||
msgstr "Delegated to {target}: {line}"
|
||||
|
||||
#: src/components/UserProfileSettings.tsx
|
||||
#: src/pages/BotContextMenu.tsx
|
||||
#: src/pages/KnowledgeSection.tsx
|
||||
@ -2623,6 +2731,22 @@ msgstr "Groups"
|
||||
msgid "Habits"
|
||||
msgstr "Habits"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing off to {name}: {task}"
|
||||
msgstr "Handing off to {name}: {task}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing over to {target}: {line}"
|
||||
msgstr "Handing over to {target}: {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff"
|
||||
msgstr "Handoff"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff to {target}: {line}"
|
||||
msgstr "Handoff to {target}: {line}"
|
||||
|
||||
#: src/components/voice/VoiceStage.tsx
|
||||
msgid "Hang up"
|
||||
msgstr "Hang up"
|
||||
@ -2726,6 +2850,18 @@ msgstr "How should your Oo sound when reading replies aloud?"
|
||||
msgid "How to check"
|
||||
msgstr "How to check"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I asked {target} to {line}"
|
||||
msgstr "I asked {target} to {line}"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm bringing {name} into the room."
|
||||
msgstr "I'm bringing {name} into the room."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm casting {name} on this — {task}"
|
||||
msgstr "I'm casting {name} on this — {task}"
|
||||
|
||||
#: src/pages/shell/computer-screen.tsx
|
||||
msgid "I’m done"
|
||||
msgstr "I’m done"
|
||||
@ -2766,6 +2902,7 @@ msgstr "Import OpenAPI JSON"
|
||||
msgid "In my lane — happy to geek out or just execute, your call."
|
||||
msgstr "In my lane — happy to geek out or just execute, your call."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
#: src/pages/ProjectSessionTimeline.tsx
|
||||
msgid "In progress"
|
||||
msgstr "In progress"
|
||||
@ -3047,6 +3184,10 @@ msgstr "Look variants"
|
||||
msgid "Looking"
|
||||
msgstr "Looking"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Looping in {name}."
|
||||
msgstr "Looping in {name}."
|
||||
|
||||
#: src/pages/ModelSettingsOverlay.tsx
|
||||
#: src/pages/shell/bot-panel.tsx
|
||||
msgid "Low"
|
||||
@ -3748,6 +3889,10 @@ msgstr "Park"
|
||||
msgid "Party hat"
|
||||
msgstr "Party hat"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Passing the baton to {target} — {line}"
|
||||
msgstr "Passing the baton to {target} — {line}"
|
||||
|
||||
#: src/components/AskCard.tsx
|
||||
#: src/pages/AccountSettingsOverlay.tsx
|
||||
#: src/pages/Auth.tsx
|
||||
@ -3789,6 +3934,10 @@ msgstr "Peach"
|
||||
msgid "Peaks too high for {name} (max {max} dBTP)"
|
||||
msgstr "Peaks too high for {name} (max {max} dBTP)"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Peer reply"
|
||||
msgstr "Peer reply"
|
||||
|
||||
#: src/components/OoAtelier.tsx
|
||||
msgid "Penguin"
|
||||
msgstr "Penguin"
|
||||
@ -4273,6 +4422,10 @@ msgstr "Routine"
|
||||
msgid "Routines"
|
||||
msgstr "Routines"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Routing to {name}: {task}"
|
||||
msgstr "Routing to {name}: {task}"
|
||||
|
||||
#: src/pages/RoutineEditor.tsx
|
||||
msgid "Run at"
|
||||
msgstr "Run at"
|
||||
@ -4298,6 +4451,10 @@ msgstr "Running"
|
||||
msgid "Running · Stop"
|
||||
msgstr "Running · Stop"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Running {name} on the numbers — {task}"
|
||||
msgstr "Running {name} on the numbers — {task}"
|
||||
|
||||
#: src/lib/voice-persona-copy.ts
|
||||
msgid "Running the numbers…"
|
||||
msgstr "Running the numbers…"
|
||||
@ -4704,6 +4861,10 @@ msgstr "Species"
|
||||
msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings."
|
||||
msgstr "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Spinning up {name} for {task}"
|
||||
msgstr "Spinning up {name} for {task}"
|
||||
|
||||
#: src/lib/model-tier-label.ts
|
||||
msgid "Standard"
|
||||
msgstr "Standard"
|
||||
@ -4956,6 +5117,10 @@ msgstr "this computer"
|
||||
msgid "This file is not valid UTF-8 Markdown."
|
||||
msgstr "This file is not valid UTF-8 Markdown."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "This goes to {target} now."
|
||||
msgstr "This goes to {target} now."
|
||||
|
||||
#: src/pages/HostComputerPrompt.tsx
|
||||
msgid "this Mac"
|
||||
msgstr "this Mac"
|
||||
@ -5078,6 +5243,10 @@ msgstr "Transcription"
|
||||
msgid "Transcription request timed out."
|
||||
msgstr "Transcription request timed out."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Transferring to {target}."
|
||||
msgstr "Transferring to {target}."
|
||||
|
||||
#: src/pages/ExternalConversationSettings.tsx
|
||||
msgid "Treat like a person"
|
||||
msgstr "Treat like a person"
|
||||
|
||||
@ -242,6 +242,10 @@ msgstr ""
|
||||
msgid "{minutes}m ago"
|
||||
msgstr "hace {minutes} min"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} couldn't resist chiming in."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} failed"
|
||||
@ -252,6 +256,78 @@ msgstr "{name} falló"
|
||||
msgid "{name} finished"
|
||||
msgstr "{name} terminó"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} fired back: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} geeked out: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} has a counter-read."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is crunching — one beat."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is heads-down on this."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is helping with {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is in the work — give me a moment."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is on the data."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is riffing back…"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is taking the keyboard."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is weighing in."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is working on your request."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} joined the thread."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} jumped in with a take."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} on the numbers: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} pushed back — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} replied — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} riffed: {line}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} stopped"
|
||||
@ -274,6 +350,10 @@ msgstr "{size} B"
|
||||
msgid "{steps, plural, one {# step} other {# steps}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{task}"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: runActivityLabel(run.status)
|
||||
#: src/pages/HeuteBrief.tsx
|
||||
msgid "{title}, {0}"
|
||||
@ -721,6 +801,14 @@ msgstr ""
|
||||
msgid "Ask within my scope — I won't just yes you."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Asked {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Assembling {name}."
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: preset.time
|
||||
#: src/pages/RoutineSchedule.tsx
|
||||
msgid "at {0}"
|
||||
@ -788,6 +876,10 @@ msgstr ""
|
||||
msgid "Available after the routine is saved"
|
||||
msgstr "Disponible después de guardar la rutina"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Aware"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/browser/InAppBrowser.tsx
|
||||
#: src/components/CookingMode.tsx
|
||||
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||
@ -1256,6 +1348,10 @@ msgstr "Código"
|
||||
msgid "Coder"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Collaborating"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: group.title
|
||||
#: src/pages/shell/sidebar-group-header.tsx
|
||||
msgid "Collapse {0}"
|
||||
@ -1412,6 +1508,10 @@ msgstr ""
|
||||
msgid "Connected. Its tools are available from your next message."
|
||||
msgstr "Conectado: sus herramientas estarán disponibles desde tu próximo mensaje."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Connecting you with {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/integrations/IntegrationSetup.tsx
|
||||
#: src/pages/McpServersOverlay.tsx
|
||||
#: src/pages/memory-providers/BuiltinSettingsForm.tsx
|
||||
@ -1977,6 +2077,14 @@ msgstr "Predeterminado"
|
||||
msgid "Default ({0})"
|
||||
msgstr "Predeterminado ({0})"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserProfileSettings.tsx
|
||||
#: src/pages/BotContextMenu.tsx
|
||||
#: src/pages/KnowledgeSection.tsx
|
||||
@ -2623,6 +2731,22 @@ msgstr ""
|
||||
msgid "Habits"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing off to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing over to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/voice/VoiceStage.tsx
|
||||
msgid "Hang up"
|
||||
msgstr "Colgar"
|
||||
@ -2726,6 +2850,18 @@ msgstr ""
|
||||
msgid "How to check"
|
||||
msgstr "Cómo comprobar"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I asked {target} to {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm bringing {name} into the room."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm casting {name} on this — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/shell/computer-screen.tsx
|
||||
msgid "I’m done"
|
||||
msgstr "Listo"
|
||||
@ -2766,6 +2902,7 @@ msgstr "Importar JSON de OpenAPI"
|
||||
msgid "In my lane — happy to geek out or just execute, your call."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
#: src/pages/ProjectSessionTimeline.tsx
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
@ -3047,6 +3184,10 @@ msgstr ""
|
||||
msgid "Looking"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Looping in {name}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ModelSettingsOverlay.tsx
|
||||
#: src/pages/shell/bot-panel.tsx
|
||||
msgid "Low"
|
||||
@ -3748,6 +3889,10 @@ msgstr "Aparcar"
|
||||
msgid "Party hat"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Passing the baton to {target} — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/AskCard.tsx
|
||||
#: src/pages/AccountSettingsOverlay.tsx
|
||||
#: src/pages/Auth.tsx
|
||||
@ -3789,6 +3934,10 @@ msgstr ""
|
||||
msgid "Peaks too high for {name} (max {max} dBTP)"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Peer reply"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/OoAtelier.tsx
|
||||
msgid "Penguin"
|
||||
msgstr ""
|
||||
@ -4273,6 +4422,10 @@ msgstr "Rutina"
|
||||
msgid "Routines"
|
||||
msgstr "Rutinas"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Routing to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/RoutineEditor.tsx
|
||||
msgid "Run at"
|
||||
msgstr "Ejecutar a las"
|
||||
@ -4298,6 +4451,10 @@ msgstr "En ejecución"
|
||||
msgid "Running · Stop"
|
||||
msgstr "En ejecución · Detener"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Running {name} on the numbers — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/voice-persona-copy.ts
|
||||
msgid "Running the numbers…"
|
||||
msgstr ""
|
||||
@ -4704,6 +4861,10 @@ msgstr ""
|
||||
msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Spinning up {name} for {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/model-tier-label.ts
|
||||
msgid "Standard"
|
||||
msgstr ""
|
||||
@ -4956,6 +5117,10 @@ msgstr "esta computadora"
|
||||
msgid "This file is not valid UTF-8 Markdown."
|
||||
msgstr "Este archivo no es Markdown UTF-8 válido."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "This goes to {target} now."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/HostComputerPrompt.tsx
|
||||
msgid "this Mac"
|
||||
msgstr "esta Mac"
|
||||
@ -5078,6 +5243,10 @@ msgstr ""
|
||||
msgid "Transcription request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Transferring to {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ExternalConversationSettings.tsx
|
||||
msgid "Treat like a person"
|
||||
msgstr ""
|
||||
|
||||
@ -242,6 +242,10 @@ msgstr ""
|
||||
msgid "{minutes}m ago"
|
||||
msgstr "{minutes} मिनट पहले"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} couldn't resist chiming in."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} failed"
|
||||
@ -252,6 +256,78 @@ msgstr "{name} विफल रहा"
|
||||
msgid "{name} finished"
|
||||
msgstr "{name} पूरा हुआ"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} fired back: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} geeked out: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} has a counter-read."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is crunching — one beat."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is heads-down on this."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is helping with {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is in the work — give me a moment."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is on the data."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is riffing back…"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is taking the keyboard."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is weighing in."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is working on your request."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} joined the thread."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} jumped in with a take."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} on the numbers: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} pushed back — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} replied — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} riffed: {line}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} stopped"
|
||||
@ -274,6 +350,10 @@ msgstr "{size} B"
|
||||
msgid "{steps, plural, one {# step} other {# steps}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{task}"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: runActivityLabel(run.status)
|
||||
#: src/pages/HeuteBrief.tsx
|
||||
msgid "{title}, {0}"
|
||||
@ -721,6 +801,14 @@ msgstr ""
|
||||
msgid "Ask within my scope — I won't just yes you."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Asked {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Assembling {name}."
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: preset.time
|
||||
#: src/pages/RoutineSchedule.tsx
|
||||
msgid "at {0}"
|
||||
@ -788,6 +876,10 @@ msgstr ""
|
||||
msgid "Available after the routine is saved"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Aware"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/browser/InAppBrowser.tsx
|
||||
#: src/components/CookingMode.tsx
|
||||
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||
@ -1256,6 +1348,10 @@ msgstr "कोड"
|
||||
msgid "Coder"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Collaborating"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: group.title
|
||||
#: src/pages/shell/sidebar-group-header.tsx
|
||||
msgid "Collapse {0}"
|
||||
@ -1412,6 +1508,10 @@ msgstr ""
|
||||
msgid "Connected. Its tools are available from your next message."
|
||||
msgstr "कनेक्टेड। इसके टूल आपके अगले संदेश से उपलब्ध हैं।"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Connecting you with {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/integrations/IntegrationSetup.tsx
|
||||
#: src/pages/McpServersOverlay.tsx
|
||||
#: src/pages/memory-providers/BuiltinSettingsForm.tsx
|
||||
@ -1977,6 +2077,14 @@ msgstr "डिफ़ॉल्ट"
|
||||
msgid "Default ({0})"
|
||||
msgstr "डिफ़ॉल्ट ({0})"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserProfileSettings.tsx
|
||||
#: src/pages/BotContextMenu.tsx
|
||||
#: src/pages/KnowledgeSection.tsx
|
||||
@ -2623,6 +2731,22 @@ msgstr ""
|
||||
msgid "Habits"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing off to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing over to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/voice/VoiceStage.tsx
|
||||
msgid "Hang up"
|
||||
msgstr "कॉल समाप्त करें"
|
||||
@ -2726,6 +2850,18 @@ msgstr ""
|
||||
msgid "How to check"
|
||||
msgstr "जांच कैसे करें"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I asked {target} to {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm bringing {name} into the room."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm casting {name} on this — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/shell/computer-screen.tsx
|
||||
msgid "I’m done"
|
||||
msgstr "मेरा काम हो गया"
|
||||
@ -2766,6 +2902,7 @@ msgstr "OpenAPI JSON इम्पोर्ट करें"
|
||||
msgid "In my lane — happy to geek out or just execute, your call."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
#: src/pages/ProjectSessionTimeline.tsx
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
@ -3047,6 +3184,10 @@ msgstr ""
|
||||
msgid "Looking"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Looping in {name}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ModelSettingsOverlay.tsx
|
||||
#: src/pages/shell/bot-panel.tsx
|
||||
msgid "Low"
|
||||
@ -3748,6 +3889,10 @@ msgstr "पार्क करें"
|
||||
msgid "Party hat"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Passing the baton to {target} — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/AskCard.tsx
|
||||
#: src/pages/AccountSettingsOverlay.tsx
|
||||
#: src/pages/Auth.tsx
|
||||
@ -3789,6 +3934,10 @@ msgstr ""
|
||||
msgid "Peaks too high for {name} (max {max} dBTP)"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Peer reply"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/OoAtelier.tsx
|
||||
msgid "Penguin"
|
||||
msgstr ""
|
||||
@ -4273,6 +4422,10 @@ msgstr "रूटीन"
|
||||
msgid "Routines"
|
||||
msgstr "रूटीन्स"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Routing to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/RoutineEditor.tsx
|
||||
msgid "Run at"
|
||||
msgstr ""
|
||||
@ -4298,6 +4451,10 @@ msgstr "चल रहा है"
|
||||
msgid "Running · Stop"
|
||||
msgstr "चल रहा है · रोकें"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Running {name} on the numbers — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/voice-persona-copy.ts
|
||||
msgid "Running the numbers…"
|
||||
msgstr ""
|
||||
@ -4704,6 +4861,10 @@ msgstr ""
|
||||
msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Spinning up {name} for {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/model-tier-label.ts
|
||||
msgid "Standard"
|
||||
msgstr ""
|
||||
@ -4956,6 +5117,10 @@ msgstr "यह कंप्यूटर"
|
||||
msgid "This file is not valid UTF-8 Markdown."
|
||||
msgstr "यह फ़ाइल मान्य UTF-8 Markdown नहीं है।"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "This goes to {target} now."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/HostComputerPrompt.tsx
|
||||
msgid "this Mac"
|
||||
msgstr "यह Mac"
|
||||
@ -5078,6 +5243,10 @@ msgstr ""
|
||||
msgid "Transcription request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Transferring to {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ExternalConversationSettings.tsx
|
||||
msgid "Treat like a person"
|
||||
msgstr ""
|
||||
|
||||
@ -242,6 +242,10 @@ msgstr ""
|
||||
msgid "{minutes}m ago"
|
||||
msgstr "{minutes}분 전"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} couldn't resist chiming in."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} failed"
|
||||
@ -252,6 +256,78 @@ msgstr "{name} 실패"
|
||||
msgid "{name} finished"
|
||||
msgstr "{name} 완료"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} fired back: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} geeked out: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} has a counter-read."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is crunching — one beat."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is heads-down on this."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is helping with {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is in the work — give me a moment."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is on the data."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is riffing back…"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is taking the keyboard."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is weighing in."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is working on your request."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} joined the thread."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} jumped in with a take."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} on the numbers: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} pushed back — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} replied — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} riffed: {line}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} stopped"
|
||||
@ -274,6 +350,10 @@ msgstr "{size} B"
|
||||
msgid "{steps, plural, one {# step} other {# steps}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{task}"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: runActivityLabel(run.status)
|
||||
#: src/pages/HeuteBrief.tsx
|
||||
msgid "{title}, {0}"
|
||||
@ -721,6 +801,14 @@ msgstr ""
|
||||
msgid "Ask within my scope — I won't just yes you."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Asked {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Assembling {name}."
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: preset.time
|
||||
#: src/pages/RoutineSchedule.tsx
|
||||
msgid "at {0}"
|
||||
@ -788,6 +876,10 @@ msgstr ""
|
||||
msgid "Available after the routine is saved"
|
||||
msgstr "루틴을 저장한 후 사용할 수 있습니다"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Aware"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/browser/InAppBrowser.tsx
|
||||
#: src/components/CookingMode.tsx
|
||||
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||
@ -1256,6 +1348,10 @@ msgstr "코드"
|
||||
msgid "Coder"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Collaborating"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: group.title
|
||||
#: src/pages/shell/sidebar-group-header.tsx
|
||||
msgid "Collapse {0}"
|
||||
@ -1412,6 +1508,10 @@ msgstr ""
|
||||
msgid "Connected. Its tools are available from your next message."
|
||||
msgstr "연결되었습니다. 다음 메시지부터 이 도구를 사용할 수 있습니다."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Connecting you with {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/integrations/IntegrationSetup.tsx
|
||||
#: src/pages/McpServersOverlay.tsx
|
||||
#: src/pages/memory-providers/BuiltinSettingsForm.tsx
|
||||
@ -1977,6 +2077,14 @@ msgstr "기본값"
|
||||
msgid "Default ({0})"
|
||||
msgstr "기본값 ({0})"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserProfileSettings.tsx
|
||||
#: src/pages/BotContextMenu.tsx
|
||||
#: src/pages/KnowledgeSection.tsx
|
||||
@ -2623,6 +2731,22 @@ msgstr ""
|
||||
msgid "Habits"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing off to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing over to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/voice/VoiceStage.tsx
|
||||
msgid "Hang up"
|
||||
msgstr "통화 종료"
|
||||
@ -2726,6 +2850,18 @@ msgstr ""
|
||||
msgid "How to check"
|
||||
msgstr "완료 확인 방법"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I asked {target} to {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm bringing {name} into the room."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm casting {name} on this — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/shell/computer-screen.tsx
|
||||
msgid "I’m done"
|
||||
msgstr "조작 끝내기"
|
||||
@ -2766,6 +2902,7 @@ msgstr "OpenAPI JSON 가져오기"
|
||||
msgid "In my lane — happy to geek out or just execute, your call."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
#: src/pages/ProjectSessionTimeline.tsx
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
@ -3047,6 +3184,10 @@ msgstr ""
|
||||
msgid "Looking"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Looping in {name}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ModelSettingsOverlay.tsx
|
||||
#: src/pages/shell/bot-panel.tsx
|
||||
msgid "Low"
|
||||
@ -3748,6 +3889,10 @@ msgstr "보류"
|
||||
msgid "Party hat"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Passing the baton to {target} — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/AskCard.tsx
|
||||
#: src/pages/AccountSettingsOverlay.tsx
|
||||
#: src/pages/Auth.tsx
|
||||
@ -3789,6 +3934,10 @@ msgstr ""
|
||||
msgid "Peaks too high for {name} (max {max} dBTP)"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Peer reply"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/OoAtelier.tsx
|
||||
msgid "Penguin"
|
||||
msgstr ""
|
||||
@ -4273,6 +4422,10 @@ msgstr "자동 실행"
|
||||
msgid "Routines"
|
||||
msgstr "자동 실행"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Routing to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/RoutineEditor.tsx
|
||||
msgid "Run at"
|
||||
msgstr "실행 시간"
|
||||
@ -4298,6 +4451,10 @@ msgstr "실행 중"
|
||||
msgid "Running · Stop"
|
||||
msgstr "실행 중 · 중지"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Running {name} on the numbers — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/voice-persona-copy.ts
|
||||
msgid "Running the numbers…"
|
||||
msgstr ""
|
||||
@ -4704,6 +4861,10 @@ msgstr ""
|
||||
msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Spinning up {name} for {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/model-tier-label.ts
|
||||
msgid "Standard"
|
||||
msgstr ""
|
||||
@ -4956,6 +5117,10 @@ msgstr "이 컴퓨터"
|
||||
msgid "This file is not valid UTF-8 Markdown."
|
||||
msgstr "이 파일은 올바른 UTF-8 Markdown 형식이 아닙니다."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "This goes to {target} now."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/HostComputerPrompt.tsx
|
||||
msgid "this Mac"
|
||||
msgstr "이 Mac"
|
||||
@ -5078,6 +5243,10 @@ msgstr ""
|
||||
msgid "Transcription request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Transferring to {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ExternalConversationSettings.tsx
|
||||
msgid "Treat like a person"
|
||||
msgstr ""
|
||||
|
||||
@ -242,6 +242,10 @@ msgstr ""
|
||||
msgid "{minutes}m ago"
|
||||
msgstr "{minutes}m atrás"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} couldn't resist chiming in."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} failed"
|
||||
@ -252,6 +256,78 @@ msgstr "{name} falhou"
|
||||
msgid "{name} finished"
|
||||
msgstr "{name} terminou"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} fired back: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} geeked out: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} has a counter-read."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is crunching — one beat."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is heads-down on this."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is helping with {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is in the work — give me a moment."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is on the data."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is riffing back…"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is taking the keyboard."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is weighing in."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is working on your request."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} joined the thread."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} jumped in with a take."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} on the numbers: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} pushed back — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} replied — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} riffed: {line}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} stopped"
|
||||
@ -274,6 +350,10 @@ msgstr "{size} B"
|
||||
msgid "{steps, plural, one {# step} other {# steps}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{task}"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: runActivityLabel(run.status)
|
||||
#: src/pages/HeuteBrief.tsx
|
||||
msgid "{title}, {0}"
|
||||
@ -721,6 +801,14 @@ msgstr ""
|
||||
msgid "Ask within my scope — I won't just yes you."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Asked {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Assembling {name}."
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: preset.time
|
||||
#: src/pages/RoutineSchedule.tsx
|
||||
msgid "at {0}"
|
||||
@ -788,6 +876,10 @@ msgstr ""
|
||||
msgid "Available after the routine is saved"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Aware"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/browser/InAppBrowser.tsx
|
||||
#: src/components/CookingMode.tsx
|
||||
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||
@ -1256,6 +1348,10 @@ msgstr "Código"
|
||||
msgid "Coder"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Collaborating"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: group.title
|
||||
#: src/pages/shell/sidebar-group-header.tsx
|
||||
msgid "Collapse {0}"
|
||||
@ -1412,6 +1508,10 @@ msgstr ""
|
||||
msgid "Connected. Its tools are available from your next message."
|
||||
msgstr "Conectado. Suas ferramentas estão disponíveis na sua próxima mensagem."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Connecting you with {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/integrations/IntegrationSetup.tsx
|
||||
#: src/pages/McpServersOverlay.tsx
|
||||
#: src/pages/memory-providers/BuiltinSettingsForm.tsx
|
||||
@ -1977,6 +2077,14 @@ msgstr "Padrão"
|
||||
msgid "Default ({0})"
|
||||
msgstr "Padrão ({0})"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserProfileSettings.tsx
|
||||
#: src/pages/BotContextMenu.tsx
|
||||
#: src/pages/KnowledgeSection.tsx
|
||||
@ -2623,6 +2731,22 @@ msgstr ""
|
||||
msgid "Habits"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing off to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing over to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/voice/VoiceStage.tsx
|
||||
msgid "Hang up"
|
||||
msgstr "Desligar"
|
||||
@ -2726,6 +2850,18 @@ msgstr ""
|
||||
msgid "How to check"
|
||||
msgstr "Como verificar"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I asked {target} to {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm bringing {name} into the room."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm casting {name} on this — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/shell/computer-screen.tsx
|
||||
msgid "I’m done"
|
||||
msgstr "Eu terminei."
|
||||
@ -2766,6 +2902,7 @@ msgstr "Importar OpenAPI JSON"
|
||||
msgid "In my lane — happy to geek out or just execute, your call."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
#: src/pages/ProjectSessionTimeline.tsx
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
@ -3047,6 +3184,10 @@ msgstr ""
|
||||
msgid "Looking"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Looping in {name}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ModelSettingsOverlay.tsx
|
||||
#: src/pages/shell/bot-panel.tsx
|
||||
msgid "Low"
|
||||
@ -3748,6 +3889,10 @@ msgstr "Suspender"
|
||||
msgid "Party hat"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Passing the baton to {target} — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/AskCard.tsx
|
||||
#: src/pages/AccountSettingsOverlay.tsx
|
||||
#: src/pages/Auth.tsx
|
||||
@ -3789,6 +3934,10 @@ msgstr ""
|
||||
msgid "Peaks too high for {name} (max {max} dBTP)"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Peer reply"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/OoAtelier.tsx
|
||||
msgid "Penguin"
|
||||
msgstr ""
|
||||
@ -4273,6 +4422,10 @@ msgstr "Rotina"
|
||||
msgid "Routines"
|
||||
msgstr "Rotinas"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Routing to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/RoutineEditor.tsx
|
||||
msgid "Run at"
|
||||
msgstr ""
|
||||
@ -4298,6 +4451,10 @@ msgstr "Em execução"
|
||||
msgid "Running · Stop"
|
||||
msgstr "Em execução · Parar"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Running {name} on the numbers — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/voice-persona-copy.ts
|
||||
msgid "Running the numbers…"
|
||||
msgstr ""
|
||||
@ -4704,6 +4861,10 @@ msgstr ""
|
||||
msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Spinning up {name} for {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/model-tier-label.ts
|
||||
msgid "Standard"
|
||||
msgstr ""
|
||||
@ -4956,6 +5117,10 @@ msgstr "Este computador"
|
||||
msgid "This file is not valid UTF-8 Markdown."
|
||||
msgstr "Este arquivo não é válido UTF-8 Markdown."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "This goes to {target} now."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/HostComputerPrompt.tsx
|
||||
msgid "this Mac"
|
||||
msgstr "esse Mac"
|
||||
@ -5078,6 +5243,10 @@ msgstr ""
|
||||
msgid "Transcription request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Transferring to {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ExternalConversationSettings.tsx
|
||||
msgid "Treat like a person"
|
||||
msgstr ""
|
||||
|
||||
@ -242,6 +242,10 @@ msgstr ""
|
||||
msgid "{minutes}m ago"
|
||||
msgstr "{minutes} мин назад"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} couldn't resist chiming in."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} failed"
|
||||
@ -252,6 +256,78 @@ msgstr "{name}: ошибка"
|
||||
msgid "{name} finished"
|
||||
msgstr "{name}: завершено"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} fired back: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} geeked out: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} has a counter-read."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is crunching — one beat."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is heads-down on this."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is helping with {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is in the work — give me a moment."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is on the data."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is riffing back…"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is taking the keyboard."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is weighing in."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is working on your request."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} joined the thread."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} jumped in with a take."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} on the numbers: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} pushed back — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} replied — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} riffed: {line}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} stopped"
|
||||
@ -274,6 +350,10 @@ msgstr "{size} Б"
|
||||
msgid "{steps, plural, one {# step} other {# steps}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{task}"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: runActivityLabel(run.status)
|
||||
#: src/pages/HeuteBrief.tsx
|
||||
msgid "{title}, {0}"
|
||||
@ -721,6 +801,14 @@ msgstr "Попросите владельца сервера настроить
|
||||
msgid "Ask within my scope — I won't just yes you."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Asked {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Assembling {name}."
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: preset.time
|
||||
#: src/pages/RoutineSchedule.tsx
|
||||
msgid "at {0}"
|
||||
@ -788,6 +876,10 @@ msgstr ""
|
||||
msgid "Available after the routine is saved"
|
||||
msgstr "Доступно после сохранения задачи"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Aware"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/browser/InAppBrowser.tsx
|
||||
#: src/components/CookingMode.tsx
|
||||
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||
@ -1256,6 +1348,10 @@ msgstr "Код"
|
||||
msgid "Coder"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Collaborating"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: group.title
|
||||
#: src/pages/shell/sidebar-group-header.tsx
|
||||
msgid "Collapse {0}"
|
||||
@ -1412,6 +1508,10 @@ msgstr ""
|
||||
msgid "Connected. Its tools are available from your next message."
|
||||
msgstr "Подключено. Его инструменты доступны из вашего следующего сообщения."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Connecting you with {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/integrations/IntegrationSetup.tsx
|
||||
#: src/pages/McpServersOverlay.tsx
|
||||
#: src/pages/memory-providers/BuiltinSettingsForm.tsx
|
||||
@ -1977,6 +2077,14 @@ msgstr ""
|
||||
msgid "Default ({0})"
|
||||
msgstr "По умолчанию ({0})"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserProfileSettings.tsx
|
||||
#: src/pages/BotContextMenu.tsx
|
||||
#: src/pages/KnowledgeSection.tsx
|
||||
@ -2623,6 +2731,22 @@ msgstr "Группы"
|
||||
msgid "Habits"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing off to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing over to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/voice/VoiceStage.tsx
|
||||
msgid "Hang up"
|
||||
msgstr "Завершить звонок"
|
||||
@ -2726,6 +2850,18 @@ msgstr ""
|
||||
msgid "How to check"
|
||||
msgstr "Как проверить"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I asked {target} to {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm bringing {name} into the room."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm casting {name} on this — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/shell/computer-screen.tsx
|
||||
msgid "I’m done"
|
||||
msgstr "Готово"
|
||||
@ -2766,6 +2902,7 @@ msgstr "Импортировать OpenAPI JSON"
|
||||
msgid "In my lane — happy to geek out or just execute, your call."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
#: src/pages/ProjectSessionTimeline.tsx
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
@ -3047,6 +3184,10 @@ msgstr ""
|
||||
msgid "Looking"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Looping in {name}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ModelSettingsOverlay.tsx
|
||||
#: src/pages/shell/bot-panel.tsx
|
||||
msgid "Low"
|
||||
@ -3748,6 +3889,10 @@ msgstr "Приостановить"
|
||||
msgid "Party hat"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Passing the baton to {target} — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/AskCard.tsx
|
||||
#: src/pages/AccountSettingsOverlay.tsx
|
||||
#: src/pages/Auth.tsx
|
||||
@ -3789,6 +3934,10 @@ msgstr ""
|
||||
msgid "Peaks too high for {name} (max {max} dBTP)"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Peer reply"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/OoAtelier.tsx
|
||||
msgid "Penguin"
|
||||
msgstr ""
|
||||
@ -4273,6 +4422,10 @@ msgstr "Задача"
|
||||
msgid "Routines"
|
||||
msgstr "Задачи"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Routing to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/RoutineEditor.tsx
|
||||
msgid "Run at"
|
||||
msgstr "Запустить в"
|
||||
@ -4298,6 +4451,10 @@ msgstr "Выполняется"
|
||||
msgid "Running · Stop"
|
||||
msgstr "Выполняется · Остановить"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Running {name} on the numbers — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/voice-persona-copy.ts
|
||||
msgid "Running the numbers…"
|
||||
msgstr ""
|
||||
@ -4704,6 +4861,10 @@ msgstr ""
|
||||
msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Spinning up {name} for {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/model-tier-label.ts
|
||||
msgid "Standard"
|
||||
msgstr ""
|
||||
@ -4956,6 +5117,10 @@ msgstr "этот компьютер"
|
||||
msgid "This file is not valid UTF-8 Markdown."
|
||||
msgstr "Этот файл недействителен в формате UTF-8 Markdown."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "This goes to {target} now."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/HostComputerPrompt.tsx
|
||||
msgid "this Mac"
|
||||
msgstr "этот Mac"
|
||||
@ -5078,6 +5243,10 @@ msgstr ""
|
||||
msgid "Transcription request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Transferring to {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ExternalConversationSettings.tsx
|
||||
msgid "Treat like a person"
|
||||
msgstr "Считать человеком"
|
||||
|
||||
@ -242,6 +242,10 @@ msgstr ""
|
||||
msgid "{minutes}m ago"
|
||||
msgstr "{minutes} dk önce"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} couldn't resist chiming in."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} failed"
|
||||
@ -252,6 +256,78 @@ msgstr "{name} başarısız oldu"
|
||||
msgid "{name} finished"
|
||||
msgstr "{name} çalışmasını tamamladı"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} fired back: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} geeked out: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} has a counter-read."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is crunching — one beat."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is heads-down on this."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is helping with {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is in the work — give me a moment."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is on the data."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is riffing back…"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is taking the keyboard."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is weighing in."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is working on your request."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} joined the thread."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} jumped in with a take."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} on the numbers: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} pushed back — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} replied — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} riffed: {line}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} stopped"
|
||||
@ -274,6 +350,10 @@ msgstr "{size} B"
|
||||
msgid "{steps, plural, one {# step} other {# steps}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{task}"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: runActivityLabel(run.status)
|
||||
#: src/pages/HeuteBrief.tsx
|
||||
msgid "{title}, {0}"
|
||||
@ -721,6 +801,14 @@ msgstr ""
|
||||
msgid "Ask within my scope — I won't just yes you."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Asked {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Assembling {name}."
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: preset.time
|
||||
#: src/pages/RoutineSchedule.tsx
|
||||
msgid "at {0}"
|
||||
@ -788,6 +876,10 @@ msgstr ""
|
||||
msgid "Available after the routine is saved"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Aware"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/browser/InAppBrowser.tsx
|
||||
#: src/components/CookingMode.tsx
|
||||
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||
@ -1256,6 +1348,10 @@ msgstr "Kod"
|
||||
msgid "Coder"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Collaborating"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: group.title
|
||||
#: src/pages/shell/sidebar-group-header.tsx
|
||||
msgid "Collapse {0}"
|
||||
@ -1412,6 +1508,10 @@ msgstr ""
|
||||
msgid "Connected. Its tools are available from your next message."
|
||||
msgstr "Bağlandı. Araçları bir sonraki mesajından itibaren kullanılabilir."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Connecting you with {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/integrations/IntegrationSetup.tsx
|
||||
#: src/pages/McpServersOverlay.tsx
|
||||
#: src/pages/memory-providers/BuiltinSettingsForm.tsx
|
||||
@ -1977,6 +2077,14 @@ msgstr "Varsayılan"
|
||||
msgid "Default ({0})"
|
||||
msgstr "Varsayılan ({0})"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserProfileSettings.tsx
|
||||
#: src/pages/BotContextMenu.tsx
|
||||
#: src/pages/KnowledgeSection.tsx
|
||||
@ -2623,6 +2731,22 @@ msgstr ""
|
||||
msgid "Habits"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing off to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing over to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/voice/VoiceStage.tsx
|
||||
msgid "Hang up"
|
||||
msgstr "Aramayı sonlandır"
|
||||
@ -2726,6 +2850,18 @@ msgstr ""
|
||||
msgid "How to check"
|
||||
msgstr "Nasıl kontrol edilir"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I asked {target} to {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm bringing {name} into the room."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm casting {name} on this — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/shell/computer-screen.tsx
|
||||
msgid "I’m done"
|
||||
msgstr "Bitirdim"
|
||||
@ -2766,6 +2902,7 @@ msgstr "OpenAPI JSON içe aktar"
|
||||
msgid "In my lane — happy to geek out or just execute, your call."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
#: src/pages/ProjectSessionTimeline.tsx
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
@ -3047,6 +3184,10 @@ msgstr ""
|
||||
msgid "Looking"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Looping in {name}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ModelSettingsOverlay.tsx
|
||||
#: src/pages/shell/bot-panel.tsx
|
||||
msgid "Low"
|
||||
@ -3748,6 +3889,10 @@ msgstr "Beklet"
|
||||
msgid "Party hat"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Passing the baton to {target} — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/AskCard.tsx
|
||||
#: src/pages/AccountSettingsOverlay.tsx
|
||||
#: src/pages/Auth.tsx
|
||||
@ -3789,6 +3934,10 @@ msgstr ""
|
||||
msgid "Peaks too high for {name} (max {max} dBTP)"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Peer reply"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/OoAtelier.tsx
|
||||
msgid "Penguin"
|
||||
msgstr ""
|
||||
@ -4273,6 +4422,10 @@ msgstr "Rutin"
|
||||
msgid "Routines"
|
||||
msgstr "Rutinler"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Routing to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/RoutineEditor.tsx
|
||||
msgid "Run at"
|
||||
msgstr ""
|
||||
@ -4298,6 +4451,10 @@ msgstr "Çalışıyor"
|
||||
msgid "Running · Stop"
|
||||
msgstr "Çalışıyor · Durdur"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Running {name} on the numbers — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/voice-persona-copy.ts
|
||||
msgid "Running the numbers…"
|
||||
msgstr ""
|
||||
@ -4704,6 +4861,10 @@ msgstr ""
|
||||
msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Spinning up {name} for {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/model-tier-label.ts
|
||||
msgid "Standard"
|
||||
msgstr ""
|
||||
@ -4956,6 +5117,10 @@ msgstr "bu bilgisayar"
|
||||
msgid "This file is not valid UTF-8 Markdown."
|
||||
msgstr "Bu dosya geçerli bir UTF-8 Markdown değil."
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "This goes to {target} now."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/HostComputerPrompt.tsx
|
||||
msgid "this Mac"
|
||||
msgstr "bu Mac"
|
||||
@ -5078,6 +5243,10 @@ msgstr ""
|
||||
msgid "Transcription request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Transferring to {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ExternalConversationSettings.tsx
|
||||
msgid "Treat like a person"
|
||||
msgstr ""
|
||||
|
||||
@ -242,6 +242,10 @@ msgstr ""
|
||||
msgid "{minutes}m ago"
|
||||
msgstr "{minutes} 分钟前"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} couldn't resist chiming in."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} failed"
|
||||
@ -252,6 +256,78 @@ msgstr "{name} 失败"
|
||||
msgid "{name} finished"
|
||||
msgstr "{name} 已完成"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} fired back: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} geeked out: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} has a counter-read."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is crunching — one beat."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is heads-down on this."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is helping with {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is in the work — give me a moment."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is on the data."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is riffing back…"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is taking the keyboard."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is weighing in."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} is working on your request."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} joined the thread."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} jumped in with a take."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} on the numbers: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} pushed back — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} replied — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{name} riffed: {line}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/lib/browser-notifications.ts
|
||||
msgid "{name} stopped"
|
||||
@ -274,6 +350,10 @@ msgstr "{size} B"
|
||||
msgid "{steps, plural, one {# step} other {# steps}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "{task}"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: runActivityLabel(run.status)
|
||||
#: src/pages/HeuteBrief.tsx
|
||||
msgid "{title}, {0}"
|
||||
@ -721,6 +801,14 @@ msgstr ""
|
||||
msgid "Ask within my scope — I won't just yes you."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Asked {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Assembling {name}."
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: preset.time
|
||||
#: src/pages/RoutineSchedule.tsx
|
||||
msgid "at {0}"
|
||||
@ -788,6 +876,10 @@ msgstr ""
|
||||
msgid "Available after the routine is saved"
|
||||
msgstr "保存例行任务后可用"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Aware"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/browser/InAppBrowser.tsx
|
||||
#: src/components/CookingMode.tsx
|
||||
#: src/components/onboarding/OnboardingNameStep.tsx
|
||||
@ -1256,6 +1348,10 @@ msgstr "代码"
|
||||
msgid "Coder"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Collaborating"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: group.title
|
||||
#: src/pages/shell/sidebar-group-header.tsx
|
||||
msgid "Collapse {0}"
|
||||
@ -1412,6 +1508,10 @@ msgstr ""
|
||||
msgid "Connected. Its tools are available from your next message."
|
||||
msgstr "已连接,下一条消息起即可使用其工具。"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Connecting you with {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/integrations/IntegrationSetup.tsx
|
||||
#: src/pages/McpServersOverlay.tsx
|
||||
#: src/pages/memory-providers/BuiltinSettingsForm.tsx
|
||||
@ -1977,6 +2077,14 @@ msgstr "默认"
|
||||
msgid "Default ({0})"
|
||||
msgstr "默认({0})"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Delegated to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserProfileSettings.tsx
|
||||
#: src/pages/BotContextMenu.tsx
|
||||
#: src/pages/KnowledgeSection.tsx
|
||||
@ -2623,6 +2731,22 @@ msgstr ""
|
||||
msgid "Habits"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing off to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handing over to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Handoff to {target}: {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/voice/VoiceStage.tsx
|
||||
msgid "Hang up"
|
||||
msgstr "挂断"
|
||||
@ -2726,6 +2850,18 @@ msgstr ""
|
||||
msgid "How to check"
|
||||
msgstr "如何检查"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I asked {target} to {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm bringing {name} into the room."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "I'm casting {name} on this — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/shell/computer-screen.tsx
|
||||
msgid "I’m done"
|
||||
msgstr "我做完了"
|
||||
@ -2766,6 +2902,7 @@ msgstr "导入 OpenAPI JSON"
|
||||
msgid "In my lane — happy to geek out or just execute, your call."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
#: src/pages/ProjectSessionTimeline.tsx
|
||||
msgid "In progress"
|
||||
msgstr ""
|
||||
@ -3047,6 +3184,10 @@ msgstr ""
|
||||
msgid "Looking"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Looping in {name}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ModelSettingsOverlay.tsx
|
||||
#: src/pages/shell/bot-panel.tsx
|
||||
msgid "Low"
|
||||
@ -3748,6 +3889,10 @@ msgstr "搁置"
|
||||
msgid "Party hat"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Passing the baton to {target} — {line}"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/AskCard.tsx
|
||||
#: src/pages/AccountSettingsOverlay.tsx
|
||||
#: src/pages/Auth.tsx
|
||||
@ -3789,6 +3934,10 @@ msgstr ""
|
||||
msgid "Peaks too high for {name} (max {max} dBTP)"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Peer reply"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/OoAtelier.tsx
|
||||
msgid "Penguin"
|
||||
msgstr ""
|
||||
@ -4273,6 +4422,10 @@ msgstr "例行任务"
|
||||
msgid "Routines"
|
||||
msgstr "例行任务"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Routing to {name}: {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/RoutineEditor.tsx
|
||||
msgid "Run at"
|
||||
msgstr "运行时间"
|
||||
@ -4298,6 +4451,10 @@ msgstr "运行中"
|
||||
msgid "Running · Stop"
|
||||
msgstr "运行中 · 停止"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Running {name} on the numbers — {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/voice-persona-copy.ts
|
||||
msgid "Running the numbers…"
|
||||
msgstr ""
|
||||
@ -4704,6 +4861,10 @@ msgstr ""
|
||||
msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Spinning up {name} for {task}"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/model-tier-label.ts
|
||||
msgid "Standard"
|
||||
msgstr ""
|
||||
@ -4956,6 +5117,10 @@ msgstr "这台电脑"
|
||||
msgid "This file is not valid UTF-8 Markdown."
|
||||
msgstr "此文件不是有效的 UTF-8 Markdown。"
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "This goes to {target} now."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/HostComputerPrompt.tsx
|
||||
msgid "this Mac"
|
||||
msgstr "这台 Mac"
|
||||
@ -5078,6 +5243,10 @@ msgstr ""
|
||||
msgid "Transcription request timed out."
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/awareness-persona-copy.ts
|
||||
msgid "Transferring to {target}."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/ExternalConversationSettings.tsx
|
||||
msgid "Treat like a person"
|
||||
msgstr ""
|
||||
|
||||
@ -7,10 +7,10 @@ import {
|
||||
VoiceStage,
|
||||
type VoiceTranscriptLine,
|
||||
} from "../components/voice/VoiceStage";
|
||||
import { narrateAwarenessDescriptor } from "../lib/awareness-persona-copy";
|
||||
import {
|
||||
cadencePulseMs,
|
||||
extractVoiceAwareness,
|
||||
narrateAwarenessForPersona,
|
||||
resolveBotPersona,
|
||||
} from "../lib/bot-persona";
|
||||
import {
|
||||
@ -137,8 +137,9 @@ export function CallView({
|
||||
[snapshot, botName],
|
||||
);
|
||||
const awarenessLabel = useMemo(
|
||||
() => (awarenessEvent ? narrateAwarenessForPersona(persona, awarenessEvent) : null),
|
||||
[awarenessEvent, persona],
|
||||
() =>
|
||||
awarenessEvent ? t(narrateAwarenessDescriptor(persona.archetype, awarenessEvent)) : null,
|
||||
[awarenessEvent, persona.archetype, t],
|
||||
);
|
||||
|
||||
function setCallPhase(next: VoicePhase) {
|
||||
|
||||
@ -158,12 +158,14 @@ Severity: **P0** ship blocker · **P1** major UX/trust · **P2** polish · **P3*
|
||||
|
||||
| ID | Sev | Issue | Fix |
|
||||
|----|-----|-------|-----|
|
||||
| R3-git | P0 | Computer image missing git/gh/ssh/curl/sudo | `infra/sandboxes/computer/Dockerfile` + `computer-image-tools.test.sh` |
|
||||
| R3-git | P0 | Computer image missing git/gh/ssh/curl | `infra/sandboxes/computer/Dockerfile` + `computer-image-tools.test.sh` in validate CI |
|
||||
| R3-mount | P0 | `Space.workspacePath` not mounted in Oo | Bind at `/home/rakazo/project` for deployment owner project spaces |
|
||||
| R3-path | P0 | `/home/rakazo/...` file paths doubled | `stripVirtualWorkspaceRoot` + tests |
|
||||
| R3-branch | P0 | Default branch from checkout not remote | `readGitCheckout` uses `origin/HEAD` |
|
||||
| R3-push | P0 | Unrestricted git push / force-push | `git-shell-policy.ts` + approval for first push/`gh pr` |
|
||||
| R3-token | P0 | No owner GitHub credential path | Encrypted `ownerGithubTokenCipher` + `deployment.update` (write-only) |
|
||||
| R3-push | P0 | Unrestricted git push / force-push | Shell policy + **supervisor GitHub broker** (host-side git/gh with enforced policy) |
|
||||
| R3-token | P0 | GitHub PAT reachable inside computer env | Ciphertext in `deployment_settings`; PAT stays on supervisor/API broker only |
|
||||
| R3-sudo | P0 | NOPASSWD sudo in computer image | Removed; dev tools preinstalled in image |
|
||||
| R3-own | P0 | Project bind mount root-owned files | Container runs as host uid/gid + ownership integration test |
|
||||
| R3-runaway | P1 | Runaway tool loops | Default 48 tool fuse → `need-user` ask; 15m stall ask |
|
||||
| R3-ux | P1 | Narration flood / stream fragments | Activity line + streaming text merge in `progressMessageText` |
|
||||
| R3-i18n | P2 | English run chrome in DE | Lingui for awareness strip + working narration |
|
||||
@ -172,7 +174,7 @@ Severity: **P0** ship blocker · **P1** major UX/trust · **P2** polish · **P3*
|
||||
|
||||
**Evo ops:** Rebuild `rakazo/computer:local` on Evo after merge (`pnpm sandbox:build` or compose `computer-image` target). Server deploy via `ops/evo-deploy/deploy.sh` updates app images; the computer image is separate—rebuild and restart sandboxes so new containers pick up git tools.
|
||||
|
||||
**Security (GitHub token):** Token is stored only as ciphertext in `deployment_settings.ownerGithubTokenCipher`, set by the deployment owner through `deployment.update`. It is injected as `GITHUB_TOKEN`/`GH_TOKEN` only for the owner’s runs in non-default (project) spaces—not baked into the image or repo. Push/PR policy is enforced in the shell tool layer regardless of token presence.
|
||||
**Security (GitHub broker):** The deployment owner stores a PAT as ciphertext (`ownerGithubTokenCipher` via `deployment.update`). Project computers never receive `GITHUB_TOKEN`/`GH_TOKEN`. Instead they get a run-scoped broker URL + opaque run token; `/usr/local/lib/rakazo/bin/git` and `gh` forward network operations to the sandbox supervisor (`/internal/github-broker`), which holds the PAT and runs git/gh on the host workspace path. The broker enforces: allowlisted remote only, no default-branch push, no force-push, and the same per-run first push/`gh pr` approval flag as the shell tool. Configure `SANDBOX_GITHUB_BROKER_URL` (reachable from bot containers) and register sessions when runs start.
|
||||
|
||||
### Remaining — Phase 3+
|
||||
|
||||
|
||||
@ -33,7 +33,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gh \
|
||||
openssh-client \
|
||||
curl \
|
||||
sudo \
|
||||
chromium \
|
||||
fonts-liberation \
|
||||
fonts-dejavu-core \
|
||||
@ -42,10 +41,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& groupadd --gid 1000 rakazo \
|
||||
&& useradd --uid 1000 --gid rakazo --create-home --shell /bin/bash rakazo \
|
||||
&& usermod -aG sudo rakazo \
|
||||
&& printf '%s\n' 'rakazo ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/rakazo \
|
||||
&& chmod 440 /etc/sudoers.d/rakazo \
|
||||
&& mkdir -p /etc/rakazo/fluxbox \
|
||||
&& mkdir -p /etc/rakazo/fluxbox /usr/local/lib/rakazo/bin \
|
||||
&& printf '%s\n' '#!/bin/sh' 'exec xsetroot -solid "#111113" "$@"' > /usr/bin/fbsetbg \
|
||||
&& chmod +x /usr/bin/fbsetbg \
|
||||
&& ln -sfn /bin/true /usr/bin/xmessage
|
||||
@ -54,6 +50,9 @@ ENV HOME=/home/rakazo
|
||||
WORKDIR /home/rakazo
|
||||
# --chmod normalizes modes: a restrictive host umask (e.g. 077 on hardened hosts) would
|
||||
# otherwise leave these files unreadable to USER 1000 at runtime.
|
||||
COPY --chmod=755 bin/rakazo-git bin/rakazo-gh /usr/local/lib/rakazo/bin/
|
||||
RUN ln -sf /usr/local/lib/rakazo/bin/rakazo-git /usr/local/lib/rakazo/bin/git \
|
||||
&& ln -sf /usr/local/lib/rakazo/bin/rakazo-gh /usr/local/lib/rakazo/bin/gh
|
||||
COPY --chmod=755 start.sh /usr/local/bin/rakazo-computer
|
||||
COPY --chmod=755 control.py /usr/local/bin/rakazo-computer-control
|
||||
COPY --from=capture-builder /build/librakazo-xcapture.so /usr/local/lib/librakazo-xcapture.so
|
||||
|
||||
18
infra/sandboxes/computer/bin/rakazo-gh
Normal file
18
infra/sandboxes/computer/bin/rakazo-gh
Normal file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
REAL_GH=/usr/bin/gh
|
||||
if [[ "${RAKAZO_GITHUB_BROKER_URL:-}" == "" || "${RAKAZO_GITHUB_BROKER_RUN_TOKEN:-}" == "" ]]; then
|
||||
exec "$REAL_GH" "$@"
|
||||
fi
|
||||
joined="$*"
|
||||
if [[ ! "$joined" =~ (^|[[:space:]])pr([[:space:]]|$) ]]; then
|
||||
exec "$REAL_GH" "$@"
|
||||
fi
|
||||
payload="$(python3 -c 'import json,sys; print(json.dumps({"argv":["gh"]+sys.argv[1:]}))' -- "$@")"
|
||||
response="$(curl -fsS -X POST "${RAKAZO_GITHUB_BROKER_URL}/v1/gh" \
|
||||
-H "Authorization: Bearer ${RAKAZO_GITHUB_BROKER_RUN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary "$payload")"
|
||||
code="$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("code",1))' <<<"$response")"
|
||||
python3 -c 'import json,sys; b=json.load(sys.stdin); sys.stdout.write(b.get("stdout") or ""); sys.stderr.write(b.get("stderr") or "")' <<<"$response"
|
||||
exit "$code"
|
||||
20
infra/sandboxes/computer/bin/rakazo-git
Normal file
20
infra/sandboxes/computer/bin/rakazo-git
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
REAL_GIT=/usr/bin/git
|
||||
if [[ "${RAKAZO_GITHUB_BROKER_URL:-}" == "" || "${RAKAZO_GITHUB_BROKER_RUN_TOKEN:-}" == "" ]]; then
|
||||
exec "$REAL_GIT" "$@"
|
||||
fi
|
||||
sub="${1:-}"
|
||||
case "$sub" in
|
||||
push|fetch|pull|clone|ls-remote)
|
||||
payload="$(python3 -c 'import json,sys; print(json.dumps({"argv":["git"]+sys.argv[1:]}))' -- "$@")"
|
||||
response="$(curl -fsS -X POST "${RAKAZO_GITHUB_BROKER_URL}/v1/git" \
|
||||
-H "Authorization: Bearer ${RAKAZO_GITHUB_BROKER_RUN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary "$payload")"
|
||||
code="$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("code",1))' <<<"$response")"
|
||||
python3 -c 'import json,sys; b=json.load(sys.stdin); sys.stdout.write(b.get("stdout") or ""); sys.stderr.write(b.get("stderr") or "")' <<<"$response"
|
||||
exit "$code"
|
||||
;;
|
||||
esac
|
||||
exec "$REAL_GIT" "$@"
|
||||
@ -3,9 +3,10 @@ set -euo pipefail
|
||||
|
||||
IMAGE="${RAKAZO_COMPUTER_IMAGE:-rakazo/computer:local}"
|
||||
|
||||
for tool in git gh ssh curl sudo; do
|
||||
for tool in git gh ssh curl; do
|
||||
docker run --rm "$IMAGE" bash -lc "command -v $tool >/dev/null"
|
||||
done
|
||||
|
||||
docker run --rm "$IMAGE" bash -lc 'git --version | grep -q "^git version "'
|
||||
docker run --rm "$IMAGE" bash -lc 'gh --version | grep -q "^gh version "'
|
||||
docker run --rm "$IMAGE" bash -lc 'test "$(command -v git)" = "/usr/local/lib/rakazo/bin/git"'
|
||||
|
||||
@ -102,7 +102,7 @@ describe("graphical computer spec", () => {
|
||||
expect(JSON.stringify(options)).not.toMatch(/sleep/);
|
||||
expect(options.HostConfig.Binds).toEqual(["/var/rakazo/homes/abc:/home/rakazo"]);
|
||||
expect(options.Env).toContain(
|
||||
"PATH=/home/rakazo/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"PATH=/usr/local/lib/rakazo/bin:/home/rakazo/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
);
|
||||
expect(options.Env).toContain("NPM_CONFIG_PREFIX=/home/rakazo/.local");
|
||||
expect(options.Env?.join("\n")).not.toMatch(/AXIOM_|LOG_LEVEL|LOG_FORMAT/);
|
||||
|
||||
@ -8,6 +8,8 @@ export const COMPUTER_IMAGE = process.env.RAKAZO_COMPUTER_IMAGE ?? "rakazo/compu
|
||||
export const COMPUTER_UID = 1000;
|
||||
export const COMPUTER_GID = 1000;
|
||||
export const COMPUTER_USER = `${COMPUTER_UID}:${COMPUTER_GID}`;
|
||||
export const COMPUTER_PATH =
|
||||
"/usr/local/lib/rakazo/bin:/home/rakazo/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
||||
|
||||
export { screenPorts };
|
||||
export const COMPUTER_CONTROL_PORT = 7070;
|
||||
@ -293,7 +295,10 @@ export function containerCreateOptions(input: ComputerCreateInput) {
|
||||
Env: [
|
||||
"DISPLAY=:1",
|
||||
"HOME=/home/rakazo",
|
||||
"PATH=/home/rakazo/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
`PATH=${COMPUTER_PATH}`,
|
||||
...(input.projectWorkspacePath && process.env.SANDBOX_GITHUB_BROKER_URL
|
||||
? [`RAKAZO_GITHUB_BROKER_URL=${process.env.SANDBOX_GITHUB_BROKER_URL.replace(/\/$/, "")}`]
|
||||
: []),
|
||||
"NPM_CONFIG_PREFIX=/home/rakazo/.local",
|
||||
"PIP_USER=1",
|
||||
...(input.controlToken ? [`RAKAZO_COMPUTER_CONTROL_TOKEN=${input.controlToken}`] : []),
|
||||
|
||||
22
infra/sandboxes/supervisor/src/github-broker.test.ts
Normal file
22
infra/sandboxes/supervisor/src/github-broker.test.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { signGithubBrokerRunToken } from "@rakazo/core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { githubBrokerSessionsForTests } from "./github-broker.js";
|
||||
import { hasValidBearerToken } from "./supervisor-logic.js";
|
||||
|
||||
describe("github broker sessions", () => {
|
||||
it("stores sessions for supervisor-authenticated registration", () => {
|
||||
const token = "supervisor-test-token";
|
||||
expect(hasValidBearerToken(`Bearer ${token}`, token)).toBe(true);
|
||||
githubBrokerSessionsForTests().set("run-a", {
|
||||
runId: "run-a",
|
||||
workspacePath: "/tmp/project",
|
||||
allowedRemoteUrl: "https://github.com/example/demo.git",
|
||||
defaultBranch: "main",
|
||||
githubToken: "ghp_test",
|
||||
pushApproved: false,
|
||||
});
|
||||
const runToken = signGithubBrokerRunToken("run-a", token);
|
||||
expect(runToken.startsWith("rgb1.")).toBe(true);
|
||||
githubBrokerSessionsForTests().delete("run-a");
|
||||
});
|
||||
});
|
||||
197
infra/sandboxes/supervisor/src/github-broker.ts
Normal file
197
infra/sandboxes/supervisor/src/github-broker.ts
Normal file
@ -0,0 +1,197 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import {
|
||||
evaluateGitShellPolicy,
|
||||
ghBrokerArgv,
|
||||
gitNetworkArgv,
|
||||
gitRemoteMatchesAllowlist,
|
||||
resolveSupervisorToken,
|
||||
verifyGithubBrokerRunToken,
|
||||
} from "@rakazo/core";
|
||||
import type { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { hasValidBearerToken } from "./supervisor-logic.js";
|
||||
|
||||
export type GithubBrokerSession = {
|
||||
runId: string;
|
||||
workspacePath: string;
|
||||
allowedRemoteUrl: string | null;
|
||||
defaultBranch: string | null;
|
||||
githubToken: string;
|
||||
pushApproved: boolean;
|
||||
};
|
||||
|
||||
const sessions = new Map<string, GithubBrokerSession>();
|
||||
|
||||
export function githubBrokerSessionsForTests() {
|
||||
return sessions;
|
||||
}
|
||||
|
||||
function brokerSecret(): string {
|
||||
return resolveSupervisorToken(process.env);
|
||||
}
|
||||
|
||||
function runIdFromRunToken(authorization: string | undefined): string | null {
|
||||
if (!authorization?.startsWith("Bearer ")) return null;
|
||||
const token = authorization.slice("Bearer ".length).trim();
|
||||
for (const runId of sessions.keys()) {
|
||||
if (verifyGithubBrokerRunToken(token, brokerSecret(), runId)) return runId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function sessionForRequest(authorization: string | undefined): GithubBrokerSession | null {
|
||||
const runId = runIdFromRunToken(authorization);
|
||||
if (!runId) return null;
|
||||
return sessions.get(runId) ?? null;
|
||||
}
|
||||
|
||||
async function runHostGit(
|
||||
session: GithubBrokerSession,
|
||||
argv: string[],
|
||||
): Promise<{ stdout: string; stderr: string; code: number }> {
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(argv[0]!, argv.slice(1), {
|
||||
cwd: session.workspacePath,
|
||||
env: {
|
||||
...process.env,
|
||||
HOME: session.workspacePath,
|
||||
GIT_TERMINAL_PROMPT: "0",
|
||||
GH_TOKEN: session.githubToken,
|
||||
GITHUB_TOKEN: session.githubToken,
|
||||
},
|
||||
});
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
child.stdout?.on("data", (chunk: Buffer) => {
|
||||
stdout += chunk.toString("utf8");
|
||||
});
|
||||
child.stderr?.on("data", (chunk: Buffer) => {
|
||||
stderr += chunk.toString("utf8");
|
||||
});
|
||||
child.on("close", (code) => resolve({ stdout, stderr, code: code ?? 1 }));
|
||||
child.on("error", (error) => resolve({ stdout: "", stderr: error.message, code: 1 }));
|
||||
});
|
||||
}
|
||||
|
||||
async function readOriginRemote(session: GithubBrokerSession): Promise<string | null> {
|
||||
const result = await runHostGit(session, ["/usr/bin/git", "remote", "get-url", "origin"]);
|
||||
if (result.code !== 0) return null;
|
||||
return result.stdout.trim() || null;
|
||||
}
|
||||
|
||||
async function assertRemoteAllowed(session: GithubBrokerSession, argv: string[]) {
|
||||
if (!session.allowedRemoteUrl) {
|
||||
throw new Error("This space has no allowlisted git remote.");
|
||||
}
|
||||
const remoteArg = argv.find((part, index) => index > 1 && !part.startsWith("-"));
|
||||
const candidate = remoteArg ?? (await readOriginRemote(session));
|
||||
if (candidate && !gitRemoteMatchesAllowlist(candidate, session.allowedRemoteUrl)) {
|
||||
throw new Error("Push and fetch are limited to this space's linked repository remote.");
|
||||
}
|
||||
}
|
||||
|
||||
export function mountGithubBrokerRoutes(app: Hono, supervisorToken: string) {
|
||||
app.use("/internal/github-broker/*", async (c, next) => {
|
||||
const pathname = c.req.path;
|
||||
if (pathname.startsWith("/internal/github-broker/sessions")) {
|
||||
if (!hasValidBearerToken(c.req.header("authorization"), supervisorToken)) {
|
||||
return c.json({ error: "unauthorized" }, 401);
|
||||
}
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
if (!sessionForRequest(c.req.header("authorization"))) {
|
||||
return c.json({ error: "unauthorized" }, 401);
|
||||
}
|
||||
await next();
|
||||
});
|
||||
|
||||
app.post("/internal/github-broker/sessions", async (c) => {
|
||||
const body = z
|
||||
.object({
|
||||
runId: z.string().min(1),
|
||||
workspacePath: z.string().min(1),
|
||||
allowedRemoteUrl: z.string().nullable(),
|
||||
defaultBranch: z.string().nullable(),
|
||||
githubToken: z.string().min(1),
|
||||
pushApproved: z.boolean().optional(),
|
||||
})
|
||||
.parse(await c.req.json());
|
||||
const workspacePath = path.resolve(body.workspacePath);
|
||||
sessions.set(body.runId, {
|
||||
runId: body.runId,
|
||||
workspacePath,
|
||||
allowedRemoteUrl: body.allowedRemoteUrl,
|
||||
defaultBranch: body.defaultBranch,
|
||||
githubToken: body.githubToken,
|
||||
pushApproved: body.pushApproved ?? false,
|
||||
});
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
app.patch("/internal/github-broker/sessions/:runId", async (c) => {
|
||||
const runId = c.req.param("runId");
|
||||
const body = z.object({ pushApproved: z.boolean() }).parse(await c.req.json());
|
||||
const session = sessions.get(runId);
|
||||
if (!session) return c.json({ error: "not found" }, 404);
|
||||
session.pushApproved = body.pushApproved;
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
app.delete("/internal/github-broker/sessions/:runId", async (c) => {
|
||||
sessions.delete(c.req.param("runId"));
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
app.post("/internal/github-broker/v1/git", async (c) => {
|
||||
const session = sessionForRequest(c.req.header("authorization"));
|
||||
if (!session) return c.json({ error: "unauthorized" }, 401);
|
||||
const body = z.object({ argv: z.array(z.string()).min(1) }).parse(await c.req.json());
|
||||
const argv = body.argv[0] === "git" ? body.argv : ["git", ...body.argv];
|
||||
if (!gitNetworkArgv(argv)) {
|
||||
return c.json({ error: "unsupported git invocation" }, 400);
|
||||
}
|
||||
const command = argv.join(" ");
|
||||
const policy = evaluateGitShellPolicy({
|
||||
command,
|
||||
defaultBranch: session.defaultBranch,
|
||||
gitPushApprovedThisRun: session.pushApproved,
|
||||
});
|
||||
if (policy.action === "block") {
|
||||
return c.json({ stdout: "", stderr: policy.reason, code: 1 });
|
||||
}
|
||||
if (policy.action === "require_approval") {
|
||||
return c.json({ stdout: "", stderr: policy.reason, code: 1 });
|
||||
}
|
||||
try {
|
||||
await assertRemoteAllowed(session, argv);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return c.json({ stdout: "", stderr: message, code: 1 });
|
||||
}
|
||||
const result = await runHostGit(session, ["/usr/bin/git", ...argv.slice(1)]);
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
app.post("/internal/github-broker/v1/gh", async (c) => {
|
||||
const session = sessionForRequest(c.req.header("authorization"));
|
||||
if (!session) return c.json({ error: "unauthorized" }, 401);
|
||||
const body = z.object({ argv: z.array(z.string()).min(1) }).parse(await c.req.json());
|
||||
const argv = body.argv[0] === "gh" ? body.argv : ["gh", ...body.argv];
|
||||
if (!ghBrokerArgv(argv)) {
|
||||
return c.json({ error: "unsupported gh invocation" }, 400);
|
||||
}
|
||||
const policy = evaluateGitShellPolicy({
|
||||
command: argv.join(" "),
|
||||
defaultBranch: session.defaultBranch,
|
||||
gitPushApprovedThisRun: session.pushApproved,
|
||||
});
|
||||
if (policy.action === "block" || policy.action === "require_approval") {
|
||||
const reason = policy.reason;
|
||||
return c.json({ stdout: "", stderr: reason, code: 1 });
|
||||
}
|
||||
const result = await runHostGit(session, ["/usr/bin/gh", ...argv.slice(1)]);
|
||||
return c.json(result);
|
||||
});
|
||||
}
|
||||
@ -23,6 +23,7 @@ import {
|
||||
assertVolumeSubpathSupport,
|
||||
COMPUTER_GID,
|
||||
COMPUTER_IMAGE,
|
||||
COMPUTER_PATH,
|
||||
COMPUTER_UID,
|
||||
COMPUTER_USER,
|
||||
computerHomeStorage,
|
||||
@ -48,6 +49,7 @@ import {
|
||||
xdotoolCommand,
|
||||
} from "./computer-spec.js";
|
||||
import { assertComputerHomeWritable } from "./home-ownership.js";
|
||||
import { mountGithubBrokerRoutes } from "./github-broker.js";
|
||||
import {
|
||||
assertRequestIdentity,
|
||||
attemptComputerControl,
|
||||
@ -123,6 +125,7 @@ const app = new Hono();
|
||||
export { app as supervisorApp };
|
||||
|
||||
app.use("*", requestLogging());
|
||||
mountGithubBrokerRoutes(app, supervisorToken);
|
||||
|
||||
export function resolveDockerSocketPath(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
@ -332,7 +335,7 @@ app.post("/computers/:id/exec", async (c) => {
|
||||
env: [
|
||||
`DISPLAY=${layout.display}`,
|
||||
"HOME=/home/rakazo",
|
||||
"PATH=/home/rakazo/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
`PATH=${COMPUTER_PATH}`,
|
||||
"NPM_CONFIG_PREFIX=/home/rakazo/.local",
|
||||
"PIP_USER=1",
|
||||
...Object.entries(body.env ?? {}).map(([k, v]) => `${k}=${v}`),
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
import { execFileSync, mkdtempSync, rmSync, statSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { COMPUTER_IMAGE, hostComputerUser } from "./computer-spec.js";
|
||||
|
||||
function dockerAvailable(): boolean {
|
||||
try {
|
||||
execFileSync("docker", ["info"], { stdio: "ignore" });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
describe("project workspace bind ownership", () => {
|
||||
it("writes project files with the host uid/gid", () => {
|
||||
if (process.env.RAKAZO_PROJECT_WORKSPACE_OWNERSHIP_TEST !== "1" || !dockerAvailable()) {
|
||||
return;
|
||||
}
|
||||
const hostDir = mkdtempSync(path.join(tmpdir(), "rakazo-project-own-"));
|
||||
const marker = "ownership-marker.txt";
|
||||
const user = hostComputerUser(process.getuid?.(), process.getgid?.());
|
||||
try {
|
||||
execFileSync(
|
||||
"docker",
|
||||
[
|
||||
"run",
|
||||
"--rm",
|
||||
"-u",
|
||||
user,
|
||||
"-v",
|
||||
`${hostDir}:/home/rakazo/project`,
|
||||
process.env.RAKAZO_COMPUTER_IMAGE ?? COMPUTER_IMAGE,
|
||||
"bash",
|
||||
"-lc",
|
||||
`echo ok > /home/rakazo/project/${marker}`,
|
||||
],
|
||||
{ stdio: "inherit" },
|
||||
);
|
||||
const stat = statSync(path.join(hostDir, marker));
|
||||
expect(stat.uid).toBe(process.getuid?.());
|
||||
expect(stat.gid).toBe(process.getgid?.());
|
||||
} finally {
|
||||
rmSync(hostDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -1,24 +1,47 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { ownerGithubEnvironmentForRun } from "./deployment-github-token.js";
|
||||
import {
|
||||
ownerGithubEnvironmentForRun,
|
||||
ownerGithubTokenForBroker,
|
||||
} from "./deployment-github-token.js";
|
||||
|
||||
describe("ownerGithubEnvironmentForRun", () => {
|
||||
it("returns tokens only for the deployment owner in a project space", () => {
|
||||
it("returns broker env without GitHub tokens for the deployment owner in a project space", () => {
|
||||
vi.stubEnv("SANDBOX_GITHUB_BROKER_URL", "http://supervisor:7091/internal/github-broker");
|
||||
const secrets = { load: vi.fn(() => "ghp_test") };
|
||||
expect(
|
||||
ownerGithubEnvironmentForRun({
|
||||
settings: { ownerUserId: "owner-1", ownerGithubTokenCipher: "cipher" },
|
||||
userId: "owner-1",
|
||||
spaceIsDefault: false,
|
||||
secrets,
|
||||
}),
|
||||
).toEqual({ GITHUB_TOKEN: "ghp_test", GH_TOKEN: "ghp_test" });
|
||||
const env = ownerGithubEnvironmentForRun({
|
||||
settings: { ownerUserId: "owner-1", ownerGithubTokenCipher: "cipher" },
|
||||
userId: "owner-1",
|
||||
spaceIsDefault: false,
|
||||
secrets,
|
||||
runId: "run-1",
|
||||
hasProjectWorkspace: true,
|
||||
});
|
||||
expect(env.GITHUB_TOKEN).toBeUndefined();
|
||||
expect(env.GH_TOKEN).toBeUndefined();
|
||||
expect(env.RAKAZO_GITHUB_BROKER_URL).toBe("http://supervisor:7091/internal/github-broker");
|
||||
expect(env.RAKAZO_GITHUB_BROKER_RUN_TOKEN).toMatch(/^rgb1\./);
|
||||
expect(
|
||||
ownerGithubEnvironmentForRun({
|
||||
settings: { ownerUserId: "owner-1", ownerGithubTokenCipher: "cipher" },
|
||||
userId: "member-2",
|
||||
spaceIsDefault: false,
|
||||
secrets,
|
||||
runId: "run-1",
|
||||
hasProjectWorkspace: true,
|
||||
}),
|
||||
).toEqual({});
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("loads the owner token only for broker registration", () => {
|
||||
const secrets = { load: vi.fn(() => "ghp_test") };
|
||||
expect(
|
||||
ownerGithubTokenForBroker({
|
||||
settings: { ownerUserId: "owner-1", ownerGithubTokenCipher: "cipher" },
|
||||
userId: "owner-1",
|
||||
spaceIsDefault: false,
|
||||
secrets,
|
||||
}),
|
||||
).toBe("ghp_test");
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,17 +1,21 @@
|
||||
import type { DeploymentSettings } from "@rakazo/db";
|
||||
import { githubBrokerEnvironmentForRun } from "./github-broker-client.js";
|
||||
|
||||
type SecretLoader = {
|
||||
load(ciphertext: string, recordId: string): string;
|
||||
};
|
||||
|
||||
/** Injects the deployment owner's GitHub token into project computer shells only. */
|
||||
/** Broker env for project computers — never injects GITHUB_TOKEN/GH_TOKEN into the sandbox. */
|
||||
export function ownerGithubEnvironmentForRun(input: {
|
||||
settings: Pick<DeploymentSettings, "ownerUserId" | "ownerGithubTokenCipher"> | null;
|
||||
userId: string;
|
||||
spaceIsDefault: boolean;
|
||||
secrets: SecretLoader;
|
||||
runId: string;
|
||||
hasProjectWorkspace: boolean;
|
||||
}): Record<string, string> {
|
||||
if (input.spaceIsDefault) return {};
|
||||
if (!input.hasProjectWorkspace) return {};
|
||||
if (!input.settings?.ownerGithubTokenCipher) return {};
|
||||
if (input.settings.ownerUserId !== input.userId) return {};
|
||||
const token = input.secrets.load(
|
||||
@ -19,5 +23,22 @@ export function ownerGithubEnvironmentForRun(input: {
|
||||
"deployment-github-token",
|
||||
);
|
||||
if (!token.trim()) return {};
|
||||
return { GITHUB_TOKEN: token, GH_TOKEN: token };
|
||||
return githubBrokerEnvironmentForRun(input.runId);
|
||||
}
|
||||
|
||||
export function ownerGithubTokenForBroker(input: {
|
||||
settings: Pick<DeploymentSettings, "ownerUserId" | "ownerGithubTokenCipher"> | null;
|
||||
userId: string;
|
||||
spaceIsDefault: boolean;
|
||||
secrets: SecretLoader;
|
||||
}): string | undefined {
|
||||
if (input.spaceIsDefault) return undefined;
|
||||
if (!input.settings?.ownerGithubTokenCipher) return undefined;
|
||||
if (input.settings.ownerUserId !== input.userId) return undefined;
|
||||
const token = input.secrets.load(
|
||||
input.settings.ownerGithubTokenCipher,
|
||||
"deployment-github-token",
|
||||
);
|
||||
const trimmed = token.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
@ -237,7 +237,16 @@ import {
|
||||
loadDelegatedTaskInstruction,
|
||||
syncDelegatedTaskRun,
|
||||
} from "./delegated-tasks.js";
|
||||
import { ownerGithubEnvironmentForRun } from "./deployment-github-token.js";
|
||||
import {
|
||||
ownerGithubEnvironmentForRun,
|
||||
ownerGithubTokenForBroker,
|
||||
} from "./deployment-github-token.js";
|
||||
import {
|
||||
deleteGithubBrokerSession,
|
||||
registerGithubBrokerSession,
|
||||
setGithubBrokerPushApproved,
|
||||
} from "./github-broker-client.js";
|
||||
import { resolveProjectWorkspaceBind } from "./project-workspace-bind.js";
|
||||
import { resolveDeploymentModel } from "./deployment-model.js";
|
||||
import {
|
||||
missingTurnImagesInstruction,
|
||||
@ -1169,6 +1178,7 @@ export function createRunExecutor(deps: ExecutorDeps) {
|
||||
defaultCredential,
|
||||
settings,
|
||||
spaceRecord,
|
||||
projectWorkspacePath,
|
||||
configuredMemory,
|
||||
savedSkills,
|
||||
agentSkills,
|
||||
@ -1209,7 +1219,11 @@ export function createRunExecutor(deps: ExecutorDeps) {
|
||||
}),
|
||||
deps.prisma.space.findUnique({
|
||||
where: { id: run.spaceId },
|
||||
select: { isDefault: true, gitDefaultBranch: true },
|
||||
select: { isDefault: true, gitDefaultBranch: true, gitRemoteUrl: true },
|
||||
}),
|
||||
resolveProjectWorkspaceBind(deps.prisma, {
|
||||
spaceId: run.spaceId,
|
||||
userId: run.userId,
|
||||
}),
|
||||
deps.memoryProviders.resolve({ spaceId: run.spaceId, userId: run.userId }),
|
||||
deps.prisma.taughtSkill.findMany({
|
||||
@ -1228,6 +1242,21 @@ export function createRunExecutor(deps: ExecutorDeps) {
|
||||
}),
|
||||
loadUserContext(deps.prisma, { userId: run.userId, spaceId: run.spaceId }),
|
||||
]);
|
||||
const ownerGithubToken = ownerGithubTokenForBroker({
|
||||
settings,
|
||||
userId: run.userId,
|
||||
spaceIsDefault: spaceRecord?.isDefault ?? true,
|
||||
secrets: deps.secretStore,
|
||||
});
|
||||
if (projectWorkspacePath && ownerGithubToken) {
|
||||
await registerGithubBrokerSession({
|
||||
runId,
|
||||
workspacePath: projectWorkspacePath,
|
||||
allowedRemoteUrl: spaceRecord?.gitRemoteUrl ?? null,
|
||||
defaultBranch: spaceRecord?.gitDefaultBranch ?? null,
|
||||
githubToken: ownerGithubToken,
|
||||
}).catch(() => undefined);
|
||||
}
|
||||
const agentEnvironment = {
|
||||
...decryptAgentEnvironment(agentSecretRows, deps.secretStore),
|
||||
...ownerGithubEnvironmentForRun({
|
||||
@ -1235,6 +1264,8 @@ export function createRunExecutor(deps: ExecutorDeps) {
|
||||
userId: run.userId,
|
||||
spaceIsDefault: spaceRecord?.isDefault ?? true,
|
||||
secrets: deps.secretStore,
|
||||
runId,
|
||||
hasProjectWorkspace: Boolean(projectWorkspacePath),
|
||||
}),
|
||||
};
|
||||
runSecrets.push(...Object.values(agentEnvironment));
|
||||
@ -2092,6 +2123,7 @@ export function createRunExecutor(deps: ExecutorDeps) {
|
||||
let gateDecision: "ask" | "allow" = plan === "ask" ? "ask" : "allow";
|
||||
if (gitShellNeedsApproval && nextApprovedTool) {
|
||||
gitPushApprovedRuns.add(runId);
|
||||
void setGithubBrokerPushApproved(runId, true);
|
||||
}
|
||||
const needsApprovalEarly = plan === "ask" || plan === "judge";
|
||||
// A resumed approval keeps its key even if "Always allow" changed the policy.
|
||||
@ -5004,6 +5036,8 @@ export function createRunExecutor(deps: ExecutorDeps) {
|
||||
throw new Error("Run setup failed; retrying");
|
||||
}
|
||||
} finally {
|
||||
gitPushApprovedRuns.delete(runId);
|
||||
void deleteGithubBrokerSession(runId);
|
||||
detachShutdown?.();
|
||||
clearInterval(leasePoll);
|
||||
clearInterval(heartbeat);
|
||||
|
||||
@ -1,81 +1,8 @@
|
||||
export type GitShellPolicyInput = {
|
||||
command: string;
|
||||
defaultBranch: string | null | undefined;
|
||||
gitPushApprovedThisRun: boolean;
|
||||
};
|
||||
|
||||
export type GitShellPolicyDecision =
|
||||
| { action: "allow" }
|
||||
| { action: "block"; reason: string }
|
||||
| { action: "require_approval"; reason: string };
|
||||
|
||||
const GIT_PUSH_RE = /\bgit\b[^\n;&|]*\bpush\b/i;
|
||||
const GIT_FORCE_PUSH_RE = /(?:--force-with-lease|--force(?:\s|$)|(?:^|\s)-f(?:\s|$))/i;
|
||||
const GH_PR_RE = /\bgh\b(?:(?:\s+[^;&|]+)*\s+pr\b|\s+pr\b)(?:\s+create\b|\s|$|[;&|])/i;
|
||||
|
||||
export function evaluateGitShellPolicy(input: GitShellPolicyInput): GitShellPolicyDecision {
|
||||
const command = input.command.trim();
|
||||
if (!command) return { action: "allow" };
|
||||
|
||||
if (GIT_PUSH_RE.test(command)) {
|
||||
if (GIT_FORCE_PUSH_RE.test(command)) {
|
||||
return {
|
||||
action: "block",
|
||||
reason: "Force-push is not allowed. Push to a new branch without force flags.",
|
||||
};
|
||||
}
|
||||
const targetBranch = parseGitPushRef(command);
|
||||
const protectedBranch = normalizeBranchName(input.defaultBranch);
|
||||
if (protectedBranch && targetBranch && targetBranch === protectedBranch) {
|
||||
return {
|
||||
action: "block",
|
||||
reason: `Push to the default branch (${protectedBranch}) is not allowed. Create a feature branch and push that instead.`,
|
||||
};
|
||||
}
|
||||
if (!input.gitPushApprovedThisRun) {
|
||||
return {
|
||||
action: "require_approval",
|
||||
reason:
|
||||
"This run would push commits to a remote. Approve once to allow git push in this run.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (GH_PR_RE.test(command) && !input.gitPushApprovedThisRun) {
|
||||
return {
|
||||
action: "require_approval",
|
||||
reason:
|
||||
"Opening a pull request is a consequential git action. Approve once to allow gh pr in this run.",
|
||||
};
|
||||
}
|
||||
|
||||
return { action: "allow" };
|
||||
}
|
||||
|
||||
function normalizeBranchName(value: string | null | undefined): string | null {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed || trimmed === "HEAD") return null;
|
||||
return trimmed.replace(/^origin\//, "");
|
||||
}
|
||||
|
||||
/** Best-effort parse of the ref git push would update on the remote. */
|
||||
export function parseGitPushRef(command: string): string | null {
|
||||
const tokens = command
|
||||
.replace(/&&/g, ";")
|
||||
.replace(/\|\|/g, ";")
|
||||
.split(/[;&|]/)
|
||||
.flatMap((segment) => segment.trim().split(/\s+/))
|
||||
.filter(Boolean);
|
||||
const pushIndex = tokens.indexOf("push");
|
||||
if (pushIndex < 0 || !tokens.slice(0, pushIndex).includes("git")) return null;
|
||||
const args = tokens.slice(pushIndex + 1).filter((token) => !token.startsWith("-"));
|
||||
if (args.length === 0) return null;
|
||||
const last = args.at(-1);
|
||||
if (!last) return null;
|
||||
if (last.includes(":")) {
|
||||
const [, dest] = last.split(":");
|
||||
return normalizeBranchName(dest) ?? null;
|
||||
}
|
||||
if (args.length >= 2) return normalizeBranchName(last);
|
||||
return normalizeBranchName(last);
|
||||
}
|
||||
export {
|
||||
evaluateGitShellPolicy,
|
||||
ghBrokerArgv,
|
||||
gitNetworkArgv,
|
||||
parseGitPushRef,
|
||||
type GitShellPolicyDecision,
|
||||
type GitShellPolicyInput,
|
||||
} from "@rakazo/core";
|
||||
|
||||
75
packages/adapters/src/github-broker-client.ts
Normal file
75
packages/adapters/src/github-broker-client.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { resolveSupervisorToken, signGithubBrokerRunToken } from "@rakazo/core";
|
||||
|
||||
export type GithubBrokerSessionInput = {
|
||||
runId: string;
|
||||
workspacePath: string;
|
||||
allowedRemoteUrl: string | null;
|
||||
defaultBranch: string | null;
|
||||
githubToken: string;
|
||||
};
|
||||
|
||||
function supervisorBaseUrl(): string | undefined {
|
||||
const raw = process.env.SANDBOX_SUPERVISOR_URL?.trim();
|
||||
return raw ? raw.replace(/\/$/, "") : undefined;
|
||||
}
|
||||
|
||||
function supervisorAuthHeaders(): Record<string, string> | undefined {
|
||||
const token = resolveSupervisorToken(process.env);
|
||||
return { authorization: `Bearer ${token}`, "content-type": "application/json" };
|
||||
}
|
||||
|
||||
export function githubBrokerRunToken(runId: string): string {
|
||||
return signGithubBrokerRunToken(runId, resolveSupervisorToken(process.env));
|
||||
}
|
||||
|
||||
export function githubBrokerEnvironmentForRun(runId: string): Record<string, string> {
|
||||
const base = process.env.SANDBOX_GITHUB_BROKER_URL?.trim();
|
||||
if (!base) return {};
|
||||
return {
|
||||
RAKAZO_GITHUB_BROKER_URL: base.replace(/\/$/, ""),
|
||||
RAKAZO_GITHUB_BROKER_RUN_TOKEN: githubBrokerRunToken(runId),
|
||||
};
|
||||
}
|
||||
|
||||
export async function registerGithubBrokerSession(input: GithubBrokerSessionInput): Promise<void> {
|
||||
const base = supervisorBaseUrl();
|
||||
const headers = supervisorAuthHeaders();
|
||||
if (!base || !headers) return;
|
||||
const res = await fetch(`${base}/internal/github-broker/sessions`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
runId: input.runId,
|
||||
workspacePath: input.workspacePath,
|
||||
allowedRemoteUrl: input.allowedRemoteUrl,
|
||||
defaultBranch: input.defaultBranch,
|
||||
githubToken: input.githubToken,
|
||||
pushApproved: false,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const detail = await res.text().catch(() => "");
|
||||
throw new Error(`github broker session registration failed: ${res.status} ${detail}`.trim());
|
||||
}
|
||||
}
|
||||
|
||||
export async function setGithubBrokerPushApproved(runId: string, pushApproved: boolean): Promise<void> {
|
||||
const base = supervisorBaseUrl();
|
||||
const headers = supervisorAuthHeaders();
|
||||
if (!base || !headers) return;
|
||||
await fetch(`${base}/internal/github-broker/sessions/${encodeURIComponent(runId)}`, {
|
||||
method: "PATCH",
|
||||
headers,
|
||||
body: JSON.stringify({ pushApproved }),
|
||||
}).catch(() => undefined);
|
||||
}
|
||||
|
||||
export async function deleteGithubBrokerSession(runId: string): Promise<void> {
|
||||
const base = supervisorBaseUrl();
|
||||
const headers = supervisorAuthHeaders();
|
||||
if (!base || !headers) return;
|
||||
await fetch(`${base}/internal/github-broker/sessions/${encodeURIComponent(runId)}`, {
|
||||
method: "DELETE",
|
||||
headers,
|
||||
}).catch(() => undefined);
|
||||
}
|
||||
99
packages/core/src/git-shell-policy.ts
Normal file
99
packages/core/src/git-shell-policy.ts
Normal file
@ -0,0 +1,99 @@
|
||||
export type GitShellPolicyInput = {
|
||||
command: string;
|
||||
defaultBranch: string | null | undefined;
|
||||
gitPushApprovedThisRun: boolean;
|
||||
};
|
||||
|
||||
export type GitShellPolicyDecision =
|
||||
| { action: "allow" }
|
||||
| { action: "block"; reason: string }
|
||||
| { action: "require_approval"; reason: string };
|
||||
|
||||
const GIT_PUSH_RE = /\bgit\b[^\n;&|]*\bpush\b/i;
|
||||
const GIT_FORCE_PUSH_RE = /(?:--force-with-lease|--force(?:\s|$)|(?:^|\s)-f(?:\s|$))/i;
|
||||
const GH_PR_RE = /\bgh\b(?:(?:\s+[^;&|]+)*\s+pr\b|\s+pr\b)(?:\s+create\b|\s|$|[;&|])/i;
|
||||
|
||||
export function evaluateGitShellPolicy(input: GitShellPolicyInput): GitShellPolicyDecision {
|
||||
const command = input.command.trim();
|
||||
if (!command) return { action: "allow" };
|
||||
|
||||
if (GIT_PUSH_RE.test(command)) {
|
||||
if (GIT_FORCE_PUSH_RE.test(command)) {
|
||||
return {
|
||||
action: "block",
|
||||
reason: "Force-push is not allowed. Push to a new branch without force flags.",
|
||||
};
|
||||
}
|
||||
const targetBranch = parseGitPushRef(command);
|
||||
const protectedBranch = normalizeBranchName(input.defaultBranch);
|
||||
if (protectedBranch && targetBranch && targetBranch === protectedBranch) {
|
||||
return {
|
||||
action: "block",
|
||||
reason: `Push to the default branch (${protectedBranch}) is not allowed. Create a feature branch and push that instead.`,
|
||||
};
|
||||
}
|
||||
if (!input.gitPushApprovedThisRun) {
|
||||
return {
|
||||
action: "require_approval",
|
||||
reason:
|
||||
"This run would push commits to a remote. Approve once to allow git push in this run.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (GH_PR_RE.test(command) && !input.gitPushApprovedThisRun) {
|
||||
return {
|
||||
action: "require_approval",
|
||||
reason:
|
||||
"Opening a pull request is a consequential git action. Approve once to allow gh pr in this run.",
|
||||
};
|
||||
}
|
||||
|
||||
return { action: "allow" };
|
||||
}
|
||||
|
||||
function normalizeBranchName(value: string | null | undefined): string | null {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed || trimmed === "HEAD") return null;
|
||||
return trimmed.replace(/^origin\//, "");
|
||||
}
|
||||
|
||||
/** Best-effort parse of the ref git push would update on the remote. */
|
||||
export function parseGitPushRef(command: string): string | null {
|
||||
const tokens = command
|
||||
.replace(/&&/g, ";")
|
||||
.replace(/\|\|/g, ";")
|
||||
.split(/[;&|]/)
|
||||
.flatMap((segment) => segment.trim().split(/\s+/))
|
||||
.filter(Boolean);
|
||||
const pushIndex = tokens.indexOf("push");
|
||||
if (pushIndex < 0 || !tokens.slice(0, pushIndex).includes("git")) return null;
|
||||
const args = tokens.slice(pushIndex + 1).filter((token) => !token.startsWith("-"));
|
||||
if (args.length === 0) return null;
|
||||
const last = args.at(-1);
|
||||
if (!last) return null;
|
||||
if (last.includes(":")) {
|
||||
const [, dest] = last.split(":");
|
||||
return normalizeBranchName(dest) ?? null;
|
||||
}
|
||||
if (args.length >= 2) return normalizeBranchName(last);
|
||||
return normalizeBranchName(last);
|
||||
}
|
||||
|
||||
export function gitNetworkArgv(argv: string[]): boolean {
|
||||
if (argv.length === 0 || argv[0] !== "git") return false;
|
||||
const sub = argv[1];
|
||||
return (
|
||||
sub === "push" ||
|
||||
sub === "fetch" ||
|
||||
sub === "pull" ||
|
||||
sub === "clone" ||
|
||||
sub === "ls-remote"
|
||||
);
|
||||
}
|
||||
|
||||
export function ghBrokerArgv(argv: string[]): boolean {
|
||||
if (argv.length === 0 || argv[0] !== "gh") return false;
|
||||
const joined = argv.join(" ");
|
||||
return GH_PR_RE.test(joined);
|
||||
}
|
||||
18
packages/core/src/github-broker-token.ts
Normal file
18
packages/core/src/github-broker-token.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { createHmac, timingSafeEqual } from "node:crypto";
|
||||
|
||||
const PREFIX = "rgb1.";
|
||||
|
||||
/** Opaque run-scoped token for the sandbox GitHub broker (not a GitHub PAT). */
|
||||
export function signGithubBrokerRunToken(runId: string, secret: string): string {
|
||||
const digest = createHmac("sha256", secret).update(runId, "utf8").digest("base64url");
|
||||
return `${PREFIX}${digest}`;
|
||||
}
|
||||
|
||||
export function verifyGithubBrokerRunToken(token: string, secret: string, runId: string): boolean {
|
||||
if (!token.startsWith(PREFIX)) return false;
|
||||
const expected = signGithubBrokerRunToken(runId, secret);
|
||||
const a = Buffer.from(token, "utf8");
|
||||
const b = Buffer.from(expected, "utf8");
|
||||
if (a.length !== b.length) return false;
|
||||
return timingSafeEqual(a, b);
|
||||
}
|
||||
30
packages/core/src/github-remote-allowlist.ts
Normal file
30
packages/core/src/github-remote-allowlist.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/** Normalize a git remote URL for allowlist comparison. */
|
||||
export function normalizeGitRemoteUrl(raw: string | null | undefined): string | null {
|
||||
const trimmed = raw?.trim();
|
||||
if (!trimmed) return null;
|
||||
try {
|
||||
if (/^git@/i.test(trimmed)) {
|
||||
const match = /^git@([^:]+):(.+?)(?:\.git)?$/i.exec(trimmed);
|
||||
if (!match) return null;
|
||||
return `https://${match[1]!.toLowerCase()}/${match[2]!.replace(/^\//, "")}`.replace(
|
||||
/\.git$/i,
|
||||
"",
|
||||
);
|
||||
}
|
||||
const url = new URL(trimmed);
|
||||
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
||||
return `${url.protocol}//${url.host.toLowerCase()}${url.pathname.replace(/\/$/, "").replace(/\.git$/i, "")}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function gitRemoteMatchesAllowlist(
|
||||
remoteUrl: string | null | undefined,
|
||||
allowedRemoteUrl: string | null | undefined,
|
||||
): boolean {
|
||||
const remote = normalizeGitRemoteUrl(remoteUrl);
|
||||
const allowed = normalizeGitRemoteUrl(allowedRemoteUrl);
|
||||
if (!remote || !allowed) return false;
|
||||
return remote === allowed;
|
||||
}
|
||||
@ -19,6 +19,9 @@ export * from "./computer-updates.js";
|
||||
export * from "./cron.js";
|
||||
export * from "./delegated-tasks.js";
|
||||
export * from "./events.js";
|
||||
export * from "./git-shell-policy.js";
|
||||
export * from "./github-broker-token.js";
|
||||
export * from "./github-remote-allowlist.js";
|
||||
export * from "./featured-connectors.js";
|
||||
export * from "./group-mentions.js";
|
||||
export * from "./http-response.js";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user