Compare commits

..

10 Commits

Author SHA1 Message Date
Shoubhit Dash
069acaf821 merge dev into implement-background-agents 2026-03-05 21:37:14 +05:30
opencode-agent[bot]
9507b0eace chore: update nix node_modules hashes 2026-03-05 15:09:14 +00:00
Dax
4da199697b feat(tui): add onClick handler to InlineTool and Task components (#16187) 2026-03-05 15:02:30 +00:00
Adam
9cccaa693a chore(app): ghostty-web fork 2026-03-05 08:58:11 -06:00
Dax Raad
bb37e908ad ci: remove unused publishConfig that was breaking npm publishing 2026-03-05 09:45:30 -05:00
Dax Raad
d802e28381 update sdk package.json 2026-03-05 09:45:30 -05:00
Shoubhit Dash
3bd3904902 refine tui subagent status feedback 2026-03-04 11:21:44 +05:30
Shoubhit Dash
1a705cbca5 fix task_status stale terminal reads 2026-03-04 11:21:31 +05:30
Shoubhit Dash
e6222529e7 refine background task ux and auto-resume 2026-03-04 10:48:30 +05:30
Shoubhit Dash
2453f40d88 implement background agents 2026-02-28 00:25:08 +05:30
31 changed files with 728 additions and 1457 deletions

View File

@@ -47,7 +47,7 @@
"@thisbeyond/solid-dnd": "0.7.5",
"diff": "catalog:",
"fuzzysort": "catalog:",
"ghostty-web": "0.4.0",
"ghostty-web": "github:anomalyco/ghostty-web#main",
"luxon": "catalog:",
"marked": "catalog:",
"marked-shiki": "catalog:",
@@ -2958,7 +2958,7 @@
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
"ghostty-web": ["ghostty-web@0.4.0", "", {}, "sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg=="],
"ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#4af877d", {}, "anomalyco-ghostty-web-4af877d", "sha512-fbEK8mtr7ar4ySsF+JUGjhaZrane7dKphanN+SxHt5XXI6yLMAh/Hpf6sNCOyyVa2UlGCd7YpXG/T2v2RUAX+A=="],
"gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],

View File

@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-ZmxeRNy2chc9py4m1iW6B+c/NSccMnVZ0lfni/EMdHw=",
"aarch64-linux": "sha256-R+1mxsmAQicerN8ixVy0ff6V8bZ4GH18MHpihvWnaTg=",
"aarch64-darwin": "sha256-m+QT20ohlqo9e86qXu67eKthZm6VDRLwlqJ9CNlEV+0=",
"x86_64-darwin": "sha256-4GeNPyTT2Hq4rxHGSON23ul5Ud3yFGE0QUVsB03Gidc="
"x86_64-linux": "sha256-v83hWzYVg/g4zJiBpGsQ71wTdndPk3BQVZ2mjMApUIQ=",
"aarch64-linux": "sha256-inpMwkQqwBFP2wL8w/pTOP7q3fg1aOqvE0wgzVd3/B8=",
"aarch64-darwin": "sha256-r42LGrQWqDyIy62mBSU5Nf3M22dJ3NNo7mjN/1h8d8Y=",
"x86_64-darwin": "sha256-J6XrrdK5qBK3sQBQOO/B3ZluOnsAf5f65l4q/K1nDTI="
}
}

View File

@@ -57,7 +57,7 @@
"@thisbeyond/solid-dnd": "0.7.5",
"diff": "catalog:",
"fuzzysort": "catalog:",
"ghostty-web": "0.4.0",
"ghostty-web": "github:anomalyco/ghostty-web#main",
"luxon": "catalog:",
"marked": "catalog:",
"marked-shiki": "catalog:",

View File

@@ -8,7 +8,6 @@ import { SettingsGeneral } from "./settings-general"
import { SettingsKeybinds } from "./settings-keybinds"
import { SettingsProviders } from "./settings-providers"
import { SettingsModels } from "./settings-models"
import { SettingsMcp } from "./settings-mcp"
export const DialogSettings: Component = () => {
const language = useLanguage()
@@ -46,10 +45,6 @@ export const DialogSettings: Component = () => {
<Icon name="models" />
{language.t("settings.models.title")}
</Tabs.Trigger>
<Tabs.Trigger value="mcp">
<Icon name="mcp" />
{language.t("settings.mcp.title")}
</Tabs.Trigger>
</div>
</div>
</div>
@@ -72,9 +67,6 @@ export const DialogSettings: Component = () => {
<Tabs.Content value="models" class="no-scrollbar">
<SettingsModels />
</Tabs.Content>
<Tabs.Content value="mcp" class="no-scrollbar">
<SettingsMcp />
</Tabs.Content>
</Tabs>
</Dialog>
)

View File

@@ -1,665 +1,15 @@
import type { Config, McpLocalConfig, McpRemoteConfig, McpStatus } from "@opencode-ai/sdk/v2/client"
import { Button } from "@opencode-ai/ui/button"
import { Icon, type IconProps } from "@opencode-ai/ui/icon"
import { Tag } from "@opencode-ai/ui/tag"
import { Switch } from "@opencode-ai/ui/switch"
import { TextField } from "@opencode-ai/ui/text-field"
import { showToast } from "@opencode-ai/ui/toast"
import { For, Show, createMemo, onMount, type Component } from "solid-js"
import { createStore } from "solid-js/store"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
import { Component } from "solid-js"
import { useLanguage } from "@/context/language"
type Mode = "remote" | "local"
type McpMap = NonNullable<Config["mcp"]>
type McpEntry = McpMap[string]
type McpConfig = McpLocalConfig | McpRemoteConfig
type McpState = McpStatus["status"]
const FEATURED = [
{
name: "playwright",
title: "Playwright",
description: "Browser automation tools for testing, scraping, and repros.",
icon: "window-cursor",
panel: "linear-gradient(135deg, rgba(59, 130, 246, 0.14), rgba(15, 23, 42, 0.04))",
glow: "rgba(96, 165, 250, 0.18)",
badge: "rgba(37, 99, 235, 0.14)",
color: "rgb(37, 99, 235)",
config: {
type: "local",
command: ["npx", "-y", "@playwright/mcp@latest"],
},
},
{
name: "context7",
title: "Context7",
description: "Fresh framework docs and API references in one remote server.",
icon: "code-lines",
panel: "linear-gradient(135deg, rgba(14, 165, 233, 0.16), rgba(15, 23, 42, 0.04))",
glow: "rgba(56, 189, 248, 0.2)",
badge: "rgba(8, 145, 178, 0.14)",
color: "rgb(8, 145, 178)",
config: {
type: "remote",
url: "https://mcp.context7.com/mcp",
},
},
{
name: "linear",
title: "Linear",
description: "Issue and project workflows from your Linear workspace.",
icon: "branch",
panel: "linear-gradient(135deg, rgba(124, 58, 237, 0.14), rgba(30, 41, 59, 0.04))",
glow: "rgba(139, 92, 246, 0.18)",
badge: "rgba(109, 40, 217, 0.14)",
color: "rgb(109, 40, 217)",
config: {
type: "remote",
url: "https://mcp.linear.app/sse",
oauth: {},
},
},
{
name: "sentry",
title: "Sentry",
description: "Error monitoring, traces, and issue triage in one place.",
icon: "warning",
panel: "linear-gradient(135deg, rgba(14, 116, 144, 0.16), rgba(15, 23, 42, 0.04))",
glow: "rgba(6, 182, 212, 0.16)",
badge: "rgba(8, 145, 178, 0.14)",
color: "rgb(8, 145, 178)",
config: {
type: "remote",
url: "https://mcp.sentry.dev/mcp",
oauth: {},
},
},
] satisfies Array<{
name: string
title: string
description: string
icon: IconProps["name"]
panel: string
glow: string
badge: string
color: string
config: McpConfig
}>
const STATUS = {
connected: "mcp.status.connected",
failed: "mcp.status.failed",
needs_auth: "mcp.status.needs_auth",
disabled: "mcp.status.disabled",
needs_client_registration: "settings.mcp.status.needs_client_registration",
} satisfies Record<McpState, string>
const empty = (mode: Mode = "remote") => ({
mode,
name: "",
url: "",
command: "",
headers: "",
environment: "",
timeout: "",
})
const isConfig = (value: McpEntry | undefined): value is McpConfig =>
typeof value === "object" && value !== null && "type" in value
const split = (value: string) =>
value
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
const parseMap = (value: string, allowColon: boolean) => {
const out: Record<string, string> = {}
for (const line of split(value)) {
const eq = line.indexOf("=")
const cut = !allowColon ? eq : ([line.indexOf(":"), eq].filter((part) => part > 0).sort((a, b) => a - b)[0] ?? -1)
if (cut < 1) return { error: line }
const key = line.slice(0, cut).trim()
const item = line.slice(cut + 1).trim()
if (!key || !item) return { error: line }
out[key] = item
}
return { value: Object.keys(out).length > 0 ? out : undefined }
}
const parseCmd = (value: string) =>
(value.match(/"[^"]*"|'[^']*'|[^\s]+/g) ?? []).map((part) => {
if (part.startsWith('"') && part.endsWith('"')) return part.slice(1, -1)
if (part.startsWith("'") && part.endsWith("'")) return part.slice(1, -1)
return part
})
export const SettingsMcp: Component = () => {
const lang = useLanguage()
const sdk = useGlobalSDK()
const sync = useGlobalSync()
const [state, setState] = createStore({
form: empty(),
submitting: "",
statusLoading: false,
status: {} as Record<string, McpStatus>,
})
const busy = createMemo(() => state.submitting.length > 0)
const items = createMemo(() => {
return Object.entries(sync.data.config.mcp ?? {})
.filter((item): item is [string, McpConfig] => isConfig(item[1]))
.map(([name, config]) => ({ name, config }))
.sort((a, b) => a.name.localeCompare(b.name))
})
const names = createMemo(() => new Set(items().map((item) => item.name)))
const spin = () => `${lang.t("common.loading")}${lang.t("common.loading.ellipsis")}`
const kind = (value: Mode) => {
if (value === "remote") return lang.t("settings.mcp.type.remote")
return lang.t("settings.mcp.type.local")
}
const fail = (description: string) => {
showToast({
variant: "error",
title: lang.t("common.requestFailed"),
description,
})
}
const load = () => {
setState("statusLoading", true)
return sdk.client.mcp
.status()
.then((x) => {
setState("status", x.data ?? {})
})
.catch(() => undefined)
.finally(() => {
setState("statusLoading", false)
})
}
const save = (next: McpMap, job: string, onSuccess: () => void, title: string, description: string) => {
const prev = sync.data.config.mcp
setState("submitting", job)
sync.set("config", "mcp", next)
sync
.updateConfig({ mcp: next })
.then(() => {
onSuccess()
void load()
showToast({
variant: "success",
icon: "circle-check",
title,
description,
})
})
.catch((err: unknown) => {
sync.set("config", "mcp", prev)
fail(err instanceof Error ? err.message : String(err))
})
.finally(() => {
setState("submitting", "")
})
}
const add = (name: string, config: McpConfig, job: string, reset: boolean) => {
const key = name.trim()
if (!key) {
fail(lang.t("settings.mcp.validation.name"))
return
}
if (names().has(key)) {
fail(lang.t("settings.mcp.validation.duplicate", { name: key }))
return
}
const next = {
...(sync.data.config.mcp ?? {}),
[key]: config,
}
save(
next,
job,
() => {
if (!reset) return
setState("form", empty(state.form.mode))
},
lang.t("settings.mcp.toast.added.title"),
lang.t("settings.mcp.toast.added.description", { name: key }),
)
}
const addForm = () => {
if (busy()) return
const timeout = state.form.timeout.trim()
const wait = timeout ? Number(timeout) : undefined
if (wait !== undefined && (!Number.isInteger(wait) || wait <= 0)) {
fail(lang.t("settings.mcp.validation.timeout"))
return
}
if (state.form.mode === "remote") {
const url = state.form.url.trim()
if (!url) {
fail(lang.t("settings.mcp.validation.url"))
return
}
const headers = parseMap(state.form.headers, true)
if (headers.error) {
fail(lang.t("settings.mcp.validation.headers", { line: headers.error }))
return
}
add(
state.form.name,
{
type: "remote",
url,
...(headers.value ? { headers: headers.value } : {}),
...(wait ? { timeout: wait } : {}),
},
"form",
true,
)
return
}
const command = parseCmd(state.form.command.trim())
if (command.length === 0) {
fail(lang.t("settings.mcp.validation.command"))
return
}
const environment = parseMap(state.form.environment, false)
if (environment.error) {
fail(lang.t("settings.mcp.validation.environment", { line: environment.error }))
return
}
add(
state.form.name,
{
type: "local",
command,
...(environment.value ? { environment: environment.value } : {}),
...(wait ? { timeout: wait } : {}),
},
"form",
true,
)
}
const addFeatured = (item: (typeof FEATURED)[number]) => {
if (busy()) return
add(item.name, item.config, `featured:${item.name}`, false)
}
const toggle = (name: string, enabled: boolean) => {
if (busy()) return
const current = (sync.data.config.mcp ?? {})[name]
if (!isConfig(current)) return
const next = {
...(sync.data.config.mcp ?? {}),
[name]: {
...current,
enabled,
},
}
save(
next,
`toggle:${name}`,
() => undefined,
lang.t("settings.mcp.toast.updated.title"),
lang.t("settings.mcp.toast.updated.description", {
name,
state: enabled ? lang.t("settings.mcp.state.enabled") : lang.t("settings.mcp.state.disabled"),
}),
)
}
const remove = (name: string) => {
if (busy()) return
const next = { ...(sync.data.config.mcp ?? {}) }
delete next[name]
save(
next,
`remove:${name}`,
() => undefined,
lang.t("settings.mcp.toast.removed.title"),
lang.t("settings.mcp.toast.removed.description", { name }),
)
}
const label = (name: string) => {
const value = state.status[name]?.status
if (!value) return
return lang.t(STATUS[value])
}
const issue = (name: string) => {
const value = state.status[name]
if (!value || !("error" in value)) return
return value.error
}
const line = (config: McpConfig) => {
if (config.type === "remote") return config.url
return config.command.join(" ")
}
onMount(() => {
void load()
})
// TODO: Replace this placeholder with full MCP settings controls.
const language = useLanguage()
return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
<div class="flex flex-col gap-1 pt-6 pb-8 max-w-[720px]">
<h2 class="text-16-medium text-text-strong">{lang.t("settings.mcp.title")}</h2>
<p class="text-14-regular text-text-weak">{lang.t("settings.mcp.description")}</p>
</div>
</div>
<div class="flex flex-col gap-8 max-w-[720px]">
<div class="flex flex-col gap-3">
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong">{lang.t("settings.mcp.section.featured")}</h3>
<p class="text-12-regular text-text-weak">{lang.t("settings.mcp.section.featured.description")}</p>
</div>
<div class="grid gap-3 sm:grid-cols-2">
<For each={FEATURED}>
{(item) => {
const added = () => names().has(item.name)
const pending = () => state.submitting === `featured:${item.name}`
return (
<button
type="button"
class="group relative overflow-hidden rounded-2xl border border-border-weak-base p-4 text-left transition-transform duration-200 disabled:cursor-default"
classList={{
"hover:-translate-y-0.5": !added() && !busy(),
"opacity-60": added(),
}}
disabled={added() || busy()}
onClick={() => addFeatured(item)}
>
<div class="absolute inset-0" aria-hidden="true">
<div class="absolute inset-0" style={{ background: item.panel }} />
<div
class="absolute -right-6 -top-6 size-24 rounded-full blur-2xl"
style={{ background: item.glow }}
/>
</div>
<div class="relative flex flex-col gap-4">
<div class="flex items-start justify-between gap-3">
<div
class="flex size-11 shrink-0 items-center justify-center rounded-2xl border border-border-weak-base"
style={{ background: item.badge, color: item.color }}
>
<Icon name={item.icon} class="size-5" />
</div>
<span class="rounded-full bg-surface-base px-2.5 py-1 text-11-medium text-text-weak">
{kind(item.config.type)}
</span>
</div>
<div class="flex flex-col gap-1">
<div class="flex flex-wrap items-center gap-2">
<span class="text-14-medium text-text-strong">{item.title}</span>
<Show when={added()}>
<span class="rounded-full bg-surface-base px-2 py-0.5 text-11-medium text-text-weak">
{lang.t("settings.mcp.featured.added")}
</span>
</Show>
<Show when={pending()}>
<span class="text-11-regular text-text-weak">{spin()}</span>
</Show>
</div>
<span class="text-12-regular leading-5 text-text-weak">{item.description}</span>
</div>
</div>
</button>
)
}}
</For>
</div>
</div>
<div class="flex flex-col gap-3">
<div class="flex flex-col gap-1">
<div class="flex flex-wrap items-center gap-2">
<h3 class="text-14-medium text-text-strong">{lang.t("settings.mcp.section.configured")}</h3>
<Show when={state.statusLoading}>
<span class="text-11-regular text-text-weak">{spin()}</span>
</Show>
</div>
<p class="text-12-regular text-text-weak">{lang.t("settings.mcp.section.configured.description")}</p>
</div>
<div class="bg-surface-raised-base px-4 rounded-lg">
<Show
when={items().length > 0}
fallback={<div class="py-4 text-14-regular text-text-weak">{lang.t("dialog.mcp.empty")}</div>}
>
<For each={items()}>
{(item) => {
const current = () => state.status[item.name]?.status
const text = () => label(item.name)
const problem = () => issue(item.name)
const enabled = () => item.config.enabled !== false
const pendingToggle = () => state.submitting === `toggle:${item.name}`
const pending = () => state.submitting === `remove:${item.name}`
return (
<div class="flex flex-wrap items-start justify-between gap-4 py-4 border-b border-border-weak-base last:border-none">
<div class="min-w-0 flex-1 flex flex-col gap-2">
<div class="flex flex-wrap items-center gap-2">
<span class="text-14-medium text-text-strong">{item.name}</span>
<Tag>{kind(item.config.type)}</Tag>
<Show when={text()}>
<span
class="rounded-full bg-surface-base px-2 py-0.5 text-11-medium"
classList={{
"text-icon-success-base": current() === "connected",
"text-icon-warning-base": current() === "needs_auth",
"text-icon-critical-base":
current() === "failed" || current() === "needs_client_registration",
"text-text-weak": current() === "disabled",
}}
>
{text()}
</span>
</Show>
</div>
<span class="text-12-regular text-text-weak break-all">{line(item.config)}</span>
<Show when={problem()}>
<span class="text-12-regular text-icon-critical-base break-all">{problem()}</span>
</Show>
</div>
<div class="flex items-center gap-3" onClick={(e) => e.stopPropagation()}>
<div class="flex items-center gap-2 pl-1">
<Show when={pendingToggle()}>
<span class="text-11-regular text-text-weak">{spin()}</span>
</Show>
<Switch
checked={enabled()}
disabled={busy()}
onChange={(next) => toggle(item.name, next)}
hideLabel
>
{item.name}
</Switch>
<span class="text-12-regular text-text-weak">
{enabled() ? lang.t("settings.mcp.state.enabled") : lang.t("settings.mcp.state.disabled")}
</span>
</div>
<Button size="large" variant="ghost" disabled={busy()} onClick={() => remove(item.name)}>
{pending() ? spin() : lang.t("settings.mcp.action.remove")}
</Button>
</div>
</div>
)
}}
</For>
</Show>
</div>
</div>
<div class="flex flex-col gap-3">
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong">{lang.t("settings.mcp.section.add")}</h3>
<p class="text-12-regular text-text-weak">{lang.t("settings.mcp.section.add.description")}</p>
</div>
<div class="rounded-2xl border border-border-weak-base bg-surface-raised-base p-4 sm:p-5">
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<span class="text-12-medium text-text-strong">{lang.t("settings.mcp.form.type.label")}</span>
<div class="inline-flex w-full gap-1 rounded-xl bg-surface-base p-1 sm:w-auto">
<For each={["remote", "local"] as const}>
{(mode) => (
<button
type="button"
class="h-9 flex-1 rounded-lg px-3 text-12-medium transition-colors sm:flex-none"
classList={{
"bg-surface-raised-base text-text-strong": state.form.mode === mode,
"text-text-weak": state.form.mode !== mode,
}}
onClick={() => {
if (busy()) return
setState("form", "mode", mode)
}}
>
{kind(mode)}
</button>
)}
</For>
</div>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<TextField
label={lang.t("settings.mcp.form.name.label")}
value={state.form.name}
onChange={(value) => setState("form", "name", value)}
placeholder={lang.t("settings.mcp.form.name.placeholder")}
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
/>
<TextField
label={lang.t("settings.mcp.form.timeout.label")}
value={state.form.timeout}
onChange={(value) => setState("form", "timeout", value)}
placeholder={lang.t("settings.mcp.form.timeout.placeholder")}
inputMode="numeric"
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
/>
</div>
<Show
when={state.form.mode === "remote"}
fallback={
<>
<TextField
label={lang.t("settings.mcp.form.command.label")}
value={state.form.command}
onChange={(value) => setState("form", "command", value)}
placeholder={lang.t("settings.mcp.form.command.placeholder")}
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
/>
<TextField
label={lang.t("settings.mcp.form.environment.label")}
description={lang.t("settings.mcp.form.environment.description")}
value={state.form.environment}
onChange={(value) => setState("form", "environment", value)}
placeholder="API_KEY={env:API_KEY}"
multiline
rows={4}
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
/>
</>
}
>
<TextField
label={lang.t("settings.mcp.form.url.label")}
value={state.form.url}
onChange={(value) => setState("form", "url", value)}
placeholder={lang.t("settings.mcp.form.url.placeholder")}
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
/>
<TextField
label={lang.t("settings.mcp.form.headers.label")}
description={lang.t("settings.mcp.form.headers.description")}
value={state.form.headers}
onChange={(value) => setState("form", "headers", value)}
placeholder="Authorization: Bearer {env:API_KEY}"
multiline
rows={4}
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
/>
</Show>
<Button
size="large"
variant="secondary"
icon="plus-small"
class="w-full justify-center sm:w-auto"
disabled={busy()}
onClick={addForm}
>
{state.submitting === "form" ? spin() : lang.t("settings.mcp.action.add")}
</Button>
</div>
</div>
</div>
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.mcp.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.mcp.description")}</p>
</div>
</div>
)

View File

@@ -665,47 +665,6 @@ export const dict = {
"settings.commands.description": "ستكون إعدادات الأمر قابلة للتكوين هنا.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "ستكون إعدادات MCP قابلة للتكوين هنا.",
"settings.mcp.section.featured": "مميّز",
"settings.mcp.section.featured.description": "أضف إعدادًا مسبقًا لخوادم MCP الشهيرة بنقرة واحدة.",
"settings.mcp.section.configured": "الخوادم المهيأة",
"settings.mcp.section.configured.description":
"اطلع على خوادم MCP المثبتة، وكيفية اتصالها، وقم بإزالة تلك التي لم تعد بحاجة إليها.",
"settings.mcp.section.add": "أضف خادمًا",
"settings.mcp.section.add.description": "أنشئ تكوين خادم MCP محلي أو بعيد خاص بك.",
"settings.mcp.type.local": "محلي",
"settings.mcp.type.remote": "بعيد",
"settings.mcp.featured.added": "تمت الإضافة",
"settings.mcp.action.add": "أضف خادمًا",
"settings.mcp.action.remove": "إزالة",
"settings.mcp.state.enabled": "مفعل",
"settings.mcp.state.disabled": "معطل",
"settings.mcp.form.type.label": "نوع الاتصال",
"settings.mcp.form.name.label": "اسم الخادم",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "عنوان URL البعيد",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "الأمر",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "الرؤوس (Headers)",
"settings.mcp.form.headers.description": "اختياري. أضف رأسًا واحدًا في كل سطر بصيغة KEY: value.",
"settings.mcp.form.environment.label": "البيئة",
"settings.mcp.form.environment.description": "اختياري. أضف متغيرًا واحدًا في كل سطر بصيغة KEY=value.",
"settings.mcp.form.timeout.label": "المهلة (مللي ثانية)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "تمت إضافة خادم MCP",
"settings.mcp.toast.added.description": "تم حفظ {{name}} في إعدادات MCP الخاصة بك.",
"settings.mcp.toast.updated.title": "تم تحديث خادم MCP",
"settings.mcp.toast.updated.description": "أصبح {{name}} الآن {{state}}.",
"settings.mcp.toast.removed.title": "تمت إزالة خادم MCP",
"settings.mcp.toast.removed.description": "تمت إزالة {{name}} من إعدادات MCP الخاصة بك.",
"settings.mcp.validation.name": "أدخل اسم الخادم قبل الحفظ.",
"settings.mcp.validation.duplicate": "{{name}} مهيأ بالفعل.",
"settings.mcp.validation.url": "أدخل عنوان URL لخادم MCP البعيد.",
"settings.mcp.validation.command": "أدخل الأمر المستخدم لبدء تشغيل خادم MCP المحلي.",
"settings.mcp.validation.timeout": "يجب أن تكون المهلة عددًا صحيحًا موجبًا.",
"settings.mcp.validation.headers": "تعذر تحليل سطر الرأس: {{line}}",
"settings.mcp.validation.environment": "تعذر تحليل سطر البيئة: {{line}}",
"settings.mcp.status.needs_client_registration": "يحتاج إلى تسجيل العميل",
"settings.permissions.title": "الأذونات",
"settings.permissions.description": "تحكم في الأدوات التي يمكن للخادم استخدامها بشكل افتراضي.",
"settings.permissions.section.tools": "الأدوات",

View File

@@ -673,48 +673,6 @@ export const dict = {
"settings.commands.description": "Configurações de comandos estarão disponíveis aqui.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "Configurações de MCP estarão disponíveis aqui.",
"settings.mcp.section.featured": "Destaques",
"settings.mcp.section.featured.description":
"Adicione uma predefinição refinada para servidores MCP populares com um clique.",
"settings.mcp.section.configured": "Servidores configurados",
"settings.mcp.section.configured.description":
"Veja quais servidores MCP estão instalados, como eles se conectam e remova os que você não precisa mais.",
"settings.mcp.section.add": "Adicionar servidor",
"settings.mcp.section.add.description": "Crie sua própria configuração de servidor MCP local ou remoto.",
"settings.mcp.type.local": "Local",
"settings.mcp.type.remote": "Remoto",
"settings.mcp.featured.added": "Adicionado",
"settings.mcp.action.add": "Adicionar servidor",
"settings.mcp.action.remove": "Remover",
"settings.mcp.state.enabled": "Habilitado",
"settings.mcp.state.disabled": "Desabilitado",
"settings.mcp.form.type.label": "Tipo de conexão",
"settings.mcp.form.name.label": "Nome do servidor",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "URL remota",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "Comando",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "Cabeçalhos",
"settings.mcp.form.headers.description": "Opcional. Adicione um cabeçalho por linha usando KEY: value.",
"settings.mcp.form.environment.label": "Ambiente",
"settings.mcp.form.environment.description": "Opcional. Adicione uma variável por linha usando KEY=value.",
"settings.mcp.form.timeout.label": "Tempo limite (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "Servidor MCP adicionado",
"settings.mcp.toast.added.description": "{{name}} foi salvo nas suas configurações do MCP.",
"settings.mcp.toast.updated.title": "Servidor MCP atualizado",
"settings.mcp.toast.updated.description": "{{name}} está agora {{state}}.",
"settings.mcp.toast.removed.title": "Servidor MCP removido",
"settings.mcp.toast.removed.description": "{{name}} foi removido das suas configurações do MCP.",
"settings.mcp.validation.name": "Insira um nome para o servidor antes de salvar.",
"settings.mcp.validation.duplicate": "{{name}} já está configurado.",
"settings.mcp.validation.url": "Insira uma URL de servidor MCP remoto.",
"settings.mcp.validation.command": "Insira o comando usado para iniciar o servidor MCP local.",
"settings.mcp.validation.timeout": "O tempo limite deve ser um número inteiro positivo.",
"settings.mcp.validation.headers": "Não foi possível analisar a linha do cabeçalho: {{line}}",
"settings.mcp.validation.environment": "Não foi possível analisar a linha de ambiente: {{line}}",
"settings.mcp.status.needs_client_registration": "Requer registro do cliente",
"settings.permissions.title": "Permissões",
"settings.permissions.description": "Controle quais ferramentas o servidor pode usar por padrão.",
"settings.permissions.section.tools": "Ferramentas",

View File

@@ -745,47 +745,6 @@ export const dict = {
"settings.commands.description": "Postavke komandi će se ovdje moći podešavati.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP postavke će se ovdje moći podešavati.",
"settings.mcp.section.featured": "Izdvojeno",
"settings.mcp.section.featured.description": "Dodajte gotovu postavku za popularne MCP servere jednim klikom.",
"settings.mcp.section.configured": "Konfigurisani serveri",
"settings.mcp.section.configured.description":
"Pogledajte koji su MCP serveri instalirani, kako se povezuju i uklonite one koji vam više ne trebaju.",
"settings.mcp.section.add": "Dodaj server",
"settings.mcp.section.add.description": "Kreirajte vlastitu konfiguraciju lokalnog ili udaljenog MCP servera.",
"settings.mcp.type.local": "Lokalno",
"settings.mcp.type.remote": "Udaljeno",
"settings.mcp.featured.added": "Dodano",
"settings.mcp.action.add": "Dodaj server",
"settings.mcp.action.remove": "Ukloni",
"settings.mcp.state.enabled": "Omogućeno",
"settings.mcp.state.disabled": "Onemogućeno",
"settings.mcp.form.type.label": "Tip konekcije",
"settings.mcp.form.name.label": "Naziv servera",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "Udaljeni URL",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "Komanda",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "Zaglavlja",
"settings.mcp.form.headers.description": "Opcionalno. Dodajte jedno zaglavlje po liniji koristeći KEY: value.",
"settings.mcp.form.environment.label": "Okruženje",
"settings.mcp.form.environment.description": "Opcionalno. Dodajte jednu varijablu po liniji koristeći KEY=value.",
"settings.mcp.form.timeout.label": "Istek vremena (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "MCP server dodan",
"settings.mcp.toast.added.description": "{{name}} je sačuvan u vaše MCP postavke.",
"settings.mcp.toast.updated.title": "MCP server ažuriran",
"settings.mcp.toast.updated.description": "{{name}} je sada {{state}}.",
"settings.mcp.toast.removed.title": "MCP server uklonjen",
"settings.mcp.toast.removed.description": "{{name}} je uklonjen iz vaših MCP postavki.",
"settings.mcp.validation.name": "Unesite naziv servera prije čuvanja.",
"settings.mcp.validation.duplicate": "{{name}} je već konfigurisan.",
"settings.mcp.validation.url": "Unesite URL udaljenog MCP servera.",
"settings.mcp.validation.command": "Unesite komandu koja se koristi za pokretanje lokalnog MCP servera.",
"settings.mcp.validation.timeout": "Istek vremena mora biti pozitivan cijeli broj.",
"settings.mcp.validation.headers": "Nije moguće parsirati liniju zaglavlja: {{line}}",
"settings.mcp.validation.environment": "Nije moguće parsirati liniju okruženja: {{line}}",
"settings.mcp.status.needs_client_registration": "Potrebna registracija klijenta",
"settings.permissions.title": "Dozvole",
"settings.permissions.description": "Kontroliši koje alate server smije koristiti po defaultu.",

View File

@@ -739,48 +739,6 @@ export const dict = {
"settings.commands.description": "Kommandoindstillinger vil kunne konfigureres her.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP-indstillinger vil kunne konfigureres her.",
"settings.mcp.section.featured": "Udvalgte",
"settings.mcp.section.featured.description":
"Tilføj en poleret forudindstilling for populære MCP-servere med ét klik.",
"settings.mcp.section.configured": "Konfigurerede servere",
"settings.mcp.section.configured.description":
"Se hvilke MCP-servere der er installeret, hvordan de forbinder, og fjern dem du ikke længere har brug for.",
"settings.mcp.section.add": "Tilføj en server",
"settings.mcp.section.add.description": "Opret din egen lokale eller fjern MCP-serverkonfiguration.",
"settings.mcp.type.local": "Lokal",
"settings.mcp.type.remote": "Fjern",
"settings.mcp.featured.added": "Tilføjet",
"settings.mcp.action.add": "Tilføj server",
"settings.mcp.action.remove": "Fjern",
"settings.mcp.state.enabled": "Aktiveret",
"settings.mcp.state.disabled": "Deaktiveret",
"settings.mcp.form.type.label": "Forbindelsestype",
"settings.mcp.form.name.label": "Servernavn",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "Fjern-URL",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "Kommando",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "Headere",
"settings.mcp.form.headers.description": "Valgfrit. Tilføj én header pr. linje ved brug af KEY: value.",
"settings.mcp.form.environment.label": "Miljø",
"settings.mcp.form.environment.description": "Valgfrit. Tilføj én variabel pr. linje ved brug af KEY=value.",
"settings.mcp.form.timeout.label": "Timeout (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "MCP-server tilføjet",
"settings.mcp.toast.added.description": "{{name}} er blevet gemt i dine MCP-indstillinger.",
"settings.mcp.toast.updated.title": "MCP-server opdateret",
"settings.mcp.toast.updated.description": "{{name}} er nu {{state}}.",
"settings.mcp.toast.removed.title": "MCP-server fjernet",
"settings.mcp.toast.removed.description": "{{name}} er blevet fjernet fra dine MCP-indstillinger.",
"settings.mcp.validation.name": "Indtast et servernavn før du gemmer.",
"settings.mcp.validation.duplicate": "{{name}} er allerede konfigureret.",
"settings.mcp.validation.url": "Indtast en URL til en fjern MCP-server.",
"settings.mcp.validation.command": "Indtast kommandoen, der bruges til at starte den lokale MCP-server.",
"settings.mcp.validation.timeout": "Timeout skal være et positivt heltal.",
"settings.mcp.validation.headers": "Kunne ikke fortolke header-linje: {{line}}",
"settings.mcp.validation.environment": "Kunne ikke fortolke miljø-linje: {{line}}",
"settings.mcp.status.needs_client_registration": "Kræver klientregistrering",
"settings.permissions.title": "Tilladelser",
"settings.permissions.description": "Styr hvilke værktøjer serveren kan bruge som standard.",

View File

@@ -682,47 +682,6 @@ export const dict = {
"settings.commands.description": "Befehlseinstellungen können hier konfiguriert werden.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP-Einstellungen können hier konfiguriert werden.",
"settings.mcp.section.featured": "Vorgestellt",
"settings.mcp.section.featured.description": "Fügen Sie mit einem Klick eine Vorlage für beliebte MCP-Server hinzu.",
"settings.mcp.section.configured": "Konfigurierte Server",
"settings.mcp.section.configured.description":
"Sehen Sie, welche MCP-Server installiert sind, wie sie verbunden sind, und entfernen Sie nicht mehr benötigte.",
"settings.mcp.section.add": "Server hinzufügen",
"settings.mcp.section.add.description": "Erstellen Sie Ihre eigene lokale oder Remote-MCP-Server-Konfiguration.",
"settings.mcp.type.local": "Lokal",
"settings.mcp.type.remote": "Remote",
"settings.mcp.featured.added": "Hinzugefügt",
"settings.mcp.action.add": "Server hinzufügen",
"settings.mcp.action.remove": "Entfernen",
"settings.mcp.state.enabled": "Aktiviert",
"settings.mcp.state.disabled": "Deaktiviert",
"settings.mcp.form.type.label": "Verbindungstyp",
"settings.mcp.form.name.label": "Servername",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "Remote-URL",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "Befehl",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "Header",
"settings.mcp.form.headers.description": "Optional. Fügen Sie einen Header pro Zeile im Format KEY: value hinzu.",
"settings.mcp.form.environment.label": "Umgebung",
"settings.mcp.form.environment.description": "Optional. Fügen Sie eine Variable pro Zeile im Format KEY=value hinzu.",
"settings.mcp.form.timeout.label": "Timeout (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "MCP-Server hinzugefügt",
"settings.mcp.toast.added.description": "{{name}} wurde in Ihren MCP-Einstellungen gespeichert.",
"settings.mcp.toast.updated.title": "MCP-Server aktualisiert",
"settings.mcp.toast.updated.description": "{{name}} ist jetzt {{state}}.",
"settings.mcp.toast.removed.title": "MCP-Server entfernt",
"settings.mcp.toast.removed.description": "{{name}} wurde aus Ihren MCP-Einstellungen entfernt.",
"settings.mcp.validation.name": "Geben Sie vor dem Speichern einen Servernamen ein.",
"settings.mcp.validation.duplicate": "{{name}} ist bereits konfiguriert.",
"settings.mcp.validation.url": "Geben Sie eine Remote-MCP-Server-URL ein.",
"settings.mcp.validation.command": "Geben Sie den Befehl zum Starten des lokalen MCP-Servers ein.",
"settings.mcp.validation.timeout": "Das Timeout muss eine positive ganze Zahl sein.",
"settings.mcp.validation.headers": "Header-Zeile konnte nicht verarbeitet werden: {{line}}",
"settings.mcp.validation.environment": "Umgebungsvariablen-Zeile konnte nicht verarbeitet werden: {{line}}",
"settings.mcp.status.needs_client_registration": "Client-Registrierung erforderlich",
"settings.permissions.title": "Berechtigungen",
"settings.permissions.description": "Steuern Sie, welche Tools der Server standardmäßig verwenden darf.",
"settings.permissions.section.tools": "Tools",

View File

@@ -770,48 +770,7 @@ export const dict = {
"settings.commands.title": "Commands",
"settings.commands.description": "Command settings will be configurable here.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "Manage local and remote MCP servers that OpenCode can use across your workspaces.",
"settings.mcp.section.featured": "Featured",
"settings.mcp.section.featured.description": "Add a polished preset for popular MCP servers in one click.",
"settings.mcp.section.configured": "Configured servers",
"settings.mcp.section.configured.description":
"See which MCP servers are installed, how they connect, and remove the ones you no longer need.",
"settings.mcp.section.add": "Add a server",
"settings.mcp.section.add.description": "Create your own local or remote MCP server configuration.",
"settings.mcp.type.local": "Local",
"settings.mcp.type.remote": "Remote",
"settings.mcp.featured.added": "Added",
"settings.mcp.action.add": "Add server",
"settings.mcp.action.remove": "Remove",
"settings.mcp.state.enabled": "Enabled",
"settings.mcp.state.disabled": "Disabled",
"settings.mcp.form.type.label": "Connection type",
"settings.mcp.form.name.label": "Server name",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "Remote URL",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "Command",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "Headers",
"settings.mcp.form.headers.description": "Optional. Add one header per line using KEY: value.",
"settings.mcp.form.environment.label": "Environment",
"settings.mcp.form.environment.description": "Optional. Add one variable per line using KEY=value.",
"settings.mcp.form.timeout.label": "Timeout (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "MCP server added",
"settings.mcp.toast.added.description": "{{name}} has been saved to your MCP settings.",
"settings.mcp.toast.updated.title": "MCP server updated",
"settings.mcp.toast.updated.description": "{{name}} is now {{state}}.",
"settings.mcp.toast.removed.title": "MCP server removed",
"settings.mcp.toast.removed.description": "{{name}} has been removed from your MCP settings.",
"settings.mcp.validation.name": "Enter a server name before saving.",
"settings.mcp.validation.duplicate": "{{name}} is already configured.",
"settings.mcp.validation.url": "Enter a remote MCP server URL.",
"settings.mcp.validation.command": "Enter the command used to start the local MCP server.",
"settings.mcp.validation.timeout": "Timeout must be a positive whole number.",
"settings.mcp.validation.headers": "Couldn't parse header line: {{line}}",
"settings.mcp.validation.environment": "Couldn't parse environment line: {{line}}",
"settings.mcp.status.needs_client_registration": "Needs client registration",
"settings.mcp.description": "MCP settings will be configurable here.",
"settings.permissions.title": "Permissions",
"settings.permissions.description": "Control what tools the server can use by default.",

View File

@@ -751,48 +751,6 @@ export const dict = {
"settings.commands.description": "La configuración de comandos estará disponible aquí.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "La configuración de MCP estará disponible aquí.",
"settings.mcp.section.featured": "Destacados",
"settings.mcp.section.featured.description":
"Añade un ajuste preestablecido para servidores MCP populares con un solo clic.",
"settings.mcp.section.configured": "Servidores configurados",
"settings.mcp.section.configured.description":
"Consulta qué servidores MCP están instalados, cómo se conectan y elimina los que ya no necesites.",
"settings.mcp.section.add": "Añadir un servidor",
"settings.mcp.section.add.description": "Crea tu propia configuración de servidor MCP local o remoto.",
"settings.mcp.type.local": "Local",
"settings.mcp.type.remote": "Remoto",
"settings.mcp.featured.added": "Añadido",
"settings.mcp.action.add": "Añadir servidor",
"settings.mcp.action.remove": "Eliminar",
"settings.mcp.state.enabled": "Habilitado",
"settings.mcp.state.disabled": "Deshabilitado",
"settings.mcp.form.type.label": "Tipo de conexión",
"settings.mcp.form.name.label": "Nombre del servidor",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "URL remota",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "Comando",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "Encabezados",
"settings.mcp.form.headers.description": "Opcional. Añade un encabezado por línea usando KEY: value.",
"settings.mcp.form.environment.label": "Entorno",
"settings.mcp.form.environment.description": "Opcional. Añade una variable por línea usando KEY=value.",
"settings.mcp.form.timeout.label": "Tiempo de espera (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "Servidor MCP añadido",
"settings.mcp.toast.added.description": "{{name}} se ha guardado en tu configuración de MCP.",
"settings.mcp.toast.updated.title": "Servidor MCP actualizado",
"settings.mcp.toast.updated.description": "{{name}} está ahora {{state}}.",
"settings.mcp.toast.removed.title": "Servidor MCP eliminado",
"settings.mcp.toast.removed.description": "{{name}} se ha eliminado de tu configuración de MCP.",
"settings.mcp.validation.name": "Introduce un nombre de servidor antes de guardar.",
"settings.mcp.validation.duplicate": "{{name}} ya está configurado.",
"settings.mcp.validation.url": "Introduce una URL de servidor MCP remoto.",
"settings.mcp.validation.command": "Introduce el comando utilizado para iniciar el servidor MCP local.",
"settings.mcp.validation.timeout": "El tiempo de espera debe ser un número entero positivo.",
"settings.mcp.validation.headers": "No se pudo analizar la línea de encabezado: {{line}}",
"settings.mcp.validation.environment": "No se pudo analizar la línea de entorno: {{line}}",
"settings.mcp.status.needs_client_registration": "Requiere registro de cliente",
"settings.permissions.title": "Permisos",
"settings.permissions.description": "Controla qué herramientas puede usar el servidor por defecto.",

View File

@@ -679,48 +679,6 @@ export const dict = {
"settings.commands.description": "Les paramètres des commandes seront configurables ici.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "Les paramètres MCP seront configurables ici.",
"settings.mcp.section.featured": "En vedette",
"settings.mcp.section.featured.description":
"Ajoutez un préréglage soigné pour les serveurs MCP populaires en un clic.",
"settings.mcp.section.configured": "Serveurs configurés",
"settings.mcp.section.configured.description":
"Consultez les serveurs MCP installés, leur mode de connexion, et supprimez ceux dont vous n'avez plus besoin.",
"settings.mcp.section.add": "Ajouter un serveur",
"settings.mcp.section.add.description": "Créez votre propre configuration de serveur MCP local ou distant.",
"settings.mcp.type.local": "Local",
"settings.mcp.type.remote": "Distant",
"settings.mcp.featured.added": "Ajouté",
"settings.mcp.action.add": "Ajouter un serveur",
"settings.mcp.action.remove": "Supprimer",
"settings.mcp.state.enabled": "Activé",
"settings.mcp.state.disabled": "Désactivé",
"settings.mcp.form.type.label": "Type de connexion",
"settings.mcp.form.name.label": "Nom du serveur",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "URL distante",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "Commande",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "En-têtes",
"settings.mcp.form.headers.description": "Facultatif. Ajoutez un en-tête par ligne au format KEY: value.",
"settings.mcp.form.environment.label": "Environnement",
"settings.mcp.form.environment.description": "Facultatif. Ajoutez une variable par ligne au format KEY=value.",
"settings.mcp.form.timeout.label": "Délai d'attente (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "Serveur MCP ajouté",
"settings.mcp.toast.added.description": "{{name}} a été enregistré dans vos paramètres MCP.",
"settings.mcp.toast.updated.title": "Serveur MCP mis à jour",
"settings.mcp.toast.updated.description": "{{name}} est désormais {{state}}.",
"settings.mcp.toast.removed.title": "Serveur MCP supprimé",
"settings.mcp.toast.removed.description": "{{name}} a été supprimé de vos paramètres MCP.",
"settings.mcp.validation.name": "Saisissez un nom de serveur avant d'enregistrer.",
"settings.mcp.validation.duplicate": "{{name}} est déjà configuré.",
"settings.mcp.validation.url": "Saisissez une URL de serveur MCP distant.",
"settings.mcp.validation.command": "Saisissez la commande utilisée pour démarrer le serveur MCP local.",
"settings.mcp.validation.timeout": "Le délai d'attente doit être un nombre entier positif.",
"settings.mcp.validation.headers": "Impossible d'analyser la ligne d'en-tête : {{line}}",
"settings.mcp.validation.environment": "Impossible d'analyser la ligne d'environnement : {{line}}",
"settings.mcp.status.needs_client_registration": "Nécessite l'enregistrement du client",
"settings.permissions.title": "Permissions",
"settings.permissions.description": "Contrôlez les outils que le serveur peut utiliser par défaut.",
"settings.permissions.section.tools": "Outils",

View File

@@ -670,48 +670,6 @@ export const dict = {
"settings.commands.description": "コマンド設定はここで構成できます。",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP設定はここで構成できます。",
"settings.mcp.section.featured": "おすすめ",
"settings.mcp.section.featured.description":
"人気のMCPサーバー向けに調整されたプリセットをワンクリックで追加できます。",
"settings.mcp.section.configured": "設定済みのサーバー",
"settings.mcp.section.configured.description":
"インストールされているMCPサーバーや接続方法を確認したり、不要になったサーバーを削除したりできます。",
"settings.mcp.section.add": "サーバーを追加",
"settings.mcp.section.add.description": "ローカルまたはリモートの独自のMCPサーバー設定を作成します。",
"settings.mcp.type.local": "ローカル",
"settings.mcp.type.remote": "リモート",
"settings.mcp.featured.added": "追加済み",
"settings.mcp.action.add": "サーバーを追加",
"settings.mcp.action.remove": "削除",
"settings.mcp.state.enabled": "有効",
"settings.mcp.state.disabled": "無効",
"settings.mcp.form.type.label": "接続タイプ",
"settings.mcp.form.name.label": "サーバー名",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "リモートURL",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "コマンド",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "ヘッダー",
"settings.mcp.form.headers.description": "任意。1行につき1つのヘッダーを KEY: value の形式で追加してください。",
"settings.mcp.form.environment.label": "環境変数",
"settings.mcp.form.environment.description": "任意。1行につき1つの変数を KEY=value の形式で追加してください。",
"settings.mcp.form.timeout.label": "タイムアウト (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "MCPサーバーを追加しました",
"settings.mcp.toast.added.description": "{{name}} をMCP設定に保存しました。",
"settings.mcp.toast.updated.title": "MCPサーバーを更新しました",
"settings.mcp.toast.updated.description": "{{name}} は現在{{state}}です。",
"settings.mcp.toast.removed.title": "MCPサーバーを削除しました",
"settings.mcp.toast.removed.description": "MCP設定から {{name}} を削除しました。",
"settings.mcp.validation.name": "保存する前にサーバー名を入力してください。",
"settings.mcp.validation.duplicate": "{{name}} は既に設定されています。",
"settings.mcp.validation.url": "リモートMCPサーバーのURLを入力してください。",
"settings.mcp.validation.command": "ローカルMCPサーバーの起動コマンドを入力してください。",
"settings.mcp.validation.timeout": "タイムアウトには正の整数を指定してください。",
"settings.mcp.validation.headers": "ヘッダー行を解析できませんでした: {{line}}",
"settings.mcp.validation.environment": "環境変数の行を解析できませんでした: {{line}}",
"settings.mcp.status.needs_client_registration": "クライアント登録が必要です",
"settings.permissions.title": "権限",
"settings.permissions.description": "サーバーがデフォルトで使用できるツールを制御します。",
"settings.permissions.section.tools": "ツール",

View File

@@ -670,49 +670,6 @@ export const dict = {
"settings.commands.description": "명령어 설정은 여기서 구성할 수 있습니다.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP 설정은 여기서 구성할 수 있습니다.",
"settings.mcp.section.featured": "추천",
"settings.mcp.section.featured.description": "클릭 한 번으로 인기 있는 MCP 서버에 대한 프리셋을 추가하세요.",
"settings.mcp.section.configured": "구성된 서버",
"settings.mcp.section.configured.description":
"설치된 MCP 서버와 연결 방식을 확인하고, 더 이상 필요하지 않은 서버를 제거하세요.",
"settings.mcp.section.add": "서버 추가",
"settings.mcp.section.add.description": "로컬 또는 원격 MCP 서버 구성을 직접 생성하세요.",
"settings.mcp.type.local": "로컬",
"settings.mcp.type.remote": "원격",
"settings.mcp.featured.added": "추가됨",
"settings.mcp.action.add": "서버 추가",
"settings.mcp.action.remove": "제거",
"settings.mcp.state.enabled": "활성화됨",
"settings.mcp.state.disabled": "비활성화됨",
"settings.mcp.form.type.label": "연결 유형",
"settings.mcp.form.name.label": "서버 이름",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "원격 URL",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "명령",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "헤더",
"settings.mcp.form.headers.description":
"선택 사항입니다. KEY: value 형식을 사용하여 한 줄에 하나의 헤더를 추가하세요.",
"settings.mcp.form.environment.label": "환경 변수",
"settings.mcp.form.environment.description":
"선택 사항입니다. KEY=value 형식을 사용하여 한 줄에 하나의 변수를 추가하세요.",
"settings.mcp.form.timeout.label": "시간 초과 (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "MCP 서버 추가됨",
"settings.mcp.toast.added.description": "{{name}} 서버가 MCP 설정에 저장되었습니다.",
"settings.mcp.toast.updated.title": "MCP 서버 업데이트됨",
"settings.mcp.toast.updated.description": "{{name}}이(가) 이제 {{state}} 상태입니다.",
"settings.mcp.toast.removed.title": "MCP 서버 제거됨",
"settings.mcp.toast.removed.description": "{{name}} 서버가 MCP 설정에서 제거되었습니다.",
"settings.mcp.validation.name": "저장하기 전에 서버 이름을 입력하세요.",
"settings.mcp.validation.duplicate": "{{name}}은(는) 이미 구성되어 있습니다.",
"settings.mcp.validation.url": "원격 MCP 서버 URL을 입력하세요.",
"settings.mcp.validation.command": "로컬 MCP 서버를 시작하는 데 사용되는 명령을 입력하세요.",
"settings.mcp.validation.timeout": "시간 초과는 양의 정수여야 합니다.",
"settings.mcp.validation.headers": "헤더 줄을 구문 분석할 수 없습니다: {{line}}",
"settings.mcp.validation.environment": "환경 변수 줄을 구문 분석할 수 없습니다: {{line}}",
"settings.mcp.status.needs_client_registration": "클라이언트 등록 필요",
"settings.permissions.title": "권한",
"settings.permissions.description": "서버가 기본적으로 사용할 수 있는 도구를 제어합니다.",
"settings.permissions.section.tools": "도구",

View File

@@ -747,47 +747,6 @@ export const dict = {
"settings.commands.description": "Kommandoinnstillinger vil kunne konfigureres her.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP-innstillinger vil kunne konfigureres her.",
"settings.mcp.section.featured": "Utvalgte",
"settings.mcp.section.featured.description": "Legg til et ferdig oppsett for populære MCP-servere med ett klikk.",
"settings.mcp.section.configured": "Konfigurerte servere",
"settings.mcp.section.configured.description":
"Se hvilke MCP-servere som er installert, hvordan de kobler til, og fjern de du ikke lenger trenger.",
"settings.mcp.section.add": "Legg til en server",
"settings.mcp.section.add.description": "Opprett din egen konfigurasjon for lokal eller ekstern MCP-server.",
"settings.mcp.type.local": "Lokal",
"settings.mcp.type.remote": "Ekstern",
"settings.mcp.featured.added": "Lagt til",
"settings.mcp.action.add": "Legg til server",
"settings.mcp.action.remove": "Fjern",
"settings.mcp.state.enabled": "Aktivert",
"settings.mcp.state.disabled": "Deaktivert",
"settings.mcp.form.type.label": "Tilkoblingstype",
"settings.mcp.form.name.label": "Servernavn",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "Ekstern URL",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "Kommando",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "Headere",
"settings.mcp.form.headers.description": "Valgfritt. Legg til én header per linje med KEY: value.",
"settings.mcp.form.environment.label": "Miljø",
"settings.mcp.form.environment.description": "Valgfritt. Legg til én variabel per linje med KEY=value.",
"settings.mcp.form.timeout.label": "Tidsavbrudd (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "MCP-server lagt til",
"settings.mcp.toast.added.description": "{{name}} er lagret i dine MCP-innstillinger.",
"settings.mcp.toast.updated.title": "MCP-server oppdatert",
"settings.mcp.toast.updated.description": "{{name}} er nå {{state}}.",
"settings.mcp.toast.removed.title": "MCP-server fjernet",
"settings.mcp.toast.removed.description": "{{name}} er fjernet fra dine MCP-innstillinger.",
"settings.mcp.validation.name": "Skriv inn et servernavn før du lagrer.",
"settings.mcp.validation.duplicate": "{{name}} er allerede konfigurert.",
"settings.mcp.validation.url": "Skriv inn URL for ekstern MCP-server.",
"settings.mcp.validation.command": "Skriv inn kommandoen for å starte den lokale MCP-serveren.",
"settings.mcp.validation.timeout": "Tidsavbrudd må være et positivt heltall.",
"settings.mcp.validation.headers": "Kunne ikke tolke header-linje: {{line}}",
"settings.mcp.validation.environment": "Kunne ikke tolke miljøvariabel-linje: {{line}}",
"settings.mcp.status.needs_client_registration": "Krever klientregistrering",
"settings.permissions.title": "Tillatelser",
"settings.permissions.description": "Kontroller hvilke verktøy serveren kan bruke som standard.",

View File

@@ -672,48 +672,6 @@ export const dict = {
"settings.commands.description": "Ustawienia poleceń będą tutaj konfigurowalne.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "Ustawienia MCP będą tutaj konfigurowalne.",
"settings.mcp.section.featured": "Wyróżnione",
"settings.mcp.section.featured.description":
"Dodaj dopracowany zestaw dla popularnych serwerów MCP jednym kliknięciem.",
"settings.mcp.section.configured": "Skonfigurowane serwery",
"settings.mcp.section.configured.description":
"Zobacz, które serwery MCP są zainstalowane, jak się łączą, i usuń te, których już nie potrzebujesz.",
"settings.mcp.section.add": "Dodaj serwer",
"settings.mcp.section.add.description": "Utwórz własną konfigurację lokalnego lub zdalnego serwera MCP.",
"settings.mcp.type.local": "Lokalny",
"settings.mcp.type.remote": "Zdalny",
"settings.mcp.featured.added": "Dodano",
"settings.mcp.action.add": "Dodaj serwer",
"settings.mcp.action.remove": "Usuń",
"settings.mcp.state.enabled": "Włączony",
"settings.mcp.state.disabled": "Wyłączony",
"settings.mcp.form.type.label": "Typ połączenia",
"settings.mcp.form.name.label": "Nazwa serwera",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "Zdalny adres URL",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "Polecenie",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "Nagłówki",
"settings.mcp.form.headers.description": "Opcjonalne. Dodaj jeden nagłówek w wierszu używając formatu KEY: value.",
"settings.mcp.form.environment.label": "Środowisko",
"settings.mcp.form.environment.description": "Opcjonalne. Dodaj jedną zmienną w wierszu używając formatu KEY=value.",
"settings.mcp.form.timeout.label": "Limit czasu (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "Dodano serwer MCP",
"settings.mcp.toast.added.description": "{{name}} został zapisany w ustawieniach MCP.",
"settings.mcp.toast.updated.title": "Zaktualizowano serwer MCP",
"settings.mcp.toast.updated.description": "{{name}} jest teraz {{state}}.",
"settings.mcp.toast.removed.title": "Usunięto serwer MCP",
"settings.mcp.toast.removed.description": "{{name}} został usunięty z ustawień MCP.",
"settings.mcp.validation.name": "Wprowadź nazwę serwera przed zapisaniem.",
"settings.mcp.validation.duplicate": "{{name}} jest już skonfigurowany.",
"settings.mcp.validation.url": "Wprowadź adres URL zdalnego serwera MCP.",
"settings.mcp.validation.command": "Wprowadź polecenie służące do uruchomienia lokalnego serwera MCP.",
"settings.mcp.validation.timeout": "Limit czasu musi być dodatnią liczbą całkowitą.",
"settings.mcp.validation.headers": "Nie udało się przetworzyć wiersza nagłówka: {{line}}",
"settings.mcp.validation.environment": "Nie udało się przetworzyć wiersza środowiska: {{line}}",
"settings.mcp.status.needs_client_registration": "Wymaga rejestracji klienta",
"settings.permissions.title": "Uprawnienia",
"settings.permissions.description": "Kontroluj, jakich narzędzi serwer może używać domyślnie.",
"settings.permissions.section.tools": "Narzędzia",

View File

@@ -747,49 +747,6 @@ export const dict = {
"settings.commands.description": "Настройки команд будут доступны здесь.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "Настройки MCP будут доступны здесь.",
"settings.mcp.section.featured": "Рекомендуемые",
"settings.mcp.section.featured.description": "Добавляйте готовые настройки для популярных серверов MCP в один клик.",
"settings.mcp.section.configured": "Настроенные серверы",
"settings.mcp.section.configured.description":
"Просматривайте установленные серверы MCP, способы их подключения и удаляйте ненужные.",
"settings.mcp.section.add": "Добавить сервер",
"settings.mcp.section.add.description": "Создайте собственную конфигурацию локального или удаленного сервера MCP.",
"settings.mcp.type.local": "Локальный",
"settings.mcp.type.remote": "Удаленный",
"settings.mcp.featured.added": "Добавлено",
"settings.mcp.action.add": "Добавить сервер",
"settings.mcp.action.remove": "Удалить",
"settings.mcp.state.enabled": "Включено",
"settings.mcp.state.disabled": "Отключено",
"settings.mcp.form.type.label": "Тип подключения",
"settings.mcp.form.name.label": "Имя сервера",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "URL удаленного сервера",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "Команда",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "Заголовки",
"settings.mcp.form.headers.description":
"Необязательно. Добавьте по одному заголовку на строку в формате KEY: value.",
"settings.mcp.form.environment.label": "Окружение",
"settings.mcp.form.environment.description":
"Необязательно. Добавьте по одной переменной на строку в формате KEY=value.",
"settings.mcp.form.timeout.label": "Тайм-аут (мс)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "Сервер MCP добавлен",
"settings.mcp.toast.added.description": "{{name}} сохранен в настройках MCP.",
"settings.mcp.toast.updated.title": "Сервер MCP обновлен",
"settings.mcp.toast.updated.description": "{{name}} теперь {{state}}.",
"settings.mcp.toast.removed.title": "Сервер MCP удален",
"settings.mcp.toast.removed.description": "{{name}} удален из настроек MCP.",
"settings.mcp.validation.name": "Введите имя сервера перед сохранением.",
"settings.mcp.validation.duplicate": "{{name}} уже настроен.",
"settings.mcp.validation.url": "Введите URL удаленного сервера MCP.",
"settings.mcp.validation.command": "Введите команду для запуска локального сервера MCP.",
"settings.mcp.validation.timeout": "Тайм-аут должен быть положительным целым числом.",
"settings.mcp.validation.headers": "Не удалось разобрать строку заголовка: {{line}}",
"settings.mcp.validation.environment": "Не удалось разобрать строку окружения: {{line}}",
"settings.mcp.status.needs_client_registration": "Требуется регистрация клиента",
"settings.permissions.title": "Разрешения",
"settings.permissions.description": "Контролируйте какие инструменты сервер может использовать по умолчанию.",

View File

@@ -738,47 +738,6 @@ export const dict = {
"settings.commands.description": "การตั้งค่าคำสั่งจะสามารถกำหนดค่าได้ที่นี่",
"settings.mcp.title": "MCP",
"settings.mcp.description": "การตั้งค่า MCP จะสามารถกำหนดค่าได้ที่นี่",
"settings.mcp.section.featured": "แนะนำ",
"settings.mcp.section.featured.description": "เพิ่มค่าที่ตั้งไว้ล่วงหน้าสำหรับเซิร์ฟเวอร์ MCP ยอดนิยมได้ในคลิกเดียว",
"settings.mcp.section.configured": "เซิร์ฟเวอร์ที่กำหนดค่าแล้ว",
"settings.mcp.section.configured.description":
"ดูว่าเซิร์ฟเวอร์ MCP ใดติดตั้งอยู่ เชื่อมต่ออย่างไร และลบสิ่งที่ไม่ต้องการออก",
"settings.mcp.section.add": "เพิ่มเซิร์ฟเวอร์",
"settings.mcp.section.add.description": "สร้างการกำหนดค่าเซิร์ฟเวอร์ MCP แบบ Local หรือ Remote ของคุณเอง",
"settings.mcp.type.local": "Local",
"settings.mcp.type.remote": "Remote",
"settings.mcp.featured.added": "เพิ่มแล้ว",
"settings.mcp.action.add": "เพิ่มเซิร์ฟเวอร์",
"settings.mcp.action.remove": "ลบ",
"settings.mcp.state.enabled": "เปิดใช้งาน",
"settings.mcp.state.disabled": "ปิดใช้งาน",
"settings.mcp.form.type.label": "ประเภทการเชื่อมต่อ",
"settings.mcp.form.name.label": "ชื่อเซิร์ฟเวอร์",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "URL ระยะไกล",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "คำสั่ง",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "Headers",
"settings.mcp.form.headers.description": "ไม่บังคับ เพิ่มหนึ่ง header ต่อบรรทัดโดยใช้ KEY: value",
"settings.mcp.form.environment.label": "Environment",
"settings.mcp.form.environment.description": "ไม่บังคับ เพิ่มหนึ่งตัวแปรต่อบรรทัดโดยใช้ KEY=value",
"settings.mcp.form.timeout.label": "Timeout (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "เพิ่มเซิร์ฟเวอร์ MCP แล้ว",
"settings.mcp.toast.added.description": "บันทึก {{name}} ลงในค่าติดตั้ง MCP ของคุณแล้ว",
"settings.mcp.toast.updated.title": "อัปเดตเซิร์ฟเวอร์ MCP แล้ว",
"settings.mcp.toast.updated.description": "ขณะนี้ {{name}} อยู่ในสถานะ {{state}}",
"settings.mcp.toast.removed.title": "ลบเซิร์ฟเวอร์ MCP แล้ว",
"settings.mcp.toast.removed.description": "ลบ {{name}} ออกจากค่าติดตั้ง MCP ของคุณแล้ว",
"settings.mcp.validation.name": "ป้อนชื่อเซิร์ฟเวอร์ก่อนบันทึก",
"settings.mcp.validation.duplicate": "มีการกำหนดค่า {{name}} ไว้แล้ว",
"settings.mcp.validation.url": "ป้อน URL ของเซิร์ฟเวอร์ MCP ระยะไกล",
"settings.mcp.validation.command": "ป้อนคำสั่งที่ใช้เริ่มต้นเซิร์ฟเวอร์ MCP แบบ Local",
"settings.mcp.validation.timeout": "Timeout ต้องเป็นจำนวนเต็มบวก",
"settings.mcp.validation.headers": "ไม่สามารถแยกวิเคราะห์บรรทัด header: {{line}}",
"settings.mcp.validation.environment": "ไม่สามารถแยกวิเคราะห์บรรทัด environment: {{line}}",
"settings.mcp.status.needs_client_registration": "ต้องลงทะเบียนไคลเอนต์",
"settings.permissions.title": "สิทธิ์",
"settings.permissions.description": "ควบคุมเครื่องมือที่เซิร์ฟเวอร์สามารถใช้โดยค่าเริ่มต้น",

View File

@@ -759,50 +759,6 @@ export const dict = {
"settings.commands.description": "Komut ayarları burada yapılandırılabilecek.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP ayarları burada yapılandırılabilecek.",
"settings.mcp.section.featured": "Öne Çıkanlar",
"settings.mcp.section.featured.description":
"Tek tıkla popüler MCP sunucuları için cilalanmış bir hazır ayar ekleyin.",
"settings.mcp.section.configured": "Yapılandırılmış sunucular",
"settings.mcp.section.configured.description":
"Hangi MCP sunucularının yüklü olduğunu ve nasıl bağlandıklarını görün; artık ihtiyaç duymadıklarınızı kaldırın.",
"settings.mcp.section.add": "Sunucu ekle",
"settings.mcp.section.add.description": "Kendi yerel veya uzak MCP sunucu yapılandırmanızı oluşturun.",
"settings.mcp.type.local": "Yerel",
"settings.mcp.type.remote": "Uzak",
"settings.mcp.featured.added": "Eklendi",
"settings.mcp.action.add": "Sunucu ekle",
"settings.mcp.action.remove": "Kaldır",
"settings.mcp.state.enabled": "Etkin",
"settings.mcp.state.disabled": "Devre Dışı",
"settings.mcp.form.type.label": "Bağlantı türü",
"settings.mcp.form.name.label": "Sunucu adı",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "Uzak URL",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "Komut",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "Başlıklar",
"settings.mcp.form.headers.description":
"İsteğe bağlı. Her satıra bir başlık olacak şekilde KEY: value biçiminde ekleyin.",
"settings.mcp.form.environment.label": "Ortam",
"settings.mcp.form.environment.description":
"İsteğe bağlı. Her satıra bir değişken olacak şekilde KEY=value biçiminde ekleyin.",
"settings.mcp.form.timeout.label": "Zaman aşımı (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "MCP sunucusu eklendi",
"settings.mcp.toast.added.description": "{{name}}, MCP ayarlarınıza kaydedildi.",
"settings.mcp.toast.updated.title": "MCP sunucusu güncellendi",
"settings.mcp.toast.updated.description": "{{name}} artık {{state}}.",
"settings.mcp.toast.removed.title": "MCP sunucusu kaldırıldı",
"settings.mcp.toast.removed.description": "{{name}}, MCP ayarlarınızdan kaldırıldı.",
"settings.mcp.validation.name": "Kaydetmeden önce bir sunucu adı girin.",
"settings.mcp.validation.duplicate": "{{name}} zaten yapılandırılmış.",
"settings.mcp.validation.url": "Bir uzak MCP sunucu URL'si girin.",
"settings.mcp.validation.command": "Yerel MCP sunucusunu başlatmak için kullanılan komutu girin.",
"settings.mcp.validation.timeout": "Zaman aşımı pozitif bir tam sayı olmalıdır.",
"settings.mcp.validation.headers": "Başlık satırı ayrıştırılamadı: {{line}}",
"settings.mcp.validation.environment": "Ortam satırı ayrıştırılamadı: {{line}}",
"settings.mcp.status.needs_client_registration": "İstemci kaydı gerekiyor",
"settings.permissions.title": "İzinler",
"settings.permissions.description": "Sunucunun varsayılan olarak hangi araçları kullanabileceğini kontrol edin.",

View File

@@ -738,46 +738,6 @@ export const dict = {
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP 设置将在此处可配置。",
"settings.mcp.section.featured": "精选",
"settings.mcp.section.featured.description": "一键添加经过精心优化的热门 MCP 服务器预设。",
"settings.mcp.section.configured": "已配置的服务器",
"settings.mcp.section.configured.description": "查看已安装的 MCP 服务器及其连接方式,并移除不再需要的服务器。",
"settings.mcp.section.add": "添加服务器",
"settings.mcp.section.add.description": "创建您自己的本地或远程 MCP 服务器配置。",
"settings.mcp.type.local": "本地",
"settings.mcp.type.remote": "远程",
"settings.mcp.featured.added": "已添加",
"settings.mcp.action.add": "添加服务器",
"settings.mcp.action.remove": "移除",
"settings.mcp.state.enabled": "已启用",
"settings.mcp.state.disabled": "已禁用",
"settings.mcp.form.type.label": "连接类型",
"settings.mcp.form.name.label": "服务器名称",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "远程 URL",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "命令",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "请求头",
"settings.mcp.form.headers.description": "可选。每行添加一个请求头,格式为 KEY: value。",
"settings.mcp.form.environment.label": "环境变量",
"settings.mcp.form.environment.description": "可选。每行添加一个变量,格式为 KEY=value。",
"settings.mcp.form.timeout.label": "超时 (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "MCP 服务器已添加",
"settings.mcp.toast.added.description": "{{name}} 已保存到您的 MCP 设置中。",
"settings.mcp.toast.updated.title": "MCP 服务器已更新",
"settings.mcp.toast.updated.description": "{{name}} 现已{{state}}。",
"settings.mcp.toast.removed.title": "MCP 服务器已移除",
"settings.mcp.toast.removed.description": "{{name}} 已从您的 MCP 设置中移除。",
"settings.mcp.validation.name": "保存前请输入服务器名称。",
"settings.mcp.validation.duplicate": "{{name}} 已配置。",
"settings.mcp.validation.url": "请输入远程 MCP 服务器 URL。",
"settings.mcp.validation.command": "请输入用于启动本地 MCP 服务器的命令。",
"settings.mcp.validation.timeout": "超时必须为正整数。",
"settings.mcp.validation.headers": "无法解析请求头行:{{line}}",
"settings.mcp.validation.environment": "无法解析环境行:{{line}}",
"settings.mcp.status.needs_client_registration": "需要客户端注册",
"settings.permissions.title": "权限",
"settings.permissions.description": "控制服务器默认可以使用哪些工具。",

View File

@@ -731,46 +731,6 @@ export const dict = {
"settings.commands.description": "命令設定將在此處可設定。",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP 設定將在此處可設定。",
"settings.mcp.section.featured": "精選",
"settings.mcp.section.featured.description": "一鍵新增熱門 MCP 伺服器的精選預設設定。",
"settings.mcp.section.configured": "已設定的伺服器",
"settings.mcp.section.configured.description": "查看已安裝的 MCP 伺服器及其連線方式,並移除不再需要的伺服器。",
"settings.mcp.section.add": "新增伺服器",
"settings.mcp.section.add.description": "建立您自己的本機或遠端 MCP 伺服器設定。",
"settings.mcp.type.local": "本機",
"settings.mcp.type.remote": "遠端",
"settings.mcp.featured.added": "已新增",
"settings.mcp.action.add": "新增伺服器",
"settings.mcp.action.remove": "移除",
"settings.mcp.state.enabled": "已啟用",
"settings.mcp.state.disabled": "已停用",
"settings.mcp.form.type.label": "連線類型",
"settings.mcp.form.name.label": "伺服器名稱",
"settings.mcp.form.name.placeholder": "my-mcp-server",
"settings.mcp.form.url.label": "遠端 URL",
"settings.mcp.form.url.placeholder": "https://mcp.example.com/mcp",
"settings.mcp.form.command.label": "指令",
"settings.mcp.form.command.placeholder": "npx -y @modelcontextprotocol/server-memory",
"settings.mcp.form.headers.label": "標頭",
"settings.mcp.form.headers.description": "選用。每行新增一個標頭,格式為 KEY: value。",
"settings.mcp.form.environment.label": "環境變數",
"settings.mcp.form.environment.description": "選用。每行新增一個變數,格式為 KEY=value。",
"settings.mcp.form.timeout.label": "逾時 (ms)",
"settings.mcp.form.timeout.placeholder": "5000",
"settings.mcp.toast.added.title": "已新增 MCP 伺服器",
"settings.mcp.toast.added.description": "{{name}} 已儲存至您的 MCP 設定。",
"settings.mcp.toast.updated.title": "MCP 伺服器已更新",
"settings.mcp.toast.updated.description": "{{name}} 現已{{state}}。",
"settings.mcp.toast.removed.title": "已移除 MCP 伺服器",
"settings.mcp.toast.removed.description": "{{name}} 已從您的 MCP 設定中移除。",
"settings.mcp.validation.name": "儲存前請輸入伺服器名稱。",
"settings.mcp.validation.duplicate": "{{name}} 已經設定過了。",
"settings.mcp.validation.url": "請輸入遠端 MCP 伺服器 URL。",
"settings.mcp.validation.command": "請輸入用於啟動本機 MCP 伺服器的指令。",
"settings.mcp.validation.timeout": "逾時必須是正整數。",
"settings.mcp.validation.headers": "無法解析標頭行:{{line}}",
"settings.mcp.validation.environment": "無法解析環境變數行:{{line}}",
"settings.mcp.status.needs_client_registration": "需要用戶端註冊",
"settings.permissions.title": "權限",
"settings.permissions.description": "控制伺服器預設可以使用哪些工具。",

View File

@@ -22,6 +22,5 @@
}
},
"include": ["src", "package.json"],
"exclude": ["dist", "ts-dist"],
"references": [{ "path": "../sdk/js" }]
"exclude": ["dist", "ts-dist"]
}

View File

@@ -129,14 +129,40 @@ export function Session() {
.toSorted((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
})
const messages = createMemo(() => sync.data.message[route.sessionID] ?? [])
const localPermissions = createMemo(() => sync.data.permission[route.sessionID] ?? [])
const localQuestions = createMemo(() => sync.data.question[route.sessionID] ?? [])
const childSessions = createMemo(() => {
if (session()?.parentID) return []
return children().filter((x) => x.id !== route.sessionID)
})
const permissions = createMemo(() => {
if (session()?.parentID) return []
return children().flatMap((x) => sync.data.permission[x.id] ?? [])
const child = childSessions().flatMap((x) => sync.data.permission[x.id] ?? [])
return [...localPermissions(), ...child]
})
const questions = createMemo(() => {
if (session()?.parentID) return []
return children().flatMap((x) => sync.data.question[x.id] ?? [])
const child = childSessions().flatMap((x) => sync.data.question[x.id] ?? [])
return [...localQuestions(), ...child]
})
const activeSubagents = createMemo(() =>
childSessions().flatMap((item) => {
const status = sync.data.session_status?.[item.id]
if (status?.type !== "busy" && status?.type !== "retry") return []
const count = (sync.data.message[item.id] ?? [])
.flatMap((message) => sync.data.part[message.id] ?? [])
.filter(
(part) => part.type === "tool" && (part.state.status === "completed" || part.state.status === "error"),
).length
return [
{
session: item,
status,
count,
},
]
}),
)
const pending = createMemo(() => {
return messages().findLast((x) => x.role === "assistant" && !x.time.completed)?.id
@@ -1150,6 +1176,29 @@ export function Session() {
</For>
</scrollbox>
<box flexShrink={0}>
<Show when={activeSubagents().length > 0}>
<box paddingLeft={3} paddingBottom={1} gap={0}>
<text fg={theme.text}>
<span style={{ fg: theme.textMuted }}>Subagents</span> {activeSubagents().length} running
<span style={{ fg: theme.textMuted }}> · {keybind.print("session_child_cycle")} open</span>
</text>
<For each={activeSubagents()}>
{(item) => (
<text
fg={theme.textMuted}
onMouseUp={() => {
navigate({
type: "session",
sessionID: item.session.id,
})
}}
>
{Locale.truncate(item.session.title, 36)} · {item.count} toolcalls
</text>
)}
</For>
</box>
</Show>
<Show when={permissions().length > 0}>
<PermissionPrompt request={permissions()[0]} />
</Show>
@@ -1157,7 +1206,7 @@ export function Session() {
<QuestionPrompt request={questions()[0]} />
</Show>
<Prompt
visible={!session()?.parentID && permissions().length === 0 && questions().length === 0}
visible={!session()?.parentID && localPermissions().length === 0 && localQuestions().length === 0}
ref={(r) => {
prompt = r
promptRef.set(r)
@@ -1166,7 +1215,7 @@ export function Session() {
r.set(route.initialPrompt)
}
}}
disabled={permissions().length > 0 || questions().length > 0}
disabled={localPermissions().length > 0 || localQuestions().length > 0}
onSubmit={() => {
toBottom()
}}
@@ -1625,11 +1674,14 @@ function InlineTool(props: {
spinner?: boolean
children: JSX.Element
part: ToolPart
onClick?: () => void
}) {
const [margin, setMargin] = createSignal(0)
const { theme } = useTheme()
const ctx = use()
const sync = useSync()
const renderer = useRenderer()
const [hover, setHover] = createSignal(false)
const permission = createMemo(() => {
const callID = sync.data.permission[ctx.sessionID]?.at(0)?.tool?.callID
@@ -1639,6 +1691,7 @@ function InlineTool(props: {
const fg = createMemo(() => {
if (permission()) return theme.warning
if (hover() && props.onClick) return theme.text
if (props.complete) return theme.textMuted
return theme.text
})
@@ -1656,6 +1709,12 @@ function InlineTool(props: {
<box
marginTop={margin()}
paddingLeft={3}
onMouseOver={() => props.onClick && setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => {
if (renderer.getSelection()?.getSelectedText()) return
props.onClick?.()
}}
renderBefore={function () {
const el = this as BoxRenderable
const parent = el.parent
@@ -1943,10 +2002,8 @@ function WebSearch(props: ToolProps<any>) {
}
function Task(props: ToolProps<typeof TaskTool>) {
const { theme } = useTheme()
const keybind = useKeybind()
const { navigate } = useRoute()
const local = useLocal()
const sync = useSync()
onMount(() => {
@@ -1964,9 +2021,47 @@ function Task(props: ToolProps<typeof TaskTool>) {
)
})
const current = createMemo(() => tools().findLast((x) => (x.state as any).title))
const isRunning = createMemo(() => props.part.state.status === "running")
const current = createMemo(() => tools().findLast((x) => x.state.status !== "pending"))
const background = createMemo(() => props.metadata.background === true)
const status = createMemo(() => {
const sessionID = props.metadata.sessionId
if (!sessionID) return
return sync.data.session_status?.[sessionID]
})
const counts = createMemo(() => {
const all = tools()
const done = all.filter((item) => item.state.status === "completed" || item.state.status === "error").length
return {
all: all.length,
done,
}
})
const childRunning = createMemo(() => status()?.type === "busy" || status()?.type === "retry")
const latest = createMemo(() => {
const user = messages().findLast((msg) => msg.role === "user")
const assistant = messages().findLast((msg) => msg.role === "assistant")
return {
user,
assistant,
}
})
const terminal = createMemo(() => {
const assistant = latest().assistant
if (!assistant) return false
const user = latest().user
if (user && user.id > assistant.id) return false
if (assistant.error) return true
return !!assistant.finish && !["tool-calls", "unknown"].includes(assistant.finish)
})
const backgroundRunning = createMemo(() => background() && childRunning())
const failed = createMemo(() => !!background() && terminal() && !!latest().assistant?.error)
const statusLabel = createMemo(() => {
if (backgroundRunning()) return "running in background"
if (!terminal()) return "background task pending sync"
if (failed()) return "background task failed"
return "background task finished"
})
const isRunning = createMemo(() => props.part.state.status === "running" || childRunning())
const duration = createMemo(() => {
const first = messages().find((x) => x.role === "user")?.time.created
@@ -1977,16 +2072,21 @@ function Task(props: ToolProps<typeof TaskTool>) {
const content = createMemo(() => {
if (!props.input.description) return ""
let content = [`Task ${props.input.description}`]
const toolLabel = `${childRunning() ? counts().done : counts().all} toolcalls`
const content = [`Task ${props.input.description}`]
if (background()) content.push(`${statusLabel()}`)
if (isRunning() && tools().length > 0) {
// content[0] += ` · ${tools().length} toolcalls`
if (current()) content.push(`${Locale.titlecase(current()!.tool)} ${(current()!.state as any).title}`)
else content.push(`${tools().length} toolcalls`)
const title = current() && (current()!.state as any).title
if (title) content.push(`${Locale.titlecase(current()!.tool)} ${title}`)
else content.push(`${toolLabel}`)
}
if (props.part.state.status === "completed") {
content.push(`${tools().length} toolcalls · ${Locale.duration(duration())}`)
content.push(`${toolLabel} · ${Locale.duration(duration())}`)
} else if (props.metadata.sessionId) {
content.push(`${keybind.print("session_child_cycle")} view subagents`)
}
return content.join("\n")
@@ -1999,6 +2099,11 @@ function Task(props: ToolProps<typeof TaskTool>) {
complete={props.input.description}
pending="Delegating..."
part={props.part}
onClick={() => {
if (props.metadata.sessionId) {
navigate({ type: "session", sessionID: props.metadata.sessionId })
}
}}
>
{content()}
</InlineTool>

View File

@@ -7,6 +7,7 @@ import { GrepTool } from "./grep"
import { BatchTool } from "./batch"
import { ReadTool } from "./read"
import { TaskTool } from "./task"
import { TaskStatusTool } from "./task_status"
import { TodoWriteTool, TodoReadTool } from "./todo"
import { WebFetchTool } from "./webfetch"
import { WriteTool } from "./write"
@@ -110,6 +111,7 @@ export namespace ToolRegistry {
EditTool,
WriteTool,
TaskTool,
TaskStatusTool,
WebFetchTool,
TodoWriteTool,
// TodoReadTool,

View File

@@ -6,6 +6,7 @@ import { MessageV2 } from "../session/message-v2"
import { Identifier } from "../id/id"
import { Agent } from "../agent/agent"
import { SessionPrompt } from "../session/prompt"
import { SessionStatus } from "../session/status"
import { iife } from "@/util/iife"
import { defer } from "@/util/defer"
import { Config } from "../config/config"
@@ -22,8 +23,93 @@ const parameters = z.object({
)
.optional(),
command: z.string().describe("The command that triggered this task").optional(),
background: z
.boolean()
.optional()
.describe("When true, launch the subagent in the background and return immediately"),
})
function output(sessionID: string, text: string) {
return [
`task_id: ${sessionID} (for resuming to continue this task if needed)`,
"",
"<task_result>",
text,
"</task_result>",
].join("\n")
}
function backgroundOutput(sessionID: string) {
return [
`task_id: ${sessionID} (for polling this task with task_status)`,
"state: running",
"",
"<task_result>",
"Background task started. Continue your current work and call task_status when you need the result.",
"</task_result>",
].join("\n")
}
function backgroundMessage(input: {
sessionID: string
description: string
state: "completed" | "error"
text: string
}) {
const tag = input.state === "completed" ? "task_result" : "task_error"
const title =
input.state === "completed"
? `Background task completed: ${input.description}`
: `Background task failed: ${input.description}`
return [title, `task_id: ${input.sessionID}`, `state: ${input.state}`, `<${tag}>`, input.text, `</${tag}>`].join("\n")
}
function errorText(error: unknown) {
if (error instanceof Error) return error.message
return String(error)
}
function resultTaskID(input: unknown) {
if (!input || typeof input !== "object") return
const taskID = Reflect.get(input, "task_id")
if (typeof taskID === "string") return taskID
}
function polled(input: { message: MessageV2.WithParts; taskID: string }) {
if (input.message.info.role !== "assistant") return false
return input.message.parts.some((part) => {
if (part.type !== "tool") return false
if (part.tool !== "task_status") return false
if (part.state.status !== "completed") return false
return resultTaskID(part.state.input) === input.taskID
})
}
async function latestUser(sessionID: string) {
const [message] = await Session.messages({
sessionID,
limit: 1,
})
if (!message) return
if (message.info.role !== "user") return
return message.info.id
}
async function continueParent(input: { parentID: string; userID: string; taskID: string }) {
const message =
SessionStatus.get(input.parentID).type === "idle"
? undefined
: await SessionPrompt.loop({
sessionID: input.parentID,
}).catch(() => undefined)
if (message && polled({ message, taskID: input.taskID })) return
if (SessionStatus.get(input.parentID).type !== "idle") return
if ((await latestUser(input.parentID)) !== input.userID) return
await SessionPrompt.loop({
sessionID: input.parentID,
})
}
export const TaskTool = Tool.define("task", async (ctx) => {
const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))
@@ -103,62 +189,110 @@ export const TaskTool = Tool.define("task", async (ctx) => {
const msg = await MessageV2.get({ sessionID: ctx.sessionID, messageID: ctx.messageID })
if (msg.info.role !== "assistant") throw new Error("Not an assistant message")
const model = agent.model ?? {
const parentModel = {
modelID: msg.info.modelID,
providerID: msg.info.providerID,
}
const model = agent.model ?? parentModel
const background = params.background === true
const metadata = {
sessionId: session.id,
model,
...(background ? { background: true } : {}),
}
ctx.metadata({
title: params.description,
metadata: {
sessionId: session.id,
model,
},
metadata,
})
const messageID = Identifier.ascending("message")
const run = async () => {
const promptParts = await SessionPrompt.resolvePromptParts(params.prompt)
const result = await SessionPrompt.prompt({
messageID: Identifier.ascending("message"),
sessionID: session.id,
model: {
modelID: model.modelID,
providerID: model.providerID,
},
agent: agent.name,
tools: {
todowrite: false,
todoread: false,
...(hasTaskPermission ? {} : { task: false }),
...Object.fromEntries((config.experimental?.primary_tools ?? []).map((t) => [t, false])),
},
parts: promptParts,
})
return result.parts.findLast((x) => x.type === "text")?.text ?? ""
}
if (background) {
const inject = (state: "completed" | "error", text: string) =>
SessionPrompt.prompt({
sessionID: ctx.sessionID,
noReply: true,
model: {
modelID: parentModel.modelID,
providerID: parentModel.providerID,
},
agent: ctx.agent,
parts: [
{
type: "text",
synthetic: true,
text: backgroundMessage({
sessionID: session.id,
description: params.description,
state,
text,
}),
},
],
})
void run()
.then((text) =>
inject("completed", text)
.then((message) =>
continueParent({
parentID: ctx.sessionID,
userID: message.info.id,
taskID: session.id,
}),
)
.catch(() => {}),
)
.catch((error) =>
inject("error", errorText(error))
.then((message) =>
continueParent({
parentID: ctx.sessionID,
userID: message.info.id,
taskID: session.id,
}),
)
.catch(() => {}),
)
return {
title: params.description,
metadata,
output: backgroundOutput(session.id),
}
}
function cancel() {
SessionPrompt.cancel(session.id)
}
ctx.abort.addEventListener("abort", cancel)
using _ = defer(() => ctx.abort.removeEventListener("abort", cancel))
const promptParts = await SessionPrompt.resolvePromptParts(params.prompt)
const result = await SessionPrompt.prompt({
messageID,
sessionID: session.id,
model: {
modelID: model.modelID,
providerID: model.providerID,
},
agent: agent.name,
tools: {
todowrite: false,
todoread: false,
...(hasTaskPermission ? {} : { task: false }),
...Object.fromEntries((config.experimental?.primary_tools ?? []).map((t) => [t, false])),
},
parts: promptParts,
})
const text = result.parts.findLast((x) => x.type === "text")?.text ?? ""
const output = [
`task_id: ${session.id} (for resuming to continue this task if needed)`,
"",
"<task_result>",
text,
"</task_result>",
].join("\n")
const text = await run()
return {
title: params.description,
metadata: {
sessionId: session.id,
model,
},
output,
metadata,
output: output(session.id, text),
}
},
}

View File

@@ -17,11 +17,13 @@ When NOT to use the Task tool:
Usage notes:
1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result. The output includes a task_id you can reuse later to continue the same subagent session.
3. Each agent invocation starts with a fresh context unless you provide task_id to resume the same subagent session (which continues with its previous messages and tool outputs). When starting fresh, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
4. The agent's outputs should generally be trusted
5. Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent. Tell it how to verify its work if possible (e.g., relevant test commands).
6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
2. By default, task waits for completion and returns the result immediately, along with a task_id you can reuse later to continue the same subagent session.
3. Set background=true to launch asynchronously. In background mode, continue your current work without waiting.
4. For background runs, use task_status(task_id=..., wait=false) to poll, or wait=true to block until done (optionally with timeout_ms).
5. Each agent invocation starts with a fresh context unless you provide task_id to resume the same subagent session (which continues with its previous messages and tool outputs). When starting fresh, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
6. The agent's outputs should generally be trusted
7. Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent. Tell it how to verify its work if possible (e.g., relevant test commands).
8. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
Example usage (NOTE: The agents below are fictional examples for illustration only - use the actual agents listed above):

View File

@@ -0,0 +1,163 @@
import z from "zod"
import { Tool } from "./tool"
import DESCRIPTION from "./task_status.txt"
import { Identifier } from "../id/id"
import { Session } from "../session"
import { SessionStatus } from "../session/status"
import { MessageV2 } from "../session/message-v2"
type State = "running" | "completed" | "error"
const DEFAULT_TIMEOUT = 60_000
const POLL_MS = 300
const parameters = z.object({
task_id: Identifier.schema("session").describe("The task_id returned by the task tool"),
wait: z.boolean().optional().describe("When true, wait until the task reaches a terminal state or timeout"),
timeout_ms: z
.number()
.int()
.positive()
.optional()
.describe("Maximum milliseconds to wait when wait=true (default: 60000)"),
})
function format(input: { taskID: string; state: State; text: string }) {
return [`task_id: ${input.taskID}`, `state: ${input.state}`, "", "<task_result>", input.text, "</task_result>"].join(
"\n",
)
}
function errorText(error: NonNullable<MessageV2.Assistant["error"]>) {
const data = error.data as Record<string, unknown> | undefined
const message = data?.message
if (typeof message === "string" && message) return message
return error.name
}
async function inspect(taskID: string) {
const status = SessionStatus.get(taskID)
if (status.type === "busy" || status.type === "retry") {
return {
state: "running" as const,
text: status.type === "retry" ? `Task is retrying: ${status.message}` : "Task is still running.",
}
}
let latestUser: MessageV2.User | undefined
let latestAssistant:
| {
info: MessageV2.Assistant
parts: MessageV2.Part[]
}
| undefined
for await (const item of MessageV2.stream(taskID)) {
if (!latestUser && item.info.role === "user") latestUser = item.info
if (!latestAssistant && item.info.role === "assistant") {
latestAssistant = {
info: item.info,
parts: item.parts,
}
}
if (latestUser && latestAssistant) break
}
if (!latestAssistant) {
return {
state: "running" as const,
text: "Task has started but has not produced output yet.",
}
}
if (latestUser && latestUser.id > latestAssistant.info.id) {
return {
state: "running" as const,
text: "Task is starting.",
}
}
const text = latestAssistant.parts.findLast((part) => part.type === "text")?.text ?? ""
if (latestAssistant.info.error) {
const summary = errorText(latestAssistant.info.error)
return {
state: "error" as const,
text: text || summary,
}
}
const done = latestAssistant.info.finish && !["tool-calls", "unknown"].includes(latestAssistant.info.finish)
if (done) {
return {
state: "completed" as const,
text,
}
}
return {
state: "running" as const,
text: text || "Task is still running.",
}
}
function sleep(ms: number, abort: AbortSignal) {
return new Promise<void>((resolve, reject) => {
if (abort.aborted) {
reject(new Error("Task status polling aborted"))
return
}
const onAbort = () => {
clearTimeout(timer)
reject(new Error("Task status polling aborted"))
}
const timer = setTimeout(() => {
abort.removeEventListener("abort", onAbort)
resolve()
}, ms)
abort.addEventListener("abort", onAbort, { once: true })
})
}
export const TaskStatusTool = Tool.define("task_status", {
description: DESCRIPTION,
parameters,
async execute(params, ctx) {
await Session.get(params.task_id)
let result = await inspect(params.task_id)
if (!params.wait || result.state !== "running") {
return {
title: "Task status",
metadata: {
task_id: params.task_id,
state: result.state,
timed_out: false,
},
output: format({ taskID: params.task_id, state: result.state, text: result.text }),
}
}
const timeout = params.timeout_ms ?? DEFAULT_TIMEOUT
const end = Date.now() + timeout
while (Date.now() < end) {
const left = end - Date.now()
await sleep(Math.min(POLL_MS, left), ctx.abort)
result = await inspect(params.task_id)
if (result.state !== "running") break
}
const done = result.state !== "running"
const text = done ? result.text : `Timed out after ${timeout}ms while waiting for task completion.`
return {
title: "Task status",
metadata: {
task_id: params.task_id,
state: result.state,
timed_out: !done,
},
output: format({ taskID: params.task_id, state: result.state, text }),
}
},
})

View File

@@ -0,0 +1,13 @@
Poll the status of a subagent task launched with the task tool.
Use this to check background tasks started with `task(background=true)`.
Parameters:
- `task_id` (required): the task session id returned by the task tool
- `wait` (optional): when true, wait for completion
- `timeout_ms` (optional): max wait duration in milliseconds when `wait=true`
Returns compact, parseable output:
- `task_id`
- `state` (`running`, `completed`, or `error`)
- `<task_result>...</task_result>` containing final output, error summary, or current progress text

View File

@@ -0,0 +1,231 @@
import { describe, expect, test } from "bun:test"
import { tmpdir } from "../fixture/fixture"
import { Instance } from "../../src/project/instance"
import { Session } from "../../src/session"
import { Identifier } from "../../src/id/id"
import { SessionStatus } from "../../src/session/status"
import { TaskStatusTool } from "../../src/tool/task_status"
import { MessageV2 } from "../../src/session/message-v2"
const ctx = {
sessionID: "session_test",
messageID: "message_test",
callID: "call_test",
agent: "build",
abort: AbortSignal.any([]),
messages: [],
metadata: () => {},
ask: async () => {},
}
async function user(sessionID: string) {
await Session.updateMessage({
id: Identifier.ascending("message"),
sessionID,
role: "user",
time: {
created: Date.now(),
},
agent: "build",
model: {
providerID: "test-provider",
modelID: "test-model",
},
})
}
async function assistant(input: { sessionID: string; text: string; error?: string }) {
const msg = await Session.updateMessage({
id: Identifier.ascending("message"),
sessionID: input.sessionID,
role: "assistant",
time: {
created: Date.now(),
completed: Date.now(),
},
parentID: Identifier.ascending("message"),
modelID: "test-model",
providerID: "test-provider",
mode: "build",
agent: "build",
path: {
cwd: process.cwd(),
root: process.cwd(),
},
cost: 0,
tokens: {
input: 0,
output: 0,
reasoning: 0,
cache: {
read: 0,
write: 0,
},
},
finish: "stop",
...(input.error
? {
error: new MessageV2.APIError({
message: input.error,
isRetryable: false,
}).toObject(),
}
: {}),
})
await Session.updatePart({
id: Identifier.ascending("part"),
sessionID: input.sessionID,
messageID: msg.id,
type: "text",
text: input.text,
})
}
describe("tool.task_status", () => {
test("returns running while session status is busy", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
SessionStatus.set(session.id, { type: "busy" })
const result = await tool.execute({ task_id: session.id }, ctx)
expect(result.output).toContain("state: running")
SessionStatus.set(session.id, { type: "idle" })
},
})
})
test("returns completed with final task output", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
await assistant({
sessionID: session.id,
text: "all done",
})
const result = await tool.execute({ task_id: session.id }, ctx)
expect(result.output).toContain("state: completed")
expect(result.output).toContain("all done")
},
})
})
test("wait=true blocks until terminal status", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
SessionStatus.set(session.id, { type: "busy" })
const transition = Bun.sleep(150).then(async () => {
SessionStatus.set(session.id, { type: "idle" })
await assistant({
sessionID: session.id,
text: "finished later",
})
})
const result = await tool.execute(
{
task_id: session.id,
wait: true,
timeout_ms: 4_000,
},
ctx,
)
await transition
expect(result.output).toContain("state: completed")
expect(result.output).toContain("finished later")
},
})
})
test("returns error when child run fails", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
await assistant({
sessionID: session.id,
text: "",
error: "child failed",
})
const result = await tool.execute({ task_id: session.id }, ctx)
expect(result.output).toContain("state: error")
expect(result.output).toContain("child failed")
expect(result.metadata.state).toBe("error")
},
})
})
test("wait=true times out with timed_out metadata", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
SessionStatus.set(session.id, { type: "busy" })
const result = await tool.execute(
{
task_id: session.id,
wait: true,
timeout_ms: 80,
},
ctx,
)
expect(result.output).toContain("Timed out after 80ms")
expect(result.metadata.timed_out).toBe(true)
expect(result.metadata.state).toBe("running")
SessionStatus.set(session.id, { type: "idle" })
},
})
})
test("returns running for resumed task with a newer user turn", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
await user(session.id)
await assistant({
sessionID: session.id,
text: "old done",
})
await user(session.id)
const result = await tool.execute({ task_id: session.id }, ctx)
expect(result.output).toContain("state: running")
expect(result.output).toContain("Task is starting.")
},
})
})
})

View File

@@ -12,18 +12,9 @@
".": "./src/index.ts",
"./client": "./src/client.ts",
"./server": "./src/server.ts",
"./v2": {
"types": "./dist/v2/index.d.ts",
"default": "./src/v2/index.ts"
},
"./v2/client": {
"types": "./dist/v2/client.d.ts",
"default": "./src/v2/client.ts"
},
"./v2/gen/client": {
"types": "./dist/v2/gen/client/index.d.ts",
"default": "./src/v2/gen/client/index.ts"
},
"./v2": "./src/v2/index.ts",
"./v2/client": "./src/v2/client.ts",
"./v2/gen/client": "./src/v2/gen/client/index.ts",
"./v2/server": "./src/v2/server.ts"
},
"files": [
@@ -36,8 +27,5 @@
"typescript": "catalog:",
"@typescript/native-preview": "catalog:"
},
"dependencies": {},
"publishConfig": {
"directory": "dist"
}
"dependencies": {}
}