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:
Cursor Agent 2026-10-07 16:48:47 +00:00
parent 35ac96fed3
commit 0c9e6d7c58
No known key found for this signature in database
35 changed files with 2333 additions and 147 deletions

View File

@ -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 }}

View File

@ -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(() => {

View File

@ -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}

View 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.`;
}
}

View File

@ -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(

View File

@ -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"

View File

@ -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"

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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 "Считать человеком"

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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) {

View File

@ -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+

View File

@ -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

View 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"

View 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" "$@"

View File

@ -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"'

View File

@ -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/);

View File

@ -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}`] : []),

View 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");
});
});

View 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);
});
}

View File

@ -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}`),

View File

@ -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 });
}
});
});

View File

@ -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");
});
});

View File

@ -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;
}

View File

@ -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);

View File

@ -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";

View 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);
}

View 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);
}

View 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);
}

View 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;
}

View File

@ -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";