Compare commits

...

6 Commits

Author SHA1 Message Date
Arvin Xu 17fd96ca5a 🐛 fix(desktop): re-throw non-transient unhandled rejections
Installing an unhandledRejection listener overrides Node's default
--unhandled-rejections=throw, so the prior log-and-return swallowed
genuine main-process rejections (e.g. an unawaited app.bootstrap()),
leaving the app partially booted instead of crashing. Re-throw the
non-transient branch to restore the fatal behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 11:44:59 +08:00
Arvin Xu 58a44e5ed3 🐛 fix(desktop): swallow transient net errors in main process
Electron's net stack (SimpleURLLoaderWrapper, used internally by
electron-updater's net.request) emits transient connectivity errors
(ERR_NETWORK_CHANGED, ERR_NETWORK_IO_SUSPENDED) on Wi-Fi/VPN switch and
system sleep. With no global guard they bubble up as an uncaughtException
and pop the "A JavaScript error occurred in the main process" dialog.

Add a process-level handler that swallows known transient net-stack
errors and re-throws everything else, so genuine crashes stay visible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:41:59 +08:00
Rdmclin2 e5adb393b3 feat: workspace device support (#16134)
* feat: workspace device support

Squashed commits:
- chore: update device constraint
- chore: local system support remote device workspace
- fix: reconnect time
- fix: workspace connect daemon parameter
- fix: device list refresh
- chore: device admin
- feat: support workspace device
- feat: device list support workspace
- feat: add workspaceId to device gateway

* chore: update i18n rc

* 🐛 fix(workspace): narrow wsOwnerProcedure ctx.workspaceId to string

The OSS stub left `wsOwnerProcedure = authedProcedure`, so consumers
saw `ctx.workspaceId: string | null | undefined` and broke type-check
in `lambda/device.ts` (signWorkspaceDeviceToken / DeviceModel).
Add a guard middleware that throws + narrows the type, matching the
cloud override semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: update i18n files

* fix: remote device test case

* fix: device workspaceId request

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-21 00:41:06 +08:00
Arvin Xu 434c16c49d 🔖 chore(cli): bump @lobehub/cli to 0.0.34 (#16139) 2026-06-21 00:35:51 +08:00
Arvin Xu 52d2306793 feat(verify): ingest path for standalone verify run (#16132)
*  feat(verify): ingest path for standalone sessions (createRun / ingestResult / uploadEvidence / upsertReport + CLI)

Add the server + CLI surface to persist a verification session that isn't a live
Agent Run — e.g. the agent-testing harness pushing its local report into the
verify tables so it can be reviewed in-app.

- model: VerifyCheckResultModel.upsertByCheckItem (insert-or-update on the stable
  (verifyRunId, checkItemId) key, for direct verdict ingest); VerifyRunModel.query
  (recent sessions).
- router (verify): createRun / getRun / listRuns; ingestResult (verdict +
  toulmin/suggestion, derives status); uploadEvidence + listEvidence (anchored on
  checkResultId, accepts an already-uploaded fileId or inline content);
  upsertReport / getReport. All keyed by verifyRunId, no operation required.
- CLI: `lh verify ingest-report <dir>` reads result.json + report.md + assets,
  creates a session, ingests each case as a check result, uploads its evidence
  files, and writes the report (`--open` prints the in-app URL). Plus
  `lh verify upload-evidence` (LOBE-10618) for one-off artifact attach.
- tests: upsertByCheckItem insert-then-overwrite.

Stacked on the verify_runs schema branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

*  feat(verify): standalone report viewer page (/verify-report?id=<verifyRunId>)

Make an ingested verification session viewable in-app without an Agent Run / chat
context — the missing half of the agent-testing ingest flow.

- server: verify.getReportBundle({verifyRunId}) returns { run, report, results }
  in one call, with each result's evidence and a resolved (signed) file URL per
  file-backed artifact (via FileService.getFullFileUrl).
- client: verifyService.getReportBundle + useVerifyReportBundle SWR hook + key.
- ui: Verify/ReportViewer renders the verdict/stats header, each check result with
  its inline evidence (image / text), and the full markdown report.
- route: /verify-report registered in desktop + desktop.desktop + mobile router
  configs (sync test green).
- cli: `lh verify ingest-report --open` now prints /verify-report?id=… (the prior
  /verify-im hint was wrong — that route is the messenger account-linking page).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ♻️ refactor(verify): use path param /verify/:runId for report viewer

Switch the standalone verification-report route from the query-based
/verify-report?id=<id> to a path param /verify/:runId, so the URL is
cleaner and shareable. ReportViewer now reads runId via useParams, the
CLI --open output prints the new path, and the route file moves to
src/routes/verify/[runId]/index.tsx across all router configs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* 🐛 fix(verify): use path-param route /verify/:runId + allowlist it in the SPA matcher

Verified the ingest→viewer flow end-to-end against a local full-stack env and
fixed what that surfaced:

- route: /verify-report?id= → /verify/:runId (path param); ReportViewer reads
  useParams; registered in all three router configs.
- proxy: add `/verify/(.*)` to the SPA route matcher (src/proxy.ts) — without it
  the server 307-redirects unknown paths to `/`, so the viewer never loaded.
- cli: ingest-report no longer double-writes the case observation (was set on both
  `suggestion` and `toulmin.evidence`, rendering twice); observation → toulmin,
  `suggestion` reserved for an actual remediation hint. --open prints /verify/<id>.

E2E confirmed: lh verify ingest-report created a standalone session
(operation_id=NULL) + 3 results + report; /verify/:runId renders it in-app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

*  feat(verify): atomize CLI into per-entity commands (run / result / evidence / report)

Per review: break the verify CLI into atomic, per-entity capabilities so each can
be called directly, with the report ingest kept as an aggregate convenience.

- cli: new command groups —
  - `verify run create | list | get`
  - `verify result ingest | list (--run | --operation)`
  - `verify evidence upload (--file | --content) | list`
  - `verify report upsert | get`
  Renamed the agent-path executor `verify run <op>` → `verify execute <op>` to free
  the `run` noun for the session entity; `verify ingest-report` stays as the
  aggregate (it just composes the atomic procedures).
- server: add verify.listResultsByRun (run-keyed result list) backing `result list --run`.
- model: VerifyRunModel guards operation_id reservation with assertOperationOwned
  so a run can't hijack another owner's (globally-unique) operation link; test added.

Verified end-to-end against a local full-stack env with real R2 storage: both the
aggregate `ingest-report` and a fully atomic create→ingest→upload(file)→report
chain produce a standalone session that renders at /verify/:runId, with
file-backed evidence served from R2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* 🐛 fix: validate verify ingestion ownership

* 🗃️ chore: remove verify migration backfill

*  test: cover verify ingest run ownership

*  allow public verify report viewing

* 🐛 fix verify report evidence rendering

* 🐛 tolerate missing verify evidence file URLs

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:29:40 +08:00
Arvin Xu 330694fe49 🐛 fix(conversation): surface terminal errors on assistant turns that already streamed content (#16086)
* 🐛 fix(conversation): surface terminal errors on assistant turns that already streamed content

A turn that streamed content + a successful tool call before hitting a
terminal error (e.g. upstream 529 overload) had its `error` silently
dropped on the read side: both the AssistantGroup `ContentBlock` and the
normal `Assistant` message gated the error UI behind empty content, so
the message rendered as if it had completed cleanly — no error banner, no
retry.

Now the error is shown below the content when a turn errors after
producing output, while keeping the existing "error replaces the whole
block" behavior when nothing was streamed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* 🐛 fix(conversation): keep streamed content when dismissing an error alert

The fallback ErrorContent alert is closable and its afterClose unconditionally
deleted the whole message. Now that errors render below already-streamed
content, dismissing the alert would wipe the content it was meant to preserve.

Dismiss now clears only the error (updateMessageError(id, null)) when the
message still has content, and keeps the existing delete behavior for
messages that are nothing but an error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:26:03 +08:00
223 changed files with 4758 additions and 1972 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ module.exports = defineConfig({
markdown: {
reference:
'You need to maintain the component format of the mdx file; the output text does not need to be wrapped in any code block syntax on the outermost layer.\n' +
fs.readFileSync(path.join(__dirname, 'docs/glossary.md'), 'utf8'),
fs.readFileSync(path.join(__dirname, 'docs/glossary.mdx'), 'utf8'),
entry: ['./README.md', './docs/**/*.md', './docs/**/*.mdx'],
entryLocale: 'en-US',
outputLocales: ['zh-CN'],
+7 -1
View File
@@ -1,6 +1,6 @@
.\" Code generated by `npm run man:generate`; DO NOT EDIT.
.\" Manual command details come from the Commander command tree.
.TH LH 1 "" "@lobehub/cli 0.0.32" "User Commands"
.TH LH 1 "" "@lobehub/cli 0.0.34" "User Commands"
.SH NAME
lh \- LobeHub CLI \- manage and connect to LobeHub services
.SH SYNOPSIS
@@ -41,6 +41,9 @@ Show a manual page for the CLI or a subcommand
.B connect
Connect to the device gateway and listen for tool calls
.TP
.B disconnect
Disconnect from the device gateway (alias for `connect stop`)
.TP
.B device
Manage connected devices
.TP
@@ -127,6 +130,9 @@ Manage evaluation workflows
.TP
.B migrate
Migrate data from external tools (OpenClaw, ChatGPT, Claude, etc.)
.TP
.B update
Update the LobeHub CLI to the latest published version
.SH OPTIONS
.TP
.B \-V, \-\-version
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@lobehub/cli",
"version": "0.0.32",
"version": "0.0.34",
"type": "module",
"bin": {
"lh": "./dist/index.js",
+39 -13
View File
@@ -12,7 +12,8 @@ import { log } from '../utils/logger';
export type TrpcClient = ReturnType<typeof createTRPCClient<LambdaRouter>>;
export type ToolsTrpcClient = ReturnType<typeof createTRPCClient<ToolsRouter>>;
let _client: TrpcClient | undefined;
const PERSONAL_KEY = '__personal__';
const _clients = new Map<string, TrpcClient>();
let _toolsClient: ToolsTrpcClient | undefined;
async function getAuthAndServer() {
@@ -53,21 +54,40 @@ async function getAuthAndServer() {
};
}
export async function getTrpcClient(): Promise<TrpcClient> {
if (_client) return _client;
/**
* Resolve the workspace scope for outbound tRPC calls.
*
* Precedence: explicit caller arg → `LOBEHUB_WORKSPACE_ID` env (inherited
* from a workspace-dispatched parent process, e.g. openclaw spawned by the
* device's `runHeteroTask`) → personal mode. Without this, agentNotify
* callbacks on workspace topics would resolve through personal-mode
* TopicModel and 404.
*/
function resolveWorkspaceId(explicit?: string): string | undefined {
if (explicit) return explicit;
const fromEnv = process.env.LOBEHUB_WORKSPACE_ID;
return fromEnv && fromEnv.length > 0 ? fromEnv : undefined;
}
export async function getTrpcClient(workspaceId?: string): Promise<TrpcClient> {
const wsId = resolveWorkspaceId(workspaceId);
const cacheKey = wsId ?? PERSONAL_KEY;
const cached = _clients.get(cacheKey);
if (cached) return cached;
const { headers, serverUrl } = await getAuthAndServer();
_client = createTRPCClient<LambdaRouter>({
const client = createTRPCClient<LambdaRouter>({
links: [
httpLink({
headers,
headers: wsId ? { ...headers, 'X-Workspace-Id': wsId } : headers,
transformer: superjson,
url: `${serverUrl}/trpc/lambda`,
}),
],
});
_clients.set(cacheKey, client);
return _client;
return client;
}
/**
@@ -77,13 +97,19 @@ export async function getTrpcClient(): Promise<TrpcClient> {
* via env/stored creds and `process.exit(1)` when none exist, which would
* abort an otherwise-valid explicit-token session.
*/
export function createLambdaClient(auth: {
serverUrl: string;
token: string;
tokenType: 'apiKey' | 'jwt' | 'serviceToken';
}): TrpcClient {
const headers =
auth.tokenType === 'apiKey' ? { 'X-API-Key': auth.token } : { 'Oidc-Auth': auth.token };
export function createLambdaClient(
auth: {
serverUrl: string;
token: string;
tokenType: 'apiKey' | 'jwt' | 'serviceToken';
},
/** When set, scopes the request to a workspace (e.g. workspace-device enrollment). */
workspaceId?: string,
): TrpcClient {
const headers: Record<string, string> = {
...(auth.tokenType === 'apiKey' ? { 'X-API-Key': auth.token } : { 'Oidc-Auth': auth.token }),
...(workspaceId ? { 'X-Workspace-Id': workspaceId } : {}),
};
return createTRPCClient<LambdaRouter>({
links: [httpLink({ headers, transformer: superjson, url: `${auth.serverUrl}/trpc/lambda` })],
+98 -47
View File
@@ -18,7 +18,6 @@ import type {
import { GatewayClient } from '@lobechat/device-gateway-client';
import type { Command } from 'commander';
import { getValidToken } from '../auth/refresh';
import { resolveToken } from '../auth/resolveToken';
import { CLI_API_KEY_ENV } from '../constants/auth';
import { OFFICIAL_GATEWAY_URL } from '../constants/urls';
@@ -34,7 +33,13 @@ import {
writeStatus,
} from '../daemon/manager';
import { spawnHeteroAgentRun } from '../device/agentRun';
import { registerDevice, resolveDeviceIdentity } from '../device/register';
import {
mintWorkspaceConnectToken,
registerDevice,
registerWorkspaceDevice,
resolveDeviceIdentity,
resolveWorkspaceDeviceIdentity,
} from '../device/register';
import { loadOrCreateConnectionId, loadSettings, normalizeUrl, saveSettings } from '../settings';
import { executeToolCall } from '../tools';
import { cleanupAllProcesses } from '../tools/shell';
@@ -47,6 +52,8 @@ interface ConnectOptions {
gateway?: string;
token?: string;
verbose?: boolean;
/** Enroll this machine as a device of the given workspace (admin only). */
workspace?: string;
}
export function registerConnectCommand(program: Command) {
@@ -56,6 +63,7 @@ export function registerConnectCommand(program: Command) {
.option('--token <jwt>', 'JWT access token')
.option('--gateway <url>', 'Device gateway URL')
.option('--device-id <id>', 'Device ID (auto-generated if not provided)')
.option('--workspace <id>', 'Enroll as a device of this workspace (admin only)')
.option('-v, --verbose', 'Enable verbose logging')
.option('-d, --daemon', 'Run as a background daemon process')
.option('--daemon-child', 'Internal: runs as the daemon child process')
@@ -185,6 +193,7 @@ function buildDaemonArgs(options: ConnectOptions): string[] {
if (options.token) args.push('--token', options.token);
if (options.gateway) args.push('--gateway', options.gateway);
if (options.deviceId) args.push('--device-id', options.deviceId);
if (options.workspace) args.push('--workspace', options.workspace);
if (options.verbose) args.push('--verbose');
return args;
@@ -209,10 +218,43 @@ async function runConnect(options: ConnectOptions, isDaemonChild: boolean) {
const resolvedGatewayUrl = gatewayUrl || OFFICIAL_GATEWAY_URL;
// Workspace enrollment: the device joins a workspace pool (reachable by all
// members) instead of the personal pool. It authenticates with a minted
// workspace-device token (carrying the `workspace_id` claim) and uses a
// workspace-derived deviceId. `auth` stays the admin's identity — used only to
// (re-)mint the connect token and register the row.
const workspaceId = options.workspace;
// Resolve a stable device identity. An explicit `--device-id` wins (lets a
// user pin a VM to a fixed identity); otherwise derive from the machine id so
// the same machine + user maps to one device across reconnects.
const identity = resolveDeviceIdentity(auth.userId, options.deviceId);
// the same machine maps to one device across reconnects.
const identity = workspaceId
? resolveWorkspaceDeviceIdentity(workspaceId, options.deviceId)
: resolveDeviceIdentity(auth.userId, options.deviceId);
// The token the gateway socket authenticates with. Re-minted on refresh for
// workspace devices (see `refreshConnectToken`).
let connectToken = auth.token;
let connectTokenType: 'apiKey' | 'jwt' | 'serviceToken' = auth.tokenType;
if (workspaceId) {
const minted = await mintWorkspaceConnectToken(auth, workspaceId);
connectToken = minted.token;
connectTokenType = 'jwt';
}
// Re-resolve the admin auth and, for workspace mode, re-mint the connect token.
const refreshConnectToken = async (): Promise<string | undefined> => {
const refreshed = await resolveToken({});
if (!refreshed) return undefined;
auth = refreshed;
if (workspaceId) {
const minted = await mintWorkspaceConnectToken(auth, workspaceId);
connectToken = minted.token;
return connectToken;
}
connectToken = refreshed.token;
return connectToken;
};
// Freeform channel label (`cli` by default); `LOBEHUB_CLI_CHANNEL` lets a
// dev build tag itself `cli-dev` so the gateway can prioritise / display it.
@@ -225,9 +267,10 @@ async function runConnect(options: ConnectOptions, isDaemonChild: boolean) {
gatewayUrl: resolvedGatewayUrl,
logger: isDaemonChild ? createDaemonLogger() : log,
serverUrl: auth.serverUrl,
token: auth.token,
tokenType: auth.tokenType,
userId: auth.userId,
token: connectToken,
tokenType: connectTokenType,
userId: workspaceId ? undefined : auth.userId,
workspaceId,
});
const info = (msg: string) => {
@@ -376,15 +419,21 @@ async function runConnect(options: ConnectOptions, isDaemonChild: boolean) {
updateStatus('reconnecting');
});
// Proactive token refresh — schedule before JWT expires
const startProactiveRefresh = () =>
// Proactive token refresh — schedule before the connect token expires. For a
// workspace device `refreshConnectToken` re-mints the workspace token; for a
// personal device it refreshes the user token. Scheduling watches the actual
// connect token, so the workspace token's shorter life is respected.
const startProactiveRefresh = (): (() => void) | null =>
scheduleProactiveRefresh(
auth,
(refreshed) => {
client.updateToken(refreshed.token);
auth = refreshed;
// Schedule next refresh based on the new token
cancelRefreshTimer = startProactiveRefresh();
connectToken,
connectTokenType,
async () => {
const newToken = await refreshConnectToken();
if (newToken) {
client.updateToken(newToken);
cancelRefreshTimer = startProactiveRefresh();
}
return newToken;
},
info,
error,
@@ -395,15 +444,15 @@ async function runConnect(options: ConnectOptions, isDaemonChild: boolean) {
// (e.g., auto-reconnect may send an expired JWT before proactive refresh fires)
let authFailedRefreshAttempted = false;
client.on('auth_failed', async (reason) => {
if (auth.tokenType === 'jwt' && !authFailedRefreshAttempted) {
if (connectTokenType === 'jwt' && !authFailedRefreshAttempted) {
authFailedRefreshAttempted = true;
info(`Authentication failed (${reason}). Attempting token refresh...`);
try {
const refreshed = await resolveToken({});
if (refreshed && refreshed.token !== auth.token) {
const prev = connectToken;
const newToken = await refreshConnectToken();
if (newToken && newToken !== prev) {
info('Token refreshed successfully. Reconnecting...');
client.updateToken(refreshed.token);
auth = refreshed;
client.updateToken(newToken);
authFailedRefreshAttempted = false;
cancelRefreshTimer = startProactiveRefresh();
await client.reconnect();
@@ -424,7 +473,7 @@ async function runConnect(options: ConnectOptions, isDaemonChild: boolean) {
// Handle auth expired — refresh token and reconnect automatically
client.on('auth_expired', async () => {
if (auth.tokenType === 'apiKey') {
if (connectTokenType === 'apiKey') {
// API keys don't expire; ignore stale auth_expired signals
return;
}
@@ -432,11 +481,10 @@ async function runConnect(options: ConnectOptions, isDaemonChild: boolean) {
info('Authentication expired. Attempting to refresh token...');
try {
const refreshed = await resolveToken({});
if (refreshed) {
const newToken = await refreshConnectToken();
if (newToken) {
info('Token refreshed successfully. Reconnecting...');
client.updateToken(refreshed.token);
auth = refreshed;
client.updateToken(newToken);
cancelRefreshTimer = startProactiveRefresh();
await client.reconnect();
return;
@@ -486,7 +534,8 @@ async function runConnect(options: ConnectOptions, isDaemonChild: boolean) {
try {
// Reuse the already-resolved auth (respects `--token` mode) so we don't
// re-discover creds and exit when none are found.
await registerDevice(auth, identity);
if (workspaceId) await registerWorkspaceDevice(auth, identity, workspaceId);
else await registerDevice(auth, identity);
} catch (err) {
error(`Device registration failed (non-fatal): ${(err as Error).message}`);
}
@@ -534,47 +583,49 @@ function parseJwtExp(token: string): number | undefined {
}
/**
* Schedule a proactive token refresh before the JWT expires.
* Returns a cleanup function that cancels the scheduled timer.
* Schedule a proactive token refresh before the (connect) token expires.
* `refresh` performs the actual refresh — re-minting a workspace token or
* refreshing the user token — and returns the new token. Returns a cleanup
* function that cancels the scheduled timer.
*/
function scheduleProactiveRefresh(
auth: { token: string; tokenType: string },
onRefreshed: (newAuth: Awaited<ReturnType<typeof resolveToken>>) => void,
token: string,
tokenType: string,
refresh: () => Promise<string | undefined>,
info: (msg: string) => void,
error: (msg: string) => void,
): (() => void) | null {
if (auth.tokenType !== 'jwt') return null;
if (tokenType !== 'jwt') return null;
const exp = parseJwtExp(auth.token);
const exp = parseJwtExp(token);
if (!exp) return null;
const refreshAt = (exp - PROACTIVE_REFRESH_BUFFER) * 1000;
const delay = refreshAt - Date.now();
if (delay < 0) {
// Already past the refresh window — refresh immediately on next tick
const lifetimeMs = exp * 1000 - Date.now();
if (lifetimeMs <= 0) {
// Token already expired — refresh once on next tick.
void doRefresh();
return null;
}
// Refresh ahead of expiry, but never let the buffer meet or exceed the token's
// remaining lifetime: a buffer >= lifetime collapses the refresh window to <=0
// and busy-loops re-minting (e.g. a 1h token with a 1h buffer). Cap the buffer
// at half the remaining lifetime so a short-lived token refreshes about once per
// half-life instead of spinning.
const bufferMs = Math.min(PROACTIVE_REFRESH_BUFFER * 1000, lifetimeMs / 2);
const delay = lifetimeMs - bufferMs;
const timer = setTimeout(() => void doRefresh(), delay);
return () => clearTimeout(timer);
async function doRefresh() {
try {
// Use the same buffer so getValidToken actually triggers a refresh
const result = await getValidToken(PROACTIVE_REFRESH_BUFFER);
if (!result) {
const newToken = await refresh();
if (!newToken) {
error('Proactive token refresh failed — no valid credentials.');
return;
}
const refreshed = await resolveToken({});
// Only notify if the token actually changed to avoid reschedule loops
if (refreshed.token !== auth.token) {
info('Proactively refreshed token.');
onRefreshed(refreshed);
}
if (newToken !== token) info('Proactively refreshed token.');
} catch {
error('Proactive token refresh failed.');
}
+42
View File
@@ -88,3 +88,45 @@ describe('verify rubric config commands', () => {
expect(printed).toContain('4');
});
});
describe('verify evidence upload command', () => {
let exitSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
mockGetTrpcClient.mockReset();
exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code?: number) => {
throw new Error(`process.exit ${code}`);
}) as any);
});
afterEach(() => {
exitSpy.mockRestore();
});
const run = async (args: string[]) => {
const program = new Command();
program.exitOverride();
registerVerifyCommand(program);
await program.parseAsync(['node', 'lh', 'verify', ...args]);
};
it('rejects evidence with both file and inline content', async () => {
await expect(
run([
'evidence',
'upload',
'--check',
'result-1',
'--type',
'text',
'--file',
'artifact.txt',
'--content',
'inline payload',
]),
).rejects.toThrow('process.exit 1');
expect(exitSpy).toHaveBeenCalledWith(1);
expect(mockGetTrpcClient).not.toHaveBeenCalled();
});
});
+434 -7
View File
@@ -1,9 +1,13 @@
import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';
import type { Command } from 'commander';
import pc from 'picocolors';
import { getTrpcClient } from '../api/client';
import { confirm, outputJson, printTable, timeAgo, truncate } from '../utils/format';
import { log } from '../utils/logger';
import { uploadLocalFile } from '../utils/uploadLocalFile';
// ── Helpers ────────────────────────────────────────────────
@@ -32,6 +36,36 @@ function assertEnum<T extends string>(value: T | undefined, allowed: T[], flag:
}
}
type Verdict = 'failed' | 'passed' | 'uncertain';
type EvidenceType = 'dom_snapshot' | 'gif' | 'screenshot' | 'text' | 'transcript' | 'video';
/** Map a free-form case/summary result token onto the verify verdict vocabulary. */
function toVerdict(raw: unknown): Verdict {
const s = String(raw ?? '').toLowerCase();
if (['pass', 'passed', 'ok', 'success'].includes(s)) return 'passed';
if (['fail', 'failed', 'error'].includes(s)) return 'failed';
return 'uncertain'; // partial / blocked / skipped / pending / unknown
}
/** Pick an evidence medium from a file extension. */
function evidenceTypeForFile(file: string): EvidenceType {
const ext = path.extname(file).toLowerCase().slice(1);
if (ext === 'gif') return 'gif';
if (['png', 'jpg', 'jpeg', 'webp', 'svg', 'bmp'].includes(ext)) return 'screenshot';
if (['mp4', 'webm', 'mov', 'm4v'].includes(ext)) return 'video';
if (['html', 'htm'].includes(ext)) return 'dom_snapshot';
return 'text';
}
/** Normalize a case's `evidence` field (string | string[] | {path}[]) to path strings. */
function evidencePaths(evidence: unknown): string[] {
if (!evidence) return [];
const arr = Array.isArray(evidence) ? evidence : [evidence];
return arr
.map((e) => (typeof e === 'string' ? e : (e?.path ?? e?.file)))
.filter((p): p is string => typeof p === 'string' && p.length > 0);
}
// ── Command Registration ───────────────────────────────────
export function registerVerifyCommand(program: Command) {
@@ -368,9 +402,9 @@ export function registerVerifyCommand(program: Command) {
console.log(`${pc.green('✓')} Skipped verification for run ${pc.bold(operationId)}`);
});
// ════════════ run / results ════════════
// ════════════ execute (agent path) ════════════
verify
.command('run <operationId>')
.command('execute <operationId>')
.description('Execute the confirmed plan against a deliverable (LLM judge)')
.requiredOption('--goal <goal>', "The run's task")
.requiredOption('--deliverable <text>', 'The output to judge')
@@ -406,13 +440,147 @@ export function registerVerifyCommand(program: Command) {
},
);
verify
.command('results <operationId>')
.description('List check results for a run')
// ════════════ run (verification session entity) ════════════
const run = verify.command('run').description('Verification sessions (verify_runs)');
run
.command('create')
.description('Create a standalone verification session')
.option('--source <source>', 'agent | agent-testing', 'agent-testing')
.option('--operation <id>', 'Link to an existing Agent Run')
.option('--title <title>', 'Session title')
.option('--goal <goal>', 'Goal/task being verified')
.option('--json [fields]', 'Output JSON')
.action(async (operationId: string, options: { json?: boolean | string }) => {
.action(
async (options: {
goal?: string;
json?: boolean | string;
operation?: string;
source?: string;
title?: string;
}) => {
const client = await getTrpcClient();
const created = await client.verify.createRun.mutate({
goal: options.goal,
operationId: options.operation,
source: options.source as any,
title: options.title,
});
if (options.json !== undefined) {
outputJson(created, typeof options.json === 'string' ? options.json : undefined);
return;
}
console.log(`${pc.green('✓')} Created run ${pc.bold(created.id)}`);
},
);
run
.command('list')
.description('List recent verification sessions')
.option('--json [fields]', 'Output JSON')
.action(async (options: { json?: boolean | string }) => {
const client = await getTrpcClient();
const results = await client.verify.listResults.query({ operationId });
const runs = await client.verify.listRuns.query();
if (options.json !== undefined) {
outputJson(runs, typeof options.json === 'string' ? options.json : undefined);
return;
}
if (runs.length === 0) return void console.log('No runs found.');
printTable(
runs.map((r: any) => [
r.id,
truncate(r.title || '', 40),
r.source,
r.status ?? '',
r.operationId ? 'agent' : 'standalone',
r.createdAt ? timeAgo(r.createdAt) : '',
]),
['ID', 'TITLE', 'SOURCE', 'STATUS', 'KIND', 'CREATED'],
);
});
run
.command('get <runId>')
.description('Show a verification session')
.option('--json [fields]', 'Output JSON')
.action(async (runId: string, options: { json?: boolean | string }) => {
const client = await getTrpcClient();
const item = await client.verify.getRun.query({ verifyRunId: runId });
if (options.json !== undefined) {
outputJson(item, typeof options.json === 'string' ? options.json : undefined);
return;
}
if (!item) return void console.log('Run not found.');
console.log(JSON.stringify(item, null, 2));
});
// ════════════ result (check result entity) ════════════
const result = verify.command('result').description('Check results (verify_check_results)');
result
.command('ingest')
.description('Upsert one check result by (run, checkItemId) from a supplied verdict')
.requiredOption('--run <verifyRunId>', 'Target session id')
.requiredOption('--check <checkItemId>', 'Stable check item id within the session')
.requiredOption('--verdict <verdict>', 'passed|failed|uncertain')
.option('--title <title>', 'Check title')
.option('--index <n>', 'Display index')
.option('--confidence <n>', '0-1 confidence')
.option('--status <status>', 'pending|running|passed|failed|skipped (derived from verdict)')
.option('--evidence <text>', 'Key observation (stored as Toulmin evidence)')
.option('--suggestion <text>', 'Remediation hint')
.option('--soft', 'Non-blocking (required=false); defaults to blocking')
.option('--json [fields]', 'Output JSON')
.action(
async (options: {
check: string;
confidence?: string;
evidence?: string;
index?: string;
json?: boolean | string;
run: string;
soft?: boolean;
status?: string;
suggestion?: string;
title?: string;
verdict: string;
}) => {
const client = await getTrpcClient();
const created = await client.verify.ingestResult.mutate({
checkItemId: options.check,
checkItemIndex: options.index ? Number.parseInt(options.index, 10) : undefined,
checkItemTitle: options.title,
confidence: options.confidence ? Number.parseFloat(options.confidence) : undefined,
required: options.soft ? false : undefined,
status: options.status as any,
suggestion: options.suggestion,
toulmin: options.evidence ? { evidence: options.evidence } : undefined,
verdict: options.verdict as any,
verifyRunId: options.run,
});
if (options.json !== undefined) {
outputJson(created, typeof options.json === 'string' ? options.json : undefined);
return;
}
console.log(`${pc.green('✓')} Result ${pc.bold(created.id)} (${created.verdict})`);
},
);
result
.command('list')
.description('List check results — by session (--run) or by Agent Run (--operation)')
.option('--run <verifyRunId>', 'List by verification session')
.option('--operation <operationId>', 'List by Agent Run')
.option('--json [fields]', 'Output JSON')
.action(async (options: { json?: boolean | string; operation?: string; run?: string }) => {
if (!options.run && !options.operation) {
log.error('Provide either --run or --operation');
process.exit(1);
}
const client = await getTrpcClient();
const results = options.run
? await client.verify.listResultsByRun.query({ verifyRunId: options.run })
: await client.verify.listResults.query({ operationId: options.operation! });
if (options.json !== undefined) {
outputJson(results, typeof options.json === 'string' ? options.json : undefined);
return;
@@ -421,6 +589,143 @@ export function registerVerifyCommand(program: Command) {
printResults(results);
});
// ════════════ evidence (artifact entity) ════════════
const evidence = verify.command('evidence').description('Evidence artifacts (verify_evidence)');
evidence
.command('upload')
.description('Attach an evidence artifact (file or inline text) to a check result')
.requiredOption('--check <checkResultId>', 'Target check result id')
.requiredOption('--type <type>', 'screenshot|gif|video|text|dom_snapshot|transcript')
.option('--file <path>', 'Local file to upload as the artifact')
.option('--content <text>', 'Inline text payload (instead of a file)')
.option('--by <capturedBy>', 'agent-browser|cdp|cli|program|llm_judge', 'cli')
.option('--desc <text>', 'Human-readable caption')
.option('--json [fields]', 'Output JSON')
.action(
async (options: {
by?: string;
check: string;
content?: string;
desc?: string;
file?: string;
json?: boolean | string;
type: string;
}) => {
if (Boolean(options.file) === Boolean(options.content)) {
log.error('Provide exactly one of --file or --content');
process.exit(1);
}
const client = await getTrpcClient();
let fileId: string | undefined;
if (options.file) {
const uploaded = await uploadLocalFile(client, options.file);
fileId = uploaded.id;
}
const ev = await client.verify.uploadEvidence.mutate({
capturedBy: options.by as any,
checkResultId: options.check,
content: options.content,
description: options.desc,
fileId,
type: options.type as any,
});
if (options.json !== undefined) {
outputJson(ev, typeof options.json === 'string' ? options.json : undefined);
return;
}
console.log(
`${pc.green('✓')} Evidence ${pc.bold(ev.id)}${fileId ? ` (file ${fileId})` : ''}`,
);
},
);
evidence
.command('list <checkResultId>')
.description('List evidence for a check result')
.option('--json [fields]', 'Output JSON')
.action(async (checkResultId: string, options: { json?: boolean | string }) => {
const client = await getTrpcClient();
const rows = await client.verify.listEvidence.query({ checkResultId });
if (options.json !== undefined) {
outputJson(rows, typeof options.json === 'string' ? options.json : undefined);
return;
}
if (rows.length === 0) return void console.log('No evidence.');
printTable(
rows.map((e: any) => [
e.id,
e.type,
e.capturedBy ?? '',
e.fileId ? 'file' : 'inline',
truncate(e.description || '', 40),
]),
['ID', 'TYPE', 'BY', 'PAYLOAD', 'DESC'],
);
});
// ════════════ report (narrative entity) ════════════
const report = verify.command('report').description('Verification reports (verify_reports)');
report
.command('upsert')
.description('Write (overwrite) the report for a session')
.requiredOption('--run <verifyRunId>', 'Target session id')
.option('--verdict <verdict>', 'passed|failed|uncertain')
.option('--summary <text>', 'Short summary')
.option('--content <markdown>', 'Full markdown body')
.option('--total <n>', 'Total checks')
.option('--passed <n>', 'Passed checks')
.option('--failed <n>', 'Failed checks')
.option('--uncertain <n>', 'Uncertain checks')
.option('--json [fields]', 'Output JSON')
.action(
async (options: {
content?: string;
failed?: string;
json?: boolean | string;
passed?: string;
run: string;
summary?: string;
total?: string;
uncertain?: string;
verdict?: string;
}) => {
const num = (s?: string) => (s === undefined ? undefined : Number.parseInt(s, 10));
const client = await getTrpcClient();
const created = await client.verify.upsertReport.mutate({
content: options.content,
failedChecks: num(options.failed),
passedChecks: num(options.passed),
summary: options.summary,
totalChecks: num(options.total),
uncertainChecks: num(options.uncertain),
verdict: options.verdict as any,
verifyRunId: options.run,
});
if (options.json !== undefined) {
outputJson(created, typeof options.json === 'string' ? options.json : undefined);
return;
}
console.log(`${pc.green('✓')} Report ${pc.bold(created.id)} (${created.verdict ?? '—'})`);
},
);
report
.command('get <runId>')
.description('Show the report for a session')
.option('--json [fields]', 'Output JSON')
.action(async (runId: string, options: { json?: boolean | string }) => {
const client = await getTrpcClient();
const item = await client.verify.getReport.query({ verifyRunId: runId });
if (options.json !== undefined) {
outputJson(item, typeof options.json === 'string' ? options.json : undefined);
return;
}
if (!item) return void console.log('No report.');
console.log(JSON.stringify(item, null, 2));
});
// ════════════ feedback ════════════
verify
.command('decision <resultId> <decision>')
@@ -431,6 +736,128 @@ export function registerVerifyCommand(program: Command) {
await client.verify.submitDecision.mutate({ decision, resultId });
console.log(`${pc.green('✓')} Recorded ${pc.bold(decision)} on result ${pc.bold(resultId)}`);
});
// ════════════ ingest (aggregate convenience over the atomic commands) ════════════
verify
.command('ingest-report <reportDir>')
.description(
'Ingest a local agent-testing report (result.json + report.md + assets) as a verify session',
)
.option('--source <source>', 'agent | agent-testing', 'agent-testing')
.option('--operation <id>', 'Link the session to an existing Agent Run')
.option('--title <title>', 'Override the session title')
.option('--goal <goal>', 'The goal/task being verified')
.option('--open', 'Print the in-app URL to open the report')
.option('--json [fields]', 'Output JSON')
.action(
async (
reportDir: string,
options: {
goal?: string;
json?: boolean | string;
open?: boolean;
operation?: string;
source?: string;
title?: string;
},
) => {
const dir = path.resolve(reportDir);
const resultPath = path.join(dir, 'result.json');
if (!existsSync(resultPath)) {
log.error(`result.json not found in ${dir}`);
process.exit(1);
}
let result: any;
try {
result = JSON.parse(readFileSync(resultPath, 'utf8'));
} catch {
log.error('result.json is not valid JSON');
process.exit(1);
}
const cases: any[] = Array.isArray(result.cases) ? result.cases : [];
const summary = result.summary ?? {};
const reportMdPath = path.join(dir, 'report.md');
const content = existsSync(reportMdPath) ? readFileSync(reportMdPath, 'utf8') : undefined;
const client = await getTrpcClient();
// 1. Create the verification session.
const run = await client.verify.createRun.mutate({
goal: options.goal,
operationId: options.operation,
source: options.source as any,
title: options.title ?? result.title,
});
// 2. Ingest each case as a check result + its evidence.
let uploaded = 0;
for (const [index, c] of cases.entries()) {
const checkItemId = String(c.id ?? c.checkItemId ?? `case-${index + 1}`);
const verdict = toVerdict(c.result ?? c.status ?? c.verdict);
const observation = c.keyObservation ?? c.observation ?? c.note;
const checkResult = await client.verify.ingestResult.mutate({
checkItemId,
checkItemIndex: index,
checkItemTitle: c.name ?? c.case ?? c.title ?? checkItemId,
required: c.required ?? true,
// The case's key observation is recorded as Toulmin evidence; a real
// remediation hint (if the report provides one) goes to `suggestion`.
suggestion: typeof c.suggestion === 'string' ? c.suggestion : undefined,
toulmin: typeof observation === 'string' ? { evidence: observation } : undefined,
verdict,
verifierType: 'agent',
verifyRunId: run.id,
});
for (const rel of evidencePaths(c.evidence)) {
const abs = path.isAbsolute(rel) ? rel : path.join(dir, rel);
if (!existsSync(abs)) {
log.warn(`evidence not found, skipping: ${rel}`);
continue;
}
const file = await uploadLocalFile(client, abs);
await client.verify.uploadEvidence.mutate({
capturedBy: 'cli',
checkResultId: checkResult.id,
description: c.name ?? path.basename(abs),
fileId: file.id,
type: evidenceTypeForFile(abs),
});
uploaded += 1;
}
}
// 3. Write the report (full markdown + stats snapshot).
await client.verify.upsertReport.mutate({
content,
failedChecks: summary.failed,
passedChecks: summary.passed,
summary: typeof summary.note === 'string' ? summary.note : undefined,
totalChecks: summary.total ?? cases.length,
uncertainChecks: (summary.blocked ?? 0) + (summary.uncertain ?? 0) || undefined,
verdict: summary.verdict ? toVerdict(summary.verdict) : undefined,
verifyRunId: run.id,
});
if (options.json !== undefined) {
outputJson(
{ cases: cases.length, evidence: uploaded, verifyRunId: run.id },
typeof options.json === 'string' ? options.json : undefined,
);
return;
}
console.log(
`${pc.green('✓')} Ingested ${pc.bold(String(cases.length))} case(s), ${pc.bold(String(uploaded))} evidence file(s)`,
);
console.log(`${pc.bold('verifyRunId')}: ${run.id}`);
if (options.open) {
console.log(`${pc.bold('open')}: /verify/${run.id}`);
}
},
);
}
function printResults(results: any[]): void {
+5
View File
@@ -10,6 +10,11 @@ export interface TaskEntry {
startedAt: string;
taskId: string;
topicId: string;
/**
* Workspace that owns the dispatched topic. Persisted so the cancel-time
* notify still scopes to the right workspace after the daemon restarts.
*/
workspaceId?: string;
}
function getRegistryPath(): string {
+42
View File
@@ -38,3 +38,45 @@ export async function registerDevice(
platform: process.platform,
});
}
type Auth = { serverUrl: string; token: string; tokenType: 'apiKey' | 'jwt' | 'serviceToken' };
/**
* Identity for a WORKSPACE device: derived from the workspaceId (namespaced) so
* the same physical machine enrolled into a workspace is a distinct device from
* its personal identity, and stable across reconnects.
*/
export function resolveWorkspaceDeviceIdentity(
workspaceId: string,
explicitDeviceId?: string,
): DeviceIdentity {
if (explicitDeviceId) return { deviceId: explicitDeviceId, identitySource: 'fallback' };
return deriveDeviceId(`workspace:${workspaceId}`);
}
/**
* Mint a workspace-device connect token (owner-only on the server). The returned
* token carries the `workspace_id` claim the gateway routes by.
*/
export async function mintWorkspaceConnectToken(
auth: Auth,
workspaceId: string,
): Promise<{ token: string; workspaceId: string }> {
const trpc = createLambdaClient(auth, workspaceId);
return trpc.device.mintWorkspaceConnectToken.mutate();
}
/** Register this machine as a device of the given workspace (owner-only). */
export async function registerWorkspaceDevice(
auth: Auth,
identity: DeviceIdentity,
workspaceId: string,
): Promise<void> {
const trpc = createLambdaClient(auth, workspaceId);
await trpc.device.registerWorkspaceDevice.mutate({
deviceId: identity.deviceId,
hostname: os.hostname(),
identitySource: identity.identitySource,
platform: process.platform,
});
}
@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { getTrpcClient } from '../../api/client';
import { removeTask, saveTask } from '../../daemon/taskRegistry';
import { runHeteroTask } from '../heteroTask';
@@ -34,6 +35,8 @@ vi.mock('../../api/client', () => ({
}),
}));
const getTrpcClientMock = vi.mocked(getTrpcClient);
vi.mock('../../utils/logger', () => ({
log: { error: vi.fn(), info: vi.fn(), warn: vi.fn() },
}));
@@ -248,4 +251,56 @@ describe('runHeteroTask (openclaw)', () => {
expect(removeTask).toHaveBeenCalledWith('task-1');
killSpy.mockRestore();
});
it('threads workspaceId into the saved task entry and the spawned child env', async () => {
const child = makeMockChild(6666);
spawnMock.mockReturnValue(child);
await runHeteroTask({
agentId: 'agent-ws',
agentType: 'openclaw',
operationId: 'op-ws',
prompt: 'workspace dispatch',
taskId: 'task-ws',
topicId: 'topic-ws',
workspaceId: 'ws-42',
});
expect(saveTask).toHaveBeenCalledWith(expect.objectContaining({ workspaceId: 'ws-42' }));
const [, , spawnOpts] = spawnMock.mock.calls[0] as [
string,
string[],
{ env: NodeJS.ProcessEnv },
];
expect(spawnOpts.env.LOBEHUB_WORKSPACE_ID).toBe('ws-42');
});
it('passes workspaceId to getTrpcClient when the close handler auto-notifies', async () => {
const child = makeMockChild(7777);
spawnMock.mockReturnValue(child);
await runHeteroTask({
agentId: 'agent-ws',
agentType: 'openclaw',
operationId: 'op-ws-2',
prompt: 'ws prompt',
taskId: 'task-ws-2',
topicId: 'topic-ws-2',
workspaceId: 'ws-99',
});
getTrpcClientMock.mockClear();
// Abnormal exit triggers sendAutoNotify + sendDoneSignal — both must scope
// to the dispatching workspace or agentNotify resolves the topic in
// personal mode and 404s.
child._emit('close', 1, null);
// Await microtask drain so the close-handler promise chain settles.
await new Promise((r) => setImmediate(r));
expect(getTrpcClientMock.mock.calls.length).toBeGreaterThan(0);
for (const call of getTrpcClientMock.mock.calls) {
expect(call[0]).toBe('ws-99');
}
});
});
+35 -14
View File
@@ -57,6 +57,13 @@ export interface RunHeteroTaskParams {
prompt: string;
taskId: string;
topicId: string;
/**
* Workspace id seeded by the server when the dispatched topic lives in a
* workspace. Threaded into auto-notify calls (as `X-Workspace-Id`) and into
* the spawned child's `LOBEHUB_WORKSPACE_ID` env so its own `lh notify`
* shells inherit the same scope.
*/
workspaceId?: string;
}
export interface CancelHeteroTaskParams {
@@ -69,9 +76,10 @@ async function sendAutoNotify(
taskId: string,
text: string,
agentId?: string,
workspaceId?: string,
): Promise<void> {
try {
const client = await getTrpcClient();
const client = await getTrpcClient(workspaceId);
await client.agentNotify.notify.mutate({
agentId,
content: text,
@@ -90,9 +98,13 @@ async function sendAutoNotify(
* `sendAutoNotify` which writes an error message AND triggers completion via
* the `done` flag.
*/
async function sendDoneSignal(topicId: string, agentId?: string): Promise<void> {
async function sendDoneSignal(
topicId: string,
agentId?: string,
workspaceId?: string,
): Promise<void> {
try {
const client = await getTrpcClient();
const client = await getTrpcClient(workspaceId);
await client.agentNotify.notify.mutate({
agentId,
content: '',
@@ -138,9 +150,15 @@ function buildNotifyProtocol(lhPath: string, topicId: string): string {
}
export async function runHeteroTask(params: RunHeteroTaskParams): Promise<string> {
const { agentId, agentType, cwd, operationId, prompt, taskId, topicId } = params;
const { agentId, agentType, cwd, operationId, prompt, taskId, topicId, workspaceId } = params;
const workDir = cwd || process.cwd();
const lhPath = resolveLhPath();
// Propagate workspace scope into the spawned child so its own `lh notify`
// invocations (and any grandchildren it shells out) inherit the same scope
// via getTrpcClient → resolveWorkspaceId.
const childEnv: NodeJS.ProcessEnv = workspaceId
? { ...process.env, LOBEHUB_WORKSPACE_ID: workspaceId }
: { ...process.env };
if (agentType === 'openclaw') {
// openclaw agent --local is one-shot: each invocation processes one message and exits.
@@ -182,7 +200,7 @@ export async function runHeteroTask(params: RunHeteroTaskParams): Promise<string
{
cwd: workDir,
detached: true,
env: { ...process.env },
env: childEnv,
stdio: 'ignore',
},
);
@@ -201,6 +219,7 @@ export async function runHeteroTask(params: RunHeteroTaskParams): Promise<string
startedAt: new Date().toISOString(),
taskId,
topicId,
workspaceId,
});
log.info(`OpenClaw task started: taskId=${taskId} pid=${pid} agent=${openclawAgent}`);
@@ -216,12 +235,12 @@ export async function runHeteroTask(params: RunHeteroTaskParams): Promise<string
: `Task failed (exit code: ${code})`;
// Send error message first, THEN signal done (sequential).
// Fire-and-forget both, but ensure done is always sent even if notify fails.
void sendAutoNotify(topicId, taskId, text, agentId).finally(() =>
sendDoneSignal(topicId, agentId),
void sendAutoNotify(topicId, taskId, text, agentId, workspaceId).finally(() =>
sendDoneSignal(topicId, agentId, workspaceId),
);
} else {
// Clean exit — openclaw already sent its final message; just signal done.
void sendDoneSignal(topicId, agentId);
void sendDoneSignal(topicId, agentId, workspaceId);
}
});
@@ -253,7 +272,7 @@ export async function runHeteroTask(params: RunHeteroTaskParams): Promise<string
const child = spawn('hermes', hermesArgs, {
cwd: workDir,
detached: true,
env: { ...process.env },
env: childEnv,
stdio: ['ignore', 'pipe', 'ignore'],
});
@@ -269,6 +288,7 @@ export async function runHeteroTask(params: RunHeteroTaskParams): Promise<string
startedAt: new Date().toISOString(),
taskId,
topicId,
workspaceId,
});
log.info(`Hermes task started: taskId=${taskId} pid=${pid}`);
@@ -284,8 +304,8 @@ export async function runHeteroTask(params: RunHeteroTaskParams): Promise<string
const text = signal
? `Task cancelled (signal: ${signal})`
: `Task failed (exit code: ${code})`;
void sendAutoNotify(topicId, taskId, text, agentId).finally(() =>
sendDoneSignal(topicId, agentId),
void sendAutoNotify(topicId, taskId, text, agentId, workspaceId).finally(() =>
sendDoneSignal(topicId, agentId, workspaceId),
);
return;
}
@@ -298,11 +318,11 @@ export async function runHeteroTask(params: RunHeteroTaskParams): Promise<string
if (sessionId) saveHermesSessionId(topicId, sessionId);
if (response) {
void sendAutoNotify(topicId, taskId, response, agentId).finally(() =>
sendDoneSignal(topicId, agentId),
void sendAutoNotify(topicId, taskId, response, agentId, workspaceId).finally(() =>
sendDoneSignal(topicId, agentId, workspaceId),
);
} else {
void sendDoneSignal(topicId, agentId);
void sendDoneSignal(topicId, agentId, workspaceId);
}
});
@@ -334,6 +354,7 @@ export async function cancelHeteroTask(params: CancelHeteroTaskParams): Promise<
taskId,
'Task already completed or cancelled',
entry.agentId,
entry.workspaceId,
);
}
@@ -77,6 +77,12 @@ interface PlatformTaskEntry {
operationId: string;
pid: number;
topicId: string;
/**
* Workspace that owns the dispatched topic — used at exit time so the
* cleanup notify still scopes to the workspace agentNotify resolves the
* topic in (the server seeds this via the `runHeteroTask` args).
*/
workspaceId?: string;
}
/**
@@ -524,6 +530,7 @@ export default class GatewayConnectionCtr extends ControllerModule {
prompt: string;
taskId: string;
topicId: string;
workspaceId?: string;
},
);
return { content: json, state: safeJsonParse(json), success: true };
@@ -765,8 +772,9 @@ export default class GatewayConnectionCtr extends ControllerModule {
prompt: string;
taskId: string;
topicId: string;
workspaceId?: string;
}): Promise<string> {
const { agentId, agentType, cwd, operationId, prompt, taskId, topicId } = args;
const { agentId, agentType, cwd, operationId, prompt, taskId, topicId, workspaceId } = args;
const workDir = cwd || process.cwd();
const [serverUrl, accessToken] = await Promise.all([
@@ -774,11 +782,15 @@ export default class GatewayConnectionCtr extends ControllerModule {
this.remoteServerConfigCtr.getAccessToken(),
]);
// Inject auth into child env so `lh notify` can authenticate without CLI config.
// Inject auth + workspace scope into child env so `lh notify` can
// authenticate AND target the same workspace as the dispatched topic
// (without LOBEHUB_WORKSPACE_ID, the CLI's notify falls back to personal
// mode and the workspace topic 404s).
const childEnv: NodeJS.ProcessEnv = {
...process.env,
...(accessToken && { LOBEHUB_JWT: accessToken }),
...(serverUrl && { LOBEHUB_SERVER: serverUrl }),
...(workspaceId && { LOBEHUB_WORKSPACE_ID: workspaceId }),
};
if (agentType === 'openclaw') {
@@ -823,7 +835,14 @@ export default class GatewayConnectionCtr extends ControllerModule {
if (pid === undefined) throw new Error('Failed to get PID for openclaw process');
child.unref();
this.platformTasks.set(taskId, { agentId, agentType, operationId, pid, topicId });
this.platformTasks.set(taskId, {
agentId,
agentType,
operationId,
pid,
topicId,
workspaceId,
});
child.on('close', (code, signal) => {
this.platformTasks.delete(taskId);
@@ -831,11 +850,31 @@ export default class GatewayConnectionCtr extends ControllerModule {
const text = signal
? `Task cancelled (signal: ${signal})`
: `Task failed (exit code: ${code})`;
void this.sendNotify({ agentId, content: text, role: 'assistant', topicId }).finally(() =>
this.sendNotify({ agentId, content: '', done: true, role: 'assistant', topicId }),
void this.sendNotify({
agentId,
content: text,
role: 'assistant',
topicId,
workspaceId,
}).finally(() =>
this.sendNotify({
agentId,
content: '',
done: true,
role: 'assistant',
topicId,
workspaceId,
}),
);
} else {
void this.sendNotify({ agentId, content: '', done: true, role: 'assistant', topicId });
void this.sendNotify({
agentId,
content: '',
done: true,
role: 'assistant',
topicId,
workspaceId,
});
}
});
@@ -874,7 +913,14 @@ export default class GatewayConnectionCtr extends ControllerModule {
if (pid === undefined) throw new Error('Failed to get PID for hermes process');
child.unref();
this.platformTasks.set(taskId, { agentId, agentType, operationId, pid, topicId });
this.platformTasks.set(taskId, {
agentId,
agentType,
operationId,
pid,
topicId,
workspaceId,
});
let stdout = '';
child.stdout.on('data', (chunk: Buffer) => {
@@ -888,8 +934,21 @@ export default class GatewayConnectionCtr extends ControllerModule {
const text = signal
? `Task cancelled (signal: ${signal})`
: `Task failed (exit code: ${code})`;
void this.sendNotify({ agentId, content: text, role: 'assistant', topicId }).finally(() =>
this.sendNotify({ agentId, content: '', done: true, role: 'assistant', topicId }),
void this.sendNotify({
agentId,
content: text,
role: 'assistant',
topicId,
workspaceId,
}).finally(() =>
this.sendNotify({
agentId,
content: '',
done: true,
role: 'assistant',
topicId,
workspaceId,
}),
);
return;
}
@@ -902,11 +961,31 @@ export default class GatewayConnectionCtr extends ControllerModule {
if (sessionId) this.hermesSessionMap.set(topicId, sessionId);
if (response) {
void this.sendNotify({ agentId, content: response, role: 'assistant', topicId }).finally(
() => this.sendNotify({ agentId, content: '', done: true, role: 'assistant', topicId }),
void this.sendNotify({
agentId,
content: response,
role: 'assistant',
topicId,
workspaceId,
}).finally(() =>
this.sendNotify({
agentId,
content: '',
done: true,
role: 'assistant',
topicId,
workspaceId,
}),
);
} else {
void this.sendNotify({ agentId, content: '', done: true, role: 'assistant', topicId });
void this.sendNotify({
agentId,
content: '',
done: true,
role: 'assistant',
topicId,
workspaceId,
});
}
});
@@ -934,6 +1013,7 @@ export default class GatewayConnectionCtr extends ControllerModule {
content: 'Task already completed or cancelled',
role: 'assistant',
topicId: entry.topicId,
workspaceId: entry.workspaceId,
});
}
@@ -951,6 +1031,12 @@ export default class GatewayConnectionCtr extends ControllerModule {
done?: boolean;
role: string;
topicId: string;
/**
* Workspace scope for the notify. When set, attaches `X-Workspace-Id` so
* agentNotify resolves the workspace-owned topic instead of falling back
* to personal mode (which would 404 the lookup).
*/
workspaceId?: string;
}): Promise<void> {
try {
const [serverUrl, token] = await Promise.all([
@@ -959,12 +1045,16 @@ export default class GatewayConnectionCtr extends ControllerModule {
]);
if (!serverUrl || !token) return;
const { workspaceId, ...body } = params;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'Oidc-Auth': token,
};
if (workspaceId) headers['X-Workspace-Id'] = workspaceId;
await fetch(`${serverUrl}/trpc/lambda/agentNotify.notify`, {
body: JSON.stringify({ json: params }),
headers: {
'Content-Type': 'application/json',
'Oidc-Auth': token,
},
body: JSON.stringify({ json: body }),
headers,
method: 'POST',
});
} catch {
+5
View File
@@ -3,6 +3,11 @@ import './pre-app-init';
import fixPath from 'fix-path';
import { App } from './core/App';
import { installProcessErrorHandlers } from './process-error-handlers';
// Guard the main process against transient network blips (Wi-Fi/VPN switch,
// system sleep) emitted by Electron's net stack as uncaught exceptions.
installProcessErrorHandlers();
const app = new App();
@@ -0,0 +1,83 @@
import { createLogger } from '@/utils/logger';
const logger = createLogger('main:process-error-handlers');
/**
* Transient Chromium network errors emitted by Electron's `net` stack
* (`SimpleURLLoaderWrapper`). These happen during normal operation — switching
* Wi-Fi / VPN, the machine sleeping, the network interface dropping — and are
* NOT application bugs. Electron emits them as an `error` event on the internal
* loader; when nothing is listening they bubble up as an `uncaughtException`
* and pop the "A JavaScript error occurred in the main process" dialog, even
* though the request layer already handles the failure via promise rejection.
*
* We swallow these specific cases so transient connectivity blips never crash
* the main process. Everything else is re-thrown to preserve normal crash
* visibility.
*
* @see https://github.com/electron/electron/issues/24948
*/
const TRANSIENT_NET_ERROR_CODES = new Set([
'ERR_NETWORK_CHANGED',
'ERR_NETWORK_IO_SUSPENDED',
'ERR_INTERNET_DISCONNECTED',
'ERR_NETWORK_ACCESS_DENIED',
'ERR_CONNECTION_RESET',
'ERR_CONNECTION_ABORTED',
'ERR_CONNECTION_CLOSED',
'ERR_NAME_NOT_RESOLVED',
'ERR_TIMED_OUT',
]);
const isTransientNetError = (error: unknown): boolean => {
if (!error) return false;
const message = error instanceof Error ? error.message : String(error);
// Electron net errors are formatted as `net::ERR_XXX`.
const match = message.match(/net::(ERR_[A-Z_]+)/);
if (match && TRANSIENT_NET_ERROR_CODES.has(match[1])) return true;
// Belt-and-suspenders: these only ever originate from the net loader.
const stack = error instanceof Error ? (error.stack ?? '') : '';
return /net::ERR_/.test(message) && stack.includes('SimpleURLLoaderWrapper');
};
/**
* Install global guards for the Electron main process. Must be called as early
* as possible (before the rest of the app boots) so it catches errors from any
* module's top-level / async work.
*/
export const installProcessErrorHandlers = () => {
process.on('uncaughtException', (error) => {
if (isTransientNetError(error)) {
logger.warn('Ignoring transient network error in main process:', error.message);
return;
}
// Re-throw so genuine bugs still surface as a crash instead of being
// silently swallowed by this handler.
logger.error('Uncaught exception in main process:', error);
throw error;
});
process.on('unhandledRejection', (reason) => {
if (isTransientNetError(reason)) {
logger.warn(
'Ignoring transient network rejection in main process:',
reason instanceof Error ? reason.message : String(reason),
);
return;
}
// Installing this listener overrides Node's default
// `--unhandled-rejections=throw`, so we must re-throw to preserve the fatal
// behavior. Throwing here surfaces as an uncaughtException (handled above,
// which also re-throws non-transient errors), instead of leaving the app
// partially booted on a genuine failure (e.g. an unawaited app.bootstrap()).
logger.error('Unhandled rejection in main process:', reason);
throw reason;
});
logger.info('Process error handlers installed');
};
@@ -0,0 +1,252 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { verifyRouter } from '@/server/routers/lambda/verify';
import { FileService } from '@/server/services/file';
const modelMocks = vi.hoisted(() => ({
findRunById: vi.fn(),
findResultById: vi.fn(),
getFullFileUrl: vi.fn(),
getServerDB: vi.fn(async () => ({})),
upsertByCheckItem: vi.fn(),
}));
vi.mock('@/database/core/db-adaptor', () => ({
getServerDB: modelMocks.getServerDB,
}));
vi.mock('@/database/models/verifyCheckResult', () => ({
VerifyCheckResultModel: vi.fn(() => ({
findById: modelMocks.findResultById,
upsertByCheckItem: modelMocks.upsertByCheckItem,
})),
}));
vi.mock('@/database/models/verifyRun', () => ({
VerifyRunModel: vi.fn(() => ({
findById: modelMocks.findRunById,
})),
}));
vi.mock('@/server/services/verify', () => ({
VerifyExecutorService: class VerifyExecutorService {},
VerifyFeedbackService: class VerifyFeedbackService {},
VerifyPlanGeneratorService: class VerifyPlanGeneratorService {},
}));
vi.mock('@/server/services/file', () => ({
FileService: vi.fn(() => ({
getFullFileUrl: modelMocks.getFullFileUrl,
})),
}));
const createCaller = () => verifyRouter.createCaller({ userId: 'verify-router-test-user' } as any);
const createPublicCaller = () => verifyRouter.createCaller({} as any);
const selectRows = <T>(rows: T[]) => ({
from: vi.fn(() => ({
where: vi.fn(() => ({
orderBy: vi.fn(async () => rows),
})),
})),
});
describe('verifyRouter', () => {
beforeEach(() => {
vi.clearAllMocks();
modelMocks.getServerDB.mockResolvedValue({});
vi.mocked(FileService).mockImplementation(
() =>
({
getFullFileUrl: modelMocks.getFullFileUrl,
}) as any,
);
});
describe('ingestResult', () => {
it("rejects a run outside the caller's scope before upserting the result", async () => {
modelMocks.findRunById.mockResolvedValueOnce(undefined);
await expect(
createCaller().ingestResult({
checkItemId: 'shared-check',
checkItemTitle: 'attacker update',
status: 'passed',
verdict: 'passed',
verifyRunId: 'other-user-run',
}),
).rejects.toThrow('Verification run not found');
expect(modelMocks.findRunById).toHaveBeenCalledWith('other-user-run');
expect(modelMocks.upsertByCheckItem).not.toHaveBeenCalled();
});
});
describe('uploadEvidence', () => {
it('rejects evidence with both inline content and fileId', async () => {
await expect(
createCaller().uploadEvidence({
checkResultId: 'result-1',
content: 'inline payload',
fileId: 'files-1',
type: 'text',
}),
).rejects.toThrow('Provide exactly one of `content` or `fileId`.');
});
it('rejects evidence without inline content or fileId', async () => {
await expect(
createCaller().uploadEvidence({
checkResultId: 'result-1',
type: 'text',
}),
).rejects.toThrow('Provide exactly one of `content` or `fileId`.');
});
});
describe('getReportBundle', () => {
it('reads a standalone report without a logged-in user', async () => {
const run = {
goal: 'Ship a working page',
id: 'run-1',
title: 'Run report',
userId: 'owner-user',
workspaceId: null,
};
const report = {
id: 'report-1',
totalChecks: 1,
verdict: 'passed',
verifyRunId: 'run-1',
};
const result = {
checkItemId: 'check-1',
checkItemIndex: 0,
checkItemTitle: 'Page renders',
id: 'result-1',
required: true,
status: 'passed',
verdict: 'passed',
verifyRunId: 'run-1',
};
const evidence = {
checkResultId: 'result-1',
content: null,
description: 'Homepage screenshot',
fileId: 'file-1',
id: 'evidence-1',
type: 'screenshot',
};
const serverDB = {
query: {
files: {
findFirst: vi.fn(async () => ({ id: 'file-1', url: 'verify/evidence.png' })),
},
verifyReports: {
findFirst: vi.fn(async () => report),
},
verifyRuns: {
findFirst: vi.fn(async () => run),
},
},
select: vi
.fn()
.mockReturnValueOnce(selectRows([result]))
.mockReturnValueOnce(selectRows([evidence])),
};
modelMocks.getServerDB.mockResolvedValue(serverDB);
modelMocks.getFullFileUrl.mockResolvedValue('https://cdn.example.com/verify/evidence.png');
const bundle = await createPublicCaller().getReportBundle({ verifyRunId: 'run-1' });
expect(bundle).toMatchObject({
report,
results: [
{
checkItemId: 'check-1',
evidence: [
{
fileId: 'file-1',
fileUrl: 'https://cdn.example.com/verify/evidence.png',
},
],
},
],
run,
});
expect(modelMocks.findRunById).not.toHaveBeenCalled();
});
it('keeps returning the bundle when file URL resolution is unavailable', async () => {
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
vi.mocked(FileService).mockImplementation(() => {
throw new Error('S3 env missing');
});
const run = {
goal: 'Ship a working page',
id: 'run-1',
title: 'Run report',
userId: 'owner-user',
workspaceId: null,
};
const result = {
checkItemId: 'check-1',
checkItemIndex: 0,
checkItemTitle: 'Page renders',
id: 'result-1',
required: true,
status: 'passed',
verdict: 'passed',
verifyRunId: 'run-1',
};
const evidence = {
checkResultId: 'result-1',
content: null,
description: 'Homepage screenshot',
fileId: 'file-1',
id: 'evidence-1',
type: 'screenshot',
};
const serverDB = {
query: {
files: {
findFirst: vi.fn(async () => ({ id: 'file-1', url: 'verify/evidence.png' })),
},
verifyReports: {
findFirst: vi.fn(async () => null),
},
verifyRuns: {
findFirst: vi.fn(async () => run),
},
},
select: vi
.fn()
.mockReturnValueOnce(selectRows([result]))
.mockReturnValueOnce(selectRows([evidence])),
};
modelMocks.getServerDB.mockResolvedValue(serverDB);
const bundle = await createPublicCaller().getReportBundle({ verifyRunId: 'run-1' });
expect(bundle).toMatchObject({
results: [
{
evidence: [
{
fileId: 'file-1',
fileUrl: null,
},
],
},
],
run,
});
expect(consoleErrorSpy).toHaveBeenCalledWith(
'[verify:getReportBundle:resolveFileUrl]',
expect.any(Error),
);
consoleErrorSpy.mockRestore();
});
});
});
+198 -67
View File
@@ -1,11 +1,16 @@
import { REMOTE_HETEROGENEOUS_AGENT_CONFIGS } from '@lobechat/heterogeneous-agents';
import type { DeviceChannel, DeviceListItem, WorkingDirEntry } from '@lobechat/types';
import type { DeviceChannel, DeviceListItem, DeviceScope, WorkingDirEntry } from '@lobechat/types';
import { z } from 'zod';
import {
wsCompatProcedure,
wsOwnerProcedure,
} from '@/business/server/trpc-middlewares/workspaceAuth';
import { DeviceModel } from '@/database/models/device';
import { authedProcedure, router } from '@/libs/trpc/lambda';
import { router } from '@/libs/trpc/lambda';
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
import { deviceGateway } from '@/server/services/deviceGateway';
import { signWorkspaceDeviceToken } from '@/libs/trpc/utils/internalJwt';
import { type DeviceAttachment, deviceGateway } from '@/server/services/deviceGateway';
import { preserveWorkspaceCache } from './deviceWorkingDirs';
import { assertWorkspaceRootApproved } from './deviceWorkspaceGuard';
@@ -22,11 +27,19 @@ const remotePlatformEnum = z.enum(
const CAPABILITY_TIMEOUT_MS = 5_000;
const PROFILE_TIMEOUT_MS = 5_000;
const deviceProcedure = authedProcedure.use(serverDatabase).use(async (opts) => {
// Workspace-aware (compat): with an `X-Workspace-Id` header the device list also
// surfaces the workspace's shared devices; without it, the personal path is
// unchanged (`ctx.workspaceId === undefined`).
const deviceProcedure = wsCompatProcedure.use(serverDatabase).use(async (opts) => {
const { ctx } = opts;
const wsId = ctx.workspaceId ?? undefined;
return opts.next({
ctx: { deviceModel: new DeviceModel(ctx.serverDB, ctx.userId), userId: ctx.userId },
ctx: {
deviceModel: new DeviceModel(ctx.serverDB, ctx.userId, wsId),
userId: ctx.userId,
workspaceId: wsId,
},
});
});
@@ -62,7 +75,7 @@ export const deviceRouter = router({
)
.query(async ({ ctx, input }) => {
const result = await deviceGateway.executeToolCall(
{ deviceId: input.deviceId, userId: ctx.userId },
{ deviceId: input.deviceId, userId: ctx.userId, workspaceId: ctx.workspaceId },
{
apiName: 'checkPlatformCapability',
arguments: JSON.stringify({ platform: input.platform }),
@@ -99,6 +112,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? null;
}),
@@ -111,6 +125,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? null;
}),
@@ -122,6 +137,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? null;
}),
@@ -133,6 +149,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? null;
}),
@@ -149,6 +166,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? [];
}),
@@ -170,6 +188,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? [];
}),
@@ -194,6 +213,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
}),
),
@@ -217,6 +237,7 @@ export const deviceRouter = router({
path: input.path,
to: input.to,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
}),
),
@@ -238,6 +259,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
}),
),
@@ -252,6 +274,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
}),
),
@@ -266,6 +289,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
}),
),
@@ -281,6 +305,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? null;
}),
@@ -297,6 +322,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? null;
}),
@@ -312,6 +338,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? [];
}),
@@ -328,6 +355,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? null;
}),
@@ -344,6 +372,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
scope: input.scope,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? null;
}),
@@ -365,6 +394,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
workingDirectory: input.workingDirectory,
});
}),
@@ -381,6 +411,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
scope: input.scope,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? null;
}),
@@ -397,6 +428,7 @@ export const deviceRouter = router({
filePath: input.filePath,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
}),
),
@@ -415,6 +447,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
items: input.items,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
workingDirectory: input.workingDirectory,
});
}),
@@ -436,6 +469,7 @@ export const deviceRouter = router({
newName: input.newName,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
workingDirectory: input.workingDirectory,
});
}),
@@ -457,6 +491,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
workingDirectory: input.workingDirectory,
});
}),
@@ -474,6 +509,7 @@ export const deviceRouter = router({
deviceId: input.deviceId,
path: input.path,
userId: ctx.userId,
workspaceId: ctx.workspaceId,
});
return result ?? null;
}),
@@ -492,7 +528,7 @@ export const deviceRouter = router({
)
.query(async ({ ctx, input }) => {
const result = await deviceGateway.executeToolCall(
{ deviceId: input.deviceId, userId: ctx.userId },
{ deviceId: input.deviceId, userId: ctx.userId, workspaceId: ctx.workspaceId },
{
apiName: 'getAgentProfile',
arguments: JSON.stringify({ platform: input.platform }),
@@ -517,7 +553,7 @@ export const deviceRouter = router({
getDeviceSystemInfo: deviceProcedure
.input(z.object({ deviceId: z.string() }))
.query(async ({ ctx, input }) => {
return deviceGateway.queryDeviceSystemInfo(ctx.userId, input.deviceId);
return deviceGateway.queryDeviceSystemInfo(ctx.userId, input.deviceId, ctx.workspaceId);
}),
/**
@@ -533,76 +569,171 @@ export const deviceRouter = router({
* a currently-reachable device during rollout.
*/
listDevices: deviceProcedure.query(async ({ ctx }): Promise<DeviceListItem[]> => {
const [registered, onlineList] = await Promise.all([
ctx.deviceModel.query(),
const wsId = ctx.workspaceId;
// Personal devices resolve under the user principal; workspace devices under
// the `workspace:<id>` principal (a separate gateway pool). Fetch both.
const [personalRows, workspaceRows, personalOnline, workspaceOnline] = await Promise.all([
ctx.deviceModel.queryPersonal(),
wsId ? ctx.deviceModel.queryWorkspaceDevices() : Promise.resolve([]),
deviceGateway.queryDeviceList(ctx.userId),
wsId ? deviceGateway.queryDeviceList(ctx.userId, wsId) : Promise.resolve([]),
]);
// The gateway already groups by device, exposing live sessions as nested
// `channels`. Flatten them into the UI-facing channel shape; fall back to a
// single synthetic channel for a legacy gateway that omits the field.
const channelsByDevice = new Map<string, DeviceChannel[]>();
for (const conn of onlineList) {
const channels: DeviceChannel[] =
conn.channels && conn.channels.length > 0
? conn.channels.map((c) => ({
channel: c.channel ?? null,
connectedAt: c.connectedAt,
// `channels`. Flatten one connection into the UI-facing channel shape; fall
// back to a single synthetic channel for a legacy gateway that omits the field.
const toChannels = (conn: DeviceAttachment): DeviceChannel[] =>
conn.channels && conn.channels.length > 0
? conn.channels.map((c) => ({
channel: c.channel ?? null,
connectedAt: c.connectedAt,
hostname: conn.hostname ?? null,
platform: conn.platform ?? null,
}))
: [
{
channel: null,
connectedAt: conn.lastSeen,
hostname: conn.hostname ?? null,
platform: conn.platform ?? null,
}))
: [
{
channel: null,
connectedAt: conn.lastSeen,
hostname: conn.hostname ?? null,
platform: conn.platform ?? null,
},
];
channelsByDevice.set(conn.deviceId, channels);
}
},
];
const seen = new Set<string>();
// Merge a DB-registered set with its live gateway pool into the UI shape.
// `scope` tags the group; deviceIds never collide across pools (a personal id
// is derived from userId, a workspace id from workspaceId).
const buildItems = (
rows: Awaited<ReturnType<typeof ctx.deviceModel.queryPersonal>>,
onlineList: DeviceAttachment[],
scope: DeviceScope,
): DeviceListItem[] => {
const channelsByDevice = new Map<string, DeviceChannel[]>();
for (const conn of onlineList) channelsByDevice.set(conn.deviceId, toChannels(conn));
const fromDb = registered.map((d) => {
seen.add(d.deviceId);
const channels = channelsByDevice.get(d.deviceId) ?? [];
const live = channels[0];
return {
channels,
defaultCwd: d.defaultCwd,
deviceId: d.deviceId,
friendlyName: d.friendlyName,
hostname: d.hostname ?? live?.hostname ?? null,
identitySource: d.identitySource,
lastSeen: d.lastSeenAt.toISOString(),
online: channels.length > 0,
platform: d.platform ?? live?.platform ?? null,
registered: true,
workingDirs: d.workingDirs ?? [],
};
});
const seen = new Set<string>();
const fromDb = rows.map((d): DeviceListItem => {
seen.add(d.deviceId);
const channels = channelsByDevice.get(d.deviceId) ?? [];
const live = channels[0];
return {
channels,
defaultCwd: d.defaultCwd,
deviceId: d.deviceId,
friendlyName: d.friendlyName,
hostname: d.hostname ?? live?.hostname ?? null,
identitySource: d.identitySource,
lastSeen: d.lastSeenAt.toISOString(),
online: channels.length > 0,
platform: d.platform ?? live?.platform ?? null,
registered: true,
scope,
workingDirs: d.workingDirs ?? [],
};
});
// Online but not yet persisted — transient until the client auto-registers.
const ghosts = [...channelsByDevice.entries()]
.filter(([deviceId]) => !seen.has(deviceId))
.map(([deviceId, channels]) => ({
channels,
defaultCwd: null,
deviceId,
friendlyName: null,
hostname: channels[0]?.hostname ?? null,
identitySource: null,
lastSeen: channels[0]?.connectedAt ?? new Date().toISOString(),
online: true,
platform: channels[0]?.platform ?? null,
registered: false,
workingDirs: [] as WorkingDirEntry[],
}));
// Online but not yet persisted — transient until the client auto-registers.
const ghosts = [...channelsByDevice.entries()]
.filter(([deviceId]) => !seen.has(deviceId))
.map(
([deviceId, channels]): DeviceListItem => ({
channels,
defaultCwd: null,
deviceId,
friendlyName: null,
hostname: channels[0]?.hostname ?? null,
identitySource: null,
lastSeen: channels[0]?.connectedAt ?? new Date().toISOString(),
online: true,
platform: channels[0]?.platform ?? null,
registered: false,
scope,
workingDirs: [] as WorkingDirEntry[],
}),
);
return [...fromDb, ...ghosts];
return [...fromDb, ...ghosts];
};
return [
...buildItems(personalRows, personalOnline, 'personal'),
...buildItems(workspaceRows, workspaceOnline, 'workspace'),
];
}),
/**
* Mint a short-lived connect token for enrolling a WORKSPACE-owned device.
* Owner-only (`wsOwnerProcedure`) — the server verifies the caller is an admin
* of the workspace, then signs a token carrying the `workspace_id` claim that
* the device gateway trusts to route the device to the `workspace:<id>`
* principal. The CLI (`lh connect --workspace`) / settings page use this.
*/
mintWorkspaceConnectToken: wsOwnerProcedure.mutation(async ({ ctx }) => {
const token = await signWorkspaceDeviceToken(ctx.workspaceId);
return { token, workspaceId: ctx.workspaceId };
}),
/**
* Enroll the calling machine as a device of the current workspace. Owner-only;
* stamps `workspace_id` so the row belongs to the workspace. Used by
* `lh connect --workspace` after minting the connect token.
*/
registerWorkspaceDevice: wsOwnerProcedure
.use(serverDatabase)
.input(
z.object({
deviceId: z.string().min(1).max(64),
hostname: z.string().nullable().optional(),
identitySource: z.enum(['machine-id', 'fallback']),
platform: z.string().max(20).nullable().optional(),
}),
)
.mutation(async ({ ctx, input }) => {
const model = new DeviceModel(ctx.serverDB, ctx.userId, ctx.workspaceId);
return model.registerWorkspaceDevice({ ...input, workspaceId: ctx.workspaceId });
}),
/**
* Rename / set working dirs of a WORKSPACE device — scoped by `workspace_id`,
* owner-gated, so any workspace owner can manage it (not just the enroller).
* Mirrors {@link deviceRouter.updateDevice} but for the workspace pool.
*/
updateWorkspaceDevice: wsOwnerProcedure
.use(serverDatabase)
.input(
z.object({
defaultCwd: z.string().nullable().optional(),
deviceId: z.string(),
friendlyName: z.string().max(100).nullable().optional(),
workingDirs: z
.array(z.object({ path: z.string(), repoType: z.enum(['git', 'github']).optional() }))
.max(20)
.optional(),
}),
)
.mutation(async ({ ctx, input }) => {
const model = new DeviceModel(ctx.serverDB, ctx.userId, ctx.workspaceId);
const { deviceId, workingDirs, ...value } = input;
const nextWorkingDirs = workingDirs
? preserveWorkspaceCache(
workingDirs,
(await model.findWorkspaceDeviceById(deviceId))?.workingDirs ?? [],
)
: undefined;
await model.updateWorkspaceDevice(deviceId, { ...value, workingDirs: nextWorkingDirs });
return { success: true };
}),
/** Remove a WORKSPACE device — scoped by `workspace_id`, owner-gated. */
removeWorkspaceDevice: wsOwnerProcedure
.use(serverDatabase)
.input(z.object({ deviceId: z.string() }))
.mutation(async ({ ctx, input }) => {
const model = new DeviceModel(ctx.serverDB, ctx.userId, ctx.workspaceId);
await model.deleteWorkspaceDevice(input.deviceId);
return { success: true };
}),
/**
* Auto-register the calling device (desktop after OIDC login / CLI on first
* `lh connect`). Upserts on (userId, deviceId); user-owned fields are
@@ -629,7 +760,7 @@ export const deviceRouter = router({
}),
status: deviceProcedure.query(async ({ ctx }) => {
return deviceGateway.queryDeviceStatus(ctx.userId);
return deviceGateway.queryDeviceStatus(ctx.userId, ctx.workspaceId);
}),
/** User-editable fields only — never the machine-reported identity columns. */
+276 -1
View File
@@ -1,14 +1,26 @@
import { TRPCError } from '@trpc/server';
import { asc, eq } from 'drizzle-orm';
import { z } from 'zod';
import { wsCompatProcedure } from '@/business/server/trpc-middlewares/workspaceAuth';
import { AgentOperationModel } from '@/database/models/agentOperation';
import { FileModel } from '@/database/models/file';
import { LlmGenerationTracingModel } from '@/database/models/llmGenerationTracing';
import { VerifyCheckResultModel } from '@/database/models/verifyCheckResult';
import { VerifyCriterionModel } from '@/database/models/verifyCriterion';
import { VerifyEvidenceModel } from '@/database/models/verifyEvidence';
import { VerifyReportModel } from '@/database/models/verifyReport';
import { VerifyRubricModel } from '@/database/models/verifyRubric';
import { VerifyRunModel } from '@/database/models/verifyRun';
import { router } from '@/libs/trpc/lambda';
import {
verifyCheckResults,
verifyEvidence,
verifyReports,
verifyRuns,
} from '@/database/schemas/verify';
import { publicProcedure, router } from '@/libs/trpc/lambda';
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
import { FileService } from '@/server/services/file';
import {
VerifyExecutorService,
VerifyFeedbackService,
@@ -19,6 +31,28 @@ const verifierTypeSchema = z.enum(['program', 'agent', 'llm']);
const onFailSchema = z.enum(['manual', 'auto_repair']);
const decisionSchema = z.enum(['accepted', 'rejected', 'overridden']);
const modelConfigSchema = z.object({ model: z.string(), provider: z.string() });
const verdictSchema = z.enum(['passed', 'failed', 'uncertain']);
const checkStatusSchema = z.enum(['pending', 'running', 'passed', 'failed', 'skipped']);
const runSourceSchema = z.enum(['agent', 'agent-testing']);
const evidenceTypeSchema = z.enum([
'screenshot',
'gif',
'video',
'text',
'dom_snapshot',
'transcript',
]);
const evidenceCapturedBySchema = z.enum(['agent-browser', 'cdp', 'cli', 'program', 'llm_judge']);
const toulminSchema = z.object({
counterEvidence: z.string().optional(),
evidence: z.string().optional(),
limitation: z.string().optional(),
reasoning: z.string().optional(),
});
/** Derive the lifecycle status from a verdict when the caller doesn't pin one. */
const statusForVerdict = (verdict: 'passed' | 'failed' | 'uncertain') =>
verdict === 'passed' ? ('passed' as const) : ('failed' as const);
/** Run-policy knobs persisted on a rubric (see VerifyRubricConfig). */
const rubricConfigSchema = z.object({
@@ -37,17 +71,35 @@ const checkItemSchema = z.object({
verifierType: verifierTypeSchema,
});
const verifyRunIdInputSchema = z.object({ verifyRunId: z.string() });
const uploadEvidenceInputSchema = z
.object({
capturedBy: evidenceCapturedBySchema.optional(),
// Exactly one of `content` (inline text) or `fileId` (already-uploaded artifact).
checkResultId: z.string(),
content: z.string().min(1).optional(),
description: z.string().optional(),
fileId: z.string().min(1).optional(),
type: evidenceTypeSchema,
})
.refine((data) => Boolean(data.content) !== Boolean(data.fileId), {
message: 'Provide exactly one of `content` or `fileId`.',
});
const verifyProcedure = wsCompatProcedure.use(serverDatabase).use(async (opts) => {
const { ctx } = opts;
const workspaceId = ctx.workspaceId ?? undefined;
return opts.next({
ctx: {
criterionModel: new VerifyCriterionModel(ctx.serverDB, ctx.userId, workspaceId),
evidenceModel: new VerifyEvidenceModel(ctx.serverDB, ctx.userId, workspaceId),
executorService: new VerifyExecutorService(ctx.serverDB, ctx.userId, workspaceId),
tracingModel: new LlmGenerationTracingModel(ctx.serverDB, ctx.userId, workspaceId),
feedbackService: new VerifyFeedbackService(ctx.serverDB, ctx.userId, workspaceId),
operationModel: new AgentOperationModel(ctx.serverDB, ctx.userId, workspaceId),
planGenerator: new VerifyPlanGeneratorService(ctx.serverDB, ctx.userId, workspaceId),
reportModel: new VerifyReportModel(ctx.serverDB, ctx.userId, workspaceId),
resultModel: new VerifyCheckResultModel(ctx.serverDB, ctx.userId, workspaceId),
rubricModel: new VerifyRubricModel(ctx.serverDB, ctx.userId, workspaceId),
runModel: new VerifyRunModel(ctx.serverDB, ctx.userId, workspaceId),
@@ -55,6 +107,31 @@ const verifyProcedure = wsCompatProcedure.use(serverDatabase).use(async (opts) =
});
});
const publicVerifyReportProcedure = publicProcedure.use(serverDatabase);
const resolveVerifyRun = async (ctx: { runModel: VerifyRunModel }, verifyRunId: string) => {
const run = await ctx.runModel.findById(verifyRunId);
if (!run) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'Verification run not found' });
}
return run;
};
const resolveCheckResult = async (
ctx: { resultModel: VerifyCheckResultModel },
checkResultId: string,
) => {
const result = await ctx.resultModel.findById(checkResultId);
if (!result) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'Verification check result not found' });
}
return result;
};
export const verifyRouter = router({
// ---- criteria (reusable atomic standards) ----
createCriterion: verifyProcedure
@@ -239,4 +316,202 @@ export const verifyRouter = router({
.mutation(async ({ ctx, input }) =>
ctx.feedbackService.submitDecision(input.resultId, input.decision),
),
// ---- ingest (standalone sessions: results / evidence / report, e.g. agent-testing) ----
// A verification session that isn't a live Agent Run (no executor): an external
// harness creates the run, ingests each check's verdict + evidence, and writes a
// report — all keyed by verifyRunId.
createRun: verifyProcedure
.input(
z.object({
goal: z.string().optional(),
operationId: z.string().optional(),
source: runSourceSchema.optional(),
title: z.string().optional(),
}),
)
.mutation(async ({ ctx, input }) =>
ctx.runModel.create({
goal: input.goal,
operationId: input.operationId,
source: input.source ?? 'agent-testing',
title: input.title,
}),
),
getRun: verifyProcedure
.input(verifyRunIdInputSchema)
.query(async ({ ctx, input }) => ctx.runModel.findById(input.verifyRunId)),
listRuns: verifyProcedure.query(async ({ ctx }) => ctx.runModel.query()),
listResultsByRun: verifyProcedure.input(verifyRunIdInputSchema).query(async ({ ctx, input }) => {
const run = await resolveVerifyRun(ctx, input.verifyRunId);
return ctx.resultModel.listByRun(run.id);
}),
ingestResult: verifyProcedure
.input(
z.object({
checkItemId: z.string(),
checkItemIndex: z.number().optional(),
checkItemTitle: z.string().optional(),
confidence: z.number().min(0).max(1).optional(),
required: z.boolean().optional(),
status: checkStatusSchema.optional(),
suggestion: z.string().optional(),
toulmin: toulminSchema.optional(),
verdict: verdictSchema,
verifierType: verifierTypeSchema.optional(),
verifyRunId: z.string(),
}),
)
.mutation(async ({ ctx, input }) => {
const run = await resolveVerifyRun(ctx, input.verifyRunId);
return ctx.resultModel.upsertByCheckItem({
checkItemId: input.checkItemId,
checkItemIndex: input.checkItemIndex,
checkItemTitle: input.checkItemTitle,
completedAt: new Date(),
confidence: input.confidence,
required: input.required ?? true,
status: input.status ?? statusForVerdict(input.verdict),
suggestion: input.suggestion,
toulmin: input.toulmin,
verdict: input.verdict,
verifierType: input.verifierType ?? 'agent',
verifyRunId: run.id,
});
}),
uploadEvidence: verifyProcedure
.input(uploadEvidenceInputSchema)
.mutation(async ({ ctx, input }) => {
const result = await resolveCheckResult(ctx, input.checkResultId);
return ctx.evidenceModel.create({
capturedAt: new Date(),
capturedBy: input.capturedBy ?? null,
checkResultId: result.id,
content: input.content ?? null,
description: input.description ?? null,
fileId: input.fileId ?? null,
type: input.type,
});
}),
listEvidence: verifyProcedure
.input(z.object({ checkResultId: z.string() }))
.query(async ({ ctx, input }) => {
const result = await resolveCheckResult(ctx, input.checkResultId);
return ctx.evidenceModel.listByCheckResult(result.id);
}),
upsertReport: verifyProcedure
.input(
z.object({
content: z.string().optional(),
failedChecks: z.number().optional(),
generatedBy: z.string().optional(),
overallConfidence: z.number().min(0).max(1).optional(),
passedChecks: z.number().optional(),
summary: z.string().optional(),
totalChecks: z.number().optional(),
uncertainChecks: z.number().optional(),
verdict: verdictSchema.optional(),
verifyRunId: z.string(),
}),
)
.mutation(async ({ ctx, input }) => {
const run = await resolveVerifyRun(ctx, input.verifyRunId);
return ctx.reportModel.upsertByRun({
content: input.content ?? null,
failedChecks: input.failedChecks ?? null,
generatedBy: input.generatedBy ?? 'agent-testing',
overallConfidence: input.overallConfidence ?? null,
passedChecks: input.passedChecks ?? null,
summary: input.summary ?? null,
totalChecks: input.totalChecks ?? null,
uncertainChecks: input.uncertainChecks ?? null,
verdict: input.verdict ?? null,
verifyRunId: run.id,
});
}),
getReport: verifyProcedure.input(verifyRunIdInputSchema).query(async ({ ctx, input }) => {
const run = await resolveVerifyRun(ctx, input.verifyRunId);
return ctx.reportModel.findByRun(run.id);
}),
/**
* One-shot payload for the standalone report viewer: the session, its report,
* and every check result with its evidence — addressed purely by verifyRunId
* (no operation / chat context required).
*/
getReportBundle: publicVerifyReportProcedure
.input(verifyRunIdInputSchema)
.query(async ({ ctx, input }) => {
const run = await ctx.serverDB.query.verifyRuns.findFirst({
where: eq(verifyRuns.id, input.verifyRunId),
});
if (!run) return null;
const [report, results] = await Promise.all([
ctx.serverDB.query.verifyReports.findFirst({
where: eq(verifyReports.verifyRunId, input.verifyRunId),
}),
ctx.serverDB
.select()
.from(verifyCheckResults)
.where(eq(verifyCheckResults.verifyRunId, input.verifyRunId))
.orderBy(asc(verifyCheckResults.checkItemIndex)),
]);
// Resolve a displayable URL for each file-backed evidence artifact.
let fileService: FileService | null | undefined;
const getFileService = () => {
if (fileService !== undefined) return fileService;
try {
fileService = new FileService(ctx.serverDB, run.userId, run.workspaceId ?? undefined);
} catch (error) {
console.error('[verify:getReportBundle:resolveFileUrl]', error);
fileService = null;
}
return fileService;
};
const resolveFileUrl = async (fileId: string | null) => {
if (!fileId) return null;
try {
const file = await FileModel.getFileById(ctx.serverDB, fileId);
if (!file?.url) return null;
const service = getFileService();
return service ? await service.getFullFileUrl(file.url) : null;
} catch (error) {
console.error('[verify:getReportBundle:resolveFileUrl]', error);
return null;
}
};
const resultsWithEvidence = await Promise.all(
results.map(async (r) => {
const evidence = await ctx.serverDB
.select()
.from(verifyEvidence)
.where(eq(verifyEvidence.checkResultId, r.id))
.orderBy(asc(verifyEvidence.createdAt));
return {
...r,
evidence: await Promise.all(
evidence.map(async (e) => ({ ...e, fileUrl: await resolveFileUrl(e.fileId) })),
),
};
}),
);
return { report: report ?? null, results: resultsWithEvidence, run };
}),
});
+76 -10
View File
@@ -371,6 +371,24 @@ export class AiAgentService {
return task?.id;
}
/**
* If `deviceId` is a device enrolled into the caller's current workspace,
* return that workspaceId so device-gateway calls route to the
* `workspace:<id>` principal. Returns undefined for a personal device (or no
* workspace context), keeping the personal path byte-identical.
*/
private async resolveDeviceWorkspaceId(
deviceId: string | undefined,
): Promise<string | undefined> {
if (!deviceId || !this.workspaceId) return undefined;
const row = await new DeviceModel(
this.db,
this.userId,
this.workspaceId,
).findWorkspaceDeviceById(deviceId);
return row ? this.workspaceId : undefined;
}
/**
* Resolve the "workspace init" scan (project skills + AGENTS.md) for a run
* bound to a device's project directory. Reads the cache on
@@ -392,10 +410,24 @@ export class AiAgentService {
if (!activeDeviceId) return { workspace: empty };
try {
const deviceModel = new DeviceModel(this.db, this.userId);
const device = await deviceModel.findByDeviceId(activeDeviceId);
// The active device may be personal (userId-scoped) or workspace-owned
// (workspace-scoped) — look up both pools so the bound cwd, project
// skills, and AGENTS/CLAUDE instructions still resolve for a workspace
// device. Mirrors the dispatch-side lookup (see `deviceModelForCwd`).
const deviceModel = new DeviceModel(this.db, this.userId, this.workspaceId);
const personalDevice = await deviceModel.findByDeviceId(activeDeviceId);
const workspaceDevice = personalDevice
? undefined
: await deviceModel.findWorkspaceDeviceById(activeDeviceId);
const device = personalDevice ?? workspaceDevice;
if (!device) return { workspace: empty };
// For a workspace-owned device, route the gateway RPC to the
// `workspace:<id>` principal and persist the scan via the workspace
// update path — otherwise the scan goes through the personal pool
// (empty result) and the writeback misses the row.
const deviceWorkspaceId = workspaceDevice ? this.workspaceId : undefined;
// The bound project root we scan — resolved via the shared precedence
// helper so it cannot drift from hetero dispatch / topic backfill. Read
// from the persisted `device.defaultCwd` (not a live device query, which
@@ -423,6 +455,7 @@ export class AiAgentService {
deviceId: activeDeviceId,
scope: boundCwd,
userId: this.userId,
workspaceId: deviceWorkspaceId,
});
if (!scanned) {
// Scan failed (offline mid-run / parse error). Fall back to a stale
@@ -435,9 +468,15 @@ export class AiAgentService {
}
// Persist the fresh scan back onto `workingDirs` (update in place or prepend
// a new MRU entry), keeping the JSONB payload bounded.
// a new MRU entry), keeping the JSONB payload bounded. Workspace devices
// are owned by the workspace, not a userId — use the workspace-scoped
// update path so the writeback actually lands.
const updated = upsertWorkspaceScan(workingDirs, boundCwd, scanned, Date.now());
await deviceModel.update(activeDeviceId, { workingDirs: updated });
if (deviceWorkspaceId) {
await deviceModel.updateWorkspaceDevice(activeDeviceId, { workingDirs: updated });
} else {
await deviceModel.update(activeDeviceId, { workingDirs: updated });
}
log('execAgent: scanned and cached workspace init for %s', boundCwd);
return { boundCwd, workspace: scanned };
@@ -1401,8 +1440,9 @@ export class AiAgentService {
// lh connect only handles tool_call_request (not agent_run_request),
// so we use executeToolCall with the runHeteroTask tool instead of dispatchAgentRun.
const remoteDeviceWorkspaceId = await this.resolveDeviceWorkspaceId(remoteDeviceId);
const result = await deviceGateway.executeToolCall(
{ deviceId: remoteDeviceId, userId: this.userId },
{ deviceId: remoteDeviceId, userId: this.userId, workspaceId: remoteDeviceWorkspaceId },
{
apiName: 'runHeteroTask',
arguments: JSON.stringify({
@@ -1413,6 +1453,11 @@ export class AiAgentService {
prompt,
taskId: operationId,
topicId,
// Scope notify callbacks to the same workspace as the dispatched
// topic so agentNotify can resolve the workspace-owned topic.
// Without this the device's notify call falls back to personal
// mode and TopicModel.findById returns NOT_FOUND.
workspaceId: remoteDeviceWorkspaceId,
}),
identifier: 'runHeteroTask',
},
@@ -1510,9 +1555,13 @@ export class AiAgentService {
// wins, else the device's user-configured defaultCwd. The device row
// lives in the DB (the gateway only knows live connections), so read
// it directly rather than via deviceGateway.
const boundDevice = await new DeviceModel(this.db, this.userId).findByDeviceId(
dispatchDeviceId,
);
// The bound device may be personal (userId-scoped) or a workspace
// device (workspace-scoped) — look up both so its defaultCwd resolves.
const deviceModelForCwd = new DeviceModel(this.db, this.userId, this.workspaceId);
const boundDevice =
(await deviceModelForCwd.findByDeviceId(dispatchDeviceId)) ??
(await deviceModelForCwd.findWorkspaceDeviceById(dispatchDeviceId));
const dispatchWorkspaceId = await this.resolveDeviceWorkspaceId(dispatchDeviceId);
// Resolve via the shared precedence helper so dispatch, workspace-init,
// and the new-topic backfill below all agree on the cwd.
const deviceCwd = resolveDeviceWorkingDirectory({
@@ -1548,6 +1597,9 @@ export class AiAgentService {
cwd: deviceCwd,
deviceId: dispatchDeviceId,
systemContext: deviceSystemContext,
// Route to the workspace pool when this is a workspace device; the
// operation JWT stays member-scoped (the run belongs to the member).
workspaceId: dispatchWorkspaceId,
});
if (!result.success) {
log('execAgent: hetero device dispatch failed: %s', result.error);
@@ -1871,7 +1923,16 @@ export class AiAgentService {
const boundDeviceId = topicBoundDeviceId || agentBoundDeviceId;
if (gatewayConfigured) {
try {
onlineDevices = await deviceGateway.queryDeviceList(this.userId);
// Personal pool (user principal) the current workspace's shared pool
// (workspace principal). Workspace devices are absent for non-workspace
// runs, so this is identical to the personal-only fetch there.
const [personalOnline, workspaceOnline] = await Promise.all([
deviceGateway.queryDeviceList(this.userId),
this.workspaceId
? deviceGateway.queryDeviceList(this.userId, this.workspaceId)
: Promise.resolve([]),
]);
onlineDevices = [...personalOnline, ...workspaceOnline];
log('execAgent: found %d online device(s)', onlineDevices.length);
} catch (error) {
log('execAgent: failed to query device list: %O', error);
@@ -3811,9 +3872,14 @@ export class AiAgentService {
runningOp.deviceId,
taskId,
);
const cancelWorkspaceId = await this.resolveDeviceWorkspaceId(runningOp.deviceId);
await deviceGateway
.executeToolCall(
{ deviceId: runningOp.deviceId, userId: this.userId },
{
deviceId: runningOp.deviceId,
userId: this.userId,
workspaceId: cancelWorkspaceId,
},
{
apiName: 'cancelHeteroTask',
arguments: JSON.stringify({ signal: 'SIGINT', taskId }),
@@ -65,7 +65,7 @@ describe('DeviceGateway', () => {
const result = await proxy.queryDeviceStatus('user-1');
expect(result).toEqual(expected);
expect(mockClient.queryDeviceStatus).toHaveBeenCalledWith('user-1');
expect(mockClient.queryDeviceStatus).toHaveBeenCalledWith('user-1', undefined);
});
it('should return offline status on error', async () => {
@@ -136,7 +136,7 @@ describe('DeviceGateway', () => {
platform: 'win32',
},
]);
expect(mockClient.queryDeviceList).toHaveBeenCalledWith('user-1');
expect(mockClient.queryDeviceList).toHaveBeenCalledWith('user-1', undefined);
});
it('tolerates a legacy gateway response without channels', async () => {
@@ -191,7 +191,7 @@ describe('DeviceGateway', () => {
const result = await proxy.queryDeviceSystemInfo('user-1', 'dev-1');
expect(result).toEqual(systemInfo);
expect(mockClient.getDeviceSystemInfo).toHaveBeenCalledWith('user-1', 'dev-1');
expect(mockClient.getDeviceSystemInfo).toHaveBeenCalledWith('user-1', 'dev-1', undefined);
});
it('should return undefined when result is not successful', async () => {
+127 -54
View File
@@ -87,23 +87,25 @@ export class DeviceGateway {
return !!gatewayEnv.DEVICE_GATEWAY_URL;
}
async queryDeviceStatus(userId: string): Promise<DeviceStatusResult> {
async queryDeviceStatus(userId: string, workspaceId?: string): Promise<DeviceStatusResult> {
const client = this.getClient();
if (!client) return { deviceCount: 0, online: false };
try {
return await client.queryDeviceStatus(userId);
return await client.queryDeviceStatus(userId, workspaceId);
} catch {
return { deviceCount: 0, online: false };
}
}
async queryDeviceList(userId: string): Promise<DeviceAttachment[]> {
// Pass a `workspaceId` to address a workspace-owned device pool (the gateway
// routes to the `workspace:<id>` principal); omit it for the personal pool.
async queryDeviceList(userId: string, workspaceId?: string): Promise<DeviceAttachment[]> {
const client = this.getClient();
if (!client) return [];
try {
const devices = await client.queryDeviceList(userId);
const devices = await client.queryDeviceList(userId, workspaceId);
// The gateway already dedupes to one entry per physical device, with its
// live connections nested as `channels`. Map to the runtime shape; every
// returned device has at least one channel, so it's online.
@@ -129,12 +131,13 @@ export class DeviceGateway {
async queryDeviceSystemInfo(
userId: string,
deviceId: string,
workspaceId?: string,
): Promise<DeviceSystemInfo | undefined> {
const client = this.getClient();
if (!client) return undefined;
try {
const result = await client.getDeviceSystemInfo(userId, deviceId);
const result = await client.getDeviceSystemInfo(userId, deviceId, workspaceId);
return result.success ? result.systemInfo : undefined;
} catch {
log('queryDeviceSystemInfo: failed for userId=%s, deviceId=%s', userId, deviceId);
@@ -157,8 +160,9 @@ export class DeviceGateway {
scope: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<WorkspaceInitResult | undefined> {
const { userId, deviceId, scope, timeout = 30_000 } = params;
const { userId, deviceId, scope, timeout = 30_000, workspaceId } = params;
const client = this.getClient();
if (!client) return undefined;
@@ -169,7 +173,10 @@ export class DeviceGateway {
const result = await client.invokeRpc<{
instructions?: WorkspaceInitResult['instructions'];
skills?: (ProjectSkillMeta & Record<string, unknown>)[];
}>({ deviceId, timeout, userId }, { method: 'initWorkspace', params: { scope } });
}>(
{ deviceId, timeout, userId, workspaceId },
{ method: 'initWorkspace', params: { scope } },
);
if (!result.success || !result.data) {
log('initWorkspace: failed for deviceId=%s — %s', deviceId, result.error);
@@ -198,16 +205,16 @@ export class DeviceGateway {
*/
private async invokeGitRead<T>(
method: string,
params: { deviceId: string; timeout?: number; userId: string },
params: { deviceId: string; timeout?: number; userId: string; workspaceId?: string },
rpcParams: Record<string, unknown>,
): Promise<T | undefined> {
const { userId, deviceId, timeout = 15_000 } = params;
const { userId, deviceId, timeout = 15_000, workspaceId } = params;
const client = this.getClient();
if (!client) return undefined;
try {
const result = await client.invokeRpc<T>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method, params: rpcParams },
);
@@ -224,12 +231,18 @@ export class DeviceGateway {
}
/** Branch name + detached flag for a directory on a remote device. */
gitBranch(params: { deviceId: string; path: string; userId: string }) {
gitBranch(params: { deviceId: string; path: string; userId: string; workspaceId?: string }) {
return this.invokeGitRead<DeviceGitBranchInfo>('getGitBranch', params, { path: params.path });
}
/** The GitHub PR linked to a branch in a directory on a remote device. */
gitLinkedPullRequest(params: { branch: string; deviceId: string; path: string; userId: string }) {
gitLinkedPullRequest(params: {
branch: string;
deviceId: string;
path: string;
userId: string;
workspaceId?: string;
}) {
return this.invokeGitRead<DeviceGitLinkedPullRequestResult>('getLinkedPullRequest', params, {
branch: params.branch,
path: params.path,
@@ -237,21 +250,31 @@ export class DeviceGateway {
}
/** Working-tree dirty-file counts for a directory on a remote device. */
gitWorkingTreeStatus(params: { deviceId: string; path: string; userId: string }) {
gitWorkingTreeStatus(params: {
deviceId: string;
path: string;
userId: string;
workspaceId?: string;
}) {
return this.invokeGitRead<DeviceGitWorkingTreeStatus>('getGitWorkingTreeStatus', params, {
path: params.path,
});
}
/** Ahead/behind commit counts for a directory on a remote device. */
gitAheadBehind(params: { deviceId: string; path: string; userId: string }) {
gitAheadBehind(params: { deviceId: string; path: string; userId: string; workspaceId?: string }) {
return this.invokeGitRead<DeviceGitAheadBehind>('getGitAheadBehind', params, {
path: params.path,
});
}
/** Git worktrees attached to the same repository as a directory on a remote device. */
listGitWorktrees(params: { deviceId: string; path: string; userId: string }) {
listGitWorktrees(params: {
deviceId: string;
path: string;
userId: string;
workspaceId?: string;
}) {
return this.invokeGitRead<DeviceGitWorktreeListItem[]>('listGitWorktrees', params, {
path: params.path,
});
@@ -267,14 +290,15 @@ export class DeviceGateway {
path: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceGitBranchListItem[] | undefined> {
const { userId, deviceId, path, timeout = 15_000 } = params;
const { userId, deviceId, path, timeout = 15_000, workspaceId } = params;
const client = this.getClient();
if (!client) return undefined;
try {
const result = await client.invokeRpc<DeviceGitBranchListItem[]>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'listGitBranches', params: { path } },
);
@@ -301,14 +325,15 @@ export class DeviceGateway {
path: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceGitCheckoutResult> {
const { userId, deviceId, branch, create, path, timeout = 30_000 } = params;
const { userId, deviceId, branch, create, path, timeout = 30_000, workspaceId } = params;
const client = this.getClient();
if (!client) return { error: 'Device gateway not configured', success: false };
try {
const result = await client.invokeRpc<DeviceGitCheckoutResult>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'checkoutGitBranch', params: { branch, create, path } },
);
@@ -335,14 +360,15 @@ export class DeviceGateway {
timeout?: number;
to: string;
userId: string;
workspaceId?: string;
}): Promise<DeviceGitRenameBranchResult> {
const { userId, deviceId, from, to, path, timeout = 30_000 } = params;
const { userId, deviceId, from, to, path, timeout = 30_000, workspaceId } = params;
const client = this.getClient();
if (!client) return { error: 'Device gateway not configured', success: false };
try {
const result = await client.invokeRpc<DeviceGitRenameBranchResult>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'renameGitBranch', params: { from, path, to } },
);
@@ -368,14 +394,15 @@ export class DeviceGateway {
path: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceGitDeleteBranchResult> {
const { userId, deviceId, branch, path, timeout = 30_000 } = params;
const { userId, deviceId, branch, path, timeout = 30_000, workspaceId } = params;
const client = this.getClient();
if (!client) return { error: 'Device gateway not configured', success: false };
try {
const result = await client.invokeRpc<DeviceGitDeleteBranchResult>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'deleteGitBranch', params: { branch, path } },
);
@@ -400,14 +427,15 @@ export class DeviceGateway {
path: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceGitSyncResult> {
const { userId, deviceId, path, timeout = 65_000 } = params;
const { userId, deviceId, path, timeout = 65_000, workspaceId } = params;
const client = this.getClient();
if (!client) return { error: 'Device gateway not configured', success: false };
try {
const result = await client.invokeRpc<DeviceGitSyncResult>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'pullGitBranch', params: { path } },
);
@@ -432,14 +460,15 @@ export class DeviceGateway {
path: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceGitSyncResult> {
const { userId, deviceId, path, timeout = 65_000 } = params;
const { userId, deviceId, path, timeout = 65_000, workspaceId } = params;
const client = this.getClient();
if (!client) return { error: 'Device gateway not configured', success: false };
try {
const result = await client.invokeRpc<DeviceGitSyncResult>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'pushGitBranch', params: { path } },
);
@@ -465,14 +494,15 @@ export class DeviceGateway {
path: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceGitWorkingTreePatches | undefined> {
const { userId, deviceId, path, timeout = 30_000 } = params;
const { userId, deviceId, path, timeout = 30_000, workspaceId } = params;
const client = this.getClient();
if (!client) return undefined;
try {
const result = await client.invokeRpc<DeviceGitWorkingTreePatches>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'getGitWorkingTreePatches', params: { path } },
);
@@ -498,14 +528,15 @@ export class DeviceGateway {
path: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceGitBranchDiffPatches | undefined> {
const { userId, deviceId, baseRef, path, timeout = 30_000 } = params;
const { userId, deviceId, baseRef, path, timeout = 30_000, workspaceId } = params;
const client = this.getClient();
if (!client) return undefined;
try {
const result = await client.invokeRpc<DeviceGitBranchDiffPatches>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'getGitBranchDiff', params: { baseRef, path } },
);
@@ -531,14 +562,15 @@ export class DeviceGateway {
path: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceGitWorkingTreeFiles | undefined> {
const { userId, deviceId, path, timeout = 15_000 } = params;
const { userId, deviceId, path, timeout = 15_000, workspaceId } = params;
const client = this.getClient();
if (!client) return undefined;
try {
const result = await client.invokeRpc<DeviceGitWorkingTreeFiles>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'getGitWorkingTreeFiles', params: { path } },
);
@@ -563,14 +595,15 @@ export class DeviceGateway {
scope: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceProjectFileIndexResult | undefined> {
const { userId, deviceId, scope, timeout = 30_000 } = params;
const { userId, deviceId, scope, timeout = 30_000, workspaceId } = params;
const client = this.getClient();
if (!client) return undefined;
try {
const result = await client.invokeRpc<DeviceProjectFileIndexResult>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'getProjectFileIndex', params: { scope } },
);
@@ -598,14 +631,23 @@ export class DeviceGateway {
timeout?: number;
userId: string;
workingDirectory: string;
workspaceId?: string;
}): Promise<DeviceLocalFilePreviewResult> {
const { accept, userId, deviceId, path, workingDirectory, timeout = 30_000 } = params;
const {
accept,
userId,
deviceId,
path,
workingDirectory,
timeout = 30_000,
workspaceId,
} = params;
const client = this.getClient();
if (!client) return { error: 'Device gateway not configured', success: false };
try {
const result = await client.invokeRpc<DeviceLocalFilePreviewResult>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{
method: 'getLocalFilePreview',
params: { accept, path, workingDirectory },
@@ -636,14 +678,15 @@ export class DeviceGateway {
scope: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceListProjectSkillsResult | undefined> {
const { userId, deviceId, scope, timeout = 30_000 } = params;
const { userId, deviceId, scope, timeout = 30_000, workspaceId } = params;
const client = this.getClient();
if (!client) return undefined;
try {
const result = await client.invokeRpc<DeviceListProjectSkillsResult>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'listProjectSkills', params: { scope } },
);
@@ -669,14 +712,15 @@ export class DeviceGateway {
path: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceGitRemoteBranchListItem[] | undefined> {
const { userId, deviceId, path, timeout = 15_000 } = params;
const { userId, deviceId, path, timeout = 15_000, workspaceId } = params;
const client = this.getClient();
if (!client) return undefined;
try {
const result = await client.invokeRpc<DeviceGitRemoteBranchListItem[]>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'listGitRemoteBranches', params: { path } },
);
@@ -702,14 +746,15 @@ export class DeviceGateway {
path: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<DeviceGitFileRevertResult> {
const { userId, deviceId, filePath, path, timeout = 15_000 } = params;
const { userId, deviceId, filePath, path, timeout = 15_000, workspaceId } = params;
const client = this.getClient();
if (!client) return { error: 'Device gateway not configured', success: false };
try {
const result = await client.invokeRpc<DeviceGitFileRevertResult>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'revertGitFile', params: { filePath, path } },
);
@@ -738,8 +783,9 @@ export class DeviceGateway {
timeout?: number;
userId: string;
workingDirectory: string;
workspaceId?: string;
}): Promise<DeviceMoveProjectFileResultItem[]> {
const { userId, deviceId, items, workingDirectory, timeout = 30_000 } = params;
const { userId, deviceId, items, workingDirectory, timeout = 30_000, workspaceId } = params;
const client = this.getClient();
if (!client) throw new Error('Device gateway not configured');
@@ -749,7 +795,7 @@ export class DeviceGateway {
);
const result = await client.invokeRpc<DeviceMoveProjectFileResultItem[]>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'moveLocalFiles', params: { items } },
);
@@ -773,8 +819,17 @@ export class DeviceGateway {
timeout?: number;
userId: string;
workingDirectory: string;
workspaceId?: string;
}): Promise<DeviceRenameProjectFileResult> {
const { userId, deviceId, path, newName, workingDirectory, timeout = 30_000 } = params;
const {
userId,
deviceId,
path,
newName,
workingDirectory,
timeout = 30_000,
workspaceId,
} = params;
const client = this.getClient();
if (!client) throw new Error('Device gateway not configured');
@@ -783,7 +838,7 @@ export class DeviceGateway {
assertPathsWithinWorkspace(workingDirectory, [path]);
const result = await client.invokeRpc<DeviceRenameProjectFileResult>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'renameLocalFile', params: { newName, path } },
);
@@ -807,15 +862,24 @@ export class DeviceGateway {
timeout?: number;
userId: string;
workingDirectory: string;
workspaceId?: string;
}): Promise<DeviceWriteProjectFileResult> {
const { userId, deviceId, path, content, workingDirectory, timeout = 30_000 } = params;
const {
userId,
deviceId,
path,
content,
workingDirectory,
timeout = 30_000,
workspaceId,
} = params;
const client = this.getClient();
if (!client) throw new Error('Device gateway not configured');
assertPathsWithinWorkspace(workingDirectory, [path]);
const result = await client.invokeRpc<DeviceWriteProjectFileResult>(
{ deviceId, timeout, userId },
{ deviceId, timeout, userId, workspaceId },
{ method: 'writeLocalFile', params: { content, path } },
);
@@ -839,8 +903,9 @@ export class DeviceGateway {
path: string;
timeout?: number;
userId: string;
workspaceId?: string;
}): Promise<{ exists: boolean; isDirectory: boolean; repoType?: 'git' | 'github' } | undefined> {
const { userId, deviceId, path, timeout = 8000 } = params;
const { userId, deviceId, path, timeout = 8000, workspaceId } = params;
const client = this.getClient();
if (!client) return undefined;
@@ -849,7 +914,7 @@ export class DeviceGateway {
exists: boolean;
isDirectory: boolean;
repoType?: 'git' | 'github';
}>({ deviceId, timeout, userId }, { method: 'statPath', params: { path } });
}>({ deviceId, timeout, userId, workspaceId }, { method: 'statPath', params: { path } });
if (!result.success || !result.data) {
log('statPath: failed for deviceId=%s — %s', deviceId, result.error);
@@ -876,6 +941,7 @@ export class DeviceGateway {
systemContext?: string;
topicId: string;
userId: string;
workspaceId?: string;
}): Promise<{ error?: string; success: boolean }> {
const client = this.getClient();
if (!client) return { error: 'GATEWAY_NOT_CONFIGURED', success: false };
@@ -890,7 +956,7 @@ export class DeviceGateway {
}
async executeToolCall(
params: { deviceId: string; operationId?: string; userId: string },
params: { deviceId: string; operationId?: string; userId: string; workspaceId?: string },
toolCall: { apiName: string; arguments: string; identifier: string },
timeout = 30_000,
): Promise<DeviceToolCallResult> {
@@ -919,6 +985,7 @@ export class DeviceGateway {
operationId: params.operationId,
timeout,
userId: params.userId,
workspaceId: params.workspaceId,
},
toolCall,
);
@@ -942,6 +1009,7 @@ export class DeviceGateway {
identifier: string;
params: GatewayMcpStdioParams;
userId: string;
workspaceId?: string;
},
timeout = 30_000,
): Promise<DeviceToolCallResult> {
@@ -972,7 +1040,7 @@ export class DeviceGateway {
}
async executeMessageApi(
params: { deviceId: string; userId: string },
params: { deviceId: string; userId: string; workspaceId?: string },
api: { apiName: string; payload: Record<string, unknown>; platform: string },
timeout = 30_000,
): Promise<DeviceMessageApiResult> {
@@ -995,7 +1063,12 @@ export class DeviceGateway {
try {
return await client.executeMessageApi(
{ deviceId: params.deviceId, timeout, userId: params.userId },
{
deviceId: params.deviceId,
timeout,
userId: params.userId,
workspaceId: params.workspaceId,
},
api,
);
} catch (error) {
@@ -83,7 +83,7 @@ describe('localSystemRuntime', () => {
const result = await proxy[apiName](args);
expect(mockExecuteToolCall).toHaveBeenCalledWith(
{ deviceId: 'device-1', operationId: 'op-1', userId: 'user-1' },
{ deviceId: 'device-1', operationId: 'op-1', userId: 'user-1', workspaceId: undefined },
{
apiName,
arguments: JSON.stringify(args),
@@ -110,13 +110,38 @@ describe('localSystemRuntime', () => {
await proxy[apiName](complexArgs);
expect(mockExecuteToolCall).toHaveBeenCalledWith(
{ deviceId: 'device-2', userId: 'user-2' },
{ deviceId: 'device-2', userId: 'user-2', workspaceId: undefined },
expect.objectContaining({
arguments: JSON.stringify(complexArgs),
}),
undefined,
);
});
it('should forward workspaceId so workspace-owned devices route to the correct gateway pool', async () => {
const context: ToolExecutionContext = {
activeDeviceId: 'device-ws',
toolManifestMap: {},
userId: 'user-1',
workspaceId: 'ws-42',
};
mockExecuteToolCall.mockResolvedValue({ content: '', success: true });
const proxy = localSystemRuntime.factory(context);
const apiName = LocalSystemManifest.api[0].name;
await proxy[apiName]({ path: '/tmp' });
expect(mockExecuteToolCall).toHaveBeenCalledWith(
{ deviceId: 'device-ws', userId: 'user-1', workspaceId: 'ws-42' },
expect.objectContaining({
apiName,
identifier: LocalSystemIdentifier,
}),
undefined,
);
});
});
describe('working directory injection', () => {
@@ -2,7 +2,7 @@ import {
RemoteDeviceExecutionRuntime,
RemoteDeviceIdentifier,
} from '@lobechat/builtin-tool-remote-device';
import { describe, expect, it, vi } from 'vitest';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { type ToolExecutionContext } from '../../types';
@@ -17,6 +17,10 @@ vi.mock('@/server/services/deviceGateway', () => ({
// Import after mock setup
const { remoteDeviceRuntime } = await import('../remoteDevice');
beforeEach(() => {
mockQueryDeviceList.mockReset();
});
describe('remoteDeviceRuntime', () => {
it('should have the correct identifier', () => {
expect(remoteDeviceRuntime.identifier).toBe(RemoteDeviceIdentifier);
@@ -44,7 +48,7 @@ describe('remoteDeviceRuntime', () => {
expect(runtime).toBeInstanceOf(RemoteDeviceExecutionRuntime);
});
it('should pass queryDeviceList that calls deviceGateway with the userId', async () => {
it('should query only the personal pool when no workspaceId is in context', async () => {
const context: ToolExecutionContext = {
toolManifestMap: {},
userId: 'user-1',
@@ -63,11 +67,54 @@ describe('remoteDeviceRuntime', () => {
const runtime = remoteDeviceRuntime.factory(context) as RemoteDeviceExecutionRuntime;
// Call listOnlineDevices which internally calls queryDeviceList
const result = await runtime.listOnlineDevices();
expect(mockQueryDeviceList).toHaveBeenCalledTimes(1);
expect(mockQueryDeviceList).toHaveBeenCalledWith('user-1');
expect(result.success).toBe(true);
});
it('should merge personal + workspace pools when workspaceId is in context', async () => {
const context: ToolExecutionContext = {
toolManifestMap: {},
userId: 'user-1',
workspaceId: 'ws-1',
};
const personalDevice = {
deviceId: 'd-personal',
hostname: 'laptop',
lastSeen: '2024-01-01',
online: true,
platform: 'darwin',
};
const workspaceDevice = {
deviceId: 'd-workspace',
hostname: 'shared-mac',
lastSeen: '2024-01-01',
online: true,
platform: 'darwin',
};
mockQueryDeviceList.mockImplementation((_userId: string, wsId?: string) =>
Promise.resolve(wsId ? [workspaceDevice] : [personalDevice]),
);
const runtime = remoteDeviceRuntime.factory(context) as RemoteDeviceExecutionRuntime;
const result = await runtime.listOnlineDevices();
expect(mockQueryDeviceList).toHaveBeenCalledTimes(2);
expect(mockQueryDeviceList).toHaveBeenCalledWith('user-1');
expect(mockQueryDeviceList).toHaveBeenCalledWith('user-1', 'ws-1');
expect(result.success).toBe(true);
const parsed = JSON.parse(result.content);
expect(parsed).toEqual(
expect.arrayContaining([
expect.objectContaining({ deviceId: 'd-personal' }),
expect.objectContaining({ deviceId: 'd-workspace' }),
]),
);
});
});
});
@@ -65,6 +65,10 @@ export const localSystemRuntime: ServerRuntimeRegistration = {
deviceId: context.activeDeviceId!,
operationId: context.operationId,
userId: context.userId!,
// Workspace devices live under the `workspace:<id>` principal in
// the gateway, so the relay needs the workspaceId to address the
// right DO pool. Personal device runs leave it undefined.
workspaceId: context.workspaceId,
},
{
apiName: api.name,
@@ -14,9 +14,21 @@ export const remoteDeviceRuntime: ServerRuntimeRegistration = {
}
const userId = context.userId;
const workspaceId = context.workspaceId;
return new RemoteDeviceExecutionRuntime({
queryDeviceList: () => deviceGateway.queryDeviceList(userId),
// Personal pool (user principal) the current workspace's shared pool
// (workspace principal). Mirrors execAgent's onlineDevices fetch so the
// tool refresh stays consistent with the systemRole snapshot — otherwise
// a workspace-bound chat would see its workspace device in the system
// prompt but lose it the moment the model calls listOnlineDevices.
queryDeviceList: async () => {
const [personal, workspace] = await Promise.all([
deviceGateway.queryDeviceList(userId),
workspaceId ? deviceGateway.queryDeviceList(userId, workspaceId) : Promise.resolve([]),
]);
return [...personal, ...workspace];
},
});
},
identifier: RemoteDeviceIdentifier,
+2 -4
View File
@@ -461,8 +461,7 @@ table ai_models {
updated_at "timestamp with time zone" [not null, default: `now()`]
indexes {
(id, provider_id, user_id) [name: 'ai_models_id_provider_id_user_id_unique', unique]
(id, provider_id, user_id, workspace_id) [name: 'ai_models_id_provider_id_user_id_workspace_id_unique', unique]
(id, provider_id, user_id) [pk]
user_id [name: 'ai_models_user_id_idx']
workspace_id [name: 'ai_models_workspace_id_idx']
}
@@ -489,8 +488,7 @@ table ai_providers {
updated_at "timestamp with time zone" [not null, default: `now()`]
indexes {
(id, user_id) [name: 'ai_providers_id_user_id_unique', unique]
(id, user_id, workspace_id) [name: 'ai_providers_id_user_id_workspace_id_unique', unique]
(id, user_id) [pk]
user_id [name: 'ai_providers_user_id_idx']
workspace_id [name: 'ai_providers_workspace_id_idx']
}
+28 -2
View File
@@ -41,6 +41,8 @@
"artifact.thinking": "يفكر",
"artifact.thought": "عملية التفكير",
"artifact.unknownTitle": "عمل بدون عنوان",
"audioPlayer.pause": "إيقاف تشغيل الصوت",
"audioPlayer.play": "تشغيل الصوت",
"availableAgents": "الوكلاء المتاحون",
"backToBottom": "الانتقال إلى الأحدث",
"beforeUnload.confirmLeave": "لا يزال هناك طلب قيد التشغيل. هل تريد المغادرة؟",
@@ -120,6 +122,18 @@
"createModal.groupPlaceholder": "صف ما ينبغي أن يقوم به هذا الفريق...",
"createModal.groupTitle": "ما الذي ينبغي أن يقوم به فريقك؟",
"createModal.placeholder": "صف ما ينبغي أن يقوم به وكيلك...",
"createModal.skillSuggestion.actions.createAnyway": "إنشاء الوكيل على أي حال",
"createModal.skillSuggestion.actions.createAnywayHint": "المهارة غير مناسبة؟",
"createModal.skillSuggestion.actions.install": "تثبيت المهارة",
"createModal.skillSuggestion.actions.installing": "جارٍ التثبيت…",
"createModal.skillSuggestion.actions.openSkills": "عرض في المهارات",
"createModal.skillSuggestion.actions.tryInLobeAI": "استخدام في {{name}}",
"createModal.skillSuggestion.description": "يبدو أن هذا سير عمل قابل لإعادة الاستخدام. قم بتثبيت المهارة مرة واحدة، ثم استخدمها عبر الوكلاء.",
"createModal.skillSuggestion.installError": "لم يتم تثبيت المهارة. حاول مرة أخرى، أو قم بإنشاء وكيل على أي حال.",
"createModal.skillSuggestion.installed.description": "يمكنك استخدام هذه المهارة في {{name}}، أو تمكينها لأي وكيل.",
"createModal.skillSuggestion.installed.ready": "جاهز في {{name}}",
"createModal.skillSuggestion.installed.title": "تم تثبيت المهارة",
"createModal.skillSuggestion.title": "قد تكون المهارة مناسبة أكثر",
"createModal.title": "ما الذي ينبغي أن يقوم به وكيلك؟",
"createTask.assignee": "المكلّف",
"createTask.collapse": "إخفاء الإدخال",
@@ -166,6 +180,8 @@
"extendParams.title": "ميزات توسيع النموذج",
"extendParams.urlContext.desc": "عند التفعيل، سيتم تحليل الروابط تلقائيًا لاستخراج محتوى صفحة الويب الفعلي",
"extendParams.urlContext.title": "استخراج محتوى رابط الويب",
"floatingChatPanel.collapse": "إخفاء الدردشة",
"floatingChatPanel.expand": "توسيع الدردشة",
"followUpPlaceholder": "متابعة. @ لإسناد مهام لوكلاء آخرين.",
"followUpPlaceholderHeterogeneous": "تابع.",
"gatewayMode.beta": "تجريبي",
@@ -219,9 +235,13 @@
"heteroAgent.cloudRepo.noRepos": "لم يتم تكوين أي مستودعات. أضفها في إعدادات الوكيل.",
"heteroAgent.cloudRepo.notSet": "لم يتم تحديد أي مستودع",
"heteroAgent.cloudRepo.sectionTitle": "المستودعات",
"heteroAgent.executionTarget.auto": "تلقائي",
"heteroAgent.executionTarget.autoDesc": "استخدام جهاز متصل تلقائيًا، واختيار واحد عند توفر عدة أجهزة",
"heteroAgent.executionTarget.downloadDesktop": "احصل على تطبيق سطح المكتب",
"heteroAgent.executionTarget.downloadDesktopDesc": "قم بتشغيل الوكلاء مع الوصول إلى جهاز الكمبيوتر الخاص بك",
"heteroAgent.executionTarget.downloadDesktopTitle": "احصل على تطبيق سطح المكتب",
"heteroAgent.executionTarget.gateway": "البوابة",
"heteroAgent.executionTarget.gatewayDesc": "التشغيل عبر بوابة الجهاز بحيث يمكن للعملاء الآخرين متابعة التقدم",
"heteroAgent.executionTarget.infoTooltip": "اختر جهازًا بعيدًا لتشغيل هذا الجهاز من الويب. \"هذا الجهاز\" يشغل الوكيل محليًا وهو متاح فقط داخل تطبيق سطح المكتب.",
"heteroAgent.executionTarget.loading": "جارٍ تحميل الأجهزة...",
"heteroAgent.executionTarget.local": "هذا الجهاز",
@@ -231,10 +251,12 @@
"heteroAgent.executionTarget.noneDesc": "لم يتم تمكين أي جهاز",
"heteroAgent.executionTarget.offline": "غير متصل",
"heteroAgent.executionTarget.online": "متصل",
"heteroAgent.executionTarget.personalGroup": "شخصي",
"heteroAgent.executionTarget.sandbox": "بيئة سحابية مؤقتة",
"heteroAgent.executionTarget.sandboxDesc": "تشغيل في بيئة سحابية مؤقتة",
"heteroAgent.executionTarget.title": "جهاز التنفيذ",
"heteroAgent.executionTarget.unknownDevice": "جهاز غير معروف",
"heteroAgent.executionTarget.workspaceGroup": "مساحة العمل",
"heteroAgent.fullAccess.label": "وصول كامل",
"heteroAgent.fullAccess.tooltip": "يعمل Claude Code محليًا مع صلاحية قراءة/كتابة كاملة في دليل العمل. تبديل أوضاع الصلاحيات غير متاح بعد.",
"heteroAgent.resumeReset.cwdChanged": "تم تغيير دليل العمل. لا يمكن استئناف جلسة Claude Code السابقة إلا من دليلها الأصلي، لذا بدأت محادثة جديدة.",
@@ -631,6 +653,8 @@
"taskDetail.artifacts": "المنتجات",
"taskDetail.blockedBy": "محجوب بواسطة {{id}}",
"taskDetail.cancelSchedule": "إلغاء الجدولة",
"taskDetail.closeDetail": "إغلاق التفاصيل",
"taskDetail.collapseReply": "إخفاء الرد",
"taskDetail.comment.cancel": "إلغاء",
"taskDetail.comment.delete": "حذف",
"taskDetail.comment.deleteConfirm.content": "سيتم حذف هذا التعليق بشكل دائم.",
@@ -657,6 +681,7 @@
"taskDetail.notFound.backToTasks": "العودة إلى جميع المهام",
"taskDetail.notFound.desc": "قد تكون هذه المهمة قد حُذفت، أو ليس لديك إذن لعرضها.",
"taskDetail.notFound.title": "المهمة غير موجودة",
"taskDetail.openDetail": "فتح التفاصيل",
"taskDetail.pauseTask": "إيقاف المهمة مؤقتًا",
"taskDetail.priority.high": "عالية",
"taskDetail.priority.low": "منخفضة",
@@ -925,9 +950,9 @@
"workflow.collapse": "طي",
"workflow.expandFull": "توسيع كامل",
"workflow.failedSuffix": "(فشل)",
"workflow.summaryAcrossTools": "عبر {{count}} أدوات",
"workflow.summaryCallsLead": "{{count}} مكالمات: {{tools}}",
"workflow.summaryFailed": "{{count}} فشلت",
"workflow.summaryMoreTools": "{{count}} أنواع أدوات",
"workflow.summaryTotalCalls": "{{count}} مكالمات إجمالية",
"workflow.thoughtForDuration": "تفكير لمدة {{duration}}",
"workflow.toolDisplayName.activateDevice": "تم تفعيل الجهاز",
"workflow.toolDisplayName.activateSkill": "تم تفعيل مهارة",
@@ -1043,6 +1068,7 @@
"workingPanel.resources.deleteTitle": "Delete document?",
"workingPanel.resources.deleteTitleMulti": "حذف {{count}} عنصرًا؟",
"workingPanel.resources.empty": "لا توجد مستندات بعد. ستظهر المستندات المرتبطة بهذا الوكيل هنا.",
"workingPanel.resources.emptyDocuments": "لا توجد مستندات حتى الآن. قم بإنشاء واحد باستخدام + أعلاه.",
"workingPanel.resources.error": "Failed to load resources",
"workingPanel.resources.filter.documents": "مستندات",
"workingPanel.resources.filter.skills": "المهارات",
+18 -2
View File
@@ -444,6 +444,23 @@
"tab.setting": "الإعدادات",
"tab.tasks": "المهام",
"tab.video": "الفيديو",
"taskTemplate.action.connect.button": "اتصل بـ {{provider}}",
"taskTemplate.action.connect.error": "فشل الاتصال، يرجى المحاولة مرة أخرى.",
"taskTemplate.action.connect.popupBlocked": "تم حظر نافذة الاتصال المنبثقة. اسمح بالنوافذ المنبثقة في متصفحك للمتابعة.",
"taskTemplate.action.connect.short": "اتصل",
"taskTemplate.action.connecting": "في انتظار التفويض...",
"taskTemplate.action.create.error": "فشل إنشاء المهمة. يرجى المحاولة مرة أخرى.",
"taskTemplate.action.create.success": "تمت إضافة المهمة المجدولة. يمكنك العثور عليها في Lobe AI.",
"taskTemplate.action.createButton": "إضافة مهمة",
"taskTemplate.action.creating": "جاري الإنشاء...",
"taskTemplate.action.dismiss.error": "فشل الإلغاء. يرجى المحاولة مرة أخرى.",
"taskTemplate.action.dismiss.tooltip": "غير مهتم",
"taskTemplate.action.refresh.button": "تحديث",
"taskTemplate.card.templateTag": "قالب",
"taskTemplate.schedule.daily": "كل يوم في {{time}}",
"taskTemplate.schedule.editableAfterCreateTooltip": "يمكنك تعديل الجدول الزمني بعد إنشاء المهمة.",
"taskTemplate.schedule.weekly": "كل {{weekday}} في {{time}}",
"taskTemplate.section.title": "جرب هذه المهام المجدولة",
"telemetry.allow": "السماح",
"telemetry.deny": "رفض",
"telemetry.desc": "نود جمع معلومات الاستخدام بشكل مجهول لمساعدتنا في تحسين {{appName}} وتقديم تجربة أفضل لك. يمكنك تعطيل هذا في أي وقت من خلال الإعدادات - حول.",
@@ -474,15 +491,14 @@
"userPanel.email": "دعم البريد الإلكتروني",
"userPanel.feedback": "اتصل بنا",
"userPanel.help": "مركز المساعدة",
"userPanel.inviteFriend": "ادعُ صديقًا",
"userPanel.moveGuide": "تم نقل زر الإعدادات إلى هنا",
"userPanel.plans": "خطط الاشتراك",
"userPanel.profile": "الحساب",
"userPanel.setting": "الإعدادات",
"userPanel.upgradePlan": "ترقية الخطة",
"userPanel.usages": "إحصائيات الاستخدام",
"userPanel.workspaceCredits": "أرصدة مساحة العمل",
"userPanel.workspaceSetting": "إعدادات مساحة العمل",
"userPanel.workspaceUsages": "استخدامات مساحة العمل",
"version": "الإصدار",
"zoom": "تكبير"
}
+2
View File
@@ -101,6 +101,7 @@
"LocalFile.action.open": "فتح",
"LocalFile.action.showInFolder": "عرض في المجلد",
"MaxTokenSlider.unlimited": "غير محدود",
"ModelSelect.featureTag.audio": "يدعم هذا النموذج التعرف على إدخال الصوت.",
"ModelSelect.featureTag.custom": "نموذج مخصص، يدعم افتراضيًا استدعاء الوظائف والتعرف البصري. يرجى التحقق من توفر هذه القدرات حسب الحالة الفعلية.",
"ModelSelect.featureTag.file": "يدعم هذا النموذج تحميل الملفات للقراءة والتعرف.",
"ModelSelect.featureTag.functionCall": "يدعم هذا النموذج استدعاء الوظائف.",
@@ -114,6 +115,7 @@
"ModelSwitchPanel.byModel": "حسب النموذج",
"ModelSwitchPanel.byProvider": "حسب المزوّد",
"ModelSwitchPanel.detail.abilities": "القدرات",
"ModelSwitchPanel.detail.abilities.audio": "الصوت",
"ModelSwitchPanel.detail.abilities.files": "الملفات",
"ModelSwitchPanel.detail.abilities.functionCall": "استدعاء الأداة",
"ModelSwitchPanel.detail.abilities.imageOutput": "إخراج الصورة",
-3
View File
@@ -38,9 +38,6 @@
"brief.title": "الموجز اليومي",
"brief.viewAllTasks": "عرض جميع المهام",
"brief.viewRun": "عرض التشغيل",
"freeCreditBadge.cta": "ابدأ النسخة التجريبية المجانية",
"freeCreditBadge.dismiss": "تجاهل",
"freeCreditBadge.label": "أرصدة مجانية حصرية لـ {{model}}",
"project.create": "مشروع جديد",
"project.deleteConfirm": "سيتم حذف هذا المشروع ولن يمكن استعادته. أكد للمتابعة.",
"recommendations.heteroAgent.cta": "أضف الوكيل",
+4 -4
View File
@@ -5,10 +5,12 @@
"authorize.footer.agreement": "بالمتابعة، فإنك تؤكد أنك قرأت ووافقت على <terms>الشروط والأحكام</terms> و<privacy>سياسة الخصوصية</privacy>.",
"authorize.footer.privacy": "سياسة الخصوصية",
"authorize.footer.terms": "شروط الخدمة",
"authorize.scenes.connector.confirm": "متابعة إلى السوق",
"authorize.scenes.connector.description": "يُستخدم السوق فقط لبدء تفويض هذه الخدمة. يبقى حساب {{appName}} الخاص بك منفصلًا.",
"authorize.scenes.connector.subtitle": "سجّل الدخول إلى السوق للاتصال وتفويض هذه الخدمة المجتمعية.",
"authorize.scenes.connector.title": "اتصال الخدمة المجتمعية",
"authorize.scenes.mcp.subtitle": "قم بإنشاء ملف تعريف مجتمعي لتثبيت وتشغيل هذه المهارة من المجتمع.",
"authorize.scenes.mcp.title": "تثبيت مهارة المجتمع",
"authorize.scenes.publish.subtitle": "قم بإنشاء ملف تعريف مجتمعي لنشر وإدارة قائمتك داخل المجتمع.",
"authorize.scenes.publish.title": "النشر في المجتمع",
"authorize.scenes.sandbox.subtitle": "قم بإنشاء ملف تعريف مجتمعي لتشغيل هذه الأداة في صندوق المجتمع التجريبي.",
"authorize.scenes.sandbox.title": "جرب صندوق المجتمع التجريبي",
"authorize.subtitle": "أنشئ ملفًا شخصيًا في المجتمع لتتمكن من إرسال وإدارة القوائم داخل المجتمع.",
@@ -50,8 +52,6 @@
"messages.handoffTimeout": "انتهت مهلة التفويض. أكمل العملية في المتصفح ثم أعد المحاولة.",
"messages.loading": "جارٍ بدء عملية التفويض...",
"messages.success.cloudMcpInstall": "تم التفويض بنجاح! يمكنك الآن تثبيت مهارة Cloud MCP.",
"messages.success.submit": "تم التفويض بنجاح! يمكنك الآن نشر وكيلك.",
"messages.success.upload": "تم التفويض بنجاح! يمكنك الآن نشر إصدار جديد.",
"profileSetup.cancel": "إلغاء",
"profileSetup.confirmChangeUserId.cancel": "إلغاء",
"profileSetup.confirmChangeUserId.confirm": "تغيير معرف المستخدم",
+4 -2
View File
@@ -222,6 +222,7 @@
"providerModels.item.modelConfig.extendParams.options.effort.hint": "لـ Claude Opus 4.6؛ يتحكم في مستوى الجهد (منخفض/متوسط/عالٍ/أقصى).",
"providerModels.item.modelConfig.extendParams.options.enableAdaptiveThinking.hint": "لـ Claude Opus 4.6؛ يفعّل أو يعطّل التفكير التكيفي.",
"providerModels.item.modelConfig.extendParams.options.enableReasoning.hint": "لنماذج Claude وDeepSeek وغيرها من نماذج الاستدلال؛ يفعّل التفكير العميق.",
"providerModels.item.modelConfig.extendParams.options.glm5_2ReasoningEffort.hint": "بالنسبة لـ GLM-5.2؛ يتحكم في جهد التفكير بمستويات عالية وأقصى.",
"providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint": "لسلسلة GPT-5؛ يتحكم في شدة الاستدلال.",
"providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint": "لسلسلة GPT-5.1؛ يتحكم في شدة الاستدلال.",
"providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "لسلسلة GPT-5.2 Pro؛ يتحكم في شدة الاستدلال.",
@@ -256,6 +257,7 @@
"providerModels.item.modelConfig.files.title": "دعم رفع الملفات",
"providerModels.item.modelConfig.functionCall.extra": "سيمكن هذا الإعداد النموذج من استخدام الأدوات، ولكن قدرة النموذج الفعلية على استخدامها تعتمد عليه بالكامل؛ يرجى الاختبار بنفسك.",
"providerModels.item.modelConfig.functionCall.title": "دعم استخدام الأدوات",
"providerModels.item.modelConfig.id.duplicate": "يوجد بالفعل نموذج بهذا المعرف. استخدم معرف نموذج مختلف.",
"providerModels.item.modelConfig.id.extra": "لا يمكن تعديله بعد الإنشاء وسيُستخدم كمعرف النموذج عند استدعاء الذكاء الاصطناعي",
"providerModels.item.modelConfig.id.placeholder": "يرجى إدخال معرف النموذج، مثل gpt-4o أو claude-3.5-sonnet",
"providerModels.item.modelConfig.id.title": "معرف النموذج",
@@ -270,11 +272,11 @@
"providerModels.item.modelConfig.tokens.title": "أقصى نافذة سياق",
"providerModels.item.modelConfig.tokens.unlimited": "غير محدود",
"providerModels.item.modelConfig.type.extra": "أنواع النماذج المختلفة لها استخدامات وقدرات مختلفة",
"providerModels.item.modelConfig.type.options.asr": "تحويل الكلام إلى نص",
"providerModels.item.modelConfig.type.options.chat": "دردشة",
"providerModels.item.modelConfig.type.options.embedding": "تضمين",
"providerModels.item.modelConfig.type.options.image": "توليد الصور",
"providerModels.item.modelConfig.type.options.realtime": "دردشة فورية",
"providerModels.item.modelConfig.type.options.stt": "تحويل الكلام إلى نص",
"providerModels.item.modelConfig.type.options.text2music": "نص إلى موسيقى",
"providerModels.item.modelConfig.type.options.tts": "تحويل النص إلى كلام",
"providerModels.item.modelConfig.type.options.video": "توليد الفيديو",
@@ -323,10 +325,10 @@
"providerModels.list.total": "{{count}} نموذج متاح",
"providerModels.searchNotFound": "لم يتم العثور على نتائج",
"providerModels.tabs.all": "الكل",
"providerModels.tabs.asr": "تحويل الكلام إلى نص",
"providerModels.tabs.chat": "دردشة",
"providerModels.tabs.embedding": "تضمين",
"providerModels.tabs.image": "صورة",
"providerModels.tabs.stt": "تحويل الكلام إلى نص",
"providerModels.tabs.tts": "تحويل النص إلى كلام",
"providerModels.tabs.video": "فيديو",
"sortModal.success": "تم تحديث الترتيب بنجاح",
+9 -69
View File
@@ -440,60 +440,7 @@
"llm.proxyUrl.title": "عنوان وكيل API",
"llm.waitingForMore": "سيتم <1>إضافة المزيد من النماذج</1> قريبًا، ترقبوا",
"llm.waitingForMoreLinkAriaLabel": "فتح نموذج طلب المزود",
"marketPublish.forkConfirm.by": "بواسطة {{author}}",
"marketPublish.forkConfirm.confirm": "تأكيد النشر",
"marketPublish.forkConfirm.confirmGroup": "تأكيد النشر",
"marketPublish.forkConfirm.description": "أنت على وشك نشر نسخة مشتقة تستند إلى وكيل موجود من المجتمع. سيتم إنشاء وكيلك الجديد كإدخال منفصل في السوق.",
"marketPublish.forkConfirm.descriptionGroup": "أنت على وشك نشر نسخة مشتقة تستند إلى مجموعة موجودة من المجتمع. سيتم إنشاء مجموعتك الجديدة كإدخال منفصل في السوق.",
"marketPublish.forkConfirm.title": "نشر وكيل مشتق",
"marketPublish.forkConfirm.titleGroup": "نشر مجموعة مشتقة",
"marketPublish.modal.changelog.extra": "وصف التغييرات والتحسينات الرئيسية في هذا الإصدار",
"marketPublish.modal.changelog.label": "سجل التغييرات",
"marketPublish.modal.changelog.maxLengthError": "يجب ألا يتجاوز سجل التغييرات 500 حرف",
"marketPublish.modal.changelog.placeholder": "أدخل سجل التغييرات",
"marketPublish.modal.changelog.required": "يرجى إدخال سجل التغييرات",
"marketPublish.modal.comparison.local": "الإصدار المحلي الحالي",
"marketPublish.modal.comparison.remote": "الإصدار المنشور حاليًا",
"marketPublish.modal.identifier.extra": "هذا هو المعرف الفريد للوكيل. استخدم أحرفًا صغيرة وأرقامًا وشرطات.",
"marketPublish.modal.identifier.label": "معرف الوكيل",
"marketPublish.modal.identifier.lengthError": "يجب أن يتراوح طول المعرف بين 3 و50 حرفًا",
"marketPublish.modal.identifier.patternError": "يمكن أن يحتوي المعرف فقط على أحرف صغيرة وأرقام وشرطات",
"marketPublish.modal.identifier.placeholder": "أدخل معرفًا فريدًا للوكيل، مثل web-development",
"marketPublish.modal.identifier.required": "يرجى إدخال معرف الوكيل",
"marketPublish.modal.loading.fetchingRemote": "جارٍ تحميل البيانات البعيدة...",
"marketPublish.modal.loading.submit": "جارٍ إرسال الوكيل...",
"marketPublish.modal.loading.submitGroup": "جارٍ إرسال المجموعة...",
"marketPublish.modal.loading.upload": "جارٍ نشر الإصدار الجديد...",
"marketPublish.modal.loading.uploadGroup": "جارٍ نشر إصدار جديد من المجموعة...",
"marketPublish.modal.messages.createVersionFailed": "فشل في إنشاء الإصدار: {{message}}",
"marketPublish.modal.messages.fetchRemoteFailed": "فشل في جلب بيانات الوكيل البعيدة",
"marketPublish.modal.messages.missingIdentifier": "لا يحتوي هذا الوكيل على معرف المجتمع بعد.",
"marketPublish.modal.messages.noGroup": "لم يتم تحديد أي مجموعة",
"marketPublish.modal.messages.notAuthenticated": "يرجى تسجيل الدخول إلى حسابك في المجتمع أولاً.",
"marketPublish.modal.messages.publishFailed": "فشل النشر: {{message}}",
"marketPublish.modal.submitButton": "نشر",
"marketPublish.modal.title.submit": "مشاركة في مجتمع الوكلاء",
"marketPublish.modal.title.upload": "نشر إصدار جديد",
"marketPublish.resultModal.message": "تم إرسال وكيلك للمراجعة. بمجرد الموافقة عليه، سيتم نشره تلقائيًا.",
"marketPublish.resultModal.messageGroup": "تم إرسال مجموعتك للمراجعة. بمجرد الموافقة عليها، سيتم نشرها تلقائيًا.",
"marketPublish.resultModal.title": "تم الإرسال بنجاح",
"marketPublish.resultModal.view": "عرض في المجتمع",
"marketPublish.status.underReview": "قيد المراجعة",
"marketPublish.submit.button": "مشاركة في المجتمع",
"marketPublish.submit.tooltip": "شارك هذا الوكيل في المجتمع",
"marketPublish.submitGroup.tooltip": "شارك هذه المجموعة مع المجتمع",
"marketPublish.upload.button": "نشر إصدار جديد",
"marketPublish.upload.tooltip": "نشر إصدار جديد في مجتمع الوكلاء",
"marketPublish.uploadGroup.tooltip": "نشر إصدار جديد في مجتمع المجموعات",
"marketPublish.validation.communitySetupRequired.action": "إعداد الآن",
"marketPublish.validation.communitySetupRequired.desc": "لم تقم هذه مساحة العمل بإعداد ملف تعريف المجتمع الخاص بها بعد. قم بإعداده قبل النشر في المجتمع.",
"marketPublish.validation.communitySetupRequired.memberHint": "لم تقم هذه مساحة العمل بإعداد ملف تعريف المجتمع الخاص بها بعد. اطلب من مالك مساحة العمل إعدادها قبل النشر في المجتمع.",
"marketPublish.validation.communitySetupRequired.title": "قم بإعداد ملف تعريف المجتمع أولاً",
"marketPublish.validation.confirmPublish": "النشر في السوق؟",
"marketPublish.validation.confirmPublishDesc": "بمجرد النشر، سيكون هذا المحتوى مرئيًا للجمهور في السوق ومتاحًا لأي شخص لاكتشافه واستخدامه.",
"marketPublish.validation.emptyName": "لا يمكن النشر: الاسم مطلوب",
"marketPublish.validation.emptySystemRole": "لا يمكن النشر: دور النظام مطلوب",
"marketPublish.validation.underReview": "إصدارك الجديد قيد المراجعة حاليًا. يرجى انتظار الموافقة قبل نشر إصدار جديد.",
"memory.effort.desc": "تحكم في مدى شدة استرجاع وتحديث الذاكرة من قبل الذكاء الاصطناعي.",
"memory.effort.high": "عالي — استرجاع وتحديث استباقي",
"memory.effort.level.high": "عالي",
@@ -515,14 +462,6 @@
"myAgents.actions.deprecateLoading": "جارٍ إيقاف الوكيل...",
"myAgents.actions.deprecateSuccess": "تم إيقاف الوكيل",
"myAgents.actions.edit": "تعديل الوكيل",
"myAgents.actions.publish": "نشر الوكيل",
"myAgents.actions.publishError": "فشل في نشر الوكيل",
"myAgents.actions.publishLoading": "جارٍ نشر الوكيل...",
"myAgents.actions.publishSuccess": "تم نشر الوكيل",
"myAgents.actions.unpublish": "إلغاء نشر الوكيل",
"myAgents.actions.unpublishError": "فشل في إلغاء نشر الوكيل",
"myAgents.actions.unpublishLoading": "جارٍ إلغاء نشر الوكيل...",
"myAgents.actions.unpublishSuccess": "تم إلغاء نشر الوكيل",
"myAgents.actions.viewDetail": "عرض التفاصيل",
"myAgents.detail.category": "الفئة",
"myAgents.detail.description": "الوصف",
@@ -587,7 +526,6 @@
"plugin.settings.title": "إعدادات مهارة {{id}}",
"plugin.settings.tooltip": "إعدادات المهارة",
"plugin.store": "متجر المهارات",
"publishToCommunity": "النشر في المجتمع",
"serviceModel.contextLimit.placeholder": "حد السياق",
"serviceModel.memoryModels.title": "نماذج الذاكرة",
"serviceModel.modelAssignments.title": "تعيينات النموذج",
@@ -955,13 +893,6 @@
"storageOverage.usage.estimatedCharge": "الرسوم المقدرة للدورة",
"storageOverage.usage.incurredCharge": "تم تكبدها في هذه الدورة",
"storageOverage.usage.overage": "التجاوز",
"submitAgentModal.button": "إرسال الوكيل",
"submitAgentModal.identifier": "معرّف الوكيل",
"submitAgentModal.metaMiss": "يرجى إكمال معلومات الوكيل قبل الإرسال. يجب أن تتضمن الاسم والوصف والوسوم.",
"submitAgentModal.placeholder": "أدخل معرّفًا فريدًا للوكيل، مثل: web-development",
"submitAgentModal.success": "تم إرسال الوكيل بنجاح",
"submitAgentModal.tooltips": "مشاركة في مجتمع الوكلاء",
"submitGroupModal.tooltips": "المشاركة في مجتمع المجموعات",
"sync.device.deviceName.hint": "أضف اسمًا لسهولة التعرف",
"sync.device.deviceName.placeholder": "أدخل اسم الجهاز",
"sync.device.deviceName.title": "اسم الجهاز",
@@ -1086,6 +1017,7 @@
"tools.activation.auto": "تلقائي",
"tools.activation.auto.desc": "ذكي",
"tools.activation.fixed.hint": "دائمًا قيد التشغيل — يتم إدارته بواسطة التطبيق ولا يمكن إيقاف تشغيله",
"tools.activation.pin": "الرمز السري",
"tools.activation.pinned": "مثبت",
"tools.activation.pinned.desc": "دائمًا قيد التشغيل",
"tools.add": "إضافة مهارة",
@@ -2047,6 +1979,14 @@
"workspace.wizard.step3.title": "مرحبًا بك في {{name}}!",
"workspace.wizard.title": "إنشاء مساحة العمل",
"workspaceSetting.breadcrumb.settings": "الإعدادات",
"workspaceSetting.devices.desc": "الأجهزة المشتركة المسجلة في مساحة العمل هذه. يمكن للأعضاء تشغيل الوكلاء عليها.",
"workspaceSetting.devices.empty": "لا توجد أجهزة مرتبطة بمساحة العمل حتى الآن.",
"workspaceSetting.devices.enrollDesc": "قم بتشغيل هذا على الجهاز الذي تريد مشاركته (لصاحب مساحة العمل فقط):",
"workspaceSetting.devices.enrollTitle": "إضافة جهاز",
"workspaceSetting.devices.heroDesc": "قم بتسجيل جهاز مشترك — مثل خادم بناء أو جهاز Mac لفريق العمل — وسيتمكن كل عضو من تشغيل الوكلاء عليه: قراءة/كتابة الملفات، تشغيل الأوامر، واستخدام أدوات النظام.",
"workspaceSetting.devices.heroTitle": "قم بتوصيل أول جهاز لمساحة العمل",
"workspaceSetting.devices.offline": "غير متصل",
"workspaceSetting.devices.online": "متصل",
"workspaceSetting.group.admin": "الإدارة",
"workspaceSetting.group.agent": "الوكيل",
"workspaceSetting.group.general": "عام",
+10 -5
View File
@@ -147,10 +147,6 @@
"limitation.chat.topupSuccess.title": "تم الشحن بنجاح",
"limitation.expired.desc": "انتهت صلاحية أرصدة الحوسبة الخاصة بك في خطة {{plan}} بتاريخ {{expiredAt}}. قم بالترقية الآن للحصول على أرصدة جديدة.",
"limitation.expired.title": "انتهت أرصدة الحوسبة",
"limitation.fableCampaign.desc": "Claude Fable 5 هو نموذج عالي التكلفة. تم استخدام أرصدة التجربة الخاصة بالحملة. قم بترقية خطتك للاستمرار في استخدام Fable.",
"limitation.fableCampaign.title": "تم استخدام أرصدة تجربة Fable",
"limitation.fableCampaign.upgrade": "ترقية الخطة",
"limitation.fableCampaign.upgradeToPlan": "الترقية إلى {{plan}}",
"limitation.hobby.action": "تم التكوين، تابع المحادثة",
"limitation.hobby.configAPI": "تكوين API",
"limitation.hobby.desc": "تم استهلاك أرصدة الحوسبة المجانية الخاصة بك. يرجى تكوين واجهة برمجة التطبيقات المخصصة للنموذج للمتابعة.",
@@ -342,7 +338,14 @@
"plans.workspace.noSharedCredits": "لا توجد أرصدة مشتركة",
"plans.workspace.sharedCredits": "~{{count}} أرصدة / شهريًا",
"plans.workspace.solo": "فردي (عضو واحد)",
"promoBanner.fableYearly": "المشتركين السنويين يحصلون على خصم {{percent}}% على الاستخدام لفترة محدودة",
"plansModal.creditLimit.desc": "قم بترقية خطتك لفتح المزيد من الاعتمادات الشهرية ومواصلة العمل دون انقطاع.",
"plansModal.creditLimit.title": "لقد نفدت الاعتمادات",
"plansModal.default.desc": "افتح المزيد من السعة والميزات المتقدمة.",
"plansModal.default.title": "قم بترقية خطتك",
"plansModal.fileStorageLimit.desc": "مساحة تخزين الملفات ممتلئة. قم بالترقية لمواصلة تحميل وإدارة الملفات.",
"plansModal.fileStorageLimit.title": "تم الوصول إلى حد التخزين",
"plansModal.modelAccess.desc": "هذا النموذج متاح في الخطط المدفوعة. قم بالترقية لاستخدام مجموعة النماذج الكاملة.",
"plansModal.modelAccess.title": "افتح جميع النماذج",
"qa.desc": "إذا لم تجد إجابتك، تحقق من <1>وثائق المنتج</1> لمزيد من الأسئلة الشائعة، أو تواصل معنا.",
"qa.detail": "عرض التفاصيل",
"qa.list.credit.a": "أرصدة الحوسبة هي وحدة قياس يستخدمها {{cloud}} لقياس استخدام نماذج الذكاء الاصطناعي. تستهلك النماذج المختلفة كميات مختلفة من الأرصدة.",
@@ -398,6 +401,8 @@
"referral.errors.invalidFormat": "تنسيق رمز الإحالة غير صالح، يرجى إدخال 2-8 أحرف أو أرقام أو شرطات سفلية",
"referral.errors.selfReferral": "لا يمكنك استخدام رمز الدعوة الخاص بك",
"referral.errors.updateFailed": "فشل التحديث، يرجى المحاولة لاحقًا",
"referral.hero.description": "شارك رابط الإحالة الخاص بك أدناه. بعد أن يقوم صديقك بأول عملية دفع، ستحصلان كلاكما على {{reward}} مليون اعتماد.",
"referral.hero.title": "ادعُ أصدقاءك، واحصل كلاكما على <0>{{reward}} مليون اعتماد</0>",
"referral.inviteCode.description": "شارك رمز الإحالة الحصري الخاص بك لدعوة الأصدقاء للتسجيل",
"referral.inviteCode.title": "رمز الإحالة الخاص بي",
"referral.inviteLink.description": "انسخ الرابط وشاركه مع الأصدقاء. يحصل كلاكما على أرصدة بعد أن يقوم صديقك بالدفع",
+1
View File
@@ -25,6 +25,7 @@
"actions.unmarkCompleted": "وضع علامة كنشط",
"defaultTitle": "موضوع افتراضي",
"displayItems": "عرض العناصر",
"draft": "[مسودة]",
"duplicateLoading": "جارٍ نسخ الموضوع...",
"duplicateSuccess": "تم نسخ الموضوع بنجاح",
"failedStatusTip": "تعرضت هذه العملية لخطأ — افتحها لإلقاء نظرة.",
+28 -2
View File
@@ -41,6 +41,8 @@
"artifact.thinking": "Мислене",
"artifact.thought": "Мисловен процес",
"artifact.unknownTitle": "Без заглавие",
"audioPlayer.pause": "Пауза на аудио",
"audioPlayer.play": "Пусни аудио",
"availableAgents": "Налични Агенти",
"backToBottom": "Към най-новото",
"beforeUnload.confirmLeave": "Заявка все още се изпълнява. Искате ли да напуснете въпреки това?",
@@ -120,6 +122,18 @@
"createModal.groupPlaceholder": "Опишете какво трябва да прави тази група...",
"createModal.groupTitle": "Какво трябва да прави вашата група?",
"createModal.placeholder": "Опишете какво трябва да прави вашият агент...",
"createModal.skillSuggestion.actions.createAnyway": "Създай агент въпреки това",
"createModal.skillSuggestion.actions.createAnywayHint": "Умението не е подходящо?",
"createModal.skillSuggestion.actions.install": "Инсталирай умение",
"createModal.skillSuggestion.actions.installing": "Инсталиране…",
"createModal.skillSuggestion.actions.openSkills": "Преглед в Умения",
"createModal.skillSuggestion.actions.tryInLobeAI": "Използвай в {{name}}",
"createModal.skillSuggestion.description": "Това изглежда като повторяем работен процес. Инсталирайте умението веднъж, след това го използвайте в различни агенти.",
"createModal.skillSuggestion.installError": "Умението не беше инсталирано. Опитайте отново или създайте агент въпреки това.",
"createModal.skillSuggestion.installed.description": "Можете да използвате това умение в {{name}}, или да го активирате за всеки агент.",
"createModal.skillSuggestion.installed.ready": "Готово в {{name}}",
"createModal.skillSuggestion.installed.title": "Умението е инсталирано",
"createModal.skillSuggestion.title": "Умението може да е по-подходящо",
"createModal.title": "Какво трябва да прави вашият агент?",
"createTask.assignee": "Изпълнител",
"createTask.collapse": "Скрий полето",
@@ -166,6 +180,8 @@
"extendParams.title": "Разширени функции на модела",
"extendParams.urlContext.desc": "Когато е активирано, уеб връзките ще се анализират автоматично, за да се извлече съдържанието на страницата",
"extendParams.urlContext.title": "Извличане на съдържание от уеб връзки",
"floatingChatPanel.collapse": "Свий чата",
"floatingChatPanel.expand": "Разшири чата",
"followUpPlaceholder": "Последващо действие. Използвайте @, за да възлагате задачи на други агенти.",
"followUpPlaceholderHeterogeneous": "Последващ въпрос.",
"gatewayMode.beta": "Бета",
@@ -219,9 +235,13 @@
"heteroAgent.cloudRepo.noRepos": "Няма конфигурирани хранилища. Добавете ги в настройките на агента.",
"heteroAgent.cloudRepo.notSet": "Няма избрано хранилище",
"heteroAgent.cloudRepo.sectionTitle": "Хранилища",
"heteroAgent.executionTarget.auto": "Автоматично",
"heteroAgent.executionTarget.autoDesc": "Използвайте онлайн устройство автоматично, избирайки едно, когато има няколко налични",
"heteroAgent.executionTarget.downloadDesktop": "Изтеглете десктоп приложението",
"heteroAgent.executionTarget.downloadDesktopDesc": "Стартирайте агенти с достъп до вашия компютър",
"heteroAgent.executionTarget.downloadDesktopTitle": "Изтеглете десктоп приложението",
"heteroAgent.executionTarget.gateway": "Шлюз",
"heteroAgent.executionTarget.gatewayDesc": "Изпълнявайте през шлюза на устройството, за да могат други клиенти да следят напредъка",
"heteroAgent.executionTarget.infoTooltip": "Изберете отдалечено устройство, за да управлявате тази машина от уеба. \"Това устройство\" изпълнява агента локално и е достъпно само в десктоп приложението.",
"heteroAgent.executionTarget.loading": "Зареждане на устройства...",
"heteroAgent.executionTarget.local": "Това устройство",
@@ -231,10 +251,12 @@
"heteroAgent.executionTarget.noneDesc": "Няма активирано устройство",
"heteroAgent.executionTarget.offline": "Офлайн",
"heteroAgent.executionTarget.online": "Онлайн",
"heteroAgent.executionTarget.personalGroup": "Лично",
"heteroAgent.executionTarget.sandbox": "Облачен пясъчник",
"heteroAgent.executionTarget.sandboxDesc": "Изпълнява се в временен облачен пясъчник",
"heteroAgent.executionTarget.title": "Устройство за изпълнение",
"heteroAgent.executionTarget.unknownDevice": "Неизвестно устройство",
"heteroAgent.executionTarget.workspaceGroup": "Работно пространство",
"heteroAgent.fullAccess.label": "Пълен достъп",
"heteroAgent.fullAccess.tooltip": "Claude Code работи локално с пълен достъп за четене/запис в работната директория. Превключването на режимите на достъп все още не е налично.",
"heteroAgent.resumeReset.cwdChanged": "Работната директория е променена. Предишната сесия на Claude Code може да бъде продължена само от оригиналната ѝ директория, затова е започнат нов разговор.",
@@ -631,6 +653,8 @@
"taskDetail.artifacts": "Артефакти",
"taskDetail.blockedBy": "Блокирано от {{id}}",
"taskDetail.cancelSchedule": "Отмени графика",
"taskDetail.closeDetail": "Затвори детайла",
"taskDetail.collapseReply": "Свий",
"taskDetail.comment.cancel": "Отказ",
"taskDetail.comment.delete": "Изтриване",
"taskDetail.comment.deleteConfirm.content": "Този коментар ще бъде изтрит завинаги.",
@@ -657,6 +681,7 @@
"taskDetail.notFound.backToTasks": "Обратно към всички задачи",
"taskDetail.notFound.desc": "Тази задача може да е изтрита или нямате разрешение да я видите.",
"taskDetail.notFound.title": "Задачата не е намерена",
"taskDetail.openDetail": "Отвори детайла",
"taskDetail.pauseTask": "Пауза на задачата",
"taskDetail.priority.high": "Висок",
"taskDetail.priority.low": "Нисък",
@@ -925,9 +950,9 @@
"workflow.collapse": "Свий",
"workflow.expandFull": "Разгъни напълно",
"workflow.failedSuffix": "(неуспешно)",
"workflow.summaryAcrossTools": "през {{count}} инструмента",
"workflow.summaryCallsLead": "{{count}} обаждания: {{tools}}",
"workflow.summaryFailed": "{{count}} неуспешни",
"workflow.summaryMoreTools": "{{count}} вида инструменти",
"workflow.summaryTotalCalls": "{{count}} извиквания общо",
"workflow.thoughtForDuration": "Мисли в продължение на {{duration}}",
"workflow.toolDisplayName.activateDevice": "Активирано устройство",
"workflow.toolDisplayName.activateSkill": "Активира умение",
@@ -1043,6 +1068,7 @@
"workingPanel.resources.deleteTitle": "Delete document?",
"workingPanel.resources.deleteTitleMulti": "Изтриване на {{count}} елемента?",
"workingPanel.resources.empty": "Все още няма документи. Документите, свързани с този агент, ще се показват тук.",
"workingPanel.resources.emptyDocuments": "Все още няма документи. Създайте един с + горе.",
"workingPanel.resources.error": "Failed to load resources",
"workingPanel.resources.filter.documents": "Документи",
"workingPanel.resources.filter.skills": "Умения",
+18 -2
View File
@@ -444,6 +444,23 @@
"tab.setting": "Настройки",
"tab.tasks": "Задачи",
"tab.video": "Видео",
"taskTemplate.action.connect.button": "Свържете {{provider}}",
"taskTemplate.action.connect.error": "Свързването не бе успешно, моля опитайте отново.",
"taskTemplate.action.connect.popupBlocked": "Изскачащият прозорец за свързване е блокиран. Разрешете изскачащи прозорци в браузъра си, за да продължите.",
"taskTemplate.action.connect.short": "Свържете",
"taskTemplate.action.connecting": "Изчакване за оторизация…",
"taskTemplate.action.create.error": "Неуспешно създаване на задача. Моля, опитайте отново.",
"taskTemplate.action.create.success": "Добавена е планирана задача. Намерете я в Lobe AI.",
"taskTemplate.action.createButton": "Добавете задача",
"taskTemplate.action.creating": "Създаване...",
"taskTemplate.action.dismiss.error": "Неуспешно отхвърляне. Моля, опитайте отново.",
"taskTemplate.action.dismiss.tooltip": "Не се интересувам",
"taskTemplate.action.refresh.button": "Обновете",
"taskTemplate.card.templateTag": "Шаблон",
"taskTemplate.schedule.daily": "Всеки ден в {{time}}",
"taskTemplate.schedule.editableAfterCreateTooltip": "Можете да коригирате графика след създаването на задачата.",
"taskTemplate.schedule.weekly": "Всеки {{weekday}} в {{time}}",
"taskTemplate.section.title": "Опитайте тези планирани задачи",
"telemetry.allow": "Разреши",
"telemetry.deny": "Откажи",
"telemetry.desc": "Бихме искали анонимно да събираме информация за използването, за да подобрим {{appName}} и да ви предоставим по-добро потребителско изживяване. Можете да го изключите по всяко време в Настройки - Относно.",
@@ -474,15 +491,14 @@
"userPanel.email": "Имейл поддръжка",
"userPanel.feedback": "Свържете се с нас",
"userPanel.help": "Център за помощ",
"userPanel.inviteFriend": "Поканете приятел",
"userPanel.moveGuide": "Бутонът за настройки е преместен тук",
"userPanel.plans": "Абонаментни планове",
"userPanel.profile": "Акаунт",
"userPanel.setting": "Настройки",
"userPanel.upgradePlan": "Надграждане на плана",
"userPanel.usages": "Статистика на използване",
"userPanel.workspaceCredits": "Кредити за работно пространство",
"userPanel.workspaceSetting": "Настройки на работното пространство",
"userPanel.workspaceUsages": "Използване на работното пространство",
"version": "Версия",
"zoom": "Увеличение"
}
+2
View File
@@ -101,6 +101,7 @@
"LocalFile.action.open": "Отвори",
"LocalFile.action.showInFolder": "Покажи в папката",
"MaxTokenSlider.unlimited": "Неограничено",
"ModelSelect.featureTag.audio": "Този модел поддържа разпознаване на аудио вход.",
"ModelSelect.featureTag.custom": "Персонализиран модел, по подразбиране поддържа извикване на функции и визуално разпознаване. Моля, проверете наличността на тези възможности според конкретната ситуация.",
"ModelSelect.featureTag.file": "Този модел поддържа качване на файлове за четене и разпознаване.",
"ModelSelect.featureTag.functionCall": "Този модел поддържа извикване на функции.",
@@ -114,6 +115,7 @@
"ModelSwitchPanel.byModel": "По модел",
"ModelSwitchPanel.byProvider": "По доставчик",
"ModelSwitchPanel.detail.abilities": "Възможности",
"ModelSwitchPanel.detail.abilities.audio": "Аудио",
"ModelSwitchPanel.detail.abilities.files": "Файлове",
"ModelSwitchPanel.detail.abilities.functionCall": "Извикване на инструмент",
"ModelSwitchPanel.detail.abilities.imageOutput": "Изход на изображение",
-3
View File
@@ -38,9 +38,6 @@
"brief.title": "Дневен обзор",
"brief.viewAllTasks": "Преглед на всички задачи",
"brief.viewRun": "Преглед на изпълнението",
"freeCreditBadge.cta": "Започнете безплатен пробен период",
"freeCreditBadge.dismiss": "Отхвърли",
"freeCreditBadge.label": "Ексклузивни безплатни кредити за {{model}}",
"project.create": "Нов проект",
"project.deleteConfirm": "Този проект ще бъде изтрит и не може да бъде възстановен. Потвърдете, за да продължите.",
"recommendations.heteroAgent.cta": "Добавяне на агент",
+4 -4
View File
@@ -5,10 +5,12 @@
"authorize.footer.agreement": "Продължавайки, потвърждаваш, че си прочел и се съгласяваш с <terms>Общите условия</terms> и <privacy>Политиката за поверителност</privacy>.",
"authorize.footer.privacy": "Политика за поверителност",
"authorize.footer.terms": "Общи условия",
"authorize.scenes.connector.confirm": "Продължете към Market",
"authorize.scenes.connector.description": "Market се използва само за стартиране на тази услуга за упълномощаване. Вашият акаунт в {{appName}} остава отделен.",
"authorize.scenes.connector.subtitle": "Влезте в Market, за да свържете и упълномощите тази обществена услуга.",
"authorize.scenes.connector.title": "Свържете обществена услуга",
"authorize.scenes.mcp.subtitle": "Създайте профил в общността, за да инсталирате и използвате това умение от общността.",
"authorize.scenes.mcp.title": "Инсталиране на умение от общността",
"authorize.scenes.publish.subtitle": "Създайте профил в общността, за да публикувате и управлявате вашия списък в общността.",
"authorize.scenes.publish.title": "Публикуване в общността",
"authorize.scenes.sandbox.subtitle": "Създайте профил в общността, за да използвате този инструмент в пясъчника на общността.",
"authorize.scenes.sandbox.title": "Опитайте пясъчника на общността",
"authorize.subtitle": "Създай профил в общността, за да публикуваш и управляваш обяви в нея.",
@@ -50,8 +52,6 @@
"messages.handoffTimeout": "Времето за удостоверяване изтече. Завършете го в браузъра си и опитайте отново.",
"messages.loading": "Стартиране на процеса на удостоверяване...",
"messages.success.cloudMcpInstall": "Удостоверяването е успешно! Вече можете да инсталирате умението Cloud MCP.",
"messages.success.submit": "Удостоверяването е успешно! Вече можете да публикувате своя агент.",
"messages.success.upload": "Удостоверяването е успешно! Вече можете да публикувате нова версия.",
"profileSetup.cancel": "Отказ",
"profileSetup.confirmChangeUserId.cancel": "Отказ",
"profileSetup.confirmChangeUserId.confirm": "Промени потребителското име",
+4 -2
View File
@@ -222,6 +222,7 @@
"providerModels.item.modelConfig.extendParams.options.effort.hint": "За Claude Opus 4.6; контролира нивото на усилие (ниско/средно/високо/максимално).",
"providerModels.item.modelConfig.extendParams.options.enableAdaptiveThinking.hint": "За Claude Opus 4.6; включва или изключва адаптивното мислене.",
"providerModels.item.modelConfig.extendParams.options.enableReasoning.hint": "За Claude, DeepSeek и други модели с логическо мислене; отключва по-задълбочено разсъждение.",
"providerModels.item.modelConfig.extendParams.options.glm5_2ReasoningEffort.hint": "За GLM-5.2; контролира усилието за разсъждение с високи и максимални нива.",
"providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint": "За серията GPT-5; контролира интензивността на разсъждението.",
"providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint": "За серията GPT-5.1; контролира интензивността на разсъждението.",
"providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "За серията GPT-5.2 Pro; контролира интензивността на разсъждението.",
@@ -256,6 +257,7 @@
"providerModels.item.modelConfig.files.title": "Поддръжка на качване на файлове",
"providerModels.item.modelConfig.functionCall.extra": "Тази настройка активира възможността на модела да използва инструменти. Дали ще ги използва ефективно зависи от самия модел. Моля, тествайте.",
"providerModels.item.modelConfig.functionCall.title": "Поддръжка на използване на инструменти",
"providerModels.item.modelConfig.id.duplicate": "Модел с този ID вече съществува. Използвайте различен ID за модела.",
"providerModels.item.modelConfig.id.extra": "Не може да се променя след създаване и ще се използва като ID на модела при извикване",
"providerModels.item.modelConfig.id.placeholder": "Въведете ID на модела, напр. gpt-4o или claude-3.5-sonnet",
"providerModels.item.modelConfig.id.title": "ID на модела",
@@ -270,11 +272,11 @@
"providerModels.item.modelConfig.tokens.title": "Максимален контекстов прозорец",
"providerModels.item.modelConfig.tokens.unlimited": "Неограничен",
"providerModels.item.modelConfig.type.extra": "Различните типове модели имат различни приложения и възможности",
"providerModels.item.modelConfig.type.options.asr": "Реч към текст",
"providerModels.item.modelConfig.type.options.chat": "Чат",
"providerModels.item.modelConfig.type.options.embedding": "Вграждане",
"providerModels.item.modelConfig.type.options.image": "Генериране на изображения",
"providerModels.item.modelConfig.type.options.realtime": "Чат в реално време",
"providerModels.item.modelConfig.type.options.stt": "Реч към текст",
"providerModels.item.modelConfig.type.options.text2music": "Текст към музика",
"providerModels.item.modelConfig.type.options.tts": "Текст към реч",
"providerModels.item.modelConfig.type.options.video": "Генериране на видео",
@@ -323,10 +325,10 @@
"providerModels.list.total": "Налични {{count}} модела",
"providerModels.searchNotFound": "Няма намерени резултати от търсенето",
"providerModels.tabs.all": "Всички",
"providerModels.tabs.asr": "ASR",
"providerModels.tabs.chat": "Чат",
"providerModels.tabs.embedding": "Вграждане",
"providerModels.tabs.image": "Изображение",
"providerModels.tabs.stt": "ASR",
"providerModels.tabs.tts": "TTS",
"providerModels.tabs.video": "Видео",
"sortModal.success": "Сортирането е успешно обновено",
+9 -69
View File
@@ -440,60 +440,7 @@
"llm.proxyUrl.title": "API прокси URL",
"llm.waitingForMore": "Очакват се <1>още модели</1>, следете за новини",
"llm.waitingForMoreLinkAriaLabel": "Отвори формуляр за заявка към доставчик",
"marketPublish.forkConfirm.by": "от {{author}}",
"marketPublish.forkConfirm.confirm": "Потвърди публикуване",
"marketPublish.forkConfirm.confirmGroup": "Потвърди публикуване",
"marketPublish.forkConfirm.description": "Вие сте на път да публикувате производна версия, базирана на съществуващ агент от общността. Вашият нов агент ще бъде създаден като отделен запис в пазара.",
"marketPublish.forkConfirm.descriptionGroup": "Вие сте на път да публикувате производна версия, базирана на съществуваща група от общността. Вашата нова група ще бъде създадена като отделен запис в пазара.",
"marketPublish.forkConfirm.title": "Публикуване на производен агент",
"marketPublish.forkConfirm.titleGroup": "Публикуване на производна група",
"marketPublish.modal.changelog.extra": "Опишете основните промени и подобрения в тази версия",
"marketPublish.modal.changelog.label": "Дневник на промените",
"marketPublish.modal.changelog.maxLengthError": "Дневникът на промените не трябва да надвишава 500 знака",
"marketPublish.modal.changelog.placeholder": "Въведете дневника на промените",
"marketPublish.modal.changelog.required": "Моля, въведете дневника на промените",
"marketPublish.modal.comparison.local": "Текуща локална версия",
"marketPublish.modal.comparison.remote": "Публикувана версия",
"marketPublish.modal.identifier.extra": "Това е уникалният идентификатор на агента. Използвайте малки букви, цифри и тирета.",
"marketPublish.modal.identifier.label": "Идентификатор на агента",
"marketPublish.modal.identifier.lengthError": "Идентификаторът трябва да е между 3 и 50 знака",
"marketPublish.modal.identifier.patternError": "Идентификаторът може да съдържа само малки букви, цифри и тирета",
"marketPublish.modal.identifier.placeholder": "Въведете уникален идентификатор за агента, напр. web-development",
"marketPublish.modal.identifier.required": "Моля, въведете идентификатор на агента",
"marketPublish.modal.loading.fetchingRemote": "Зареждане на отдалечени данни...",
"marketPublish.modal.loading.submit": "Изпращане на агента...",
"marketPublish.modal.loading.submitGroup": "Изпращане на групата...",
"marketPublish.modal.loading.upload": "Публикуване на нова версия...",
"marketPublish.modal.loading.uploadGroup": "Публикуване на нова версия на групата...",
"marketPublish.modal.messages.createVersionFailed": "Неуспешно създаване на версия: {{message}}",
"marketPublish.modal.messages.fetchRemoteFailed": "Неуспешно извличане на данни за отдалечения агент",
"marketPublish.modal.messages.missingIdentifier": "Този агент все още няма идентификатор в Общността.",
"marketPublish.modal.messages.noGroup": "Няма избрана група",
"marketPublish.modal.messages.notAuthenticated": "Първо влезте в профила си в Общността.",
"marketPublish.modal.messages.publishFailed": "Публикуването не бе успешно: {{message}}",
"marketPublish.modal.submitButton": "Публикувай",
"marketPublish.modal.title.submit": "Сподели в Общността на агентите",
"marketPublish.modal.title.upload": "Публикувай нова версия",
"marketPublish.resultModal.message": "Вашият агент е изпратен за преглед. След одобрение ще бъде автоматично публикуван.",
"marketPublish.resultModal.messageGroup": "Вашата група е изпратена за преглед. След одобрение ще бъде автоматично публикувана.",
"marketPublish.resultModal.title": "Успешно изпращане",
"marketPublish.resultModal.view": "Виж в Общността",
"marketPublish.status.underReview": "В процес на преглед",
"marketPublish.submit.button": "Сподели в Общността",
"marketPublish.submit.tooltip": "Споделете този агент в Общността",
"marketPublish.submitGroup.tooltip": "Споделете тази група с общността",
"marketPublish.upload.button": "Публикувай нова версия",
"marketPublish.upload.tooltip": "Публикувайте нова версия в Общността на агентите",
"marketPublish.uploadGroup.tooltip": "Публикувайте нова версия в общността на групите",
"marketPublish.validation.communitySetupRequired.action": "Настройте сега",
"marketPublish.validation.communitySetupRequired.desc": "Това работно пространство все още не е настроило своя профил в Общността. Настройте го преди публикуване в Общността.",
"marketPublish.validation.communitySetupRequired.memberHint": "Това работно пространство все още не е настроило своя профил в Общността. Помолете собственик на работното пространство да го настрои преди публикуване в Общността.",
"marketPublish.validation.communitySetupRequired.title": "Първо настройте профила в Общността",
"marketPublish.validation.confirmPublish": "Публикуване в Маркета?",
"marketPublish.validation.confirmPublishDesc": "След публикуване, това съдържание ще бъде публично видимо в маркета и достъпно за всеки да го открие и използва.",
"marketPublish.validation.emptyName": "Не може да се публикува: Името е задължително",
"marketPublish.validation.emptySystemRole": "Не може да се публикува: Системната роля е задължителна",
"marketPublish.validation.underReview": "Вашата нова версия в момента се преглежда. Моля, изчакайте одобрение преди да публикувате нова версия.",
"memory.effort.desc": "Контролирайте колко агресивно AI извлича и актуализира паметта.",
"memory.effort.high": "Високо — Проактивно извличане и актуализации",
"memory.effort.level.high": "Високо",
@@ -515,14 +462,6 @@
"myAgents.actions.deprecateLoading": "Оттегляне на агента...",
"myAgents.actions.deprecateSuccess": "Агентът е оттеглен",
"myAgents.actions.edit": "Редактирай агент",
"myAgents.actions.publish": "Публикувай агент",
"myAgents.actions.publishError": "Неуспешно публикуване на агента",
"myAgents.actions.publishLoading": "Публикуване на агента...",
"myAgents.actions.publishSuccess": "Агентът е публикуван",
"myAgents.actions.unpublish": "Скрий агента",
"myAgents.actions.unpublishError": "Неуспешно скриване на агента",
"myAgents.actions.unpublishLoading": "Скриване на агента...",
"myAgents.actions.unpublishSuccess": "Агентът е скрит",
"myAgents.actions.viewDetail": "Виж подробности",
"myAgents.detail.category": "Категория",
"myAgents.detail.description": "Описание",
@@ -587,7 +526,6 @@
"plugin.settings.title": "Конфигурация на умение {{id}}",
"plugin.settings.tooltip": "Конфигурация на умение",
"plugin.store": "Магазин за умения",
"publishToCommunity": "Публикувай в общността",
"serviceModel.contextLimit.placeholder": "Ограничение на контекста",
"serviceModel.memoryModels.title": "Модели на паметта",
"serviceModel.modelAssignments.title": "Назначения на модели",
@@ -955,13 +893,6 @@
"storageOverage.usage.estimatedCharge": "Очаквана такса за цикъл",
"storageOverage.usage.incurredCharge": "Начислено за този цикъл",
"storageOverage.usage.overage": "Превишение",
"submitAgentModal.button": "Изпрати агент",
"submitAgentModal.identifier": "Идентификатор на агент",
"submitAgentModal.metaMiss": "Моля, попълнете информацията за агента преди изпращане. Тя трябва да включва име, описание и тагове",
"submitAgentModal.placeholder": "Въведете уникален идентификатор за агента, напр. web-development",
"submitAgentModal.success": "Агентът е изпратен успешно",
"submitAgentModal.tooltips": "Сподели в общността на агентите",
"submitGroupModal.tooltips": "Сподели в общността на групите",
"sync.device.deviceName.hint": "Добавете име за по-лесно разпознаване",
"sync.device.deviceName.placeholder": "Въведете име на устройство",
"sync.device.deviceName.title": "Име на устройство",
@@ -1086,6 +1017,7 @@
"tools.activation.auto": "Автоматично",
"tools.activation.auto.desc": "Интелигентно",
"tools.activation.fixed.hint": "Винаги включено — управлява се от приложението и не може да бъде изключено",
"tools.activation.pin": "Пин",
"tools.activation.pinned": "Закрепено",
"tools.activation.pinned.desc": "Винаги включено",
"tools.add": "Добави умение",
@@ -2047,6 +1979,14 @@
"workspace.wizard.step3.title": "Добре дошли в {{name}}!",
"workspace.wizard.title": "Създайте работно пространство",
"workspaceSetting.breadcrumb.settings": "Настройки",
"workspaceSetting.devices.desc": "Споделени машини, записани в това работно пространство. Членовете могат да изпълняват агенти на тях.",
"workspaceSetting.devices.empty": "Все още няма устройства в работното пространство.",
"workspaceSetting.devices.enrollDesc": "Изпълнете това на машината, която искате да споделите (само за собственика на работното пространство):",
"workspaceSetting.devices.enrollTitle": "Добавете устройство",
"workspaceSetting.devices.heroDesc": "Запишете споделена машина — сървър за изграждане или екипен Mac — и всеки член може да изпълнява агенти на нея: четене/запис на файлове, изпълнение на команди и използване на системни инструменти.",
"workspaceSetting.devices.heroTitle": "Свържете първото устройство към работното пространство",
"workspaceSetting.devices.offline": "Офлайн",
"workspaceSetting.devices.online": "Онлайн",
"workspaceSetting.group.admin": "Администратор",
"workspaceSetting.group.agent": "Агент",
"workspaceSetting.group.general": "Общи",
+10 -5
View File
@@ -147,10 +147,6 @@
"limitation.chat.topupSuccess.title": "Успешно зареждане",
"limitation.expired.desc": "Вашите {{plan}} изчислителни кредити изтекоха на {{expiredAt}}. Надградете плана си сега, за да получите нови кредити.",
"limitation.expired.title": "Изчислителните кредити са изтекли",
"limitation.fableCampaign.desc": "Claude Fable 5 е модел с висока цена. Кредитите за пробната кампания са изчерпани. Надградете плана си, за да продължите да използвате Fable.",
"limitation.fableCampaign.title": "Изчерпани кредити за пробната версия на Fable",
"limitation.fableCampaign.upgrade": "Надградете плана",
"limitation.fableCampaign.upgradeToPlan": "Надградете до {{plan}}",
"limitation.hobby.action": "Конфигурирано, продължи разговора",
"limitation.hobby.configAPI": "Конфигурирай API",
"limitation.hobby.desc": "Вашите безплатни изчислителни кредити са изчерпани. Моля, конфигурирайте персонализиран API на модел, за да продължите.",
@@ -342,7 +338,14 @@
"plans.workspace.noSharedCredits": "Без споделени кредити",
"plans.workspace.sharedCredits": "~{{count}} кредити / месец",
"plans.workspace.solo": "Соло (1 член)",
"promoBanner.fableYearly": "Годишните абонати получават {{percent}}% отстъпка за ограничено време",
"plansModal.creditLimit.desc": "Надградете плана си, за да отключите повече месечни кредити и да продължите работа без прекъсване.",
"plansModal.creditLimit.title": "Нямате кредити",
"plansModal.default.desc": "Отключете повече капацитет и разширени функции.",
"plansModal.default.title": "Надградете плана си",
"plansModal.fileStorageLimit.desc": "Вашето файлово хранилище е пълно. Надградете, за да продължите да качвате и управлявате файлове.",
"plansModal.fileStorageLimit.title": "Достигнат лимит за хранилище",
"plansModal.modelAccess.desc": "Този модел е достъпен в платените планове. Надградете, за да използвате пълния набор от модели.",
"plansModal.modelAccess.title": "Отключете всички модели",
"qa.desc": "Ако въпросът ви не е отговорен, проверете <1>Документацията на продукта</1> за още често задавани въпроси или се свържете с нас.",
"qa.detail": "Виж подробности",
"qa.list.credit.a": "Изчислителните кредити са мярка, използвана от {{cloud}} за измерване на използването на AI модели при извикване на модели. Различните AI модели консумират различно количество изчислителни кредити.",
@@ -398,6 +401,8 @@
"referral.errors.invalidFormat": "Невалиден формат на кода, въведете 2–8 букви, цифри или долни черти",
"referral.errors.selfReferral": "Не можете да използвате собствения си код за покана",
"referral.errors.updateFailed": "Неуспешна актуализация, моля опитайте отново по-късно",
"referral.hero.description": "Споделете вашия реферален линк по-долу. След като вашият приятел направи първото си плащане, и двамата ще получите {{reward}}M кредита.",
"referral.hero.title": "Поканете приятели, и двамата печелите <0>{{reward}}M кредита</0>",
"referral.inviteCode.description": "Споделете своя уникален код за покана, за да поканите приятели да се регистрират",
"referral.inviteCode.title": "Моят код за покана",
"referral.inviteLink.description": "Копирайте линка и го споделете с приятели. И двамата получавате кредити, след като вашият приятел направи плащане.",
+1
View File
@@ -25,6 +25,7 @@
"actions.unmarkCompleted": "Отбележи като активна",
"defaultTitle": "Тема по подразбиране",
"displayItems": "Показване на елементи",
"draft": "[Чернова]",
"duplicateLoading": "Копиране на тема...",
"duplicateSuccess": "Темата беше успешно копирана",
"failedStatusTip": "Този процес срещна грешка — отворете го, за да разгледате.",
+28 -2
View File
@@ -41,6 +41,8 @@
"artifact.thinking": "Denkt nach",
"artifact.thought": "Denkprozess",
"artifact.unknownTitle": "Unbenannte Arbeit",
"audioPlayer.pause": "Audio pausieren",
"audioPlayer.play": "Audio abspielen",
"availableAgents": "Verfügbare Agenten",
"backToBottom": "Zum neuesten Beitrag springen",
"beforeUnload.confirmLeave": "Eine Anfrage läuft noch. Trotzdem verlassen?",
@@ -120,6 +122,18 @@
"createModal.groupPlaceholder": "Beschreiben Sie, was diese Gruppe tun soll...",
"createModal.groupTitle": "Was soll Ihre Gruppe tun?",
"createModal.placeholder": "Beschreiben Sie, was Ihr Agent tun soll...",
"createModal.skillSuggestion.actions.createAnyway": "Agent trotzdem erstellen",
"createModal.skillSuggestion.actions.createAnywayHint": "Skill passt nicht?",
"createModal.skillSuggestion.actions.install": "Skill installieren",
"createModal.skillSuggestion.actions.installing": "Wird installiert…",
"createModal.skillSuggestion.actions.openSkills": "In Skills anzeigen",
"createModal.skillSuggestion.actions.tryInLobeAI": "In {{name}} verwenden",
"createModal.skillSuggestion.description": "Das scheint ein wiederverwendbarer Workflow zu sein. Installieren Sie den Skill einmal und nutzen Sie ihn dann in verschiedenen Agents.",
"createModal.skillSuggestion.installError": "Skill wurde nicht installiert. Versuchen Sie es erneut oder erstellen Sie trotzdem einen Agent.",
"createModal.skillSuggestion.installed.description": "Sie können diesen Skill in {{name}} verwenden oder ihn für jeden Agent aktivieren.",
"createModal.skillSuggestion.installed.ready": "Bereit in {{name}}",
"createModal.skillSuggestion.installed.title": "Skill installiert",
"createModal.skillSuggestion.title": "Ein Skill könnte besser passen",
"createModal.title": "Was soll Ihr Agent tun?",
"createTask.assignee": "Zuständige Person",
"createTask.collapse": "Eingabe ausblenden",
@@ -166,6 +180,8 @@
"extendParams.title": "Modellerweiterungsfunktionen",
"extendParams.urlContext.desc": "Wenn aktiviert, werden Weblinks automatisch analysiert, um den tatsächlichen Seiteninhalt abzurufen",
"extendParams.urlContext.title": "Webseiteninhalte extrahieren",
"floatingChatPanel.collapse": "Chat minimieren",
"floatingChatPanel.expand": "Chat maximieren",
"followUpPlaceholder": "Folgen Sie nach. @, um Aufgaben anderen Agenten zuzuweisen.",
"followUpPlaceholderHeterogeneous": "Weiter ausführen.",
"gatewayMode.beta": "Beta",
@@ -219,9 +235,13 @@
"heteroAgent.cloudRepo.noRepos": "Keine Repositories konfiguriert. Fügen Sie diese in den Agenteneinstellungen hinzu.",
"heteroAgent.cloudRepo.notSet": "Kein Repository ausgewählt",
"heteroAgent.cloudRepo.sectionTitle": "Repositories",
"heteroAgent.executionTarget.auto": "Automatisch",
"heteroAgent.executionTarget.autoDesc": "Automatisch ein Online-Gerät verwenden, wobei eines ausgewählt wird, wenn mehrere verfügbar sind",
"heteroAgent.executionTarget.downloadDesktop": "Desktop-App herunterladen",
"heteroAgent.executionTarget.downloadDesktopDesc": "Führen Sie Agenten mit Zugriff auf Ihren Computer aus",
"heteroAgent.executionTarget.downloadDesktopTitle": "Desktop-App herunterladen",
"heteroAgent.executionTarget.gateway": "Gateway",
"heteroAgent.executionTarget.gatewayDesc": "Über das Geräte-Gateway ausführen, damit andere Clients den Fortschritt verfolgen können",
"heteroAgent.executionTarget.infoTooltip": "Wählen Sie ein Remote-Gerät aus, um diese Maschine über das Web zu steuern. \"Dieses Gerät\" führt den Agenten lokal aus und ist nur in der Desktop-App verfügbar.",
"heteroAgent.executionTarget.loading": "Geräte werden geladen…",
"heteroAgent.executionTarget.local": "Dieses Gerät",
@@ -231,10 +251,12 @@
"heteroAgent.executionTarget.noneDesc": "Kein Gerät aktiviert",
"heteroAgent.executionTarget.offline": "Offline",
"heteroAgent.executionTarget.online": "Online",
"heteroAgent.executionTarget.personalGroup": "Persönlich",
"heteroAgent.executionTarget.sandbox": "Cloud-Sandbox",
"heteroAgent.executionTarget.sandboxDesc": "In einer temporären Cloud-Sandbox ausführen",
"heteroAgent.executionTarget.title": "Ausführungsgerät",
"heteroAgent.executionTarget.unknownDevice": "Unbekanntes Gerät",
"heteroAgent.executionTarget.workspaceGroup": "Arbeitsbereich",
"heteroAgent.fullAccess.label": "Vollzugriff",
"heteroAgent.fullAccess.tooltip": "Claude Code läuft lokal mit vollständigem Lese-/Schreibzugriff auf das Arbeitsverzeichnis. Das Umschalten von Berechtigungsmodi ist derzeit nicht verfügbar.",
"heteroAgent.resumeReset.cwdChanged": "Arbeitsverzeichnis geändert. Die vorherige Claude-Code-Sitzung kann nur aus dem ursprünglichen Verzeichnis fortgesetzt werden, daher wurde eine neue Unterhaltung gestartet.",
@@ -631,6 +653,8 @@
"taskDetail.artifacts": "Artefakte",
"taskDetail.blockedBy": "Blockiert durch {{id}}",
"taskDetail.cancelSchedule": "Planung abbrechen",
"taskDetail.closeDetail": "Details schließen",
"taskDetail.collapseReply": "Einklappen",
"taskDetail.comment.cancel": "Abbrechen",
"taskDetail.comment.delete": "Löschen",
"taskDetail.comment.deleteConfirm.content": "Dieser Kommentar wird dauerhaft entfernt.",
@@ -657,6 +681,7 @@
"taskDetail.notFound.backToTasks": "Zurück zu allen Aufgaben",
"taskDetail.notFound.desc": "Diese Aufgabe wurde möglicherweise gelöscht oder Sie haben keine Berechtigung, sie anzusehen.",
"taskDetail.notFound.title": "Aufgabe nicht gefunden",
"taskDetail.openDetail": "Details öffnen",
"taskDetail.pauseTask": "Aufgabe pausieren",
"taskDetail.priority.high": "Hoch",
"taskDetail.priority.low": "Niedrig",
@@ -925,9 +950,9 @@
"workflow.collapse": "Einklappen",
"workflow.expandFull": "Vollständig ausklappen",
"workflow.failedSuffix": "(fehlgeschlagen)",
"workflow.summaryAcrossTools": "über {{count}} Tools",
"workflow.summaryCallsLead": "{{count}} Anrufe: {{tools}}",
"workflow.summaryFailed": "{{count}} fehlgeschlagen",
"workflow.summaryMoreTools": "{{count}} Werkzeugarten",
"workflow.summaryTotalCalls": "{{count}} Aufrufe insgesamt",
"workflow.thoughtForDuration": "Nachgedacht für {{duration}}",
"workflow.toolDisplayName.activateDevice": "Gerät aktiviert",
"workflow.toolDisplayName.activateSkill": "Eine Fähigkeit wurde aktiviert",
@@ -1043,6 +1068,7 @@
"workingPanel.resources.deleteTitle": "Delete document?",
"workingPanel.resources.deleteTitleMulti": "{{count}} Elemente löschen?",
"workingPanel.resources.empty": "Noch keine Dokumente. Dokumente, die diesem Agenten zugeordnet sind, werden hier angezeigt.",
"workingPanel.resources.emptyDocuments": "Noch keine Dokumente. Erstellen Sie eines mit dem + oben.",
"workingPanel.resources.error": "Failed to load resources",
"workingPanel.resources.filter.documents": "Dokumente",
"workingPanel.resources.filter.skills": "Fähigkeiten",
+18 -2
View File
@@ -444,6 +444,23 @@
"tab.setting": "Einstellungen",
"tab.tasks": "Aufgaben",
"tab.video": "Video",
"taskTemplate.action.connect.button": "Verbinden mit {{provider}}",
"taskTemplate.action.connect.error": "Verbindung fehlgeschlagen, bitte versuchen Sie es erneut.",
"taskTemplate.action.connect.popupBlocked": "Verbindungspopup blockiert. Erlauben Sie Popups in Ihrem Browser, um fortzufahren.",
"taskTemplate.action.connect.short": "Verbinden",
"taskTemplate.action.connecting": "Warten auf Autorisierung…",
"taskTemplate.action.create.error": "Aufgabe konnte nicht erstellt werden. Bitte versuchen Sie es erneut.",
"taskTemplate.action.create.success": "Geplante Aufgabe hinzugefügt. Finden Sie sie in Lobe AI.",
"taskTemplate.action.createButton": "Aufgabe hinzufügen",
"taskTemplate.action.creating": "Wird erstellt...",
"taskTemplate.action.dismiss.error": "Abweisung fehlgeschlagen. Bitte versuchen Sie es erneut.",
"taskTemplate.action.dismiss.tooltip": "Nicht interessiert",
"taskTemplate.action.refresh.button": "Aktualisieren",
"taskTemplate.card.templateTag": "Vorlage",
"taskTemplate.schedule.daily": "Jeden Tag um {{time}}",
"taskTemplate.schedule.editableAfterCreateTooltip": "Sie können den Zeitplan nach der Erstellung der Aufgabe anpassen.",
"taskTemplate.schedule.weekly": "Jeden {{weekday}} um {{time}}",
"taskTemplate.section.title": "Probieren Sie diese geplanten Aufgaben aus",
"telemetry.allow": "Zulassen",
"telemetry.deny": "Ablehnen",
"telemetry.desc": "Wir möchten anonym Nutzungsdaten erfassen, um {{appName}} zu verbessern und Ihnen ein besseres Produkterlebnis zu bieten. Sie können dies jederzeit unter Einstellungen Über deaktivieren.",
@@ -474,15 +491,14 @@
"userPanel.email": "E-Mail-Support",
"userPanel.feedback": "Kontaktieren Sie uns",
"userPanel.help": "Hilfezentrum",
"userPanel.inviteFriend": "Einen Freund einladen",
"userPanel.moveGuide": "Die Schaltfläche für Einstellungen wurde hierher verschoben",
"userPanel.plans": "Abonnementpläne",
"userPanel.profile": "Konto",
"userPanel.setting": "Einstellungen",
"userPanel.upgradePlan": "Plan upgraden",
"userPanel.usages": "Nutzungsstatistiken",
"userPanel.workspaceCredits": "Arbeitsbereich-Guthaben",
"userPanel.workspaceSetting": "Arbeitsbereich-Einstellungen",
"userPanel.workspaceUsages": "Arbeitsbereich-Nutzung",
"version": "Version",
"zoom": "Zoom"
}
+2
View File
@@ -101,6 +101,7 @@
"LocalFile.action.open": "Öffnen",
"LocalFile.action.showInFolder": "Im Ordner anzeigen",
"MaxTokenSlider.unlimited": "Unbegrenzt",
"ModelSelect.featureTag.audio": "Dieses Modell unterstützt die Erkennung von Audioeingaben.",
"ModelSelect.featureTag.custom": "Benutzerdefiniertes Modell, unterstützt standardmäßig Funktionsaufrufe und visuelle Erkennung. Bitte prüfen Sie die tatsächliche Verfügbarkeit dieser Funktionen.",
"ModelSelect.featureTag.file": "Dieses Modell unterstützt das Hochladen von Dateien zur Analyse und Erkennung.",
"ModelSelect.featureTag.functionCall": "Dieses Modell unterstützt Funktionsaufrufe.",
@@ -114,6 +115,7 @@
"ModelSwitchPanel.byModel": "Nach Modell",
"ModelSwitchPanel.byProvider": "Nach Anbieter",
"ModelSwitchPanel.detail.abilities": "Fähigkeiten",
"ModelSwitchPanel.detail.abilities.audio": "Audio",
"ModelSwitchPanel.detail.abilities.files": "Dateien",
"ModelSwitchPanel.detail.abilities.functionCall": "Werkzeugaufruf",
"ModelSwitchPanel.detail.abilities.imageOutput": "Bildausgabe",
-3
View File
@@ -38,9 +38,6 @@
"brief.title": "Tagesübersicht",
"brief.viewAllTasks": "Alle Aufgaben anzeigen",
"brief.viewRun": "Lauf anzeigen",
"freeCreditBadge.cta": "Kostenlose Testversion starten",
"freeCreditBadge.dismiss": "Schließen",
"freeCreditBadge.label": "Exklusive Gratisguthaben für {{model}}",
"project.create": "Neues Projekt",
"project.deleteConfirm": "Dieses Projekt wird gelöscht und kann nicht wiederhergestellt werden. Bestätigen Sie, um fortzufahren.",
"recommendations.heteroAgent.cta": "Agent hinzufügen",
+4 -4
View File
@@ -5,10 +5,12 @@
"authorize.footer.agreement": "Indem du fortfährst, bestätigst du, dass du die <terms>Nutzungsbedingungen</terms> und die <privacy>Datenschutzerklärung</privacy> gelesen hast und ihnen zustimmst.",
"authorize.footer.privacy": "Datenschutzerklärung",
"authorize.footer.terms": "Nutzungsbedingungen",
"authorize.scenes.connector.confirm": "Weiter zum Markt",
"authorize.scenes.connector.description": "Der Markt wird nur verwendet, um diese Dienstautorisierung zu starten. Ihr {{appName}}-Konto bleibt getrennt.",
"authorize.scenes.connector.subtitle": "Melden Sie sich beim Markt an, um diesen Gemeinschaftsdienst zu verbinden und zu autorisieren.",
"authorize.scenes.connector.title": "Gemeinschaftsdienst verbinden",
"authorize.scenes.mcp.subtitle": "Erstellen Sie ein Community-Profil, um diese Fähigkeit aus der Community zu installieren und auszuführen.",
"authorize.scenes.mcp.title": "Community-Fähigkeit installieren",
"authorize.scenes.publish.subtitle": "Erstellen Sie ein Community-Profil, um Ihr Angebot in der Community zu veröffentlichen und zu verwalten.",
"authorize.scenes.publish.title": "In der Community veröffentlichen",
"authorize.scenes.sandbox.subtitle": "Erstellen Sie ein Community-Profil, um dieses Tool im Community-Sandbox-Modus auszuführen.",
"authorize.scenes.sandbox.title": "Community-Sandbox ausprobieren",
"authorize.subtitle": "Erstelle ein Community-Profil, um Einträge innerhalb der Community zu verwalten und einzureichen.",
@@ -50,8 +52,6 @@
"messages.handoffTimeout": "Autorisierung abgelaufen. Schließe sie im Browser ab und versuche es erneut.",
"messages.loading": "Autorisierungsvorgang wird gestartet...",
"messages.success.cloudMcpInstall": "Autorisierung erfolgreich! Du kannst jetzt die Cloud MCP-Funktion installieren.",
"messages.success.submit": "Autorisierung erfolgreich! Du kannst jetzt deinen Agenten veröffentlichen.",
"messages.success.upload": "Autorisierung erfolgreich! Du kannst jetzt eine neue Version veröffentlichen.",
"profileSetup.cancel": "Abbrechen",
"profileSetup.confirmChangeUserId.cancel": "Abbrechen",
"profileSetup.confirmChangeUserId.confirm": "Benutzer-ID ändern",
+4 -2
View File
@@ -222,6 +222,7 @@
"providerModels.item.modelConfig.extendParams.options.effort.hint": "Für Claude Opus 4.6; steuert das Anstrengungsniveau (niedrig/mittel/hoch/maximal).",
"providerModels.item.modelConfig.extendParams.options.enableAdaptiveThinking.hint": "Für Claude Opus 4.6; schaltet adaptives Denken ein oder aus.",
"providerModels.item.modelConfig.extendParams.options.enableReasoning.hint": "Für Claude-, DeepSeek- und andere Modelle mit logischem Denken; ermöglicht tiefere Überlegungen.",
"providerModels.item.modelConfig.extendParams.options.glm5_2ReasoningEffort.hint": "Für GLM-5.2; steuert den Aufwand für logisches Denken mit den Stufen Hoch und Maximal.",
"providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint": "Für die GPT-5-Serie; steuert die Intensität des logischen Denkens.",
"providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint": "Für die GPT-5.1-Serie; steuert die Intensität des logischen Denkens.",
"providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "Für die GPT-5.2 Pro-Serie; steuert die Intensität des logischen Denkens.",
@@ -256,6 +257,7 @@
"providerModels.item.modelConfig.files.title": "Datei-Upload-Unterstützung",
"providerModels.item.modelConfig.functionCall.extra": "Diese Konfiguration aktiviert nur die Fähigkeit des Modells, Werkzeuge zu verwenden. Ob das Modell diese tatsächlich nutzen kann, hängt vom Modell selbst ab. Bitte testen Sie die Nutzbarkeit selbst.",
"providerModels.item.modelConfig.functionCall.title": "Werkzeugnutzung unterstützen",
"providerModels.item.modelConfig.id.duplicate": "Ein Modell mit dieser ID existiert bereits. Verwenden Sie eine andere Modell-ID.",
"providerModels.item.modelConfig.id.extra": "Kann nach Erstellung nicht mehr geändert werden und wird als Modell-ID bei KI-Aufrufen verwendet",
"providerModels.item.modelConfig.id.placeholder": "Bitte geben Sie die Modell-ID ein, z.B. gpt-4o oder claude-3.5-sonnet",
"providerModels.item.modelConfig.id.title": "Modell-ID",
@@ -270,11 +272,11 @@
"providerModels.item.modelConfig.tokens.title": "Maximales Kontextfenster",
"providerModels.item.modelConfig.tokens.unlimited": "Unbegrenzt",
"providerModels.item.modelConfig.type.extra": "Verschiedene Modelltypen haben unterschiedliche Anwendungsfälle und Fähigkeiten",
"providerModels.item.modelConfig.type.options.asr": "Sprache-zu-Text",
"providerModels.item.modelConfig.type.options.chat": "Chat",
"providerModels.item.modelConfig.type.options.embedding": "Embedding",
"providerModels.item.modelConfig.type.options.image": "Bildgenerierung",
"providerModels.item.modelConfig.type.options.realtime": "Echtzeit-Chat",
"providerModels.item.modelConfig.type.options.stt": "Sprache-zu-Text",
"providerModels.item.modelConfig.type.options.text2music": "Text-zu-Musik",
"providerModels.item.modelConfig.type.options.tts": "Text-zu-Sprache",
"providerModels.item.modelConfig.type.options.video": "Videoerstellung",
@@ -323,10 +325,10 @@
"providerModels.list.total": "{{count}} Modelle verfügbar",
"providerModels.searchNotFound": "Keine Suchergebnisse gefunden",
"providerModels.tabs.all": "Alle",
"providerModels.tabs.asr": "ASR",
"providerModels.tabs.chat": "Chat",
"providerModels.tabs.embedding": "Embedding",
"providerModels.tabs.image": "Bild",
"providerModels.tabs.stt": "ASR",
"providerModels.tabs.tts": "TTS",
"providerModels.tabs.video": "Video",
"sortModal.success": "Sortierung erfolgreich aktualisiert",
+9 -69
View File
@@ -440,60 +440,7 @@
"llm.proxyUrl.title": "API-Proxy-URL",
"llm.waitingForMore": "Weitere Modelle sind <1>in Planung</1>, bleib dran",
"llm.waitingForMoreLinkAriaLabel": "Anbieter-Anfrageformular öffnen",
"marketPublish.forkConfirm.by": "von {{author}}",
"marketPublish.forkConfirm.confirm": "Veröffentlichung bestätigen",
"marketPublish.forkConfirm.confirmGroup": "Veröffentlichung bestätigen",
"marketPublish.forkConfirm.description": "Sie sind dabei, eine abgeleitete Version basierend auf einem bestehenden Agenten aus der Community zu veröffentlichen. Ihr neuer Agent wird als separater Eintrag im Marktplatz erstellt.",
"marketPublish.forkConfirm.descriptionGroup": "Sie sind dabei, eine abgeleitete Version basierend auf einer bestehenden Gruppe aus der Community zu veröffentlichen. Ihre neue Gruppe wird als separater Eintrag im Marktplatz erstellt.",
"marketPublish.forkConfirm.title": "Abgeleiteten Agenten veröffentlichen",
"marketPublish.forkConfirm.titleGroup": "Abgeleitete Gruppe veröffentlichen",
"marketPublish.modal.changelog.extra": "Beschreiben Sie die wichtigsten Änderungen und Verbesserungen in dieser Version",
"marketPublish.modal.changelog.label": "Änderungsprotokoll",
"marketPublish.modal.changelog.maxLengthError": "Das Änderungsprotokoll darf 500 Zeichen nicht überschreiten",
"marketPublish.modal.changelog.placeholder": "Änderungsprotokoll eingeben",
"marketPublish.modal.changelog.required": "Bitte geben Sie das Änderungsprotokoll ein",
"marketPublish.modal.comparison.local": "Aktuelle lokale Version",
"marketPublish.modal.comparison.remote": "Derzeit veröffentlichte Version",
"marketPublish.modal.identifier.extra": "Dies ist die eindeutige Kennung des Agenten. Verwenden Sie Kleinbuchstaben, Zahlen und Bindestriche.",
"marketPublish.modal.identifier.label": "Agentenkennung",
"marketPublish.modal.identifier.lengthError": "Die Kennung muss zwischen 3 und 50 Zeichen lang sein",
"marketPublish.modal.identifier.patternError": "Die Kennung darf nur Kleinbuchstaben, Zahlen und Bindestriche enthalten",
"marketPublish.modal.identifier.placeholder": "Eindeutige Kennung für den Agenten eingeben, z.B. web-entwicklung",
"marketPublish.modal.identifier.required": "Bitte geben Sie die Agentenkennung ein",
"marketPublish.modal.loading.fetchingRemote": "Remote-Daten werden geladen...",
"marketPublish.modal.loading.submit": "Agent wird übermittelt...",
"marketPublish.modal.loading.submitGroup": "Gruppe wird übermittelt...",
"marketPublish.modal.loading.upload": "Neue Version wird veröffentlicht...",
"marketPublish.modal.loading.uploadGroup": "Neue Gruppenversion wird veröffentlicht...",
"marketPublish.modal.messages.createVersionFailed": "Version konnte nicht erstellt werden: {{message}}",
"marketPublish.modal.messages.fetchRemoteFailed": "Remote-Agentendaten konnten nicht abgerufen werden",
"marketPublish.modal.messages.missingIdentifier": "Dieser Agent hat noch keine Community-Kennung.",
"marketPublish.modal.messages.noGroup": "Keine Gruppe ausgewählt",
"marketPublish.modal.messages.notAuthenticated": "Melden Sie sich zuerst bei Ihrem Community-Konto an.",
"marketPublish.modal.messages.publishFailed": "Veröffentlichung fehlgeschlagen: {{message}}",
"marketPublish.modal.submitButton": "Veröffentlichen",
"marketPublish.modal.title.submit": "In der Agenten-Community teilen",
"marketPublish.modal.title.upload": "Neue Version veröffentlichen",
"marketPublish.resultModal.message": "Ihr Agent wurde zur Überprüfung eingereicht. Nach Freigabe wird er automatisch veröffentlicht.",
"marketPublish.resultModal.messageGroup": "Ihre Gruppe wurde zur Überprüfung eingereicht. Nach der Freigabe wird sie automatisch veröffentlicht.",
"marketPublish.resultModal.title": "Erfolgreich eingereicht",
"marketPublish.resultModal.view": "In der Community anzeigen",
"marketPublish.status.underReview": "In Prüfung",
"marketPublish.submit.button": "In der Community teilen",
"marketPublish.submit.tooltip": "Diesen Agenten in der Community teilen",
"marketPublish.submitGroup.tooltip": "Diese Gruppe mit der Community teilen",
"marketPublish.upload.button": "Neue Version veröffentlichen",
"marketPublish.upload.tooltip": "Eine neue Version in der Agenten-Community veröffentlichen",
"marketPublish.uploadGroup.tooltip": "Neue Version in der Gruppen-Community veröffentlichen",
"marketPublish.validation.communitySetupRequired.action": "Jetzt einrichten",
"marketPublish.validation.communitySetupRequired.desc": "Dieser Arbeitsbereich hat sein Community-Profil noch nicht eingerichtet. Richten Sie es ein, bevor Sie es in der Community veröffentlichen.",
"marketPublish.validation.communitySetupRequired.memberHint": "Dieser Arbeitsbereich hat sein Community-Profil noch nicht eingerichtet. Bitten Sie einen Arbeitsbereichsbesitzer, es einzurichten, bevor Sie es in der Community veröffentlichen.",
"marketPublish.validation.communitySetupRequired.title": "Community-Profil zuerst einrichten",
"marketPublish.validation.confirmPublish": "Im Markt veröffentlichen?",
"marketPublish.validation.confirmPublishDesc": "Nach der Veröffentlichung ist dieser Inhalt öffentlich im Markt sichtbar und für jeden zugänglich.",
"marketPublish.validation.emptyName": "Veröffentlichung nicht möglich: Name ist erforderlich",
"marketPublish.validation.emptySystemRole": "Veröffentlichung nicht möglich: Systemrolle ist erforderlich",
"marketPublish.validation.underReview": "Ihre neue Version wird derzeit geprüft. Bitte warten Sie auf die Genehmigung, bevor Sie eine neue Version veröffentlichen.",
"memory.effort.desc": "Steuern Sie, wie aggressiv die KI Speicher abruft und aktualisiert.",
"memory.effort.high": "Hoch — Proaktives Abrufen und Aktualisieren",
"memory.effort.level.high": "Hoch",
@@ -515,14 +462,6 @@
"myAgents.actions.deprecateLoading": "Agent wird veraltet...",
"myAgents.actions.deprecateSuccess": "Agent veraltet",
"myAgents.actions.edit": "Agent bearbeiten",
"myAgents.actions.publish": "Agent veröffentlichen",
"myAgents.actions.publishError": "Agent konnte nicht veröffentlicht werden",
"myAgents.actions.publishLoading": "Agent wird veröffentlicht...",
"myAgents.actions.publishSuccess": "Agent veröffentlicht",
"myAgents.actions.unpublish": "Agent zurückziehen",
"myAgents.actions.unpublishError": "Agent konnte nicht zurückgezogen werden",
"myAgents.actions.unpublishLoading": "Agent wird zurückgezogen...",
"myAgents.actions.unpublishSuccess": "Agent zurückgezogen",
"myAgents.actions.viewDetail": "Details anzeigen",
"myAgents.detail.category": "Kategorie",
"myAgents.detail.description": "Beschreibung",
@@ -587,7 +526,6 @@
"plugin.settings.title": "{{id}} Fähigkeitenkonfiguration",
"plugin.settings.tooltip": "Fähigkeitenkonfiguration",
"plugin.store": "Fähigkeiten-Store",
"publishToCommunity": "In der Community veröffentlichen",
"serviceModel.contextLimit.placeholder": "Kontextlimit",
"serviceModel.memoryModels.title": "Speichermodelle",
"serviceModel.modelAssignments.title": "Modellzuweisungen",
@@ -955,13 +893,6 @@
"storageOverage.usage.estimatedCharge": "Geschätzte Zyklusgebühr",
"storageOverage.usage.incurredCharge": "In diesem Zyklus angefallen",
"storageOverage.usage.overage": "Überlastung",
"submitAgentModal.button": "Agent einreichen",
"submitAgentModal.identifier": "Agentenkennung",
"submitAgentModal.metaMiss": "Bitte vervollständigen Sie die Agenteninformationen vor dem Einreichen. Name, Beschreibung und Tags sind erforderlich.",
"submitAgentModal.placeholder": "Geben Sie eine eindeutige Kennung für den Agenten ein, z.B. web-entwicklung",
"submitAgentModal.success": "Agent erfolgreich eingereicht",
"submitAgentModal.tooltips": "In der Agenten-Community teilen",
"submitGroupModal.tooltips": "In der Gruppen-Community teilen",
"sync.device.deviceName.hint": "Fügen Sie einen Namen zur leichteren Identifizierung hinzu",
"sync.device.deviceName.placeholder": "Gerätenamen eingeben",
"sync.device.deviceName.title": "Gerätename",
@@ -1086,6 +1017,7 @@
"tools.activation.auto": "Automatisch",
"tools.activation.auto.desc": "Intelligent",
"tools.activation.fixed.hint": "Immer aktiv — wird von der App verwaltet und kann nicht deaktiviert werden",
"tools.activation.pin": "Pin",
"tools.activation.pinned": "Angeheftet",
"tools.activation.pinned.desc": "Immer an",
"tools.add": "Fähigkeit hinzufügen",
@@ -2047,6 +1979,14 @@
"workspace.wizard.step3.title": "Willkommen bei {{name}}!",
"workspace.wizard.title": "Arbeitsbereich erstellen",
"workspaceSetting.breadcrumb.settings": "Einstellungen",
"workspaceSetting.devices.desc": "Gemeinsam genutzte Geräte, die in diesem Arbeitsbereich registriert sind. Mitglieder können darauf Agenten ausführen.",
"workspaceSetting.devices.empty": "Noch keine Geräte im Arbeitsbereich.",
"workspaceSetting.devices.enrollDesc": "Führen Sie dies auf dem Gerät aus, das Sie teilen möchten (nur für Arbeitsbereichsinhaber):",
"workspaceSetting.devices.enrollTitle": "Ein Gerät hinzufügen",
"workspaceSetting.devices.heroDesc": "Registrieren Sie ein gemeinsam genutztes Gerät einen Build-Server oder einen Team-Mac und jedes Mitglied kann darauf Agenten ausführen: Dateien lesen/schreiben, Befehle ausführen und Systemtools aufrufen.",
"workspaceSetting.devices.heroTitle": "Verbinden Sie Ihr erstes Arbeitsbereichsgerät",
"workspaceSetting.devices.offline": "Offline",
"workspaceSetting.devices.online": "Online",
"workspaceSetting.group.admin": "Admin",
"workspaceSetting.group.agent": "Agent",
"workspaceSetting.group.general": "Allgemein",
+10 -5
View File
@@ -147,10 +147,6 @@
"limitation.chat.topupSuccess.title": "Aufladung erfolgreich",
"limitation.expired.desc": "Deine {{plan}}-Rechenguthaben sind am {{expiredAt}} abgelaufen. Upgrade jetzt, um neue Guthaben zu erhalten.",
"limitation.expired.title": "Rechenguthaben abgelaufen",
"limitation.fableCampaign.desc": "Claude Fable 5 ist ein hochpreisiges Modell. Die Kampagnen-Testguthaben sind aufgebraucht. Aktualisieren Sie Ihren Plan, um Fable weiterhin zu nutzen.",
"limitation.fableCampaign.title": "Fable-Testguthaben aufgebraucht",
"limitation.fableCampaign.upgrade": "Plan aktualisieren",
"limitation.fableCampaign.upgradeToPlan": "Upgrade auf {{plan}}",
"limitation.hobby.action": "Konfiguriert, weiter chatten",
"limitation.hobby.configAPI": "API konfigurieren",
"limitation.hobby.desc": "Deine kostenlosen Rechenguthaben sind aufgebraucht. Bitte konfiguriere eine benutzerdefinierte Modell-API, um fortzufahren.",
@@ -342,7 +338,14 @@
"plans.workspace.noSharedCredits": "Keine geteilten Credits",
"plans.workspace.sharedCredits": "~{{count}} Credits / Monat",
"plans.workspace.solo": "Solo (1 Mitglied)",
"promoBanner.fableYearly": "Jahresabonnenten erhalten {{percent}}% Rabatt für eine begrenzte Zeit",
"plansModal.creditLimit.desc": "Aktualisieren Sie Ihren Plan, um mehr monatliche Credits freizuschalten und ohne Unterbrechung weiterzuarbeiten.",
"plansModal.creditLimit.title": "Ihnen sind die Credits ausgegangen",
"plansModal.default.desc": "Schalten Sie mehr Kapazität und erweiterte Funktionen frei.",
"plansModal.default.title": "Aktualisieren Sie Ihren Plan",
"plansModal.fileStorageLimit.desc": "Ihr Dateispeicher ist voll. Aktualisieren Sie, um weiterhin Dateien hochzuladen und zu verwalten.",
"plansModal.fileStorageLimit.title": "Speicherlimit erreicht",
"plansModal.modelAccess.desc": "Dieses Modell ist in kostenpflichtigen Plänen verfügbar. Aktualisieren Sie, um die vollständige Modellauswahl zu nutzen.",
"plansModal.modelAccess.title": "Alle Modelle freischalten",
"qa.desc": "Wenn Ihre Frage nicht beantwortet wurde, besuchen Sie die <1>Produktdokumentation</1> für weitere FAQs oder kontaktieren Sie uns.",
"qa.detail": "Details anzeigen",
"qa.list.credit.a": "Rechen-Credits sind eine Metrik von {{cloud}}, um die Nutzung von KI-Modellen zu messen. Verschiedene Modelle verbrauchen unterschiedlich viele Credits.",
@@ -398,6 +401,8 @@
"referral.errors.invalidFormat": "Ungültiges Format, bitte 28 Buchstaben, Zahlen oder Unterstriche eingeben",
"referral.errors.selfReferral": "Du kannst deinen eigenen Einladungscode nicht verwenden",
"referral.errors.updateFailed": "Aktualisierung fehlgeschlagen, bitte später erneut versuchen",
"referral.hero.description": "Teilen Sie Ihren Empfehlungslink unten. Nachdem Ihr Freund seine erste Zahlung geleistet hat, erhalten Sie beide jeweils {{reward}}M Credits.",
"referral.hero.title": "Laden Sie Freunde ein, Sie verdienen beide <0>{{reward}}M Credits</0>",
"referral.inviteCode.description": "Teilen Sie Ihren exklusiven Empfehlungscode, um Freunde einzuladen",
"referral.inviteCode.title": "Mein Empfehlungscode",
"referral.inviteLink.description": "Kopieren Sie den Link und teilen Sie ihn mit Freunden. Beide erhalten Credits, nachdem Ihr Freund eine Zahlung getätigt hat.",
+1
View File
@@ -25,6 +25,7 @@
"actions.unmarkCompleted": "Als aktiv markieren",
"defaultTitle": "Standardthema",
"displayItems": "Elemente anzeigen",
"draft": "[Entwurf]",
"duplicateLoading": "Thema wird kopiert...",
"duplicateSuccess": "Thema erfolgreich kopiert",
"failedStatusTip": "Dieser Durchlauf hat einen Fehler — öffnen Sie ihn, um nachzusehen.",
+2
View File
@@ -252,10 +252,12 @@
"heteroAgent.executionTarget.noneDesc": "No device enabled",
"heteroAgent.executionTarget.offline": "Offline",
"heteroAgent.executionTarget.online": "Online",
"heteroAgent.executionTarget.personalGroup": "Personal",
"heteroAgent.executionTarget.sandbox": "Cloud Sandbox",
"heteroAgent.executionTarget.sandboxDesc": "Run in an ephemeral cloud sandbox",
"heteroAgent.executionTarget.title": "Execution Device",
"heteroAgent.executionTarget.unknownDevice": "Unknown device",
"heteroAgent.executionTarget.workspaceGroup": "Workspace",
"heteroAgent.fullAccess.label": "Full access",
"heteroAgent.fullAccess.tooltip": "Claude Code runs locally with full read/write access to the working directory. Switching permission modes is not available yet.",
"heteroAgent.resumeReset.cwdChanged": "Working directory changed. Previous Claude Code session can only be resumed from its original directory, so a new conversation has started.",
+1
View File
@@ -498,6 +498,7 @@
"userPanel.setting": "Settings",
"userPanel.upgradePlan": "Upgrade Plan",
"userPanel.usages": "Usage",
"userPanel.workspaceSetting": "Workspace Settings",
"version": "Version",
"zoom": "Zoom"
}
-3
View File
@@ -38,9 +38,6 @@
"brief.title": "Brief",
"brief.viewAllTasks": "View all tasks",
"brief.viewRun": "View run",
"freeCreditBadge.cta": "Start free trial",
"freeCreditBadge.dismiss": "Dismiss",
"freeCreditBadge.label": "Exclusive free credits for {{model}}",
"project.create": "New project",
"project.deleteConfirm": "This project will be deleted and can't be recovered. Confirm to continue.",
"recommendations.heteroAgent.cta": "Add Agent",
+8
View File
@@ -1979,6 +1979,14 @@
"workspace.wizard.step3.title": "Welcome to {{name}}!",
"workspace.wizard.title": "Create Workspace",
"workspaceSetting.breadcrumb.settings": "Settings",
"workspaceSetting.devices.desc": "Shared machines enrolled into this workspace. Members can run agents on them.",
"workspaceSetting.devices.empty": "No workspace devices yet.",
"workspaceSetting.devices.enrollDesc": "Run this on the machine you want to share (workspace owner only):",
"workspaceSetting.devices.enrollTitle": "Add a device",
"workspaceSetting.devices.heroDesc": "Enroll a shared machine — a build server or a team Mac — and every member can run agents on it: read/write files, run commands, and call system tools.",
"workspaceSetting.devices.heroTitle": "Connect your first workspace device",
"workspaceSetting.devices.offline": "Offline",
"workspaceSetting.devices.online": "Online",
"workspaceSetting.group.admin": "Admin",
"workspaceSetting.group.agent": "Agent",
"workspaceSetting.group.general": "General",
-5
View File
@@ -147,10 +147,6 @@
"limitation.chat.topupSuccess.title": "Top-up Successful",
"limitation.expired.desc": "Your {{plan}} credits expired on {{expiredAt}}. Upgrade your plan now to get credits.",
"limitation.expired.title": "Credits Expired",
"limitation.fableCampaign.desc": "Claude Fable 5 is a high-cost model. The campaign trial credits have been used up. Upgrade your plan to keep using Fable.",
"limitation.fableCampaign.title": "Fable Trial Credits Used Up",
"limitation.fableCampaign.upgrade": "Upgrade Plan",
"limitation.fableCampaign.upgradeToPlan": "Upgrade to {{plan}}",
"limitation.hobby.action": "Configured, continue chatting",
"limitation.hobby.configAPI": "Configure API",
"limitation.hobby.desc": "Your free credits have been exhausted. Please configure a custom model API to continue.",
@@ -350,7 +346,6 @@
"plansModal.fileStorageLimit.title": "Storage limit reached",
"plansModal.modelAccess.desc": "This model is available on paid plans. Upgrade to use the full model lineup.",
"plansModal.modelAccess.title": "Unlock all models",
"promoBanner.fableYearly": "Annual subscribers get {{percent}}% usage off for a limited time",
"qa.desc": "If your question is not answered, check <1>Product Documentation</1> for more FAQs, or contact us.",
"qa.detail": "View Details",
"qa.list.credit.a": "Credits are how {{cloud}} measures AI model usage. Different AI models consume different amounts of credits.",
+28 -2
View File
@@ -41,6 +41,8 @@
"artifact.thinking": "Pensando",
"artifact.thought": "Proceso de pensamiento",
"artifact.unknownTitle": "Trabajo sin título",
"audioPlayer.pause": "Pausar audio",
"audioPlayer.play": "Reproducir audio",
"availableAgents": "Agentes disponibles",
"backToBottom": "Ir al último mensaje",
"beforeUnload.confirmLeave": "Una solicitud aún está en curso. ¿Salir de todos modos?",
@@ -120,6 +122,18 @@
"createModal.groupPlaceholder": "Describe lo que debería hacer este grupo...",
"createModal.groupTitle": "¿Qué debería hacer tu grupo?",
"createModal.placeholder": "Describe lo que debería hacer tu agente...",
"createModal.skillSuggestion.actions.createAnyway": "Crear agente de todos modos",
"createModal.skillSuggestion.actions.createAnywayHint": "¿Habilidad no adecuada?",
"createModal.skillSuggestion.actions.install": "Instalar habilidad",
"createModal.skillSuggestion.actions.installing": "Instalando…",
"createModal.skillSuggestion.actions.openSkills": "Ver en Habilidades",
"createModal.skillSuggestion.actions.tryInLobeAI": "Usar en {{name}}",
"createModal.skillSuggestion.description": "Esto parece un flujo de trabajo reutilizable. Instala la habilidad una vez y luego úsala en todos los agentes.",
"createModal.skillSuggestion.installError": "La habilidad no se instaló. Inténtalo de nuevo o crea un agente de todos modos.",
"createModal.skillSuggestion.installed.description": "Puedes usar esta habilidad en {{name}} o habilitarla para cualquier agente.",
"createModal.skillSuggestion.installed.ready": "Listo en {{name}}",
"createModal.skillSuggestion.installed.title": "Habilidad instalada",
"createModal.skillSuggestion.title": "Una habilidad podría encajar mejor",
"createModal.title": "¿Qué debería hacer tu agente?",
"createTask.assignee": "Asignado a",
"createTask.collapse": "Ocultar entrada",
@@ -166,6 +180,8 @@
"extendParams.title": "Funciones de Extensión del Modelo",
"extendParams.urlContext.desc": "Cuando está habilitado, los enlaces web se analizarán automáticamente para recuperar el contenido real de la página",
"extendParams.urlContext.title": "Extraer contenido de enlaces web",
"floatingChatPanel.collapse": "Colapsar chat",
"floatingChatPanel.expand": "Expandir chat",
"followUpPlaceholder": "Seguimiento. Usa @ para asignar tareas a otros agentes.",
"followUpPlaceholderHeterogeneous": "Continuar.",
"gatewayMode.beta": "Beta",
@@ -219,9 +235,13 @@
"heteroAgent.cloudRepo.noRepos": "No hay repositorios configurados. Agrégalos en la configuración del agente.",
"heteroAgent.cloudRepo.notSet": "Ningún repositorio seleccionado",
"heteroAgent.cloudRepo.sectionTitle": "Repositorios",
"heteroAgent.executionTarget.auto": "Automático",
"heteroAgent.executionTarget.autoDesc": "Usar un dispositivo en línea automáticamente, eligiendo uno cuando haya varios disponibles",
"heteroAgent.executionTarget.downloadDesktop": "Obtener la aplicación de escritorio",
"heteroAgent.executionTarget.downloadDesktopDesc": "Ejecuta agentes con acceso a tu computadora",
"heteroAgent.executionTarget.downloadDesktopTitle": "Obtener la aplicación de escritorio",
"heteroAgent.executionTarget.gateway": "Puerta de enlace",
"heteroAgent.executionTarget.gatewayDesc": "Ejecutar a través de la puerta de enlace del dispositivo para que otros clientes puedan seguir el progreso",
"heteroAgent.executionTarget.infoTooltip": "Elige un dispositivo remoto para controlar esa máquina desde la web. \"Este dispositivo\" ejecuta el agente localmente y solo está disponible dentro de la aplicación de escritorio.",
"heteroAgent.executionTarget.loading": "Cargando dispositivos…",
"heteroAgent.executionTarget.local": "Este dispositivo",
@@ -231,10 +251,12 @@
"heteroAgent.executionTarget.noneDesc": "No hay dispositivos habilitados",
"heteroAgent.executionTarget.offline": "Desconectado",
"heteroAgent.executionTarget.online": "Conectado",
"heteroAgent.executionTarget.personalGroup": "Personal",
"heteroAgent.executionTarget.sandbox": "Sandbox en la nube",
"heteroAgent.executionTarget.sandboxDesc": "Ejecutar en un sandbox efímero en la nube",
"heteroAgent.executionTarget.title": "Dispositivo de Ejecución",
"heteroAgent.executionTarget.unknownDevice": "Dispositivo desconocido",
"heteroAgent.executionTarget.workspaceGroup": "Espacio de trabajo",
"heteroAgent.fullAccess.label": "Acceso completo",
"heteroAgent.fullAccess.tooltip": "Claude Code se ejecuta localmente con acceso completo de lectura y escritura al directorio de trabajo. Cambiar los modos de permiso aún no está disponible.",
"heteroAgent.resumeReset.cwdChanged": "El directorio de trabajo ha cambiado. La sesión anterior de Claude Code solo puede reanudarse desde su directorio original, por lo que se ha iniciado una nueva conversación.",
@@ -631,6 +653,8 @@
"taskDetail.artifacts": "Artefactos",
"taskDetail.blockedBy": "Bloqueado por {{id}}",
"taskDetail.cancelSchedule": "Cancelar programación",
"taskDetail.closeDetail": "Cerrar detalle",
"taskDetail.collapseReply": "Colapsar",
"taskDetail.comment.cancel": "Cancelar",
"taskDetail.comment.delete": "Eliminar",
"taskDetail.comment.deleteConfirm.content": "Este comentario se eliminará permanentemente.",
@@ -657,6 +681,7 @@
"taskDetail.notFound.backToTasks": "Volver a todas las tareas",
"taskDetail.notFound.desc": "Es posible que esta tarea haya sido eliminada o que no tengas permiso para verla.",
"taskDetail.notFound.title": "Tarea no encontrada",
"taskDetail.openDetail": "Abrir detalle",
"taskDetail.pauseTask": "Pausar tarea",
"taskDetail.priority.high": "Alta",
"taskDetail.priority.low": "Baja",
@@ -925,9 +950,9 @@
"workflow.collapse": "Contraer",
"workflow.expandFull": "Expandir completamente",
"workflow.failedSuffix": "(fallido)",
"workflow.summaryAcrossTools": "a través de {{count}} herramientas",
"workflow.summaryCallsLead": "{{count}} llamadas: {{tools}}",
"workflow.summaryFailed": "{{count}} fallos",
"workflow.summaryMoreTools": "{{count}} tipos de herramientas",
"workflow.summaryTotalCalls": "{{count}} llamadas en total",
"workflow.thoughtForDuration": "Reflexionó durante {{duration}}",
"workflow.toolDisplayName.activateDevice": "Dispositivo activado",
"workflow.toolDisplayName.activateSkill": "Activó una habilidad",
@@ -1043,6 +1068,7 @@
"workingPanel.resources.deleteTitle": "Delete document?",
"workingPanel.resources.deleteTitleMulti": "¿Eliminar {{count}} elementos?",
"workingPanel.resources.empty": "Aún no hay documentos. Los documentos asociados con este agente aparecerán aquí.",
"workingPanel.resources.emptyDocuments": "Aún no hay documentos. Crea uno con el + arriba.",
"workingPanel.resources.error": "Failed to load resources",
"workingPanel.resources.filter.documents": "Documentos",
"workingPanel.resources.filter.skills": "Habilidades",
+18 -2
View File
@@ -444,6 +444,23 @@
"tab.setting": "Configuración",
"tab.tasks": "Tareas",
"tab.video": "Vídeo",
"taskTemplate.action.connect.button": "Conectar {{provider}}",
"taskTemplate.action.connect.error": "Conexión fallida, por favor inténtalo de nuevo.",
"taskTemplate.action.connect.popupBlocked": "Ventana emergente de conexión bloqueada. Permite ventanas emergentes en tu navegador para continuar.",
"taskTemplate.action.connect.short": "Conectar",
"taskTemplate.action.connecting": "Esperando autorización…",
"taskTemplate.action.create.error": "No se pudo crear la tarea. Por favor, inténtalo de nuevo.",
"taskTemplate.action.create.success": "Tarea programada añadida. Encuéntrala en Lobe AI.",
"taskTemplate.action.createButton": "Añadir tarea",
"taskTemplate.action.creating": "Creando...",
"taskTemplate.action.dismiss.error": "No se pudo descartar. Por favor, inténtalo de nuevo.",
"taskTemplate.action.dismiss.tooltip": "No me interesa",
"taskTemplate.action.refresh.button": "Actualizar",
"taskTemplate.card.templateTag": "Plantilla",
"taskTemplate.schedule.daily": "Todos los días a las {{time}}",
"taskTemplate.schedule.editableAfterCreateTooltip": "Puedes ajustar el horario después de crear la tarea.",
"taskTemplate.schedule.weekly": "Todos los {{weekday}} a las {{time}}",
"taskTemplate.section.title": "Prueba estas tareas programadas",
"telemetry.allow": "Permitir",
"telemetry.deny": "Denegar",
"telemetry.desc": "Nos gustaría recopilar información de uso de forma anónima para ayudarnos a mejorar {{appName}} y ofrecerte una mejor experiencia. Puedes desactivar esta opción en cualquier momento en Configuración - Acerca de.",
@@ -474,15 +491,14 @@
"userPanel.email": "Soporte por correo",
"userPanel.feedback": "Contáctanos",
"userPanel.help": "Centro de ayuda",
"userPanel.inviteFriend": "Invitar a un amigo",
"userPanel.moveGuide": "El botón de configuración se ha movido aquí",
"userPanel.plans": "Planes de suscripción",
"userPanel.profile": "Cuenta",
"userPanel.setting": "Configuración",
"userPanel.upgradePlan": "Actualizar Plan",
"userPanel.usages": "Estadísticas de uso",
"userPanel.workspaceCredits": "Créditos del Espacio de Trabajo",
"userPanel.workspaceSetting": "Configuración del Espacio de Trabajo",
"userPanel.workspaceUsages": "Uso del Espacio de Trabajo",
"version": "Versión",
"zoom": "Zoom"
}
+2
View File
@@ -101,6 +101,7 @@
"LocalFile.action.open": "Abrir",
"LocalFile.action.showInFolder": "Mostrar en carpeta",
"MaxTokenSlider.unlimited": "Ilimitado",
"ModelSelect.featureTag.audio": "Este modelo admite el reconocimiento de entrada de audio.",
"ModelSelect.featureTag.custom": "Modelo personalizado, por defecto, admite llamadas a funciones y reconocimiento visual. Verifica la disponibilidad de estas capacidades según el caso.",
"ModelSelect.featureTag.file": "Este modelo admite la carga de archivos para lectura y reconocimiento.",
"ModelSelect.featureTag.functionCall": "Este modelo admite llamadas a funciones.",
@@ -114,6 +115,7 @@
"ModelSwitchPanel.byModel": "Por modelo",
"ModelSwitchPanel.byProvider": "Por proveedor",
"ModelSwitchPanel.detail.abilities": "Capacidades",
"ModelSwitchPanel.detail.abilities.audio": "Audio",
"ModelSwitchPanel.detail.abilities.files": "Archivos",
"ModelSwitchPanel.detail.abilities.functionCall": "Llamada a herramienta",
"ModelSwitchPanel.detail.abilities.imageOutput": "Salida de imagen",
-3
View File
@@ -38,9 +38,6 @@
"brief.title": "Informe diario",
"brief.viewAllTasks": "Ver todas las tareas",
"brief.viewRun": "Ver ejecución",
"freeCreditBadge.cta": "Comienza la prueba gratuita",
"freeCreditBadge.dismiss": "Descartar",
"freeCreditBadge.label": "Créditos gratis exclusivos para {{model}}",
"project.create": "Nuevo proyecto",
"project.deleteConfirm": "Este proyecto se eliminará y no se podrá recuperar. Confirma para continuar.",
"recommendations.heteroAgent.cta": "Agregar agente",
+4 -4
View File
@@ -5,10 +5,12 @@
"authorize.footer.agreement": "Al continuar, confirmas que has leído y aceptas los <terms>Términos y Condiciones</terms> y la <privacy>Política de Privacidad</privacy>.",
"authorize.footer.privacy": "Política de Privacidad",
"authorize.footer.terms": "Términos del Servicio",
"authorize.scenes.connector.confirm": "Continuar al Mercado",
"authorize.scenes.connector.description": "El Mercado solo se utiliza para iniciar esta autorización de servicio. Tu cuenta de {{appName}} permanece separada.",
"authorize.scenes.connector.subtitle": "Inicia sesión en el Mercado para conectar y autorizar este servicio comunitario.",
"authorize.scenes.connector.title": "Conectar Servicio Comunitario",
"authorize.scenes.mcp.subtitle": "Crea un perfil comunitario para instalar y ejecutar esta habilidad desde la comunidad.",
"authorize.scenes.mcp.title": "Instalar Habilidad Comunitaria",
"authorize.scenes.publish.subtitle": "Crea un perfil comunitario para publicar y gestionar tu listado dentro de la comunidad.",
"authorize.scenes.publish.title": "Publicar en la Comunidad",
"authorize.scenes.sandbox.subtitle": "Crea un perfil comunitario para ejecutar esta herramienta en el sandbox de la comunidad.",
"authorize.scenes.sandbox.title": "Probar el Sandbox de la Comunidad",
"authorize.subtitle": "Crea un perfil de comunidad para enviar y gestionar publicaciones dentro de la comunidad.",
@@ -50,8 +52,6 @@
"messages.handoffTimeout": "La autorización ha expirado. Complétala en tu navegador y vuelve a intentarlo.",
"messages.loading": "Iniciando proceso de autorización...",
"messages.success.cloudMcpInstall": "¡Autorización exitosa! Ahora puedes instalar la habilidad Cloud MCP.",
"messages.success.submit": "¡Autorización exitosa! Ahora puedes publicar tu agente.",
"messages.success.upload": "¡Autorización exitosa! Ahora puedes publicar una nueva versión.",
"profileSetup.cancel": "Cancelar",
"profileSetup.confirmChangeUserId.cancel": "Cancelar",
"profileSetup.confirmChangeUserId.confirm": "Cambiar ID de usuario",
+4 -2
View File
@@ -222,6 +222,7 @@
"providerModels.item.modelConfig.extendParams.options.effort.hint": "Para Claude Opus 4.6; controla el nivel de esfuerzo (bajo/medio/alto/máximo).",
"providerModels.item.modelConfig.extendParams.options.enableAdaptiveThinking.hint": "Para Claude Opus 4.6; activa o desactiva el pensamiento adaptativo.",
"providerModels.item.modelConfig.extendParams.options.enableReasoning.hint": "Para Claude, DeepSeek y otros modelos de razonamiento; permite un pensamiento más profundo.",
"providerModels.item.modelConfig.extendParams.options.glm5_2ReasoningEffort.hint": "Para GLM-5.2; controla el esfuerzo de razonamiento con niveles Alto y Máximo.",
"providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint": "Para la serie GPT-5; controla la intensidad del razonamiento.",
"providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint": "Para la serie GPT-5.1; controla la intensidad del razonamiento.",
"providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "Para la serie GPT-5.2 Pro; controla la intensidad del razonamiento.",
@@ -256,6 +257,7 @@
"providerModels.item.modelConfig.files.title": "Soporte de carga de archivos",
"providerModels.item.modelConfig.functionCall.extra": "Esta configuración solo habilita la capacidad del modelo para usar herramientas, permitiendo agregar habilidades tipo herramienta. Sin embargo, si el modelo puede usarlas depende completamente de él; por favor, prueba su funcionalidad.",
"providerModels.item.modelConfig.functionCall.title": "Soporte para uso de herramientas",
"providerModels.item.modelConfig.id.duplicate": "Ya existe un modelo con este ID. Utiliza un ID de modelo diferente.",
"providerModels.item.modelConfig.id.extra": "No se puede modificar después de la creación y se usará como ID del modelo al llamar a la IA",
"providerModels.item.modelConfig.id.placeholder": "Introduce el ID del modelo, por ejemplo, gpt-4o o claude-3.5-sonnet",
"providerModels.item.modelConfig.id.title": "ID del modelo",
@@ -270,11 +272,11 @@
"providerModels.item.modelConfig.tokens.title": "Ventana de contexto máxima",
"providerModels.item.modelConfig.tokens.unlimited": "Ilimitado",
"providerModels.item.modelConfig.type.extra": "Los diferentes tipos de modelos tienen distintos casos de uso y capacidades",
"providerModels.item.modelConfig.type.options.asr": "Texto a voz",
"providerModels.item.modelConfig.type.options.chat": "Chat",
"providerModels.item.modelConfig.type.options.embedding": "Embedding",
"providerModels.item.modelConfig.type.options.image": "Generación de imágenes",
"providerModels.item.modelConfig.type.options.realtime": "Chat en tiempo real",
"providerModels.item.modelConfig.type.options.stt": "Voz a texto",
"providerModels.item.modelConfig.type.options.text2music": "Texto a música",
"providerModels.item.modelConfig.type.options.tts": "Texto a voz",
"providerModels.item.modelConfig.type.options.video": "Generación de video",
@@ -323,10 +325,10 @@
"providerModels.list.total": "{{count}} modelos disponibles",
"providerModels.searchNotFound": "No se encontraron resultados de búsqueda",
"providerModels.tabs.all": "Todos",
"providerModels.tabs.asr": "ASR",
"providerModels.tabs.chat": "Chat",
"providerModels.tabs.embedding": "Embedding",
"providerModels.tabs.image": "Imagen",
"providerModels.tabs.stt": "ASR",
"providerModels.tabs.tts": "TTS",
"providerModels.tabs.video": "Vídeo",
"sortModal.success": "Orden actualizado con éxito",
+9 -69
View File
@@ -440,60 +440,7 @@
"llm.proxyUrl.title": "URL del Proxy de API",
"llm.waitingForMore": "Se <1>planea añadir más modelos</1>, mantente atento",
"llm.waitingForMoreLinkAriaLabel": "Abrir formulario de solicitud de proveedor",
"marketPublish.forkConfirm.by": "por {{author}}",
"marketPublish.forkConfirm.confirm": "Confirmar publicación",
"marketPublish.forkConfirm.confirmGroup": "Confirmar publicación",
"marketPublish.forkConfirm.description": "Estás a punto de publicar una versión derivada basada en un agente existente de la comunidad. Tu nuevo agente se creará como una entrada separada en el mercado.",
"marketPublish.forkConfirm.descriptionGroup": "Estás a punto de publicar una versión derivada basada en un grupo existente de la comunidad. Tu nuevo grupo se creará como una entrada separada en el mercado.",
"marketPublish.forkConfirm.title": "Publicar agente derivado",
"marketPublish.forkConfirm.titleGroup": "Publicar grupo derivado",
"marketPublish.modal.changelog.extra": "Describe los cambios clave y mejoras en esta versión",
"marketPublish.modal.changelog.label": "Registro de cambios",
"marketPublish.modal.changelog.maxLengthError": "El registro de cambios no debe exceder los 500 caracteres",
"marketPublish.modal.changelog.placeholder": "Introduce el registro de cambios",
"marketPublish.modal.changelog.required": "Por favor, introduce el registro de cambios",
"marketPublish.modal.comparison.local": "Versión local actual",
"marketPublish.modal.comparison.remote": "Versión publicada actualmente",
"marketPublish.modal.identifier.extra": "Este es el identificador único del Agente. Usa letras minúsculas, números y guiones.",
"marketPublish.modal.identifier.label": "Identificador del Agente",
"marketPublish.modal.identifier.lengthError": "El identificador debe tener entre 3 y 50 caracteres",
"marketPublish.modal.identifier.patternError": "El identificador solo puede contener letras minúsculas, números y guiones",
"marketPublish.modal.identifier.placeholder": "Introduce un identificador único para el agente, por ejemplo, desarrollo-web",
"marketPublish.modal.identifier.required": "Por favor, introduce el identificador del agente",
"marketPublish.modal.loading.fetchingRemote": "Cargando datos remotos...",
"marketPublish.modal.loading.submit": "Enviando Agente...",
"marketPublish.modal.loading.submitGroup": "Enviando grupo...",
"marketPublish.modal.loading.upload": "Publicando nueva versión...",
"marketPublish.modal.loading.uploadGroup": "Publicando nueva versión del grupo...",
"marketPublish.modal.messages.createVersionFailed": "Error al crear la versión: {{message}}",
"marketPublish.modal.messages.fetchRemoteFailed": "Error al obtener los datos del agente remoto",
"marketPublish.modal.messages.missingIdentifier": "Este Agente aún no tiene un identificador de la Comunidad.",
"marketPublish.modal.messages.noGroup": "Ningún grupo seleccionado",
"marketPublish.modal.messages.notAuthenticated": "Inicia sesión en tu cuenta de la Comunidad primero.",
"marketPublish.modal.messages.publishFailed": "Error al publicar: {{message}}",
"marketPublish.modal.submitButton": "Publicar",
"marketPublish.modal.title.submit": "Compartir con la Comunidad de Agentes",
"marketPublish.modal.title.upload": "Publicar Nueva Versión",
"marketPublish.resultModal.message": "Tu Agente ha sido enviado para revisión. Una vez aprobado, se publicará automáticamente.",
"marketPublish.resultModal.messageGroup": "Tu grupo ha sido enviado para revisión. Una vez aprobado, se publicará automáticamente.",
"marketPublish.resultModal.title": "Envío Exitoso",
"marketPublish.resultModal.view": "Ver en la Comunidad",
"marketPublish.status.underReview": "En revisión",
"marketPublish.submit.button": "Compartir con la Comunidad",
"marketPublish.submit.tooltip": "Comparte este Agente con la Comunidad",
"marketPublish.submitGroup.tooltip": "Comparte este grupo con la comunidad",
"marketPublish.upload.button": "Publicar Nueva Versión",
"marketPublish.upload.tooltip": "Publica una nueva versión en la Comunidad de Agentes",
"marketPublish.uploadGroup.tooltip": "Publica una nueva versión en la comunidad de grupos",
"marketPublish.validation.communitySetupRequired.action": "Configurar ahora",
"marketPublish.validation.communitySetupRequired.desc": "Este espacio de trabajo aún no ha configurado su perfil de Comunidad. Configúralo antes de publicar en la Comunidad.",
"marketPublish.validation.communitySetupRequired.memberHint": "Este espacio de trabajo aún no ha configurado su perfil de Comunidad. Pide a un propietario del espacio de trabajo que lo configure antes de publicar en la Comunidad.",
"marketPublish.validation.communitySetupRequired.title": "Configura primero el perfil de Comunidad",
"marketPublish.validation.confirmPublish": "¿Publicar en el Mercado?",
"marketPublish.validation.confirmPublishDesc": "Una vez publicado, este contenido será visible públicamente en el mercado y estará disponible para que cualquiera lo descubra y utilice.",
"marketPublish.validation.emptyName": "No se puede publicar: El nombre es obligatorio",
"marketPublish.validation.emptySystemRole": "No se puede publicar: El rol del sistema es obligatorio",
"marketPublish.validation.underReview": "Tu nueva versión está actualmente en revisión. Por favor, espera la aprobación antes de publicar una nueva versión.",
"memory.effort.desc": "Controla cuán agresivamente la IA recupera y actualiza la memoria.",
"memory.effort.high": "Alto — Recuperación y actualizaciones proactivas",
"memory.effort.level.high": "Alto",
@@ -515,14 +462,6 @@
"myAgents.actions.deprecateLoading": "Retirando agente...",
"myAgents.actions.deprecateSuccess": "Agente retirado",
"myAgents.actions.edit": "Editar Agente",
"myAgents.actions.publish": "Publicar Agente",
"myAgents.actions.publishError": "Error al publicar el agente",
"myAgents.actions.publishLoading": "Publicando agente...",
"myAgents.actions.publishSuccess": "Agente publicado",
"myAgents.actions.unpublish": "Despublicar Agente",
"myAgents.actions.unpublishError": "Error al despublicar el agente",
"myAgents.actions.unpublishLoading": "Despublicando agente...",
"myAgents.actions.unpublishSuccess": "Agente despublicado",
"myAgents.actions.viewDetail": "Ver Detalles",
"myAgents.detail.category": "Categoría",
"myAgents.detail.description": "Descripción",
@@ -587,7 +526,6 @@
"plugin.settings.title": "Configuración de Habilidad {{id}}",
"plugin.settings.tooltip": "Configuración de Habilidad",
"plugin.store": "Tienda de Habilidades",
"publishToCommunity": "Publicar en la comunidad",
"serviceModel.contextLimit.placeholder": "Límite de contexto",
"serviceModel.memoryModels.title": "Modelos de memoria",
"serviceModel.modelAssignments.title": "Asignaciones de modelo",
@@ -955,13 +893,6 @@
"storageOverage.usage.estimatedCharge": "Cargo estimado del ciclo",
"storageOverage.usage.incurredCharge": "Incurrido en este ciclo",
"storageOverage.usage.overage": "Exceso",
"submitAgentModal.button": "Enviar Agente",
"submitAgentModal.identifier": "Identificador del Agente",
"submitAgentModal.metaMiss": "Por favor, completa la información del agente antes de enviarlo. Debe incluir nombre, descripción y etiquetas",
"submitAgentModal.placeholder": "Introduce un identificador único para el agente, por ejemplo: desarrollo-web",
"submitAgentModal.success": "Agente enviado con éxito",
"submitAgentModal.tooltips": "Compartir con la Comunidad de Agentes",
"submitGroupModal.tooltips": "Compartir con la comunidad de grupos",
"sync.device.deviceName.hint": "Agrega un nombre para facilitar la identificación",
"sync.device.deviceName.placeholder": "Introduce el nombre del dispositivo",
"sync.device.deviceName.title": "Nombre del Dispositivo",
@@ -1086,6 +1017,7 @@
"tools.activation.auto": "Automático",
"tools.activation.auto.desc": "Inteligente",
"tools.activation.fixed.hint": "Siempre activado — gestionado por la aplicación y no se puede desactivar",
"tools.activation.pin": "Pin",
"tools.activation.pinned": "Fijado",
"tools.activation.pinned.desc": "Siempre Activado",
"tools.add": "Agregar Habilidad",
@@ -2047,6 +1979,14 @@
"workspace.wizard.step3.title": "¡Bienvenido a {{name}}!",
"workspace.wizard.title": "Crear espacio de trabajo",
"workspaceSetting.breadcrumb.settings": "Configuración",
"workspaceSetting.devices.desc": "Máquinas compartidas inscritas en este espacio de trabajo. Los miembros pueden ejecutar agentes en ellas.",
"workspaceSetting.devices.empty": "Aún no hay dispositivos en el espacio de trabajo.",
"workspaceSetting.devices.enrollDesc": "Ejecuta esto en la máquina que deseas compartir (solo el propietario del espacio de trabajo):",
"workspaceSetting.devices.enrollTitle": "Agregar un dispositivo",
"workspaceSetting.devices.heroDesc": "Inscribe una máquina compartida — un servidor de compilación o un Mac de equipo — y cada miembro podrá ejecutar agentes en ella: leer/escribir archivos, ejecutar comandos y utilizar herramientas del sistema.",
"workspaceSetting.devices.heroTitle": "Conecta tu primer dispositivo del espacio de trabajo",
"workspaceSetting.devices.offline": "Desconectado",
"workspaceSetting.devices.online": "Conectado",
"workspaceSetting.group.admin": "Administrador",
"workspaceSetting.group.agent": "Agente",
"workspaceSetting.group.general": "General",
+10 -5
View File
@@ -147,10 +147,6 @@
"limitation.chat.topupSuccess.title": "Recarga Exitosa",
"limitation.expired.desc": "Tus créditos de cómputo del plan {{plan}} expiraron el {{expiredAt}}. Mejora tu plan ahora para obtener más créditos.",
"limitation.expired.title": "Créditos de Cómputo Expirados",
"limitation.fableCampaign.desc": "Claude Fable 5 es un modelo de alto costo. Los créditos de prueba de la campaña se han agotado. Mejora tu plan para seguir usando Fable.",
"limitation.fableCampaign.title": "Créditos de Prueba de Fable Agotados",
"limitation.fableCampaign.upgrade": "Mejorar Plan",
"limitation.fableCampaign.upgradeToPlan": "Mejorar a {{plan}}",
"limitation.hobby.action": "Configurado, continuar chateando",
"limitation.hobby.configAPI": "Configurar API",
"limitation.hobby.desc": "Tus créditos gratuitos de cómputo se han agotado. Por favor configura una API de modelo personalizada para continuar.",
@@ -342,7 +338,14 @@
"plans.workspace.noSharedCredits": "Sin créditos compartidos",
"plans.workspace.sharedCredits": "~{{count}} Créditos / mes",
"plans.workspace.solo": "Solo (1 miembro)",
"promoBanner.fableYearly": "Los suscriptores anuales obtienen un {{percent}}% de descuento en el uso por tiempo limitado",
"plansModal.creditLimit.desc": "Mejora tu plan para desbloquear más créditos mensuales y seguir trabajando sin interrupciones.",
"plansModal.creditLimit.title": "Te has quedado sin créditos",
"plansModal.default.desc": "Desbloquea más capacidad y funciones avanzadas.",
"plansModal.default.title": "Mejora tu plan",
"plansModal.fileStorageLimit.desc": "Tu almacenamiento de archivos está lleno. Mejora tu plan para seguir subiendo y gestionando archivos.",
"plansModal.fileStorageLimit.title": "Límite de almacenamiento alcanzado",
"plansModal.modelAccess.desc": "Este modelo está disponible en planes de pago. Mejora tu plan para acceder a toda la gama de modelos.",
"plansModal.modelAccess.title": "Desbloquea todos los modelos",
"qa.desc": "Si tu pregunta no está respondida, revisa la <1>Documentación del Producto</1> para más preguntas frecuentes, o contáctanos.",
"qa.detail": "Ver Detalles",
"qa.list.credit.a": "Los créditos de cómputo son una métrica utilizada por {{cloud}} para medir el uso de modelos de IA al llamarlos. Diferentes modelos consumen diferentes cantidades de créditos.",
@@ -398,6 +401,8 @@
"referral.errors.invalidFormat": "Formato inválido, introduce 2-8 letras, números o guiones bajos",
"referral.errors.selfReferral": "No puedes usar tu propio código de invitación",
"referral.errors.updateFailed": "Error al actualizar, por favor intenta más tarde",
"referral.hero.description": "Comparte tu enlace de referencia a continuación. Después de que tu amigo realice su primer pago, ambos ganarán {{reward}}M de créditos.",
"referral.hero.title": "Invita a tus amigos, ambos ganan <0>{{reward}}M de créditos</0>",
"referral.inviteCode.description": "Comparte tu código exclusivo para invitar amigos a registrarse",
"referral.inviteCode.title": "Mi Código de Referido",
"referral.inviteLink.description": "Copia el enlace y compártelo con amigos. Ambos ganan créditos después de que tu amigo realice un pago",
+1
View File
@@ -25,6 +25,7 @@
"actions.unmarkCompleted": "Marcar como activa",
"defaultTitle": "Tema predeterminado",
"displayItems": "Mostrar elementos",
"draft": "[Borrador]",
"duplicateLoading": "Copiando tema...",
"duplicateSuccess": "Tema copiado con éxito",
"failedStatusTip": "Esta ejecución tuvo un error — ábrela para echar un vistazo.",
+28 -2
View File
@@ -41,6 +41,8 @@
"artifact.thinking": "در حال تفکر",
"artifact.thought": "فرآیند تفکر",
"artifact.unknownTitle": "کار بدون عنوان",
"audioPlayer.pause": "توقف صدا",
"audioPlayer.play": "پخش صدا",
"availableAgents": "عوامل در دسترس",
"backToBottom": "پرش به آخرین پیام",
"beforeUnload.confirmLeave": "درخواستی هنوز در حال اجراست. آیا می‌خواهید خارج شوید؟",
@@ -120,6 +122,18 @@
"createModal.groupPlaceholder": "توضیح دهید این گروه باید چه کاری انجام دهد...",
"createModal.groupTitle": "گروه شما باید چه کاری انجام دهد؟",
"createModal.placeholder": "توضیح دهید عامل شما باید چه کاری انجام دهد...",
"createModal.skillSuggestion.actions.createAnyway": "ایجاد عامل به هر حال",
"createModal.skillSuggestion.actions.createAnywayHint": "مهارت مناسب نیست؟",
"createModal.skillSuggestion.actions.install": "نصب مهارت",
"createModal.skillSuggestion.actions.installing": "در حال نصب...",
"createModal.skillSuggestion.actions.openSkills": "مشاهده در مهارت‌ها",
"createModal.skillSuggestion.actions.tryInLobeAI": "استفاده در {{name}}",
"createModal.skillSuggestion.description": "این به نظر یک جریان کاری قابل استفاده مجدد است. مهارت را یک بار نصب کنید، سپس آن را در عوامل مختلف استفاده کنید.",
"createModal.skillSuggestion.installError": "مهارت نصب نشد. دوباره تلاش کنید یا به هر حال یک عامل ایجاد کنید.",
"createModal.skillSuggestion.installed.description": "شما می‌توانید این مهارت را در {{name}} استفاده کنید یا آن را برای هر عاملی فعال کنید.",
"createModal.skillSuggestion.installed.ready": "آماده در {{name}}",
"createModal.skillSuggestion.installed.title": "مهارت نصب شد",
"createModal.skillSuggestion.title": "ممکن است یک مهارت بهتر باشد",
"createModal.title": "عامل شما باید چه کاری انجام دهد؟",
"createTask.assignee": "مسئول",
"createTask.collapse": "پنهان کردن ورودی",
@@ -166,6 +180,8 @@
"extendParams.title": "ویژگی‌های توسعه مدل",
"extendParams.urlContext.desc": "در صورت فعال بودن، پیوندهای وب به‌طور خودکار تجزیه شده و محتوای صفحه بازیابی می‌شود",
"extendParams.urlContext.title": "استخراج محتوای پیوند وب",
"floatingChatPanel.collapse": "بستن چت",
"floatingChatPanel.expand": "باز کردن چت",
"followUpPlaceholder": "پیگیری. برای واگذاری وظیفه به عامل‌های دیگر از @ استفاده کنید.",
"followUpPlaceholderHeterogeneous": "پیگیری.",
"gatewayMode.beta": "بتا",
@@ -219,9 +235,13 @@
"heteroAgent.cloudRepo.noRepos": "هیچ مخزنی تنظیم نشده است. آنها را در تنظیمات عامل اضافه کنید.",
"heteroAgent.cloudRepo.notSet": "هیچ مخزنی انتخاب نشده است",
"heteroAgent.cloudRepo.sectionTitle": "مخازن",
"heteroAgent.executionTarget.auto": "خودکار",
"heteroAgent.executionTarget.autoDesc": "به طور خودکار از یک دستگاه آنلاین استفاده کنید و یکی را در صورت موجود بودن چندین دستگاه انتخاب کنید",
"heteroAgent.executionTarget.downloadDesktop": "دریافت اپلیکیشن دسکتاپ",
"heteroAgent.executionTarget.downloadDesktopDesc": "اجرای عوامل با دسترسی به کامپیوتر شما",
"heteroAgent.executionTarget.downloadDesktopTitle": "دریافت اپلیکیشن دسکتاپ",
"heteroAgent.executionTarget.gateway": "دروازه",
"heteroAgent.executionTarget.gatewayDesc": "از طریق دروازه دستگاه اجرا کنید تا سایر مشتریان بتوانند پیشرفت را دنبال کنند",
"heteroAgent.executionTarget.infoTooltip": "یک دستگاه از راه دور را انتخاب کنید تا آن ماشین را از طریق وب کنترل کنید. \"این دستگاه\" عامل را به صورت محلی اجرا می‌کند و فقط در داخل برنامه دسکتاپ در دسترس است.",
"heteroAgent.executionTarget.loading": "در حال بارگذاری دستگاه‌ها...",
"heteroAgent.executionTarget.local": "این دستگاه",
@@ -231,10 +251,12 @@
"heteroAgent.executionTarget.noneDesc": "هیچ دستگاهی فعال نشده است",
"heteroAgent.executionTarget.offline": "آفلاین",
"heteroAgent.executionTarget.online": "آنلاین",
"heteroAgent.executionTarget.personalGroup": "شخصی",
"heteroAgent.executionTarget.sandbox": "محیط آزمایشی ابری",
"heteroAgent.executionTarget.sandboxDesc": "در یک محیط آزمایشی ابری موقت اجرا شود",
"heteroAgent.executionTarget.title": "دستگاه اجرا",
"heteroAgent.executionTarget.unknownDevice": "دستگاه ناشناخته",
"heteroAgent.executionTarget.workspaceGroup": "محیط کاری",
"heteroAgent.fullAccess.label": "دسترسی کامل",
"heteroAgent.fullAccess.tooltip": "Claude Code به‌صورت محلی با دسترسی کامل خواندن/نوشتن در پوشه کاری اجرا می‌شود. تغییر حالت‌های دسترسی فعلاً امکان‌پذیر نیست.",
"heteroAgent.resumeReset.cwdChanged": "پوشه کاری تغییر کرده است. نشست قبلی Claude Code فقط از پوشه اصلی خود قابل ادامه است، بنابراین یک مکالمه جدید آغاز شد.",
@@ -631,6 +653,8 @@
"taskDetail.artifacts": "آیتم‌ها",
"taskDetail.blockedBy": "مسدود شده توسط {{id}}",
"taskDetail.cancelSchedule": "لغو زمان‌بندی",
"taskDetail.closeDetail": "بستن جزئیات",
"taskDetail.collapseReply": "بستن پاسخ",
"taskDetail.comment.cancel": "انصراف",
"taskDetail.comment.delete": "حذف",
"taskDetail.comment.deleteConfirm.content": "این نظر به‌طور دائمی حذف خواهد شد.",
@@ -657,6 +681,7 @@
"taskDetail.notFound.backToTasks": "بازگشت به همه وظایف",
"taskDetail.notFound.desc": "این وظیفه ممکن است حذف شده باشد، یا شما اجازه مشاهده آن را ندارید.",
"taskDetail.notFound.title": "وظیفه پیدا نشد",
"taskDetail.openDetail": "باز کردن جزئیات",
"taskDetail.pauseTask": "توقف وظیفه",
"taskDetail.priority.high": "زیاد",
"taskDetail.priority.low": "کم",
@@ -925,9 +950,9 @@
"workflow.collapse": "جمع کردن",
"workflow.expandFull": "نمایش کامل",
"workflow.failedSuffix": "(ناموفق)",
"workflow.summaryAcrossTools": "در میان {{count}} ابزار",
"workflow.summaryCallsLead": "{{count}} تماس‌ها: {{tools}}",
"workflow.summaryFailed": "{{count}} مورد ناموفق",
"workflow.summaryMoreTools": "{{count}} نوع ابزار",
"workflow.summaryTotalCalls": "{{count}} فراخوانی در مجموع",
"workflow.thoughtForDuration": "تفکر به مدت {{duration}}",
"workflow.toolDisplayName.activateDevice": "دستگاه فعال‌شده",
"workflow.toolDisplayName.activateSkill": "یک مهارت فعال شد",
@@ -1043,6 +1068,7 @@
"workingPanel.resources.deleteTitle": "Delete document?",
"workingPanel.resources.deleteTitleMulti": "حذف {{count}} مورد؟",
"workingPanel.resources.empty": "هنوز سندی وجود ندارد. اسناد مرتبط با این عامل در اینجا نمایش داده می‌شوند.",
"workingPanel.resources.emptyDocuments": "هنوز هیچ سندی وجود ندارد. یکی را با + بالا ایجاد کنید.",
"workingPanel.resources.error": "Failed to load resources",
"workingPanel.resources.filter.documents": "اسناد",
"workingPanel.resources.filter.skills": "مهارت‌ها",
+18 -2
View File
@@ -444,6 +444,23 @@
"tab.setting": "تنظیمات",
"tab.tasks": "کارها",
"tab.video": "ویدیو",
"taskTemplate.action.connect.button": "اتصال به {{provider}}",
"taskTemplate.action.connect.error": "اتصال ناموفق بود، لطفاً دوباره تلاش کنید.",
"taskTemplate.action.connect.popupBlocked": "پنجره اتصال مسدود شده است. برای ادامه، پاپ‌آپ‌ها را در مرورگر خود فعال کنید.",
"taskTemplate.action.connect.short": "اتصال",
"taskTemplate.action.connecting": "در انتظار تأیید...",
"taskTemplate.action.create.error": "ایجاد وظیفه ناموفق بود. لطفاً دوباره تلاش کنید.",
"taskTemplate.action.create.success": "وظیفه زمان‌بندی‌شده اضافه شد. آن را در Lobe AI پیدا کنید.",
"taskTemplate.action.createButton": "اضافه کردن وظیفه",
"taskTemplate.action.creating": "در حال ایجاد...",
"taskTemplate.action.dismiss.error": "رد کردن ناموفق بود. لطفاً دوباره تلاش کنید.",
"taskTemplate.action.dismiss.tooltip": "علاقه‌ای ندارم",
"taskTemplate.action.refresh.button": "تازه‌سازی",
"taskTemplate.card.templateTag": "الگو",
"taskTemplate.schedule.daily": "هر روز در ساعت {{time}}",
"taskTemplate.schedule.editableAfterCreateTooltip": "می‌توانید زمان‌بندی را پس از ایجاد وظیفه تنظیم کنید.",
"taskTemplate.schedule.weekly": "هر {{weekday}} در ساعت {{time}}",
"taskTemplate.section.title": "این وظایف زمان‌بندی‌شده را امتحان کنید",
"telemetry.allow": "اجازه بده",
"telemetry.deny": "رد کن",
"telemetry.desc": "ما مایلیم اطلاعات استفاده را به‌صورت ناشناس جمع‌آوری کنیم تا {{appName}} را بهبود دهیم و تجربه بهتری ارائه دهیم. می‌توانید این گزینه را در تنظیمات - درباره غیرفعال کنید.",
@@ -474,15 +491,14 @@
"userPanel.email": "پشتیبانی ایمیلی",
"userPanel.feedback": "تماس با ما",
"userPanel.help": "مرکز راهنما",
"userPanel.inviteFriend": "دعوت از یک دوست",
"userPanel.moveGuide": "دکمه تنظیمات به اینجا منتقل شده است",
"userPanel.plans": "طرح‌های اشتراک",
"userPanel.profile": "حساب کاربری",
"userPanel.setting": "تنظیمات",
"userPanel.upgradePlan": "ارتقاء طرح",
"userPanel.usages": "آمار استفاده",
"userPanel.workspaceCredits": "اعتبارات فضای کاری",
"userPanel.workspaceSetting": "تنظیمات فضای کاری",
"userPanel.workspaceUsages": "استفاده از فضای کاری",
"version": "نسخه",
"zoom": "بزرگنمایی"
}
+2
View File
@@ -101,6 +101,7 @@
"LocalFile.action.open": "باز کردن",
"LocalFile.action.showInFolder": "نمایش در پوشه",
"MaxTokenSlider.unlimited": "نامحدود",
"ModelSelect.featureTag.audio": "این مدل از شناسایی ورودی صوتی پشتیبانی می‌کند.",
"ModelSelect.featureTag.custom": "مدل سفارشی که به‌طور پیش‌فرض از تماس‌های تابع و تشخیص بصری پشتیبانی می‌کند. لطفاً بر اساس شرایط واقعی، قابلیت‌های فوق را بررسی کنید.",
"ModelSelect.featureTag.file": "این مدل از بارگذاری فایل برای خواندن و تشخیص پشتیبانی می‌کند.",
"ModelSelect.featureTag.functionCall": "این مدل از تماس‌های تابع پشتیبانی می‌کند.",
@@ -114,6 +115,7 @@
"ModelSwitchPanel.byModel": "بر اساس مدل",
"ModelSwitchPanel.byProvider": "بر اساس ارائه‌دهنده",
"ModelSwitchPanel.detail.abilities": "قابلیت‌ها",
"ModelSwitchPanel.detail.abilities.audio": "صوت",
"ModelSwitchPanel.detail.abilities.files": "فایل‌ها",
"ModelSwitchPanel.detail.abilities.functionCall": "فراخوانی ابزار",
"ModelSwitchPanel.detail.abilities.imageOutput": "خروجی تصویر",
-3
View File
@@ -38,9 +38,6 @@
"brief.title": "گزارش روزانه",
"brief.viewAllTasks": "مشاهدهٔ همهٔ وظایف",
"brief.viewRun": "مشاهده اجرا",
"freeCreditBadge.cta": "شروع آزمایش رایگان",
"freeCreditBadge.dismiss": "رد کردن",
"freeCreditBadge.label": "اعتبار رایگان انحصاری برای {{model}}",
"project.create": "پروژه جدید",
"project.deleteConfirm": "این پروژه حذف خواهد شد و امکان بازیابی آن وجود ندارد. برای ادامه تأیید کنید.",
"recommendations.heteroAgent.cta": "افزودن عامل",
+4 -4
View File
@@ -5,10 +5,12 @@
"authorize.footer.agreement": "با ادامه دادن، تأیید می‌کنید که <terms>شرایط و ضوابط</terms> و <privacy>سیاست حفظ حریم خصوصی</privacy> را خوانده‌اید و با آن موافقید.",
"authorize.footer.privacy": "سیاست حفظ حریم خصوصی",
"authorize.footer.terms": "شرایط خدمات",
"authorize.scenes.connector.confirm": "ادامه به بازار",
"authorize.scenes.connector.description": "بازار فقط برای شروع این مجوز خدمات استفاده می‌شود. حساب {{appName}} شما جدا باقی می‌ماند.",
"authorize.scenes.connector.subtitle": "برای اتصال و مجوز این خدمات اجتماعی، وارد بازار شوید.",
"authorize.scenes.connector.title": "اتصال خدمات اجتماعی",
"authorize.scenes.mcp.subtitle": "یک پروفایل جامعه ایجاد کنید تا این مهارت را از جامعه نصب و اجرا کنید.",
"authorize.scenes.mcp.title": "نصب مهارت جامعه",
"authorize.scenes.publish.subtitle": "یک پروفایل جامعه ایجاد کنید تا لیست خود را در جامعه منتشر و مدیریت کنید.",
"authorize.scenes.publish.title": "انتشار در جامعه",
"authorize.scenes.sandbox.subtitle": "یک پروفایل جامعه ایجاد کنید تا این ابزار را در محیط آزمایشی جامعه اجرا کنید.",
"authorize.scenes.sandbox.title": "امتحان محیط آزمایشی جامعه",
"authorize.subtitle": "برای ارسال و مدیریت آگهی‌ها در انجمن، یک پروفایل انجمن ایجاد کنید.",
@@ -50,8 +52,6 @@
"messages.handoffTimeout": "مهلت احراز هویت به پایان رسید. آن را در مرورگر کامل کرده و دوباره تلاش کنید.",
"messages.loading": "در حال آغاز فرآیند احراز هویت...",
"messages.success.cloudMcpInstall": "احراز هویت موفقیت‌آمیز بود! اکنون می‌توانید مهارت Cloud MCP را نصب کنید.",
"messages.success.submit": "احراز هویت موفقیت‌آمیز بود! اکنون می‌توانید عامل خود را منتشر کنید.",
"messages.success.upload": "احراز هویت موفقیت‌آمیز بود! اکنون می‌توانید نسخه جدیدی منتشر کنید.",
"profileSetup.cancel": "لغو",
"profileSetup.confirmChangeUserId.cancel": "لغو",
"profileSetup.confirmChangeUserId.confirm": "تغییر شناسه کاربری",
+4 -2
View File
@@ -222,6 +222,7 @@
"providerModels.item.modelConfig.extendParams.options.effort.hint": "برای Claude Opus 4.6؛ سطح تلاش را کنترل می‌کند (کم/متوسط/زیاد/حداکثر).",
"providerModels.item.modelConfig.extendParams.options.enableAdaptiveThinking.hint": "برای Claude Opus 4.6؛ تفکر تطبیقی را فعال یا غیرفعال می‌کند.",
"providerModels.item.modelConfig.extendParams.options.enableReasoning.hint": "برای مدل‌های Claude، DeepSeek و سایر مدل‌های استدلالی؛ امکان تفکر عمیق‌تر را فراهم می‌کند.",
"providerModels.item.modelConfig.extendParams.options.glm5_2ReasoningEffort.hint": "برای GLM-5.2؛ تلاش استدلالی را با سطوح بالا و حداکثر کنترل می‌کند.",
"providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint": "برای سری GPT-5؛ شدت استدلال را کنترل می‌کند.",
"providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint": "برای سری GPT-5.1؛ شدت استدلال را کنترل می‌کند.",
"providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "برای سری GPT-5.2 Pro؛ شدت استدلال را کنترل می‌کند.",
@@ -256,6 +257,7 @@
"providerModels.item.modelConfig.files.title": "پشتیبانی از آپلود فایل",
"providerModels.item.modelConfig.functionCall.extra": "این پیکربندی فقط قابلیت استفاده از ابزارها را برای مدل فعال می‌کند. اما استفاده واقعی از ابزارها به خود مدل بستگی دارد؛ لطفاً قابلیت استفاده را خودتان آزمایش کنید.",
"providerModels.item.modelConfig.functionCall.title": "پشتیبانی از استفاده از ابزار",
"providerModels.item.modelConfig.id.duplicate": "مدلی با این شناسه قبلاً وجود دارد. از شناسه مدل دیگری استفاده کنید.",
"providerModels.item.modelConfig.id.extra": "پس از ایجاد قابل تغییر نیست و به عنوان شناسه مدل هنگام فراخوانی AI استفاده می‌شود",
"providerModels.item.modelConfig.id.placeholder": "شناسه مدل را وارد کنید، مانند gpt-4o یا claude-3.5-sonnet",
"providerModels.item.modelConfig.id.title": "شناسه مدل",
@@ -270,11 +272,11 @@
"providerModels.item.modelConfig.tokens.title": "حداکثر پنجره متنی",
"providerModels.item.modelConfig.tokens.unlimited": "نامحدود",
"providerModels.item.modelConfig.type.extra": "انواع مختلف مدل‌ها کاربردها و قابلیت‌های متفاوتی دارند",
"providerModels.item.modelConfig.type.options.asr": "گفتار به متن",
"providerModels.item.modelConfig.type.options.chat": "گفت‌وگو",
"providerModels.item.modelConfig.type.options.embedding": "بردارسازی",
"providerModels.item.modelConfig.type.options.image": "تولید تصویر",
"providerModels.item.modelConfig.type.options.realtime": "گفت‌وگوی بلادرنگ",
"providerModels.item.modelConfig.type.options.stt": "تبدیل گفتار به متن",
"providerModels.item.modelConfig.type.options.text2music": "تبدیل متن به موسیقی",
"providerModels.item.modelConfig.type.options.tts": "تبدیل متن به گفتار",
"providerModels.item.modelConfig.type.options.video": "تولید ویدیو",
@@ -323,10 +325,10 @@
"providerModels.list.total": "{{count}} مدل در دسترس",
"providerModels.searchNotFound": "نتیجه‌ای برای جستجو یافت نشد",
"providerModels.tabs.all": "همه",
"providerModels.tabs.asr": "ASR",
"providerModels.tabs.chat": "گفت‌وگو",
"providerModels.tabs.embedding": "بردارسازی",
"providerModels.tabs.image": "تصویر",
"providerModels.tabs.stt": "تبدیل گفتار به متن",
"providerModels.tabs.tts": "تبدیل متن به گفتار",
"providerModels.tabs.video": "ویدیو",
"sortModal.success": "مرتب‌سازی با موفقیت به‌روزرسانی شد",
+9 -69
View File
@@ -440,60 +440,7 @@
"llm.proxyUrl.title": "آدرس پراکسی API",
"llm.waitingForMore": "مدل‌های بیشتری <1>در حال برنامه‌ریزی برای افزودن</1> هستند، با ما همراه باشید",
"llm.waitingForMoreLinkAriaLabel": "فرم درخواست ارائه‌دهنده را باز کنید",
"marketPublish.forkConfirm.by": "توسط {{author}}",
"marketPublish.forkConfirm.confirm": "تأیید انتشار",
"marketPublish.forkConfirm.confirmGroup": "تأیید انتشار",
"marketPublish.forkConfirm.description": "شما در حال انتشار نسخه‌ای مشتق‌شده بر اساس یک عامل موجود از جامعه هستید. عامل جدید شما به‌عنوان یک ورودی جداگانه در بازار ایجاد خواهد شد.",
"marketPublish.forkConfirm.descriptionGroup": "شما در حال انتشار نسخه‌ای مشتق‌شده بر اساس یک گروه موجود از جامعه هستید. گروه جدید شما به‌عنوان یک ورودی جداگانه در بازار ایجاد خواهد شد.",
"marketPublish.forkConfirm.title": "انتشار عامل مشتق‌شده",
"marketPublish.forkConfirm.titleGroup": "انتشار گروه مشتق‌شده",
"marketPublish.modal.changelog.extra": "توضیحی درباره تغییرات و بهبودهای کلیدی در این نسخه ارائه دهید",
"marketPublish.modal.changelog.label": "تغییرات نسخه",
"marketPublish.modal.changelog.maxLengthError": "تغییرات نسخه نباید بیش از ۵۰۰ کاراکتر باشد",
"marketPublish.modal.changelog.placeholder": "تغییرات نسخه را وارد کنید",
"marketPublish.modal.changelog.required": "لطفاً تغییرات نسخه را وارد کنید",
"marketPublish.modal.comparison.local": "نسخه محلی فعلی",
"marketPublish.modal.comparison.remote": "نسخه منتشرشده فعلی",
"marketPublish.modal.identifier.extra": "این شناسه منحصربه‌فرد عامل است. از حروف کوچک، اعداد و خط تیره استفاده کنید.",
"marketPublish.modal.identifier.label": "شناسه عامل",
"marketPublish.modal.identifier.lengthError": "شناسه باید بین ۳ تا ۵۰ کاراکتر باشد",
"marketPublish.modal.identifier.patternError": "شناسه فقط می‌تواند شامل حروف کوچک، اعداد و خط تیره باشد",
"marketPublish.modal.identifier.placeholder": "یک شناسه منحصربه‌فرد برای عامل وارد کنید، مثلاً web-development",
"marketPublish.modal.identifier.required": "لطفاً شناسه عامل را وارد کنید",
"marketPublish.modal.loading.fetchingRemote": "در حال بارگذاری داده‌های از راه دور...",
"marketPublish.modal.loading.submit": "در حال ارسال عامل...",
"marketPublish.modal.loading.submitGroup": "در حال ارسال گروه...",
"marketPublish.modal.loading.upload": "در حال انتشار نسخه جدید...",
"marketPublish.modal.loading.uploadGroup": "در حال انتشار نسخه جدید گروه...",
"marketPublish.modal.messages.createVersionFailed": "ایجاد نسخه با خطا مواجه شد: {{message}}",
"marketPublish.modal.messages.fetchRemoteFailed": "دریافت داده‌های عامل از راه دور با خطا مواجه شد",
"marketPublish.modal.messages.missingIdentifier": "این عامل هنوز شناسه‌ای در انجمن ندارد.",
"marketPublish.modal.messages.noGroup": "هیچ گروهی انتخاب نشده است",
"marketPublish.modal.messages.notAuthenticated": "ابتدا وارد حساب کاربری انجمن خود شوید.",
"marketPublish.modal.messages.publishFailed": "انتشار با خطا مواجه شد: {{message}}",
"marketPublish.modal.submitButton": "انتشار",
"marketPublish.modal.title.submit": "اشتراک‌گذاری در انجمن عامل‌ها",
"marketPublish.modal.title.upload": "انتشار نسخه جدید",
"marketPublish.resultModal.message": "عامل شما برای بررسی ارسال شد. پس از تأیید، به‌صورت خودکار منتشر خواهد شد.",
"marketPublish.resultModal.messageGroup": "گروه شما برای بررسی ارسال شده است. پس از تأیید، به‌صورت خودکار منتشر خواهد شد.",
"marketPublish.resultModal.title": "ارسال با موفقیت انجام شد",
"marketPublish.resultModal.view": "مشاهده در انجمن",
"marketPublish.status.underReview": "در حال بررسی",
"marketPublish.submit.button": "اشتراک‌گذاری در انجمن",
"marketPublish.submit.tooltip": "این عامل را در انجمن به اشتراک بگذارید",
"marketPublish.submitGroup.tooltip": "این گروه را با جامعه به اشتراک بگذارید",
"marketPublish.upload.button": "انتشار نسخه جدید",
"marketPublish.upload.tooltip": "نسخه جدیدی را در انجمن عامل‌ها منتشر کنید",
"marketPublish.uploadGroup.tooltip": "نسخه جدیدی از گروه را در جامعه منتشر کنید",
"marketPublish.validation.communitySetupRequired.action": "اکنون تنظیم کنید",
"marketPublish.validation.communitySetupRequired.desc": "این فضای کاری هنوز پروفایل جامعه خود را تنظیم نکرده است. قبل از انتشار در جامعه، آن را تنظیم کنید.",
"marketPublish.validation.communitySetupRequired.memberHint": "این فضای کاری هنوز پروفایل جامعه خود را تنظیم نکرده است. از مالک فضای کاری بخواهید قبل از انتشار در جامعه آن را تنظیم کند.",
"marketPublish.validation.communitySetupRequired.title": "ابتدا پروفایل جامعه را تنظیم کنید",
"marketPublish.validation.confirmPublish": "انتشار در بازار؟",
"marketPublish.validation.confirmPublishDesc": "پس از انتشار، این محتوا به صورت عمومی در بازار قابل مشاهده خواهد بود و هر کسی می‌تواند آن را کشف و استفاده کند.",
"marketPublish.validation.emptyName": "انتشار غیرممکن است: نام الزامی است",
"marketPublish.validation.emptySystemRole": "انتشار غیرممکن است: نقش سیستم الزامی است",
"marketPublish.validation.underReview": "نسخه جدید شما در حال حاضر در دست بررسی است. لطفاً قبل از انتشار نسخه جدید، منتظر تأیید باشید.",
"memory.effort.desc": "کنترل کنید که هوش مصنوعی با چه شدتی حافظه را بازیابی و به‌روزرسانی کند.",
"memory.effort.high": "زیاد — بازیابی و به‌روزرسانی پیشگیرانه",
"memory.effort.level.high": "زیاد",
@@ -515,14 +462,6 @@
"myAgents.actions.deprecateLoading": "در حال غیرفعال‌سازی عامل...",
"myAgents.actions.deprecateSuccess": "عامل با موفقیت غیرفعال شد",
"myAgents.actions.edit": "ویرایش عامل",
"myAgents.actions.publish": "انتشار عامل",
"myAgents.actions.publishError": "انتشار عامل با خطا مواجه شد",
"myAgents.actions.publishLoading": "در حال انتشار عامل...",
"myAgents.actions.publishSuccess": "عامل با موفقیت منتشر شد",
"myAgents.actions.unpublish": "لغو انتشار عامل",
"myAgents.actions.unpublishError": "لغو انتشار عامل با خطا مواجه شد",
"myAgents.actions.unpublishLoading": "در حال لغو انتشار عامل...",
"myAgents.actions.unpublishSuccess": "عامل با موفقیت لغو انتشار شد",
"myAgents.actions.viewDetail": "مشاهده جزئیات",
"myAgents.detail.category": "دسته‌بندی",
"myAgents.detail.description": "توضیحات",
@@ -587,7 +526,6 @@
"plugin.settings.title": "پیکربندی مهارت {{id}}",
"plugin.settings.tooltip": "پیکربندی مهارت",
"plugin.store": "فروشگاه مهارت",
"publishToCommunity": "انتشار در جامعه",
"serviceModel.contextLimit.placeholder": "محدودیت زمینه",
"serviceModel.memoryModels.title": "مدل‌های حافظه",
"serviceModel.modelAssignments.title": "تخصیص مدل‌ها",
@@ -955,13 +893,6 @@
"storageOverage.usage.estimatedCharge": "هزینه تخمینی چرخه",
"storageOverage.usage.incurredCharge": "هزینه ایجاد شده در این چرخه",
"storageOverage.usage.overage": "اضافی",
"submitAgentModal.button": "ارسال عامل",
"submitAgentModal.identifier": "شناسه عامل",
"submitAgentModal.metaMiss": "لطفاً اطلاعات عامل را قبل از ارسال کامل کنید. این اطلاعات باید شامل نام، توضیح و برچسب‌ها باشد.",
"submitAgentModal.placeholder": "یک شناسه منحصربه‌فرد برای عامل وارد کنید، مثلاً web-development",
"submitAgentModal.success": "عامل با موفقیت ارسال شد",
"submitAgentModal.tooltips": "اشتراک‌گذاری در جامعه عامل‌ها",
"submitGroupModal.tooltips": "اشتراک‌گذاری در جامعه گروه‌ها",
"sync.device.deviceName.hint": "برای شناسایی آسان، یک نام اضافه کنید",
"sync.device.deviceName.placeholder": "نام دستگاه را وارد کنید",
"sync.device.deviceName.title": "نام دستگاه",
@@ -1086,6 +1017,7 @@
"tools.activation.auto": "خودکار",
"tools.activation.auto.desc": "هوشمند",
"tools.activation.fixed.hint": "همیشه فعال — توسط برنامه مدیریت می‌شود و نمی‌توان آن را خاموش کرد",
"tools.activation.pin": "پین",
"tools.activation.pinned": "پین شده",
"tools.activation.pinned.desc": "همیشه روشن",
"tools.add": "افزودن مهارت",
@@ -2047,6 +1979,14 @@
"workspace.wizard.step3.title": "به {{name}} خوش آمدید!",
"workspace.wizard.title": "ایجاد فضای کاری",
"workspaceSetting.breadcrumb.settings": "تنظیمات",
"workspaceSetting.devices.desc": "ماشین‌های مشترک که در این فضای کاری ثبت شده‌اند. اعضا می‌توانند عوامل را روی آن‌ها اجرا کنند.",
"workspaceSetting.devices.empty": "هنوز هیچ دستگاهی در فضای کاری وجود ندارد.",
"workspaceSetting.devices.enrollDesc": "این را روی ماشینی که می‌خواهید به اشتراک بگذارید اجرا کنید (فقط مالک فضای کاری):",
"workspaceSetting.devices.enrollTitle": "افزودن دستگاه",
"workspaceSetting.devices.heroDesc": "یک ماشین مشترک — یک سرور ساخت یا یک مک تیمی — ثبت کنید و هر عضو می‌تواند عوامل را روی آن اجرا کند: خواندن/نوشتن فایل‌ها، اجرای دستورات و استفاده از ابزارهای سیستمی.",
"workspaceSetting.devices.heroTitle": "اولین دستگاه فضای کاری خود را متصل کنید",
"workspaceSetting.devices.offline": "آفلاین",
"workspaceSetting.devices.online": "آنلاین",
"workspaceSetting.group.admin": "مدیر",
"workspaceSetting.group.agent": "نماینده",
"workspaceSetting.group.general": "عمومی",
+10 -5
View File
@@ -147,10 +147,6 @@
"limitation.chat.topupSuccess.title": "شارژ با موفقیت انجام شد",
"limitation.expired.desc": "اعتبار محاسباتی پلن {{plan}} شما در تاریخ {{expiredAt}} منقضی شده است. برای دریافت اعتبار جدید، پلن خود را ارتقا دهید.",
"limitation.expired.title": "اعتبار محاسباتی منقضی شده است",
"limitation.fableCampaign.desc": "مدل Claude Fable 5 یک مدل پرهزینه است. اعتبارهای آزمایشی کمپین استفاده شده‌اند. برای ادامه استفاده از Fable، طرح خود را ارتقا دهید.",
"limitation.fableCampaign.title": "اعتبارهای آزمایشی Fable تمام شده‌اند",
"limitation.fableCampaign.upgrade": "ارتقای طرح",
"limitation.fableCampaign.upgradeToPlan": "ارتقا به {{plan}}",
"limitation.hobby.action": "پیکربندی شد، ادامه گفتگو",
"limitation.hobby.configAPI": "پیکربندی API",
"limitation.hobby.desc": "اعتبار رایگان شما به پایان رسیده است. لطفاً API مدل سفارشی خود را پیکربندی کنید.",
@@ -342,7 +338,14 @@
"plans.workspace.noSharedCredits": "بدون اعتبارهای مشترک",
"plans.workspace.sharedCredits": "~{{count}} اعتبار / ماه",
"plans.workspace.solo": "تک‌نفره (1 عضو)",
"promoBanner.fableYearly": "مشترکین سالانه {{percent}}% تخفیف برای مدت محدود دریافت می‌کنند",
"plansModal.creditLimit.desc": "طرح خود را ارتقا دهید تا اعتبار ماهانه بیشتری باز کنید و بدون وقفه به کار خود ادامه دهید.",
"plansModal.creditLimit.title": "اعتبار شما تمام شده است",
"plansModal.default.desc": "ظرفیت بیشتر و ویژگی‌های پیشرفته را باز کنید.",
"plansModal.default.title": "طرح خود را ارتقا دهید",
"plansModal.fileStorageLimit.desc": "فضای ذخیره‌سازی فایل شما پر شده است. برای ادامه آپلود و مدیریت فایل‌ها، طرح خود را ارتقا دهید.",
"plansModal.fileStorageLimit.title": "محدودیت فضای ذخیره‌سازی رسید",
"plansModal.modelAccess.desc": "این مدل در طرح‌های پولی در دسترس است. برای استفاده از مجموعه کامل مدل‌ها، طرح خود را ارتقا دهید.",
"plansModal.modelAccess.title": "همه مدل‌ها را باز کنید",
"qa.desc": "اگر پاسخ سوال خود را پیدا نکردید، به <1>مستندات محصول</1> مراجعه کنید یا با ما تماس بگیرید.",
"qa.detail": "مشاهده جزئیات",
"qa.list.credit.a": "اعتبارات محاسباتی معیاری هستند که توسط {{cloud}} برای اندازه‌گیری میزان استفاده از مدل‌های هوش مصنوعی هنگام فراخوانی آن‌ها استفاده می‌شود. مدل‌های مختلف هوش مصنوعی میزان متفاوتی از اعتبار محاسباتی مصرف می‌کنند.",
@@ -398,6 +401,8 @@
"referral.errors.invalidFormat": "فرمت کد دعوت نامعتبر است، لطفاً ۲ تا ۸ حرف، عدد یا زیرخط وارد کنید",
"referral.errors.selfReferral": "نمی‌توانید از کد دعوت خودتان استفاده کنید",
"referral.errors.updateFailed": "به‌روزرسانی ناموفق بود، لطفاً بعداً دوباره تلاش کنید",
"referral.hero.description": "لینک ارجاع خود را در زیر به اشتراک بگذارید. پس از اولین پرداخت دوست شما، هر دو {{reward}}M اعتبار دریافت می‌کنید.",
"referral.hero.title": "دوستان خود را دعوت کنید، هر دو <0>{{reward}}M اعتبار</0> دریافت کنید",
"referral.inviteCode.description": "کد دعوت اختصاصی خود را به اشتراک بگذارید تا دوستان را به ثبت‌نام دعوت کنید",
"referral.inviteCode.title": "کد دعوت من",
"referral.inviteLink.description": "لینک را کپی کنید و با دوستان خود به اشتراک بگذارید. هر دو شما پس از پرداخت دوستتان اعتبار دریافت می‌کنید",
+1
View File
@@ -25,6 +25,7 @@
"actions.unmarkCompleted": "علامت‌گذاری به‌عنوان فعال",
"defaultTitle": "گفت‌وگوی پیش‌فرض",
"displayItems": "نمایش موارد",
"draft": "[پیش‌نویس]",
"duplicateLoading": "در حال کپی‌برداری از گفت‌وگو...",
"duplicateSuccess": "گفت‌وگو با موفقیت کپی شد",
"failedStatusTip": "این اجرا با خطا مواجه شد — برای بررسی آن را باز کنید.",
+28 -2
View File
@@ -41,6 +41,8 @@
"artifact.thinking": "Réflexion en cours",
"artifact.thought": "Processus de réflexion",
"artifact.unknownTitle": "Travail sans titre",
"audioPlayer.pause": "Mettre en pause l'audio",
"audioPlayer.play": "Lire l'audio",
"availableAgents": "Agents disponibles",
"backToBottom": "Aller au plus récent",
"beforeUnload.confirmLeave": "Une requête est encore en cours. Quitter quand même ?",
@@ -120,6 +122,18 @@
"createModal.groupPlaceholder": "Décrivez ce que ce groupe doit faire...",
"createModal.groupTitle": "Que doit faire votre groupe ?",
"createModal.placeholder": "Décrivez ce que votre agent doit faire...",
"createModal.skillSuggestion.actions.createAnyway": "Créer l'Agent quand même",
"createModal.skillSuggestion.actions.createAnywayHint": "Compétence non adaptée ?",
"createModal.skillSuggestion.actions.install": "Installer la Compétence",
"createModal.skillSuggestion.actions.installing": "Installation en cours…",
"createModal.skillSuggestion.actions.openSkills": "Voir dans Compétences",
"createModal.skillSuggestion.actions.tryInLobeAI": "Utiliser dans {{name}}",
"createModal.skillSuggestion.description": "Cela ressemble à un flux de travail réutilisable. Installez la Compétence une fois, puis utilisez-la dans plusieurs Agents.",
"createModal.skillSuggestion.installError": "La Compétence n'a pas été installée. Réessayez ou créez un Agent quand même.",
"createModal.skillSuggestion.installed.description": "Vous pouvez utiliser cette Compétence dans {{name}}, ou l'activer pour tout Agent.",
"createModal.skillSuggestion.installed.ready": "Prêt dans {{name}}",
"createModal.skillSuggestion.installed.title": "Compétence installée",
"createModal.skillSuggestion.title": "Une Compétence pourrait mieux convenir",
"createModal.title": "Que doit faire votre agent ?",
"createTask.assignee": "Attribué à",
"createTask.collapse": "Masquer le champ",
@@ -166,6 +180,8 @@
"extendParams.title": "Fonctionnalités dextension du modèle",
"extendParams.urlContext.desc": "Lorsquil est activé, les liens web sont automatiquement analysés pour extraire le contenu réel de la page",
"extendParams.urlContext.title": "Extraire le contenu des liens web",
"floatingChatPanel.collapse": "Réduire le chat",
"floatingChatPanel.expand": "Agrandir le chat",
"followUpPlaceholder": "Donner suite. @ pour attribuer des tâches à dautres agents.",
"followUpPlaceholderHeterogeneous": "Poursuivre.",
"gatewayMode.beta": "Bêta",
@@ -219,9 +235,13 @@
"heteroAgent.cloudRepo.noRepos": "Aucun dépôt configuré. Ajoutez-les dans les paramètres de l'agent.",
"heteroAgent.cloudRepo.notSet": "Aucun dépôt sélectionné",
"heteroAgent.cloudRepo.sectionTitle": "Dépôts",
"heteroAgent.executionTarget.auto": "Auto",
"heteroAgent.executionTarget.autoDesc": "Utiliser automatiquement un appareil en ligne, en choisissant un lorsqu'il y en a plusieurs disponibles",
"heteroAgent.executionTarget.downloadDesktop": "Obtenir l'application de bureau",
"heteroAgent.executionTarget.downloadDesktopDesc": "Exécutez des agents avec accès à votre ordinateur",
"heteroAgent.executionTarget.downloadDesktopTitle": "Obtenir l'application de bureau",
"heteroAgent.executionTarget.gateway": "Passerelle",
"heteroAgent.executionTarget.gatewayDesc": "Exécuter via la passerelle de l'appareil pour que d'autres clients puissent suivre la progression",
"heteroAgent.executionTarget.infoTooltip": "Choisissez un appareil distant pour piloter cette machine depuis le web. \"Cet appareil\" exécute l'agent localement et n'est disponible que dans l'application de bureau.",
"heteroAgent.executionTarget.loading": "Chargement des appareils…",
"heteroAgent.executionTarget.local": "Cet appareil",
@@ -231,10 +251,12 @@
"heteroAgent.executionTarget.noneDesc": "Aucun appareil activé",
"heteroAgent.executionTarget.offline": "Hors ligne",
"heteroAgent.executionTarget.online": "En ligne",
"heteroAgent.executionTarget.personalGroup": "Personnel",
"heteroAgent.executionTarget.sandbox": "Bac à sable cloud",
"heteroAgent.executionTarget.sandboxDesc": "Exécuter dans un bac à sable cloud éphémère",
"heteroAgent.executionTarget.title": "Appareil d'exécution",
"heteroAgent.executionTarget.unknownDevice": "Appareil inconnu",
"heteroAgent.executionTarget.workspaceGroup": "Espace de travail",
"heteroAgent.fullAccess.label": "Accès complet",
"heteroAgent.fullAccess.tooltip": "Claude Code sexécute localement avec un accès complet en lecture/écriture au répertoire de travail. Le changement de mode dautorisation nest pas encore disponible.",
"heteroAgent.resumeReset.cwdChanged": "Répertoire de travail modifié. La session précédente de Claude Code ne peut être reprise qu’à partir de son répertoire dorigine ; une nouvelle conversation a donc commencé.",
@@ -631,6 +653,8 @@
"taskDetail.artifacts": "Artefacts",
"taskDetail.blockedBy": "Bloqué par {{id}}",
"taskDetail.cancelSchedule": "Annuler la planification",
"taskDetail.closeDetail": "Fermer le détail",
"taskDetail.collapseReply": "Réduire",
"taskDetail.comment.cancel": "Annuler",
"taskDetail.comment.delete": "Supprimer",
"taskDetail.comment.deleteConfirm.content": "Ce commentaire sera définitivement supprimé.",
@@ -657,6 +681,7 @@
"taskDetail.notFound.backToTasks": "Retour à toutes les tâches",
"taskDetail.notFound.desc": "Cette tâche a peut-être été supprimée ou vous n'avez pas la permission de la consulter.",
"taskDetail.notFound.title": "Tâche introuvable",
"taskDetail.openDetail": "Ouvrir le détail",
"taskDetail.pauseTask": "Mettre la tâche en pause",
"taskDetail.priority.high": "Haute",
"taskDetail.priority.low": "Basse",
@@ -925,9 +950,9 @@
"workflow.collapse": "Réduire",
"workflow.expandFull": "Développer complètement",
"workflow.failedSuffix": "(échec)",
"workflow.summaryAcrossTools": "à travers {{count}} outils",
"workflow.summaryCallsLead": "{{count}} appels : {{tools}}",
"workflow.summaryFailed": "{{count}} échecs",
"workflow.summaryMoreTools": "{{count}} types doutils",
"workflow.summaryTotalCalls": "{{count}} appels au total",
"workflow.thoughtForDuration": "Réflexion pendant {{duration}}",
"workflow.toolDisplayName.activateDevice": "Appareil activé",
"workflow.toolDisplayName.activateSkill": "Compétence activée",
@@ -1043,6 +1068,7 @@
"workingPanel.resources.deleteTitle": "Delete document?",
"workingPanel.resources.deleteTitleMulti": "Supprimer {{count}} éléments ?",
"workingPanel.resources.empty": "Aucun document pour linstant. Les documents associés à cet agent apparaîtront ici.",
"workingPanel.resources.emptyDocuments": "Aucun document pour le moment. Créez-en un avec le + ci-dessus.",
"workingPanel.resources.error": "Failed to load resources",
"workingPanel.resources.filter.documents": "Documents",
"workingPanel.resources.filter.skills": "Compétences",
+18 -2
View File
@@ -444,6 +444,23 @@
"tab.setting": "Paramètres",
"tab.tasks": "Tâches",
"tab.video": "Vidéo",
"taskTemplate.action.connect.button": "Connecter {{provider}}",
"taskTemplate.action.connect.error": "Échec de la connexion, veuillez réessayer.",
"taskTemplate.action.connect.popupBlocked": "Fenêtre contextuelle de connexion bloquée. Autorisez les fenêtres contextuelles dans votre navigateur pour continuer.",
"taskTemplate.action.connect.short": "Connecter",
"taskTemplate.action.connecting": "En attente d'autorisation…",
"taskTemplate.action.create.error": "Échec de la création de la tâche. Veuillez réessayer.",
"taskTemplate.action.create.success": "Tâche planifiée ajoutée. Retrouvez-la dans Lobe AI.",
"taskTemplate.action.createButton": "Ajouter une tâche",
"taskTemplate.action.creating": "Création en cours...",
"taskTemplate.action.dismiss.error": "Échec de la suppression. Veuillez réessayer.",
"taskTemplate.action.dismiss.tooltip": "Pas intéressé",
"taskTemplate.action.refresh.button": "Actualiser",
"taskTemplate.card.templateTag": "Modèle",
"taskTemplate.schedule.daily": "Tous les jours à {{time}}",
"taskTemplate.schedule.editableAfterCreateTooltip": "Vous pouvez ajuster le planning après avoir créé la tâche.",
"taskTemplate.schedule.weekly": "Chaque {{weekday}} à {{time}}",
"taskTemplate.section.title": "Essayez ces tâches planifiées",
"telemetry.allow": "Autoriser",
"telemetry.deny": "Refuser",
"telemetry.desc": "Nous souhaitons collecter anonymement des informations d'utilisation pour améliorer {{appName}} et vous offrir une meilleure expérience. Vous pouvez désactiver cette option à tout moment dans Paramètres - À propos.",
@@ -474,15 +491,14 @@
"userPanel.email": "Assistance par e-mail",
"userPanel.feedback": "Nous contacter",
"userPanel.help": "Centre d'aide",
"userPanel.inviteFriend": "Inviter un ami",
"userPanel.moveGuide": "Le bouton des paramètres a été déplacé ici",
"userPanel.plans": "Formules d'abonnement",
"userPanel.profile": "Compte",
"userPanel.setting": "Paramètres",
"userPanel.upgradePlan": "Mettre à niveau le plan",
"userPanel.usages": "Statistiques d'utilisation",
"userPanel.workspaceCredits": "Crédits de l'espace de travail",
"userPanel.workspaceSetting": "Paramètres de l'espace de travail",
"userPanel.workspaceUsages": "Utilisation de l'espace de travail",
"version": "Version",
"zoom": "Zoom"
}
+2
View File
@@ -101,6 +101,7 @@
"LocalFile.action.open": "Ouvrir",
"LocalFile.action.showInFolder": "Afficher dans le dossier",
"MaxTokenSlider.unlimited": "Illimité",
"ModelSelect.featureTag.audio": "Ce modèle prend en charge la reconnaissance des entrées audio.",
"ModelSelect.featureTag.custom": "Modèle personnalisé, prend en charge par défaut les appels de fonction et la reconnaissance visuelle. Veuillez vérifier la disponibilité réelle de ces fonctionnalités.",
"ModelSelect.featureTag.file": "Ce modèle prend en charge le téléversement de fichiers pour lecture et reconnaissance.",
"ModelSelect.featureTag.functionCall": "Ce modèle prend en charge les appels de fonction.",
@@ -114,6 +115,7 @@
"ModelSwitchPanel.byModel": "Par modèle",
"ModelSwitchPanel.byProvider": "Par fournisseur",
"ModelSwitchPanel.detail.abilities": "Capacités",
"ModelSwitchPanel.detail.abilities.audio": "Audio",
"ModelSwitchPanel.detail.abilities.files": "Fichiers",
"ModelSwitchPanel.detail.abilities.functionCall": "Appel doutil",
"ModelSwitchPanel.detail.abilities.imageOutput": "Sortie dimage",
-3
View File
@@ -38,9 +38,6 @@
"brief.title": "Compte rendu quotidien",
"brief.viewAllTasks": "Afficher toutes les tâches",
"brief.viewRun": "Voir l'exécution",
"freeCreditBadge.cta": "Commencer l'essai gratuit",
"freeCreditBadge.dismiss": "Fermer",
"freeCreditBadge.label": "Crédits gratuits exclusifs pour {{model}}",
"project.create": "Nouveau projet",
"project.deleteConfirm": "Ce projet sera supprimé et ne pourra pas être récupéré. Confirmez pour continuer.",
"recommendations.heteroAgent.cta": "Ajouter un agent",
+4 -4
View File
@@ -5,10 +5,12 @@
"authorize.footer.agreement": "En continuant, vous confirmez avoir lu et accepté les <terms>Conditions générales</terms> et la <privacy>Politique de confidentialité</privacy>.",
"authorize.footer.privacy": "Politique de confidentialité",
"authorize.footer.terms": "Conditions d'utilisation",
"authorize.scenes.connector.confirm": "Continuer vers Market",
"authorize.scenes.connector.description": "Market est uniquement utilisé pour démarrer cette autorisation de service. Votre compte {{appName}} reste distinct.",
"authorize.scenes.connector.subtitle": "Connectez-vous à Market pour connecter et autoriser ce service communautaire.",
"authorize.scenes.connector.title": "Connecter le service communautaire",
"authorize.scenes.mcp.subtitle": "Créez un profil communautaire pour installer et exécuter cette compétence depuis la communauté.",
"authorize.scenes.mcp.title": "Installer une Compétence Communautaire",
"authorize.scenes.publish.subtitle": "Créez un profil communautaire pour publier et gérer votre annonce au sein de la communauté.",
"authorize.scenes.publish.title": "Publier dans la Communauté",
"authorize.scenes.sandbox.subtitle": "Créez un profil communautaire pour utiliser cet outil dans le bac à sable communautaire.",
"authorize.scenes.sandbox.title": "Essayer le Bac à Sable Communautaire",
"authorize.subtitle": "Créez un profil communautaire pour soumettre et gérer des annonces au sein de la communauté.",
@@ -50,8 +52,6 @@
"messages.handoffTimeout": "Délai d'autorisation dépassé. Terminez-le dans votre navigateur, puis réessayez.",
"messages.loading": "Démarrage du processus d'autorisation...",
"messages.success.cloudMcpInstall": "Autorisation réussie ! Vous pouvez maintenant installer la compétence Cloud MCP.",
"messages.success.submit": "Autorisation réussie ! Vous pouvez maintenant publier votre agent.",
"messages.success.upload": "Autorisation réussie ! Vous pouvez maintenant publier une nouvelle version.",
"profileSetup.cancel": "Annuler",
"profileSetup.confirmChangeUserId.cancel": "Annuler",
"profileSetup.confirmChangeUserId.confirm": "Changer l'identifiant",
+4 -2
View File
@@ -222,6 +222,7 @@
"providerModels.item.modelConfig.extendParams.options.effort.hint": "Pour Claude Opus 4.6 ; contrôle le niveau d'effort (faible/moyen/élevé/maximal).",
"providerModels.item.modelConfig.extendParams.options.enableAdaptiveThinking.hint": "Pour Claude Opus 4.6 ; active ou désactive la pensée adaptative.",
"providerModels.item.modelConfig.extendParams.options.enableReasoning.hint": "Pour Claude, DeepSeek et autres modèles de raisonnement ; active une réflexion plus approfondie.",
"providerModels.item.modelConfig.extendParams.options.glm5_2ReasoningEffort.hint": "Pour GLM-5.2 ; contrôle l'effort de raisonnement avec des niveaux Élevé et Maximal.",
"providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint": "Pour la série GPT-5 ; contrôle lintensité du raisonnement.",
"providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint": "Pour la série GPT-5.1 ; contrôle lintensité du raisonnement.",
"providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "Pour la série GPT-5.2 Pro ; contrôle lintensité du raisonnement.",
@@ -256,6 +257,7 @@
"providerModels.item.modelConfig.files.title": "Prise en charge du téléversement de fichiers",
"providerModels.item.modelConfig.functionCall.extra": "Cette configuration activera uniquement la capacité du modèle à utiliser des outils. L'utilisation effective dépend entièrement du modèle lui-même ; veuillez tester sa compatibilité.",
"providerModels.item.modelConfig.functionCall.title": "Prise en charge de l'utilisation d'outils",
"providerModels.item.modelConfig.id.duplicate": "Un modèle avec cet ID existe déjà. Utilisez un autre ID de modèle.",
"providerModels.item.modelConfig.id.extra": "Ne peut pas être modifié après création et sera utilisé comme identifiant du modèle lors des appels IA",
"providerModels.item.modelConfig.id.placeholder": "Veuillez saisir l'identifiant du modèle, par ex. gpt-4o ou claude-3.5-sonnet",
"providerModels.item.modelConfig.id.title": "Identifiant du modèle",
@@ -270,11 +272,11 @@
"providerModels.item.modelConfig.tokens.title": "Fenêtre de contexte maximale",
"providerModels.item.modelConfig.tokens.unlimited": "Illimité",
"providerModels.item.modelConfig.type.extra": "Les différents types de modèles ont des cas d'utilisation et des capacités distincts",
"providerModels.item.modelConfig.type.options.asr": "Transcription vocale",
"providerModels.item.modelConfig.type.options.chat": "Chat",
"providerModels.item.modelConfig.type.options.embedding": "Embedding",
"providerModels.item.modelConfig.type.options.image": "Génération d'images",
"providerModels.item.modelConfig.type.options.realtime": "Chat en temps réel",
"providerModels.item.modelConfig.type.options.stt": "Reconnaissance vocale",
"providerModels.item.modelConfig.type.options.text2music": "Texte en musique",
"providerModels.item.modelConfig.type.options.tts": "Synthèse vocale",
"providerModels.item.modelConfig.type.options.video": "Génération de vidéo",
@@ -323,10 +325,10 @@
"providerModels.list.total": "{{count}} modèles disponibles",
"providerModels.searchNotFound": "Aucun résultat trouvé",
"providerModels.tabs.all": "Tous",
"providerModels.tabs.asr": "ASR",
"providerModels.tabs.chat": "Chat",
"providerModels.tabs.embedding": "Embedding",
"providerModels.tabs.image": "Image",
"providerModels.tabs.stt": "ASR",
"providerModels.tabs.tts": "TTS",
"providerModels.tabs.video": "Vidéo",
"sortModal.success": "Tri mis à jour avec succès",
+9 -69
View File
@@ -440,60 +440,7 @@
"llm.proxyUrl.title": "URL du proxy API",
"llm.waitingForMore": "D'autres modèles <1>seront ajoutés prochainement</1>, restez à l'écoute",
"llm.waitingForMoreLinkAriaLabel": "Ouvrir le formulaire de demande de fournisseur",
"marketPublish.forkConfirm.by": "par {{author}}",
"marketPublish.forkConfirm.confirm": "Confirmer la publication",
"marketPublish.forkConfirm.confirmGroup": "Confirmer la publication",
"marketPublish.forkConfirm.description": "Vous êtes sur le point de publier une version dérivée basée sur un agent existant de la communauté. Votre nouvel agent sera créé comme une entrée distincte dans la place de marché.",
"marketPublish.forkConfirm.descriptionGroup": "Vous êtes sur le point de publier une version dérivée basée sur un groupe existant de la communauté. Votre nouveau groupe sera créé comme une entrée distincte dans la place de marché.",
"marketPublish.forkConfirm.title": "Publier un agent dérivé",
"marketPublish.forkConfirm.titleGroup": "Publier un groupe dérivé",
"marketPublish.modal.changelog.extra": "Décrivez les principales modifications et améliorations de cette version",
"marketPublish.modal.changelog.label": "Journal des modifications",
"marketPublish.modal.changelog.maxLengthError": "Le journal des modifications ne doit pas dépasser 500 caractères",
"marketPublish.modal.changelog.placeholder": "Saisissez le journal des modifications",
"marketPublish.modal.changelog.required": "Veuillez saisir le journal des modifications",
"marketPublish.modal.comparison.local": "Version locale actuelle",
"marketPublish.modal.comparison.remote": "Version actuellement publiée",
"marketPublish.modal.identifier.extra": "Il s'agit de l'identifiant unique de l'agent. Utilisez des lettres minuscules, des chiffres et des tirets.",
"marketPublish.modal.identifier.label": "Identifiant de l'agent",
"marketPublish.modal.identifier.lengthError": "L'identifiant doit comporter entre 3 et 50 caractères",
"marketPublish.modal.identifier.patternError": "L'identifiant ne peut contenir que des lettres minuscules, des chiffres et des tirets",
"marketPublish.modal.identifier.placeholder": "Saisissez un identifiant unique pour l'agent, ex. : developpement-web",
"marketPublish.modal.identifier.required": "Veuillez saisir l'identifiant de l'agent",
"marketPublish.modal.loading.fetchingRemote": "Chargement des données distantes...",
"marketPublish.modal.loading.submit": "Soumission de l'agent...",
"marketPublish.modal.loading.submitGroup": "Soumission du groupe en cours...",
"marketPublish.modal.loading.upload": "Publication de la nouvelle version...",
"marketPublish.modal.loading.uploadGroup": "Publication de la nouvelle version du groupe en cours...",
"marketPublish.modal.messages.createVersionFailed": "Échec de la création de la version : {{message}}",
"marketPublish.modal.messages.fetchRemoteFailed": "Échec du chargement des données de l'agent distant",
"marketPublish.modal.messages.missingIdentifier": "Cet agent n'a pas encore d'identifiant communautaire.",
"marketPublish.modal.messages.noGroup": "Aucun groupe sélectionné",
"marketPublish.modal.messages.notAuthenticated": "Connectez-vous d'abord à votre compte Communauté.",
"marketPublish.modal.messages.publishFailed": "Échec de la publication : {{message}}",
"marketPublish.modal.submitButton": "Publier",
"marketPublish.modal.title.submit": "Partager avec la Communauté des Agents",
"marketPublish.modal.title.upload": "Publier une nouvelle version",
"marketPublish.resultModal.message": "Votre agent a été soumis pour révision. Une fois approuvé, il sera mis en ligne automatiquement.",
"marketPublish.resultModal.messageGroup": "Votre groupe a été soumis pour révision. Une fois approuvé, il sera mis en ligne automatiquement.",
"marketPublish.resultModal.title": "Soumission réussie",
"marketPublish.resultModal.view": "Voir dans la Communauté",
"marketPublish.status.underReview": "En cours d'examen",
"marketPublish.submit.button": "Partager avec la Communauté",
"marketPublish.submit.tooltip": "Partager cet agent avec la Communauté",
"marketPublish.submitGroup.tooltip": "Partager ce groupe avec la communauté",
"marketPublish.upload.button": "Publier une nouvelle version",
"marketPublish.upload.tooltip": "Publier une nouvelle version dans la Communauté des Agents",
"marketPublish.uploadGroup.tooltip": "Publier une nouvelle version dans la communauté du groupe",
"marketPublish.validation.communitySetupRequired.action": "Configurer maintenant",
"marketPublish.validation.communitySetupRequired.desc": "Cet espace de travail n'a pas encore configuré son profil Communauté. Configurez-le avant de publier dans la Communauté.",
"marketPublish.validation.communitySetupRequired.memberHint": "Cet espace de travail n'a pas encore configuré son profil Communauté. Demandez à un propriétaire de l'espace de travail de le configurer avant de publier dans la Communauté.",
"marketPublish.validation.communitySetupRequired.title": "Configurer d'abord le profil Communauté",
"marketPublish.validation.confirmPublish": "Publier sur le Marché ?",
"marketPublish.validation.confirmPublishDesc": "Une fois publié, ce contenu sera visible publiquement sur le marché et accessible à tous pour le découvrir et l'utiliser.",
"marketPublish.validation.emptyName": "Impossible de publier : Le nom est requis",
"marketPublish.validation.emptySystemRole": "Impossible de publier : Le rôle système est requis",
"marketPublish.validation.underReview": "Votre nouvelle version est actuellement en cours d'examen. Veuillez attendre l'approbation avant de publier une nouvelle version.",
"memory.effort.desc": "Contrôlez l'intensité avec laquelle l'IA récupère et met à jour la mémoire.",
"memory.effort.high": "Élevé — Récupération et mises à jour proactives",
"memory.effort.level.high": "Élevé",
@@ -515,14 +462,6 @@
"myAgents.actions.deprecateLoading": "Mise en obsolescence de l'agent...",
"myAgents.actions.deprecateSuccess": "Agent rendu obsolète",
"myAgents.actions.edit": "Modifier l'agent",
"myAgents.actions.publish": "Publier l'agent",
"myAgents.actions.publishError": "Échec de la publication de l'agent",
"myAgents.actions.publishLoading": "Publication de l'agent...",
"myAgents.actions.publishSuccess": "Agent publié",
"myAgents.actions.unpublish": "Dépublier l'agent",
"myAgents.actions.unpublishError": "Échec de la dépublication de l'agent",
"myAgents.actions.unpublishLoading": "Dépublication de l'agent...",
"myAgents.actions.unpublishSuccess": "Agent dépublié",
"myAgents.actions.viewDetail": "Voir les détails",
"myAgents.detail.category": "Catégorie",
"myAgents.detail.description": "Description",
@@ -587,7 +526,6 @@
"plugin.settings.title": "Configuration de la compétence {{id}}",
"plugin.settings.tooltip": "Configuration de la compétence",
"plugin.store": "Skill Store",
"publishToCommunity": "Publier dans la communauté",
"serviceModel.contextLimit.placeholder": "Limite de contexte",
"serviceModel.memoryModels.title": "Modèles de mémoire",
"serviceModel.modelAssignments.title": "Attributions de modèles",
@@ -955,13 +893,6 @@
"storageOverage.usage.estimatedCharge": "Charge estimée du cycle",
"storageOverage.usage.incurredCharge": "Engagé ce cycle",
"storageOverage.usage.overage": "Dépassement",
"submitAgentModal.button": "Soumettre lagent",
"submitAgentModal.identifier": "Identifiant de lagent",
"submitAgentModal.metaMiss": "Veuillez compléter les informations de lagent avant de soumettre. Cela doit inclure le nom, la description et les étiquettes.",
"submitAgentModal.placeholder": "Saisissez un identifiant unique pour lagent, ex. : developpement-web",
"submitAgentModal.success": "Agent soumis avec succès",
"submitAgentModal.tooltips": "Partager avec la communauté des agents",
"submitGroupModal.tooltips": "Partager avec la communauté du groupe",
"sync.device.deviceName.hint": "Ajoutez un nom pour faciliter lidentification",
"sync.device.deviceName.placeholder": "Saisir le nom de lappareil",
"sync.device.deviceName.title": "Nom de lappareil",
@@ -1086,6 +1017,7 @@
"tools.activation.auto": "Auto",
"tools.activation.auto.desc": "Intelligent",
"tools.activation.fixed.hint": "Toujours activé — géré par l'application et ne peut pas être désactivé",
"tools.activation.pin": "Code PIN",
"tools.activation.pinned": "Épinglé",
"tools.activation.pinned.desc": "Toujours Activé",
"tools.add": "Ajouter une compétence",
@@ -2047,6 +1979,14 @@
"workspace.wizard.step3.title": "Bienvenue sur {{name}} !",
"workspace.wizard.title": "Créer un espace de travail",
"workspaceSetting.breadcrumb.settings": "Paramètres",
"workspaceSetting.devices.desc": "Machines partagées inscrites dans cet espace de travail. Les membres peuvent y exécuter des agents.",
"workspaceSetting.devices.empty": "Aucun appareil d'espace de travail pour le moment.",
"workspaceSetting.devices.enrollDesc": "Exécutez ceci sur la machine que vous souhaitez partager (propriétaire de l'espace de travail uniquement) :",
"workspaceSetting.devices.enrollTitle": "Ajouter un appareil",
"workspaceSetting.devices.heroDesc": "Inscrivez une machine partagée — un serveur de build ou un Mac d'équipe — et chaque membre pourra y exécuter des agents : lire/écrire des fichiers, exécuter des commandes et utiliser des outils système.",
"workspaceSetting.devices.heroTitle": "Connectez votre premier appareil d'espace de travail",
"workspaceSetting.devices.offline": "Hors ligne",
"workspaceSetting.devices.online": "En ligne",
"workspaceSetting.group.admin": "Administrateur",
"workspaceSetting.group.agent": "Agent",
"workspaceSetting.group.general": "Général",
+10 -5
View File
@@ -147,10 +147,6 @@
"limitation.chat.topupSuccess.title": "Recharge réussie",
"limitation.expired.desc": "Vos crédits de calcul {{plan}} ont expiré le {{expiredAt}}. Mettez à niveau votre forfait pour obtenir de nouveaux crédits.",
"limitation.expired.title": "Crédits de calcul expirés",
"limitation.fableCampaign.desc": "Claude Fable 5 est un modèle coûteux. Les crédits d'essai de la campagne ont été épuisés. Mettez à niveau votre plan pour continuer à utiliser Fable.",
"limitation.fableCampaign.title": "Crédits d'essai Fable épuisés",
"limitation.fableCampaign.upgrade": "Mettre à niveau le plan",
"limitation.fableCampaign.upgradeToPlan": "Passer au plan {{plan}}",
"limitation.hobby.action": "Configuré, continuer à discuter",
"limitation.hobby.configAPI": "Configurer l'API",
"limitation.hobby.desc": "Vos crédits de calcul gratuits sont épuisés. Veuillez configurer une API de modèle personnalisée pour continuer.",
@@ -342,7 +338,14 @@
"plans.workspace.noSharedCredits": "Pas de crédits partagés",
"plans.workspace.sharedCredits": "~{{count}} crédits / mois",
"plans.workspace.solo": "Solo (1 membre)",
"promoBanner.fableYearly": "Les abonnés annuels bénéficient de {{percent}}% de réduction pour une durée limitée",
"plansModal.creditLimit.desc": "Mettez à niveau votre plan pour débloquer plus de crédits mensuels et continuer à travailler sans interruption.",
"plansModal.creditLimit.title": "Vous n'avez plus de crédits",
"plansModal.default.desc": "Débloquez plus de capacité et des fonctionnalités avancées.",
"plansModal.default.title": "Mettez à niveau votre plan",
"plansModal.fileStorageLimit.desc": "Votre espace de stockage est plein. Mettez à niveau pour continuer à télécharger et gérer vos fichiers.",
"plansModal.fileStorageLimit.title": "Limite de stockage atteinte",
"plansModal.modelAccess.desc": "Ce modèle est disponible avec des plans payants. Mettez à niveau pour accéder à l'ensemble complet des modèles.",
"plansModal.modelAccess.title": "Débloquez tous les modèles",
"qa.desc": "Si votre question nest pas traitée, consultez la <1>documentation produit</1> pour plus de FAQ ou contactez-nous.",
"qa.detail": "Voir les détails",
"qa.list.credit.a": "Les crédits de calcul sont une unité utilisée par {{cloud}} pour mesurer lutilisation des modèles IA. Chaque modèle consomme un nombre différent de crédits.",
@@ -398,6 +401,8 @@
"referral.errors.invalidFormat": "Format invalide, veuillez entrer 2 à 8 lettres, chiffres ou underscores",
"referral.errors.selfReferral": "Vous ne pouvez pas utiliser votre propre code d'invitation",
"referral.errors.updateFailed": "Échec de la mise à jour, veuillez réessayer plus tard",
"referral.hero.description": "Partagez votre lien de parrainage ci-dessous. Après que votre ami ait effectué son premier paiement, vous gagnez chacun {{reward}}M crédits.",
"referral.hero.title": "Invitez des amis, vous gagnez tous les deux <0>{{reward}}M crédits</0>",
"referral.inviteCode.description": "Partagez votre code de parrainage exclusif pour inviter vos amis",
"referral.inviteCode.title": "Mon code de parrainage",
"referral.inviteLink.description": "Copiez le lien et partagez-le avec vos amis. Vous gagnez tous les deux des crédits après que votre ami ait effectué un paiement.",
+1
View File
@@ -25,6 +25,7 @@
"actions.unmarkCompleted": "Marquer comme actif",
"defaultTitle": "Sujet par défaut",
"displayItems": "Afficher les éléments",
"draft": "[Brouillon]",
"duplicateLoading": "Copie du sujet en cours...",
"duplicateSuccess": "Sujet copié avec succès",
"failedStatusTip": "Cette exécution a rencontré une erreur — ouvrez-la pour en savoir plus.",
+28 -2
View File
@@ -41,6 +41,8 @@
"artifact.thinking": "Riflessione in corso",
"artifact.thought": "Processo di pensiero",
"artifact.unknownTitle": "Lavoro senza titolo",
"audioPlayer.pause": "Pausa audio",
"audioPlayer.play": "Riproduci audio",
"availableAgents": "Agenti disponibili",
"backToBottom": "Vai all'ultimo",
"beforeUnload.confirmLeave": "Una richiesta è ancora in corso. Vuoi uscire comunque?",
@@ -120,6 +122,18 @@
"createModal.groupPlaceholder": "Descrivi cosa dovrebbe fare questo gruppo...",
"createModal.groupTitle": "Cosa dovrebbe fare il tuo gruppo?",
"createModal.placeholder": "Descrivi cosa dovrebbe fare il tuo agente...",
"createModal.skillSuggestion.actions.createAnyway": "Crea Agente Comunque",
"createModal.skillSuggestion.actions.createAnywayHint": "Abilità non adatta?",
"createModal.skillSuggestion.actions.install": "Installa Abilità",
"createModal.skillSuggestion.actions.installing": "Installazione in corso…",
"createModal.skillSuggestion.actions.openSkills": "Visualizza in Abilità",
"createModal.skillSuggestion.actions.tryInLobeAI": "Usa in {{name}}",
"createModal.skillSuggestion.description": "Sembra un flusso di lavoro riutilizzabile. Installa l'Abilità una volta, poi usala con tutti gli Agenti.",
"createModal.skillSuggestion.installError": "L'Abilità non è stata installata. Riprova o crea comunque un Agente.",
"createModal.skillSuggestion.installed.description": "Puoi usare questa Abilità in {{name}}, o abilitarla per qualsiasi Agente.",
"createModal.skillSuggestion.installed.ready": "Pronto in {{name}}",
"createModal.skillSuggestion.installed.title": "Abilità installata",
"createModal.skillSuggestion.title": "Un'Abilità potrebbe essere più adatta",
"createModal.title": "Cosa dovrebbe fare il tuo agente?",
"createTask.assignee": "Assegnatario",
"createTask.collapse": "Nascondi input",
@@ -166,6 +180,8 @@
"extendParams.title": "Funzionalità Estese del Modello",
"extendParams.urlContext.desc": "Se abilitato, i link web verranno automaticamente analizzati per recuperare il contenuto della pagina",
"extendParams.urlContext.title": "Estrai Contenuto da Link Web",
"floatingChatPanel.collapse": "Riduci chat",
"floatingChatPanel.expand": "Espandi chat",
"followUpPlaceholder": "Follow-up. Usa @ per assegnare attività ad altri agenti.",
"followUpPlaceholderHeterogeneous": "Continua.",
"gatewayMode.beta": "Beta",
@@ -219,9 +235,13 @@
"heteroAgent.cloudRepo.noRepos": "Nessun repository configurato. Aggiungili nelle impostazioni dell'agente.",
"heteroAgent.cloudRepo.notSet": "Nessun repository selezionato",
"heteroAgent.cloudRepo.sectionTitle": "Repository",
"heteroAgent.executionTarget.auto": "Automatico",
"heteroAgent.executionTarget.autoDesc": "Usa automaticamente un dispositivo online, scegliendone uno quando ce ne sono diversi disponibili",
"heteroAgent.executionTarget.downloadDesktop": "Scarica l'App Desktop",
"heteroAgent.executionTarget.downloadDesktopDesc": "Esegui agenti con accesso al tuo computer",
"heteroAgent.executionTarget.downloadDesktopTitle": "Scarica l'app desktop",
"heteroAgent.executionTarget.gateway": "Gateway",
"heteroAgent.executionTarget.gatewayDesc": "Esegui tramite il gateway del dispositivo in modo che altri client possano seguire i progressi",
"heteroAgent.executionTarget.infoTooltip": "Scegli un dispositivo remoto per controllare quella macchina dal web. \"Questo dispositivo\" esegue l'agente localmente ed è disponibile solo nell'app desktop.",
"heteroAgent.executionTarget.loading": "Caricamento dispositivi…",
"heteroAgent.executionTarget.local": "Questo dispositivo",
@@ -231,10 +251,12 @@
"heteroAgent.executionTarget.noneDesc": "Nessun dispositivo abilitato",
"heteroAgent.executionTarget.offline": "Offline",
"heteroAgent.executionTarget.online": "Online",
"heteroAgent.executionTarget.personalGroup": "Personale",
"heteroAgent.executionTarget.sandbox": "Sandbox cloud",
"heteroAgent.executionTarget.sandboxDesc": "Esegui in un sandbox cloud temporaneo",
"heteroAgent.executionTarget.title": "Dispositivo di esecuzione",
"heteroAgent.executionTarget.unknownDevice": "Dispositivo sconosciuto",
"heteroAgent.executionTarget.workspaceGroup": "Workspace",
"heteroAgent.fullAccess.label": "Accesso completo",
"heteroAgent.fullAccess.tooltip": "Claude Code viene eseguito localmente con accesso completo in lettura/scrittura alla directory di lavoro. Il cambio delle modalità di autorizzazione non è ancora disponibile.",
"heteroAgent.resumeReset.cwdChanged": "La directory di lavoro è stata modificata. Una sessione precedente di Claude Code può essere ripristinata solo dalla directory originale, quindi è iniziata una nuova conversazione.",
@@ -631,6 +653,8 @@
"taskDetail.artifacts": "Artefatti",
"taskDetail.blockedBy": "Bloccata da {{id}}",
"taskDetail.cancelSchedule": "Annulla pianificazione",
"taskDetail.closeDetail": "Chiudi dettaglio",
"taskDetail.collapseReply": "Riduci",
"taskDetail.comment.cancel": "Annulla",
"taskDetail.comment.delete": "Elimina",
"taskDetail.comment.deleteConfirm.content": "Questo commento verrà rimosso definitivamente.",
@@ -657,6 +681,7 @@
"taskDetail.notFound.backToTasks": "Torna a tutte le attività",
"taskDetail.notFound.desc": "Questa attività potrebbe essere stata eliminata o non hai il permesso di visualizzarla.",
"taskDetail.notFound.title": "Attività non trovata",
"taskDetail.openDetail": "Apri dettaglio",
"taskDetail.pauseTask": "Metti in pausa",
"taskDetail.priority.high": "Alta",
"taskDetail.priority.low": "Bassa",
@@ -925,9 +950,9 @@
"workflow.collapse": "Comprimi",
"workflow.expandFull": "Espandi completamente",
"workflow.failedSuffix": "(non riuscito)",
"workflow.summaryAcrossTools": "attraverso {{count}} strumenti",
"workflow.summaryCallsLead": "{{count}} chiamate: {{tools}}",
"workflow.summaryFailed": "{{count}} non riuscite",
"workflow.summaryMoreTools": "{{count}} tipi di strumenti",
"workflow.summaryTotalCalls": "{{count}} chiamate totali",
"workflow.thoughtForDuration": "Riflessione per {{duration}}",
"workflow.toolDisplayName.activateDevice": "Dispositivo attivato",
"workflow.toolDisplayName.activateSkill": "Ha attivato un'abilità",
@@ -1043,6 +1068,7 @@
"workingPanel.resources.deleteTitle": "Delete document?",
"workingPanel.resources.deleteTitleMulti": "Eliminare {{count}} elementi?",
"workingPanel.resources.empty": "Nessun documento al momento. I documenti associati a questo agente verranno mostrati qui.",
"workingPanel.resources.emptyDocuments": "Nessun documento ancora. Creane uno con il + sopra.",
"workingPanel.resources.error": "Failed to load resources",
"workingPanel.resources.filter.documents": "Documenti",
"workingPanel.resources.filter.skills": "Competenze",
+18 -2
View File
@@ -444,6 +444,23 @@
"tab.setting": "Impostazioni",
"tab.tasks": "Attività",
"tab.video": "Video",
"taskTemplate.action.connect.button": "Connetti {{provider}}",
"taskTemplate.action.connect.error": "Connessione fallita, riprova.",
"taskTemplate.action.connect.popupBlocked": "Popup di connessione bloccato. Consenti i popup nel tuo browser per continuare.",
"taskTemplate.action.connect.short": "Connetti",
"taskTemplate.action.connecting": "In attesa di autorizzazione…",
"taskTemplate.action.create.error": "Impossibile creare l'attività. Riprova.",
"taskTemplate.action.create.success": "Attività pianificata aggiunta. Trovala in Lobe AI.",
"taskTemplate.action.createButton": "Aggiungi attività",
"taskTemplate.action.creating": "Creazione in corso...",
"taskTemplate.action.dismiss.error": "Impossibile ignorare. Riprova.",
"taskTemplate.action.dismiss.tooltip": "Non interessato",
"taskTemplate.action.refresh.button": "Aggiorna",
"taskTemplate.card.templateTag": "Modello",
"taskTemplate.schedule.daily": "Ogni giorno alle {{time}}",
"taskTemplate.schedule.editableAfterCreateTooltip": "Puoi modificare la pianificazione dopo aver creato l'attività.",
"taskTemplate.schedule.weekly": "Ogni {{weekday}} alle {{time}}",
"taskTemplate.section.title": "Prova queste attività pianificate",
"telemetry.allow": "Consenti",
"telemetry.deny": "Nega",
"telemetry.desc": "Vorremmo raccogliere in modo anonimo informazioni sull'utilizzo per migliorare {{appName}} e offrirti un'esperienza migliore. Puoi disattivare questa opzione in qualsiasi momento in Impostazioni - Informazioni.",
@@ -474,15 +491,14 @@
"userPanel.email": "Supporto via email",
"userPanel.feedback": "Contattaci",
"userPanel.help": "Centro assistenza",
"userPanel.inviteFriend": "Invita un amico",
"userPanel.moveGuide": "Il pulsante delle impostazioni è stato spostato qui",
"userPanel.plans": "Piani di abbonamento",
"userPanel.profile": "Account",
"userPanel.setting": "Impostazioni",
"userPanel.upgradePlan": "Aggiorna piano",
"userPanel.usages": "Statistiche di utilizzo",
"userPanel.workspaceCredits": "Crediti dello Spazio di Lavoro",
"userPanel.workspaceSetting": "Impostazioni dello Spazio di Lavoro",
"userPanel.workspaceUsages": "Utilizzo dello Spazio di Lavoro",
"version": "Versione",
"zoom": "Zoom"
}
+2
View File
@@ -101,6 +101,7 @@
"LocalFile.action.open": "Apri",
"LocalFile.action.showInFolder": "Mostra nella Cartella",
"MaxTokenSlider.unlimited": "Illimitato",
"ModelSelect.featureTag.audio": "Questo modello supporta il riconoscimento dell'input audio.",
"ModelSelect.featureTag.custom": "Modello personalizzato, supporta per impostazione predefinita sia le chiamate di funzione che il riconoscimento visivo. Verifica la disponibilità delle funzionalità in base alla situazione reale.",
"ModelSelect.featureTag.file": "Questo modello supporta il caricamento di file per lettura e riconoscimento.",
"ModelSelect.featureTag.functionCall": "Questo modello supporta le chiamate di funzione.",
@@ -114,6 +115,7 @@
"ModelSwitchPanel.byModel": "Per Modello",
"ModelSwitchPanel.byProvider": "Per Fornitore",
"ModelSwitchPanel.detail.abilities": "Funzionalità",
"ModelSwitchPanel.detail.abilities.audio": "Audio",
"ModelSwitchPanel.detail.abilities.files": "File",
"ModelSwitchPanel.detail.abilities.functionCall": "Chiamata Strumento",
"ModelSwitchPanel.detail.abilities.imageOutput": "Output Immagine",
-3
View File
@@ -38,9 +38,6 @@
"brief.title": "Riepilogo quotidiano",
"brief.viewAllTasks": "Visualizza tutte le attività",
"brief.viewRun": "Visualizza esecuzione",
"freeCreditBadge.cta": "Inizia la prova gratuita",
"freeCreditBadge.dismiss": "Chiudi",
"freeCreditBadge.label": "Crediti gratuiti esclusivi per {{model}}",
"project.create": "Nuovo progetto",
"project.deleteConfirm": "Questo progetto verrà eliminato e non potrà essere recuperato. Conferma per continuare.",
"recommendations.heteroAgent.cta": "Aggiungi Agente",
+4 -4
View File
@@ -5,10 +5,12 @@
"authorize.footer.agreement": "Continuando, confermi di aver letto e accettato i <terms>Termini e Condizioni</terms> e l'<privacy>Informativa sulla Privacy</privacy>.",
"authorize.footer.privacy": "Informativa sulla Privacy",
"authorize.footer.terms": "Termini di Servizio",
"authorize.scenes.connector.confirm": "Continua su Market",
"authorize.scenes.connector.description": "Market viene utilizzato solo per avviare questa autorizzazione al servizio. Il tuo account {{appName}} rimane separato.",
"authorize.scenes.connector.subtitle": "Accedi a Market per connettere e autorizzare questo servizio comunitario.",
"authorize.scenes.connector.title": "Connetti Servizio Comunitario",
"authorize.scenes.mcp.subtitle": "Crea un profilo comunitario per installare ed eseguire questa abilità dalla comunità.",
"authorize.scenes.mcp.title": "Installa Abilità della Comunità",
"authorize.scenes.publish.subtitle": "Crea un profilo comunitario per pubblicare e gestire il tuo annuncio all'interno della comunità.",
"authorize.scenes.publish.title": "Pubblica nella Comunità",
"authorize.scenes.sandbox.subtitle": "Crea un profilo comunitario per eseguire questo strumento nel sandbox della comunità.",
"authorize.scenes.sandbox.title": "Prova il Sandbox della Comunità",
"authorize.subtitle": "Crea un profilo della community per inviare e gestire inserzioni all'interno della community.",
@@ -50,8 +52,6 @@
"messages.handoffTimeout": "Tempo scaduto per l'autorizzazione. Completa nel browser, poi riprova.",
"messages.loading": "Avvio del processo di autorizzazione...",
"messages.success.cloudMcpInstall": "Autorizzazione riuscita! Ora puoi installare la skill Cloud MCP.",
"messages.success.submit": "Autorizzazione riuscita! Ora puoi pubblicare il tuo agente.",
"messages.success.upload": "Autorizzazione riuscita! Ora puoi pubblicare una nuova versione.",
"profileSetup.cancel": "Annulla",
"profileSetup.confirmChangeUserId.cancel": "Annulla",
"profileSetup.confirmChangeUserId.confirm": "Cambia ID utente",
+4 -2
View File
@@ -222,6 +222,7 @@
"providerModels.item.modelConfig.extendParams.options.effort.hint": "Per Claude Opus 4.6; controlla il livello di impegno (basso/medio/alto/massimo).",
"providerModels.item.modelConfig.extendParams.options.enableAdaptiveThinking.hint": "Per Claude Opus 4.6; attiva o disattiva il pensiero adattivo.",
"providerModels.item.modelConfig.extendParams.options.enableReasoning.hint": "Per Claude, DeepSeek e altri modelli di ragionamento; abilita un pensiero più profondo.",
"providerModels.item.modelConfig.extendParams.options.glm5_2ReasoningEffort.hint": "Per GLM-5.2; controlla lo sforzo di ragionamento con livelli Alto e Massimo.",
"providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint": "Per la serie GPT-5; controlla l'intensità del ragionamento.",
"providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint": "Per la serie GPT-5.1; controlla l'intensità del ragionamento.",
"providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "Per la serie GPT-5.2 Pro; controlla l'intensità del ragionamento.",
@@ -256,6 +257,7 @@
"providerModels.item.modelConfig.files.title": "Supporto caricamento file",
"providerModels.item.modelConfig.functionCall.extra": "Questa configurazione abilita solo la capacità del modello di utilizzare strumenti. L'effettiva usabilità dipende interamente dal modello stesso; testare in autonomia.",
"providerModels.item.modelConfig.functionCall.title": "Supporto utilizzo strumenti",
"providerModels.item.modelConfig.id.duplicate": "Esiste già un modello con questo ID. Usa un ID modello diverso.",
"providerModels.item.modelConfig.id.extra": "Non modificabile dopo la creazione e sarà utilizzato come ID del modello nelle chiamate AI",
"providerModels.item.modelConfig.id.placeholder": "Inserisci l'ID del modello, ad es. gpt-4o o claude-3.5-sonnet",
"providerModels.item.modelConfig.id.title": "ID modello",
@@ -270,11 +272,11 @@
"providerModels.item.modelConfig.tokens.title": "Finestra di contesto massima",
"providerModels.item.modelConfig.tokens.unlimited": "Illimitato",
"providerModels.item.modelConfig.type.extra": "Tipi di modelli diversi hanno casi d'uso e capacità differenti",
"providerModels.item.modelConfig.type.options.asr": "Da Voce a Testo",
"providerModels.item.modelConfig.type.options.chat": "Chat",
"providerModels.item.modelConfig.type.options.embedding": "Embedding",
"providerModels.item.modelConfig.type.options.image": "Generazione immagini",
"providerModels.item.modelConfig.type.options.realtime": "Chat in tempo reale",
"providerModels.item.modelConfig.type.options.stt": "Da voce a testo",
"providerModels.item.modelConfig.type.options.text2music": "Testo in musica",
"providerModels.item.modelConfig.type.options.tts": "Da testo a voce",
"providerModels.item.modelConfig.type.options.video": "Generazione Video",
@@ -323,10 +325,10 @@
"providerModels.list.total": "{{count}} modelli disponibili",
"providerModels.searchNotFound": "Nessun risultato trovato",
"providerModels.tabs.all": "Tutti",
"providerModels.tabs.asr": "ASR",
"providerModels.tabs.chat": "Chat",
"providerModels.tabs.embedding": "Embedding",
"providerModels.tabs.image": "Immagine",
"providerModels.tabs.stt": "ASR",
"providerModels.tabs.tts": "TTS",
"providerModels.tabs.video": "Video",
"sortModal.success": "Ordinamento aggiornato con successo",
+9 -69
View File
@@ -440,60 +440,7 @@
"llm.proxyUrl.title": "URL Proxy API",
"llm.waitingForMore": "Altri modelli <1>in arrivo</1>, resta aggiornato",
"llm.waitingForMoreLinkAriaLabel": "Apri il modulo di richiesta Fornitore",
"marketPublish.forkConfirm.by": "di {{author}}",
"marketPublish.forkConfirm.confirm": "Conferma Pubblicazione",
"marketPublish.forkConfirm.confirmGroup": "Conferma Pubblicazione",
"marketPublish.forkConfirm.description": "Stai per pubblicare una versione derivata basata su un agente esistente della community. Il tuo nuovo agente verrà creato come una voce separata nel marketplace.",
"marketPublish.forkConfirm.descriptionGroup": "Stai per pubblicare una versione derivata basata su un gruppo esistente della community. Il tuo nuovo gruppo sarà creato come una voce separata nel marketplace.",
"marketPublish.forkConfirm.title": "Pubblica Agente Derivato",
"marketPublish.forkConfirm.titleGroup": "Pubblica Gruppo Derivato",
"marketPublish.modal.changelog.extra": "Descrivi i principali cambiamenti e miglioramenti di questa versione",
"marketPublish.modal.changelog.label": "Registro modifiche",
"marketPublish.modal.changelog.maxLengthError": "Il registro modifiche non può superare i 500 caratteri",
"marketPublish.modal.changelog.placeholder": "Inserisci il registro modifiche",
"marketPublish.modal.changelog.required": "Inserisci il registro modifiche",
"marketPublish.modal.comparison.local": "Versione locale attuale",
"marketPublish.modal.comparison.remote": "Versione attualmente pubblicata",
"marketPublish.modal.identifier.extra": "Questo è l'identificatore univoco dell'Agente. Usa lettere minuscole, numeri e trattini.",
"marketPublish.modal.identifier.label": "Identificatore Agente",
"marketPublish.modal.identifier.lengthError": "L'identificatore deve contenere tra 3 e 50 caratteri",
"marketPublish.modal.identifier.patternError": "L'identificatore può contenere solo lettere minuscole, numeri e trattini",
"marketPublish.modal.identifier.placeholder": "Inserisci un identificatore univoco per l'agente, es. sviluppo-web",
"marketPublish.modal.identifier.required": "Inserisci l'identificatore dell'agente",
"marketPublish.modal.loading.fetchingRemote": "Caricamento dati remoti...",
"marketPublish.modal.loading.submit": "Invio dell'agente...",
"marketPublish.modal.loading.submitGroup": "Invio del Gruppo in corso...",
"marketPublish.modal.loading.upload": "Pubblicazione nuova versione...",
"marketPublish.modal.loading.uploadGroup": "Pubblicazione della nuova versione del gruppo in corso...",
"marketPublish.modal.messages.createVersionFailed": "Creazione versione non riuscita: {{message}}",
"marketPublish.modal.messages.fetchRemoteFailed": "Impossibile recuperare i dati remoti dell'agente",
"marketPublish.modal.messages.missingIdentifier": "Questo agente non ha ancora un identificatore Community.",
"marketPublish.modal.messages.noGroup": "Nessun gruppo selezionato",
"marketPublish.modal.messages.notAuthenticated": "Accedi prima al tuo account Community.",
"marketPublish.modal.messages.publishFailed": "Pubblicazione fallita: {{message}}",
"marketPublish.modal.submitButton": "Pubblica",
"marketPublish.modal.title.submit": "Condividi con la Community degli Agenti",
"marketPublish.modal.title.upload": "Pubblica nuova versione",
"marketPublish.resultModal.message": "Il tuo agente è stato inviato per la revisione. Una volta approvato, sarà pubblicato automaticamente.",
"marketPublish.resultModal.messageGroup": "Il tuo gruppo è stato inviato per la revisione. Una volta approvato, sarà pubblicato automaticamente.",
"marketPublish.resultModal.title": "Invio riuscito",
"marketPublish.resultModal.view": "Visualizza nella Community",
"marketPublish.status.underReview": "In revisione",
"marketPublish.submit.button": "Condividi con la Community",
"marketPublish.submit.tooltip": "Condividi questo agente con la Community",
"marketPublish.submitGroup.tooltip": "Condividi questo gruppo con la community",
"marketPublish.upload.button": "Pubblica nuova versione",
"marketPublish.upload.tooltip": "Pubblica una nuova versione nella Community degli Agenti",
"marketPublish.uploadGroup.tooltip": "Pubblica una nuova versione nella Community del Gruppo",
"marketPublish.validation.communitySetupRequired.action": "Configura ora",
"marketPublish.validation.communitySetupRequired.desc": "Questo spazio di lavoro non ha ancora configurato il suo profilo Community. Configuralo prima di pubblicare nella Community.",
"marketPublish.validation.communitySetupRequired.memberHint": "Questo spazio di lavoro non ha ancora configurato il suo profilo Community. Chiedi al proprietario dello spazio di lavoro di configurarlo prima di pubblicare nella Community.",
"marketPublish.validation.communitySetupRequired.title": "Configura prima il profilo Community",
"marketPublish.validation.confirmPublish": "Pubblicare nel Market?",
"marketPublish.validation.confirmPublishDesc": "Una volta pubblicato, questo contenuto sarà visibile pubblicamente nel market e disponibile per chiunque.",
"marketPublish.validation.emptyName": "Impossibile pubblicare: il nome è obbligatorio",
"marketPublish.validation.emptySystemRole": "Impossibile pubblicare: il ruolo di sistema è obbligatorio",
"marketPublish.validation.underReview": "La tua nuova versione è attualmente in revisione. Attendi l'approvazione prima di pubblicare una nuova versione.",
"memory.effort.desc": "Controlla quanto aggressivamente l'IA recupera e aggiorna la memoria.",
"memory.effort.high": "Alta — Recupero e aggiornamenti proattivi",
"memory.effort.level.high": "Alta",
@@ -515,14 +462,6 @@
"myAgents.actions.deprecateLoading": "Deprecazione dell'agente in corso...",
"myAgents.actions.deprecateSuccess": "Agente deprecato",
"myAgents.actions.edit": "Modifica Agente",
"myAgents.actions.publish": "Pubblica Agente",
"myAgents.actions.publishError": "Pubblicazione dell'agente non riuscita",
"myAgents.actions.publishLoading": "Pubblicazione dell'agente in corso...",
"myAgents.actions.publishSuccess": "Agente pubblicato",
"myAgents.actions.unpublish": "Rimuovi Agente",
"myAgents.actions.unpublishError": "Rimozione dell'agente non riuscita",
"myAgents.actions.unpublishLoading": "Rimozione dell'agente in corso...",
"myAgents.actions.unpublishSuccess": "Agente rimosso",
"myAgents.actions.viewDetail": "Visualizza Dettagli",
"myAgents.detail.category": "Categoria",
"myAgents.detail.description": "Descrizione",
@@ -587,7 +526,6 @@
"plugin.settings.title": "Configurazione Competenza {{id}}",
"plugin.settings.tooltip": "Configurazione Competenza",
"plugin.store": "Skill Store",
"publishToCommunity": "Pubblica nella Community",
"serviceModel.contextLimit.placeholder": "Limite di contesto",
"serviceModel.memoryModels.title": "Modelli di memoria",
"serviceModel.modelAssignments.title": "Assegnazioni modello",
@@ -955,13 +893,6 @@
"storageOverage.usage.estimatedCharge": "Addebito stimato per il ciclo",
"storageOverage.usage.incurredCharge": "Addebito sostenuto in questo ciclo",
"storageOverage.usage.overage": "Eccedenza",
"submitAgentModal.button": "Invia Agente",
"submitAgentModal.identifier": "Identificatore Agente",
"submitAgentModal.metaMiss": "Completa le informazioni dell'agente prima di inviare. Devono includere nome, descrizione e tag",
"submitAgentModal.placeholder": "Inserisci un identificatore univoco per l'agente, es. sviluppo-web",
"submitAgentModal.success": "Agente inviato con successo",
"submitAgentModal.tooltips": "Condividi con la Community degli Agenti",
"submitGroupModal.tooltips": "Condividi nella Community del Gruppo",
"sync.device.deviceName.hint": "Aggiungi un nome per facilitarne l'identificazione",
"sync.device.deviceName.placeholder": "Inserisci nome dispositivo",
"sync.device.deviceName.title": "Nome Dispositivo",
@@ -1086,6 +1017,7 @@
"tools.activation.auto": "Automatico",
"tools.activation.auto.desc": "Intelligente",
"tools.activation.fixed.hint": "Sempre attivo — gestito dall'app e non può essere disattivato",
"tools.activation.pin": "Pin",
"tools.activation.pinned": "Fissato",
"tools.activation.pinned.desc": "Sempre Attivo",
"tools.add": "Aggiungi Competenza",
@@ -2047,6 +1979,14 @@
"workspace.wizard.step3.title": "Benvenuto in {{name}}!",
"workspace.wizard.title": "Crea Workspace",
"workspaceSetting.breadcrumb.settings": "Impostazioni",
"workspaceSetting.devices.desc": "Macchine condivise registrate in questo workspace. I membri possono eseguire agenti su di esse.",
"workspaceSetting.devices.empty": "Nessun dispositivo del workspace al momento.",
"workspaceSetting.devices.enrollDesc": "Esegui questo comando sulla macchina che desideri condividere (solo il proprietario del workspace):",
"workspaceSetting.devices.enrollTitle": "Aggiungi un dispositivo",
"workspaceSetting.devices.heroDesc": "Registra una macchina condivisa — un server di build o un Mac del team — e ogni membro potrà eseguire agenti su di essa: leggere/scrivere file, eseguire comandi e utilizzare strumenti di sistema.",
"workspaceSetting.devices.heroTitle": "Connetti il tuo primo dispositivo del workspace",
"workspaceSetting.devices.offline": "Offline",
"workspaceSetting.devices.online": "Online",
"workspaceSetting.group.admin": "Amministratore",
"workspaceSetting.group.agent": "Agente",
"workspaceSetting.group.general": "Generale",
+10 -5
View File
@@ -147,10 +147,6 @@
"limitation.chat.topupSuccess.title": "Ricarica riuscita",
"limitation.expired.desc": "I tuoi crediti di calcolo del piano {{plan}} sono scaduti il {{expiredAt}}. Aggiorna il tuo piano per ottenere nuovi crediti.",
"limitation.expired.title": "Crediti di calcolo scaduti",
"limitation.fableCampaign.desc": "Claude Fable 5 è un modello ad alto costo. I crediti di prova della campagna sono stati esauriti. Aggiorna il tuo piano per continuare a utilizzare Fable.",
"limitation.fableCampaign.title": "Crediti di Prova Fable Esauriti",
"limitation.fableCampaign.upgrade": "Aggiorna Piano",
"limitation.fableCampaign.upgradeToPlan": "Passa a {{plan}}",
"limitation.hobby.action": "Configurato, continua a chattare",
"limitation.hobby.configAPI": "Configura API",
"limitation.hobby.desc": "Hai esaurito i crediti di calcolo gratuiti. Configura un'API modello personalizzata per continuare.",
@@ -342,7 +338,14 @@
"plans.workspace.noSharedCredits": "Nessun credito condiviso",
"plans.workspace.sharedCredits": "~{{count}} Crediti / mese",
"plans.workspace.solo": "Solo (1 membro)",
"promoBanner.fableYearly": "Gli abbonati annuali ottengono uno sconto del {{percent}}% sull'utilizzo per un periodo limitato",
"plansModal.creditLimit.desc": "Aggiorna il tuo piano per sbloccare più crediti mensili e continuare a lavorare senza interruzioni.",
"plansModal.creditLimit.title": "Hai esaurito i crediti",
"plansModal.default.desc": "Sblocca più capacità e funzionalità avanzate.",
"plansModal.default.title": "Aggiorna il tuo piano",
"plansModal.fileStorageLimit.desc": "Il tuo spazio di archiviazione è pieno. Aggiorna per continuare a caricare e gestire i file.",
"plansModal.fileStorageLimit.title": "Limite di archiviazione raggiunto",
"plansModal.modelAccess.desc": "Questo modello è disponibile nei piani a pagamento. Aggiorna per utilizzare l'intera gamma di modelli.",
"plansModal.modelAccess.title": "Sblocca tutti i modelli",
"qa.desc": "Se non trovi risposta alla tua domanda, consulta la <1>Documentazione del Prodotto</1> per ulteriori FAQ o contattaci.",
"qa.detail": "Visualizza Dettagli",
"qa.list.credit.a": "I crediti di calcolo sono una metrica utilizzata da {{cloud}} per misurare l'utilizzo dei modelli AI. Modelli diversi consumano quantità diverse di crediti.",
@@ -398,6 +401,8 @@
"referral.errors.invalidFormat": "Formato del codice invito non valido, inserisci 2-8 lettere, numeri o underscore",
"referral.errors.selfReferral": "Non puoi utilizzare il tuo stesso codice invito",
"referral.errors.updateFailed": "Aggiornamento fallito, riprova più tardi",
"referral.hero.description": "Condividi il tuo link di riferimento qui sotto. Dopo che il tuo amico effettua il suo primo pagamento, guadagnerete entrambi {{reward}}M crediti.",
"referral.hero.title": "Invita amici, guadagnate entrambi <0>{{reward}}M crediti</0>",
"referral.inviteCode.description": "Condividi il tuo codice invito esclusivo per invitare amici a registrarsi",
"referral.inviteCode.title": "Il Mio Codice Invito",
"referral.inviteLink.description": "Copia il link e condividilo con gli amici. Entrambi guadagnate crediti dopo che il tuo amico effettua un pagamento",
+1
View File
@@ -25,6 +25,7 @@
"actions.unmarkCompleted": "Segna come attivo",
"defaultTitle": "Argomento Predefinito",
"displayItems": "Visualizza Elementi",
"draft": "[Bozza]",
"duplicateLoading": "Copia dell'argomento in corso...",
"duplicateSuccess": "Argomento copiato con successo",
"failedStatusTip": "Questa esecuzione ha riscontrato un errore — aprila per dare un'occhiata.",
+28 -2
View File
@@ -41,6 +41,8 @@
"artifact.thinking": "思考中",
"artifact.thought": "思考プロセス",
"artifact.unknownTitle": "無題の作品",
"audioPlayer.pause": "オーディオを一時停止",
"audioPlayer.play": "オーディオを再生",
"availableAgents": "利用可能なアシスタント",
"backToBottom": "最新に戻る",
"beforeUnload.confirmLeave": "生成中のリクエストがあります。離れてもよろしいですか?",
@@ -120,6 +122,18 @@
"createModal.groupPlaceholder": "このグループが何をするか説明してください…",
"createModal.groupTitle": "このグループは何をすべきですか?",
"createModal.placeholder": "エージェントが何をするべきか説明してください…",
"createModal.skillSuggestion.actions.createAnyway": "エージェントを作成する",
"createModal.skillSuggestion.actions.createAnywayHint": "スキルが適合しない場合",
"createModal.skillSuggestion.actions.install": "スキルをインストール",
"createModal.skillSuggestion.actions.installing": "インストール中…",
"createModal.skillSuggestion.actions.openSkills": "スキルを表示",
"createModal.skillSuggestion.actions.tryInLobeAI": "{{name}}で使用",
"createModal.skillSuggestion.description": "これは再利用可能なワークフローのようです。一度スキルをインストールすれば、複数のエージェントで使用できます。",
"createModal.skillSuggestion.installError": "スキルがインストールされませんでした。再試行するか、エージェントを作成してください。",
"createModal.skillSuggestion.installed.description": "{{name}}でこのスキルを使用するか、他のエージェントで有効にできます。",
"createModal.skillSuggestion.installed.ready": "{{name}}で準備完了",
"createModal.skillSuggestion.installed.title": "スキルがインストールされました",
"createModal.skillSuggestion.title": "スキルが適している可能性があります",
"createModal.title": "エージェントは何をすべきですか?",
"createTask.assignee": "担当者",
"createTask.collapse": "入力を隠す",
@@ -166,6 +180,8 @@
"extendParams.title": "モデル拡張機能",
"extendParams.urlContext.desc": "有効にすると、ウェブリンクを自動解析し、ウェブページのコンテキストをコンテキストとして抽出します",
"extendParams.urlContext.title": "ウェブリンクコンテンツの抽出",
"floatingChatPanel.collapse": "チャットを折りたたむ",
"floatingChatPanel.expand": "チャットを展開",
"followUpPlaceholder": "フォローアップ。@で他のエージェントにタスクを割り当てできます。",
"followUpPlaceholderHeterogeneous": "フォローアップ。",
"gatewayMode.beta": "ベータ版",
@@ -219,9 +235,13 @@
"heteroAgent.cloudRepo.noRepos": "リポジトリが設定されていません。エージェント設定で追加してください。",
"heteroAgent.cloudRepo.notSet": "リポジトリが選択されていません",
"heteroAgent.cloudRepo.sectionTitle": "リポジトリ",
"heteroAgent.executionTarget.auto": "自動",
"heteroAgent.executionTarget.autoDesc": "複数のオンラインデバイスが利用可能な場合、自動的に選択して使用",
"heteroAgent.executionTarget.downloadDesktop": "デスクトップアプリを取得",
"heteroAgent.executionTarget.downloadDesktopDesc": "コンピュータにアクセスできるエージェントを実行",
"heteroAgent.executionTarget.downloadDesktopTitle": "デスクトップアプリを取得",
"heteroAgent.executionTarget.gateway": "ゲートウェイ",
"heteroAgent.executionTarget.gatewayDesc": "デバイスゲートウェイを通じて実行し、他のクライアントが進行状況を追跡可能",
"heteroAgent.executionTarget.infoTooltip": "ウェブからそのマシンを操作するためにリモートデバイスを選択してください。「このデバイス」はエージェントをローカルで実行し、デスクトップアプリ内でのみ利用可能です。",
"heteroAgent.executionTarget.loading": "デバイスを読み込み中…",
"heteroAgent.executionTarget.local": "このデバイス",
@@ -231,10 +251,12 @@
"heteroAgent.executionTarget.noneDesc": "有効なデバイスがありません",
"heteroAgent.executionTarget.offline": "オフライン",
"heteroAgent.executionTarget.online": "オンライン",
"heteroAgent.executionTarget.personalGroup": "個人",
"heteroAgent.executionTarget.sandbox": "クラウドサンドボックス",
"heteroAgent.executionTarget.sandboxDesc": "一時的なクラウドサンドボックスで実行",
"heteroAgent.executionTarget.title": "実行デバイス",
"heteroAgent.executionTarget.unknownDevice": "不明なデバイス",
"heteroAgent.executionTarget.workspaceGroup": "ワークスペース",
"heteroAgent.fullAccess.label": "フルアクセス",
"heteroAgent.fullAccess.tooltip": "Claude Code はローカルで動作し、作業ディレクトリへの読み書きが可能です。権限モードの切り替えはまだ利用できません。",
"heteroAgent.resumeReset.cwdChanged": "作業ディレクトリが変更されました。以前の Claude Code セッションは元のディレクトリからのみ再開できるため、新しい会話が開始されました。",
@@ -631,6 +653,8 @@
"taskDetail.artifacts": "アーティファクト",
"taskDetail.blockedBy": "{{id}} によってブロックされています",
"taskDetail.cancelSchedule": "スケジュールをキャンセル",
"taskDetail.closeDetail": "詳細を閉じる",
"taskDetail.collapseReply": "折りたたむ",
"taskDetail.comment.cancel": "キャンセル",
"taskDetail.comment.delete": "削除",
"taskDetail.comment.deleteConfirm.content": "このコメントは完全に削除されます。",
@@ -657,6 +681,7 @@
"taskDetail.notFound.backToTasks": "すべてのタスクに戻る",
"taskDetail.notFound.desc": "このタスクは削除された可能性があるか、表示する権限がありません。",
"taskDetail.notFound.title": "タスクが見つかりません",
"taskDetail.openDetail": "詳細を開く",
"taskDetail.pauseTask": "タスクを一時停止",
"taskDetail.priority.high": "高",
"taskDetail.priority.low": "低",
@@ -925,9 +950,9 @@
"workflow.collapse": "折りたたむ",
"workflow.expandFull": "すべて展開",
"workflow.failedSuffix": "(失敗)",
"workflow.summaryAcrossTools": "{{count}}ツールを横断",
"workflow.summaryCallsLead": "{{count}}件の通話: {{tools}}",
"workflow.summaryFailed": "{{count}} 件失敗",
"workflow.summaryMoreTools": "{{count}} 種類のツール",
"workflow.summaryTotalCalls": "合計 {{count}} 回の呼び出し",
"workflow.thoughtForDuration": "{{duration}}間の思考",
"workflow.toolDisplayName.activateDevice": "有効化されたデバイス",
"workflow.toolDisplayName.activateSkill": "スキルを有効化しました",
@@ -1043,6 +1068,7 @@
"workingPanel.resources.deleteTitle": "Delete document?",
"workingPanel.resources.deleteTitleMulti": "{{count}} 件のアイテムを削除しますか?",
"workingPanel.resources.empty": "まだドキュメントはありません。このエージェントに関連するドキュメントがここに表示されます。",
"workingPanel.resources.emptyDocuments": "まだドキュメントがありません。上部の+を使って作成してください。",
"workingPanel.resources.error": "Failed to load resources",
"workingPanel.resources.filter.documents": "ドキュメント",
"workingPanel.resources.filter.skills": "スキル",
+18 -2
View File
@@ -444,6 +444,23 @@
"tab.setting": "設定",
"tab.tasks": "タスク",
"tab.video": "ビデオ",
"taskTemplate.action.connect.button": "{{provider}}に接続",
"taskTemplate.action.connect.error": "接続に失敗しました。もう一度お試しください。",
"taskTemplate.action.connect.popupBlocked": "接続ポップアップがブロックされました。ブラウザでポップアップを許可して続行してください。",
"taskTemplate.action.connect.short": "接続",
"taskTemplate.action.connecting": "認証を待っています…",
"taskTemplate.action.create.error": "タスクの作成に失敗しました。もう一度お試しください。",
"taskTemplate.action.create.success": "スケジュールされたタスクが追加されました。Lobe AIで確認してください。",
"taskTemplate.action.createButton": "タスクを追加",
"taskTemplate.action.creating": "作成中...",
"taskTemplate.action.dismiss.error": "却下に失敗しました。もう一度お試しください。",
"taskTemplate.action.dismiss.tooltip": "興味がありません",
"taskTemplate.action.refresh.button": "更新",
"taskTemplate.card.templateTag": "テンプレート",
"taskTemplate.schedule.daily": "毎日{{time}}に",
"taskTemplate.schedule.editableAfterCreateTooltip": "タスクを作成した後にスケジュールを調整できます。",
"taskTemplate.schedule.weekly": "毎週{{weekday}}の{{time}}に",
"taskTemplate.section.title": "これらのスケジュールされたタスクを試してみてください",
"telemetry.allow": "許可する",
"telemetry.deny": "拒否する",
"telemetry.desc": "{{appName}} を改善し、より良い製品体験を提供するために、匿名の使用情報を収集させていただきます。「設定」-「情報」でいつでも無効にできます",
@@ -474,15 +491,14 @@
"userPanel.email": "メールサポート",
"userPanel.feedback": "お問い合わせ",
"userPanel.help": "ヘルプセンター",
"userPanel.inviteFriend": "友達を招待する",
"userPanel.moveGuide": "設定ボタンがこちらに移動しました",
"userPanel.plans": "サブスクリプションプラン",
"userPanel.profile": "アカウント管理",
"userPanel.setting": "アプリ設定",
"userPanel.upgradePlan": "プランをアップグレード",
"userPanel.usages": "使用統計",
"userPanel.workspaceCredits": "ワークスペースクレジット",
"userPanel.workspaceSetting": "ワークスペース設定",
"userPanel.workspaceUsages": "ワークスペース使用状況",
"version": "バージョン",
"zoom": "ズーム"
}
+2
View File
@@ -101,6 +101,7 @@
"LocalFile.action.open": "開く",
"LocalFile.action.showInFolder": "フォルダーで表示",
"MaxTokenSlider.unlimited": "無制限",
"ModelSelect.featureTag.audio": "このモデルは音声入力認識に対応しています。",
"ModelSelect.featureTag.custom": "カスタムモデル、デフォルトでは関数呼び出しとビジョン認識の両方をサポートしています。上記機能の有効性を確認してください。",
"ModelSelect.featureTag.file": "このモデルはファイルのアップロードと認識をサポートしています。",
"ModelSelect.featureTag.functionCall": "このモデルは関数呼び出し(Function Call)をサポートしています。",
@@ -114,6 +115,7 @@
"ModelSwitchPanel.byModel": "モデル別",
"ModelSwitchPanel.byProvider": "プロバイダー別",
"ModelSwitchPanel.detail.abilities": "機能",
"ModelSwitchPanel.detail.abilities.audio": "音声",
"ModelSwitchPanel.detail.abilities.files": "ファイル",
"ModelSwitchPanel.detail.abilities.functionCall": "ツール呼び出し",
"ModelSwitchPanel.detail.abilities.imageOutput": "画像出力",
-3
View File
@@ -38,9 +38,6 @@
"brief.title": "デイリーブリーフ",
"brief.viewAllTasks": "すべてのタスクを表示",
"brief.viewRun": "実行を表示",
"freeCreditBadge.cta": "無料トライアルを開始",
"freeCreditBadge.dismiss": "閉じる",
"freeCreditBadge.label": "{{model}} 用の限定無料クレジット",
"project.create": "新しいプロジェクトを作成",
"project.deleteConfirm": "このプロジェクトは削除され、復元できません。続行するには確認してください。",
"recommendations.heteroAgent.cta": "エージェントを追加",
+4 -4
View File
@@ -5,10 +5,12 @@
"authorize.footer.agreement": "続行することで、<terms>利用規約</terms>および<privacy>プライバシーポリシー</privacy>に同意したものとみなされます。",
"authorize.footer.privacy": "プライバシーポリシー",
"authorize.footer.terms": "利用規約",
"authorize.scenes.connector.confirm": "マーケットに進む",
"authorize.scenes.connector.description": "マーケットはこのサービス認証を開始するためだけに使用されます。あなたの{{appName}}アカウントは別々に保持されます。",
"authorize.scenes.connector.subtitle": "コミュニティサービスを接続して認証するためにマーケットにサインインしてください。",
"authorize.scenes.connector.title": "コミュニティサービスを接続",
"authorize.scenes.mcp.subtitle": "コミュニティでこのスキルをインストールして実行するために、コミュニティプロフィールを作成してください。",
"authorize.scenes.mcp.title": "コミュニティスキルをインストール",
"authorize.scenes.publish.subtitle": "コミュニティ内でリスティングを公開および管理するために、コミュニティプロフィールを作成してください。",
"authorize.scenes.publish.title": "コミュニティに公開",
"authorize.scenes.sandbox.subtitle": "コミュニティサンドボックスでこのツールを実行するために、コミュニティプロフィールを作成してください。",
"authorize.scenes.sandbox.title": "コミュニティサンドボックスを試す",
"authorize.subtitle": "コミュニティプロフィールを作成して、投稿や掲載情報の管理を行いましょう。",
@@ -50,8 +52,6 @@
"messages.handoffTimeout": "認証の待機時間がタイムアウトしました。ブラウザでの操作を完了させた後、もう一度お試しください。",
"messages.loading": "認証プロセスを開始しています...",
"messages.success.cloudMcpInstall": "許可が成功しました!これで Cloud MCP プラグインをインストールできます。",
"messages.success.submit": "認証に成功しました!アシスタントを公開できます。",
"messages.success.upload": "認証に成功しました!新しいバージョンを公開できます。",
"profileSetup.cancel": "キャンセル",
"profileSetup.confirmChangeUserId.cancel": "キャンセル",
"profileSetup.confirmChangeUserId.confirm": "ユーザーIDを変更",
+4 -2
View File
@@ -222,6 +222,7 @@
"providerModels.item.modelConfig.extendParams.options.effort.hint": "Claude Opus 4.6 用:労力レベルを制御します(低/中/高/最大)。",
"providerModels.item.modelConfig.extendParams.options.enableAdaptiveThinking.hint": "Claude Opus 4.6 用:アダプティブシンキングのオン/オフを切り替えます。",
"providerModels.item.modelConfig.extendParams.options.enableReasoning.hint": "Claude、DeepSeekなどの推論モデル向け;より深い思考を可能にします。",
"providerModels.item.modelConfig.extendParams.options.glm5_2ReasoningEffort.hint": "GLM-5.2の場合、高および最大レベルで推論の努力を制御します。",
"providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint": "GPT-5シリーズ向け;推論の強度を制御します。",
"providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint": "GPT-5.1シリーズ向け;推論の強度を制御します。",
"providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint": "GPT-5.2 Proシリーズ向け;推論の強度を制御します。",
@@ -256,6 +257,7 @@
"providerModels.item.modelConfig.files.title": "ファイルアップロードをサポート",
"providerModels.item.modelConfig.functionCall.extra": "この設定は、モデルがツールを使用する機能を有効にし、モデルにツールタイプのプラグインを追加できるようにします。ただし、実際にツールを使用できるかどうかはモデル自体に依存するため、使用可能性を自分でテストしてください",
"providerModels.item.modelConfig.functionCall.title": "ツール使用のサポート",
"providerModels.item.modelConfig.id.duplicate": "このIDのモデルはすでに存在します。別のモデルIDを使用してください。",
"providerModels.item.modelConfig.id.extra": "作成後は変更できません。AIを呼び出す際にモデルIDとして使用されます。",
"providerModels.item.modelConfig.id.placeholder": "モデルIDを入力してください。例:gpt-4o または claude-3.5-sonnet",
"providerModels.item.modelConfig.id.title": "モデル ID",
@@ -270,11 +272,11 @@
"providerModels.item.modelConfig.tokens.title": "最大コンテキストウィンドウ",
"providerModels.item.modelConfig.tokens.unlimited": "無制限",
"providerModels.item.modelConfig.type.extra": "異なるモデルタイプは、それぞれ異なる使用シーンと能力を持っています",
"providerModels.item.modelConfig.type.options.asr": "音声からテキストへ",
"providerModels.item.modelConfig.type.options.chat": "チャット",
"providerModels.item.modelConfig.type.options.embedding": "ベクトル化",
"providerModels.item.modelConfig.type.options.image": "画像生成",
"providerModels.item.modelConfig.type.options.realtime": "リアルタイムチャット",
"providerModels.item.modelConfig.type.options.stt": "音声からテキストへ",
"providerModels.item.modelConfig.type.options.text2music": "テキストから音楽へ",
"providerModels.item.modelConfig.type.options.tts": "音声合成",
"providerModels.item.modelConfig.type.options.video": "ビデオ生成",
@@ -323,10 +325,10 @@
"providerModels.list.total": "利用可能なモデルは合計 {{count}} 件です",
"providerModels.searchNotFound": "検索結果が見つかりませんでした",
"providerModels.tabs.all": "すべて",
"providerModels.tabs.asr": "ASR",
"providerModels.tabs.chat": "チャット",
"providerModels.tabs.embedding": "埋め込み",
"providerModels.tabs.image": "画像",
"providerModels.tabs.stt": "音声認識",
"providerModels.tabs.tts": "音声合成",
"providerModels.tabs.video": "ビデオ",
"sortModal.success": "ソートが更新されました",
+9 -69
View File
@@ -440,60 +440,7 @@
"llm.proxyUrl.title": "APIプロキシアドレス",
"llm.waitingForMore": "さらに多くのモデルが <1>計画されています</1>。お楽しみに",
"llm.waitingForMoreLinkAriaLabel": "モデルプロバイダー接続リクエストフォームを開く",
"marketPublish.forkConfirm.by": "{{author}} による",
"marketPublish.forkConfirm.confirm": "公開を確認",
"marketPublish.forkConfirm.confirmGroup": "公開を確認",
"marketPublish.forkConfirm.description": "コミュニティ内の既存エージェントを元にした派生バージョンを公開しようとしています。新しいエージェントはマーケットプレイスに別のエントリとして作成されます。",
"marketPublish.forkConfirm.descriptionGroup": "コミュニティ内の既存グループを元に派生バージョンを公開しようとしています。新しいグループはマーケットプレイスに別のエントリとして作成されます。",
"marketPublish.forkConfirm.title": "派生エージェントの公開",
"marketPublish.forkConfirm.titleGroup": "派生グループの公開",
"marketPublish.modal.changelog.extra": "このバージョンの主な変更点と改善点を記述してください",
"marketPublish.modal.changelog.label": "変更ログ",
"marketPublish.modal.changelog.maxLengthError": "変更ログは500文字以内で入力してください",
"marketPublish.modal.changelog.placeholder": "変更ログを入力してください",
"marketPublish.modal.changelog.required": "変更ログを入力してください",
"marketPublish.modal.comparison.local": "ローカルの現在のバージョン",
"marketPublish.modal.comparison.remote": "公開中のバージョン",
"marketPublish.modal.identifier.extra": "識別子はアシスタントの一意なIDとして使用されます。小文字、数字、ハイフンの使用を推奨します",
"marketPublish.modal.identifier.label": "アシスタント識別子",
"marketPublish.modal.identifier.lengthError": "識別子の長さは3〜50文字である必要があります",
"marketPublish.modal.identifier.patternError": "識別子には小文字、数字、ハイフンのみ使用できます",
"marketPublish.modal.identifier.placeholder": "例: web-development のように一意な識別子を入力してください",
"marketPublish.modal.identifier.required": "アシスタント識別子を入力してください",
"marketPublish.modal.loading.fetchingRemote": "リモートデータを読み込み中...",
"marketPublish.modal.loading.submit": "アシスタントを公開中...",
"marketPublish.modal.loading.submitGroup": "グループを送信中...",
"marketPublish.modal.loading.upload": "新しいバージョンを公開中...",
"marketPublish.modal.loading.uploadGroup": "新しいグループバージョンを公開中...",
"marketPublish.modal.messages.createVersionFailed": "バージョンの作成に失敗しました: {{message}}",
"marketPublish.modal.messages.fetchRemoteFailed": "リモートアシスタントデータの取得に失敗しました",
"marketPublish.modal.messages.missingIdentifier": "このアシスタントにはまだコミュニティ識別子がありません",
"marketPublish.modal.messages.noGroup": "グループが選択されていません",
"marketPublish.modal.messages.notAuthenticated": "まずはコミュニティアカウントにログインしてください",
"marketPublish.modal.messages.publishFailed": "公開に失敗しました: {{message}}",
"marketPublish.modal.submitButton": "公開",
"marketPublish.modal.title.submit": "アシスタントコミュニティに共有",
"marketPublish.modal.title.upload": "新しいバージョンを公開",
"marketPublish.resultModal.message": "作成したアシスタントは審査に提出されました。審査に通過すると自動的に公開されます。",
"marketPublish.resultModal.messageGroup": "グループは審査のために送信されました。承認されると自動的に公開されます。",
"marketPublish.resultModal.title": "送信成功",
"marketPublish.resultModal.view": "コミュニティで確認",
"marketPublish.status.underReview": "審査中",
"marketPublish.submit.button": "コミュニティに共有",
"marketPublish.submit.tooltip": "アシスタントをコミュニティに共有する",
"marketPublish.submitGroup.tooltip": "このグループをコミュニティに共有",
"marketPublish.upload.button": "新しいバージョンを公開",
"marketPublish.upload.tooltip": "新しいバージョンをアシスタントコミュニティに公開",
"marketPublish.uploadGroup.tooltip": "グループコミュニティに新しいバージョンを公開",
"marketPublish.validation.communitySetupRequired.action": "今すぐ設定",
"marketPublish.validation.communitySetupRequired.desc": "このワークスペースはまだコミュニティプロファイルを設定していません。コミュニティに公開する前に設定してください。",
"marketPublish.validation.communitySetupRequired.memberHint": "このワークスペースはまだコミュニティプロファイルを設定していません。ワークスペースの所有者に設定を依頼してください。",
"marketPublish.validation.communitySetupRequired.title": "まずコミュニティプロファイルを設定",
"marketPublish.validation.confirmPublish": "マーケットに公開しますか?",
"marketPublish.validation.confirmPublishDesc": "公開されると、このコンテンツはマーケットで公開され、誰でも発見して使用できるようになります。",
"marketPublish.validation.emptyName": "公開できません:名前は必須です",
"marketPublish.validation.emptySystemRole": "公開できません:システムロールは必須です",
"marketPublish.validation.underReview": "新しいバージョンは現在審査中です。承認されるまでお待ちください。",
"memory.effort.desc": "AIが記憶を取得および更新する積極性を制御します。",
"memory.effort.high": "高 — 積極的な取得と更新",
"memory.effort.level.high": "高",
@@ -515,14 +462,6 @@
"myAgents.actions.deprecateLoading": "アシスタントを廃止中...",
"myAgents.actions.deprecateSuccess": "アシスタントが廃止されました",
"myAgents.actions.edit": "アシスタントを編集",
"myAgents.actions.publish": "アシスタントを公開",
"myAgents.actions.publishError": "アシスタントの公開に失敗しました",
"myAgents.actions.publishLoading": "アシスタントを公開中...",
"myAgents.actions.publishSuccess": "アシスタントが公開されました",
"myAgents.actions.unpublish": "アシスタントを非公開",
"myAgents.actions.unpublishError": "アシスタントの非公開に失敗しました",
"myAgents.actions.unpublishLoading": "アシスタントを非公開にしています...",
"myAgents.actions.unpublishSuccess": "アシスタントが非公開になりました",
"myAgents.actions.viewDetail": "詳細を見る",
"myAgents.detail.category": "カテゴリ",
"myAgents.detail.description": "説明",
@@ -587,7 +526,6 @@
"plugin.settings.title": "{{id}} スキル設定",
"plugin.settings.tooltip": "スキル設定",
"plugin.store": "スキルストア",
"publishToCommunity": "コミュニティに公開",
"serviceModel.contextLimit.placeholder": "コンテキスト制限",
"serviceModel.memoryModels.title": "メモリーモデル",
"serviceModel.modelAssignments.title": "モデル割り当て",
@@ -955,13 +893,6 @@
"storageOverage.usage.estimatedCharge": "推定サイクル料金",
"storageOverage.usage.incurredCharge": "このサイクルで発生した料金",
"storageOverage.usage.overage": "超過",
"submitAgentModal.button": "アシスタントを提出",
"submitAgentModal.identifier": "アシスタント識別子(identifier",
"submitAgentModal.metaMiss": "アシスタント情報を入力してから提出してください。名前、説明、タグが必要です。",
"submitAgentModal.placeholder": "アシスタントの識別子を入力してください。一意である必要があります。例:web-development",
"submitAgentModal.success": "アシスタントの送信が成功しました",
"submitAgentModal.tooltips": "アシスタントコミュニティに共有",
"submitGroupModal.tooltips": "グループコミュニティに共有",
"sync.device.deviceName.hint": "識別のために名前を追加",
"sync.device.deviceName.placeholder": "デバイス名を入力",
"sync.device.deviceName.title": "デバイス名",
@@ -1086,6 +1017,7 @@
"tools.activation.auto": "自動",
"tools.activation.auto.desc": "スマート",
"tools.activation.fixed.hint": "常にオン—アプリによって管理され、オフにすることはできません",
"tools.activation.pin": "ピン",
"tools.activation.pinned": "固定",
"tools.activation.pinned.desc": "常にオン",
"tools.add": "スキルを統合",
@@ -2047,6 +1979,14 @@
"workspace.wizard.step3.title": "ようこそ、{{name}}",
"workspace.wizard.title": "ワークスペースを作成",
"workspaceSetting.breadcrumb.settings": "設定",
"workspaceSetting.devices.desc": "このワークスペースに登録された共有マシン。メンバーはこれらのマシンでエージェントを実行できます。",
"workspaceSetting.devices.empty": "まだワークスペースデバイスがありません。",
"workspaceSetting.devices.enrollDesc": "共有したいマシンで以下を実行してください(ワークスペース所有者のみ):",
"workspaceSetting.devices.enrollTitle": "デバイスを追加",
"workspaceSetting.devices.heroDesc": "共有マシン(ビルドサーバーやチームのMacなど)を登録すると、すべてのメンバーが以下を実行できます:ファイルの読み書き、コマンドの実行、システムツールの呼び出し。",
"workspaceSetting.devices.heroTitle": "最初のワークスペースデバイスを接続",
"workspaceSetting.devices.offline": "オフライン",
"workspaceSetting.devices.online": "オンライン",
"workspaceSetting.group.admin": "管理",
"workspaceSetting.group.agent": "エージェント",
"workspaceSetting.group.general": "一般",

Some files were not shown because too many files have changed in this diff Show More