-
-
-
-
{t("agentManager.hoverCard.base")}
-
- {props.worktree.remote
- ? `${props.worktree.remote}/${props.worktree.parentBranch}`
- : props.worktree.parentBranch}
-
+
+
+ {t("agentManager.worktree.doubleClickRename")}
-
-
-
- {t("agentManager.hoverCard.sessions")}
- {props.sessions}
-
-
-
- {t("agentManager.worktree.stale")}
-
-
- {t("agentManager.worktree.stale")}
-
-
- {t("agentManager.worktree.staleTooltip")}
-
- {
- e.stopPropagation()
- props.onRemoveStale()
- }}
- >
- {t("agentManager.worktree.removeStale")}
-
-
-
-
-
- 0}>
-
- {t("agentManager.hoverCard.files")}
- {props.stats!.files}
-
-
- 0 || props.stats!.deletions > 0}>
-
- {t("agentManager.hoverCard.changes")}
-
- 0}>
- +{props.stats!.additions}
-
- 0}>
- −{props.stats!.deletions}
-
+
+
+
-
- 0 || props.stats!.behind > 0}>
-
- {t("agentManager.hoverCard.commits")}
-
- 0}>
- ↑{props.stats!.ahead}
-
- 0}>
- ↓{props.stats!.behind}
-
+
+
+
+ props.onOpen()}>
+
+ {t("agentManager.worktree.openInVscode")}
+
+
-
-
-
-
-
-
- {t("agentManager.worktree.doubleClickRename")}
-
-
-
+
+
+
props.onCopyPath()}>
+
+ {t("agentManager.worktree.copyPath")}
+
+
+
+
>
)
}
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css
index 217de5038e7..6cce27a333a 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css
+++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css
@@ -735,7 +735,6 @@ button.am-section-toggle:hover .am-section-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- max-width: 150px;
}
.am-tab-close {
@@ -3475,3 +3474,32 @@ body.vscode-high-contrast-light {
outline-offset: -1px;
}
}
+
+/* Context menu — restyle to match dropdown-menu visuals.
+ The kilo-ui ContextMenu component hardcodes its own data-component / data-slot
+ attributes after the prop spread, so we can't override them via props.
+ Instead we apply a class and re-declare the styles here. */
+
+.am-ctx-menu {
+ min-width: 210px;
+ border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent) !important;
+ box-shadow: var(--shadow-md) !important;
+
+ [data-slot="context-menu-item"],
+ [data-slot="context-menu-checkbox-item"],
+ [data-slot="context-menu-radio-item"],
+ [data-slot="context-menu-sub-trigger"] {
+ padding: 6px 10px;
+ font-size: var(--font-size-small);
+ transition: none;
+
+ &:hover,
+ &[data-highlighted] {
+ background: var(--surface-raised-base-hover);
+ }
+ }
+
+ [data-slot="context-menu-separator"] {
+ margin: 4px 0;
+ }
+}
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/file-tree-utils.ts b/packages/kilo-vscode/webview-ui/agent-manager/file-tree-utils.ts
index 3762d5993d4..a45c1ef9cc0 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/file-tree-utils.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/file-tree-utils.ts
@@ -33,9 +33,23 @@ export function buildFileTree(diffs: WorktreeFileDiff[]): FileTreeNode[] {
parent.push({ name: filename, path: diff.file, diff })
}
+ sortTree(root)
return root
}
+/** Sort children at every level: directories first (alphabetically), then files (alphabetically). */
+function sortTree(nodes: FileTreeNode[]) {
+ nodes.sort((a, b) => {
+ const aDir = a.children ? 0 : 1
+ const bDir = b.children ? 0 : 1
+ if (aDir !== bDir) return aDir - bDir
+ return a.name.localeCompare(b.name)
+ })
+ for (const node of nodes) {
+ if (node.children) sortTree(node.children)
+ }
+}
+
// Flatten single-child directory chains: src/components/ instead of src > components
export function flatten(nodes: FileTreeNode[]): FileTreeNode[] {
return nodes.map((node) => {
@@ -51,3 +65,18 @@ export function flattenChain(node: FileTreeNode): FileTreeNode {
if (!child.children) return node
return flattenChain({ name: `${node.name}/${child.name}`, path: child.path, children: child.children })
}
+
+/** Return diffs sorted to match the tree's depth-first visual order. */
+export function treeOrder(diffs: WorktreeFileDiff[]): WorktreeFileDiff[] {
+ if (diffs.length <= 1) return diffs
+ const tree = buildFileTree(diffs)
+ const result: WorktreeFileDiff[] = []
+ function walk(nodes: FileTreeNode[]) {
+ for (const node of nodes) {
+ if (node.diff) result.push(node.diff)
+ if (node.children) walk(node.children)
+ }
+ }
+ walk(tree)
+ return result
+}
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts
index 17dd3d354d9..461c4288671 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "خيارات Worktree متقدمة",
"agentManager.worktree.defaultBaseBranch": "الفرع الأساسي الافتراضي",
"agentManager.worktree.defaultBaseBranchAuto": "اكتشاف تلقائي",
+ "agentManager.worktree.copyPath": "نسخ المسار",
+ "agentManager.worktree.openInVscode": "فتح في VS Code",
+ "agentManager.worktree.rename": "إعادة تسمية",
"agentManager.hoverCard.branch": "الفرع",
"agentManager.hoverCard.base": "الأساس",
"agentManager.hoverCard.sessions": "الجلسات",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "بدون عنوان",
"agentManager.session.newSession": "جلسة جديدة",
"agentManager.session.openInWorktree": "فتح في Worktree",
+ "agentManager.session.openLocally": "فتح محليًا",
"agentManager.session.readonly": "جلسة للقراءة فقط",
"agentManager.session.noSessions": "لا توجد جلسات مفتوحة",
"agentManager.tab.close": "إغلاق",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts
index 983879bff5c..a2a5531c4b4 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Opções avançadas de Worktree",
"agentManager.worktree.defaultBaseBranch": "Branch base padrão",
"agentManager.worktree.defaultBaseBranchAuto": "Detecção automática",
+ "agentManager.worktree.copyPath": "Copiar Caminho",
+ "agentManager.worktree.openInVscode": "Abrir no VS Code",
+ "agentManager.worktree.rename": "Renomear",
"agentManager.hoverCard.branch": "BRANCH",
"agentManager.hoverCard.base": "Base",
"agentManager.hoverCard.sessions": "Sessões",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "Sem título",
"agentManager.session.newSession": "Nova Sessão",
"agentManager.session.openInWorktree": "Abrir no Worktree",
+ "agentManager.session.openLocally": "Abrir localmente",
"agentManager.session.readonly": "Sessão somente leitura",
"agentManager.session.noSessions": "Nenhuma sessão aberta",
"agentManager.tab.close": "Fechar",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts
index 9d64195b11d..d92d7434157 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Napredne opcije Worktree-a",
"agentManager.worktree.defaultBaseBranch": "Zadana bazna grana",
"agentManager.worktree.defaultBaseBranchAuto": "Automatsko otkrivanje",
+ "agentManager.worktree.copyPath": "Kopiraj putanju",
+ "agentManager.worktree.openInVscode": "Otvori u VS Code",
+ "agentManager.worktree.rename": "Preimenuj",
"agentManager.hoverCard.branch": "BRANCH",
"agentManager.hoverCard.base": "Baza",
"agentManager.hoverCard.sessions": "Sesije",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "Bez naslova",
"agentManager.session.newSession": "Nova sesija",
"agentManager.session.openInWorktree": "Otvori u Worktree-u",
+ "agentManager.session.openLocally": "Otvori lokalno",
"agentManager.session.readonly": "Sesija samo za čitanje",
"agentManager.session.noSessions": "Nema otvorenih sesija",
"agentManager.tab.close": "Zatvori",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts
index d547f114097..b6b7b692229 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Avancerede Worktree-indstillinger",
"agentManager.worktree.defaultBaseBranch": "Standard basisgren",
"agentManager.worktree.defaultBaseBranchAuto": "Automatisk registrering",
+ "agentManager.worktree.copyPath": "Kopier sti",
+ "agentManager.worktree.openInVscode": "Åbn i VS Code",
+ "agentManager.worktree.rename": "Omdøb",
"agentManager.hoverCard.branch": "BRANCH",
"agentManager.hoverCard.base": "Base",
"agentManager.hoverCard.sessions": "Sessioner",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "Uden titel",
"agentManager.session.newSession": "Ny session",
"agentManager.session.openInWorktree": "Åbn i Worktree",
+ "agentManager.session.openLocally": "Åbn lokalt",
"agentManager.session.readonly": "Skrivebeskyttet session",
"agentManager.session.noSessions": "Ingen åbne sessioner",
"agentManager.tab.close": "Luk",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts
index 8426901d6cc..bc6a0e0f5bc 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Erweiterte Worktree-Optionen",
"agentManager.worktree.defaultBaseBranch": "Standard-Basiszweig",
"agentManager.worktree.defaultBaseBranchAuto": "Automatisch erkennen",
+ "agentManager.worktree.copyPath": "Pfad kopieren",
+ "agentManager.worktree.openInVscode": "In VS Code öffnen",
+ "agentManager.worktree.rename": "Umbenennen",
"agentManager.hoverCard.branch": "BRANCH",
"agentManager.hoverCard.base": "Basis",
"agentManager.hoverCard.sessions": "Sitzungen",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "Unbenannt",
"agentManager.session.newSession": "Neue Sitzung",
"agentManager.session.openInWorktree": "In Worktree öffnen",
+ "agentManager.session.openLocally": "Lokal öffnen",
"agentManager.session.readonly": "Schreibgeschützte Sitzung",
"agentManager.session.noSessions": "Keine Sitzungen geöffnet",
"agentManager.tab.close": "Schließen",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts
index 430003a9cb1..a4ed3a190e4 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts
@@ -17,6 +17,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Advanced worktree options",
"agentManager.worktree.defaultBaseBranch": "Default Base Branch",
"agentManager.worktree.defaultBaseBranchAuto": "Auto-detect",
+ "agentManager.worktree.copyPath": "Copy Path",
+ "agentManager.worktree.openInVscode": "Open in VS Code",
+ "agentManager.worktree.rename": "Rename",
"agentManager.hoverCard.branch": "BRANCH",
"agentManager.hoverCard.base": "Base",
@@ -29,6 +32,7 @@ export const dict = {
"agentManager.session.untitled": "Untitled",
"agentManager.session.newSession": "New Session",
"agentManager.session.openInWorktree": "Open in worktree",
+ "agentManager.session.openLocally": "Open locally",
"agentManager.session.readonly": "Read-only session",
"agentManager.session.noSessions": "No sessions open",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts
index bf96d0f5361..576d2c6e11a 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Opciones avanzadas de Worktree",
"agentManager.worktree.defaultBaseBranch": "Rama base predeterminada",
"agentManager.worktree.defaultBaseBranchAuto": "Detección automática",
+ "agentManager.worktree.copyPath": "Copiar ruta",
+ "agentManager.worktree.openInVscode": "Abrir en VS Code",
+ "agentManager.worktree.rename": "Renombrar",
"agentManager.hoverCard.branch": "BRANCH",
"agentManager.hoverCard.base": "Base",
"agentManager.hoverCard.sessions": "Sesiones",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "Sin título",
"agentManager.session.newSession": "Nueva Sesión",
"agentManager.session.openInWorktree": "Abrir en Worktree",
+ "agentManager.session.openLocally": "Abrir localmente",
"agentManager.session.readonly": "Sesión de solo lectura",
"agentManager.session.noSessions": "No hay sesiones abiertas",
"agentManager.tab.close": "Cerrar",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts
index 2c672e5ce7b..3f880892387 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Options avancées du Worktree",
"agentManager.worktree.defaultBaseBranch": "Branche de base par défaut",
"agentManager.worktree.defaultBaseBranchAuto": "Détection automatique",
+ "agentManager.worktree.copyPath": "Copier le chemin",
+ "agentManager.worktree.openInVscode": "Ouvrir dans VS Code",
+ "agentManager.worktree.rename": "Renommer",
"agentManager.hoverCard.branch": "BRANCHE",
"agentManager.hoverCard.base": "Base",
"agentManager.hoverCard.sessions": "Sessions",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "Sans titre",
"agentManager.session.newSession": "Nouvelle session",
"agentManager.session.openInWorktree": "Ouvrir dans le Worktree",
+ "agentManager.session.openLocally": "Ouvrir localement",
"agentManager.session.readonly": "Session en lecture seule",
"agentManager.session.noSessions": "Aucune session ouverte",
"agentManager.tab.close": "Fermer",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts
index e73ed3bccd8..44c226ad37c 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Worktreeの詳細オプション",
"agentManager.worktree.defaultBaseBranch": "デフォルトベースブランチ",
"agentManager.worktree.defaultBaseBranchAuto": "自動検出",
+ "agentManager.worktree.copyPath": "パスをコピー",
+ "agentManager.worktree.openInVscode": "VS Codeで開く",
+ "agentManager.worktree.rename": "名前を変更",
"agentManager.hoverCard.branch": "ブランチ",
"agentManager.hoverCard.base": "ベース",
"agentManager.hoverCard.sessions": "セッション",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "無題",
"agentManager.session.newSession": "新しいセッション",
"agentManager.session.openInWorktree": "Worktreeで開く",
+ "agentManager.session.openLocally": "ローカルで開く",
"agentManager.session.readonly": "読み取り専用セッション",
"agentManager.session.noSessions": "開いているセッションがありません",
"agentManager.tab.close": "閉じる",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts
index 92df68890d4..8258caba61a 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Worktree 고급 옵션",
"agentManager.worktree.defaultBaseBranch": "기본 베이스 브랜치",
"agentManager.worktree.defaultBaseBranchAuto": "자동 감지",
+ "agentManager.worktree.copyPath": "경로 복사",
+ "agentManager.worktree.openInVscode": "VS Code에서 열기",
+ "agentManager.worktree.rename": "이름 바꾸기",
"agentManager.hoverCard.branch": "브랜치",
"agentManager.hoverCard.base": "베이스",
"agentManager.hoverCard.sessions": "세션",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "제목 없음",
"agentManager.session.newSession": "새 세션",
"agentManager.session.openInWorktree": "Worktree에서 열기",
+ "agentManager.session.openLocally": "로컬에서 열기",
"agentManager.session.readonly": "읽기 전용 세션",
"agentManager.session.noSessions": "열린 세션 없음",
"agentManager.tab.close": "닫기",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts
index cf6b39fce00..40394720d21 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts
@@ -17,6 +17,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Geavanceerde worktree opties",
"agentManager.worktree.defaultBaseBranch": "Standaard basis branch",
"agentManager.worktree.defaultBaseBranchAuto": "Automatisch detecteren",
+ "agentManager.worktree.copyPath": "Pad kopiëren",
+ "agentManager.worktree.openInVscode": "Openen in VS Code",
+ "agentManager.worktree.rename": "Hernoemen",
"agentManager.hoverCard.branch": "BRANCH",
"agentManager.hoverCard.base": "Basis",
@@ -29,6 +32,7 @@ export const dict = {
"agentManager.session.untitled": "Naamloos",
"agentManager.session.newSession": "Nieuwe sessie",
"agentManager.session.openInWorktree": "Openen in worktree",
+ "agentManager.session.openLocally": "Lokaal openen",
"agentManager.session.readonly": "Alleen-lezen sessie",
"agentManager.session.noSessions": "Geen open sessies",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts
index 37f3ac6a93e..1a67eac546f 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Avanserte Worktree-alternativer",
"agentManager.worktree.defaultBaseBranch": "Standard basisgren",
"agentManager.worktree.defaultBaseBranchAuto": "Automatisk gjenkjenning",
+ "agentManager.worktree.copyPath": "Kopier sti",
+ "agentManager.worktree.openInVscode": "Åpne i VS Code",
+ "agentManager.worktree.rename": "Gi nytt navn",
"agentManager.hoverCard.branch": "BRANCH",
"agentManager.hoverCard.base": "Base",
"agentManager.hoverCard.sessions": "Økter",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "Uten tittel",
"agentManager.session.newSession": "Ny økt",
"agentManager.session.openInWorktree": "Åpne i Worktree",
+ "agentManager.session.openLocally": "Åpne lokalt",
"agentManager.session.readonly": "Skrivebeskyttet økt",
"agentManager.session.noSessions": "Ingen åpne økter",
"agentManager.tab.close": "Lukk",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts
index 504baadd599..57a71417611 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Zaawansowane opcje Worktree",
"agentManager.worktree.defaultBaseBranch": "Domyślna gałąź bazowa",
"agentManager.worktree.defaultBaseBranchAuto": "Automatyczne wykrywanie",
+ "agentManager.worktree.copyPath": "Kopiuj ścieżkę",
+ "agentManager.worktree.openInVscode": "Otwórz w VS Code",
+ "agentManager.worktree.rename": "Zmień nazwę",
"agentManager.hoverCard.branch": "BRANCH",
"agentManager.hoverCard.base": "Baza",
"agentManager.hoverCard.sessions": "Sesje",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "Bez tytułu",
"agentManager.session.newSession": "Nowa sesja",
"agentManager.session.openInWorktree": "Otwórz w Worktree",
+ "agentManager.session.openLocally": "Otwórz lokalnie",
"agentManager.session.readonly": "Sesja tylko do odczytu",
"agentManager.session.noSessions": "Brak otwartych sesji",
"agentManager.tab.close": "Zamknij",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts
index c01e829ac47..5748502ce48 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Расширенные настройки Worktree",
"agentManager.worktree.defaultBaseBranch": "Базовая ветка по умолчанию",
"agentManager.worktree.defaultBaseBranchAuto": "Автоопределение",
+ "agentManager.worktree.copyPath": "Копировать путь",
+ "agentManager.worktree.openInVscode": "Открыть в VS Code",
+ "agentManager.worktree.rename": "Переименовать",
"agentManager.hoverCard.branch": "ВЕТКА",
"agentManager.hoverCard.base": "Основа",
"agentManager.hoverCard.sessions": "Сессии",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "Без названия",
"agentManager.session.newSession": "Новая сессия",
"agentManager.session.openInWorktree": "Открыть в Worktree",
+ "agentManager.session.openLocally": "Открыть локально",
"agentManager.session.readonly": "Сессия только для чтения",
"agentManager.session.noSessions": "Нет открытых сессий",
"agentManager.tab.close": "Закрыть",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts
index d8451dfa4e4..62c833e0b70 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "ตัวเลือก Worktree ขั้นสูง",
"agentManager.worktree.defaultBaseBranch": "สาขาฐานเริ่มต้น",
"agentManager.worktree.defaultBaseBranchAuto": "ตรวจจับอัตโนมัติ",
+ "agentManager.worktree.copyPath": "คัดลอกเส้นทาง",
+ "agentManager.worktree.openInVscode": "เปิดใน VS Code",
+ "agentManager.worktree.rename": "เปลี่ยนชื่อ",
"agentManager.hoverCard.branch": "BRANCH",
"agentManager.hoverCard.base": "ฐาน",
"agentManager.hoverCard.sessions": "เซสชัน",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "ไม่มีชื่อ",
"agentManager.session.newSession": "เซสชันใหม่",
"agentManager.session.openInWorktree": "เปิดใน Worktree",
+ "agentManager.session.openLocally": "เปิดในเครื่อง",
"agentManager.session.readonly": "เซสชันอ่านอย่างเดียว",
"agentManager.session.noSessions": "ไม่มีเซสชันที่เปิดอยู่",
"agentManager.tab.close": "ปิด",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts
index ef876c6a915..10ce0c20985 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts
@@ -17,6 +17,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Gelişmiş worktree seçenekleri",
"agentManager.worktree.defaultBaseBranch": "Varsayılan Temel Dal",
"agentManager.worktree.defaultBaseBranchAuto": "Otomatik algıla",
+ "agentManager.worktree.copyPath": "Yolu Kopyala",
+ "agentManager.worktree.openInVscode": "VS Code'da Aç",
+ "agentManager.worktree.rename": "Yeniden Adlandır",
"agentManager.hoverCard.branch": "DAL",
"agentManager.hoverCard.base": "Temel",
@@ -29,6 +32,7 @@ export const dict = {
"agentManager.session.untitled": "Adsız",
"agentManager.session.newSession": "Yeni Oturum",
"agentManager.session.openInWorktree": "Worktree içinde aç",
+ "agentManager.session.openLocally": "Yerel olarak aç",
"agentManager.session.readonly": "Salt okunur oturum",
"agentManager.session.noSessions": "Açık oturum yok",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts
index 42e3c048450..fd8f3a5071d 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Worktree 高级选项",
"agentManager.worktree.defaultBaseBranch": "默认基础分支",
"agentManager.worktree.defaultBaseBranchAuto": "自动检测",
+ "agentManager.worktree.copyPath": "复制路径",
+ "agentManager.worktree.openInVscode": "在 VS Code 中打开",
+ "agentManager.worktree.rename": "重命名",
"agentManager.hoverCard.branch": "分支",
"agentManager.hoverCard.base": "基础",
"agentManager.hoverCard.sessions": "会话",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "无标题",
"agentManager.session.newSession": "新建会话",
"agentManager.session.openInWorktree": "在 Worktree 中打开",
+ "agentManager.session.openLocally": "在本地打开",
"agentManager.session.readonly": "只读会话",
"agentManager.session.noSessions": "没有打开的会话",
"agentManager.tab.close": "关闭",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts
index 1640249553b..0906b6f73f0 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts
@@ -16,6 +16,9 @@ export const dict = {
"agentManager.worktree.advancedOptions": "Worktree 進階選項",
"agentManager.worktree.defaultBaseBranch": "預設基礎分支",
"agentManager.worktree.defaultBaseBranchAuto": "自動偵測",
+ "agentManager.worktree.copyPath": "複製路徑",
+ "agentManager.worktree.openInVscode": "在 VS Code 中打開",
+ "agentManager.worktree.rename": "重新命名",
"agentManager.hoverCard.branch": "分支",
"agentManager.hoverCard.base": "基底",
"agentManager.hoverCard.sessions": "工作階段",
@@ -26,6 +29,7 @@ export const dict = {
"agentManager.session.untitled": "未命名",
"agentManager.session.newSession": "新建工作階段",
"agentManager.session.openInWorktree": "在 Worktree 中開啟",
+ "agentManager.session.openLocally": "在本機開啟",
"agentManager.session.readonly": "唯讀工作階段",
"agentManager.session.noSessions": "沒有開啟的工作階段",
"agentManager.tab.close": "關閉",
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/keybind-tokens.ts b/packages/kilo-vscode/webview-ui/agent-manager/keybind-tokens.ts
new file mode 100644
index 00000000000..923cd2a5596
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/agent-manager/keybind-tokens.ts
@@ -0,0 +1,23 @@
+/** Parse a display keybinding string into separate key tokens for rendering.
+ * Windows/Linux format ("Ctrl+Shift+W") splits on "+".
+ * Mac format ("⌘⇧W") splits on known modifier symbols. */
+export function parseBindingTokens(binding: string): string[] {
+ if (!binding) return []
+ // Windows/Linux: "Ctrl+Shift+W" → ["Ctrl", "Shift", "W"]
+ if (binding.includes("+")) return binding.split("+")
+ // Mac: "⌘⇧W" → ["⌘", "⇧", "W"] — peel off known modifier symbols
+ const tokens: string[] = []
+ let rest = binding
+ const modifiers = ["⌘", "⇧", "⌃", "⌥"]
+ while (rest.length > 0) {
+ const mod = modifiers.find((m) => rest.startsWith(m))
+ if (mod) {
+ tokens.push(mod)
+ rest = rest.slice(mod.length)
+ } else {
+ tokens.push(rest)
+ break
+ }
+ }
+ return tokens
+}
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/review-annotations.ts b/packages/kilo-vscode/webview-ui/agent-manager/review-annotations.ts
index 028b68440c3..97055b096b0 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/review-annotations.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/review-annotations.ts
@@ -20,6 +20,7 @@ export interface AnnotationMeta {
file: string
side: AnnotationSide
line: number
+ editing?: boolean
}
interface AnnotationHandlers {
@@ -71,6 +72,35 @@ function makeActionButton(title: string, icon: SVGSVGElement, action: () => void
return button
}
+export function buildFileAnnotations(
+ file: string,
+ fileComments: ReviewComment[],
+ edit: string | null,
+ draft: { file: string; side: AnnotationSide; line: number } | null,
+ draftMeta: AnnotationMeta | null,
+): { annotations: DiffLineAnnotation
[]; draftMeta: AnnotationMeta | null } {
+ const result: DiffLineAnnotation[] = fileComments.map((c) => ({
+ side: c.side,
+ lineNumber: c.line,
+ metadata: {
+ type: "comment" as const,
+ comment: c,
+ file: c.file,
+ side: c.side,
+ line: c.line,
+ editing: c.id === edit,
+ },
+ }))
+
+ if (draft && draft.file === file) {
+ if (!draftMeta || draftMeta.file !== draft.file || draftMeta.side !== draft.side || draftMeta.line !== draft.line) {
+ draftMeta = { type: "draft", comment: null, file: draft.file, side: draft.side, line: draft.line }
+ }
+ result.push({ side: draft.side, lineNumber: draft.line, metadata: draftMeta })
+ }
+ return { annotations: result, draftMeta }
+}
+
export function buildReviewAnnotation(
annotation: DiffLineAnnotation,
handlers: AnnotationHandlers,
@@ -146,7 +176,7 @@ export function buildReviewAnnotation(
}
const comment = meta.comment!
- if (handlers.editing === comment.id) {
+ if (meta.editing) {
wrapper.className = "am-annotation am-annotation-draft"
const header = document.createElement("div")
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/sortable-tab.tsx b/packages/kilo-vscode/webview-ui/agent-manager/sortable-tab.tsx
index 2432e5d74c9..7238060c76f 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/sortable-tab.tsx
+++ b/packages/kilo-vscode/webview-ui/agent-manager/sortable-tab.tsx
@@ -20,6 +20,7 @@ import { Icon } from "@kilocode/kilo-ui/icon"
import { TooltipKeybind } from "@kilocode/kilo-ui/tooltip"
import { ContextMenu } from "@kilocode/kilo-ui/context-menu"
import { useLanguage } from "../src/context/language"
+import { parseBindingTokens } from "./keybind-tokens"
/** Lock drag movement to the X axis (horizontal-only tab dragging). */
export const ConstrainDragYAxis: Component = () => {
@@ -92,7 +93,7 @@ export const SortableTab: Component<{
-
+
diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx
index daeff967578..9f535c0fcfa 100644
--- a/packages/kilo-vscode/webview-ui/src/App.tsx
+++ b/packages/kilo-vscode/webview-ui/src/App.tsx
@@ -1,4 +1,4 @@
-import { Component, createSignal, createMemo, Switch, Match, Show, onMount, onCleanup } from "solid-js"
+import { Component, createSignal, createMemo, Switch, Match, onMount, onCleanup } from "solid-js"
import { ThemeProvider } from "@kilocode/kilo-ui/theme"
import { DialogProvider } from "@kilocode/kilo-ui/context/dialog"
import { MarkedProvider } from "@kilocode/kilo-ui/context/marked"
@@ -20,7 +20,6 @@ import { SessionProvider, useSession } from "./context/session"
import { LanguageProvider } from "./context/language"
import { ChatView } from "./components/chat"
import { MarketplaceView } from "./components/marketplace"
-import { KiloNotifications } from "./components/chat/KiloNotifications"
import { registerExpandedTaskTool } from "./components/chat/TaskToolExpanded"
import { registerVscodeToolOverrides } from "./components/chat/VscodeToolOverrides"
@@ -29,8 +28,7 @@ import { registerVscodeToolOverrides } from "./components/chat/VscodeToolOverrid
registerExpandedTaskTool()
// Apply VS Code sidebar preferences to other tools (e.g. bash expanded by default).
registerVscodeToolOverrides()
-import SessionList from "./components/history/SessionList"
-import CloudSessionList from "./components/history/CloudSessionList"
+import HistoryView from "./components/history/HistoryView"
import { MigrationWizard } from "./components/migration" // legacy-migration
import { NotificationsProvider } from "./context/notifications"
import type { Message as SDKMessage, Part as SDKPart } from "@kilocode/sdk/v2"
@@ -40,7 +38,6 @@ type ViewType =
| "newTask"
| "marketplace"
| "history"
- | "cloudHistory"
| "profile"
| "settings"
| "migration" // legacy-migration
@@ -49,7 +46,6 @@ const VALID_VIEWS = new Set([
"newTask",
"marketplace",
"history",
- "cloudHistory",
"profile",
"settings",
"migration", // legacy-migration
@@ -113,6 +109,10 @@ export const DataBridge: Component<{ children: any }> = (props) => {
vscode.postMessage({ type: "openFile", filePath, line, column })
}
+ const openUrl = (url: string) => {
+ vscode.postMessage({ type: "openExternal", url })
+ }
+
const directory = () => {
const dir = server.workspaceDirectory()
if (!dir) return ""
@@ -128,6 +128,7 @@ export const DataBridge: Component<{ children: any }> = (props) => {
onQuestionReply={reply}
onQuestionReject={reject}
onOpenFile={open}
+ onOpenUrl={openUrl}
>
{props.children}
@@ -167,9 +168,6 @@ const AppContent: Component = () => {
case "historyButtonClicked":
setCurrentView("history")
break
- case "cloudHistoryButtonClicked":
- setCurrentView("cloudHistory")
- break
case "profileButtonClicked":
setCurrentView("profile")
break
@@ -230,26 +228,19 @@ const AppContent: Component = () => {
return (
-
}>
+
}>
-
-
-
-
+ setCurrentView("history")}
+ continueInWorktree
+ />
-
-
-
- {
- session.selectCloudSession(cloudSessionId)
- setCurrentView("newTask")
- }}
- />
+ setCurrentView("newTask")} />
void
+ onShowHistory?: () => void
readonly?: boolean
+ /** When true, show the "Continue in Worktree" button. Defaults to true in the sidebar. */
+ continueInWorktree?: boolean
}
export const ChatView: Component = (props) => {
@@ -31,11 +37,17 @@ export const ChatView: Component = (props) => {
const server = useServer()
// Show "Show Changes" only in the standalone sidebar, not inside Agent Manager
const isSidebar = () => worktreeMode === undefined
+ // Show "Continue in Worktree": only when explicitly enabled via prop
+ const canContinueInWorktree = () => props.continueInWorktree === true
const id = () => session.currentSessionID()
const hasMessages = () => session.messages().length > 0
const idle = () => session.status() !== "busy"
+ // "Continue in Worktree" state
+ const [transferring, setTransferring] = createSignal(false)
+ const [transferDetail, setTransferDetail] = createSignal("")
+
// Permissions and questions scoped to this session's family (self + subagents).
// Each ChatView only sees its own session tree — no cross-session leakage.
// Memoized so the BFS walk in sessionFamily() runs once per reactive update,
@@ -67,7 +79,7 @@ export const ChatView: Component = (props) => {
onMount(() => {
if (props.readonly) return
const handler = (e: KeyboardEvent) => {
- if (e.key === "Escape" && session.status() === "busy") {
+ if (e.key === "Escape" && session.status() === "busy" && !e.defaultPrevented) {
e.preventDefault()
session.abort()
}
@@ -76,6 +88,34 @@ export const ChatView: Component = (props) => {
onCleanup(() => document.removeEventListener("keydown", handler))
})
+ // Listen for "Continue in Worktree" progress messages
+ {
+ const labels: Record = {
+ capturing: "Capturing changes...",
+ creating: "Creating worktree...",
+ setup: "Running setup...",
+ transferring: "Transferring changes...",
+ forking: "Starting session...",
+ }
+ const cleanup = vscode.onMessage((msg) => {
+ if (msg.type !== "continueInWorktreeProgress") return
+ const m = msg as { status: string; error?: string }
+ if (m.status === "done") {
+ setTransferring(false)
+ setTransferDetail("")
+ return
+ }
+ if (m.status === "error") {
+ setTransferring(false)
+ setTransferDetail("")
+ showToast({ title: m.error ?? "Failed to continue in worktree" })
+ return
+ }
+ setTransferDetail(labels[m.status] ?? "Working...")
+ })
+ onCleanup(cleanup)
+ }
+
const decide = (response: "once" | "always" | "reject", approvedAlways: string[], deniedAlways: string[]) => {
const perm = permissionRequest()
if (!perm || session.respondingPermissions().has(perm.id)) return
@@ -87,7 +127,7 @@ export const ChatView: Component = (props) => {
@@ -110,27 +150,67 @@ export const ChatView: Component = (props) => {
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx
index c7c45ff5570..f4ea7fe6f8b 100644
--- a/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx
@@ -56,7 +56,7 @@ export const CloudImportDialog: Component = (props) => {
error={error()}
/>
-
dialog.close()}>
+ dialog.close()}>
{language.t("common.cancel")}
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx
index 2bacc16c8c5..7beffc2fc00 100644
--- a/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/chat/KiloNotifications.tsx
@@ -1,11 +1,9 @@
-import { Component, Show, createMemo, createSignal } from "solid-js"
-import { Button } from "@kilocode/kilo-ui/button"
-import { IconButton } from "@kilocode/kilo-ui/icon-button"
-import { Icon } from "@kilocode/kilo-ui/icon"
+import { Component, Show, createEffect, createMemo, createSignal } from "solid-js"
import { useNotifications } from "../../context/notifications"
import { useVSCode } from "../../context/vscode"
import { useSession } from "../../context/session"
import { useProvider } from "../../context/provider"
+import { useLanguage } from "../../context/language"
import { KILO_PROVIDER_ID } from "../../../../src/shared/provider-model"
import { TelemetryEventName } from "../../../../src/services/telemetry/types"
@@ -14,6 +12,7 @@ export const KiloNotifications: Component = () => {
const vscode = useVSCode()
const session = useSession()
const provider = useProvider()
+ const language = useLanguage()
const [index, setIndex] = createSignal(0)
const items = filteredNotifications
@@ -21,18 +20,24 @@ export const KiloNotifications: Component = () => {
const safeIndex = () => Math.min(index(), Math.max(0, total() - 1))
const current = createMemo(() => (total() === 0 ? undefined : items()[safeIndex()]))
- const prev = () => setIndex((i) => (i - 1 + total()) % total())
- const next = () => setIndex((i) => (i + 1) % total())
+ // Clamp index whenever the list shrinks so navigation always reflects reality
+ createEffect(() => {
+ const max = Math.max(0, total() - 1)
+ if (index() > max) setIndex(max)
+ })
const handleAction = (url: string) => {
vscode.postMessage({ type: "openExternal", url })
}
- const handleDismiss = () => {
- const n = current()
- if (!n) return
- dismiss(n.id)
- setIndex((i) => Math.min(i, Math.max(0, total() - 2)))
+ const isLast = () => safeIndex() === total() - 1
+
+ const handleNext = () => {
+ if (isLast()) {
+ for (const n of items()) dismiss(n.id)
+ } else {
+ setIndex(safeIndex() + 1)
+ }
}
/**
@@ -73,35 +78,36 @@ export const KiloNotifications: Component = () => {
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx
index c0ef0f1c983..ed3ac698ac9 100644
--- a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx
@@ -10,18 +10,17 @@
import { Component, For, Show, createEffect, createMemo, onCleanup, JSX } from "solid-js"
import { Icon } from "@kilocode/kilo-ui/icon"
import { Spinner } from "@kilocode/kilo-ui/spinner"
-import { Button } from "@kilocode/kilo-ui/button"
import { useDialog } from "@kilocode/kilo-ui/context/dialog"
import { createAutoScroll } from "@kilocode/kilo-ui/hooks"
import { useSession } from "../../context/session"
import { useServer } from "../../context/server"
import { useLanguage } from "../../context/language"
import { formatRelativeDate } from "../../utils/date"
-import { CloudImportDialog } from "./CloudImportDialog"
import { FeedbackDialog } from "./FeedbackDialog"
import { VscodeSessionTurn } from "./VscodeSessionTurn"
import { RevertBanner } from "./RevertBanner"
import { AccountSwitcher } from "../shared/AccountSwitcher"
+import { KiloNotifications } from "./KiloNotifications"
import { WorkingIndicator } from "../shared/WorkingIndicator"
import { activeUserMessageID as getActiveUserMessageID } from "../../context/session-queue"
@@ -40,6 +39,7 @@ const KiloLogo = (): JSX.Element => {
interface MessageListProps {
onSelectSession?: (id: string) => void
+ onShowHistory?: () => void
}
export const MessageList: Component = (props) => {
@@ -50,7 +50,6 @@ export const MessageList: Component = (props) => {
const autoScroll = createAutoScroll({
working: () => session.status() !== "idle",
- overflowAnchor: "dynamic",
})
// Resume auto-scroll when a bottom-dock permission/question is dismissed
@@ -92,7 +91,10 @@ export const MessageList: Component = (props) => {
return (
-
+
= (props) => {
)}
+
+ props.onShowHistory?.()}>
+
+ {language.t("session.showHistory")}
+
+
-
- dialog.show(() => (
- {
- session.selectCloudSession(id)
- }}
- />
- ))
- }
- >
- {language.t("session.cloud.import")}
-
dialog.show(() => )}>
{language.t("feedback.button")}
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx
index eb246e806ed..cf8804cff15 100644
--- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx
@@ -22,6 +22,7 @@ import { useFileMention } from "../../hooks/useFileMention"
import { useSlashCommand } from "../../hooks/useSlashCommand"
import { useGhostText } from "../../hooks/useGhostText"
import { useImageAttachments } from "../../hooks/useImageAttachments"
+import { convertToMentionPath } from "../../utils/path-mentions"
import { usePromptHistory } from "../../hooks/usePromptHistory"
import { WandSparkles } from "@kilocode/kilo-ui/lucide"
import { fileName, dirName, buildHighlightSegments, atEnd } from "./prompt-input-utils"
@@ -56,6 +57,25 @@ export const PromptInput: Component = (props) => {
const excluded = worktree ? new Set(["sessions"]) : undefined
const slash = useSlashCommand(vscode, excluded)
const imageAttach = useImageAttachments()
+ imageAttach.setFilePathDropHandler((paths) => {
+ const cwd = server.workspaceDirectory()
+ const resolved = paths.map((p) => convertToMentionPath(p, cwd))
+ const ref = textareaRef
+ if (!ref) return
+ const val = ref.value
+ const cursor = ref.selectionStart ?? val.length
+ const before = val.substring(0, cursor)
+ const after = val.substring(cursor)
+ const inserted = resolved.map((p) => `@${p}`).join(" ")
+ const result = before + inserted + " " + after
+ ref.value = result
+ setText(result)
+ mention.addPaths(resolved, cwd)
+ const pos = cursor + inserted.length + 1
+ ref.setSelectionRange(pos, pos)
+ ref.focus()
+ adjustHeight()
+ })
const history = usePromptHistory()
const sessionKey = () => session.currentSessionID() ?? "__new__"
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx
index cdf4bce13dd..0c581bb4b0d 100644
--- a/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx
@@ -173,6 +173,7 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
pick(value, true)
setStore("editing", false)
+ if (single()) submit()
}
const toggleCollapse = () => setStore("collapsed", !store.collapsed)
@@ -217,14 +218,14 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
onKeyDown={onRoot}
>
{/* Single unified header row — always visible */}
-
+
{summary()}
{question()?.question}
-
+
e.stopPropagation()}>
= (props) => {
const autoScroll = createAutoScroll({
working: running,
- overflowAnchor: "auto",
})
const openInTab = (e: MouseEvent) => {
diff --git a/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx
index e6c1c0a119c..0c570fdd7e2 100644
--- a/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx
@@ -2,11 +2,11 @@
* CloudSessionList component
* Displays cloud sessions from the Kilo cloud API, grouped by date.
* Supports filtering by repository (git URL) and search by title.
+ * Header/back button/import button are owned by the parent HistoryView.
*/
-import { Component, createSignal, createEffect, onMount, onCleanup, Show } from "solid-js"
+import { Component, Show, createSignal, createEffect, onMount, onCleanup } from "solid-js"
import { List } from "@kilocode/kilo-ui/list"
-import { Button } from "@kilocode/kilo-ui/button"
import { Checkbox } from "@kilocode/kilo-ui/checkbox"
import { useVSCode } from "../../context/vscode"
import { useLanguage } from "../../context/language"
@@ -125,12 +125,7 @@ const CloudSessionList: Component = (props) => {
}
return (
-
-
-
- {language.t("session.cloud.repoOnly") ?? "Only this repository"}
-
-
+
items={sessions()}
key={(s) => s.id}
@@ -138,7 +133,18 @@ const CloudSessionList: Component = (props) => {
onSelect={(s) => {
if (s) props.onSelectSession?.(s.id)
}}
- search={{ placeholder: language.t("session.search.placeholder"), autofocus: false }}
+ search={{
+ placeholder: language.t("session.search.placeholder"),
+ autofocus: false,
+ action:
+ gitUrl() !== null ? (
+
+
+ {language.t("session.cloud.repoOnly") ?? "Only this repository"}
+
+
+ ) : undefined,
+ }}
emptyMessage={
loading() ? (language.t("common.loading") ?? "Loading...") : (language.t("session.empty") ?? "No sessions")
}
@@ -157,9 +163,9 @@ const CloudSessionList: Component = (props) => {
-
+
{language.t("common.loadMore") ?? "Load more"}
-
+
diff --git a/packages/kilo-vscode/webview-ui/src/components/history/HistoryView.tsx b/packages/kilo-vscode/webview-ui/src/components/history/HistoryView.tsx
new file mode 100644
index 00000000000..3283d1a4ccc
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/components/history/HistoryView.tsx
@@ -0,0 +1,80 @@
+/**
+ * HistoryView component
+ * Unified panel for local and cloud session history.
+ * Contains a tab bar ("Local" | "Cloud") and an always-visible "Import session" button.
+ */
+
+import { Component, createSignal } from "solid-js"
+import { Button } from "@kilocode/kilo-ui/button"
+import { useDialog } from "@kilocode/kilo-ui/context/dialog"
+import { useLanguage } from "../../context/language"
+import { useSession } from "../../context/session"
+import { CloudImportDialog } from "../chat/CloudImportDialog"
+import SessionList from "./SessionList"
+import CloudSessionList from "./CloudSessionList"
+
+interface HistoryViewProps {
+ onSelectSession: (id: string) => void
+ onBack?: () => void
+}
+
+const HistoryView: Component
= (props) => {
+ const language = useLanguage()
+ const dialog = useDialog()
+ const session = useSession()
+ const [tab, setTab] = createSignal<"local" | "cloud">("local")
+
+ function openImport() {
+ dialog.show(() => (
+ {
+ selectCloudSession(id)
+ }}
+ />
+ ))
+ }
+
+ function selectCloudSession(id: string) {
+ session.selectCloudSession(id)
+ props.onBack?.()
+ }
+
+ return (
+
+
+
+
+ {tab() === "local" ? (
+
+ ) : (
+
+ )}
+
+
+ )
+}
+
+export default HistoryView
diff --git a/packages/kilo-vscode/webview-ui/src/components/history/SessionList.tsx b/packages/kilo-vscode/webview-ui/src/components/history/SessionList.tsx
index 402d7c7b815..e2f84cfde37 100644
--- a/packages/kilo-vscode/webview-ui/src/components/history/SessionList.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/history/SessionList.tsx
@@ -2,6 +2,7 @@
* SessionList component
* Displays all sessions grouped by date, with context menu for rename/delete.
* Uses kilo-ui List component for keyboard navigation and accessibility.
+ * Header/back button are owned by the parent HistoryView.
*/
import { Component, Show, createSignal, onMount, type JSX } from "solid-js"
diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css
index d3f96e688a2..bf56c52d0c4 100644
--- a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css
+++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css
@@ -214,6 +214,7 @@
display: flex;
flex-direction: column;
gap: 16px;
+ padding: 0 20px 20px;
}
.install-modal-section {
@@ -247,7 +248,7 @@
.install-modal-result {
text-align: center;
- padding: 12px 0;
+ padding: 12px 20px 20px;
}
.install-modal-success {
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AboutKiloCodeTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AboutKiloCodeTab.tsx
index cbf098b2a35..c531abe2de3 100644
--- a/packages/kilo-vscode/webview-ui/src/components/settings/AboutKiloCodeTab.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/AboutKiloCodeTab.tsx
@@ -1,7 +1,12 @@
-import { Component } from "solid-js"
+import { Component, createSignal, onCleanup } from "solid-js"
+import { Button } from "@kilocode/kilo-ui/button"
+import { Icon } from "@kilocode/kilo-ui/icon"
+import { showToast } from "@kilocode/kilo-ui/toast"
import { useLanguage } from "../../context/language"
import { useVSCode } from "../../context/vscode"
-import type { ConnectionState } from "../../types/messages"
+import { useConfig } from "../../context/config"
+import type { ConnectionState, ExtensionMessage } from "../../types/messages"
+import { buildExport, parseImport, MAX_IMPORT_SIZE } from "./settings-io"
export interface AboutKiloCodeTabProps {
port: number | null
@@ -13,11 +18,96 @@ export interface AboutKiloCodeTabProps {
const AboutKiloCodeTab: Component = (props) => {
const language = useLanguage()
const vscode = useVSCode()
+ const { updateConfig } = useConfig()
+ const [importing, setImporting] = createSignal(false)
+ const [exporting, setExporting] = createSignal(false)
+ let epoch = 0
const open = (url: string) => {
vscode.postMessage({ type: "openExternal", url })
}
+ // Listen for globalConfigLoaded response
+ const handler = (event: MessageEvent) => {
+ const msg = event.data as ExtensionMessage
+ if (msg.type !== "globalConfigLoaded" || !exporting()) return
+ setExporting(false)
+ epoch++
+ const payload = buildExport(msg.config)
+ const json = JSON.stringify(payload, null, 2)
+ const blob = new Blob([json], { type: "application/json" })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement("a")
+ a.href = url
+ a.download = "kilo-settings.json"
+ a.click()
+ URL.revokeObjectURL(url)
+ }
+ window.addEventListener("message", handler)
+ onCleanup(() => window.removeEventListener("message", handler))
+
+ // ----- Export -----
+ const handleExport = () => {
+ if (exporting()) return
+ setExporting(true)
+ const token = ++epoch
+ vscode.postMessage({ type: "requestGlobalConfig" })
+ setTimeout(() => {
+ if (epoch === token) setExporting(false)
+ }, 5000)
+ }
+
+ // ----- Import -----
+ const handleImport = () => {
+ if (importing()) return
+ const input = document.createElement("input")
+ input.type = "file"
+ input.accept = ".json"
+ input.style.display = "none"
+ input.addEventListener("change", () => {
+ const file = input.files?.[0]
+ if (!file) return
+ if (file.size > MAX_IMPORT_SIZE) {
+ showToast({ variant: "error", title: language.t("settings.aboutKiloCode.importSettings.tooLarge") })
+ return
+ }
+ setImporting(true)
+ const reader = new FileReader()
+ reader.onload = () => {
+ setImporting(false)
+ const text = reader.result as string
+ const result = parseImport(text)
+ if (!result.ok) {
+ const key =
+ result.error === "invalidJson"
+ ? "settings.aboutKiloCode.importSettings.invalidJson"
+ : "settings.aboutKiloCode.importSettings.invalidConfig"
+ showToast({ variant: "error", title: language.t(key) })
+ return
+ }
+ if (result.warning === "newerVersion") {
+ showToast({
+ variant: "default",
+ title: language.t("settings.aboutKiloCode.importSettings.newerVersion"),
+ })
+ }
+ updateConfig(result.config)
+ showToast({
+ variant: "success",
+ title: language.t("settings.aboutKiloCode.importSettings.success"),
+ })
+ }
+ reader.onerror = () => {
+ setImporting(false)
+ showToast({ variant: "error", title: language.t("settings.aboutKiloCode.importSettings.invalidJson") })
+ }
+ reader.readAsText(file)
+ })
+ document.body.appendChild(input)
+ input.click()
+ document.body.removeChild(input)
+ }
+
const getStatusColor = () => {
switch (props.connectionState) {
case "connected":
@@ -158,6 +248,31 @@ const AboutKiloCodeTab: Component = (props) => {
+ {/* Settings Transfer */}
+
+
{language.t("settings.aboutKiloCode.settingsTransfer.title")}
+
+ {language.t("settings.aboutKiloCode.settingsTransfer.description")}
+
+
+
+
+ {language.t("settings.aboutKiloCode.exportSettings")}
+
+
+
+ {language.t("settings.aboutKiloCode.importSettings")}
+
+
+
+
{/* legacy-migration start */}
{language.t("settings.aboutKiloCode.legacyMigration.title")}
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx
index 3a411594bf4..9245f7bd014 100644
--- a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx
@@ -10,7 +10,13 @@ import { useDialog } from "@kilocode/kilo-ui/context/dialog"
import { useConfig } from "../../context/config"
import { useSession } from "../../context/session"
import { useLanguage } from "../../context/language"
-import type { AgentConfig, AgentInfo, SkillInfo } from "../../types/messages"
+import type { AgentInfo, SkillInfo } from "../../types/messages"
+import ModeEditView from "./ModeEditView"
+import ModeCreateView from "./ModeCreateView"
+import McpEditView from "./McpEditView"
+import WorkflowsTab from "./agent-behaviour/WorkflowsTab"
+import { parseImport, MAX_IMPORT_SIZE } from "./mode-io"
+import type { ImportError } from "./mode-io"
type SubtabId = "agents" | "mcpServers" | "rules" | "workflows" | "skills"
@@ -34,20 +40,8 @@ interface SelectOption {
import SettingsRow from "./SettingsRow"
-const Placeholder: Component<{ text: string }> = (props) => (
-
-
- {useLanguage().t("settings.agentBehaviour.notImplemented")} {props.text}
-
-
-)
+// View states for the agents subtab
+type AgentView = "list" | "create" | "edit"
const AgentBehaviourTab: Component = () => {
const language = useLanguage()
@@ -55,11 +49,17 @@ const AgentBehaviourTab: Component = () => {
const session = useSession()
const dialog = useDialog()
const [activeSubtab, setActiveSubtab] = createSignal
("agents")
- const [selectedAgent, setSelectedAgent] = createSignal("")
const [newSkillPath, setNewSkillPath] = createSignal("")
const [newSkillUrl, setNewSkillUrl] = createSignal("")
const [newInstruction, setNewInstruction] = createSignal("")
+ // Agent view state
+ const [agentView, setAgentView] = createSignal("list")
+ const [editingAgent, setEditingAgent] = createSignal("")
+
+ // MCP view state
+ const [editingMcp, setEditingMcp] = createSignal("")
+
// Fetch skills whenever the skills subtab becomes active
createEffect(() => {
if (activeSubtab() === "skills") {
@@ -70,8 +70,8 @@ const AgentBehaviourTab: Component = () => {
const agentNames = createMemo(() => {
const names = session.agents().map((a) => a.name)
// Also include any agents from config that might not be in the agent list
- const configAgents = Object.keys(config().agent ?? {})
- for (const name of configAgents) {
+ const agents = Object.keys(config().agent ?? {})
+ for (const name of agents) {
if (!names.includes(name)) {
names.push(name)
}
@@ -84,30 +84,6 @@ const AgentBehaviourTab: Component = () => {
...agentNames().map((name) => ({ value: name, label: name })),
])
- const agentSelectorOptions = createMemo(() => [
- { value: "", label: language.t("settings.agentBehaviour.selectAgent") },
- ...agentNames().map((name) => ({ value: name, label: name })),
- ])
-
- const currentAgentConfig = createMemo(() => {
- const name = selectedAgent()
- if (!name) {
- return {}
- }
- return config().agent?.[name] ?? {}
- })
-
- const updateAgentConfig = (name: string, partial: Partial) => {
- const existing = config().agent ?? {}
- const current = existing[name] ?? {}
- updateConfig({
- agent: {
- ...existing,
- [name]: { ...current, ...partial },
- },
- })
- }
-
const instructions = () => config().instructions ?? []
const addInstruction = () => {
@@ -214,7 +190,14 @@ const AgentBehaviourTab: Component = () => {
// Delay optimistic removal until after dialog close animation (100ms)
// to prevent the reactive list re-render from firing click handlers
// on shifted list items while the dialog overlay is still present.
- setTimeout(() => session.removeMode(agent.name), 150)
+ setTimeout(() => {
+ session.removeMode(agent.name)
+ // If we were editing this mode, go back to list
+ if (editingAgent() === agent.name) {
+ setAgentView("list")
+ setEditingAgent("")
+ }
+ }, 150)
}}
>
{language.t("settings.agentBehaviour.removeMode.button")}
@@ -225,205 +208,242 @@ const AgentBehaviourTab: Component = () => {
))
}
- const renderAgentsSubtab = () => (
-
- {/* Default agent */}
-
-
- o.value === (config().default_agent ?? ""))}
- value={(o) => o.value}
- label={(o) => o.label}
- onSelect={(o) => {
- if (!o) return
- const next = o.value || undefined
- if (next === (config().default_agent ?? undefined)) return
- updateConfig({ default_agent: next })
- }}
- variant="secondary"
- size="small"
- triggerVariant="settings"
- />
-
-
+ const startEdit = (name: string) => {
+ setEditingAgent(name)
+ setAgentView("edit")
+ }
- {/* Available agents list */}
-
-
- {language.t("settings.agentBehaviour.availableAgents")}
-
-
-
- {(name, index) => {
- const agent = () => session.agents().find((a) => a.name === name)
- return (
-
-
-
{name}
-
-
- {agent()!.description}
-
-
-
-
- )
- }}
-
-
+ const back = () => {
+ setAgentView("list")
+ setEditingAgent("")
+ }
-
-
- {/* Model override */}
+ const [importError, setImportError] = createSignal("")
+
+ const errorKey = (tag: ImportError) => `settings.agentBehaviour.importMode.${tag}` as const
+
+ const importMode = (file: File) => {
+ setImportError("")
+ if (file.size > MAX_IMPORT_SIZE) {
+ setImportError(language.t(errorKey("tooLarge")))
+ return
+ }
+ const reader = new FileReader()
+ reader.onload = () => {
+ const result = parseImport(reader.result as string, agentNames())
+ if (!result.ok) {
+ setImportError(language.t(errorKey(result.error)))
+ return
+ }
+ const existing = config().agent ?? {}
+ updateConfig({ agent: { ...existing, [result.name]: result.config } })
+ setImportError("")
+ }
+ reader.readAsText(file)
+ }
+
+ const triggerImport = () => {
+ const input = document.createElement("input")
+ input.type = "file"
+ input.accept = ".json"
+ input.onchange = () => {
+ const file = input.files?.[0]
+ if (file) importMode(file)
+ }
+ input.click()
+ }
+
+ const renderAgentsSubtab = () => {
+ const view = agentView()
+ if (view === "create") return
+ if (view === "edit") return
+
+ return (
+
+ {/* Default agent */}
+
-
- updateAgentConfig(selectedAgent(), {
- model: val.trim() || undefined,
- })
- }
- />
-
-
- {/* System prompt */}
-
-
- updateAgentConfig(selectedAgent(), {
- prompt: val.trim() || undefined,
- })
- }
- />
-
-
- {/* Temperature */}
-
- {
- const parsed = parseFloat(val)
- updateAgentConfig(selectedAgent(), { temperature: isNaN(parsed) ? undefined : parsed })
- }}
- />
-
-
- {/* Top-p */}
-
- {
- const parsed = parseFloat(val)
- updateAgentConfig(selectedAgent(), { top_p: isNaN(parsed) ? undefined : parsed })
- }}
- />
-
-
- {/* Max steps */}
-
- {
- const parsed = parseInt(val, 10)
- updateAgentConfig(selectedAgent(), { steps: isNaN(parsed) ? undefined : parsed })
+ o.value === (config().default_agent ?? ""))}
+ value={(o) => o.value}
+ label={(o) => o.label}
+ onSelect={(o) => {
+ if (!o) return
+ const next = o.value || undefined
+ if (next === (config().default_agent ?? undefined)) return
+ updateConfig({ default_agent: next })
}}
+ variant="secondary"
+ size="small"
+ triggerVariant="settings"
/>
-
- {/* Available modes (non-native only, with remove button) */}
-
0}>
-
- {language.t("settings.agentBehaviour.availableModes")}
-
-
-
- {(agent, index) => (
+ {/* Available agents list header + create button */}
+
+
{language.t("settings.agentBehaviour.availableAgents")}
+
+
+ {language.t("settings.agentBehaviour.importMode")}
+
+ setAgentView("create")}>
+ {language.t("settings.agentBehaviour.createMode")}
+
+
+
+
+
+
+ {importError()}
+
+
+
+ {/* Agents list - clickable to edit */}
+ 0}
+ fallback={
+
-
-
- {agent.name}
-
-
-
- {agent.description}
-
-
-
-
{
- e.stopPropagation()
- confirmRemoveMode(agent)
- }}
- />
+ {language.t("settings.agentBehaviour.noModesFound")}
- )}
-
-
-
-
- )
+
+ }
+ >
+
+
+ {(name, index) => {
+ const agent = () => session.agents().find((a) => a.name === name)
+ const isCustom = () => !agent()?.native
+ const agentCfg = () => config().agent?.[name] ?? {}
+ const disabled = () => agentCfg().disable ?? false
+ const hidden = () => agentCfg().hidden ?? false
+ return (
+ startEdit(name)}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.background = "var(--bg-hover-base, var(--vscode-list-hoverBackground))"
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.background = "transparent"
+ }}
+ >
+
+
+
{name}
+
+
+ custom
+
+
+
+
+ {language.t("settings.agentBehaviour.badge.hidden")}
+
+
+
+
+ {language.t("settings.agentBehaviour.badge.disabled")}
+
+
+
+
+
+ {agent()!.description}
+
+
+
+
+
+ {
+ e.stopPropagation()
+ const a = agent()
+ if (a) confirmRemoveMode(a)
+ }}
+ />
+
+
+
+
+ )
+ }}
+
+
+
+
+ )
+ }
const confirmRemoveMcp = (name: string) => {
dialog.show(() => (
@@ -452,6 +472,24 @@ const AgentBehaviourTab: Component = () => {
const renderMcpSubtab = () => {
const mcpEntries = createMemo(() => Object.entries(config().mcp ?? {}))
+ const [expanded, setExpanded] = createSignal>({})
+
+ const toggle = (name: string) => {
+ setExpanded((prev) => ({ ...prev, [name]: !prev[name] }))
+ }
+
+ if (editingMcp()) {
+ return (
+ setEditingMcp("")}
+ onRemove={(name) => {
+ confirmRemoveMcp(name)
+ setEditingMcp("")
+ }}
+ />
+ )
+ }
return (
@@ -472,50 +510,147 @@ const AgentBehaviourTab: Component = () => {
>
- {([name, mcp], index) => (
-
-
-
{name}
+ {([name, mcp], index) => {
+ const open = () => expanded()[name] ?? false
+ const env = () => Object.entries(mcp.environment ?? mcp.env ?? {})
+ return (
+
+ {/* Header row */}
toggle(name)}
>
-
-
- command:{" "}
- {Array.isArray(mcp.command)
- ? mcp.command.join(" ")
- : `${mcp.command} ${(mcp.args ?? []).join(" ")}`}
-
-
-
- url: {mcp.url}
-
+
+
{
+ e.stopPropagation()
+ toggle(name)
+ }}
+ />
+ {name}
+
+ {mcp.url ? "remote" : "stdio"}
+
+
+
+ {
+ e.stopPropagation()
+ confirmRemoveMcp(name)
+ }}
+ />
+ {
+ e.stopPropagation()
+ setEditingMcp(name)
+ }}
+ />
+
+
+ {/* Expandable detail */}
+
+
+
+
+
+ {language.t("settings.agentBehaviour.mcpDetail.command")}:{" "}
+
+
+ {Array.isArray(mcp.command) ? mcp.command[0] : mcp.command}
+
+
+ 1) ||
+ (!Array.isArray(mcp.command) && mcp.args && mcp.args.length > 0)
+ }
+ >
+
+
+ {language.t("settings.agentBehaviour.mcpDetail.args")}:{" "}
+
+
+ {Array.isArray(mcp.command)
+ ? (mcp.command as string[]).slice(1).join(" ")
+ : (mcp.args ?? []).join(" ")}
+
+
+
+
+
+
+ URL:
+
+ {mcp.url}
+
+
+
+
0}>
+
+
+ {language.t("settings.agentBehaviour.mcpDetail.env")}:
+
+
+
+ {([key, val]) => (
+
+ {key}={val}
+
+ )}
+
+
+
+
+ {language.t("settings.agentBehaviour.mcpDetail.disabled")}
+
+
+
+
-
{
- e.stopPropagation()
- confirmRemoveMcp(name)
- }}
- />
-
- )}
+ )
+ }}
@@ -677,6 +812,18 @@ const AgentBehaviourTab: Component = () => {
const renderRulesSubtab = () => (
+ {/* Description */}
+
+ {language.t("settings.agentBehaviour.rules.description")}
+
+
{
case "rules":
return renderRulesSubtab()
case "workflows":
- return
+ return
case "skills":
return renderSkillsSubtab()
default:
@@ -780,7 +927,15 @@ const AgentBehaviourTab: Component = () => {
{(subtab) => (
setActiveSubtab(subtab.id)}
+ onClick={() => {
+ setActiveSubtab(subtab.id)
+ // Reset views when switching subtabs
+ if (subtab.id === "agents") {
+ setAgentView("list")
+ setEditingAgent("")
+ }
+ setEditingMcp("")
+ }}
style={{
padding: "8px 16px",
border: "none",
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx
index b51ea53d870..751dd2a1e6a 100644
--- a/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx
@@ -112,7 +112,6 @@ const TRAILING_TOOLS: ToolDef[] = [
* change, this map must be updated to match.
*/
const TOOL_DEFAULTS: Partial> = {
- bash: "ask",
doom_loop: "ask",
external_directory: "ask",
}
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx
index a1debefb010..6add99ed13f 100644
--- a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx
@@ -3,18 +3,33 @@ import { useDialog } from "@kilocode/kilo-ui/context/dialog"
import { Dialog } from "@kilocode/kilo-ui/dialog"
import { IconButton } from "@kilocode/kilo-ui/icon-button"
import { ProviderIcon } from "@kilocode/kilo-ui/provider-icon"
+import { Spinner } from "@kilocode/kilo-ui/spinner"
import { TextField } from "@kilocode/kilo-ui/text-field"
import { showToast } from "@kilocode/kilo-ui/toast"
-import { For, onCleanup } from "solid-js"
+import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import { useConfig } from "../../context/config"
import { useLanguage } from "../../context/language"
import { useProvider } from "../../context/provider"
import { useVSCode } from "../../context/vscode"
+import type { ExtensionMessage, ProviderConfig } from "../../types/messages"
import { createProviderAction } from "../../utils/provider-action"
const PROVIDER_ID = /^[a-z0-9][a-z0-9-_]*$/
const OPENAI_COMPATIBLE = "@ai-sdk/openai-compatible"
+const DEBOUNCE_MS = 500
+const SEARCH_DEBOUNCE_MS = 150
+
+/** Subsequence fuzzy match — "gpt4o" matches "gpt-4o-mini". */
+function fuzzy(query: string, target: string) {
+ const q = query.toLowerCase()
+ const t = target.toLowerCase()
+ let qi = 0
+ for (let ti = 0; ti < t.length && qi < q.length; ti++) {
+ if (t[ti] === q[qi]) qi++
+ }
+ return qi === q.length
+}
type Translator = ReturnType["t"]
@@ -46,11 +61,16 @@ type FormErrors = {
headers: Array<{ key?: string; value?: string }>
}
+type FetchedModel = { id: string; name: string }
+
type ValidateArgs = {
form: FormState
t: Translator
+ editing: boolean
disabledProviders: string[]
existingProviderIDs: Set
+ /** Preserved env vars from the existing provider config (edit mode only) */
+ existingEnv?: string[]
}
function validateCustomProvider(input: ValidateArgs) {
@@ -60,6 +80,8 @@ function validateCustomProvider(input: ValidateArgs) {
const apiKey = input.form.apiKey.trim()
const env = apiKey.match(/^\{env:([^}]+)\}$/)?.[1]?.trim()
+ // When editing and apiKey is empty, preserve existing env from the original config
+ const existingEnv = input.editing && !apiKey ? input.existingEnv : undefined
const key = apiKey && !env ? apiKey : undefined
const idError = !providerID
@@ -78,9 +100,11 @@ function validateCustomProvider(input: ValidateArgs) {
const disabled = input.disabledProviders.includes(providerID)
const existsError = idError
? undefined
- : input.existingProviderIDs.has(providerID) && !disabled
- ? input.t("provider.custom.error.providerID.exists")
- : undefined
+ : input.editing
+ ? undefined
+ : input.existingProviderIDs.has(providerID) && !disabled
+ ? input.t("provider.custom.error.providerID.exists")
+ : undefined
const seenModels = new Set()
const modelErrors = input.form.models.map((m) => {
@@ -149,7 +173,7 @@ function validateCustomProvider(input: ValidateArgs) {
config: {
npm: OPENAI_COMPATIBLE,
name,
- ...(env ? { env: [env] } : {}),
+ ...(env ? { env: [env] } : existingEnv ? { env: existingEnv } : {}),
options,
models,
},
@@ -157,8 +181,14 @@ function validateCustomProvider(input: ValidateArgs) {
}
}
-interface CustomProviderDialogProps {
+export interface CustomProviderDialogProps {
onBack?: () => void
+ /** When set, the dialog opens in edit mode with pre-filled values. */
+ existing?: {
+ providerID: string
+ name: string
+ config: ProviderConfig
+ }
}
const CustomProviderDialog = (props: CustomProviderDialogProps) => {
@@ -170,13 +200,32 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => {
const action = createProviderAction(vscode)
onCleanup(action.dispose)
+ const editing = () => !!props.existing
+
+ function initModels(): ModelRow[] {
+ const cfg = props.existing?.config
+ if (!cfg?.models || typeof cfg.models !== "object") return [{ id: "", name: "" }]
+ const entries = Object.entries(cfg.models)
+ if (entries.length === 0) return [{ id: "", name: "" }]
+ return entries.map(([id, m]) => ({ id, name: (m as { name?: string })?.name ?? id }))
+ }
+
+ function initHeaders(): HeaderRow[] {
+ const opts = props.existing?.config?.options as { headers?: Record } | undefined
+ const headers = opts?.headers
+ if (!headers || typeof headers !== "object") return [{ key: "", value: "" }]
+ const entries = Object.entries(headers)
+ if (entries.length === 0) return [{ key: "", value: "" }]
+ return entries.map(([key, value]) => ({ key, value }))
+ }
+
const [form, setForm] = createStore({
- providerID: "",
- name: "",
- baseURL: "",
+ providerID: props.existing?.providerID ?? "",
+ name: props.existing?.name ?? "",
+ baseURL: (props.existing?.config?.options as { baseURL?: string } | undefined)?.baseURL ?? "",
apiKey: "",
- models: [{ id: "", name: "" }],
- headers: [{ key: "", value: "" }],
+ models: initModels(),
+ headers: initHeaders(),
saving: false,
})
@@ -184,10 +233,193 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => {
providerID: undefined,
name: undefined,
baseURL: undefined,
- models: [{}],
- headers: [{}],
+ models: form.models.map(() => ({})),
+ headers: form.headers.map(() => ({})),
})
+ // ── Fetch models state ──────────────────────────────────────────────
+
+ const [fetching, setFetching] = createSignal(false)
+ const [fetchError, setFetchError] = createSignal()
+ const [fetchedModels, setFetchedModels] = createSignal()
+ const [selected, setSelected] = createSignal>(new Set())
+ const [fetchStatus, setFetchStatus] = createSignal()
+
+ // Search within fetched models
+ const [search, setSearch] = createSignal("")
+ const [debouncedSearch, setDebouncedSearch] = createSignal("")
+
+ createEffect(() => {
+ const q = search()
+ const timer = setTimeout(() => setDebouncedSearch(q), SEARCH_DEBOUNCE_MS)
+ onCleanup(() => clearTimeout(timer))
+ })
+
+ const filtered = createMemo(() => {
+ const models = fetchedModels()
+ if (!models) return []
+ const q = debouncedSearch()
+ if (!q) return models
+ return models.filter((m) => fuzzy(q, m.id) || fuzzy(q, m.name))
+ })
+
+ // ── Auto-fetch on debounce ──────────────────────────────────────────
+
+ // Dedicated signals for the URL and API key drive the auto-fetch effect.
+ // We avoid reading form.baseURL / form.apiKey inside createEffect because
+ // SolidJS store proxies track at the property level — any store write
+ // (including setForm("models", ...)) invalidates effects that read from
+ // the same store, causing unwanted re-runs that wipe the model picker.
+ const [fetchURL, setFetchURL] = createSignal(form.baseURL)
+ const [fetchKey, setFetchKey] = createSignal(form.apiKey)
+ let fetchVersion = 0
+
+ createEffect(() => {
+ const url = fetchURL()
+ const key = fetchKey()
+ void key // subscribe to key changes without using the value here
+
+ // Clear previous results whenever URL or key changes
+ setFetchedModels(undefined)
+ setFetchError(undefined)
+ setFetchStatus(undefined)
+ setSearch("")
+
+ if (!/^https?:\/\//.test(url.trim())) return
+
+ fetchVersion++
+ const version = fetchVersion
+ const timer = setTimeout(() => {
+ if (version === fetchVersion) doFetch()
+ }, DEBOUNCE_MS)
+ onCleanup(() => clearTimeout(timer))
+ })
+
+ // ── Core fetch logic ────────────────────────────────────────────────
+
+ function doFetch() {
+ // Snapshot all values from signals/store before entering async.
+ // This avoids reading the store proxy inside callbacks, which could
+ // subscribe to unrelated store properties and cause re-render loops.
+ const url = fetchURL().trim()
+ const raw = fetchKey().trim()
+ const env = raw.match(/^\{env:([^}]+)\}$/)?.[1]?.trim()
+ const apiKey = raw && !env ? raw : undefined
+ const existing = new Set(form.models.map((m) => m.id.trim()).filter(Boolean))
+
+ const hdrs = form.headers
+ .map((h) => ({ key: h.key.trim(), value: h.value.trim() }))
+ .filter((h) => !!h.key && !!h.value)
+ const headers = hdrs.length > 0 ? Object.fromEntries(hdrs.map((h) => [h.key, h.value])) : undefined
+
+ // Bump version so any in-flight response from a previous fetch is ignored
+ fetchVersion++
+ const version = fetchVersion
+
+ setFetching(true)
+ setFetchError(undefined)
+ setFetchedModels(undefined)
+ setFetchStatus(undefined)
+ setSearch("")
+
+ const rid = crypto.randomUUID()
+
+ const unsub = vscode.onMessage((msg: ExtensionMessage) => {
+ if (msg.type !== "customProviderModelsFetched") return
+ if (!("requestId" in msg) || msg.requestId !== rid) return
+ unsub()
+
+ // Stale response — a newer fetch was triggered while this one was in-flight
+ if (version !== fetchVersion) return
+
+ setFetching(false)
+
+ if (msg.error) {
+ setFetchError(msg.auth ? language.t("provider.custom.models.fetch.authError") : msg.error)
+ return
+ }
+
+ const models = msg.models ?? []
+ if (models.length === 0) {
+ setFetchError(language.t("provider.custom.models.fetch.empty"))
+ return
+ }
+
+ // Filter using the snapshot taken at fetch time
+ const fresh = models.filter((m) => !existing.has(m.id))
+
+ if (fresh.length === 0) {
+ setFetchStatus(language.t("provider.custom.models.fetch.allExist"))
+ return
+ }
+
+ // Pre-select all and show the picker
+ setSelected(new Set(fresh.map((m) => m.id)))
+ setFetchedModels(fresh)
+ })
+
+ vscode.postMessage({
+ type: "fetchCustomProviderModels",
+ requestId: rid,
+ baseURL: url,
+ apiKey,
+ headers,
+ })
+ }
+
+ // ── Model picker actions ────────────────────────────────────────────
+
+ function toggleModel(id: string) {
+ const next = new Set(selected())
+ if (next.has(id)) next.delete(id)
+ else next.add(id)
+ setSelected(next)
+ }
+
+ function selectAll() {
+ const next = new Set(selected())
+ for (const m of filtered()) next.add(m.id)
+ setSelected(next)
+ }
+
+ function deselectAll() {
+ const next = new Set(selected())
+ for (const m of filtered()) next.delete(m.id)
+ setSelected(next)
+ }
+
+ function count() {
+ return selected().size
+ }
+
+ function addSelected() {
+ const models = fetchedModels()
+ if (!models) return
+ const sel = selected()
+ const picked = models.filter((m) => sel.has(m.id))
+ if (picked.length === 0) return
+
+ // Replace the single empty row or append
+ const empty = form.models.length === 1 && !form.models[0].id.trim() && !form.models[0].name.trim()
+ const merged = empty ? picked : [...form.models, ...picked]
+
+ setForm("models", merged)
+ setErrors(
+ "models",
+ merged.map(() => ({})),
+ )
+ setFetchStatus(language.t("provider.custom.models.fetch.added", { count: String(picked.length) }))
+ setFetchedModels(undefined)
+ setSearch("")
+ }
+
+ function cancelFetch() {
+ setFetchedModels(undefined)
+ setSearch("")
+ }
+
+ // ── Form helpers ────────────────────────────────────────────────────
+
function goBack() {
if (props.onBack) {
props.onBack()
@@ -222,8 +454,10 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => {
const output = validateCustomProvider({
form,
t: language.t,
+ editing: editing(),
disabledProviders: config().disabled_providers ?? [],
existingProviderIDs: new Set(Object.keys(provider.providers())),
+ existingEnv: props.existing?.config?.env,
})
setErrors(output.errors)
return output.result
@@ -264,6 +498,8 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => {
)
}
+ // ── Render ──────────────────────────────────────────────────────────
+
return (
{
- {language.t("provider.custom.title")}
+ {editing() ? language.t("provider.custom.edit.title") : language.t("provider.custom.title")}
@@ -317,7 +553,7 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => {
{
onChange={(v) => setForm("providerID", v)}
validationState={errors.providerID ? "invalid" : undefined}
error={errors.providerID}
+ disabled={editing()}
/>
{
label={language.t("provider.custom.field.baseURL.label")}
placeholder={language.t("provider.custom.field.baseURL.placeholder")}
value={form.baseURL}
- onChange={(v) => setForm("baseURL", v)}
+ onChange={(v) => {
+ setForm("baseURL", v)
+ setFetchURL(v)
+ }}
validationState={errors.baseURL ? "invalid" : undefined}
error={errors.baseURL}
/>
@@ -348,15 +588,23 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => {
placeholder={language.t("provider.custom.field.apiKey.placeholder")}
description={language.t("provider.custom.field.apiKey.description")}
value={form.apiKey}
- onChange={(v) => setForm("apiKey", v)}
+ onChange={(v) => {
+ setForm("apiKey", v)
+ setFetchKey(v)
+ }}
/>
{/* Models */}
-
- {language.t("provider.custom.models.label")}
-
+
+
+ {language.t("provider.custom.models.label")}
+
+
+
+
+
{(m, i) => (
@@ -397,6 +645,130 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => {
{language.t("provider.custom.models.add")}
+
+ {/* Fetch error */}
+
+ {(err) => (
+ {err()}
+ )}
+
+
+ {/* Fetch status (success/info messages) */}
+
+ {(status) => (
+
+ {status()}
+
+ )}
+
+
+ {/* Model selection picker */}
+
+ {(models) => (
+
+ {/* Header with count + toggle */}
+
+
+
+ {language.t("provider.custom.models.fetch.showing", {
+ shown: String(filtered().length),
+ total: String(models().length),
+ })}
+
+
+
+
+ {language.t("provider.custom.models.fetch.selectAll")}
+
+
+ {language.t("provider.custom.models.fetch.deselectAll")}
+
+
+
+
+ {/* Search */}
+
10}>
+
+
+
+ {/* Model list */}
+
+
+ {(m) => (
+
+ toggleModel(m.id)}
+ style={{ cursor: "pointer" }}
+ />
+ {m.id}
+
+ )}
+
+
+
+ {/* Actions */}
+
+
+ {language.t("provider.custom.models.fetch.add", { count: String(count()) })}
+
+
+ {language.t("common.cancel")}
+
+
+
+ )}
+
{/* Headers */}
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/LanguageTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/LanguageTab.tsx
index 394de4096ab..0ef3311b186 100644
--- a/packages/kilo-vscode/webview-ui/src/components/settings/LanguageTab.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/LanguageTab.tsx
@@ -2,23 +2,25 @@ import { Component } from "solid-js"
import { Select } from "@kilocode/kilo-ui/select"
import { useLanguage, LOCALES, LOCALE_LABELS, type Locale } from "../../context/language"
-const options = ["", ...LOCALES] as const
-type Option = "" | Locale
+const AUTO = "auto"
+const options = [AUTO, ...LOCALES] as const
+type Option = typeof AUTO | Locale
const LanguageTab: Component = () => {
const language = useLanguage()
+ const current = () => language.userOverride() || AUTO
return (
{language.t("settings.language.description")}
(opt === "" ? language.t("settings.language.auto") : LOCALE_LABELS[opt])}
+ current={current()}
+ label={(opt: Option) => (opt === AUTO ? language.t("settings.language.auto") : LOCALE_LABELS[opt])}
value={(opt: Option) => opt}
onSelect={(opt) => {
if (opt !== undefined) {
- language.setLocale(opt as Locale | "")
+ language.setLocale(opt === AUTO ? "" : (opt as Locale))
}
}}
variant="secondary"
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/McpEditView.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/McpEditView.tsx
new file mode 100644
index 00000000000..634794307a0
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/McpEditView.tsx
@@ -0,0 +1,221 @@
+import { Component, Show, createMemo, createSignal, For } from "solid-js"
+import { TextField } from "@kilocode/kilo-ui/text-field"
+import { Card } from "@kilocode/kilo-ui/card"
+import { Button } from "@kilocode/kilo-ui/button"
+import { IconButton } from "@kilocode/kilo-ui/icon-button"
+
+import { useConfig } from "../../context/config"
+import { useLanguage } from "../../context/language"
+import type { McpConfig } from "../../types/messages"
+import SettingsRow from "./SettingsRow"
+
+interface Props {
+ name: string
+ onBack: () => void
+ onRemove: (name: string) => void
+}
+
+const McpEditView: Component = (props) => {
+ const language = useLanguage()
+ const { config, updateConfig } = useConfig()
+
+ const cfg = createMemo(() => config().mcp?.[props.name] ?? {})
+
+ const [envKey, setEnvKey] = createSignal("")
+ const [envVal, setEnvVal] = createSignal("")
+
+ const update = (partial: Partial) => {
+ const existing = config().mcp ?? {}
+ const current = existing[props.name] ?? {}
+ updateConfig({
+ mcp: { ...existing, [props.name]: { ...current, ...partial } },
+ })
+ }
+
+ const transport = () => cfg().type ?? (cfg().url ? "remote" : "local")
+
+ const cmd = () => {
+ const c = cfg().command
+ if (Array.isArray(c)) return c[0] ?? ""
+ return c ?? ""
+ }
+
+ const args = () => {
+ const c = cfg().command
+ if (Array.isArray(c)) return c.slice(1).join("\n")
+ return ""
+ }
+
+ const env = createMemo(() => Object.entries(cfg().environment ?? cfg().env ?? {}))
+
+ const addEnv = () => {
+ const key = envKey().trim()
+ const val = envVal().trim()
+ if (!key) return
+ const existing = cfg().environment ?? cfg().env ?? {}
+ update({ environment: { ...existing, [key]: val } })
+ setEnvKey("")
+ setEnvVal("")
+ }
+
+ const removeEnv = (key: string) => {
+ const existing = { ...(cfg().environment ?? cfg().env ?? {}) }
+ delete existing[key]
+ update({ environment: existing })
+ }
+
+ return (
+
+
+
+
+
+ {language.t("settings.agentBehaviour.editMcp")} — {props.name}
+
+
+
props.onRemove(props.name)} />
+
+
+ {/* Transport info */}
+
+
+ {transport() === "local"
+ ? language.t("settings.agentBehaviour.editMcp.transportLocal")
+ : language.t("settings.agentBehaviour.editMcp.transportRemote")}
+
+
+
+ {/* Command / URL */}
+
+
+
+ {language.t("settings.agentBehaviour.addMcp.command")}
+
+ {
+ const existing = cfg().command
+ const rest = Array.isArray(existing) ? existing.slice(1) : []
+ update({ command: [val.trim(), ...rest] })
+ }}
+ />
+
+
+
+ {language.t("settings.agentBehaviour.addMcp.args")}
+
+
+ {language.t("settings.agentBehaviour.addMcp.args.help")}
+
+ {
+ const parts = val.split(/\n/).filter(Boolean)
+ update({ command: [cmd(), ...parts] })
+ }}
+ />
+
+
+
+
+
+
+ {language.t("settings.agentBehaviour.addMcp.url")}
+
+ update({ url: val.trim() || undefined })}
+ />
+
+
+
+ {/* Environment variables (local servers only) */}
+
+
+
+ {language.t("settings.agentBehaviour.editMcp.env")}
+
+
+ {language.t("settings.agentBehaviour.editMcp.env.help")}
+
+
+ 0 ? "1px solid var(--border-weak-base)" : "none",
+ }}
+ >
+
+ setEnvKey(val)} />
+
+
+ setEnvVal(val)}
+ onKeyDown={(e: KeyboardEvent) => {
+ if (e.key === "Enter") addEnv()
+ }}
+ />
+
+
+ {language.t("common.add")}
+
+
+
+
+ {([key, val], index) => (
+
+
+ {key}={val}
+
+ removeEnv(key)} />
+
+ )}
+
+
+
+
+
+
+ {language.t("settings.agentBehaviour.editMode.back")}
+
+
+
+ )
+}
+
+export default McpEditView
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ModeCreateView.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ModeCreateView.tsx
new file mode 100644
index 00000000000..25f78820757
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/ModeCreateView.tsx
@@ -0,0 +1,147 @@
+import { Component, Show, createSignal } from "solid-js"
+import { TextField } from "@kilocode/kilo-ui/text-field"
+import { Card } from "@kilocode/kilo-ui/card"
+import { Button } from "@kilocode/kilo-ui/button"
+import { IconButton } from "@kilocode/kilo-ui/icon-button"
+
+import { useConfig } from "../../context/config"
+import { useLanguage } from "../../context/language"
+import type { AgentConfig } from "../../types/messages"
+import SettingsRow from "./SettingsRow"
+
+interface Props {
+ /** Names already taken (used for uniqueness validation). */
+ taken: string[]
+ onBack: () => void
+}
+
+const ModeCreateView: Component = (props) => {
+ const language = useLanguage()
+ const { config, updateConfig } = useConfig()
+
+ const [name, setName] = createSignal("")
+ const [description, setDescription] = createSignal("")
+ const [prompt, setPrompt] = createSignal("")
+ const [error, setError] = createSignal("")
+
+ const validate = (val: string): string => {
+ if (!val.trim()) return language.t("settings.agentBehaviour.createMode.nameRequired")
+ if (!/^[a-z][a-z0-9-]*$/.test(val.trim())) return language.t("settings.agentBehaviour.createMode.nameInvalid")
+ if (props.taken.includes(val.trim())) return language.t("settings.agentBehaviour.createMode.nameTaken")
+ return ""
+ }
+
+ const reset = () => {
+ setName("")
+ setDescription("")
+ setPrompt("")
+ setError("")
+ }
+
+ const cancel = () => {
+ reset()
+ props.onBack()
+ }
+
+ const submit = () => {
+ const slug = name().trim()
+ const msg = validate(slug)
+ if (msg) {
+ setError(msg)
+ return
+ }
+ const existing = config().agent ?? {}
+ const partial: Partial = {
+ mode: "primary",
+ description: description().trim() || undefined,
+ prompt: prompt().trim() || undefined,
+ }
+ updateConfig({
+ agent: { ...existing, [slug]: { ...(existing[slug] ?? {}), ...partial } },
+ })
+ reset()
+ props.onBack()
+ }
+
+ return (
+
+
+
+
+ {language.t("settings.agentBehaviour.createMode")}
+
+
+
+ {/* Name */}
+
+
+ {
+ setName(val)
+ setError("")
+ }}
+ />
+
+
+ {error()}
+
+
+
+
+
+ {/* Description (full-width) */}
+
+
+ {language.t("settings.agentBehaviour.createMode.description")}
+
+
+ {language.t("settings.agentBehaviour.createMode.description.help")}
+
+ setDescription(val)}
+ />
+
+
+ {/* Prompt (full-width, auto-resizing) */}
+
+
+ {language.t("settings.agentBehaviour.createMode.prompt")}
+
+
+ {language.t("settings.agentBehaviour.createMode.prompt.help")}
+
+ setPrompt(val)}
+ />
+
+
+
+
+ {language.t("settings.agentBehaviour.createMode.cancel")}
+
+
+ {language.t("settings.agentBehaviour.createMode.button")}
+
+
+
+ )
+}
+
+export default ModeCreateView
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ModeEditView.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ModeEditView.tsx
new file mode 100644
index 00000000000..3f1a60ac162
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/ModeEditView.tsx
@@ -0,0 +1,242 @@
+import { Component, Show, createMemo } from "solid-js"
+import { TextField } from "@kilocode/kilo-ui/text-field"
+import { Switch } from "@kilocode/kilo-ui/switch"
+import { Card } from "@kilocode/kilo-ui/card"
+import { Button } from "@kilocode/kilo-ui/button"
+import { IconButton } from "@kilocode/kilo-ui/icon-button"
+
+import { useConfig } from "../../context/config"
+import { useSession } from "../../context/session"
+import { useLanguage } from "../../context/language"
+import type { AgentConfig, AgentInfo } from "../../types/messages"
+import SettingsRow from "./SettingsRow"
+import { buildExport } from "./mode-io"
+
+interface Props {
+ name: string
+ onBack: () => void
+ onRemove: (agent: AgentInfo) => void
+}
+
+const ModeEditView: Component = (props) => {
+ const language = useLanguage()
+ const { config, updateConfig } = useConfig()
+ const session = useSession()
+
+ // agent() may be undefined for modes that only exist in the config draft (just
+ // created, not yet saved). This is fine — native defaults to false (correct for
+ // custom modes) and all fields read from cfg() which comes from config context.
+ const agent = () => session.agents().find((a) => a.name === props.name)
+ const native = () => agent()?.native ?? false
+
+ const cfg = createMemo(() => config().agent?.[props.name] ?? {})
+
+ const update = (partial: Partial) => {
+ const existing = config().agent ?? {}
+ const current = existing[props.name] ?? {}
+ updateConfig({
+ agent: {
+ ...existing,
+ [props.name]: { ...current, ...partial },
+ },
+ })
+ }
+
+ const exportMode = () => {
+ const data = buildExport(props.name, cfg())
+ const json = JSON.stringify(data, null, 2)
+ const blob = new Blob([json], { type: "application/json" })
+ const url = URL.createObjectURL(blob)
+ const anchor = document.createElement("a")
+ anchor.href = url
+ anchor.download = `${props.name}.agent.json`
+ anchor.click()
+ URL.revokeObjectURL(url)
+ }
+
+ return (
+
+
+
+
+
+ {language.t("settings.agentBehaviour.editMode")} — {props.name}
+
+
+
+
+
+ {
+ const a = agent()
+ if (a) props.onRemove(a)
+ }}
+ />
+
+
+
+
+
+
+
+ {language.t("settings.agentBehaviour.editMode.native")}
+
+
+
+
+ {/* Description (full-width, custom modes only) */}
+
+
+
+ {language.t("settings.agentBehaviour.editMode.description")}
+
+ update({ description: val || undefined })}
+ />
+
+
+
+ {/* Prompt (full-width, auto-resizing) */}
+
+
+ {native()
+ ? language.t("settings.agentBehaviour.editMode.promptOverride")
+ : language.t("settings.agentBehaviour.editMode.prompt")}
+
+ update({ prompt: val || undefined })}
+ />
+
+
+ {/* Config overrides (wider inputs) */}
+
+
+ update({ model: val || undefined })}
+ />
+
+
+
+ {
+ const parsed = parseFloat(val)
+ update({ temperature: isNaN(parsed) ? undefined : parsed })
+ }}
+ />
+
+
+
+ {
+ const parsed = parseFloat(val)
+ update({ top_p: isNaN(parsed) ? undefined : parsed })
+ }}
+ />
+
+
+
+ {
+ const parsed = parseInt(val, 10)
+ update({ steps: isNaN(parsed) ? undefined : parsed })
+ }}
+ />
+
+
+
+ {
+ update({ hidden: val || undefined })
+ // Clear default_agent if hiding the current default
+ if (val && config().default_agent === props.name) {
+ updateConfig({ default_agent: undefined })
+ }
+ }}
+ hideLabel
+ >
+ {language.t("settings.agentBehaviour.hidden.title")}
+
+
+
+
+ {
+ update({ disable: val || undefined })
+ // Clear default_agent if disabling the current default
+ if (val && config().default_agent === props.name) {
+ updateConfig({ default_agent: undefined })
+ }
+ }}
+ hideLabel
+ >
+ {language.t("settings.agentBehaviour.disable.title")}
+
+
+
+
+
+
+ {language.t("settings.agentBehaviour.editMode.back")}
+
+
+
+ )
+}
+
+export default ModeEditView
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx
index 8e0afb12fd6..ee0eb3f56ac 100644
--- a/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx
@@ -60,6 +60,7 @@ const ModelsTab: Component = () => {
placement="bottom-start"
allowClear
clearLabel={language.t("settings.providers.notSet")}
+ includeAutoSmall
/>
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/PromptsTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/PromptsTab.tsx
deleted file mode 100644
index 1502281d2d4..00000000000
--- a/packages/kilo-vscode/webview-ui/src/components/settings/PromptsTab.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Component } from "solid-js"
-import { useLanguage } from "../../context/language"
-
-const PromptsTab: Component = () => {
- const language = useLanguage()
-
- return (
-
-
-
- {language.t("settings.notImplemented")} {" "}
- {language.t("settings.notImplemented.description")}
-
-
-
- )
-}
-
-export default PromptsTab
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx
index 4f4408b7735..53738608898 100644
--- a/packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx
@@ -17,7 +17,7 @@ import ProviderConnectDialog from "./ProviderConnectDialog"
import ProviderSelectDialog from "./ProviderSelectDialog"
import { CUSTOM_PROVIDER_ID, isPopularProvider, providerIcon, providerNoteKey, sortProviders } from "./provider-catalog"
import { visibleConnectedIds } from "./provider-visibility"
-import { KILO_PROVIDER_ID } from "../../../../src/shared/provider-model"
+import { KILO_PROVIDER_ID, CUSTOM_PROVIDER_PACKAGE } from "../../../../src/shared/provider-model"
import { createProviderAction } from "../../utils/provider-action"
type ProviderSource = "env" | "api" | "config" | "custom"
@@ -83,6 +83,17 @@ const ProvidersTab: Component = () => {
return source(item) !== "env"
}
+ function isCustom(item: Provider) {
+ const cfg = config().provider?.[item.id]
+ return cfg?.npm === CUSTOM_PROVIDER_PACKAGE
+ }
+
+ function editProvider(item: Provider) {
+ const cfg = config().provider?.[item.id]
+ if (!cfg) return
+ dialog.show(() => )
+ }
+
function disconnect(providerID: string, name: string) {
action.send(
{ type: "disconnectProvider", providerID },
@@ -203,9 +214,16 @@ const ProvidersTab: Component = () => {
}
>
- disconnect(item.id, item.name)}>
- {language.t("common.disconnect")}
-
+
+
+ editProvider(item)}>
+ {language.t("provider.custom.edit.title")}
+
+
+ disconnect(item.id, item.name)}>
+ {language.t("common.disconnect")}
+
+
)}
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/Settings.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/Settings.tsx
index aad28a30192..7ce196444f4 100644
--- a/packages/kilo-vscode/webview-ui/src/components/settings/Settings.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/Settings.tsx
@@ -17,8 +17,7 @@ import DisplayTab from "./DisplayTab"
import AutocompleteTab from "./AutocompleteTab"
import NotificationsTab from "./NotificationsTab"
import ContextTab from "./ContextTab"
-import TerminalTab from "./TerminalTab"
-import PromptsTab from "./PromptsTab"
+
import ExperimentalTab from "./ExperimentalTab"
import LanguageTab from "./LanguageTab"
import AboutKiloCodeTab from "./AboutKiloCodeTab"
@@ -36,7 +35,7 @@ const Settings: Component = (props) => {
const vscode = useVSCode()
const { isDirty, saveConfig, discardConfig } = useConfig()
const session = useSession()
- const [active, setActive] = createSignal(props.tab ?? "providers")
+ const [active, setActive] = createSignal(props.tab ?? "models")
const busyCount = () => Object.values(session.allStatusMap()).filter((s) => s.type === "busy").length
@@ -138,14 +137,7 @@ const Settings: Component = (props) => {
{language.t("settings.context.title")}
-
-
- {language.t("settings.terminal.title")}
-
-
-
- {language.t("settings.prompts.title")}
-
+
{language.t("settings.experimental.title")}
@@ -200,14 +192,7 @@ const Settings: Component = (props) => {
{language.t("settings.context.title")}
-
- {language.t("settings.terminal.title")}
-
-
-
- {language.t("settings.prompts.title")}
-
-
+
{language.t("settings.experimental.title")}
@@ -227,29 +212,20 @@ const Settings: Component = (props) => {
- {/* Save bar — visible when there are unsaved config changes */}
-
-
-
- {language.t("settings.saveBar.unsavedChanges")}
-
-
- {language.t("settings.saveBar.discard")}
-
-
- {language.t("settings.saveBar.save")}
-
-
-
+ {/* Save bar — slides in when there are unsaved config changes */}
+
+ {language.t("settings.saveBar.unsavedChanges")}
+
+ {language.t("settings.saveBar.discard")}
+
+
+ {language.t("settings.saveBar.save")}
+
+
)
}
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/TerminalTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/TerminalTab.tsx
deleted file mode 100644
index 92b64625509..00000000000
--- a/packages/kilo-vscode/webview-ui/src/components/settings/TerminalTab.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Component } from "solid-js"
-import { useLanguage } from "../../context/language"
-
-const TerminalTab: Component = () => {
- const language = useLanguage()
-
- return (
-
-
-
- {language.t("settings.notImplemented")} {" "}
- {language.t("settings.notImplemented.description")}
-
-
-
- )
-}
-
-export default TerminalTab
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/agent-behaviour/WorkflowsTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/agent-behaviour/WorkflowsTab.tsx
new file mode 100644
index 00000000000..4d903952794
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/agent-behaviour/WorkflowsTab.tsx
@@ -0,0 +1,151 @@
+import { Component, createMemo, createSignal, For, Show } from "solid-js"
+import { Card } from "@kilocode/kilo-ui/card"
+import { IconButton } from "@kilocode/kilo-ui/icon-button"
+
+import { useConfig } from "../../../context/config"
+import { useLanguage } from "../../../context/language"
+
+const WorkflowsTab: Component = () => {
+ const language = useLanguage()
+ const { config } = useConfig()
+
+ const cmds = createMemo(() => Object.entries(config().command ?? {}))
+ const [expanded, setExpanded] = createSignal>({})
+
+ const toggle = (name: string) => {
+ setExpanded((prev) => ({ ...prev, [name]: !prev[name] }))
+ }
+
+ return (
+
+ {/* Description */}
+
+ {language.t("settings.agentBehaviour.workflows.description")}
+
+
+
0}
+ fallback={
+
+
+ {language.t("settings.agentBehaviour.workflows.empty")}
+
+
+ }
+ >
+
+
+ {([name, cmd], index) => {
+ const open = () => expanded()[name] ?? false
+ return (
+
+ {/* Header row */}
+
toggle(name)}
+ >
+
+ {
+ e.stopPropagation()
+ toggle(name)
+ }}
+ />
+
+ /{name}
+
+
+
+ {cmd.description}
+
+
+
+
+
+ {/* Expandable detail */}
+
+
+
+
+
+ {language.t("settings.agentBehaviour.workflows.detail.description")}:{" "}
+
+ {cmd.description}
+
+
+
+
+
+ {language.t("settings.agentBehaviour.workflows.detail.template")}:{" "}
+
+
+ {cmd.template}
+
+
+
+
+
+
+ )
+ }}
+
+
+
+
+ )
+}
+
+export default WorkflowsTab
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/mode-io.ts b/packages/kilo-vscode/webview-ui/src/components/settings/mode-io.ts
new file mode 100644
index 00000000000..79428b38646
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/mode-io.ts
@@ -0,0 +1,95 @@
+import type { AgentConfig, PermissionConfig } from "../../types/messages"
+
+/** Maximum import file size in bytes (1 MB). */
+export const MAX_IMPORT_SIZE = 1_048_576
+
+const NAME_RE = /^[a-z][a-z0-9-]*$/
+const MODES = ["subagent", "primary", "all"] as const
+const LEVELS = new Set(["allow", "ask", "deny"])
+
+export type ImportError = "invalidJson" | "invalidName" | "nameTaken" | "tooLarge"
+
+export type ImportResult = { ok: true; name: string; config: AgentConfig } | { ok: false; error: ImportError }
+
+/** Check if a value is a valid permission level string. */
+function isLevel(v: unknown): boolean {
+ return typeof v === "string" && LEVELS.has(v)
+}
+
+/** Validate and extract a permission map, dropping unknown entries. */
+function parsePermission(raw: unknown): PermissionConfig | undefined {
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return undefined
+ const out: PermissionConfig = {}
+ let count = 0
+ for (const [key, val] of Object.entries(raw)) {
+ if (isLevel(val)) {
+ out[key] = val as PermissionConfig[string]
+ count++
+ } else if (typeof val === "object" && val !== null && !Array.isArray(val)) {
+ // Nested per-pattern rules like { "*": "ask", "uname": "allow" }
+ const nested: Record = {}
+ let valid = 0
+ for (const [pat, lev] of Object.entries(val)) {
+ if (isLevel(lev)) {
+ nested[pat] = lev as string
+ valid++
+ }
+ }
+ if (valid > 0) {
+ out[key] = nested as PermissionConfig[string]
+ count++
+ }
+ }
+ }
+ return count > 0 ? out : undefined
+}
+
+/**
+ * Parse a raw JSON string into a validated agent name + config.
+ * Returns an error tag (matching the i18n key suffix) on failure.
+ */
+export function parseImport(json: string, taken: string[]): ImportResult {
+ let data: unknown
+ try {
+ data = JSON.parse(json)
+ } catch {
+ return { ok: false, error: "invalidJson" }
+ }
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
+ return { ok: false, error: "invalidJson" }
+ }
+ const obj = data as Record
+
+ const name = typeof obj.name === "string" ? obj.name.trim() : ""
+ if (!name || !NAME_RE.test(name)) {
+ return { ok: false, error: "invalidName" }
+ }
+ if (taken.includes(name)) {
+ return { ok: false, error: "nameTaken" }
+ }
+
+ const partial: Partial = {}
+ if (typeof obj.description === "string") partial.description = obj.description
+ if (typeof obj.prompt === "string") partial.prompt = obj.prompt
+ if (typeof obj.model === "string") partial.model = obj.model
+ if (typeof obj.mode === "string" && (MODES as readonly string[]).includes(obj.mode))
+ partial.mode = obj.mode as AgentConfig["mode"]
+ if (typeof obj.temperature === "number") partial.temperature = obj.temperature
+ if (typeof obj.top_p === "number") partial.top_p = obj.top_p
+ if (typeof obj.steps === "number") partial.steps = obj.steps
+ const perms = parsePermission(obj.permission)
+ if (perms) partial.permission = perms
+
+ return {
+ ok: true,
+ name,
+ config: { ...partial, mode: partial.mode ?? "primary" },
+ }
+}
+
+/**
+ * Build the JSON-serialisable export payload for a mode.
+ */
+export function buildExport(name: string, cfg: AgentConfig): Record {
+ return { name, ...cfg }
+}
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/settings-io.ts b/packages/kilo-vscode/webview-ui/src/components/settings/settings-io.ts
new file mode 100644
index 00000000000..ea46f235d27
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/settings-io.ts
@@ -0,0 +1,130 @@
+import type { Config } from "../../types/messages"
+import { deepMerge } from "../../utils/config-utils"
+
+/** Maximum import file size in bytes (1 MB). */
+export const MAX_IMPORT_SIZE = 1_048_576
+
+/** Current export format version. */
+export const META_VERSION = 1
+
+/** Top-level keys recognised as valid Config fields. */
+export const KNOWN_KEYS: ReadonlyArray = [
+ "permission",
+ "model",
+ "small_model",
+ "default_agent",
+ "agent",
+ "provider",
+ "disabled_providers",
+ "enabled_providers",
+ "mcp",
+ "command",
+ "instructions",
+ "skills",
+ "snapshot",
+ "share",
+ "username",
+ "watcher",
+ "formatter",
+ "lsp",
+ "compaction",
+ "tools",
+ "layout",
+ "experimental",
+]
+
+export type ImportError = "invalidJson" | "invalidConfig" | "tooLarge"
+export type ImportWarning = "newerVersion"
+
+export type ImportResult = { ok: true; config: Config; warning?: ImportWarning } | { ok: false; error: ImportError }
+
+interface ExportMeta {
+ version: number
+ exportedAt: string
+}
+
+// ---------------------------------------------------------------------------
+// Export
+// ---------------------------------------------------------------------------
+
+/**
+ * Build a JSON-serialisable export payload from the current config.
+ * All fields are included as-is so the export is a complete snapshot
+ * that can be imported on another instance without re-entering secrets.
+ */
+export function buildExport(cfg: Config): Record {
+ const meta: ExportMeta = {
+ version: META_VERSION,
+ exportedAt: new Date().toISOString(),
+ }
+
+ const out: Record = { _meta: meta }
+
+ for (const [key, value] of Object.entries(cfg)) {
+ if (value === undefined || value === null) continue
+ out[key] = value
+ }
+
+ return out
+}
+
+// ---------------------------------------------------------------------------
+// Import
+// ---------------------------------------------------------------------------
+
+/**
+ * Parse a raw JSON string into a validated Config.
+ * Unknown keys and `_meta` are stripped. Returns an error tag on failure
+ * or a warning when the file was exported from a newer version.
+ */
+export function parseImport(json: string): ImportResult {
+ let data: unknown
+ try {
+ data = JSON.parse(json)
+ } catch {
+ return { ok: false, error: "invalidJson" }
+ }
+
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
+ return { ok: false, error: "invalidJson" }
+ }
+
+ const obj = data as Record
+
+ // Check for newer version warning
+ let warning: ImportWarning | undefined
+ const meta = obj._meta
+ if (typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
+ const version = (meta as Record).version
+ if (typeof version === "number" && version > META_VERSION) {
+ warning = "newerVersion"
+ }
+ }
+
+ // Keep only known config keys
+ const config: Record = {}
+ for (const key of KNOWN_KEYS) {
+ if (key in obj && obj[key] !== undefined) {
+ config[key] = obj[key]
+ }
+ }
+
+ // Must have at least one known key
+ if (Object.keys(config).length === 0) {
+ return { ok: false, error: "invalidConfig" }
+ }
+
+ return warning ? { ok: true, config: config as Config, warning } : { ok: true, config: config as Config }
+}
+
+// ---------------------------------------------------------------------------
+// Merge
+// ---------------------------------------------------------------------------
+
+/**
+ * Deep-merge imported config on top of existing config.
+ * Imported values take precedence; existing values not in import are preserved.
+ */
+export function mergeConfig(existing: Config, imported: Config): Config {
+ return deepMerge(existing, imported)
+}
diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx
index f91f92b9fd5..422d0b4801a 100644
--- a/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx
@@ -13,6 +13,15 @@ import { Button } from "@kilocode/kilo-ui/button"
import { useSession } from "../../context/session"
import type { AgentInfo } from "../../types/messages"
+/** Format an agent for display. Uses displayName if available, otherwise title-cases the slug. */
+function formatAgentLabel(agent: AgentInfo): string {
+ if (agent.displayName) return agent.displayName
+ return agent.name
+ .split(/[-_]/)
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
+ .join(" ")
+}
+
// ---------------------------------------------------------------------------
// Reusable base component
// ---------------------------------------------------------------------------
@@ -82,9 +91,7 @@ export const ModeSwitcherBase: Component = (props) => {
const triggerLabel = () => {
const agent = props.agents.find((a) => a.name === props.value)
- if (agent) {
- return agent.name.charAt(0).toUpperCase() + agent.name.slice(1)
- }
+ if (agent) return formatAgentLabel(agent)
return props.value || "Code"
}
@@ -93,7 +100,6 @@ export const ModeSwitcherBase: Component = (props) => {
= (props) => {
onClick={() => pick(agent.name)}
onFocus={() => setFocused(i())}
>
-
- {agent.name.charAt(0).toUpperCase() + agent.name.slice(1)}
-
+ {formatAgentLabel(agent)}
{agent.description}
diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ModelPreview.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ModelPreview.tsx
index 34232e52539..3cd4af62786 100644
--- a/packages/kilo-vscode/webview-ui/src/components/shared/ModelPreview.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/shared/ModelPreview.tsx
@@ -2,18 +2,12 @@ import { Component, Show, For } from "solid-js"
import type { EnrichedModel } from "../../context/provider"
import { Markdown } from "@kilocode/kilo-ui/markdown"
import { sanitizeName } from "./model-selector-utils"
+import { fmtPrice } from "./model-preview-utils"
interface Props {
model: EnrichedModel | null
}
-function fmtPrice(n: number): string {
- if (n === 0) return "Free"
- const per1M = n * 1_000_000
- if (per1M < 0.01) return `$${per1M.toFixed(4)}/1M`
- return `$${per1M.toFixed(2)}/1M`
-}
-
function fmtContext(n: number): string {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1)}M`
if (n >= 1_000) return `${(n / 1_000).toFixed(n % 1_000 === 0 ? 0 : 1)}K`
diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx
index a7c2529b256..115a4bce587 100644
--- a/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx
@@ -63,7 +63,7 @@ export const ModelSelectorBase: Component = (props) => {
const [selectedIndex, setSelectedIndex] = createSignal(0)
const [preActiveIdx, setPreActiveIdx] = createSignal(-1)
const [previewIdx, setPreviewIdx] = createSignal(-1)
- const [previewHeight, setPreviewHeight] = createSignal(320)
+ const [previewHeight, setPreviewHeight] = createSignal(500)
let searchRef: HTMLInputElement | undefined
let searchWrapperRef: HTMLDivElement | undefined
@@ -324,152 +324,161 @@ export const ModelSelectorBase: Component = (props) => {
}
class={`model-selector-popover${expanded() ? " model-selector-popover--expanded" : ""}`}
>
- {(bodyH) => (
-
-
-
setSearch(e.currentTarget.value)}
- />
-
- {
- setExpanded((v) => {
- if (v) {
- setPreActiveIdx(-1)
- setPreviewIdx(-1)
- }
- return !v
- })
- requestAnimationFrame(() => {
- searchRef?.focus()
- listRef?.querySelector(".model-selector-item.active")?.scrollIntoView({ block: "nearest" })
- })
- }}
+ {(bodyH) => {
+ createEffect(() => {
+ if (!expanded()) return
+ const h = bodyH()
+ if (h === undefined) return
+ const chrome = (searchWrapperRef?.offsetHeight ?? 0) + (splitterRef?.offsetHeight ?? 0)
+ setPreviewHeight((h - chrome) / 2)
+ })
+ return (
+
+
+ setSearch(e.currentTarget.value)}
/>
-
-
-
-
-
- {language.t("dialog.model.empty")}
-
-
-
- pickClear()}
- onMouseMove={() => {
- setPointer(true)
- }}
- onMouseEnter={() => {
- if (pointer()) setSelectedIndex(0)
- }}
+
-
- {props.clearLabel ?? language.t("dialog.model.notSet")}
-
-
-
+
{
+ setExpanded((v) => {
+ if (v) {
+ setPreActiveIdx(-1)
+ setPreviewIdx(-1)
+ }
+ return !v
+ })
+ requestAnimationFrame(() => {
+ searchRef?.focus()
+ listRef?.querySelector(".model-selector-item.active")?.scrollIntoView({ block: "nearest" })
+ })
+ }}
+ />
+
+
-
- {(group) => (
- <>
- {group.providerName}
-
- {(model) => {
- const idx = () => flatIndexMap().get(model) ?? 0
- const hovered = () => isSelected(idx())
- const preActive = () => isPreActive(idx())
- const showSelectBtn = () => expanded() && preActive() && !isActive(model)
- return (
- {
- setSelectedIndex(idx())
- setPreActiveIdx(idx())
- setPreviewIdx(idx())
- if (!expanded()) pick(model)
- searchRef?.focus()
- }}
- onDblClick={() => {
- if (expanded()) pick(model)
- }}
- onMouseMove={() => {
- setPointer(true)
- }}
- onMouseEnter={() => {
- if (pointer()) setSelectedIndex(idx())
- }}
- >
-
-
- {(() => {
- const full = sanitizeName(model.name)
- const sep = full.indexOf(": ")
- if (sep < 0) return {full}
- return (
- <>
- {full.slice(0, sep)}
- {full.slice(sep + 2)}
- >
- )
- })()}
-
-
- {language.t("model.tag.free")}
+
+
+ {language.t("dialog.model.empty")}
+
+
+
+ pickClear()}
+ onMouseMove={() => {
+ setPointer(true)
+ }}
+ onMouseEnter={() => {
+ if (pointer()) setSelectedIndex(0)
+ }}
+ >
+
+ {props.clearLabel ?? language.t("dialog.model.notSet")}
+
+
+
+
+
+ {(group) => (
+ <>
+ {group.providerName}
+
+ {(model) => {
+ const idx = () => flatIndexMap().get(model) ?? 0
+ const hovered = () => isSelected(idx())
+ const preActive = () => isPreActive(idx())
+ const showSelectBtn = () => expanded() && preActive() && !isActive(model)
+ return (
+ {
+ setSelectedIndex(idx())
+ setPreActiveIdx(idx())
+ setPreviewIdx(idx())
+ if (!expanded()) pick(model)
+ searchRef?.focus()
+ }}
+ onDblClick={() => {
+ if (expanded()) pick(model)
+ }}
+ onMouseMove={() => {
+ setPointer(true)
+ }}
+ onMouseEnter={() => {
+ if (pointer()) setSelectedIndex(idx())
+ }}
+ >
+
+
+ {(() => {
+ const full = sanitizeName(model.name)
+ const sep = full.indexOf(": ")
+ if (sep < 0) return {full}
+ return (
+ <>
+ {full.slice(0, sep)}
+ {full.slice(sep + 2)}
+ >
+ )
+ })()}
+
+
+ {language.t("model.tag.free")}
+
+
+
+ {
+ e.stopPropagation()
+ pick(model)
+ }}
+ >
+ {language.t("dialog.model.select")}
+
-
- {
- e.stopPropagation()
- pick(model)
- }}
- >
- {language.t("dialog.model.select")}
-
-
-
- )
- }}
-
- >
- )}
-
-
+ )
+ }}
+
+ >
+ )}
+
+
-
-
-
-
-
- )}
+ )
+ }}
)
}
diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/PopupSelector.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/PopupSelector.tsx
index a195f2fb0f7..27426e39242 100644
--- a/packages/kilo-vscode/webview-ui/src/components/shared/PopupSelector.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/shared/PopupSelector.tsx
@@ -97,11 +97,11 @@ export function PopupSelector(props:
const bodyH = createMemo(() => {
const preferred = local.expanded ? local.preferredExpandedHeight : local.preferredHeight
- if (preferred === undefined) return undefined
const h = panelH()
- if (h === undefined) return preferred
// 26px = 2px border + 24px popover-body padding (12px top + 12px bottom)
- const max = h - 26
+ const max = h !== undefined ? h - 26 : undefined
+ if (preferred === undefined) return max !== undefined ? Math.max(local.minHeight ?? 100, max) : undefined
+ if (max === undefined) return preferred
return Math.max(local.minHeight ?? 100, Math.min(preferred, max))
})
diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/model-preview-utils.ts b/packages/kilo-vscode/webview-ui/src/components/shared/model-preview-utils.ts
new file mode 100644
index 00000000000..1d7cfcb73a6
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/components/shared/model-preview-utils.ts
@@ -0,0 +1,9 @@
+/**
+ * Format a model price for display.
+ * Expects `n` in $/M tokens (as stored in model.cost.input / model.cost.output).
+ */
+export function fmtPrice(n: number): string {
+ if (n === 0) return "Free"
+ if (n < 0.01) return `$${n.toFixed(4)}/1M`
+ return `$${n.toFixed(2)}/1M`
+}
diff --git a/packages/kilo-vscode/webview-ui/src/context/config.tsx b/packages/kilo-vscode/webview-ui/src/context/config.tsx
index 66474bd2622..015c4934bbf 100644
--- a/packages/kilo-vscode/webview-ui/src/context/config.tsx
+++ b/packages/kilo-vscode/webview-ui/src/context/config.tsx
@@ -11,6 +11,7 @@
import { createContext, useContext, createSignal, onCleanup, ParentComponent, Accessor } from "solid-js"
import { useVSCode } from "./vscode"
import type { Config, ExtensionMessage } from "../types/messages"
+import { deepMerge, stripNulls, resolveConfig } from "../utils/config-utils"
interface ConfigContextValue {
config: Accessor
@@ -21,37 +22,6 @@ interface ConfigContextValue {
discardConfig: () => void
}
-function isRecord(value: unknown): value is Record {
- return !!value && typeof value === "object" && !Array.isArray(value)
-}
-
-/** Deep merge two objects, with source values overriding target values. */
-function deepMerge(target: Config, source: Partial): Config {
- const result: Record = { ...target }
- for (const [key, value] of Object.entries(source)) {
- if (isRecord(value) && isRecord(result[key])) {
- result[key] = deepMerge(result[key] as Config, value as Partial)
- } else {
- result[key] = value
- }
- }
- return result as Config
-}
-
-/** Recursively remove keys whose value is null (null = "deleted"). */
-function stripNulls(obj: Config): Config {
- const result: Record = {}
- for (const [key, value] of Object.entries(obj)) {
- if (value === null || value === undefined) continue
- if (isRecord(value)) {
- result[key] = stripNulls(value as Config)
- } else {
- result[key] = value
- }
- }
- return result as Config
-}
-
export const ConfigContext = createContext()
export const ConfigProvider: ParentComponent = (props) => {
@@ -74,7 +44,9 @@ export const ConfigProvider: ParentComponent = (props) => {
// Skip if a save is in-flight — a stale configLoaded must not overwrite
// the optimistically-updated state while the write is being confirmed.
if (saving) return
- setConfig(message.config)
+ // Re-apply the draft on top so pending changes (e.g. a toggled switch the
+ // user hasn't saved yet) stay visible instead of snapping back.
+ setConfig(resolveConfig(message.config, draft(), isDirty()))
setSaved(message.config)
setLoading(false)
return
@@ -90,11 +62,7 @@ export const ConfigProvider: ParentComponent = (props) => {
} else {
// configUpdated from a different source (e.g. PermissionDock save).
// Re-apply the draft on top so pending settings changes are preserved.
- if (isDirty()) {
- setConfig(stripNulls(deepMerge(message.config, draft())))
- } else {
- setConfig(message.config)
- }
+ setConfig(resolveConfig(message.config, draft(), isDirty()))
}
setSaved(message.config)
return
diff --git a/packages/kilo-vscode/webview-ui/src/context/notifications.tsx b/packages/kilo-vscode/webview-ui/src/context/notifications.tsx
index 5456cf2cec7..8c722585e6a 100644
--- a/packages/kilo-vscode/webview-ui/src/context/notifications.tsx
+++ b/packages/kilo-vscode/webview-ui/src/context/notifications.tsx
@@ -17,7 +17,7 @@ interface NotificationsContextValue {
dismiss: (id: string) => void
}
-const NotificationsContext = createContext()
+export const NotificationsContext = createContext()
export const NotificationsProvider: ParentComponent = (props) => {
const vscode = useVSCode()
diff --git a/packages/kilo-vscode/webview-ui/src/context/server.tsx b/packages/kilo-vscode/webview-ui/src/context/server.tsx
index 1826dd9f1dd..e5d557f2fb5 100644
--- a/packages/kilo-vscode/webview-ui/src/context/server.tsx
+++ b/packages/kilo-vscode/webview-ui/src/context/server.tsx
@@ -22,7 +22,7 @@ interface ServerContextValue {
workspaceDirectory: Accessor
}
-const ServerContext = createContext()
+export const ServerContext = createContext()
const initialDeviceAuth: DeviceAuthState = { status: "idle" }
diff --git a/packages/kilo-vscode/webview-ui/src/context/session-model-store.ts b/packages/kilo-vscode/webview-ui/src/context/session-model-store.ts
new file mode 100644
index 00000000000..86b44de23e4
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/context/session-model-store.ts
@@ -0,0 +1,113 @@
+import type { ModelSelection, Provider } from "../types/messages"
+import { resolveModelSelection } from "./model-selection"
+
+/**
+ * Pure-logic helpers for per-session and global model selection.
+ *
+ * The SessionProvider delegates to these so the core state transitions
+ * can be tested without SolidJS reactivity.
+ */
+
+export interface ModelStore {
+ /** agentName -> model (global, extension-lifetime) */
+ modelSelections: Record
+ /** sessionID -> per-session model override */
+ sessionOverrides: Record
+ /** sessionID -> agent name */
+ agentSelections: Record
+ recentModels: ModelSelection[]
+}
+
+export interface ResolveEnv {
+ providers: Record
+ connected: string[]
+ fallback: ModelSelection | null
+ getModeModel: (agentName: string) => ModelSelection | null
+ getGlobalModel: () => ModelSelection | null
+}
+
+function resolveModel(
+ env: ResolveEnv,
+ agentName: string,
+ override?: ModelSelection | null,
+ recents?: ModelSelection[],
+): ModelSelection | null {
+ return resolveModelSelection({
+ providers: env.providers,
+ connected: env.connected,
+ override,
+ mode: env.getModeModel(agentName),
+ global: env.getGlobalModel(),
+ recent: recents,
+ fallback: env.fallback,
+ })
+}
+
+/**
+ * Returns the model for a specific session, honoring per-session overrides.
+ *
+ * Precedence: sessionOverride > global modelSelections[agent] > config/default.
+ */
+export function getSessionModel(
+ store: ModelStore,
+ env: ResolveEnv,
+ sessionID: string,
+ defaultAgent: string,
+): ModelSelection | null {
+ const override = store.sessionOverrides[sessionID]
+ if (override) return override
+ const agentName = store.agentSelections[sessionID] ?? defaultAgent
+ return resolveModel(env, agentName, store.modelSelections[agentName], store.recentModels)
+}
+
+/**
+ * Returns the model for the "current" view (model picker display).
+ *
+ * Precedence: sessionOverride[sid] > global modelSelections[agent] > config/default.
+ */
+export function getSelected(
+ store: ModelStore,
+ env: ResolveEnv,
+ sessionID: string | undefined,
+ agentName: string,
+): ModelSelection | null {
+ if (sessionID) {
+ const session = store.sessionOverrides[sessionID]
+ if (session) return session
+ }
+ return resolveModel(env, agentName, store.modelSelections[agentName], store.recentModels)
+}
+
+export interface ApplyResult {
+ modelSelections: Record
+ sessionOverrides: Record
+}
+
+/**
+ * Apply a user-initiated model selection.
+ *
+ * When a session is active, writes ONLY to the per-session override so other
+ * sessions are not affected. When no session is active (sidebar), writes to
+ * the global modelSelections map.
+ */
+export function applyModel(
+ store: ModelStore,
+ agentName: string,
+ selection: ModelSelection,
+ sessionID: string | undefined,
+): ApplyResult {
+ const modelSelections = { ...store.modelSelections }
+ const sessionOverrides = { ...store.sessionOverrides }
+
+ if (sessionID) {
+ // Per-session only — do NOT mutate the global map. Writing globally
+ // here would cause every other session (that hasn't set its own
+ // override) to inherit this session's model.
+ sessionOverrides[sessionID] = selection
+ } else {
+ // No active session (sidebar) — write globally
+ modelSelections[agentName] = selection
+ }
+
+ return { modelSelections, sessionOverrides }
+}
diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx
index ba7a6fbd862..28201fa0610 100644
--- a/packages/kilo-vscode/webview-ui/src/context/session.tsx
+++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx
@@ -154,6 +154,9 @@ interface SessionContextValue {
revertedCount: Accessor
summary: Accessor
+ // Live worktree diff stats (polled from CLI backend)
+ worktreeStats: Accessor<{ files: number; additions: number; deletions: number } | undefined>
+
// Actions
revertSession: (messageID: string) => void
unrevertSession: () => void
@@ -244,11 +247,14 @@ export const SessionProvider: ParentComponent = (props) => {
if (pendingAgentSelection() === name) {
setPendingAgentSelection(null)
}
- for (const sid of Object.keys(store.agentSelections)) {
- if (store.agentSelections[sid] === name) {
- setStore("agentSelections", sid, undefined as unknown as string)
- }
- }
+ setStore(
+ "agentSelections",
+ produce((selections) => {
+ for (const sid of Object.keys(selections)) {
+ if (selections[sid] === name) delete selections[sid]
+ }
+ }),
+ )
vscode.postMessage({ type: "removeMode", name })
}
@@ -263,6 +269,11 @@ export const SessionProvider: ParentComponent = (props) => {
// Cloud session preview state
const [cloudPreviewId, setCloudPreviewId] = createSignal(null)
+ // Live worktree diff stats from extension polling
+ const [worktreeStats, setWorktreeStats] = createSignal<
+ { files: number; additions: number; deletions: number } | undefined
+ >()
+
// Tracks optimistic messageIDs that haven't been confirmed by the server yet.
// Prevents handleMessagesLoaded from wiping them when it replaces the array.
const pendingOptimistic = new Map>()
@@ -342,11 +353,18 @@ export const SessionProvider: ParentComponent = (props) => {
}
function applyModel(agentName: string, selection: ModelSelection) {
- setUserSetAgents((prev) => ({ ...prev, [agentName]: true }))
- setStore("modelSelections", agentName, selection)
pushRecent(selection)
const sid = currentSessionID()
- if (sid) setStore("sessionOverrides", sid, selection)
+ if (sid) {
+ // Per-session only — do NOT mutate the global modelSelections map.
+ // Writing globally here would cause every other session (that hasn't
+ // set its own override) to inherit this session's model.
+ setStore("sessionOverrides", sid, selection)
+ } else {
+ // No active session (sidebar) — write globally
+ setUserSetAgents((prev) => ({ ...prev, [agentName]: true }))
+ setStore("modelSelections", agentName, selection)
+ }
}
function selectModel(providerID: string, modelID: string) {
@@ -402,10 +420,24 @@ export const SessionProvider: ParentComponent = (props) => {
}
setAgents(message.agents)
setDefaultAgent(message.defaultAgent)
- // Initialize pending agent if not yet set by the user
- if (!pendingAgentSelection()) {
+
+ const names = new Set(message.agents.map((a) => a.name))
+
+ // Reset pending selection if the agent no longer exists (e.g. after org switch)
+ const pending = pendingAgentSelection()
+ if (!pending || !names.has(pending)) {
setPendingAgentSelection(message.defaultAgent)
}
+
+ // Clear per-session selections that reference a mode no longer available
+ setStore(
+ "agentSelections",
+ produce((selections) => {
+ for (const sid of Object.keys(selections)) {
+ if (selections[sid] && !names.has(selections[sid]!)) delete selections[sid]
+ }
+ }),
+ )
})
// Request agents in case the initial push was missed.
@@ -612,6 +644,10 @@ export const SessionProvider: ParentComponent = (props) => {
})
console.error("[Kilo New] Cloud session import failed:", message.error)
break
+
+ case "worktreeStatsLoaded":
+ setWorktreeStats({ files: message.files, additions: message.additions, deletions: message.deletions })
+ break
}
})
@@ -1626,6 +1662,7 @@ export const SessionProvider: ParentComponent = (props) => {
revert,
revertedCount,
summary,
+ worktreeStats,
revertSession,
unrevertSession,
sendMessage,
diff --git a/packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts b/packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts
index c937f1bb091..f87a9567441 100644
--- a/packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts
+++ b/packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts
@@ -36,6 +36,8 @@ export interface FileMention {
setMentionIndex: (index: number) => void
closeMention: () => void
parseFileAttachments: (text: string) => FileAttachment[]
+ /** Register paths as active mentions (used by drag-and-drop). Pass cwd to ensure buildFileAttachments resolves correctly. */
+ addPaths: (paths: string[], cwd: string) => void
}
export function useFileMention(vscode: VSCodeContext): FileMention {
@@ -150,6 +152,7 @@ export function useFileMention(vscode: VSCodeContext): FileMention {
}
if (e.key === "Escape") {
e.preventDefault()
+ e.stopPropagation()
closeMention()
return true
}
@@ -157,6 +160,15 @@ export function useFileMention(vscode: VSCodeContext): FileMention {
return false
}
+ const addPaths = (paths: string[], cwd: string) => {
+ if (cwd) workspaceDir = cwd
+ setMentionedPaths((prev) => {
+ const next = new Set(prev)
+ for (const p of paths) next.add(p)
+ return next
+ })
+ }
+
const parseFileAttachments = (text: string): FileAttachment[] =>
buildFileAttachments(text, mentionedPaths(), workspaceDir)
@@ -171,5 +183,6 @@ export function useFileMention(vscode: VSCodeContext): FileMention {
setMentionIndex,
closeMention,
parseFileAttachments,
+ addPaths,
}
}
diff --git a/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts b/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts
index c72d6f123fd..b72328b0f3a 100644
--- a/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts
+++ b/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts
@@ -1,5 +1,6 @@
import { createSignal } from "solid-js"
import { ACCEPTED_IMAGE_TYPES, isAcceptedImageType, isDragLeavingComponent } from "./image-attachments-utils"
+import { extractDropPaths } from "../utils/path-mentions"
export interface ImageAttachment {
id: string
@@ -8,9 +9,18 @@ export interface ImageAttachment {
dataUrl: string
}
+/** Callback for handling text/URI file path drops. */
+export type FilePathDropHandler = (paths: string[]) => void
+
export function useImageAttachments() {
const [images, setImages] = createSignal([])
const [dragging, setDragging] = createSignal(false)
+ let onFilePaths: FilePathDropHandler | undefined
+
+ /** Register a handler for file path drops (text/URI-list). */
+ const setFilePathDropHandler = (handler: FilePathDropHandler) => {
+ onFilePaths = handler
+ }
const add = (file: File) => {
if (!isAcceptedImageType(file.type)) return
@@ -47,8 +57,12 @@ export function useImageAttachments() {
}
const handleDragOver = (event: DragEvent) => {
- const hasFiles = event.dataTransfer?.types.includes("Files")
- if (!hasFiles) return
+ const types = event.dataTransfer?.types
+ if (!types) return
+ // Accept file drops and VS Code URI-list drops (explorer, editor tabs).
+ // Do NOT accept bare text/plain here — that would intercept normal text drags.
+ const acceptable = types.includes("Files") || types.includes("application/vnd.code.uri-list")
+ if (!acceptable) return
event.preventDefault()
setDragging(true)
}
@@ -62,7 +76,18 @@ export function useImageAttachments() {
const handleDrop = (event: DragEvent) => {
setDragging(false)
event.preventDefault()
- const files = event.dataTransfer?.files
+ const dt = event.dataTransfer
+ if (!dt) return
+
+ // First: check for text/URI file path drops (VS Code explorer, editor tabs)
+ const paths = extractDropPaths(dt)
+ if (paths && paths.length > 0 && onFilePaths) {
+ onFilePaths(paths)
+ return
+ }
+
+ // Second: fall through to image file drops
+ const files = dt.files
if (!files) return
for (const file of Array.from(files)) add(file)
}
@@ -78,5 +103,6 @@ export function useImageAttachments() {
handleDragOver,
handleDragLeave,
handleDrop,
+ setFilePathDropHandler,
}
}
diff --git a/packages/kilo-vscode/webview-ui/src/hooks/useSlashCommand.ts b/packages/kilo-vscode/webview-ui/src/hooks/useSlashCommand.ts
index 10f613e4b90..4835b6e1757 100644
--- a/packages/kilo-vscode/webview-ui/src/hooks/useSlashCommand.ts
+++ b/packages/kilo-vscode/webview-ui/src/hooks/useSlashCommand.ts
@@ -205,6 +205,7 @@ export function useSlashCommand(vscode: VSCodeContext, exclude?: Set): S
}
if (e.key === "Escape") {
e.preventDefault()
+ e.stopPropagation()
close()
return true
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts
index 9c5f727ae78..ac8b73e92e1 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts
@@ -745,6 +745,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "الاسم المعروض",
"provider.custom.models.remove": "إزالة النموذج",
"provider.custom.models.add": "إضافة نموذج",
+ "provider.custom.models.fetch": "جلب النماذج",
+ "provider.custom.models.fetching": "جارٍ الجلب\u2026",
+ "provider.custom.models.fetch.error": "فشل جلب النماذج: {{error}}",
+ "provider.custom.models.fetch.authError": "فشلت المصادقة. تحقق من مفتاح API أعلاه وحاول مرة أخرى.",
+ "provider.custom.models.fetch.empty": "لم يتم العثور على نماذج على هذا الخادم.",
+ "provider.custom.models.fetch.added": "تمت إضافة {{count}} نموذج(نماذج).",
+ "provider.custom.models.fetch.allExist": "جميع النماذج المجلوبة مضافة بالفعل.",
+ "provider.custom.models.fetch.selectAll": "تحديد الكل",
+ "provider.custom.models.fetch.deselectAll": "إلغاء تحديد الكل",
+ "provider.custom.models.fetch.found": "تم العثور على {{count}} نموذج",
+ "provider.custom.models.fetch.showing": "عرض {{shown}} من {{total}}",
+ "provider.custom.models.fetch.search": "البحث في النماذج\u2026",
+ "provider.custom.models.fetch.add": "إضافة {{count}} نموذج(نماذج)",
+ "provider.custom.edit.title": "تعديل المزود",
"provider.custom.headers.label": "الرؤوس (اختياري)",
"provider.custom.headers.key.label": "الرأس",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -818,8 +832,11 @@ export const dict = {
"session.delete.button": "حذف الجلسة",
"session.untitled": "بدون عنوان",
"session.recent": "الأخيرة",
+ "session.showHistory": "عرض السجل",
"session.search.placeholder": "البحث في الجلسات...",
"session.empty": "لا توجد جلسات بعد. انقر + لبدء محادثة جديدة.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "هذا المستودع فقط",
"session.cloud.import": "استيراد من السحابة",
"feedback.button": "التغذية الراجعة والدعم",
@@ -900,8 +917,7 @@ export const dict = {
"settings.autocomplete.title": "الإكمال التلقائي",
"settings.notifications.title": "الإشعارات",
"settings.context.title": "السياق",
- "settings.terminal.title": "المحطة الطرفية",
- "settings.prompts.title": "الموجهات",
+
"settings.experimental.title": "تجريبي",
"settings.language.title": "اللغة",
"settings.aboutKiloCode.title": "حول Kilo Code",
@@ -952,6 +968,16 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"إعادة تعيين جميع إعدادات إضافة Kilo Code إلى قيمها الافتراضية. لا يؤثر هذا على تكوين CLI أو الواجهة الخلفية.",
"settings.aboutKiloCode.resetSettings.button": "إعادة تعيين جميع الإعدادات",
+ "settings.aboutKiloCode.settingsTransfer.title": "نقل الإعدادات",
+ "settings.aboutKiloCode.settingsTransfer.description": "تصدير أو استيراد إعداداتك لنقلها بين نُسخ VS Code.",
+ "settings.aboutKiloCode.exportSettings": "تصدير",
+ "settings.aboutKiloCode.importSettings": "استيراد",
+ "settings.aboutKiloCode.importSettings.invalidJson": "ملف JSON غير صالح. يرجى اختيار ملف إعدادات صالح.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "الملف لا يحتوي على إعدادات Kilo صالحة.",
+ "settings.aboutKiloCode.importSettings.tooLarge": "الملف كبير جدًا. يجب أن تكون ملفات الإعدادات أقل من 1 MB.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "تم تصدير هذا الملف من إصدار أحدث من Kilo. قد يتم تجاهل بعض الإعدادات.",
+ "settings.aboutKiloCode.importSettings.success": "تم استيراد الإعدادات. راجع التغييرات أعلاه، ثم انقر على حفظ.",
"settings.agentBehaviour.subtab.modes": "الأوضاع",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -975,8 +1001,7 @@ export const dict = {
"common.add": "إضافة",
"common.choose": "اختر…",
- "settings.notImplemented": "هذا القسم لم يتم تنفيذه بعد.",
- "settings.notImplemented.description": "سيحتوي على خيارات التكوين والنص التوضيحي.",
+
"settings.autocomplete.autoTrigger.title": "تمكين الإكمال التلقائي المضمّن",
"settings.autocomplete.autoTrigger.description": "عرض اقتراحات الإكمال المضمّن تلقائياً أثناء الكتابة",
"settings.autocomplete.smartKeybinding.title": "تمكين اختصار المهمة المضمّنة الذكي",
@@ -1034,11 +1059,48 @@ export const dict = {
"settings.agentBehaviour.topP.description": "معامل أخذ العينات النووي (0-1)",
"settings.agentBehaviour.maxSteps.title": "الحد الأقصى للخطوات",
"settings.agentBehaviour.maxSteps.description": "الحد الأقصى لتكرارات الوكيل",
+ "settings.agentBehaviour.hidden.title": "مخفي",
+ "settings.agentBehaviour.hidden.description": "إخفاء هذا الوكيل من مبدل الأوضاع في إدخال الدردشة",
+ "settings.agentBehaviour.disable.title": "معطل",
+ "settings.agentBehaviour.disable.description": "تعطيل هذا الوكيل بالكامل — لن يظهر في أي مكان",
+ "settings.agentBehaviour.badge.hidden": "مخفي",
+ "settings.agentBehaviour.badge.disabled": "معطل",
"settings.agentBehaviour.discoveredSkills": "المهارات المكتشفة",
"settings.agentBehaviour.noSkillsFound":
"لم يتم العثور على مهارات. أضف مسارات مجلدات أو عناوين URL أدناه لإتاحة المهارات.",
"settings.agentBehaviour.availableModes": "الأوضاع المخصصة المتاحة",
"settings.agentBehaviour.noModesFound": "لم يتم العثور على أوضاع.",
+ "settings.agentBehaviour.createMode": "إنشاء وضع جديد",
+ "settings.agentBehaviour.createMode.name": "الاسم",
+ "settings.agentBehaviour.createMode.name.placeholder": "مثال: reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "معرّف فريد للوضع. استخدم الأحرف الصغيرة والأرقام والشرطات فقط.",
+ "settings.agentBehaviour.createMode.description": "الوصف",
+ "settings.agentBehaviour.createMode.description.placeholder": "مثال: يراجع الكود للجودة وأفضل الممارسات",
+ "settings.agentBehaviour.createMode.description.help": "وصف قصير لما يفعله هذا الوضع.",
+ "settings.agentBehaviour.createMode.prompt": "موجه النظام",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "مثال: أنت مراجع كود. ركز على جودة الكود وأفضل الممارسات والأخطاء المحتملة.",
+ "settings.agentBehaviour.createMode.prompt.help": "تعليمات لعميل الذكاء الاصطناعي عند استخدام هذا الوضع.",
+ "settings.agentBehaviour.createMode.button": "إنشاء",
+ "settings.agentBehaviour.createMode.cancel": "إلغاء",
+ "settings.agentBehaviour.createMode.nameRequired": "الاسم مطلوب",
+ "settings.agentBehaviour.createMode.nameInvalid": "يجب أن يحتوي الاسم على أحرف صغيرة وأرقام وشرطات فقط",
+ "settings.agentBehaviour.createMode.nameTaken": "يوجد وضع بهذا الاسم بالفعل",
+ "settings.agentBehaviour.importMode": "استيراد",
+ "settings.agentBehaviour.importMode.invalidName":
+ "اسم الوضع في الملف غير صالح. يجب أن يبدأ الاسم بحرف صغير ويحتوي فقط على أحرف صغيرة وأرقام وشرطات.",
+ "settings.agentBehaviour.importMode.nameTaken": "يوجد وضع بهذا الاسم بالفعل.",
+ "settings.agentBehaviour.importMode.invalidJson": "ملف JSON غير صالح. يرجى اختيار ملف تعريف وكيل صالح.",
+ "settings.agentBehaviour.importMode.tooLarge": "الملف كبير جدًا. يجب أن تكون تعريفات الوكيل أقل من 1 ميجابايت.",
+ "settings.agentBehaviour.exportMode": "تصدير تعريف الوكيل",
+ "settings.agentBehaviour.editMode": "تعديل الوضع",
+ "settings.agentBehaviour.editMode.description": "الوصف",
+ "settings.agentBehaviour.editMode.prompt": "موجه النظام",
+ "settings.agentBehaviour.editMode.save": "تم",
+ "settings.agentBehaviour.editMode.back": "العودة إلى القائمة",
+ "settings.agentBehaviour.editMode.native": "وضع مدمج (تعريف للقراءة فقط)",
+ "settings.agentBehaviour.editMode.promptOverride": "تجاوز موجه مخصص لهذا الوضع المدمج",
"settings.agentBehaviour.removeMode.title": "إزالة الوضع",
"settings.agentBehaviour.removeMode.confirm":
'هل تريد إزالة الوضع "{{name}}"؟ سيؤدي هذا إلى تعطيل الوضع عن طريق تحديث الإعدادات.',
@@ -1046,17 +1108,41 @@ export const dict = {
"settings.agentBehaviour.removeMcp.title": "إزالة خادم MCP",
"settings.agentBehaviour.removeMcp.confirm": 'هل تريد إزالة خادم MCP "{{name}}"؟ سيؤدي هذا إلى إزالته من الإعدادات.',
"settings.agentBehaviour.removeMcp.button": "إزالة",
+ "settings.agentBehaviour.editMcp": "تعديل خادم MCP",
+ "settings.agentBehaviour.editMcp.transportLocal": "خادم محلي (نقل stdio)",
+ "settings.agentBehaviour.editMcp.transportRemote": "خادم بعيد (نقل SSE/HTTP)",
+ "settings.agentBehaviour.editMcp.env": "متغيرات البيئة",
+ "settings.agentBehaviour.editMcp.env.help": "المتغيرات التي يتم تمريرها إلى عملية خادم MCP.",
+ "settings.agentBehaviour.addMcp.command": "الأمر",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "الوسائط",
+ "settings.agentBehaviour.addMcp.args.help":
+ "وسيطة واحدة لكل سطر. يتم الحفاظ على المسارات التي تحتوي على مسافات كما هي.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "عنوان URL للخادم",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "مسارات مجلدات المهارات",
"settings.agentBehaviour.skillUrls": "عناوين URL للمهارات",
"settings.agentBehaviour.removeSkill.title": "إزالة المهارة",
"settings.agentBehaviour.removeSkill.confirm":
'هل تريد إزالة المهارة "{{name}}"؟ سيؤدي هذا إلى حذف ملفات المهارة من القرص.',
"settings.agentBehaviour.removeSkill.button": "إزالة",
+ "settings.agentBehaviour.rules.description":
+ "القواعد هي ملفات تعليمات توجه سلوك الوكيل. يتم تضمينها في موجه النظام لكل محادثة. أضف مسارات الملفات أدناه لتضمين قواعد إضافية.",
"settings.agentBehaviour.instructionFiles": "ملفات تعليمات إضافية",
"settings.agentBehaviour.instructionFiles.description": "مسارات ملفات التعليمات الإضافية في موجه النظام",
+ "settings.agentBehaviour.mcpDetail.command": "الأمر",
+ "settings.agentBehaviour.mcpDetail.args": "الوسائط",
+ "settings.agentBehaviour.mcpDetail.env": "البيئة",
+ "settings.agentBehaviour.mcpDetail.disabled": "هذا الخادم معطّل.",
"settings.agentBehaviour.mcpEmpty": "لم يتم تهيئة خوادم MCP. قم بتحرير ملف تهيئة opencode لإضافة خوادم MCP.",
"settings.agentBehaviour.workflowsPlaceholder": "تُدار سير العمل عبر ملفات سير العمل في مساحة العمل.",
- "settings.agentBehaviour.notImplemented": "لم يتم التنفيذ بعد.",
+ "settings.agentBehaviour.workflows.description":
+ "سير العمل هي أوامر شرطة مائلة مخصصة محددة في التهيئة الخاصة بك. اكتب /command-name في الدردشة لتشغيلها. يتم تهيئة الأوامر في opencode.json ضمن قسم 'command'.",
+ "settings.agentBehaviour.workflows.empty": "لم يتم تهيئة أوامر مخصصة. أضف أوامر إلى opencode.json لرؤيتها هنا.",
+ "settings.agentBehaviour.workflows.detail.description": "الوصف",
+ "settings.agentBehaviour.workflows.detail.template": "القالب",
+
"settings.autoApprove.description":
"تحديد كيفية السماح بتشغيل الأدوات. معظم الأدوات معينة افتراضياً على السماح. doom_loop و external_directory معينة افتراضياً على السؤال.",
"settings.autoApprove.level.allow": "سماح",
@@ -1106,7 +1192,8 @@ export const dict = {
"settings.providers.defaultModel.title": "النموذج الافتراضي",
"settings.providers.defaultModel.description": "النموذج الأساسي للمحادثات",
"settings.providers.smallModel.title": "نموذج صغير",
- "settings.providers.smallModel.description": "نموذج خفيف لتوليد العناوين والمهام السريعة",
+ "settings.providers.smallModel.description":
+ "نموذج خفيف لتوليد العناوين ورسائل الـ commit وتحسين المطالبات والمهام السريعة الأخرى",
"settings.providers.disabled": "مزودون معطلون",
"settings.providers.disabled.description": "مزودون لإخفائهم من القائمة",
"settings.providers.enabled": "مزودون مفعلون (قائمة بيضاء)",
@@ -1188,4 +1275,8 @@ export const dict = {
"settings.saveBar.warning.many": "عدة جلسات تعمل وستتوقف",
"settings.saveBar.saveAnyway": "حفظ على أي حال",
"settings.saveBar.cancel": "إلغاء",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts
index 6bb8a8357c9..b2edcbca372 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts
@@ -752,6 +752,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "Nome de Exibição",
"provider.custom.models.remove": "Remover modelo",
"provider.custom.models.add": "Adicionar modelo",
+ "provider.custom.models.fetch": "Buscar modelos",
+ "provider.custom.models.fetching": "Buscando\u2026",
+ "provider.custom.models.fetch.error": "Falha ao buscar modelos: {{error}}",
+ "provider.custom.models.fetch.authError": "Falha na autenticação. Verifique a chave de API acima e tente novamente.",
+ "provider.custom.models.fetch.empty": "Nenhum modelo encontrado neste servidor.",
+ "provider.custom.models.fetch.added": "{{count}} modelo(s) adicionado(s).",
+ "provider.custom.models.fetch.allExist": "Todos os modelos buscados já foram adicionados.",
+ "provider.custom.models.fetch.selectAll": "Selecionar todos",
+ "provider.custom.models.fetch.deselectAll": "Desmarcar todos",
+ "provider.custom.models.fetch.found": "{{count}} modelos encontrados",
+ "provider.custom.models.fetch.showing": "Mostrando {{shown}} de {{total}}",
+ "provider.custom.models.fetch.search": "Pesquisar modelos\u2026",
+ "provider.custom.models.fetch.add": "Adicionar {{count}} modelo(s)",
+ "provider.custom.edit.title": "Editar provedor",
"provider.custom.headers.label": "Headers (opcional)",
"provider.custom.headers.key.label": "Header",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -825,8 +839,11 @@ export const dict = {
"session.delete.button": "Excluir sessão",
"session.untitled": "Sem título",
"session.recent": "Recentes",
+ "session.showHistory": "Mostrar Histórico",
"session.search.placeholder": "Buscar sessões...",
"session.empty": "Nenhuma sessão ainda. Clique + para iniciar uma nova conversa.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Apenas este repositório",
"session.cloud.import": "Importar da nuvem",
"feedback.button": "Feedback e suporte",
@@ -908,8 +925,7 @@ export const dict = {
"settings.autocomplete.title": "Autocompletar",
"settings.notifications.title": "Notificações",
"settings.context.title": "Contexto",
- "settings.terminal.title": "Terminal",
- "settings.prompts.title": "Prompts",
+
"settings.experimental.title": "Experimental",
"settings.language.title": "Idioma",
"settings.aboutKiloCode.title": "Sobre o Kilo Code",
@@ -961,6 +977,20 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Redefinir todas as configurações da extensão Kilo Code para os valores padrão. Isso não afeta a configuração do CLI ou do backend.",
"settings.aboutKiloCode.resetSettings.button": "Redefinir Todas as Configurações",
+ "settings.aboutKiloCode.settingsTransfer.title": "Transferência de configurações",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Exporte ou importe suas configurações para transferi-las entre instâncias do VS Code.",
+ "settings.aboutKiloCode.exportSettings": "Exportar",
+ "settings.aboutKiloCode.importSettings": "Importar",
+ "settings.aboutKiloCode.importSettings.invalidJson":
+ "Arquivo JSON inválido. Selecione um arquivo de configurações válido.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "O arquivo não contém configurações válidas do Kilo.",
+ "settings.aboutKiloCode.importSettings.tooLarge":
+ "O arquivo é muito grande. Arquivos de configurações devem ter menos de 1 MB.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "Este arquivo foi exportado de uma versão mais recente do Kilo. Algumas configurações podem ser ignoradas.",
+ "settings.aboutKiloCode.importSettings.success":
+ "Configurações importadas. Revise as alterações acima e clique em Salvar.",
"settings.agentBehaviour.subtab.modes": "Modos",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -986,8 +1016,7 @@ export const dict = {
"common.add": "Adicionar",
"common.choose": "Escolher…",
- "settings.notImplemented": "Esta seção ainda não foi implementada.",
- "settings.notImplemented.description": "Conterá opções de configuração e texto explicativo.",
+
"settings.autocomplete.autoTrigger.title": "Ativar completamento automático inline",
"settings.autocomplete.autoTrigger.description":
"Mostrar automaticamente sugestões de completamento inline ao digitar",
@@ -1047,11 +1076,50 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Parâmetro de amostragem nucleus (0-1)",
"settings.agentBehaviour.maxSteps.title": "Passos máximos",
"settings.agentBehaviour.maxSteps.description": "Iterações máximas do agente",
+ "settings.agentBehaviour.hidden.title": "Oculto",
+ "settings.agentBehaviour.hidden.description": "Ocultar este agente do seletor de modos na entrada do chat",
+ "settings.agentBehaviour.disable.title": "Desativado",
+ "settings.agentBehaviour.disable.description":
+ "Desativar completamente este agente — ele não aparecerá em nenhum lugar",
+ "settings.agentBehaviour.badge.hidden": "oculto",
+ "settings.agentBehaviour.badge.disabled": "desativado",
"settings.agentBehaviour.discoveredSkills": "Habilidades descobertas",
"settings.agentBehaviour.noSkillsFound":
"Nenhuma habilidade encontrada. Adicione caminhos de pastas ou URLs abaixo para disponibilizar habilidades.",
"settings.agentBehaviour.availableModes": "Modos personalizados disponíveis",
"settings.agentBehaviour.noModesFound": "Nenhum modo encontrado.",
+ "settings.agentBehaviour.createMode": "Criar Novo Modo",
+ "settings.agentBehaviour.createMode.name": "Nome",
+ "settings.agentBehaviour.createMode.name.placeholder": "ex: reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "Identificador único do modo. Use apenas letras minúsculas, números e hífens.",
+ "settings.agentBehaviour.createMode.description": "Descrição",
+ "settings.agentBehaviour.createMode.description.placeholder": "ex: Revisa código para qualidade e boas práticas",
+ "settings.agentBehaviour.createMode.description.help": "Breve descrição do que este modo faz.",
+ "settings.agentBehaviour.createMode.prompt": "Prompt do Sistema",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "ex: Você é um revisor de código. Foque em qualidade, boas práticas e bugs potenciais.",
+ "settings.agentBehaviour.createMode.prompt.help": "Instruções para o agente de IA ao usar este modo.",
+ "settings.agentBehaviour.createMode.button": "Criar",
+ "settings.agentBehaviour.createMode.cancel": "Cancelar",
+ "settings.agentBehaviour.createMode.nameRequired": "Nome é obrigatório",
+ "settings.agentBehaviour.createMode.nameInvalid": "O nome deve conter apenas letras minúsculas, números e hífens",
+ "settings.agentBehaviour.createMode.nameTaken": "Já existe um modo com este nome",
+ "settings.agentBehaviour.importMode": "Importar",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Nome de modo inválido no arquivo. O nome deve começar com uma letra minúscula e conter apenas letras minúsculas, números e hífens.",
+ "settings.agentBehaviour.importMode.nameTaken": "Já existe um modo com este nome.",
+ "settings.agentBehaviour.importMode.invalidJson":
+ "Arquivo JSON inválido. Por favor, selecione um arquivo de definição de agente válido.",
+ "settings.agentBehaviour.importMode.tooLarge": "Arquivo muito grande. Definições de agente devem ter menos de 1 MB.",
+ "settings.agentBehaviour.exportMode": "Exportar definição do agente",
+ "settings.agentBehaviour.editMode": "Editar Modo",
+ "settings.agentBehaviour.editMode.description": "Descrição",
+ "settings.agentBehaviour.editMode.prompt": "Prompt do Sistema",
+ "settings.agentBehaviour.editMode.save": "Concluído",
+ "settings.agentBehaviour.editMode.back": "Voltar à lista",
+ "settings.agentBehaviour.editMode.native": "Modo integrado (definição somente leitura)",
+ "settings.agentBehaviour.editMode.promptOverride": "Substituição de prompt personalizado para este modo integrado",
"settings.agentBehaviour.removeMode.title": "Remover modo",
"settings.agentBehaviour.removeMode.confirm":
'Remover o modo "{{name}}"? Isso desativará o modo atualizando sua configuração.',
@@ -1060,20 +1128,45 @@ export const dict = {
"settings.agentBehaviour.removeMcp.confirm":
'Remover o servidor MCP "{{name}}"? Isso o removerá da sua configuração.',
"settings.agentBehaviour.removeMcp.button": "Remover",
+ "settings.agentBehaviour.editMcp": "Editar Servidor MCP",
+ "settings.agentBehaviour.editMcp.transportLocal": "Servidor local (transporte stdio)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Servidor remoto (transporte SSE/HTTP)",
+ "settings.agentBehaviour.editMcp.env": "Variáveis de Ambiente",
+ "settings.agentBehaviour.editMcp.env.help": "Variáveis passadas para o processo do servidor MCP.",
+ "settings.agentBehaviour.addMcp.command": "Comando",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Argumentos",
+ "settings.agentBehaviour.addMcp.args.help":
+ "Um argumento por linha. Caminhos com espaços são preservados como estão.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "URL do Servidor",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "Caminhos de pastas de habilidades",
"settings.agentBehaviour.skillUrls": "URLs de habilidades",
"settings.agentBehaviour.removeSkill.title": "Remover habilidade",
"settings.agentBehaviour.removeSkill.confirm":
'Remover a habilidade "{{name}}"? Isso excluirá os arquivos da habilidade do disco.',
"settings.agentBehaviour.removeSkill.button": "Remover",
+ "settings.agentBehaviour.rules.description":
+ "Regras são arquivos de instrução que orientam o comportamento do agente. Elas são incluídas no prompt do sistema para cada conversa. Adicione caminhos de arquivos abaixo para incluir regras adicionais.",
"settings.agentBehaviour.instructionFiles": "Arquivos de instruções adicionais",
"settings.agentBehaviour.instructionFiles.description":
"Caminhos para arquivos de instruções adicionais no prompt do sistema",
+ "settings.agentBehaviour.mcpDetail.command": "Comando",
+ "settings.agentBehaviour.mcpDetail.args": "Argumentos",
+ "settings.agentBehaviour.mcpDetail.env": "Ambiente",
+ "settings.agentBehaviour.mcpDetail.disabled": "Este servidor está desativado.",
"settings.agentBehaviour.mcpEmpty":
"Nenhum servidor MCP configurado. Edite o arquivo de configuração do opencode para adicionar servidores MCP.",
"settings.agentBehaviour.workflowsPlaceholder":
"Fluxos de trabalho são gerenciados por arquivos de fluxo de trabalho no espaço de trabalho.",
- "settings.agentBehaviour.notImplemented": "Ainda não implementado.",
+ "settings.agentBehaviour.workflows.description":
+ "Fluxos de trabalho são comandos de barra personalizados definidos na sua configuração. Digite /command-name no chat para invocá-los. Os comandos são configurados no opencode.json na seção 'command'.",
+ "settings.agentBehaviour.workflows.empty":
+ "Nenhum comando personalizado configurado. Adicione comandos ao opencode.json para vê-los aqui.",
+ "settings.agentBehaviour.workflows.detail.description": "Descrição",
+ "settings.agentBehaviour.workflows.detail.template": "Modelo",
+
"settings.autoApprove.description":
"Defina como as ferramentas têm permissão para serem executadas. A maioria das ferramentas tem o padrão Permitir. doom_loop e external_directory têm o padrão Perguntar.",
"settings.autoApprove.level.allow": "Permitir",
@@ -1129,7 +1222,8 @@ export const dict = {
"settings.providers.defaultModel.title": "Modelo padrão",
"settings.providers.defaultModel.description": "Modelo principal para conversas",
"settings.providers.smallModel.title": "Modelo pequeno",
- "settings.providers.smallModel.description": "Modelo leve para geração de títulos e tarefas rápidas",
+ "settings.providers.smallModel.description":
+ "Modelo leve para geração de títulos, mensagens de commit, aprimoramento de prompts e outras tarefas rápidas",
"settings.providers.disabled": "Provedores desativados",
"settings.providers.disabled.description": "Provedores a ocultar da lista",
"settings.providers.enabled": "Provedores ativados (lista branca)",
@@ -1211,4 +1305,8 @@ export const dict = {
"settings.saveBar.warning.many": "Várias sessões estão em execução e serão interrompidas",
"settings.saveBar.saveAnyway": "Salvar mesmo assim",
"settings.saveBar.cancel": "Cancelar",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts
index 6bffbb3ca30..2f989657a50 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts
@@ -757,6 +757,21 @@ export const dict = {
"provider.custom.models.name.placeholder": "Naziv za prikaz",
"provider.custom.models.remove": "Ukloni model",
"provider.custom.models.add": "Dodaj model",
+ "provider.custom.models.fetch": "Preuzmi modele",
+ "provider.custom.models.fetching": "Preuzimanje\u2026",
+ "provider.custom.models.fetch.error": "Preuzimanje modela nije uspjelo: {{error}}",
+ "provider.custom.models.fetch.authError":
+ "Autentifikacija nije uspjela. Provjerite API ključ iznad i pokušajte ponovo.",
+ "provider.custom.models.fetch.empty": "Nisu pronađeni modeli na ovom serveru.",
+ "provider.custom.models.fetch.added": "Dodano {{count}} model(a).",
+ "provider.custom.models.fetch.allExist": "Svi preuzeti modeli su već dodani.",
+ "provider.custom.models.fetch.selectAll": "Odaberi sve",
+ "provider.custom.models.fetch.deselectAll": "Poništi odabir",
+ "provider.custom.models.fetch.found": "{{count}} modela pronađeno",
+ "provider.custom.models.fetch.showing": "Prikazano {{shown}} od {{total}}",
+ "provider.custom.models.fetch.search": "Pretraži modele\u2026",
+ "provider.custom.models.fetch.add": "Dodaj {{count}} model(a)",
+ "provider.custom.edit.title": "Uredi provajdera",
"provider.custom.headers.label": "Zaglavlja (opcionalno)",
"provider.custom.headers.key.label": "Zaglavlje",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -830,8 +845,11 @@ export const dict = {
"session.delete.button": "Izbriši sesiju",
"session.untitled": "Bez naslova",
"session.recent": "Nedavne",
+ "session.showHistory": "Prikaži historiju",
"session.search.placeholder": "Pretraži sesije...",
"session.empty": "Još nema sesija. Kliknite + za početak novog razgovora.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Samo ovaj repozitorij",
"session.cloud.import": "Uvezi iz oblaka",
"feedback.button": "Povratne informacije i podrška",
@@ -912,8 +930,7 @@ export const dict = {
"settings.autocomplete.title": "Automatsko dovršavanje",
"settings.notifications.title": "Obavještenja",
"settings.context.title": "Kontekst",
- "settings.terminal.title": "Terminal",
- "settings.prompts.title": "Upiti",
+
"settings.experimental.title": "Eksperimentalno",
"settings.language.title": "Jezik",
"settings.aboutKiloCode.title": "O Kilo Code-u",
@@ -964,6 +981,18 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Resetujte sve postavke Kilo Code ekstenzije na zadane vrijednosti. Ovo ne utiče na CLI ili backend konfiguraciju.",
"settings.aboutKiloCode.resetSettings.button": "Resetuj sve postavke",
+ "settings.aboutKiloCode.settingsTransfer.title": "Prijenos postavki",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Izvezite ili uvezite postavke za prijenos između VS Code instanci.",
+ "settings.aboutKiloCode.exportSettings": "Izvezi",
+ "settings.aboutKiloCode.importSettings": "Uvezi",
+ "settings.aboutKiloCode.importSettings.invalidJson": "Nevažeći JSON fajl. Odaberite važeći fajl s postavkama.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "Fajl ne sadrži važeće Kilo postavke.",
+ "settings.aboutKiloCode.importSettings.tooLarge": "Fajl je prevelik. Fajlovi s postavkama moraju biti manji od 1 MB.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "Ovaj fajl je izvezen iz novije verzije Kilo-a. Neke postavke mogu biti zanemarene.",
+ "settings.aboutKiloCode.importSettings.success":
+ "Postavke su uvezene. Pregledajte promjene iznad, a zatim kliknite Sačuvaj.",
"settings.agentBehaviour.subtab.modes": "Modovi",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -988,8 +1017,7 @@ export const dict = {
"common.add": "Dodaj",
"common.choose": "Odaberi…",
- "settings.notImplemented": "Ovaj dio još nije implementiran.",
- "settings.notImplemented.description": "Sadržavat će opcije konfiguracije i objašnjenje.",
+
"settings.autocomplete.autoTrigger.title": "Omogući automatsko inline dovršavanje",
"settings.autocomplete.autoTrigger.description": "Automatski prikaži prijedloge inline dovršavanja tokom tipkanja",
"settings.autocomplete.smartKeybinding.title": "Omogući pametnu prečicu inline zadatka",
@@ -1047,11 +1075,49 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Nucleus parametar uzorkovanja (0-1)",
"settings.agentBehaviour.maxSteps.title": "Maks. koraci",
"settings.agentBehaviour.maxSteps.description": "Maksimalne iteracije agenta",
+ "settings.agentBehaviour.hidden.title": "Skriveno",
+ "settings.agentBehaviour.hidden.description": "Sakrij ovog agenta iz prebacivača modova u unosu chata",
+ "settings.agentBehaviour.disable.title": "Onemogućeno",
+ "settings.agentBehaviour.disable.description": "Potpuno onemogući ovog agenta — neće se pojaviti nigdje",
+ "settings.agentBehaviour.badge.hidden": "skriveno",
+ "settings.agentBehaviour.badge.disabled": "onemogućeno",
"settings.agentBehaviour.discoveredSkills": "Otkrivene vještine",
"settings.agentBehaviour.noSkillsFound":
"Nisu pronađene vještine. Dodajte putanje mapa ili URL-ove ispod kako biste učinili vještine dostupnim.",
"settings.agentBehaviour.availableModes": "Dostupni prilagođeni modovi",
"settings.agentBehaviour.noModesFound": "Nisu pronađeni modovi.",
+ "settings.agentBehaviour.createMode": "Kreiraj novi mod",
+ "settings.agentBehaviour.createMode.name": "Naziv",
+ "settings.agentBehaviour.createMode.name.placeholder": "npr. reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "Jedinstveni identifikator moda. Koristite samo mala slova, brojeve i crtice.",
+ "settings.agentBehaviour.createMode.description": "Opis",
+ "settings.agentBehaviour.createMode.description.placeholder": "npr. Pregleda kod za kvalitet i dobre prakse",
+ "settings.agentBehaviour.createMode.description.help": "Kratak opis šta ovaj mod radi.",
+ "settings.agentBehaviour.createMode.prompt": "Sistemski prompt",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "npr. Vi ste recenzent koda. Fokusirajte se na kvalitet, dobre prakse i potencijalne greške.",
+ "settings.agentBehaviour.createMode.prompt.help": "Upute za AI agenta pri korištenju ovog moda.",
+ "settings.agentBehaviour.createMode.button": "Kreiraj",
+ "settings.agentBehaviour.createMode.cancel": "Otkaži",
+ "settings.agentBehaviour.createMode.nameRequired": "Naziv je obavezan",
+ "settings.agentBehaviour.createMode.nameInvalid": "Naziv smije sadržavati samo mala slova, brojeve i crtice",
+ "settings.agentBehaviour.createMode.nameTaken": "Mod s ovim nazivom već postoji",
+ "settings.agentBehaviour.importMode": "Uvezi",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Nevažeći naziv moda u datoteci. Naziv mora počinjati malim slovom i sadržavati samo mala slova, brojeve i crtice.",
+ "settings.agentBehaviour.importMode.nameTaken": "Mod s ovim nazivom već postoji.",
+ "settings.agentBehaviour.importMode.invalidJson":
+ "Nevažeća JSON datoteka. Molimo odaberite važeću datoteku definicije agenta.",
+ "settings.agentBehaviour.importMode.tooLarge": "Datoteka je prevelika. Definicije agenta moraju biti manje od 1 MB.",
+ "settings.agentBehaviour.exportMode": "Izvezi definiciju agenta",
+ "settings.agentBehaviour.editMode": "Uredi mod",
+ "settings.agentBehaviour.editMode.description": "Opis",
+ "settings.agentBehaviour.editMode.prompt": "Sistemski prompt",
+ "settings.agentBehaviour.editMode.save": "Gotovo",
+ "settings.agentBehaviour.editMode.back": "Nazad na listu",
+ "settings.agentBehaviour.editMode.native": "Ugrađeni mod (definicija samo za čitanje)",
+ "settings.agentBehaviour.editMode.promptOverride": "Prilagođeno nadjačavanje prompta za ovaj ugrađeni mod",
"settings.agentBehaviour.removeMode.title": "Ukloni mod",
"settings.agentBehaviour.removeMode.confirm":
'Ukloniti mod "{{name}}"? Ovo će onemogućiti mod ažuriranjem vaše konfiguracije.',
@@ -1060,18 +1126,42 @@ export const dict = {
"settings.agentBehaviour.removeMcp.confirm":
'Ukloniti MCP server "{{name}}"? Ovo će ga ukloniti iz vaše konfiguracije.',
"settings.agentBehaviour.removeMcp.button": "Ukloni",
+ "settings.agentBehaviour.editMcp": "Uredi MCP Server",
+ "settings.agentBehaviour.editMcp.transportLocal": "Lokalni server (stdio transport)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Udaljeni server (SSE/HTTP transport)",
+ "settings.agentBehaviour.editMcp.env": "Varijable okruženja",
+ "settings.agentBehaviour.editMcp.env.help": "Varijable proslijeđene procesu MCP servera.",
+ "settings.agentBehaviour.addMcp.command": "Naredba",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Argumenti",
+ "settings.agentBehaviour.addMcp.args.help": "Jedan argument po liniji. Putanje s razmacima se čuvaju kakve jesu.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "URL servera",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "Putanje mapa vještina",
"settings.agentBehaviour.skillUrls": "URL-ovi vještina",
"settings.agentBehaviour.removeSkill.title": "Ukloni vještinu",
"settings.agentBehaviour.removeSkill.confirm":
'Ukloniti vještinu "{{name}}"? Ovo će obrisati datoteke vještine sa diska.',
"settings.agentBehaviour.removeSkill.button": "Ukloni",
+ "settings.agentBehaviour.rules.description":
+ "Pravila su datoteke uputa koje usmjeravaju ponašanje agenta. Uključena su u sistemski prompt za svaki razgovor. Dodajte putanje datoteka ispod kako biste uključili dodatna pravila.",
"settings.agentBehaviour.instructionFiles": "Dodatne datoteke uputa",
"settings.agentBehaviour.instructionFiles.description": "Putanje do dodatnih datoteka uputa u sistemskom promptu",
+ "settings.agentBehaviour.mcpDetail.command": "Naredba",
+ "settings.agentBehaviour.mcpDetail.args": "Argumenti",
+ "settings.agentBehaviour.mcpDetail.env": "Okruženje",
+ "settings.agentBehaviour.mcpDetail.disabled": "Ovaj server je onemogućen.",
"settings.agentBehaviour.mcpEmpty":
"Nema konfiguriranih MCP servera. Uredite konfiguracijsku datoteku opencode za dodavanje MCP servera.",
"settings.agentBehaviour.workflowsPlaceholder": "Tokovi rada se upravljaju putem datoteka tokova rada.",
- "settings.agentBehaviour.notImplemented": "Još nije implementirano.",
+ "settings.agentBehaviour.workflows.description":
+ "Tokovi rada su prilagođene slash komande definirane u vašoj konfiguraciji. Upišite /command-name u chat da ih pokrenete. Komande se konfiguriraju u opencode.json pod sekcijom 'command'.",
+ "settings.agentBehaviour.workflows.empty":
+ "Nema konfiguriranih prilagođenih komandi. Dodajte komande u opencode.json da ih vidite ovdje.",
+ "settings.agentBehaviour.workflows.detail.description": "Opis",
+ "settings.agentBehaviour.workflows.detail.template": "Predložak",
+
"settings.autoApprove.description":
"Definišite kako je dozvoljeno pokretanje alata. Većina alata je podrazumijevano na Dozvoli. doom_loop i external_directory su podrazumijevano na Pitaj.",
"settings.autoApprove.level.allow": "Dozvoli",
@@ -1128,7 +1218,8 @@ export const dict = {
"settings.providers.defaultModel.title": "Zadani model",
"settings.providers.defaultModel.description": "Primarni model za razgovore",
"settings.providers.smallModel.title": "Mali model",
- "settings.providers.smallModel.description": "Lagani model za generiranje naslova i brze zadatke",
+ "settings.providers.smallModel.description":
+ "Lagani model za generiranje naslova, commit poruka, poboljšanje promptova i druge brze zadatke",
"settings.providers.disabled": "Onemogućeni pružatelji",
"settings.providers.disabled.description": "Pružatelji za skrivanje s popisa",
"settings.providers.enabled": "Omogućeni pružatelji (bijela lista)",
@@ -1210,4 +1301,8 @@ export const dict = {
"settings.saveBar.warning.many": "Nekoliko sesija je pokrenuto i bit će prekinuto",
"settings.saveBar.saveAnyway": "Spremi svejedno",
"settings.saveBar.cancel": "Otkaži",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts
index 512571f61b6..d0cc455b133 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts
@@ -751,6 +751,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "Visningsnavn",
"provider.custom.models.remove": "Fjern model",
"provider.custom.models.add": "Tilføj model",
+ "provider.custom.models.fetch": "Hent modeller",
+ "provider.custom.models.fetching": "Henter\u2026",
+ "provider.custom.models.fetch.error": "Kunne ikke hente modeller: {{error}}",
+ "provider.custom.models.fetch.authError": "Godkendelse mislykkedes. Kontrollér API-nøglen ovenfor, og prøv igen.",
+ "provider.custom.models.fetch.empty": "Ingen modeller fundet på denne server.",
+ "provider.custom.models.fetch.added": "{{count}} model(ler) tilføjet.",
+ "provider.custom.models.fetch.allExist": "Alle hentede modeller er allerede tilføjet.",
+ "provider.custom.models.fetch.selectAll": "Vælg alle",
+ "provider.custom.models.fetch.deselectAll": "Fravælg alle",
+ "provider.custom.models.fetch.found": "{{count}} modeller fundet",
+ "provider.custom.models.fetch.showing": "Viser {{shown}} af {{total}}",
+ "provider.custom.models.fetch.search": "Søg modeller\u2026",
+ "provider.custom.models.fetch.add": "Tilføj {{count}} model(ler)",
+ "provider.custom.edit.title": "Rediger udbyder",
"provider.custom.headers.label": "Headers (valgfrit)",
"provider.custom.headers.key.label": "Header",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -824,8 +838,11 @@ export const dict = {
"session.delete.button": "Slet session",
"session.untitled": "Unavngivet",
"session.recent": "Seneste",
+ "session.showHistory": "Vis historik",
"session.search.placeholder": "Søg sessioner...",
"session.empty": "Ingen sessioner endnu. Klik + for at starte en ny samtale.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Kun dette repository",
"session.cloud.import": "Importér fra skyen",
"feedback.button": "Feedback & support",
@@ -906,8 +923,7 @@ export const dict = {
"settings.autocomplete.title": "Autofuldførelse",
"settings.notifications.title": "Notifikationer",
"settings.context.title": "Kontekst",
- "settings.terminal.title": "Terminal",
- "settings.prompts.title": "Prompts",
+
"settings.experimental.title": "Eksperimentelt",
"settings.language.title": "Sprog",
"settings.aboutKiloCode.title": "Om Kilo Code",
@@ -959,6 +975,18 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Nulstil alle Kilo Code-udvidelsesindstillinger til standardværdierne. Dette påvirker ikke CLI- eller backend-konfiguration.",
"settings.aboutKiloCode.resetSettings.button": "Nulstil alle indstillinger",
+ "settings.aboutKiloCode.settingsTransfer.title": "Overførsel af indstillinger",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Eksportér eller importér dine indstillinger for at overføre dem mellem VS Code-instanser.",
+ "settings.aboutKiloCode.exportSettings": "Eksportér",
+ "settings.aboutKiloCode.importSettings": "Importér",
+ "settings.aboutKiloCode.importSettings.invalidJson": "Ugyldig JSON-fil. Vælg venligst en gyldig indstillingsfil.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "Filen indeholder ikke gyldige Kilo-indstillinger.",
+ "settings.aboutKiloCode.importSettings.tooLarge": "Filen er for stor. Indstillingsfiler skal være under 1 MB.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "Denne fil blev eksporteret fra en nyere version af Kilo. Nogle indstillinger kan blive ignoreret.",
+ "settings.aboutKiloCode.importSettings.success":
+ "Indstillinger importeret. Gennemgå ændringerne ovenfor, og klik derefter på Gem.",
"settings.agentBehaviour.subtab.modes": "Tilstande",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -983,8 +1011,7 @@ export const dict = {
"common.add": "Tilføj",
"common.choose": "Vælg…",
- "settings.notImplemented": "Denne sektion er endnu ikke implementeret.",
- "settings.notImplemented.description": "Den vil indeholde konfigurationsmuligheder og forklarende tekst.",
+
"settings.autocomplete.autoTrigger.title": "Aktiver automatisk inline-fuldførelse",
"settings.autocomplete.autoTrigger.description": "Vis automatisk inline-fuldførelsesforslag under indtastning",
"settings.autocomplete.smartKeybinding.title": "Aktiver smart inline-opgave-tastaturgenvej",
@@ -1042,11 +1069,48 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Nucleus-samplingparameter (0-1)",
"settings.agentBehaviour.maxSteps.title": "Maks. trin",
"settings.agentBehaviour.maxSteps.description": "Maksimale agentiterationer",
+ "settings.agentBehaviour.hidden.title": "Skjult",
+ "settings.agentBehaviour.hidden.description": "Skjul denne agent fra tilstandsvælgeren i chatinput",
+ "settings.agentBehaviour.disable.title": "Deaktiveret",
+ "settings.agentBehaviour.disable.description": "Deaktiver denne agent fuldstændigt — den vises ingen steder",
+ "settings.agentBehaviour.badge.hidden": "skjult",
+ "settings.agentBehaviour.badge.disabled": "deaktiveret",
"settings.agentBehaviour.discoveredSkills": "Opdagede skills",
"settings.agentBehaviour.noSkillsFound":
"Ingen skills fundet. Tilføj skill-mappestier eller URL'er nedenfor for at gøre skills tilgængelige.",
"settings.agentBehaviour.availableModes": "Tilgængelige brugerdefinerede tilstande",
"settings.agentBehaviour.noModesFound": "Ingen tilstande fundet.",
+ "settings.agentBehaviour.createMode": "Opret ny tilstand",
+ "settings.agentBehaviour.createMode.name": "Navn",
+ "settings.agentBehaviour.createMode.name.placeholder": "f.eks. reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "Unik identifikator for tilstanden. Brug kun små bogstaver, tal og bindestreger.",
+ "settings.agentBehaviour.createMode.description": "Beskrivelse",
+ "settings.agentBehaviour.createMode.description.placeholder": "f.eks. Gennemgår kode for kvalitet og bedste praksis",
+ "settings.agentBehaviour.createMode.description.help": "Kort beskrivelse af hvad denne tilstand gør.",
+ "settings.agentBehaviour.createMode.prompt": "Systemprompt",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "f.eks. Du er en kodegennemgangsrobot. Fokuser på kodekvalitet, bedste praksis og potentielle fejl.",
+ "settings.agentBehaviour.createMode.prompt.help": "Instruktioner til AI-agenten ved brug af denne tilstand.",
+ "settings.agentBehaviour.createMode.button": "Opret",
+ "settings.agentBehaviour.createMode.cancel": "Annuller",
+ "settings.agentBehaviour.createMode.nameRequired": "Navn er påkrævet",
+ "settings.agentBehaviour.createMode.nameInvalid": "Navn må kun indeholde små bogstaver, tal og bindestreger",
+ "settings.agentBehaviour.createMode.nameTaken": "En tilstand med dette navn eksisterer allerede",
+ "settings.agentBehaviour.importMode": "Importér",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Ugyldigt tilstandsnavn i filen. Navnet skal starte med et lille bogstav og kun indeholde små bogstaver, tal og bindestreger.",
+ "settings.agentBehaviour.importMode.nameTaken": "En tilstand med dette navn eksisterer allerede.",
+ "settings.agentBehaviour.importMode.invalidJson": "Ugyldig JSON-fil. Vælg venligst en gyldig agentdefinitionsfil.",
+ "settings.agentBehaviour.importMode.tooLarge": "Filen er for stor. Agentdefinitioner skal være under 1 MB.",
+ "settings.agentBehaviour.exportMode": "Eksportér agentdefinition",
+ "settings.agentBehaviour.editMode": "Rediger tilstand",
+ "settings.agentBehaviour.editMode.description": "Beskrivelse",
+ "settings.agentBehaviour.editMode.prompt": "Systemprompt",
+ "settings.agentBehaviour.editMode.save": "Færdig",
+ "settings.agentBehaviour.editMode.back": "Tilbage til listen",
+ "settings.agentBehaviour.editMode.native": "Indbygget tilstand (skrivebeskyttet definition)",
+ "settings.agentBehaviour.editMode.promptOverride": "Tilpasset prompt-tilsidesættelse for denne indbyggede tilstand",
"settings.agentBehaviour.removeMode.title": "Fjern tilstand",
"settings.agentBehaviour.removeMode.confirm":
'Vil du fjerne tilstanden "{{name}}"? Dette vil deaktivere tilstanden ved at opdatere din konfiguration.',
@@ -1055,18 +1119,42 @@ export const dict = {
"settings.agentBehaviour.removeMcp.confirm":
'Vil du fjerne MCP-serveren "{{name}}"? Dette vil fjerne den fra din konfiguration.',
"settings.agentBehaviour.removeMcp.button": "Fjern",
+ "settings.agentBehaviour.editMcp": "Rediger MCP-server",
+ "settings.agentBehaviour.editMcp.transportLocal": "Lokal server (stdio-transport)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Fjernserver (SSE/HTTP-transport)",
+ "settings.agentBehaviour.editMcp.env": "Miljøvariabler",
+ "settings.agentBehaviour.editMcp.env.help": "Variabler der sendes til MCP-serverprocessen.",
+ "settings.agentBehaviour.addMcp.command": "Kommando",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Argumenter",
+ "settings.agentBehaviour.addMcp.args.help": "Ét argument per linje. Stier med mellemrum bevares som de er.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "Server-URL",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "Skill-mappestier",
"settings.agentBehaviour.skillUrls": "Skill-URL'er",
"settings.agentBehaviour.removeSkill.title": "Fjern færdighed",
"settings.agentBehaviour.removeSkill.confirm":
'Vil du fjerne færdigheden "{{name}}"? Dette vil slette færdighedsfilerne fra disken.',
"settings.agentBehaviour.removeSkill.button": "Fjern",
+ "settings.agentBehaviour.rules.description":
+ "Regler er instruktionsfiler, der styrer agentens adfærd. De inkluderes i systemprompten for hver samtale. Tilføj filstier nedenfor for at inkludere yderligere regler.",
"settings.agentBehaviour.instructionFiles": "Yderligere instruktionsfiler",
"settings.agentBehaviour.instructionFiles.description": "Stier til yderligere instruktionsfiler i systemprompten",
+ "settings.agentBehaviour.mcpDetail.command": "Kommando",
+ "settings.agentBehaviour.mcpDetail.args": "Argumenter",
+ "settings.agentBehaviour.mcpDetail.env": "Miljø",
+ "settings.agentBehaviour.mcpDetail.disabled": "Denne server er deaktiveret.",
"settings.agentBehaviour.mcpEmpty":
"Ingen MCP-servere konfigureret. Rediger opencode-konfigurationsfilen for at tilføje MCP-servere.",
"settings.agentBehaviour.workflowsPlaceholder": "Workflows administreres via workflow-filer i dit arbejdsområde.",
- "settings.agentBehaviour.notImplemented": "Endnu ikke implementeret.",
+ "settings.agentBehaviour.workflows.description":
+ "Workflows er brugerdefinerede slash-kommandoer defineret i din konfiguration. Skriv /command-name i chatten for at aktivere dem. Kommandoer konfigureres i opencode.json under sektionen 'command'.",
+ "settings.agentBehaviour.workflows.empty":
+ "Ingen brugerdefinerede kommandoer konfigureret. Tilføj kommandoer til opencode.json for at se dem her.",
+ "settings.agentBehaviour.workflows.detail.description": "Beskrivelse",
+ "settings.agentBehaviour.workflows.detail.template": "Skabelon",
+
"settings.autoApprove.description":
"Definer, hvordan værktøjer må køre. De fleste værktøjer er som standard indstillet til Tillad. doom_loop og external_directory er som standard indstillet til Spørg.",
"settings.autoApprove.level.allow": "Tillad",
@@ -1120,7 +1208,8 @@ export const dict = {
"settings.providers.defaultModel.title": "Standardmodel",
"settings.providers.defaultModel.description": "Primær model til samtaler",
"settings.providers.smallModel.title": "Lille model",
- "settings.providers.smallModel.description": "Letvægtsmodel til titelgenerering og hurtige opgaver",
+ "settings.providers.smallModel.description":
+ "Letvægtsmodel til titelgenerering, generering af commit-beskeder, prompt-forbedring og andre hurtige opgaver",
"settings.providers.disabled": "Deaktiverede udbydere",
"settings.providers.disabled.description": "Udbydere at skjule fra listen",
"settings.providers.enabled": "Aktiverede udbydere (hvidliste)",
@@ -1203,4 +1292,8 @@ export const dict = {
"settings.saveBar.warning.many": "Flere sessioner kører og vil blive afbrudt",
"settings.saveBar.saveAnyway": "Gem alligevel",
"settings.saveBar.cancel": "Annuller",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts
index 5f6018e047c..8ff2588e346 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts
@@ -762,6 +762,21 @@ export const dict = {
"provider.custom.models.name.placeholder": "Anzeigename",
"provider.custom.models.remove": "Modell entfernen",
"provider.custom.models.add": "Modell hinzufügen",
+ "provider.custom.models.fetch": "Modelle abrufen",
+ "provider.custom.models.fetching": "Abrufen\u2026",
+ "provider.custom.models.fetch.error": "Modelle konnten nicht abgerufen werden: {{error}}",
+ "provider.custom.models.fetch.authError":
+ "Authentifizierung fehlgeschlagen. Überprüfen Sie den API-Schlüssel oben und versuchen Sie es erneut.",
+ "provider.custom.models.fetch.empty": "Keine Modelle auf diesem Server gefunden.",
+ "provider.custom.models.fetch.added": "{{count}} Modell(e) hinzugefügt.",
+ "provider.custom.models.fetch.allExist": "Alle abgerufenen Modelle sind bereits hinzugefügt.",
+ "provider.custom.models.fetch.selectAll": "Alle auswählen",
+ "provider.custom.models.fetch.deselectAll": "Alle abwählen",
+ "provider.custom.models.fetch.found": "{{count}} Modelle gefunden",
+ "provider.custom.models.fetch.showing": "{{shown}} von {{total}} angezeigt",
+ "provider.custom.models.fetch.search": "Modelle suchen\u2026",
+ "provider.custom.models.fetch.add": "{{count}} Modell(e) hinzufügen",
+ "provider.custom.edit.title": "Anbieter bearbeiten",
"provider.custom.headers.label": "Header (optional)",
"provider.custom.headers.key.label": "Header",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -835,8 +850,11 @@ export const dict = {
"session.delete.button": "Sitzung löschen",
"session.untitled": "Unbenannt",
"session.recent": "Kürzlich",
+ "session.showHistory": "Verlauf anzeigen",
"session.search.placeholder": "Sitzungen suchen...",
"session.empty": "Noch keine Sitzungen. Klicke + um eine neue Unterhaltung zu starten.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Nur dieses Repository",
"session.cloud.import": "Aus der Cloud importieren",
"feedback.button": "Feedback & Support",
@@ -917,8 +935,7 @@ export const dict = {
"settings.autocomplete.title": "Autovervollständigung",
"settings.notifications.title": "Benachrichtigungen",
"settings.context.title": "Kontext",
- "settings.terminal.title": "Terminal",
- "settings.prompts.title": "Prompts",
+
"settings.experimental.title": "Experimentell",
"settings.language.title": "Sprache",
"settings.aboutKiloCode.title": "Über Kilo Code",
@@ -971,6 +988,20 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Alle Kilo Code-Erweiterungseinstellungen auf Standardwerte zurücksetzen. Dies hat keinen Einfluss auf die CLI- oder Backend-Konfiguration.",
"settings.aboutKiloCode.resetSettings.button": "Alle Einstellungen zurücksetzen",
+ "settings.aboutKiloCode.settingsTransfer.title": "Einstellungen übertragen",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Exportieren oder importieren Sie Ihre Einstellungen, um sie zwischen VS Code-Instanzen zu übertragen.",
+ "settings.aboutKiloCode.exportSettings": "Exportieren",
+ "settings.aboutKiloCode.importSettings": "Importieren",
+ "settings.aboutKiloCode.importSettings.invalidJson":
+ "Ungültige JSON-Datei. Bitte wählen Sie eine gültige Einstellungsdatei aus.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "Die Datei enthält keine gültigen Kilo-Einstellungen.",
+ "settings.aboutKiloCode.importSettings.tooLarge":
+ "Die Datei ist zu groß. Einstellungsdateien müssen kleiner als 1 MB sein.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "Diese Datei wurde mit einer neueren Version von Kilo exportiert. Einige Einstellungen werden möglicherweise ignoriert.",
+ "settings.aboutKiloCode.importSettings.success":
+ "Einstellungen importiert. Überprüfen Sie die obigen Änderungen und klicken Sie dann auf Speichern.",
"settings.agentBehaviour.subtab.modes": "Modi",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -996,9 +1027,7 @@ export const dict = {
"common.add": "Hinzufügen",
"common.choose": "Auswählen…",
- "settings.notImplemented": "Dieser Bereich ist noch nicht implementiert.",
- "settings.notImplemented.description":
- "Er wird Konfigurationsoptionen und erklärende Texte zur ausgewählten Einstellungskategorie enthalten.",
+
"settings.autocomplete.autoTrigger.title": "Automatische Inline-Vervollständigung aktivieren",
"settings.autocomplete.autoTrigger.description":
"Inline-Vervollständigungsvorschläge beim Tippen automatisch anzeigen",
@@ -1060,11 +1089,50 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Nucleus-Sampling-Parameter (0-1)",
"settings.agentBehaviour.maxSteps.title": "Max. Schritte",
"settings.agentBehaviour.maxSteps.description": "Maximale Agent-Iterationen",
+ "settings.agentBehaviour.hidden.title": "Versteckt",
+ "settings.agentBehaviour.hidden.description": "Diesen Agent im Modus-Umschalter der Chat-Eingabe ausblenden",
+ "settings.agentBehaviour.disable.title": "Deaktiviert",
+ "settings.agentBehaviour.disable.description": "Diesen Agent vollständig deaktivieren — er wird nirgends angezeigt",
+ "settings.agentBehaviour.badge.hidden": "versteckt",
+ "settings.agentBehaviour.badge.disabled": "deaktiviert",
"settings.agentBehaviour.discoveredSkills": "Erkannte Skills",
"settings.agentBehaviour.noSkillsFound":
"Keine Skills gefunden. Fügen Sie unten Skill-Ordnerpfade oder URLs hinzu, um Skills verfügbar zu machen.",
"settings.agentBehaviour.availableModes": "Verfügbare benutzerdefinierte Modi",
"settings.agentBehaviour.noModesFound": "Keine Modi gefunden.",
+ "settings.agentBehaviour.createMode": "Neuen Modus erstellen",
+ "settings.agentBehaviour.createMode.name": "Name",
+ "settings.agentBehaviour.createMode.name.placeholder": "z.B. reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "Eindeutiger Bezeichner für den Modus. Nur Kleinbuchstaben, Zahlen und Bindestriche.",
+ "settings.agentBehaviour.createMode.description": "Beschreibung",
+ "settings.agentBehaviour.createMode.description.placeholder": "z.B. Überprüft Code auf Qualität und Best Practices",
+ "settings.agentBehaviour.createMode.description.help": "Kurze Beschreibung, was dieser Modus macht.",
+ "settings.agentBehaviour.createMode.prompt": "System-Prompt",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "z.B. Du bist ein Code-Reviewer. Konzentriere dich auf Codequalität, Best Practices und potenzielle Fehler.",
+ "settings.agentBehaviour.createMode.prompt.help": "Anweisungen für den KI-Agenten bei Verwendung dieses Modus.",
+ "settings.agentBehaviour.createMode.button": "Erstellen",
+ "settings.agentBehaviour.createMode.cancel": "Abbrechen",
+ "settings.agentBehaviour.createMode.nameRequired": "Name ist erforderlich",
+ "settings.agentBehaviour.createMode.nameInvalid": "Name darf nur Kleinbuchstaben, Zahlen und Bindestriche enthalten",
+ "settings.agentBehaviour.createMode.nameTaken": "Ein Modus mit diesem Namen existiert bereits",
+ "settings.agentBehaviour.importMode": "Importieren",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Ungültiger Modusname in der Datei. Der Name muss mit einem Kleinbuchstaben beginnen und darf nur Kleinbuchstaben, Zahlen und Bindestriche enthalten.",
+ "settings.agentBehaviour.importMode.nameTaken": "Ein Modus mit diesem Namen existiert bereits.",
+ "settings.agentBehaviour.importMode.invalidJson":
+ "Ungültige JSON-Datei. Bitte wählen Sie eine gültige Agenten-Definitionsdatei aus.",
+ "settings.agentBehaviour.importMode.tooLarge": "Datei ist zu groß. Agentdefinitionen müssen unter 1 MB sein.",
+ "settings.agentBehaviour.exportMode": "Agenten-Definition exportieren",
+ "settings.agentBehaviour.editMode": "Modus bearbeiten",
+ "settings.agentBehaviour.editMode.description": "Beschreibung",
+ "settings.agentBehaviour.editMode.prompt": "System-Prompt",
+ "settings.agentBehaviour.editMode.save": "Fertig",
+ "settings.agentBehaviour.editMode.back": "Zurück zur Liste",
+ "settings.agentBehaviour.editMode.native": "Eingebauter Modus (schreibgeschützte Definition)",
+ "settings.agentBehaviour.editMode.promptOverride":
+ "Benutzerdefinierte Prompt-Überschreibung für diesen eingebauten Modus",
"settings.agentBehaviour.removeMode.title": "Modus entfernen",
"settings.agentBehaviour.removeMode.confirm":
'Modus "{{name}}" entfernen? Dadurch wird der Modus durch Aktualisierung Ihrer Konfiguration deaktiviert.',
@@ -1073,19 +1141,44 @@ export const dict = {
"settings.agentBehaviour.removeMcp.confirm":
'MCP-Server "{{name}}" entfernen? Dadurch wird er aus Ihrer Konfiguration entfernt.',
"settings.agentBehaviour.removeMcp.button": "Entfernen",
+ "settings.agentBehaviour.editMcp": "MCP-Server bearbeiten",
+ "settings.agentBehaviour.editMcp.transportLocal": "Lokaler Server (stdio-Transport)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Remote-Server (SSE/HTTP-Transport)",
+ "settings.agentBehaviour.editMcp.env": "Umgebungsvariablen",
+ "settings.agentBehaviour.editMcp.env.help": "Variablen, die an den MCP-Serverprozess übergeben werden.",
+ "settings.agentBehaviour.addMcp.command": "Befehl",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Argumente",
+ "settings.agentBehaviour.addMcp.args.help":
+ "Ein Argument pro Zeile. Pfade mit Leerzeichen werden unverändert übernommen.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "Server-URL",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "Skill-Ordnerpfade",
"settings.agentBehaviour.skillUrls": "Skill-URLs",
"settings.agentBehaviour.removeSkill.title": "Skill entfernen",
"settings.agentBehaviour.removeSkill.confirm":
'Skill "{{name}}" entfernen? Dadurch werden die Skill-Dateien vom Datenträger gelöscht.',
"settings.agentBehaviour.removeSkill.button": "Entfernen",
+ "settings.agentBehaviour.rules.description":
+ "Regeln sind Anweisungsdateien, die das Verhalten des Agenten steuern. Sie werden in den System-Prompt für jede Konversation eingebunden. Fügen Sie unten Dateipfade hinzu, um zusätzliche Regeln einzubinden.",
"settings.agentBehaviour.instructionFiles": "Zusätzliche Anweisungsdateien",
"settings.agentBehaviour.instructionFiles.description": "Pfade zu zusätzlichen Anweisungsdateien im System-Prompt",
+ "settings.agentBehaviour.mcpDetail.command": "Befehl",
+ "settings.agentBehaviour.mcpDetail.args": "Argumente",
+ "settings.agentBehaviour.mcpDetail.env": "Umgebung",
+ "settings.agentBehaviour.mcpDetail.disabled": "Dieser Server ist deaktiviert.",
"settings.agentBehaviour.mcpEmpty":
"Keine MCP-Server konfiguriert. Bearbeiten Sie die opencode-Konfigurationsdatei, um MCP-Server hinzuzufügen.",
"settings.agentBehaviour.workflowsPlaceholder":
"Workflows werden über Workflow-Dateien in Ihrem Arbeitsbereich verwaltet.",
- "settings.agentBehaviour.notImplemented": "Noch nicht implementiert.",
+ "settings.agentBehaviour.workflows.description":
+ "Workflows sind benutzerdefinierte Slash-Befehle, die in Ihrer Konfiguration definiert sind. Geben Sie /command-name im Chat ein, um sie aufzurufen. Befehle werden in opencode.json im Abschnitt 'command' konfiguriert.",
+ "settings.agentBehaviour.workflows.empty":
+ "Keine benutzerdefinierten Befehle konfiguriert. Fügen Sie Befehle zu opencode.json hinzu, um sie hier zu sehen.",
+ "settings.agentBehaviour.workflows.detail.description": "Beschreibung",
+ "settings.agentBehaviour.workflows.detail.template": "Vorlage",
+
"settings.autoApprove.description":
"Legen Sie fest, wie Tools ausgeführt werden dürfen. Die meisten Tools sind standardmäßig auf Zulassen eingestellt. doom_loop und external_directory sind standardmäßig auf Fragen eingestellt.",
"settings.autoApprove.level.allow": "Erlauben",
@@ -1141,7 +1234,8 @@ export const dict = {
"settings.providers.defaultModel.title": "Standardmodell",
"settings.providers.defaultModel.description": "Primäres Modell für Gespräche",
"settings.providers.smallModel.title": "Kleines Modell",
- "settings.providers.smallModel.description": "Leichtgewichtiges Modell für Titelgenerierung und schnelle Aufgaben",
+ "settings.providers.smallModel.description":
+ "Leichtgewichtiges Modell für Titelgenerierung, Commit-Nachrichten, Prompt-Verbesserung und andere schnelle Aufgaben",
"settings.providers.disabled": "Deaktivierte Anbieter",
"settings.providers.disabled.description": "Anbieter aus der Anbieterliste ausblenden",
"settings.providers.enabled": "Aktivierte Anbieter (Whitelist)",
@@ -1225,4 +1319,8 @@ export const dict = {
"settings.saveBar.warning.many": "Mehrere Sitzungen laufen und werden unterbrochen",
"settings.saveBar.saveAnyway": "Trotzdem speichern",
"settings.saveBar.cancel": "Abbrechen",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
} satisfies Partial>
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts
index 7558d52c738..213bded0966 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts
@@ -752,6 +752,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "Display Name",
"provider.custom.models.remove": "Remove model",
"provider.custom.models.add": "Add model",
+ "provider.custom.models.fetch": "Fetch models",
+ "provider.custom.models.fetching": "Fetching\u2026",
+ "provider.custom.models.fetch.error": "Failed to fetch models: {{error}}",
+ "provider.custom.models.fetch.authError": "Authentication failed. Check the API key above and try again.",
+ "provider.custom.models.fetch.empty": "No models found on this server.",
+ "provider.custom.models.fetch.added": "Added {{count}} model(s).",
+ "provider.custom.models.fetch.allExist": "All fetched models are already added.",
+ "provider.custom.models.fetch.selectAll": "Select all",
+ "provider.custom.models.fetch.deselectAll": "Deselect all",
+ "provider.custom.models.fetch.found": "{{count}} models found",
+ "provider.custom.models.fetch.showing": "Showing {{shown}} of {{total}}",
+ "provider.custom.models.fetch.search": "Search models\u2026",
+ "provider.custom.models.fetch.add": "Add {{count}} model(s)",
+ "provider.custom.edit.title": "Edit provider",
"provider.custom.headers.label": "Headers (optional)",
"provider.custom.headers.key.label": "Header",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -824,16 +838,19 @@ export const dict = {
"session.delete.button": "Delete session",
"session.untitled": "Untitled",
"session.recent": "Recent",
+ "session.showHistory": "Show History",
"session.search.placeholder": "Search sessions...",
"session.empty": "No sessions yet. Click + to start a new conversation.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Only this repository",
- "session.cloud.import": "Import from cloud",
+ "session.cloud.import": "Import session",
"feedback.button": "Feedback & Support",
"feedback.dialog.message": "We'd love to hear your feedback or help with any issues you're experiencing.",
"feedback.dialog.github": "Report an issue on GitHub",
"feedback.dialog.discord": "Join our Discord community",
"feedback.dialog.support": "Customer Support",
- "session.cloud.import.title": "Import from cloud",
+ "session.cloud.import.title": "Import session",
"session.cloud.import.placeholder": "Session ID, URL, or kilo import command",
"session.cloud.import.button": "Import",
"session.cloud.import.invalid": "Invalid session ID format",
@@ -906,8 +923,7 @@ export const dict = {
"settings.autocomplete.title": "Autocomplete",
"settings.notifications.title": "Notifications",
"settings.context.title": "Context",
- "settings.terminal.title": "Terminal",
- "settings.prompts.title": "Prompts",
+
"settings.experimental.title": "Experimental",
"settings.language.title": "Language",
"settings.aboutKiloCode.title": "About Kilo Code",
@@ -958,6 +974,17 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Reset all Kilo Code extension settings to their default values. This does not affect CLI or backend configuration.",
"settings.aboutKiloCode.resetSettings.button": "Reset All Settings",
+ "settings.aboutKiloCode.settingsTransfer.title": "Settings Transfer",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Export or import your settings to transfer them between VS Code instances.",
+ "settings.aboutKiloCode.exportSettings": "Export",
+ "settings.aboutKiloCode.importSettings": "Import",
+ "settings.aboutKiloCode.importSettings.invalidJson": "Invalid JSON file. Please select a valid settings file.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "File does not contain valid Kilo settings.",
+ "settings.aboutKiloCode.importSettings.tooLarge": "File is too large. Settings files must be under 1 MB.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "This file was exported from a newer version of Kilo. Some settings may be ignored.",
+ "settings.aboutKiloCode.importSettings.success": "Settings imported. Review the changes above, then click Save.",
"settings.agentBehaviour.subtab.modes": "Modes",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -984,10 +1011,6 @@ export const dict = {
"common.add": "Add",
"common.choose": "Choose…",
- "settings.notImplemented": "This section is not implemented yet.",
- "settings.notImplemented.description":
- "It will contain configuration options and explanatory text related to the selected settings category. During reimplementation, use this space to validate layout, spacing, scrolling behavior, and navigation state before wiring up real controls.",
-
"settings.autocomplete.autoTrigger.title": "Enable automatic inline completions",
"settings.autocomplete.autoTrigger.description": "Automatically show inline completion suggestions as you type",
"settings.autocomplete.smartKeybinding.title": "Enable smart inline task keybinding",
@@ -1048,6 +1071,12 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Nucleus sampling parameter (0-1)",
"settings.agentBehaviour.maxSteps.title": "Max Steps",
"settings.agentBehaviour.maxSteps.description": "Maximum agentic iterations",
+ "settings.agentBehaviour.hidden.title": "Hidden",
+ "settings.agentBehaviour.hidden.description": "Hide this agent from the mode switcher in the chat input",
+ "settings.agentBehaviour.disable.title": "Disabled",
+ "settings.agentBehaviour.disable.description": "Fully disable this agent — it will not appear anywhere",
+ "settings.agentBehaviour.badge.hidden": "hidden",
+ "settings.agentBehaviour.badge.disabled": "disabled",
"settings.agentBehaviour.discoveredSkills": "Discovered Skills",
"settings.agentBehaviour.noSkillsFound":
"No skills discovered. Add skill folder paths or URLs below to make skills available.",
@@ -1062,15 +1091,71 @@ export const dict = {
"settings.agentBehaviour.removeSkill.title": "Remove skill",
"settings.agentBehaviour.removeSkill.confirm": 'Remove skill "{{name}}"? This will delete the skill files from disk.',
"settings.agentBehaviour.removeSkill.button": "Remove",
+ "settings.agentBehaviour.rules.description":
+ "Rules are instruction files that guide agent behaviour. They are included in the system prompt for every conversation. Add file paths below to include additional rules.",
"settings.agentBehaviour.instructionFiles": "Additional Instruction Files",
"settings.agentBehaviour.instructionFiles.description":
"Paths to additional instruction files that are included in the system prompt",
"settings.agentBehaviour.removeMcp.title": "Remove MCP server",
"settings.agentBehaviour.removeMcp.confirm": 'Remove MCP server "{{name}}"? This will remove it from your config.',
"settings.agentBehaviour.removeMcp.button": "Remove",
+ "settings.agentBehaviour.mcpDetail.command": "Command",
+ "settings.agentBehaviour.mcpDetail.args": "Arguments",
+ "settings.agentBehaviour.mcpDetail.env": "Environment",
+ "settings.agentBehaviour.mcpDetail.disabled": "This server is disabled.",
+ "settings.agentBehaviour.editMcp": "Edit MCP Server",
+ "settings.agentBehaviour.editMcp.transportLocal": "Local server (stdio transport)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Remote server (SSE/HTTP transport)",
+ "settings.agentBehaviour.editMcp.env": "Environment Variables",
+ "settings.agentBehaviour.editMcp.env.help": "Variables passed to the MCP server process.",
+ "settings.agentBehaviour.addMcp.command": "Command",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Arguments",
+ "settings.agentBehaviour.addMcp.args.help": "One argument per line. Paths with spaces are preserved as-is.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "Server URL",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.mcpEmpty": "No MCP servers configured. Edit the opencode config file to add MCP servers.",
"settings.agentBehaviour.workflowsPlaceholder": "Workflows are managed via workflow files in your workspace.",
- "settings.agentBehaviour.notImplemented": "Not yet implemented.",
+ "settings.agentBehaviour.workflows.description":
+ "Workflows are custom slash commands defined in your config. Type /command-name in the chat to invoke them. Commands are configured in opencode.json under the 'command' section.",
+ "settings.agentBehaviour.workflows.empty":
+ "No custom commands configured. Add commands to your opencode.json to see them here.",
+ "settings.agentBehaviour.workflows.detail.description": "Description",
+ "settings.agentBehaviour.workflows.detail.template": "Template",
+
+ "settings.agentBehaviour.createMode": "Create New Mode",
+ "settings.agentBehaviour.createMode.name": "Name",
+ "settings.agentBehaviour.createMode.name.placeholder": "e.g. reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "Unique identifier for the mode. Use lowercase letters, numbers, and hyphens only.",
+ "settings.agentBehaviour.createMode.description": "Description",
+ "settings.agentBehaviour.createMode.description.placeholder": "e.g. Reviews code for quality and best practices",
+ "settings.agentBehaviour.createMode.description.help": "Short description of what this mode does.",
+ "settings.agentBehaviour.createMode.prompt": "System Prompt",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "e.g. You are a code reviewer. Focus on code quality, best practices, and potential bugs.",
+ "settings.agentBehaviour.createMode.prompt.help": "Instructions for the AI agent when using this mode.",
+ "settings.agentBehaviour.createMode.button": "Create",
+ "settings.agentBehaviour.createMode.cancel": "Cancel",
+ "settings.agentBehaviour.createMode.nameRequired": "Name is required",
+ "settings.agentBehaviour.createMode.nameInvalid":
+ "Name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens",
+ "settings.agentBehaviour.createMode.nameTaken": "A mode with this name already exists",
+ "settings.agentBehaviour.importMode": "Import",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Invalid mode name in file. Name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.",
+ "settings.agentBehaviour.importMode.nameTaken": "A mode with this name already exists.",
+ "settings.agentBehaviour.importMode.invalidJson": "Invalid JSON file. Please select a valid agent definition file.",
+ "settings.agentBehaviour.importMode.tooLarge": "File is too large. Agent definitions must be under 1 MB.",
+ "settings.agentBehaviour.exportMode": "Export agent definition",
+ "settings.agentBehaviour.editMode": "Edit Mode",
+ "settings.agentBehaviour.editMode.description": "Description",
+ "settings.agentBehaviour.editMode.prompt": "System Prompt",
+ "settings.agentBehaviour.editMode.save": "Done",
+ "settings.agentBehaviour.editMode.back": "Back to list",
+ "settings.agentBehaviour.editMode.native": "Built-in mode (read-only definition)",
+ "settings.agentBehaviour.editMode.promptOverride": "Custom prompt override for this built-in mode",
"settings.autoApprove.description":
"Define how tools are allowed to run. Most tools default to Allow. doom_loop and external_directory default to Ask.",
@@ -1125,7 +1210,8 @@ export const dict = {
"settings.providers.defaultModel.title": "Default Model",
"settings.providers.defaultModel.description": "Primary model for conversations",
"settings.providers.smallModel.title": "Small Model",
- "settings.providers.smallModel.description": "Lightweight model for title generation and other quick tasks",
+ "settings.providers.smallModel.description":
+ "Lightweight model for title generation, commit message generation, prompt enhancement, and other quick tasks",
"settings.providers.modeModels": "Model per Mode",
"settings.providers.modeModels.description":
"Override the default model for specific modes. If not set, the global default model is used.",
@@ -1216,4 +1302,9 @@ export const dict = {
"settings.saveBar.warning.many": "Several sessions are running and will be interrupted",
"settings.saveBar.saveAnyway": "Save anyway",
"settings.saveBar.cancel": "Cancel",
+
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts
index 4bf2c991a0e..4302c5187b9 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts
@@ -757,6 +757,21 @@ export const dict = {
"provider.custom.models.name.placeholder": "Nombre para mostrar",
"provider.custom.models.remove": "Eliminar modelo",
"provider.custom.models.add": "Añadir modelo",
+ "provider.custom.models.fetch": "Obtener modelos",
+ "provider.custom.models.fetching": "Obteniendo\u2026",
+ "provider.custom.models.fetch.error": "Error al obtener modelos: {{error}}",
+ "provider.custom.models.fetch.authError":
+ "Autenticación fallida. Verifica la clave de API arriba e intenta de nuevo.",
+ "provider.custom.models.fetch.empty": "No se encontraron modelos en este servidor.",
+ "provider.custom.models.fetch.added": "Se añadieron {{count}} modelo(s).",
+ "provider.custom.models.fetch.allExist": "Todos los modelos obtenidos ya están añadidos.",
+ "provider.custom.models.fetch.selectAll": "Seleccionar todo",
+ "provider.custom.models.fetch.deselectAll": "Deseleccionar todo",
+ "provider.custom.models.fetch.found": "{{count}} modelos encontrados",
+ "provider.custom.models.fetch.showing": "Mostrando {{shown}} de {{total}}",
+ "provider.custom.models.fetch.search": "Buscar modelos\u2026",
+ "provider.custom.models.fetch.add": "Añadir {{count}} modelo(s)",
+ "provider.custom.edit.title": "Editar proveedor",
"provider.custom.headers.label": "Headers (opcional)",
"provider.custom.headers.key.label": "Header",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -830,8 +845,11 @@ export const dict = {
"session.delete.button": "Eliminar sesión",
"session.untitled": "Sin título",
"session.recent": "Recientes",
+ "session.showHistory": "Mostrar historial",
"session.search.placeholder": "Buscar sesiones...",
"session.empty": "Aún no hay sesiones. Haz clic en + para iniciar una nueva conversación.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Solo este repositorio",
"session.cloud.import": "Importar desde la nube",
"feedback.button": "Comentarios y soporte",
@@ -913,8 +931,7 @@ export const dict = {
"settings.autocomplete.title": "Autocompletado",
"settings.notifications.title": "Notificaciones",
"settings.context.title": "Contexto",
- "settings.terminal.title": "Terminal",
- "settings.prompts.title": "Prompts",
+
"settings.experimental.title": "Experimental",
"settings.language.title": "Idioma",
"settings.aboutKiloCode.title": "Acerca de Kilo Code",
@@ -965,6 +982,20 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Restablecer todas las configuraciones de la extensión Kilo Code a sus valores predeterminados. Esto no afecta la configuración del CLI o del backend.",
"settings.aboutKiloCode.resetSettings.button": "Restablecer toda la configuración",
+ "settings.aboutKiloCode.settingsTransfer.title": "Transferencia de ajustes",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Exporta o importa tus ajustes para transferirlos entre instancias de VS Code.",
+ "settings.aboutKiloCode.exportSettings": "Exportar",
+ "settings.aboutKiloCode.importSettings": "Importar",
+ "settings.aboutKiloCode.importSettings.invalidJson":
+ "Archivo JSON no válido. Seleccione un archivo de ajustes válido.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "El archivo no contiene ajustes válidos de Kilo.",
+ "settings.aboutKiloCode.importSettings.tooLarge":
+ "El archivo es demasiado grande. Los archivos de ajustes deben ser menores de 1 MB.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "Este archivo fue exportado desde una versión más reciente de Kilo. Algunos ajustes podrían ignorarse.",
+ "settings.aboutKiloCode.importSettings.success":
+ "Ajustes importados. Revise los cambios anteriores y luego haga clic en Guardar.",
"settings.agentBehaviour.subtab.modes": "Modos",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -990,8 +1021,7 @@ export const dict = {
"common.add": "Añadir",
"common.choose": "Elegir…",
- "settings.notImplemented": "Esta sección aún no está implementada.",
- "settings.notImplemented.description": "Contendrá opciones de configuración y texto explicativo.",
+
"settings.autocomplete.autoTrigger.title": "Habilitar completado en línea automático",
"settings.autocomplete.autoTrigger.description": "Mostrar automáticamente sugerencias de completado al escribir",
"settings.autocomplete.smartKeybinding.title": "Habilitar atajo inteligente de tarea en línea",
@@ -1050,11 +1080,51 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Parámetro de muestreo nucleus (0-1)",
"settings.agentBehaviour.maxSteps.title": "Pasos máximos",
"settings.agentBehaviour.maxSteps.description": "Iteraciones máximas del agente",
+ "settings.agentBehaviour.hidden.title": "Oculto",
+ "settings.agentBehaviour.hidden.description": "Ocultar este agente del selector de modos en la entrada del chat",
+ "settings.agentBehaviour.disable.title": "Desactivado",
+ "settings.agentBehaviour.disable.description": "Desactivar completamente este agente — no aparecerá en ningún lugar",
+ "settings.agentBehaviour.badge.hidden": "oculto",
+ "settings.agentBehaviour.badge.disabled": "desactivado",
"settings.agentBehaviour.discoveredSkills": "Habilidades descubiertas",
"settings.agentBehaviour.noSkillsFound":
"No se encontraron habilidades. Agregue rutas de carpetas o URLs abajo para hacer disponibles las habilidades.",
"settings.agentBehaviour.availableModes": "Modos personalizados disponibles",
"settings.agentBehaviour.noModesFound": "No se encontraron modos.",
+ "settings.agentBehaviour.createMode": "Crear nuevo modo",
+ "settings.agentBehaviour.createMode.name": "Nombre",
+ "settings.agentBehaviour.createMode.name.placeholder": "ej. reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "Identificador único del modo. Use solo letras minúsculas, números y guiones.",
+ "settings.agentBehaviour.createMode.description": "Descripción",
+ "settings.agentBehaviour.createMode.description.placeholder": "ej. Revisa código para calidad y mejores prácticas",
+ "settings.agentBehaviour.createMode.description.help": "Breve descripción de lo que hace este modo.",
+ "settings.agentBehaviour.createMode.prompt": "Prompt del sistema",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "ej. Eres un revisor de código. Enfócate en calidad, mejores prácticas y errores potenciales.",
+ "settings.agentBehaviour.createMode.prompt.help": "Instrucciones para el agente de IA al usar este modo.",
+ "settings.agentBehaviour.createMode.button": "Crear",
+ "settings.agentBehaviour.createMode.cancel": "Cancelar",
+ "settings.agentBehaviour.createMode.nameRequired": "El nombre es obligatorio",
+ "settings.agentBehaviour.createMode.nameInvalid":
+ "El nombre solo puede contener letras minúsculas, números y guiones",
+ "settings.agentBehaviour.createMode.nameTaken": "Ya existe un modo con este nombre",
+ "settings.agentBehaviour.importMode": "Importar",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Nombre de modo no válido en el archivo. El nombre debe comenzar con una letra minúscula y contener solo letras minúsculas, números y guiones.",
+ "settings.agentBehaviour.importMode.nameTaken": "Ya existe un modo con este nombre.",
+ "settings.agentBehaviour.importMode.invalidJson":
+ "Archivo JSON no válido. Por favor, seleccione un archivo de definición de agente válido.",
+ "settings.agentBehaviour.importMode.tooLarge":
+ "El archivo es demasiado grande. Las definiciones de agente deben ser menores de 1 MB.",
+ "settings.agentBehaviour.exportMode": "Exportar definición del agente",
+ "settings.agentBehaviour.editMode": "Editar modo",
+ "settings.agentBehaviour.editMode.description": "Descripción",
+ "settings.agentBehaviour.editMode.prompt": "Prompt del sistema",
+ "settings.agentBehaviour.editMode.save": "Listo",
+ "settings.agentBehaviour.editMode.back": "Volver a la lista",
+ "settings.agentBehaviour.editMode.native": "Modo integrado (definición de solo lectura)",
+ "settings.agentBehaviour.editMode.promptOverride": "Anulación de prompt personalizado para este modo integrado",
"settings.agentBehaviour.removeMode.title": "Eliminar modo",
"settings.agentBehaviour.removeMode.confirm":
'¿Eliminar el modo "{{name}}"? Esto desactivará el modo actualizando su configuración.',
@@ -1063,20 +1133,44 @@ export const dict = {
"settings.agentBehaviour.removeMcp.confirm":
'¿Eliminar el servidor MCP "{{name}}"? Esto lo eliminará de su configuración.',
"settings.agentBehaviour.removeMcp.button": "Eliminar",
+ "settings.agentBehaviour.editMcp": "Editar servidor MCP",
+ "settings.agentBehaviour.editMcp.transportLocal": "Servidor local (transporte stdio)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Servidor remoto (transporte SSE/HTTP)",
+ "settings.agentBehaviour.editMcp.env": "Variables de entorno",
+ "settings.agentBehaviour.editMcp.env.help": "Variables pasadas al proceso del servidor MCP.",
+ "settings.agentBehaviour.addMcp.command": "Comando",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Argumentos",
+ "settings.agentBehaviour.addMcp.args.help": "Un argumento por línea. Las rutas con espacios se conservan tal cual.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "URL del servidor",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "Rutas de carpetas de habilidades",
"settings.agentBehaviour.skillUrls": "URLs de habilidades",
"settings.agentBehaviour.removeSkill.title": "Eliminar habilidad",
"settings.agentBehaviour.removeSkill.confirm":
'¿Eliminar la habilidad "{{name}}"? Esto borrará los archivos de la habilidad del disco.',
"settings.agentBehaviour.removeSkill.button": "Eliminar",
+ "settings.agentBehaviour.rules.description":
+ "Las reglas son archivos de instrucciones que guían el comportamiento del agente. Se incluyen en el prompt del sistema para cada conversación. Añada rutas de archivos a continuación para incluir reglas adicionales.",
"settings.agentBehaviour.instructionFiles": "Archivos de instrucciones adicionales",
"settings.agentBehaviour.instructionFiles.description":
"Rutas a archivos de instrucciones adicionales incluidos en el prompt del sistema",
+ "settings.agentBehaviour.mcpDetail.command": "Comando",
+ "settings.agentBehaviour.mcpDetail.args": "Argumentos",
+ "settings.agentBehaviour.mcpDetail.env": "Entorno",
+ "settings.agentBehaviour.mcpDetail.disabled": "Este servidor está deshabilitado.",
"settings.agentBehaviour.mcpEmpty":
"No hay servidores MCP configurados. Edite el archivo de configuración de opencode para añadir servidores MCP.",
"settings.agentBehaviour.workflowsPlaceholder":
"Los flujos de trabajo se gestionan mediante archivos de flujo de trabajo en su espacio de trabajo.",
- "settings.agentBehaviour.notImplemented": "Aún no implementado.",
+ "settings.agentBehaviour.workflows.description":
+ "Los flujos de trabajo son comandos de barra personalizados definidos en su configuración. Escriba /command-name en el chat para invocarlos. Los comandos se configuran en opencode.json en la sección 'command'.",
+ "settings.agentBehaviour.workflows.empty":
+ "No hay comandos personalizados configurados. Añada comandos a opencode.json para verlos aquí.",
+ "settings.agentBehaviour.workflows.detail.description": "Descripción",
+ "settings.agentBehaviour.workflows.detail.template": "Plantilla",
+
"settings.autoApprove.description":
"Defina cómo se permite la ejecución de las herramientas. La mayoría de las herramientas tienen como valor predeterminado Permitir. doom_loop y external_directory tienen como valor predeterminado Preguntar.",
"settings.autoApprove.level.allow": "Permitir",
@@ -1132,7 +1226,8 @@ export const dict = {
"settings.providers.defaultModel.title": "Modelo predeterminado",
"settings.providers.defaultModel.description": "Modelo principal para conversaciones",
"settings.providers.smallModel.title": "Modelo pequeño",
- "settings.providers.smallModel.description": "Modelo ligero para generación de títulos y tareas rápidas",
+ "settings.providers.smallModel.description":
+ "Modelo ligero para generación de títulos, mensajes de commit, mejora de prompts y otras tareas rápidas",
"settings.providers.disabled": "Proveedores deshabilitados",
"settings.providers.disabled.description": "Proveedores a ocultar de la lista de proveedores",
"settings.providers.enabled": "Proveedores habilitados (lista blanca)",
@@ -1214,4 +1309,8 @@ export const dict = {
"settings.saveBar.warning.many": "Varias sesiones están en ejecución y se interrumpirán",
"settings.saveBar.saveAnyway": "Guardar de todas formas",
"settings.saveBar.cancel": "Cancelar",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts
index 6f3a2067355..fb74537fbdb 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts
@@ -762,6 +762,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "Nom d'affichage",
"provider.custom.models.remove": "Supprimer le modèle",
"provider.custom.models.add": "Ajouter un modèle",
+ "provider.custom.models.fetch": "Récupérer les modèles",
+ "provider.custom.models.fetching": "Récupération\u2026",
+ "provider.custom.models.fetch.error": "Échec de la récupération des modèles : {{error}}",
+ "provider.custom.models.fetch.authError": "Échec de l'authentification. Vérifiez la clé API ci-dessus et réessayez.",
+ "provider.custom.models.fetch.empty": "Aucun modèle trouvé sur ce serveur.",
+ "provider.custom.models.fetch.added": "{{count}} modèle(s) ajouté(s).",
+ "provider.custom.models.fetch.allExist": "Tous les modèles récupérés sont déjà ajoutés.",
+ "provider.custom.models.fetch.selectAll": "Tout sélectionner",
+ "provider.custom.models.fetch.deselectAll": "Tout désélectionner",
+ "provider.custom.models.fetch.found": "{{count}} modèles trouvés",
+ "provider.custom.models.fetch.showing": "Affichage de {{shown}} sur {{total}}",
+ "provider.custom.models.fetch.search": "Rechercher des modèles\u2026",
+ "provider.custom.models.fetch.add": "Ajouter {{count}} modèle(s)",
+ "provider.custom.edit.title": "Modifier le fournisseur",
"provider.custom.headers.label": "En-têtes (optionnel)",
"provider.custom.headers.key.label": "En-tête",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -837,8 +851,11 @@ export const dict = {
"session.delete.button": "Supprimer la session",
"session.untitled": "Sans titre",
"session.recent": "Récentes",
+ "session.showHistory": "Afficher l'historique",
"session.search.placeholder": "Rechercher des sessions...",
"session.empty": "Aucune session pour l'instant. Cliquez + pour démarrer une nouvelle conversation.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Uniquement ce dépôt",
"session.cloud.import": "Importer depuis le cloud",
"feedback.button": "Commentaires & support",
@@ -920,8 +937,7 @@ export const dict = {
"settings.autocomplete.title": "Autocomplétion",
"settings.notifications.title": "Notifications",
"settings.context.title": "Contexte",
- "settings.terminal.title": "Terminal",
- "settings.prompts.title": "Prompts",
+
"settings.experimental.title": "Expérimental",
"settings.language.title": "Langue",
"settings.aboutKiloCode.title": "À propos de Kilo Code",
@@ -974,6 +990,20 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Réinitialiser tous les paramètres de l'extension Kilo Code à leurs valeurs par défaut. Cela n'affecte pas la configuration CLI ou backend.",
"settings.aboutKiloCode.resetSettings.button": "Réinitialiser tous les paramètres",
+ "settings.aboutKiloCode.settingsTransfer.title": "Transfert des paramètres",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Exportez ou importez vos paramètres pour les transférer entre instances VS Code.",
+ "settings.aboutKiloCode.exportSettings": "Exporter",
+ "settings.aboutKiloCode.importSettings": "Importer",
+ "settings.aboutKiloCode.importSettings.invalidJson":
+ "Fichier JSON invalide. Veuillez sélectionner un fichier de paramètres valide.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "Le fichier ne contient pas de paramètres Kilo valides.",
+ "settings.aboutKiloCode.importSettings.tooLarge":
+ "Le fichier est trop volumineux. Les fichiers de paramètres doivent faire moins de 1 MB.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "Ce fichier a été exporté depuis une version plus récente de Kilo. Certains paramètres pourraient être ignorés.",
+ "settings.aboutKiloCode.importSettings.success":
+ "Paramètres importés. Vérifiez les modifications ci-dessus, puis cliquez sur Enregistrer.",
"settings.agentBehaviour.subtab.modes": "Modes",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -999,8 +1029,7 @@ export const dict = {
"common.add": "Ajouter",
"common.choose": "Choisir…",
- "settings.notImplemented": "Cette section n'est pas encore implémentée.",
- "settings.notImplemented.description": "Elle contiendra des options de configuration et du texte explicatif.",
+
"settings.autocomplete.autoTrigger.title": "Activer la complétion automatique en ligne",
"settings.autocomplete.autoTrigger.description":
"Afficher automatiquement les suggestions de complétion lors de la saisie",
@@ -1062,11 +1091,52 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Paramètre d'échantillonnage nucleus (0-1)",
"settings.agentBehaviour.maxSteps.title": "Étapes max.",
"settings.agentBehaviour.maxSteps.description": "Itérations maximales de l'agent",
+ "settings.agentBehaviour.hidden.title": "Masqué",
+ "settings.agentBehaviour.hidden.description": "Masquer cet agent du sélecteur de mode dans la saisie du chat",
+ "settings.agentBehaviour.disable.title": "Désactivé",
+ "settings.agentBehaviour.disable.description": "Désactiver complètement cet agent — il n'apparaîtra nulle part",
+ "settings.agentBehaviour.badge.hidden": "masqué",
+ "settings.agentBehaviour.badge.disabled": "désactivé",
"settings.agentBehaviour.discoveredSkills": "Compétences découvertes",
"settings.agentBehaviour.noSkillsFound":
"Aucune compétence découverte. Ajoutez des chemins de dossiers ou des URLs ci-dessous pour rendre les compétences disponibles.",
"settings.agentBehaviour.availableModes": "Modes personnalisés disponibles",
"settings.agentBehaviour.noModesFound": "Aucun mode trouvé.",
+ "settings.agentBehaviour.createMode": "Créer un nouveau mode",
+ "settings.agentBehaviour.createMode.name": "Nom",
+ "settings.agentBehaviour.createMode.name.placeholder": "ex : reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "Identifiant unique du mode. Utilisez uniquement des minuscules, des chiffres et des tirets.",
+ "settings.agentBehaviour.createMode.description": "Description",
+ "settings.agentBehaviour.createMode.description.placeholder":
+ "ex : Examine le code pour la qualité et les meilleures pratiques",
+ "settings.agentBehaviour.createMode.description.help": "Brève description de ce que fait ce mode.",
+ "settings.agentBehaviour.createMode.prompt": "Prompt système",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "ex : Vous êtes un relecteur de code. Concentrez-vous sur la qualité, les bonnes pratiques et les bugs potentiels.",
+ "settings.agentBehaviour.createMode.prompt.help": "Instructions pour l'agent IA lors de l'utilisation de ce mode.",
+ "settings.agentBehaviour.createMode.button": "Créer",
+ "settings.agentBehaviour.createMode.cancel": "Annuler",
+ "settings.agentBehaviour.createMode.nameRequired": "Le nom est requis",
+ "settings.agentBehaviour.createMode.nameInvalid":
+ "Le nom ne doit contenir que des minuscules, des chiffres et des tirets",
+ "settings.agentBehaviour.createMode.nameTaken": "Un mode avec ce nom existe déjà",
+ "settings.agentBehaviour.importMode": "Importer",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Nom de mode invalide dans le fichier. Le nom doit commencer par une lettre minuscule et ne contenir que des lettres minuscules, des chiffres et des tirets.",
+ "settings.agentBehaviour.importMode.nameTaken": "Un mode avec ce nom existe déjà.",
+ "settings.agentBehaviour.importMode.invalidJson":
+ "Fichier JSON invalide. Veuillez sélectionner un fichier de définition d'agent valide.",
+ "settings.agentBehaviour.importMode.tooLarge":
+ "Le fichier est trop volumineux. Les définitions d'agent doivent faire moins de 1 Mo.",
+ "settings.agentBehaviour.exportMode": "Exporter la définition de l'agent",
+ "settings.agentBehaviour.editMode": "Modifier le mode",
+ "settings.agentBehaviour.editMode.description": "Description",
+ "settings.agentBehaviour.editMode.prompt": "Prompt système",
+ "settings.agentBehaviour.editMode.save": "Terminé",
+ "settings.agentBehaviour.editMode.back": "Retour à la liste",
+ "settings.agentBehaviour.editMode.native": "Mode intégré (définition en lecture seule)",
+ "settings.agentBehaviour.editMode.promptOverride": "Remplacement de prompt personnalisé pour ce mode intégré",
"settings.agentBehaviour.removeMode.title": "Supprimer le mode",
"settings.agentBehaviour.removeMode.confirm":
'Supprimer le mode "{{name}}" ? Cela désactivera le mode en mettant à jour votre configuration.',
@@ -1075,19 +1145,44 @@ export const dict = {
"settings.agentBehaviour.removeMcp.confirm":
'Supprimer le serveur MCP "{{name}}" ? Cela le supprimera de votre configuration.',
"settings.agentBehaviour.removeMcp.button": "Supprimer",
+ "settings.agentBehaviour.editMcp": "Modifier le serveur MCP",
+ "settings.agentBehaviour.editMcp.transportLocal": "Serveur local (transport stdio)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Serveur distant (transport SSE/HTTP)",
+ "settings.agentBehaviour.editMcp.env": "Variables d'environnement",
+ "settings.agentBehaviour.editMcp.env.help": "Variables transmises au processus du serveur MCP.",
+ "settings.agentBehaviour.addMcp.command": "Commande",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Arguments",
+ "settings.agentBehaviour.addMcp.args.help":
+ "Un argument par ligne. Les chemins avec des espaces sont conservés tels quels.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "URL du serveur",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "Chemins des dossiers de compétences",
"settings.agentBehaviour.skillUrls": "URLs de compétences",
"settings.agentBehaviour.removeSkill.title": "Supprimer la compétence",
"settings.agentBehaviour.removeSkill.confirm":
'Supprimer la compétence "{{name}}" ? Cela supprimera les fichiers de la compétence du disque.',
"settings.agentBehaviour.removeSkill.button": "Supprimer",
+ "settings.agentBehaviour.rules.description":
+ "Les règles sont des fichiers d'instructions qui guident le comportement de l'agent. Elles sont incluses dans le prompt système pour chaque conversation. Ajoutez des chemins de fichiers ci-dessous pour inclure des règles supplémentaires.",
"settings.agentBehaviour.instructionFiles": "Fichiers d'instructions supplémentaires",
"settings.agentBehaviour.instructionFiles.description": "Chemins vers des fichiers d'instructions supplémentaires",
+ "settings.agentBehaviour.mcpDetail.command": "Commande",
+ "settings.agentBehaviour.mcpDetail.args": "Arguments",
+ "settings.agentBehaviour.mcpDetail.env": "Environnement",
+ "settings.agentBehaviour.mcpDetail.disabled": "Ce serveur est désactivé.",
"settings.agentBehaviour.mcpEmpty":
"Aucun serveur MCP configuré. Modifiez le fichier de configuration opencode pour ajouter des serveurs MCP.",
"settings.agentBehaviour.workflowsPlaceholder":
"Les workflows sont gérés via les fichiers de workflow dans votre espace de travail.",
- "settings.agentBehaviour.notImplemented": "Pas encore implémenté.",
+ "settings.agentBehaviour.workflows.description":
+ "Les workflows sont des commandes slash personnalisées définies dans votre configuration. Tapez /command-name dans le chat pour les invoquer. Les commandes sont configurées dans opencode.json dans la section 'command'.",
+ "settings.agentBehaviour.workflows.empty":
+ "Aucune commande personnalisée configurée. Ajoutez des commandes à opencode.json pour les voir ici.",
+ "settings.agentBehaviour.workflows.detail.description": "Description",
+ "settings.agentBehaviour.workflows.detail.template": "Modèle",
+
"settings.autoApprove.description":
"Définissez comment les outils sont autorisés à s'exécuter. La plupart des outils sont définis sur Autoriser par défaut. doom_loop et external_directory sont définis sur Demander par défaut.",
"settings.autoApprove.level.allow": "Autoriser",
@@ -1144,7 +1239,8 @@ export const dict = {
"settings.providers.defaultModel.title": "Modèle par défaut",
"settings.providers.defaultModel.description": "Modèle principal pour les conversations",
"settings.providers.smallModel.title": "Petit modèle",
- "settings.providers.smallModel.description": "Modèle léger pour la génération de titres et tâches rapides",
+ "settings.providers.smallModel.description":
+ "Modèle léger pour la génération de titres, de messages de commit, l'amélioration de prompts et d'autres tâches rapides",
"settings.providers.disabled": "Fournisseurs désactivés",
"settings.providers.disabled.description": "Fournisseurs à masquer de la liste",
"settings.providers.enabled": "Fournisseurs activés (liste blanche)",
@@ -1226,4 +1322,8 @@ export const dict = {
"settings.saveBar.warning.many": "Plusieurs sessions sont en cours et seront interrompues",
"settings.saveBar.saveAnyway": "Enregistrer quand même",
"settings.saveBar.cancel": "Annuler",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts
index df4a29e69db..a8e298ef97e 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts
@@ -750,6 +750,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "表示名",
"provider.custom.models.remove": "モデルを削除",
"provider.custom.models.add": "モデルを追加",
+ "provider.custom.models.fetch": "モデルを取得",
+ "provider.custom.models.fetching": "取得中\u2026",
+ "provider.custom.models.fetch.error": "モデルの取得に失敗しました: {{error}}",
+ "provider.custom.models.fetch.authError": "認証に失敗しました。上記のAPIキーを確認して再試行してください。",
+ "provider.custom.models.fetch.empty": "このサーバーにモデルが見つかりません。",
+ "provider.custom.models.fetch.added": "{{count}}個のモデルを追加しました。",
+ "provider.custom.models.fetch.allExist": "取得したモデルはすべて既に追加されています。",
+ "provider.custom.models.fetch.selectAll": "すべて選択",
+ "provider.custom.models.fetch.deselectAll": "すべて選択解除",
+ "provider.custom.models.fetch.found": "{{count}}個のモデルが見つかりました",
+ "provider.custom.models.fetch.showing": "{{total}}件中{{shown}}件を表示",
+ "provider.custom.models.fetch.search": "モデルを検索\u2026",
+ "provider.custom.models.fetch.add": "{{count}}個のモデルを追加",
+ "provider.custom.edit.title": "プロバイダーを編集",
"provider.custom.headers.label": "ヘッダー(任意)",
"provider.custom.headers.key.label": "ヘッダー",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -822,8 +836,11 @@ export const dict = {
"session.delete.button": "セッションを削除",
"session.untitled": "無題",
"session.recent": "最近",
+ "session.showHistory": "履歴を表示",
"session.search.placeholder": "セッションを検索...",
"session.empty": "セッションがありません。+ をクリックして新しい会話を始めましょう。",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "このリポジトリのみ",
"session.cloud.import": "クラウドからインポート",
"feedback.button": "フィードバック & サポート",
@@ -905,8 +922,7 @@ export const dict = {
"settings.autocomplete.title": "オートコンプリート",
"settings.notifications.title": "通知",
"settings.context.title": "コンテキスト",
- "settings.terminal.title": "ターミナル",
- "settings.prompts.title": "プロンプト",
+
"settings.experimental.title": "実験的機能",
"settings.language.title": "言語",
"settings.aboutKiloCode.title": "Kilo Codeについて",
@@ -958,6 +974,19 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Kilo Code拡張機能のすべての設定をデフォルト値にリセットします。CLIやバックエンドの設定には影響しません。",
"settings.aboutKiloCode.resetSettings.button": "すべての設定をリセット",
+ "settings.aboutKiloCode.settingsTransfer.title": "設定の移行",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "VS Code インスタンス間で設定を転送するには、エクスポートまたはインポートしてください。",
+ "settings.aboutKiloCode.exportSettings": "エクスポート",
+ "settings.aboutKiloCode.importSettings": "インポート",
+ "settings.aboutKiloCode.importSettings.invalidJson":
+ "無効な JSON ファイルです。有効な設定ファイルを選択してください。",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "ファイルに有効な Kilo の設定が含まれていません。",
+ "settings.aboutKiloCode.importSettings.tooLarge": "ファイルが大きすぎます。設定ファイルは 1 MB 以下にしてください。",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "このファイルはより新しいバージョンの Kilo からエクスポートされたものです。一部の設定が無視される場合があります。",
+ "settings.aboutKiloCode.importSettings.success":
+ "設定をインポートしました。上記の変更内容を確認してから、保存をクリックしてください。",
"settings.agentBehaviour.subtab.modes": "モード",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -982,9 +1011,7 @@ export const dict = {
"common.add": "追加",
"common.choose": "選択…",
- "settings.notImplemented": "このセクションはまだ実装されていません。",
- "settings.notImplemented.description":
- "選択した設定カテゴリに関連する設定オプションと説明テキストが含まれる予定です。",
+
"settings.autocomplete.autoTrigger.title": "自動インライン補完を有効にする",
"settings.autocomplete.autoTrigger.description": "入力時にインライン補完の提案を自動的に表示",
"settings.autocomplete.smartKeybinding.title": "スマートインラインタスクキーバインドを有効にする",
@@ -1042,11 +1069,50 @@ export const dict = {
"settings.agentBehaviour.topP.description": "核サンプリングパラメータ(0-1)",
"settings.agentBehaviour.maxSteps.title": "最大ステップ数",
"settings.agentBehaviour.maxSteps.description": "最大エージェント反復回数",
+ "settings.agentBehaviour.hidden.title": "非表示",
+ "settings.agentBehaviour.hidden.description": "チャット入力のモード切り替えからこのエージェントを非表示にする",
+ "settings.agentBehaviour.disable.title": "無効",
+ "settings.agentBehaviour.disable.description": "このエージェントを完全に無効にする — どこにも表示されなくなります",
+ "settings.agentBehaviour.badge.hidden": "非表示",
+ "settings.agentBehaviour.badge.disabled": "無効",
"settings.agentBehaviour.discoveredSkills": "検出されたスキル",
"settings.agentBehaviour.noSkillsFound":
"スキルが見つかりません。スキルを利用可能にするには、以下にスキルフォルダパスまたはURLを追加してください。",
"settings.agentBehaviour.availableModes": "利用可能なカスタムモード",
"settings.agentBehaviour.noModesFound": "モードが見つかりません。",
+ "settings.agentBehaviour.createMode": "新しいモードを作成",
+ "settings.agentBehaviour.createMode.name": "名前",
+ "settings.agentBehaviour.createMode.name.placeholder": "例: reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "モードの一意の識別子。小文字、数字、ハイフンのみ使用できます。",
+ "settings.agentBehaviour.createMode.description": "説明",
+ "settings.agentBehaviour.createMode.description.placeholder": "例: コードの品質とベストプラクティスをレビューします",
+ "settings.agentBehaviour.createMode.description.help": "このモードの機能の簡単な説明。",
+ "settings.agentBehaviour.createMode.prompt": "システムプロンプト",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "例: あなたはコードレビュアーです。コード品質、ベストプラクティス、潜在的なバグに焦点を当ててください。",
+ "settings.agentBehaviour.createMode.prompt.help": "このモードを使用するときのAIエージェントへの指示。",
+ "settings.agentBehaviour.createMode.button": "作成",
+ "settings.agentBehaviour.createMode.cancel": "キャンセル",
+ "settings.agentBehaviour.createMode.nameRequired": "名前は必須です",
+ "settings.agentBehaviour.createMode.nameInvalid": "名前には小文字、数字、ハイフンのみ使用できます",
+ "settings.agentBehaviour.createMode.nameTaken": "この名前のモードはすでに存在します",
+ "settings.agentBehaviour.importMode": "インポート",
+ "settings.agentBehaviour.importMode.invalidName":
+ "ファイル内のモード名が無効です。名前は小文字で始まり、小文字、数字、ハイフンのみを含む必要があります。",
+ "settings.agentBehaviour.importMode.nameTaken": "この名前のモードはすでに存在します。",
+ "settings.agentBehaviour.importMode.invalidJson":
+ "無効なJSONファイルです。有効なエージェント定義ファイルを選択してください。",
+ "settings.agentBehaviour.importMode.tooLarge":
+ "ファイルが大きすぎます。エージェント定義は1 MB以下である必要があります。",
+ "settings.agentBehaviour.exportMode": "エージェント定義をエクスポート",
+ "settings.agentBehaviour.editMode": "モードを編集",
+ "settings.agentBehaviour.editMode.description": "説明",
+ "settings.agentBehaviour.editMode.prompt": "システムプロンプト",
+ "settings.agentBehaviour.editMode.save": "完了",
+ "settings.agentBehaviour.editMode.back": "リストに戻る",
+ "settings.agentBehaviour.editMode.native": "組み込みモード(読み取り専用の定義)",
+ "settings.agentBehaviour.editMode.promptOverride": "この組み込みモードのカスタムプロンプト上書き",
"settings.agentBehaviour.removeMode.title": "モードを削除",
"settings.agentBehaviour.removeMode.confirm":
'モード "{{name}}" を削除しますか?設定を更新してモードを無効にします。',
@@ -1054,19 +1120,43 @@ export const dict = {
"settings.agentBehaviour.removeMcp.title": "MCPサーバーを削除",
"settings.agentBehaviour.removeMcp.confirm": 'MCPサーバー "{{name}}" を削除しますか?設定から削除されます。',
"settings.agentBehaviour.removeMcp.button": "削除",
+ "settings.agentBehaviour.editMcp": "MCPサーバーを編集",
+ "settings.agentBehaviour.editMcp.transportLocal": "ローカルサーバー(stdio トランスポート)",
+ "settings.agentBehaviour.editMcp.transportRemote": "リモートサーバー(SSE/HTTP トランスポート)",
+ "settings.agentBehaviour.editMcp.env": "環境変数",
+ "settings.agentBehaviour.editMcp.env.help": "MCPサーバープロセスに渡される変数。",
+ "settings.agentBehaviour.addMcp.command": "コマンド",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "引数",
+ "settings.agentBehaviour.addMcp.args.help": "1行に1つの引数。スペースを含むパスはそのまま保持されます。",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "サーバーURL",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "スキルフォルダパス",
"settings.agentBehaviour.skillUrls": "スキルURL",
"settings.agentBehaviour.removeSkill.title": "スキルを削除",
"settings.agentBehaviour.removeSkill.confirm":
'スキル "{{name}}" を削除しますか?これにより、ディスクからスキルファイルが削除されます。',
"settings.agentBehaviour.removeSkill.button": "削除",
+ "settings.agentBehaviour.rules.description":
+ "ルールはエージェントの動作を導く指示ファイルです。すべての会話のシステムプロンプトに含まれます。追加のルールを含めるには、以下にファイルパスを追加してください。",
"settings.agentBehaviour.instructionFiles": "追加の指示ファイル",
"settings.agentBehaviour.instructionFiles.description": "システムプロンプトに含まれる追加の指示ファイルへのパス",
+ "settings.agentBehaviour.mcpDetail.command": "コマンド",
+ "settings.agentBehaviour.mcpDetail.args": "引数",
+ "settings.agentBehaviour.mcpDetail.env": "環境",
+ "settings.agentBehaviour.mcpDetail.disabled": "このサーバーは無効です。",
"settings.agentBehaviour.mcpEmpty":
"MCPサーバーが設定されていません。opencode設定ファイルを編集してMCPサーバーを追加してください。",
"settings.agentBehaviour.workflowsPlaceholder":
"ワークフローはワークスペース内のワークフローファイルを通じて管理されます。",
- "settings.agentBehaviour.notImplemented": "まだ実装されていません。",
+ "settings.agentBehaviour.workflows.description":
+ "ワークフローは設定で定義されたカスタムスラッシュコマンドです。チャットで /command-name と入力して呼び出します。コマンドは opencode.json の 'command' セクションで設定します。",
+ "settings.agentBehaviour.workflows.empty":
+ "カスタムコマンドが設定されていません。opencode.json にコマンドを追加するとここに表示されます。",
+ "settings.agentBehaviour.workflows.detail.description": "説明",
+ "settings.agentBehaviour.workflows.detail.template": "テンプレート",
+
"settings.autoApprove.description":
"ツールの実行許可を定義します。ほとんどのツールはデフォルトで「許可」されます。doom_loop と external_directory はデフォルトで「確認」になります。",
"settings.autoApprove.level.allow": "許可",
@@ -1119,7 +1209,8 @@ export const dict = {
"settings.providers.defaultModel.title": "デフォルトモデル",
"settings.providers.defaultModel.description": "会話のプライマリモデル",
"settings.providers.smallModel.title": "小型モデル",
- "settings.providers.smallModel.description": "タイトル生成やその他の高速タスク用の軽量モデル",
+ "settings.providers.smallModel.description":
+ "タイトル生成、コミットメッセージ生成、プロンプト改善、およびその他の高速タスク用の軽量モデル",
"settings.providers.disabled": "無効化されたプロバイダー",
"settings.providers.disabled.description": "プロバイダーリストから非表示にするプロバイダー",
"settings.providers.enabled": "有効化されたプロバイダー(ホワイトリスト)",
@@ -1200,4 +1291,8 @@ export const dict = {
"settings.saveBar.warning.many": "複数のセッションが実行中で中断されます",
"settings.saveBar.saveAnyway": "それでも保存",
"settings.saveBar.cancel": "キャンセル",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts
index 1935ce8b9c4..31b562e318b 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts
@@ -751,6 +751,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "표시 이름",
"provider.custom.models.remove": "모델 제거",
"provider.custom.models.add": "모델 추가",
+ "provider.custom.models.fetch": "모델 가져오기",
+ "provider.custom.models.fetching": "가져오는 중\u2026",
+ "provider.custom.models.fetch.error": "모델을 가져오지 못했습니다: {{error}}",
+ "provider.custom.models.fetch.authError": "인증에 실패했습니다. 위의 API 키를 확인하고 다시 시도하세요.",
+ "provider.custom.models.fetch.empty": "이 서버에서 모델을 찾을 수 없습니다.",
+ "provider.custom.models.fetch.added": "{{count}}개 모델이 추가되었습니다.",
+ "provider.custom.models.fetch.allExist": "가져온 모든 모델이 이미 추가되어 있습니다.",
+ "provider.custom.models.fetch.selectAll": "모두 선택",
+ "provider.custom.models.fetch.deselectAll": "모두 선택 해제",
+ "provider.custom.models.fetch.found": "{{count}}개 모델 발견",
+ "provider.custom.models.fetch.showing": "{{total}}개 중 {{shown}}개 표시",
+ "provider.custom.models.fetch.search": "모델 검색\u2026",
+ "provider.custom.models.fetch.add": "{{count}}개 모델 추가",
+ "provider.custom.edit.title": "공급자 편집",
"provider.custom.headers.label": "헤더 (선택사항)",
"provider.custom.headers.key.label": "헤더",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -823,8 +837,11 @@ export const dict = {
"session.delete.button": "세션 삭제",
"session.untitled": "제목 없음",
"session.recent": "최근",
+ "session.showHistory": "기록 보기",
"session.search.placeholder": "세션 검색...",
"session.empty": "아직 세션이 없습니다. +를 클릭하여 새 대화를 시작하세요.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "이 저장소만",
"session.cloud.import": "클라우드에서 가져오기",
"feedback.button": "피드백 & 지원",
@@ -905,8 +922,7 @@ export const dict = {
"settings.autocomplete.title": "자동 완성",
"settings.notifications.title": "알림",
"settings.context.title": "컨텍스트",
- "settings.terminal.title": "터미널",
- "settings.prompts.title": "프롬프트",
+
"settings.experimental.title": "실험적",
"settings.language.title": "언어",
"settings.aboutKiloCode.title": "Kilo Code 정보",
@@ -957,6 +973,18 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Kilo Code 확장 프로그램의 모든 설정을 기본값으로 초기화합니다. CLI 또는 백엔드 구성에는 영향을 미치지 않습니다.",
"settings.aboutKiloCode.resetSettings.button": "모든 설정 초기화",
+ "settings.aboutKiloCode.settingsTransfer.title": "설정 이전",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "VS Code 인스턴스 간에 설정을 전송하려면 내보내기 또는 가져오기하세요.",
+ "settings.aboutKiloCode.exportSettings": "내보내기",
+ "settings.aboutKiloCode.importSettings": "가져오기",
+ "settings.aboutKiloCode.importSettings.invalidJson":
+ "유효하지 않은 JSON 파일입니다. 올바른 설정 파일을 선택해 주세요.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "파일에 유효한 Kilo 설정이 포함되어 있지 않습니다.",
+ "settings.aboutKiloCode.importSettings.tooLarge": "파일이 너무 큽니다. 설정 파일은 1 MB 이하여야 합니다.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "이 파일은 더 최신 버전의 Kilo에서 내보낸 것입니다. 일부 설정이 무시될 수 있습니다.",
+ "settings.aboutKiloCode.importSettings.success": "설정을 가져왔습니다. 위의 변경 사항을 확인한 후 저장을 클릭하세요.",
"settings.agentBehaviour.subtab.modes": "모드",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -980,8 +1008,7 @@ export const dict = {
"common.add": "추가",
"common.choose": "선택…",
- "settings.notImplemented": "이 섹션은 아직 구현되지 않았습니다.",
- "settings.notImplemented.description": "선택한 설정 카테고리와 관련된 구성 옵션 및 설명 텍스트가 포함될 예정입니다.",
+
"settings.autocomplete.autoTrigger.title": "자동 인라인 완성 활성화",
"settings.autocomplete.autoTrigger.description": "입력 시 인라인 완성 제안을 자동으로 표시",
"settings.autocomplete.smartKeybinding.title": "스마트 인라인 작업 키바인딩 활성화",
@@ -1039,11 +1066,47 @@ export const dict = {
"settings.agentBehaviour.topP.description": "핵 샘플링 매개변수 (0-1)",
"settings.agentBehaviour.maxSteps.title": "최대 단계",
"settings.agentBehaviour.maxSteps.description": "최대 에이전트 반복 횟수",
+ "settings.agentBehaviour.hidden.title": "숨김",
+ "settings.agentBehaviour.hidden.description": "채팅 입력의 모드 전환기에서 이 에이전트를 숨기기",
+ "settings.agentBehaviour.disable.title": "비활성화됨",
+ "settings.agentBehaviour.disable.description": "이 에이전트를 완전히 비활성화 — 어디에도 표시되지 않습니다",
+ "settings.agentBehaviour.badge.hidden": "숨김",
+ "settings.agentBehaviour.badge.disabled": "비활성화됨",
"settings.agentBehaviour.discoveredSkills": "검색된 스킬",
"settings.agentBehaviour.noSkillsFound":
"스킬을 찾을 수 없습니다. 스킬을 사용하려면 아래에 스킬 폴더 경로 또는 URL을 추가하세요.",
"settings.agentBehaviour.availableModes": "사용 가능한 사용자 정의 모드",
"settings.agentBehaviour.noModesFound": "모드를 찾을 수 없습니다.",
+ "settings.agentBehaviour.createMode": "새 모드 만들기",
+ "settings.agentBehaviour.createMode.name": "이름",
+ "settings.agentBehaviour.createMode.name.placeholder": "예: reviewer",
+ "settings.agentBehaviour.createMode.name.description": "모드의 고유 식별자. 소문자, 숫자, 하이픈만 사용하세요.",
+ "settings.agentBehaviour.createMode.description": "설명",
+ "settings.agentBehaviour.createMode.description.placeholder": "예: 품질 및 모범 사례를 위해 코드를 검토합니다",
+ "settings.agentBehaviour.createMode.description.help": "이 모드가 하는 일에 대한 간단한 설명.",
+ "settings.agentBehaviour.createMode.prompt": "시스템 프롬프트",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "예: 당신은 코드 리뷰어입니다. 코드 품질, 모범 사례, 잠재적 버그에 집중하세요.",
+ "settings.agentBehaviour.createMode.prompt.help": "이 모드를 사용할 때 AI 에이전트에 대한 지침.",
+ "settings.agentBehaviour.createMode.button": "만들기",
+ "settings.agentBehaviour.createMode.cancel": "취소",
+ "settings.agentBehaviour.createMode.nameRequired": "이름은 필수입니다",
+ "settings.agentBehaviour.createMode.nameInvalid": "이름에는 소문자, 숫자, 하이픈만 포함될 수 있습니다",
+ "settings.agentBehaviour.createMode.nameTaken": "이 이름의 모드가 이미 존재합니다",
+ "settings.agentBehaviour.importMode": "가져오기",
+ "settings.agentBehaviour.importMode.invalidName":
+ "파일의 모드 이름이 잘못되었습니다. 이름은 소문자로 시작하고 소문자, 숫자, 하이픈만 포함해야 합니다.",
+ "settings.agentBehaviour.importMode.nameTaken": "이 이름의 모드가 이미 존재합니다.",
+ "settings.agentBehaviour.importMode.invalidJson": "잘못된 JSON 파일입니다. 유효한 에이전트 정의 파일을 선택하세요.",
+ "settings.agentBehaviour.importMode.tooLarge": "파일이 너무 큽니다. 에이전트 정의는 1 MB 미만이어야 합니다.",
+ "settings.agentBehaviour.exportMode": "에이전트 정의 내보내기",
+ "settings.agentBehaviour.editMode": "모드 편집",
+ "settings.agentBehaviour.editMode.description": "설명",
+ "settings.agentBehaviour.editMode.prompt": "시스템 프롬프트",
+ "settings.agentBehaviour.editMode.save": "완료",
+ "settings.agentBehaviour.editMode.back": "목록으로 돌아가기",
+ "settings.agentBehaviour.editMode.native": "내장 모드 (읽기 전용 정의)",
+ "settings.agentBehaviour.editMode.promptOverride": "이 내장 모드에 대한 사용자 지정 프롬프트 재정의",
"settings.agentBehaviour.removeMode.title": "모드 제거",
"settings.agentBehaviour.removeMode.confirm":
'모드 "{{name}}"을(를) 제거하시겠습니까? 구성을 업데이트하여 모드를 비활성화합니다.',
@@ -1051,18 +1114,42 @@ export const dict = {
"settings.agentBehaviour.removeMcp.title": "MCP 서버 제거",
"settings.agentBehaviour.removeMcp.confirm": 'MCP 서버 "{{name}}"을(를) 제거하시겠습니까? 구성에서 제거됩니다.',
"settings.agentBehaviour.removeMcp.button": "제거",
+ "settings.agentBehaviour.editMcp": "MCP 서버 편집",
+ "settings.agentBehaviour.editMcp.transportLocal": "로컬 서버 (stdio 전송)",
+ "settings.agentBehaviour.editMcp.transportRemote": "원격 서버 (SSE/HTTP 전송)",
+ "settings.agentBehaviour.editMcp.env": "환경 변수",
+ "settings.agentBehaviour.editMcp.env.help": "MCP 서버 프로세스에 전달되는 변수입니다.",
+ "settings.agentBehaviour.addMcp.command": "명령어",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "인수",
+ "settings.agentBehaviour.addMcp.args.help": "한 줄에 하나의 인수. 공백이 포함된 경로는 그대로 유지됩니다.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "서버 URL",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "스킬 폴더 경로",
"settings.agentBehaviour.skillUrls": "스킬 URL",
"settings.agentBehaviour.removeSkill.title": "스킬 제거",
"settings.agentBehaviour.removeSkill.confirm":
'스킬 "{{name}}"을(를) 제거하시겠습니까? 디스크에서 스킬 파일이 삭제됩니다.',
"settings.agentBehaviour.removeSkill.button": "제거",
+ "settings.agentBehaviour.rules.description":
+ "규칙은 에이전트 동작을 안내하는 지시 파일입니다. 모든 대화의 시스템 프롬프트에 포함됩니다. 추가 규칙을 포함하려면 아래에 파일 경로를 추가하세요.",
"settings.agentBehaviour.instructionFiles": "추가 지시 파일",
"settings.agentBehaviour.instructionFiles.description": "시스템 프롬프트에 포함되는 추가 지시 파일 경로",
+ "settings.agentBehaviour.mcpDetail.command": "명령어",
+ "settings.agentBehaviour.mcpDetail.args": "인수",
+ "settings.agentBehaviour.mcpDetail.env": "환경",
+ "settings.agentBehaviour.mcpDetail.disabled": "이 서버는 비활성화되어 있습니다.",
"settings.agentBehaviour.mcpEmpty":
"MCP 서버가 구성되지 않았습니다. opencode 구성 파일을 편집하여 MCP 서버를 추가하세요.",
"settings.agentBehaviour.workflowsPlaceholder": "워크플로우는 워크스페이스의 워크플로우 파일을 통해 관리됩니다.",
- "settings.agentBehaviour.notImplemented": "아직 구현되지 않았습니다.",
+ "settings.agentBehaviour.workflows.description":
+ "워크플로우는 구성에서 정의된 사용자 정의 슬래시 명령입니다. 채팅에서 /command-name을 입력하여 실행합니다. 명령은 opencode.json의 'command' 섹션에서 구성됩니다.",
+ "settings.agentBehaviour.workflows.empty":
+ "구성된 사용자 정의 명령이 없습니다. opencode.json에 명령을 추가하면 여기에 표시됩니다.",
+ "settings.agentBehaviour.workflows.detail.description": "설명",
+ "settings.agentBehaviour.workflows.detail.template": "템플릿",
+
"settings.autoApprove.description":
"도구 실행 허용 방식을 정의합니다. 대부분의 도구 기본값은 '허용'입니다. doom_loop 및 external_directory의 기본값은 '확인'입니다.",
"settings.autoApprove.level.allow": "허용",
@@ -1110,7 +1197,8 @@ export const dict = {
"settings.providers.defaultModel.title": "기본 모델",
"settings.providers.defaultModel.description": "대화의 기본 모델",
"settings.providers.smallModel.title": "소형 모델",
- "settings.providers.smallModel.description": "제목 생성 및 기타 빠른 작업을 위한 경량 모델",
+ "settings.providers.smallModel.description":
+ "제목 생성, 커밋 메시지 생성, 프롬프트 개선 및 기타 빠른 작업을 위한 경량 모델",
"settings.providers.disabled": "비활성화된 공급자",
"settings.providers.disabled.description": "공급자 목록에서 숨길 공급자",
"settings.providers.enabled": "활성화된 공급자 (허용 목록)",
@@ -1191,4 +1279,8 @@ export const dict = {
"settings.saveBar.warning.many": "여러 세션이 실행 중이며 중단됩니다",
"settings.saveBar.saveAnyway": "그래도 저장",
"settings.saveBar.cancel": "취소",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts
index 0ec1a54c039..f7818d373f2 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts
@@ -750,6 +750,21 @@ export const dict = {
"provider.custom.models.name.placeholder": "Weergavenaam",
"provider.custom.models.remove": "Model verwijderen",
"provider.custom.models.add": "Model toevoegen",
+ "provider.custom.models.fetch": "Modellen ophalen",
+ "provider.custom.models.fetching": "Ophalen\u2026",
+ "provider.custom.models.fetch.error": "Kan modellen niet ophalen: {{error}}",
+ "provider.custom.models.fetch.authError":
+ "Authenticatie mislukt. Controleer de API-sleutel hierboven en probeer het opnieuw.",
+ "provider.custom.models.fetch.empty": "Geen modellen gevonden op deze server.",
+ "provider.custom.models.fetch.added": "{{count}} model(len) toegevoegd.",
+ "provider.custom.models.fetch.allExist": "Alle opgehaalde modellen zijn al toegevoegd.",
+ "provider.custom.models.fetch.selectAll": "Alles selecteren",
+ "provider.custom.models.fetch.deselectAll": "Alles deselecteren",
+ "provider.custom.models.fetch.found": "{{count}} modellen gevonden",
+ "provider.custom.models.fetch.showing": "{{shown}} van {{total}} weergegeven",
+ "provider.custom.models.fetch.search": "Modellen zoeken\u2026",
+ "provider.custom.models.fetch.add": "{{count}} model(len) toevoegen",
+ "provider.custom.edit.title": "Provider bewerken",
"provider.custom.headers.label": "Headers (optioneel)",
"provider.custom.headers.key.label": "Header",
"provider.custom.headers.key.placeholder": "Header-Naam",
@@ -823,8 +838,11 @@ export const dict = {
"session.delete.button": "Verwijder sessie",
"session.untitled": "Naamloos",
"session.recent": "Recent",
+ "session.showHistory": "Geschiedenis weergeven",
"session.search.placeholder": "Zoek sessies...",
"session.empty": "Nog geen sessies. Klik op + om een nieuw gesprek te starten.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Alleen deze repository",
"session.cloud.import": "Importeer uit de cloud",
"feedback.button": "Feedback & Ondersteuning",
@@ -905,8 +923,7 @@ export const dict = {
"settings.autocomplete.title": "Automatisch Aanvullen",
"settings.notifications.title": "Meldingen",
"settings.context.title": "Context",
- "settings.terminal.title": "Terminal",
- "settings.prompts.title": "Prompts",
+
"settings.experimental.title": "Experimenteel",
"settings.language.title": "Taal",
"settings.aboutKiloCode.title": "Over Kilo Code",
@@ -958,6 +975,20 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Reset alle instellingen van de Kilo Code extensie naar hun standaardwaarden. Dit heeft geen invloed op CLI of backend configuratie.",
"settings.aboutKiloCode.resetSettings.button": "Alle instellingen resetten",
+ "settings.aboutKiloCode.settingsTransfer.title": "Instellingen overdragen",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Exporteer of importeer uw instellingen om ze tussen VS Code-instanties over te dragen.",
+ "settings.aboutKiloCode.exportSettings": "Exporteren",
+ "settings.aboutKiloCode.importSettings": "Importeren",
+ "settings.aboutKiloCode.importSettings.invalidJson":
+ "Ongeldig JSON-bestand. Selecteer een geldig instellingenbestand.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "Het bestand bevat geen geldige Kilo-instellingen.",
+ "settings.aboutKiloCode.importSettings.tooLarge":
+ "Het bestand is te groot. Instellingenbestanden moeten kleiner zijn dan 1 MB.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "Dit bestand is geëxporteerd vanuit een nieuwere versie van Kilo. Sommige instellingen worden mogelijk genegeerd.",
+ "settings.aboutKiloCode.importSettings.success":
+ "Instellingen geïmporteerd. Controleer de bovenstaande wijzigingen en klik vervolgens op Opslaan.",
"settings.agentBehaviour.subtab.modes": "Modi",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -983,10 +1014,6 @@ export const dict = {
"common.add": "Toevoegen",
"common.choose": "Kies…",
- "settings.notImplemented": "Dit gedeelte is nog niet geïmplementeerd.",
- "settings.notImplemented.description":
- "Het zal configuratie-opties en verklarende tekst bevatten gerelateerd aan de geselecteerde instellingencategorie. Gebruik deze ruimte tijdens het opnieuw implementeren om lay-out, spatiëring, scrollgedrag en navigatiestatus te valideren voordat de daadwerkelijke besturingselementen worden gekoppeld.",
-
"settings.autocomplete.autoTrigger.title": "Automatische inline aanvullingen inschakelen",
"settings.autocomplete.autoTrigger.description":
"Toon automatisch suggesties voor inline aanvullingen tijdens het typen",
@@ -1052,11 +1079,25 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Nucleus sampling parameter (0-1)",
"settings.agentBehaviour.maxSteps.title": "Max Stappen",
"settings.agentBehaviour.maxSteps.description": "Maximale agent iteraties",
+ "settings.agentBehaviour.hidden.title": "Verborgen",
+ "settings.agentBehaviour.hidden.description": "Verberg deze agent uit de modusschakelaar in de chatinvoer",
+ "settings.agentBehaviour.disable.title": "Uitgeschakeld",
+ "settings.agentBehaviour.disable.description": "Schakel deze agent volledig uit — deze verschijnt nergens",
+ "settings.agentBehaviour.badge.hidden": "verborgen",
+ "settings.agentBehaviour.badge.disabled": "uitgeschakeld",
"settings.agentBehaviour.discoveredSkills": "Ontdekte Skills",
"settings.agentBehaviour.noSkillsFound":
"Geen skills ontdekt. Voeg hieronder skill mappaden of URL's toe om skills beschikbaar te maken.",
"settings.agentBehaviour.availableModes": "Beschikbare Aangepaste Modi",
"settings.agentBehaviour.noModesFound": "Geen modi gevonden.",
+ "settings.agentBehaviour.importMode": "Importeren",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Ongeldige modusnaam in bestand. De naam moet beginnen met een kleine letter en mag alleen kleine letters, cijfers en streepjes bevatten.",
+ "settings.agentBehaviour.importMode.nameTaken": "Er bestaat al een modus met deze naam.",
+ "settings.agentBehaviour.importMode.invalidJson":
+ "Ongeldig JSON-bestand. Selecteer een geldig agentdefinitiebestand.",
+ "settings.agentBehaviour.importMode.tooLarge": "Bestand is te groot. Agentdefinities moeten kleiner zijn dan 1 MB.",
+ "settings.agentBehaviour.exportMode": "Agentdefinitie exporteren",
"settings.agentBehaviour.removeMode.title": "Verwijder modus",
"settings.agentBehaviour.removeMode.confirm":
'Modus "{{name}}" verwijderen? Dit zal de modus uitschakelen door je configuratie bij te werken.',
@@ -1067,6 +1108,8 @@ export const dict = {
"settings.agentBehaviour.removeSkill.confirm":
'Skill "{{name}}" verwijderen? Dit verwijdert de skill bestanden van schijf.',
"settings.agentBehaviour.removeSkill.button": "Verwijderen",
+ "settings.agentBehaviour.rules.description":
+ "Regels zijn instructiebestanden die het gedrag van de agent sturen. Ze worden opgenomen in de systeemprompt voor elk gesprek. Voeg hieronder bestandspaden toe om aanvullende regels op te nemen.",
"settings.agentBehaviour.instructionFiles": "Aanvullende Instructiebestanden",
"settings.agentBehaviour.instructionFiles.description":
"Paden naar aanvullende instructiebestanden die zijn opgenomen in de systeem prompt",
@@ -1074,10 +1117,32 @@ export const dict = {
"settings.agentBehaviour.removeMcp.confirm":
'MCP-server "{{name}}" verwijderen? Dit zal deze uit je configuratie verwijderen.',
"settings.agentBehaviour.removeMcp.button": "Verwijderen",
+ "settings.agentBehaviour.mcpDetail.command": "Opdracht",
+ "settings.agentBehaviour.mcpDetail.args": "Argumenten",
+ "settings.agentBehaviour.mcpDetail.env": "Omgeving",
+ "settings.agentBehaviour.mcpDetail.disabled": "Deze server is uitgeschakeld.",
+ "settings.agentBehaviour.editMcp": "MCP-server bewerken",
+ "settings.agentBehaviour.editMcp.transportLocal": "Lokale server (stdio-transport)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Externe server (SSE/HTTP-transport)",
+ "settings.agentBehaviour.editMcp.env": "Omgevingsvariabelen",
+ "settings.agentBehaviour.editMcp.env.help": "Variabelen die worden doorgegeven aan het MCP-serverproces.",
+ "settings.agentBehaviour.addMcp.command": "Opdracht",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Argumenten",
+ "settings.agentBehaviour.addMcp.args.help":
+ "Eén argument per regel. Paden met spaties worden ongewijzigd overgenomen.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "Server-URL",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.mcpEmpty":
"Geen MCP-servers geconfigureerd. Bewerk het opencode configuratiebestand om MCP-servers toe te voegen.",
"settings.agentBehaviour.workflowsPlaceholder": "Workflows worden beheerd via workflowbestanden in je workspace.",
- "settings.agentBehaviour.notImplemented": "Nog niet geïmplementeerd.",
+ "settings.agentBehaviour.workflows.description":
+ "Workflows zijn aangepaste slash-commando's gedefinieerd in je configuratie. Typ /command-name in de chat om ze aan te roepen. Commando's worden geconfigureerd in opencode.json onder de sectie 'command'.",
+ "settings.agentBehaviour.workflows.empty":
+ "Geen aangepaste commando's geconfigureerd. Voeg commando's toe aan opencode.json om ze hier te zien.",
+ "settings.agentBehaviour.workflows.detail.description": "Beschrijving",
+ "settings.agentBehaviour.workflows.detail.template": "Sjabloon",
"settings.autoApprove.description":
"Definieer hoe tools mogen worden uitgevoerd. De meeste tools staan standaard op Toestaan. doom_loop en external_directory staan standaard op Vragen.",
@@ -1139,7 +1204,7 @@ export const dict = {
"settings.providers.defaultModel.description": "Primair model voor gesprekken",
"settings.providers.smallModel.title": "Klein Model",
"settings.providers.smallModel.description":
- "Lichtgewicht model voor het genereren van titels en andere snelle taken",
+ "Lichtgewicht model voor het genereren van titels, commit-berichten, promptverbetering en andere snelle taken",
"settings.providers.modeModels": "Model per Modus",
"settings.providers.modeModels.description":
"Overschrijf het standaard model voor specifieke modi. Indien niet ingesteld, wordt het globale standaard model gebruikt.",
@@ -1232,4 +1297,8 @@ export const dict = {
"settings.saveBar.warning.many": "Meerdere sessies zijn actief en worden onderbroken",
"settings.saveBar.saveAnyway": "Toch opslaan",
"settings.saveBar.cancel": "Annuleren",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts
index 41f5d36b390..485a1d0b668 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts
@@ -755,6 +755,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "Visningsnavn",
"provider.custom.models.remove": "Fjern modell",
"provider.custom.models.add": "Legg til modell",
+ "provider.custom.models.fetch": "Hent modeller",
+ "provider.custom.models.fetching": "Henter\u2026",
+ "provider.custom.models.fetch.error": "Kunne ikke hente modeller: {{error}}",
+ "provider.custom.models.fetch.authError": "Autentisering mislyktes. Sjekk API-nøkkelen ovenfor og prøv igjen.",
+ "provider.custom.models.fetch.empty": "Ingen modeller funnet på denne serveren.",
+ "provider.custom.models.fetch.added": "{{count}} modell(er) lagt til.",
+ "provider.custom.models.fetch.allExist": "Alle hentede modeller er allerede lagt til.",
+ "provider.custom.models.fetch.selectAll": "Velg alle",
+ "provider.custom.models.fetch.deselectAll": "Fjern alle valg",
+ "provider.custom.models.fetch.found": "{{count}} modeller funnet",
+ "provider.custom.models.fetch.showing": "Viser {{shown}} av {{total}}",
+ "provider.custom.models.fetch.search": "Søk etter modeller\u2026",
+ "provider.custom.models.fetch.add": "Legg til {{count}} modell(er)",
+ "provider.custom.edit.title": "Rediger leverandør",
"provider.custom.headers.label": "Headere (valgfritt)",
"provider.custom.headers.key.label": "Header",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -828,8 +842,11 @@ export const dict = {
"session.delete.button": "Slett sesjon",
"session.untitled": "Uten tittel",
"session.recent": "Nylige",
+ "session.showHistory": "Vis historikk",
"session.search.placeholder": "Søk i sesjoner...",
"session.empty": "Ingen sesjoner ennå. Klikk + for å starte en ny samtale.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Kun dette repositoriet",
"session.cloud.import": "Importer fra skyen",
"feedback.button": "Tilbakemelding & støtte",
@@ -910,8 +927,7 @@ export const dict = {
"settings.autocomplete.title": "Autofullfør",
"settings.notifications.title": "Varslinger",
"settings.context.title": "Kontekst",
- "settings.terminal.title": "Terminal",
- "settings.prompts.title": "Meldinger",
+
"settings.experimental.title": "Eksperimentelt",
"settings.language.title": "Språk",
"settings.aboutKiloCode.title": "Om Kilo Code",
@@ -962,6 +978,18 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Tilbakestill alle Kilo Code-utvidelsesinnstillinger til standardverdier. Dette påvirker ikke CLI- eller backend-konfigurasjon.",
"settings.aboutKiloCode.resetSettings.button": "Tilbakestill alle innstillinger",
+ "settings.aboutKiloCode.settingsTransfer.title": "Overføring av innstillinger",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Eksporter eller importer innstillingene dine for å overføre dem mellom VS Code-instanser.",
+ "settings.aboutKiloCode.exportSettings": "Eksporter",
+ "settings.aboutKiloCode.importSettings": "Importer",
+ "settings.aboutKiloCode.importSettings.invalidJson": "Ugyldig JSON-fil. Vennligst velg en gyldig innstillingsfil.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "Filen inneholder ikke gyldige Kilo-innstillinger.",
+ "settings.aboutKiloCode.importSettings.tooLarge": "Filen er for stor. Innstillingsfiler må være under 1 MB.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "Denne filen ble eksportert fra en nyere versjon av Kilo. Noen innstillinger kan bli ignorert.",
+ "settings.aboutKiloCode.importSettings.success":
+ "Innstillinger importert. Gjennomgå endringene ovenfor, og klikk deretter på Lagre.",
"settings.agentBehaviour.subtab.modes": "Moduser",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -986,8 +1014,7 @@ export const dict = {
"common.add": "Legg til",
"common.choose": "Velg…",
- "settings.notImplemented": "Denne seksjonen er ikke implementert ennå.",
- "settings.notImplemented.description": "Den vil inneholde konfigurasjonsalternativer og forklarende tekst.",
+
"settings.autocomplete.autoTrigger.title": "Aktiver automatisk innebygd fullføring",
"settings.autocomplete.autoTrigger.description": "Vis automatisk innebygde fullføringsforslag ved skriving",
"settings.autocomplete.smartKeybinding.title": "Aktiver smart innebygd oppgavetastbinding",
@@ -1045,11 +1072,48 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Nucleus-samplingsparameter (0-1)",
"settings.agentBehaviour.maxSteps.title": "Maks. trinn",
"settings.agentBehaviour.maxSteps.description": "Maksimale agentiterasjoner",
+ "settings.agentBehaviour.hidden.title": "Skjult",
+ "settings.agentBehaviour.hidden.description": "Skjul denne agenten fra modusvelgeren i chatinntastingen",
+ "settings.agentBehaviour.disable.title": "Deaktivert",
+ "settings.agentBehaviour.disable.description": "Deaktiver denne agenten fullstendig — den vises ikke noe sted",
+ "settings.agentBehaviour.badge.hidden": "skjult",
+ "settings.agentBehaviour.badge.disabled": "deaktivert",
"settings.agentBehaviour.discoveredSkills": "Oppdagede ferdigheter",
"settings.agentBehaviour.noSkillsFound":
"Ingen ferdigheter funnet. Legg til ferdighetsmappestier eller URLer nedenfor for å gjøre ferdigheter tilgjengelige.",
"settings.agentBehaviour.availableModes": "Tilgjengelige egendefinerte moduser",
"settings.agentBehaviour.noModesFound": "Ingen moduser funnet.",
+ "settings.agentBehaviour.createMode": "Opprett ny modus",
+ "settings.agentBehaviour.createMode.name": "Navn",
+ "settings.agentBehaviour.createMode.name.placeholder": "f.eks. reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "Unik identifikator for modusen. Bruk bare små bokstaver, tall og bindestreker.",
+ "settings.agentBehaviour.createMode.description": "Beskrivelse",
+ "settings.agentBehaviour.createMode.description.placeholder": "f.eks. Gjennomgår kode for kvalitet og beste praksis",
+ "settings.agentBehaviour.createMode.description.help": "Kort beskrivelse av hva denne modusen gjør.",
+ "settings.agentBehaviour.createMode.prompt": "Systemprompt",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "f.eks. Du er en kodeanmelder. Fokuser på kodekvalitet, beste praksis og potensielle feil.",
+ "settings.agentBehaviour.createMode.prompt.help": "Instruksjoner til AI-agenten ved bruk av denne modusen.",
+ "settings.agentBehaviour.createMode.button": "Opprett",
+ "settings.agentBehaviour.createMode.cancel": "Avbryt",
+ "settings.agentBehaviour.createMode.nameRequired": "Navn er påkrevd",
+ "settings.agentBehaviour.createMode.nameInvalid": "Navn kan bare inneholde små bokstaver, tall og bindestreker",
+ "settings.agentBehaviour.createMode.nameTaken": "En modus med dette navnet eksisterer allerede",
+ "settings.agentBehaviour.importMode": "Importer",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Ugyldig modusnavn i filen. Navnet må starte med en liten bokstav og bare inneholde små bokstaver, tall og bindestreker.",
+ "settings.agentBehaviour.importMode.nameTaken": "En modus med dette navnet eksisterer allerede.",
+ "settings.agentBehaviour.importMode.invalidJson": "Ugyldig JSON-fil. Vennligst velg en gyldig agentdefinisjonsfil.",
+ "settings.agentBehaviour.importMode.tooLarge": "Filen er for stor. Agentdefinisjoner må være under 1 MB.",
+ "settings.agentBehaviour.exportMode": "Eksporter agentdefinisjon",
+ "settings.agentBehaviour.editMode": "Rediger modus",
+ "settings.agentBehaviour.editMode.description": "Beskrivelse",
+ "settings.agentBehaviour.editMode.prompt": "Systemprompt",
+ "settings.agentBehaviour.editMode.save": "Ferdig",
+ "settings.agentBehaviour.editMode.back": "Tilbake til listen",
+ "settings.agentBehaviour.editMode.native": "Innebygd modus (skrivebeskyttet definisjon)",
+ "settings.agentBehaviour.editMode.promptOverride": "Tilpasset prompt-overstyring for denne innebygde modusen",
"settings.agentBehaviour.removeMode.title": "Fjern modus",
"settings.agentBehaviour.removeMode.confirm":
'Vil du fjerne modusen "{{name}}"? Dette vil deaktivere modusen ved å oppdatere konfigurasjonen din.',
@@ -1058,18 +1122,42 @@ export const dict = {
"settings.agentBehaviour.removeMcp.confirm":
'Vil du fjerne MCP-serveren "{{name}}"? Dette vil fjerne den fra konfigurasjonen din.',
"settings.agentBehaviour.removeMcp.button": "Fjern",
+ "settings.agentBehaviour.editMcp": "Rediger MCP-server",
+ "settings.agentBehaviour.editMcp.transportLocal": "Lokal server (stdio-transport)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Ekstern server (SSE/HTTP-transport)",
+ "settings.agentBehaviour.editMcp.env": "Miljøvariabler",
+ "settings.agentBehaviour.editMcp.env.help": "Variabler som sendes til MCP-serverprosessen.",
+ "settings.agentBehaviour.addMcp.command": "Kommando",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Argumenter",
+ "settings.agentBehaviour.addMcp.args.help": "Ett argument per linje. Stier med mellomrom bevares som de er.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "Server-URL",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "Ferdighetsmappe-stier",
"settings.agentBehaviour.skillUrls": "Ferdighets-URLer",
"settings.agentBehaviour.removeSkill.title": "Fjern ferdighet",
"settings.agentBehaviour.removeSkill.confirm":
'Vil du fjerne ferdigheten "{{name}}"? Dette vil slette ferdighetsfilene fra disken.',
"settings.agentBehaviour.removeSkill.button": "Fjern",
+ "settings.agentBehaviour.rules.description":
+ "Regler er instruksjonsfiler som styrer agentens atferd. De inkluderes i systemprompten for hver samtale. Legg til filstier nedenfor for å inkludere ekstra regler.",
"settings.agentBehaviour.instructionFiles": "Ekstra instruksjonsfiler",
"settings.agentBehaviour.instructionFiles.description": "Stier til ekstra instruksjonsfiler i systemprompten",
+ "settings.agentBehaviour.mcpDetail.command": "Kommando",
+ "settings.agentBehaviour.mcpDetail.args": "Argumenter",
+ "settings.agentBehaviour.mcpDetail.env": "Miljø",
+ "settings.agentBehaviour.mcpDetail.disabled": "Denne serveren er deaktivert.",
"settings.agentBehaviour.mcpEmpty":
"Ingen MCP-servere konfigurert. Rediger opencode-konfigurasjonsfilen for å legge til MCP-servere.",
"settings.agentBehaviour.workflowsPlaceholder": "Arbeidsflyter administreres via arbeidsflytfiler i arbeidsområdet.",
- "settings.agentBehaviour.notImplemented": "Ikke implementert ennå.",
+ "settings.agentBehaviour.workflows.description":
+ "Arbeidsflyter er egendefinerte skråstrekkommandoer definert i konfigurasjonen din. Skriv /command-name i chatten for å kjøre dem. Kommandoer konfigureres i opencode.json under seksjonen 'command'.",
+ "settings.agentBehaviour.workflows.empty":
+ "Ingen egendefinerte kommandoer konfigurert. Legg til kommandoer i opencode.json for å se dem her.",
+ "settings.agentBehaviour.workflows.detail.description": "Beskrivelse",
+ "settings.agentBehaviour.workflows.detail.template": "Mal",
+
"settings.autoApprove.description":
"Definer hvordan verktøy kan kjøre. De fleste verktøy har Tillat som standard. doom_loop og external_directory har Spør som standard.",
"settings.autoApprove.level.allow": "Tillat",
@@ -1122,7 +1210,8 @@ export const dict = {
"settings.providers.defaultModel.title": "Standardmodell",
"settings.providers.defaultModel.description": "Primær modell for samtaler",
"settings.providers.smallModel.title": "Liten modell",
- "settings.providers.smallModel.description": "Lettvektsmodell for titelgenerering og raske oppgaver",
+ "settings.providers.smallModel.description":
+ "Lettvektsmodell for titelgenerering, generering av commit-meldinger, prompt-forbedring og andre raske oppgaver",
"settings.providers.disabled": "Deaktiverte leverandører",
"settings.providers.disabled.description": "Leverandører å skjule fra listen",
"settings.providers.enabled": "Aktiverte leverandører (hvitliste)",
@@ -1201,4 +1290,8 @@ export const dict = {
"settings.saveBar.warning.many": "Flere økter kjører og vil bli avbrutt",
"settings.saveBar.saveAnyway": "Lagre uansett",
"settings.saveBar.cancel": "Avbryt",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
} satisfies Partial>
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts
index 24e68ab8ba5..5db2913b2cf 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts
@@ -755,6 +755,21 @@ export const dict = {
"provider.custom.models.name.placeholder": "Nazwa wyświetlana",
"provider.custom.models.remove": "Usuń model",
"provider.custom.models.add": "Dodaj model",
+ "provider.custom.models.fetch": "Pobierz modele",
+ "provider.custom.models.fetching": "Pobieranie\u2026",
+ "provider.custom.models.fetch.error": "Nie udało się pobrać modeli: {{error}}",
+ "provider.custom.models.fetch.authError":
+ "Uwierzytelnianie nie powiodło się. Sprawdź klucz API powyżej i spróbuj ponownie.",
+ "provider.custom.models.fetch.empty": "Nie znaleziono modeli na tym serwerze.",
+ "provider.custom.models.fetch.added": "Dodano {{count}} model(i).",
+ "provider.custom.models.fetch.allExist": "Wszystkie pobrane modele są już dodane.",
+ "provider.custom.models.fetch.selectAll": "Zaznacz wszystkie",
+ "provider.custom.models.fetch.deselectAll": "Odznacz wszystkie",
+ "provider.custom.models.fetch.found": "Znaleziono {{count}} modeli",
+ "provider.custom.models.fetch.showing": "Wyświetlanie {{shown}} z {{total}}",
+ "provider.custom.models.fetch.search": "Szukaj modeli\u2026",
+ "provider.custom.models.fetch.add": "Dodaj {{count}} model(i)",
+ "provider.custom.edit.title": "Edytuj dostawcę",
"provider.custom.headers.label": "Nagłówki (opcjonalnie)",
"provider.custom.headers.key.label": "Nagłówek",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -827,8 +842,11 @@ export const dict = {
"session.delete.button": "Usuń sesję",
"session.untitled": "Bez tytułu",
"session.recent": "Ostatnie",
+ "session.showHistory": "Pokaż historię",
"session.search.placeholder": "Szukaj sesji...",
"session.empty": "Brak sesji. Kliknij + aby rozpocząć nową rozmowę.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Tylko to repozytorium",
"session.cloud.import": "Importuj z chmury",
"feedback.button": "Opinie i wsparcie",
@@ -909,8 +927,7 @@ export const dict = {
"settings.autocomplete.title": "Autouzupełnianie",
"settings.notifications.title": "Powiadomienia",
"settings.context.title": "Kontekst",
- "settings.terminal.title": "Terminal",
- "settings.prompts.title": "Polecenia",
+
"settings.experimental.title": "Eksperymentalne",
"settings.language.title": "Język",
"settings.aboutKiloCode.title": "O Kilo Code",
@@ -962,6 +979,18 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Zresetuj wszystkie ustawienia rozszerzenia Kilo Code do wartości domyślnych. Nie wpływa to na konfigurację CLI ani backendu.",
"settings.aboutKiloCode.resetSettings.button": "Resetuj wszystkie ustawienia",
+ "settings.aboutKiloCode.settingsTransfer.title": "Przenoszenie ustawień",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Eksportuj lub importuj ustawienia, aby przenosić je między instancjami VS Code.",
+ "settings.aboutKiloCode.exportSettings": "Eksportuj",
+ "settings.aboutKiloCode.importSettings": "Importuj",
+ "settings.aboutKiloCode.importSettings.invalidJson": "Nieprawidłowy plik JSON. Wybierz prawidłowy plik ustawień.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "Plik nie zawiera prawidłowych ustawień Kilo.",
+ "settings.aboutKiloCode.importSettings.tooLarge": "Plik jest za duży. Pliki ustawień muszą mieć mniej niż 1 MB.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "Ten plik został wyeksportowany z nowszej wersji Kilo. Niektóre ustawienia mogą zostać zignorowane.",
+ "settings.aboutKiloCode.importSettings.success":
+ "Ustawienia zaimportowane. Przejrzyj powyższe zmiany, a następnie kliknij Zapisz.",
"settings.agentBehaviour.subtab.modes": "Tryby",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -986,8 +1015,7 @@ export const dict = {
"common.add": "Dodaj",
"common.choose": "Wybierz…",
- "settings.notImplemented": "Ta sekcja nie jest jeszcze zaimplementowana.",
- "settings.notImplemented.description": "Będzie zawierać opcje konfiguracji i tekst objaśniający.",
+
"settings.autocomplete.autoTrigger.title": "Włącz automatyczne uzupełnianie inline",
"settings.autocomplete.autoTrigger.description": "Automatycznie pokazuj sugestie uzupełniania podczas pisania",
"settings.autocomplete.smartKeybinding.title": "Włącz inteligentny skrót zadania inline",
@@ -1045,11 +1073,50 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Parametr próbkowania nucleus (0-1)",
"settings.agentBehaviour.maxSteps.title": "Maks. kroki",
"settings.agentBehaviour.maxSteps.description": "Maksymalna liczba iteracji agenta",
+ "settings.agentBehaviour.hidden.title": "Ukryty",
+ "settings.agentBehaviour.hidden.description": "Ukryj tego agenta z przełącznika trybów w polu czatu",
+ "settings.agentBehaviour.disable.title": "Wyłączony",
+ "settings.agentBehaviour.disable.description": "Całkowicie wyłącz tego agenta — nie pojawi się nigdzie",
+ "settings.agentBehaviour.badge.hidden": "ukryty",
+ "settings.agentBehaviour.badge.disabled": "wyłączony",
"settings.agentBehaviour.discoveredSkills": "Wykryte umiejętności",
"settings.agentBehaviour.noSkillsFound":
"Nie znaleziono umiejętności. Dodaj ścieżki folderów lub adresy URL poniżej, aby udostępnić umiejętności.",
"settings.agentBehaviour.availableModes": "Dostępne tryby niestandardowe",
"settings.agentBehaviour.noModesFound": "Nie znaleziono trybów.",
+ "settings.agentBehaviour.createMode": "Utwórz nowy tryb",
+ "settings.agentBehaviour.createMode.name": "Nazwa",
+ "settings.agentBehaviour.createMode.name.placeholder": "np. reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "Unikalny identyfikator trybu. Używaj tylko małych liter, cyfr i myślników.",
+ "settings.agentBehaviour.createMode.description": "Opis",
+ "settings.agentBehaviour.createMode.description.placeholder":
+ "np. Sprawdza kod pod kątem jakości i najlepszych praktyk",
+ "settings.agentBehaviour.createMode.description.help": "Krótki opis tego, co robi ten tryb.",
+ "settings.agentBehaviour.createMode.prompt": "Prompt systemowy",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "np. Jesteś recenzentem kodu. Skup się na jakości kodu, najlepszych praktykach i potencjalnych błędach.",
+ "settings.agentBehaviour.createMode.prompt.help": "Instrukcje dla agenta AI podczas korzystania z tego trybu.",
+ "settings.agentBehaviour.createMode.button": "Utwórz",
+ "settings.agentBehaviour.createMode.cancel": "Anuluj",
+ "settings.agentBehaviour.createMode.nameRequired": "Nazwa jest wymagana",
+ "settings.agentBehaviour.createMode.nameInvalid": "Nazwa może zawierać tylko małe litery, cyfry i myślniki",
+ "settings.agentBehaviour.createMode.nameTaken": "Tryb o tej nazwie już istnieje",
+ "settings.agentBehaviour.importMode": "Importuj",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Nieprawidłowa nazwa trybu w pliku. Nazwa musi zaczynać się od małej litery i zawierać tylko małe litery, cyfry i myślniki.",
+ "settings.agentBehaviour.importMode.nameTaken": "Tryb o tej nazwie już istnieje.",
+ "settings.agentBehaviour.importMode.invalidJson":
+ "Nieprawidłowy plik JSON. Proszę wybrać prawidłowy plik definicji agenta.",
+ "settings.agentBehaviour.importMode.tooLarge": "Plik jest za duży. Definicje agentów muszą mieć mniej niż 1 MB.",
+ "settings.agentBehaviour.exportMode": "Eksportuj definicję agenta",
+ "settings.agentBehaviour.editMode": "Edytuj tryb",
+ "settings.agentBehaviour.editMode.description": "Opis",
+ "settings.agentBehaviour.editMode.prompt": "Prompt systemowy",
+ "settings.agentBehaviour.editMode.save": "Gotowe",
+ "settings.agentBehaviour.editMode.back": "Powrót do listy",
+ "settings.agentBehaviour.editMode.native": "Tryb wbudowany (definicja tylko do odczytu)",
+ "settings.agentBehaviour.editMode.promptOverride": "Niestandardowe nadpisanie promptu dla tego wbudowanego trybu",
"settings.agentBehaviour.removeMode.title": "Usuń tryb",
"settings.agentBehaviour.removeMode.confirm":
'Usunąć tryb "{{name}}"? Spowoduje to wyłączenie trybu poprzez aktualizację konfiguracji.',
@@ -1058,19 +1125,43 @@ export const dict = {
"settings.agentBehaviour.removeMcp.confirm":
'Usunąć serwer MCP "{{name}}"? Spowoduje to usunięcie go z konfiguracji.',
"settings.agentBehaviour.removeMcp.button": "Usuń",
+ "settings.agentBehaviour.editMcp": "Edytuj serwer MCP",
+ "settings.agentBehaviour.editMcp.transportLocal": "Serwer lokalny (transport stdio)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Serwer zdalny (transport SSE/HTTP)",
+ "settings.agentBehaviour.editMcp.env": "Zmienne środowiskowe",
+ "settings.agentBehaviour.editMcp.env.help": "Zmienne przekazywane do procesu serwera MCP.",
+ "settings.agentBehaviour.addMcp.command": "Polecenie",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Argumenty",
+ "settings.agentBehaviour.addMcp.args.help": "Jeden argument na linię. Ścieżki ze spacjami są zachowywane bez zmian.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "URL serwera",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "Ścieżki folderów umiejętności",
"settings.agentBehaviour.skillUrls": "Adresy URL umiejętności",
"settings.agentBehaviour.removeSkill.title": "Usuń umiejętność",
"settings.agentBehaviour.removeSkill.confirm":
'Usunąć umiejętność "{{name}}"? Spowoduje to usunięcie plików umiejętności z dysku.',
"settings.agentBehaviour.removeSkill.button": "Usuń",
+ "settings.agentBehaviour.rules.description":
+ "Reguły to pliki instrukcji, które kierują zachowaniem agenta. Są one dołączane do promptu systemowego dla każdej rozmowy. Dodaj poniżej ścieżki plików, aby dołączyć dodatkowe reguły.",
"settings.agentBehaviour.instructionFiles": "Dodatkowe pliki instrukcji",
"settings.agentBehaviour.instructionFiles.description":
"Ścieżki do dodatkowych plików instrukcji w prompcie systemowym",
+ "settings.agentBehaviour.mcpDetail.command": "Polecenie",
+ "settings.agentBehaviour.mcpDetail.args": "Argumenty",
+ "settings.agentBehaviour.mcpDetail.env": "Środowisko",
+ "settings.agentBehaviour.mcpDetail.disabled": "Ten serwer jest wyłączony.",
"settings.agentBehaviour.mcpEmpty":
"Brak skonfigurowanych serwerów MCP. Edytuj plik konfiguracyjny opencode, aby dodać serwery MCP.",
"settings.agentBehaviour.workflowsPlaceholder": "Przepływy pracy zarządzane są za pomocą plików przepływów pracy.",
- "settings.agentBehaviour.notImplemented": "Jeszcze nie zaimplementowano.",
+ "settings.agentBehaviour.workflows.description":
+ "Przepływy pracy to niestandardowe komendy slash zdefiniowane w konfiguracji. Wpisz /command-name na czacie, aby je uruchomić. Komendy konfiguruje się w opencode.json w sekcji 'command'.",
+ "settings.agentBehaviour.workflows.empty":
+ "Brak skonfigurowanych niestandardowych komend. Dodaj komendy do opencode.json, aby je tu zobaczyć.",
+ "settings.agentBehaviour.workflows.detail.description": "Opis",
+ "settings.agentBehaviour.workflows.detail.template": "Szablon",
+
"settings.autoApprove.description":
"Zdefiniuj, jak narzędzia mogą być uruchamiane. Większość narzędzi domyślnie ma ustawienie Zezwalaj. doom_loop i external_directory domyślnie mają ustawienie Pytaj.",
"settings.autoApprove.level.allow": "Zezwól",
@@ -1127,7 +1218,8 @@ export const dict = {
"settings.providers.defaultModel.title": "Domyślny model",
"settings.providers.defaultModel.description": "Główny model do rozmów",
"settings.providers.smallModel.title": "Mały model",
- "settings.providers.smallModel.description": "Lekki model do generowania tytułów i szybkich zadań",
+ "settings.providers.smallModel.description":
+ "Lekki model do generowania tytułów, wiadomości commitów, ulepszania zapytań i innych szybkich zadań",
"settings.providers.disabled": "Wyłączeni dostawcy",
"settings.providers.disabled.description": "Dostawcy do ukrycia z listy",
"settings.providers.enabled": "Włączeni dostawcy (biała lista)",
@@ -1208,4 +1300,8 @@ export const dict = {
"settings.saveBar.warning.many": "Kilka sesji jest uruchomionych i zostanie przerwanych",
"settings.saveBar.saveAnyway": "Zapisz mimo to",
"settings.saveBar.cancel": "Anuluj",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts
index 16f690a128a..c9b42fbe0a4 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts
@@ -758,6 +758,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "Отображаемое имя",
"provider.custom.models.remove": "Удалить модель",
"provider.custom.models.add": "Добавить модель",
+ "provider.custom.models.fetch": "Получить модели",
+ "provider.custom.models.fetching": "Загрузка\u2026",
+ "provider.custom.models.fetch.error": "Не удалось получить модели: {{error}}",
+ "provider.custom.models.fetch.authError": "Ошибка аутентификации. Проверьте API-ключ выше и попробуйте снова.",
+ "provider.custom.models.fetch.empty": "На этом сервере модели не найдены.",
+ "provider.custom.models.fetch.added": "Добавлено {{count}} модель(ей).",
+ "provider.custom.models.fetch.allExist": "Все полученные модели уже добавлены.",
+ "provider.custom.models.fetch.selectAll": "Выбрать все",
+ "provider.custom.models.fetch.deselectAll": "Снять выбор",
+ "provider.custom.models.fetch.found": "Найдено {{count}} моделей",
+ "provider.custom.models.fetch.showing": "Показано {{shown}} из {{total}}",
+ "provider.custom.models.fetch.search": "Поиск моделей\u2026",
+ "provider.custom.models.fetch.add": "Добавить {{count}} модель(ей)",
+ "provider.custom.edit.title": "Редактировать провайдера",
"provider.custom.headers.label": "Заголовки (необязательно)",
"provider.custom.headers.key.label": "Заголовок",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -831,8 +845,11 @@ export const dict = {
"session.delete.button": "Удалить сессию",
"session.untitled": "Без названия",
"session.recent": "Недавние",
+ "session.showHistory": "Показать историю",
"session.search.placeholder": "Поиск сессий...",
"session.empty": "Сессий пока нет. Нажмите + чтобы начать новый разговор.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Только этот репозиторий",
"session.cloud.import": "Импорт из облака",
"feedback.button": "Отзывы и поддержка",
@@ -913,8 +930,7 @@ export const dict = {
"settings.autocomplete.title": "Автодополнение",
"settings.notifications.title": "Уведомления",
"settings.context.title": "Контекст",
- "settings.terminal.title": "Терминал",
- "settings.prompts.title": "Промпты",
+
"settings.experimental.title": "Экспериментальное",
"settings.language.title": "Язык",
"settings.aboutKiloCode.title": "О Kilo Code",
@@ -965,6 +981,18 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Сбросить все настройки расширения Kilo Code до значений по умолчанию. Это не влияет на конфигурацию CLI или бэкенда.",
"settings.aboutKiloCode.resetSettings.button": "Сбросить все настройки",
+ "settings.aboutKiloCode.settingsTransfer.title": "Перенос настроек",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Экспортируйте или импортируйте настройки для переноса между экземплярами VS Code.",
+ "settings.aboutKiloCode.exportSettings": "Экспорт",
+ "settings.aboutKiloCode.importSettings": "Импорт",
+ "settings.aboutKiloCode.importSettings.invalidJson": "Недопустимый файл JSON. Выберите корректный файл настроек.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "Файл не содержит допустимых настроек Kilo.",
+ "settings.aboutKiloCode.importSettings.tooLarge": "Файл слишком большой. Файлы настроек должны быть менее 1 MB.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "Этот файл был экспортирован из более новой версии Kilo. Некоторые настройки могут быть проигнорированы.",
+ "settings.aboutKiloCode.importSettings.success":
+ "Настройки импортированы. Просмотрите изменения выше и нажмите «Сохранить».",
"settings.agentBehaviour.subtab.modes": "Режимы",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -989,8 +1017,7 @@ export const dict = {
"common.add": "Добавить",
"common.choose": "Выбрать…",
- "settings.notImplemented": "Этот раздел ещё не реализован.",
- "settings.notImplemented.description": "Здесь будут параметры конфигурации и пояснительный текст.",
+
"settings.autocomplete.autoTrigger.title": "Включить автоматическое встроенное дополнение",
"settings.autocomplete.autoTrigger.description": "Автоматически показывать предложения дополнения при вводе",
"settings.autocomplete.smartKeybinding.title": "Включить умную клавишу встроенной задачи",
@@ -1048,11 +1075,51 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Параметр nucleus-сэмплирования (0-1)",
"settings.agentBehaviour.maxSteps.title": "Макс. шагов",
"settings.agentBehaviour.maxSteps.description": "Максимальное число итераций агента",
+ "settings.agentBehaviour.hidden.title": "Скрытый",
+ "settings.agentBehaviour.hidden.description": "Скрыть этого агента из переключателя режимов в поле ввода чата",
+ "settings.agentBehaviour.disable.title": "Отключён",
+ "settings.agentBehaviour.disable.description": "Полностью отключить этого агента — он не будет отображаться нигде",
+ "settings.agentBehaviour.badge.hidden": "скрытый",
+ "settings.agentBehaviour.badge.disabled": "отключён",
"settings.agentBehaviour.discoveredSkills": "Обнаруженные навыки",
"settings.agentBehaviour.noSkillsFound":
"Навыки не обнаружены. Добавьте пути к папкам навыков или URL-адреса ниже, чтобы сделать навыки доступными.",
"settings.agentBehaviour.availableModes": "Доступные пользовательские режимы",
"settings.agentBehaviour.noModesFound": "Режимы не найдены.",
+ "settings.agentBehaviour.createMode": "Создать новый режим",
+ "settings.agentBehaviour.createMode.name": "Название",
+ "settings.agentBehaviour.createMode.name.placeholder": "например, reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "Уникальный идентификатор режима. Используйте только строчные буквы, цифры и дефисы.",
+ "settings.agentBehaviour.createMode.description": "Описание",
+ "settings.agentBehaviour.createMode.description.placeholder":
+ "например, Проверяет код на качество и соответствие лучшим практикам",
+ "settings.agentBehaviour.createMode.description.help": "Краткое описание того, что делает этот режим.",
+ "settings.agentBehaviour.createMode.prompt": "Системный промпт",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "например, Вы ревьюер кода. Сосредоточьтесь на качестве кода, лучших практиках и потенциальных ошибках.",
+ "settings.agentBehaviour.createMode.prompt.help": "Инструкции для ИИ-агента при использовании этого режима.",
+ "settings.agentBehaviour.createMode.button": "Создать",
+ "settings.agentBehaviour.createMode.cancel": "Отмена",
+ "settings.agentBehaviour.createMode.nameRequired": "Название обязательно",
+ "settings.agentBehaviour.createMode.nameInvalid": "Название может содержать только строчные буквы, цифры и дефисы",
+ "settings.agentBehaviour.createMode.nameTaken": "Режим с таким названием уже существует",
+ "settings.agentBehaviour.importMode": "Импорт",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Недопустимое имя режима в файле. Имя должно начинаться со строчной буквы и содержать только строчные буквы, цифры и дефисы.",
+ "settings.agentBehaviour.importMode.nameTaken": "Режим с таким названием уже существует.",
+ "settings.agentBehaviour.importMode.invalidJson":
+ "Недопустимый файл JSON. Пожалуйста, выберите корректный файл определения агента.",
+ "settings.agentBehaviour.importMode.tooLarge": "Файл слишком большой. Определения агентов должны быть менее 1 МБ.",
+ "settings.agentBehaviour.exportMode": "Экспортировать определение агента",
+ "settings.agentBehaviour.editMode": "Редактировать режим",
+ "settings.agentBehaviour.editMode.description": "Описание",
+ "settings.agentBehaviour.editMode.prompt": "Системный промпт",
+ "settings.agentBehaviour.editMode.save": "Готово",
+ "settings.agentBehaviour.editMode.back": "Вернуться к списку",
+ "settings.agentBehaviour.editMode.native": "Встроенный режим (определение только для чтения)",
+ "settings.agentBehaviour.editMode.promptOverride":
+ "Пользовательское переопределение промпта для этого встроенного режима",
"settings.agentBehaviour.removeMode.title": "Удалить режим",
"settings.agentBehaviour.removeMode.confirm":
'Удалить режим "{{name}}"? Это отключит режим, обновив вашу конфигурацию.',
@@ -1060,18 +1127,42 @@ export const dict = {
"settings.agentBehaviour.removeMcp.title": "Удалить сервер MCP",
"settings.agentBehaviour.removeMcp.confirm": 'Удалить сервер MCP "{{name}}"? Это удалит его из вашей конфигурации.',
"settings.agentBehaviour.removeMcp.button": "Удалить",
+ "settings.agentBehaviour.editMcp": "Редактировать сервер MCP",
+ "settings.agentBehaviour.editMcp.transportLocal": "Локальный сервер (транспорт stdio)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Удалённый сервер (транспорт SSE/HTTP)",
+ "settings.agentBehaviour.editMcp.env": "Переменные окружения",
+ "settings.agentBehaviour.editMcp.env.help": "Переменные, передаваемые процессу сервера MCP.",
+ "settings.agentBehaviour.addMcp.command": "Команда",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Аргументы",
+ "settings.agentBehaviour.addMcp.args.help": "Один аргумент на строку. Пути с пробелами сохраняются как есть.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "URL сервера",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "Пути папок навыков",
"settings.agentBehaviour.skillUrls": "URL навыков",
"settings.agentBehaviour.removeSkill.title": "Удалить навык",
"settings.agentBehaviour.removeSkill.confirm":
'Удалить навык "{{name}}"? Это приведет к удалению файлов навыка с диска.',
"settings.agentBehaviour.removeSkill.button": "Удалить",
+ "settings.agentBehaviour.rules.description":
+ "Правила — это файлы инструкций, которые направляют поведение агента. Они включаются в системный промпт для каждого разговора. Добавьте пути к файлам ниже, чтобы включить дополнительные правила.",
"settings.agentBehaviour.instructionFiles": "Дополнительные файлы инструкций",
"settings.agentBehaviour.instructionFiles.description": "Пути к дополнительным файлам инструкций в системном промпте",
+ "settings.agentBehaviour.mcpDetail.command": "Команда",
+ "settings.agentBehaviour.mcpDetail.args": "Аргументы",
+ "settings.agentBehaviour.mcpDetail.env": "Окружение",
+ "settings.agentBehaviour.mcpDetail.disabled": "Этот сервер отключён.",
"settings.agentBehaviour.mcpEmpty":
"MCP-серверы не настроены. Отредактируйте файл конфигурации opencode для добавления MCP-серверов.",
"settings.agentBehaviour.workflowsPlaceholder": "Рабочие процессы управляются через файлы рабочих процессов.",
- "settings.agentBehaviour.notImplemented": "Ещё не реализовано.",
+ "settings.agentBehaviour.workflows.description":
+ "Рабочие процессы — это пользовательские слэш-команды, определённые в вашей конфигурации. Введите /command-name в чате, чтобы вызвать их. Команды настраиваются в opencode.json в разделе 'command'.",
+ "settings.agentBehaviour.workflows.empty":
+ "Пользовательские команды не настроены. Добавьте команды в opencode.json, чтобы увидеть их здесь.",
+ "settings.agentBehaviour.workflows.detail.description": "Описание",
+ "settings.agentBehaviour.workflows.detail.template": "Шаблон",
+
"settings.autoApprove.description":
"Определите правила запуска инструментов. Большинство инструментов по умолчанию Разрешены. Для doom_loop и external_directory по умолчанию установлено Спрашивать.",
"settings.autoApprove.level.allow": "Разрешить",
@@ -1126,7 +1217,8 @@ export const dict = {
"settings.providers.defaultModel.title": "Модель по умолчанию",
"settings.providers.defaultModel.description": "Основная модель для разговоров",
"settings.providers.smallModel.title": "Малая модель",
- "settings.providers.smallModel.description": "Лёгкая модель для генерации заголовков и быстрых задач",
+ "settings.providers.smallModel.description":
+ "Лёгкая модель для генерации заголовков, сообщений коммитов, улучшения промптов и других быстрых задач",
"settings.providers.disabled": "Отключённые провайдеры",
"settings.providers.disabled.description": "Провайдеры для скрытия из списка",
"settings.providers.enabled": "Включённые провайдеры (белый список)",
@@ -1207,4 +1299,8 @@ export const dict = {
"settings.saveBar.warning.many": "Несколько сеансов выполняются и будут прерваны",
"settings.saveBar.saveAnyway": "Сохранить в любом случае",
"settings.saveBar.cancel": "Отмена",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts
index d41aa865244..7d812f4fb1a 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts
@@ -748,6 +748,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "ชื่อที่แสดง",
"provider.custom.models.remove": "ลบโมเดล",
"provider.custom.models.add": "เพิ่มโมเดล",
+ "provider.custom.models.fetch": "ดึงโมเดล",
+ "provider.custom.models.fetching": "กำลังดึง\u2026",
+ "provider.custom.models.fetch.error": "ดึงโมเดลล้มเหลว: {{error}}",
+ "provider.custom.models.fetch.authError": "การยืนยันตัวตนล้มเหลว ตรวจสอบคีย์ API ด้านบนแล้วลองอีกครั้ง",
+ "provider.custom.models.fetch.empty": "ไม่พบโมเดลบนเซิร์ฟเวอร์นี้",
+ "provider.custom.models.fetch.added": "เพิ่มแล้ว {{count}} โมเดล",
+ "provider.custom.models.fetch.allExist": "โมเดลที่ดึงมาทั้งหมดถูกเพิ่มไปแล้ว",
+ "provider.custom.models.fetch.selectAll": "เลือกทั้งหมด",
+ "provider.custom.models.fetch.deselectAll": "ยกเลิกการเลือกทั้งหมด",
+ "provider.custom.models.fetch.found": "พบ {{count}} โมเดล",
+ "provider.custom.models.fetch.showing": "แสดง {{shown}} จาก {{total}}",
+ "provider.custom.models.fetch.search": "ค้นหาโมเดล\u2026",
+ "provider.custom.models.fetch.add": "เพิ่ม {{count}} โมเดล",
+ "provider.custom.edit.title": "แก้ไขผู้ให้บริการ",
"provider.custom.headers.label": "ส่วนหัว (ไม่จำเป็น)",
"provider.custom.headers.key.label": "ส่วนหัว",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -820,8 +834,11 @@ export const dict = {
"session.delete.button": "ลบเซสชัน",
"session.untitled": "ไม่มีชื่อ",
"session.recent": "ล่าสุด",
+ "session.showHistory": "แสดงประวัติ",
"session.search.placeholder": "ค้นหาเซสชัน...",
"session.empty": "ยังไม่มีเซสชัน คลิก + เพื่อเริ่มการสนทนาใหม่",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "เฉพาะรีโพซิทอรีนี้",
"session.cloud.import": "นำเข้าจากคลาวด์",
"feedback.button": "ข้อเสนอแนะและการสนับสนุน",
@@ -902,8 +919,7 @@ export const dict = {
"settings.autocomplete.title": "เติมข้อความอัตโนมัติ",
"settings.notifications.title": "การแจ้งเตือน",
"settings.context.title": "บริบท",
- "settings.terminal.title": "เทอร์มินัล",
- "settings.prompts.title": "พรอมต์",
+
"settings.experimental.title": "ทดลอง",
"settings.language.title": "ภาษา",
"settings.aboutKiloCode.title": "เกี่ยวกับ Kilo Code",
@@ -953,6 +969,17 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"รีเซ็ตการตั้งค่าส่วนขยาย Kilo Code ทั้งหมดเป็นค่าเริ่มต้น ไม่ส่งผลกระทบต่อการกำหนดค่า CLI หรือแบ็กเอนด์",
"settings.aboutKiloCode.resetSettings.button": "รีเซ็ตการตั้งค่าทั้งหมด",
+ "settings.aboutKiloCode.settingsTransfer.title": "ถ่ายโอนการตั้งค่า",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "ส่งออกหรือนำเข้าการตั้งค่าเพื่อถ่ายโอนระหว่างอินสแตนซ์ VS Code",
+ "settings.aboutKiloCode.exportSettings": "ส่งออก",
+ "settings.aboutKiloCode.importSettings": "นำเข้า",
+ "settings.aboutKiloCode.importSettings.invalidJson": "ไฟล์ JSON ไม่ถูกต้อง กรุณาเลือกไฟล์การตั้งค่าที่ถูกต้อง",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "ไฟล์ไม่มีการตั้งค่า Kilo ที่ถูกต้อง",
+ "settings.aboutKiloCode.importSettings.tooLarge": "ไฟล์มีขนาดใหญ่เกินไป ไฟล์การตั้งค่าต้องมีขนาดไม่เกิน 1 MB",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "ไฟล์นี้ถูกส่งออกจาก Kilo เวอร์ชันใหม่กว่า การตั้งค่าบางรายการอาจถูกข้ามไป",
+ "settings.aboutKiloCode.importSettings.success": "นำเข้าการตั้งค่าแล้ว ตรวจสอบการเปลี่ยนแปลงด้านบน จากนั้นคลิกบันทึก",
"settings.agentBehaviour.subtab.modes": "โหมด",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -976,8 +1003,7 @@ export const dict = {
"common.add": "เพิ่ม",
"common.choose": "เลือก…",
- "settings.notImplemented": "ส่วนนี้ยังไม่ได้ใช้งาน",
- "settings.notImplemented.description": "จะมีตัวเลือกการกำหนดค่าและข้อความอธิบาย",
+
"settings.autocomplete.autoTrigger.title": "เปิดใช้งานการเติมอัตโนมัติแบบอินไลน์",
"settings.autocomplete.autoTrigger.description": "แสดงข้อเสนอแนะการเติมอัตโนมัติระหว่างพิมพ์",
"settings.autocomplete.smartKeybinding.title": "เปิดใช้งานปุ่มลัดงานอินไลน์อัจฉริยะ",
@@ -1035,10 +1061,47 @@ export const dict = {
"settings.agentBehaviour.topP.description": "พารามิเตอร์ nucleus sampling (0-1)",
"settings.agentBehaviour.maxSteps.title": "ขั้นตอนสูงสุด",
"settings.agentBehaviour.maxSteps.description": "จำนวนรอบเอเจนต์สูงสุด",
+ "settings.agentBehaviour.hidden.title": "ซ่อน",
+ "settings.agentBehaviour.hidden.description": "ซ่อนเอเจนต์นี้จากตัวสลับโหมดในช่องป้อนแชท",
+ "settings.agentBehaviour.disable.title": "ปิดใช้งาน",
+ "settings.agentBehaviour.disable.description": "ปิดใช้งานเอเจนต์นี้อย่างสมบูรณ์ — จะไม่ปรากฏที่ใดเลย",
+ "settings.agentBehaviour.badge.hidden": "ซ่อน",
+ "settings.agentBehaviour.badge.disabled": "ปิดใช้งาน",
"settings.agentBehaviour.discoveredSkills": "ทักษะที่ค้นพบ",
"settings.agentBehaviour.noSkillsFound": "ไม่พบทักษะ เพิ่มเส้นทางโฟลเดอร์หรือ URL ด้านล่างเพื่อทำให้ทักษะพร้อมใช้งาน",
"settings.agentBehaviour.availableModes": "โหมดกำหนดเองที่ใช้ได้",
"settings.agentBehaviour.noModesFound": "ไม่พบโหมด",
+ "settings.agentBehaviour.createMode": "สร้างโหมดใหม่",
+ "settings.agentBehaviour.createMode.name": "ชื่อ",
+ "settings.agentBehaviour.createMode.name.placeholder": "เช่น reviewer",
+ "settings.agentBehaviour.createMode.name.description":
+ "ตัวระบุเฉพาะสำหรับโหมด ใช้เฉพาะตัวอักษรพิมพ์เล็ก ตัวเลข และขีดกลาง",
+ "settings.agentBehaviour.createMode.description": "คำอธิบาย",
+ "settings.agentBehaviour.createMode.description.placeholder": "เช่น ตรวจสอบโค้ดเพื่อคุณภาพและแนวปฏิบัติที่ดีที่สุด",
+ "settings.agentBehaviour.createMode.description.help": "คำอธิบายสั้น ๆ ว่าโหมดนี้ทำอะไร",
+ "settings.agentBehaviour.createMode.prompt": "System Prompt",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "เช่น คุณเป็นผู้ตรวจสอบโค้ด มุ่งเน้นที่คุณภาพโค้ด แนวปฏิบัติที่ดีที่สุด และบั๊กที่อาจเกิดขึ้น",
+ "settings.agentBehaviour.createMode.prompt.help": "คำแนะนำสำหรับ AI agent เมื่อใช้โหมดนี้",
+ "settings.agentBehaviour.createMode.button": "สร้าง",
+ "settings.agentBehaviour.createMode.cancel": "ยกเลิก",
+ "settings.agentBehaviour.createMode.nameRequired": "ต้องระบุชื่อ",
+ "settings.agentBehaviour.createMode.nameInvalid": "ชื่อต้องประกอบด้วยตัวอักษรพิมพ์เล็ก ตัวเลข และขีดกลางเท่านั้น",
+ "settings.agentBehaviour.createMode.nameTaken": "มีโหมดที่มีชื่อนี้อยู่แล้ว",
+ "settings.agentBehaviour.importMode": "นำเข้า",
+ "settings.agentBehaviour.importMode.invalidName":
+ "ชื่อโหมดในไฟล์ไม่ถูกต้อง ชื่อต้องขึ้นต้นด้วยตัวอักษรพิมพ์เล็กและประกอบด้วยตัวอักษรพิมพ์เล็ก ตัวเลข และขีดกลางเท่านั้น",
+ "settings.agentBehaviour.importMode.nameTaken": "มีโหมดที่มีชื่อนี้อยู่แล้ว",
+ "settings.agentBehaviour.importMode.invalidJson": "ไฟล์ JSON ไม่ถูกต้อง กรุณาเลือกไฟล์นิยามเอเจนต์ที่ถูกต้อง",
+ "settings.agentBehaviour.importMode.tooLarge": "ไฟล์มีขนาดใหญ่เกินไป คำจำกัดความเอเจนต์ต้องมีขนาดไม่เกิน 1 MB",
+ "settings.agentBehaviour.exportMode": "ส่งออกนิยามเอเจนต์",
+ "settings.agentBehaviour.editMode": "แก้ไขโหมด",
+ "settings.agentBehaviour.editMode.description": "คำอธิบาย",
+ "settings.agentBehaviour.editMode.prompt": "System Prompt",
+ "settings.agentBehaviour.editMode.save": "เสร็จสิ้น",
+ "settings.agentBehaviour.editMode.back": "กลับไปที่รายการ",
+ "settings.agentBehaviour.editMode.native": "โหมดในตัว (นิยามสำหรับอ่านอย่างเดียว)",
+ "settings.agentBehaviour.editMode.promptOverride": "การแทนที่ prompt แบบกำหนดเองสำหรับโหมดในตัวนี้",
"settings.agentBehaviour.removeMode.title": "ลบโหมด",
"settings.agentBehaviour.removeMode.confirm":
'ต้องการลบโหมด "{{name}}" หรือไม่? การดำเนินการนี้จะปิดใช้งานโหมดโดยอัปเดตการกำหนดค่าของคุณ',
@@ -1047,18 +1110,42 @@ export const dict = {
"settings.agentBehaviour.removeMcp.confirm":
'ต้องการลบเซิร์ฟเวอร์ MCP "{{name}}" หรือไม่? การดำเนินการนี้จะลบออกจากการกำหนดค่าของคุณ',
"settings.agentBehaviour.removeMcp.button": "ลบ",
+ "settings.agentBehaviour.editMcp": "แก้ไขเซิร์ฟเวอร์ MCP",
+ "settings.agentBehaviour.editMcp.transportLocal": "เซิร์ฟเวอร์ภายใน (การขนส่ง stdio)",
+ "settings.agentBehaviour.editMcp.transportRemote": "เซิร์ฟเวอร์ระยะไกล (การขนส่ง SSE/HTTP)",
+ "settings.agentBehaviour.editMcp.env": "ตัวแปรสภาพแวดล้อม",
+ "settings.agentBehaviour.editMcp.env.help": "ตัวแปรที่ส่งไปยังกระบวนการเซิร์ฟเวอร์ MCP",
+ "settings.agentBehaviour.addMcp.command": "คำสั่ง",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "อาร์กิวเมนต์",
+ "settings.agentBehaviour.addMcp.args.help": "หนึ่งอาร์กิวเมนต์ต่อบรรทัด เส้นทางที่มีช่องว่างจะถูกเก็บรักษาตามเดิม",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "URL เซิร์ฟเวอร์",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "เส้นทางโฟลเดอร์ทักษะ",
"settings.agentBehaviour.skillUrls": "URL ทักษะ",
"settings.agentBehaviour.removeSkill.title": "ลบทักษะ",
"settings.agentBehaviour.removeSkill.confirm":
'ต้องการลบทักษะ "{{name}}" หรือไม่? การดำเนินการนี้จะลบไฟล์ทักษะออกจากดิสก์',
"settings.agentBehaviour.removeSkill.button": "ลบ",
+ "settings.agentBehaviour.rules.description":
+ "กฎคือไฟล์คำสั่งที่แนะนำพฤติกรรมของเอเจนต์ กฎเหล่านี้จะถูกรวมอยู่ในพรอมต์ระบบสำหรับทุกการสนทนา เพิ่มเส้นทางไฟล์ด้านล่างเพื่อรวมกฎเพิ่มเติม",
"settings.agentBehaviour.instructionFiles": "ไฟล์คำสั่งเพิ่มเติม",
"settings.agentBehaviour.instructionFiles.description": "เส้นทางไฟล์คำสั่งเพิ่มเติมในพรอมต์ระบบ",
+ "settings.agentBehaviour.mcpDetail.command": "คำสั่ง",
+ "settings.agentBehaviour.mcpDetail.args": "อาร์กิวเมนต์",
+ "settings.agentBehaviour.mcpDetail.env": "สภาพแวดล้อม",
+ "settings.agentBehaviour.mcpDetail.disabled": "เซิร์ฟเวอร์นี้ถูกปิดใช้งาน",
"settings.agentBehaviour.mcpEmpty":
"ไม่ได้กำหนดค่าเซิร์ฟเวอร์ MCP แก้ไขไฟล์กำหนดค่า opencode เพื่อเพิ่มเซิร์ฟเวอร์ MCP",
"settings.agentBehaviour.workflowsPlaceholder": "เวิร์กโฟลว์จัดการผ่านไฟล์เวิร์กโฟลว์ในพื้นที่ทำงาน",
- "settings.agentBehaviour.notImplemented": "ยังไม่ได้ใช้งาน",
+ "settings.agentBehaviour.workflows.description":
+ "เวิร์กโฟลว์คือคำสั่งสแลชแบบกำหนดเองที่กำหนดไว้ในการตั้งค่าของคุณ พิมพ์ /command-name ในแชทเพื่อเรียกใช้ คำสั่งถูกกำหนดค่าใน opencode.json ภายใต้ส่วน 'command'",
+ "settings.agentBehaviour.workflows.empty":
+ "ไม่มีคำสั่งแบบกำหนดเองที่กำหนดค่าไว้ เพิ่มคำสั่งใน opencode.json เพื่อดูที่นี่",
+ "settings.agentBehaviour.workflows.detail.description": "คำอธิบาย",
+ "settings.agentBehaviour.workflows.detail.template": "เทมเพลต",
+
"settings.autoApprove.description":
"กำหนดวิธีอนุญาตการทำงานของเครื่องมือ โดยค่าเริ่มต้นเครื่องมือส่วนใหญ่คืออนุญาต ส่วน doom_loop และ external_directory ค่าเริ่มต้นคือถาม",
"settings.autoApprove.level.allow": "อนุญาต",
@@ -1106,7 +1193,8 @@ export const dict = {
"settings.providers.defaultModel.title": "โมเดลเริ่มต้น",
"settings.providers.defaultModel.description": "โมเดลหลักสำหรับบทสนทนา",
"settings.providers.smallModel.title": "โมเดลขนาดเล็ก",
- "settings.providers.smallModel.description": "โมเดลน้ำหนักเบาสำหรับสร้างชื่อและงานด่วน",
+ "settings.providers.smallModel.description":
+ "โมเดลน้ำหนักเบาสำหรับสร้างชื่อ สร้างข้อความคอมมิต ปรับปรุงพรอมต์ และงานด่วนอื่นๆ",
"settings.providers.disabled": "ผู้ให้บริการที่ปิดใช้งาน",
"settings.providers.disabled.description": "ผู้ให้บริการที่จะซ่อนจากรายการ",
"settings.providers.enabled": "ผู้ให้บริการที่เปิดใช้งาน (รายการที่อนุญาต)",
@@ -1187,4 +1275,8 @@ export const dict = {
"settings.saveBar.warning.many": "มีหลายเซสชันกำลังทำงานและจะถูกขัดจังหวะ",
"settings.saveBar.saveAnyway": "บันทึกต่อไป",
"settings.saveBar.cancel": "ยกเลิก",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts
index 0da999bcab6..07ffb9711bd 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts
@@ -753,6 +753,21 @@ export const dict = {
"provider.custom.models.name.placeholder": "Görünen Ad",
"provider.custom.models.remove": "Modeli kaldır",
"provider.custom.models.add": "Model ekle",
+ "provider.custom.models.fetch": "Modelleri getir",
+ "provider.custom.models.fetching": "Getiriliyor\u2026",
+ "provider.custom.models.fetch.error": "Modeller getirilemedi: {{error}}",
+ "provider.custom.models.fetch.authError":
+ "Kimlik doğrulama başarısız oldu. Yukarıdaki API anahtarını kontrol edin ve tekrar deneyin.",
+ "provider.custom.models.fetch.empty": "Bu sunucuda model bulunamadı.",
+ "provider.custom.models.fetch.added": "{{count}} model eklendi.",
+ "provider.custom.models.fetch.allExist": "Getirilen tüm modeller zaten eklenmiş.",
+ "provider.custom.models.fetch.selectAll": "Tümünü seç",
+ "provider.custom.models.fetch.deselectAll": "Tümünün seçimini kaldır",
+ "provider.custom.models.fetch.found": "{{count}} model bulundu",
+ "provider.custom.models.fetch.showing": "{{total}} modelden {{shown}} tanesi gösteriliyor",
+ "provider.custom.models.fetch.search": "Model ara\u2026",
+ "provider.custom.models.fetch.add": "{{count}} model ekle",
+ "provider.custom.edit.title": "Sağlayıcıyı düzenle",
"provider.custom.headers.label": "Başlıklar (isteğe bağlı)",
"provider.custom.headers.key.label": "Başlık",
"provider.custom.headers.key.placeholder": "Başlık-Adı",
@@ -825,8 +840,11 @@ export const dict = {
"session.delete.button": "Oturumu sil",
"session.untitled": "Adsız",
"session.recent": "Son",
+ "session.showHistory": "Geçmişi Göster",
"session.search.placeholder": "Oturum ara...",
"session.empty": "Henüz oturum yok. Yeni bir sohbet başlatmak için + tıklayın.",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "Yalnızca bu depo",
"session.cloud.import": "Buluttan içe aktar",
"feedback.button": "Geri Bildirim ve Destek",
@@ -908,8 +926,7 @@ export const dict = {
"settings.autocomplete.title": "Otomatik Tamamlama",
"settings.notifications.title": "Bildirimler",
"settings.context.title": "Bağlam",
- "settings.terminal.title": "Terminal",
- "settings.prompts.title": "Komutlar",
+
"settings.experimental.title": "Deneysel",
"settings.language.title": "Dil",
"settings.aboutKiloCode.title": "Kilo Code Hakkında",
@@ -961,6 +978,18 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"Tüm Kilo Code uzantı ayarlarını varsayılan değerlerine sıfırla. Bu, CLI veya arka uç yapılandırmasını etkilemez.",
"settings.aboutKiloCode.resetSettings.button": "Tüm Ayarları Sıfırla",
+ "settings.aboutKiloCode.settingsTransfer.title": "Ayar Aktarımı",
+ "settings.aboutKiloCode.settingsTransfer.description":
+ "Ayarlarınızı VS Code örnekleri arasında aktarmak için dışa veya içe aktarın.",
+ "settings.aboutKiloCode.exportSettings": "Dışa Aktar",
+ "settings.aboutKiloCode.importSettings": "İçe Aktar",
+ "settings.aboutKiloCode.importSettings.invalidJson": "Geçersiz JSON dosyası. Lütfen geçerli bir ayar dosyası seçin.",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "Dosya geçerli Kilo ayarları içermiyor.",
+ "settings.aboutKiloCode.importSettings.tooLarge": "Dosya çok büyük. Ayar dosyaları 1 MB altında olmalıdır.",
+ "settings.aboutKiloCode.importSettings.newerVersion":
+ "Bu dosya Kilo'nun daha yeni bir sürümünden dışa aktarılmış. Bazı ayarlar göz ardı edilebilir.",
+ "settings.aboutKiloCode.importSettings.success":
+ "Ayarlar içe aktarıldı. Yukarıdaki değişiklikleri gözden geçirin, ardından Kaydet'e tıklayın.",
"settings.agentBehaviour.subtab.modes": "Modlar",
"settings.agentBehaviour.subtab.agents": "Ajanlar",
@@ -986,10 +1015,6 @@ export const dict = {
"common.add": "Ekle",
"common.choose": "Seç…",
- "settings.notImplemented": "Bu bölüm henüz uygulanmadı.",
- "settings.notImplemented.description":
- "Seçilen ayar kategorisiyle ilgili yapılandırma seçenekleri ve açıklayıcı metin içerecek. Yeniden uygulama sırasında gerçek kontrolleri bağlamadan önce düzen, boşluk, kaydırma davranışı ve gezinme durumunu doğrulamak için bu alanı kullanın.",
-
"settings.autocomplete.autoTrigger.title": "Otomatik satır içi tamamlamaları etkinleştir",
"settings.autocomplete.autoTrigger.description": "Yazarken satır içi tamamlama önerilerini otomatik göster",
"settings.autocomplete.smartKeybinding.title": "Akıllı satır içi görev tuş atamasını etkinleştir",
@@ -1051,11 +1076,25 @@ export const dict = {
"settings.agentBehaviour.topP.description": "Çekirdek örnekleme parametresi (0-1)",
"settings.agentBehaviour.maxSteps.title": "Maksimum Adım",
"settings.agentBehaviour.maxSteps.description": "Maksimum ajanlık yinelemesi",
+ "settings.agentBehaviour.hidden.title": "Gizli",
+ "settings.agentBehaviour.hidden.description": "Bu ajanı sohbet girişindeki mod değiştiriciden gizle",
+ "settings.agentBehaviour.disable.title": "Devre dışı",
+ "settings.agentBehaviour.disable.description": "Bu ajanı tamamen devre dışı bırak — hiçbir yerde görünmeyecek",
+ "settings.agentBehaviour.badge.hidden": "gizli",
+ "settings.agentBehaviour.badge.disabled": "devre dışı",
"settings.agentBehaviour.discoveredSkills": "Keşfedilen Beceriler",
"settings.agentBehaviour.noSkillsFound":
"Keşfedilen beceri yok. Becerileri kullanılabilir kılmak için aşağıya beceri klasör yolları veya URL'ler ekleyin.",
"settings.agentBehaviour.availableModes": "Mevcut Özel Modlar",
"settings.agentBehaviour.noModesFound": "Mod bulunamadı.",
+ "settings.agentBehaviour.importMode": "İçe Aktar",
+ "settings.agentBehaviour.importMode.invalidName":
+ "Dosyadaki mod adı geçersiz. Ad küçük harfle başlamalı ve yalnızca küçük harfler, rakamlar ve tire içermelidir.",
+ "settings.agentBehaviour.importMode.nameTaken": "Bu ada sahip bir mod zaten mevcut.",
+ "settings.agentBehaviour.importMode.invalidJson":
+ "Geçersiz JSON dosyası. Lütfen geçerli bir ajan tanım dosyası seçin.",
+ "settings.agentBehaviour.importMode.tooLarge": "Dosya çok büyük. Ajan tanımları 1 MB'den küçük olmalıdır.",
+ "settings.agentBehaviour.exportMode": "Ajan tanımını dışa aktar",
"settings.agentBehaviour.removeMode.title": "Modu kaldır",
"settings.agentBehaviour.removeMode.confirm":
'"{{name}}" modu kaldırılsın mı? Bu, yapılandırmanızı güncelleyerek modu devre dışı bırakacak.',
@@ -1066,6 +1105,8 @@ export const dict = {
"settings.agentBehaviour.removeSkill.confirm":
'"{{name}}" becerisi kaldırılsın mı? Bu, beceri dosyalarını diskten silecek.',
"settings.agentBehaviour.removeSkill.button": "Kaldır",
+ "settings.agentBehaviour.rules.description":
+ "Kurallar, ajanın davranışını yönlendiren talimat dosyalarıdır. Her konuşma için sistem komutuna dahil edilirler. Ek kurallar eklemek için aşağıya dosya yolları ekleyin.",
"settings.agentBehaviour.instructionFiles": "Ek Talimat Dosyaları",
"settings.agentBehaviour.instructionFiles.description":
"Sistem komutuna dahil edilen ek talimat dosyalarının yolları",
@@ -1073,10 +1114,31 @@ export const dict = {
"settings.agentBehaviour.removeMcp.confirm":
'"{{name}}" MCP sunucusu kaldırılsın mı? Bu, yapılandırmanızdan kaldırılacak.',
"settings.agentBehaviour.removeMcp.button": "Kaldır",
+ "settings.agentBehaviour.mcpDetail.command": "Komut",
+ "settings.agentBehaviour.mcpDetail.args": "Argümanlar",
+ "settings.agentBehaviour.mcpDetail.env": "Ortam",
+ "settings.agentBehaviour.mcpDetail.disabled": "Bu sunucu devre dışı.",
+ "settings.agentBehaviour.editMcp": "MCP Sunucusunu Düzenle",
+ "settings.agentBehaviour.editMcp.transportLocal": "Yerel sunucu (stdio taşıma)",
+ "settings.agentBehaviour.editMcp.transportRemote": "Uzak sunucu (SSE/HTTP taşıma)",
+ "settings.agentBehaviour.editMcp.env": "Ortam Değişkenleri",
+ "settings.agentBehaviour.editMcp.env.help": "MCP sunucu sürecine aktarılan değişkenler.",
+ "settings.agentBehaviour.addMcp.command": "Komut",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "Argümanlar",
+ "settings.agentBehaviour.addMcp.args.help": "Satır başına bir argüman. Boşluklu yollar olduğu gibi korunur.",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "Sunucu URL'si",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.mcpEmpty":
"Yapılandırılmış MCP sunucusu yok. MCP sunucuları eklemek için opencode yapılandırma dosyasını düzenleyin.",
"settings.agentBehaviour.workflowsPlaceholder": "İş akışları çalışma alanınızdaki iş akışı dosyaları ile yönetilir.",
- "settings.agentBehaviour.notImplemented": "Henüz uygulanmadı.",
+ "settings.agentBehaviour.workflows.description":
+ "İş akışları, yapılandırmanızda tanımlanan özel eğik çizgi komutlarıdır. Çağırmak için sohbette /command-name yazın. Komutlar opencode.json dosyasındaki 'command' bölümünde yapılandırılır.",
+ "settings.agentBehaviour.workflows.empty":
+ "Yapılandırılmış özel komut yok. Burada görmek için opencode.json dosyasına komutlar ekleyin.",
+ "settings.agentBehaviour.workflows.detail.description": "Açıklama",
+ "settings.agentBehaviour.workflows.detail.template": "Şablon",
"settings.autoApprove.description":
"Araçların nasıl çalıştırılacağını tanımlayın. Çoğu araç varsayılan olarak İzin Ver'dir. doom_loop ve external_directory varsayılan olarak Sor'dur.",
@@ -1135,7 +1197,8 @@ export const dict = {
"settings.providers.defaultModel.title": "Varsayılan Model",
"settings.providers.defaultModel.description": "Sohbetler için birincil model",
"settings.providers.smallModel.title": "Küçük Model",
- "settings.providers.smallModel.description": "Başlık oluşturma ve diğer hızlı görevler için hafif model",
+ "settings.providers.smallModel.description":
+ "Başlık oluşturma, commit mesajı oluşturma, komut istemi iyileştirme ve diğer hızlı görevler için hafif model",
"settings.providers.modeModels": "Mod Başına Model",
"settings.providers.modeModels.description":
"Belirli modlar için varsayılan modeli geçersiz kılın. Ayarlanmadıysa genel varsayılan model kullanılır.",
@@ -1226,4 +1289,8 @@ export const dict = {
"settings.saveBar.warning.many": "Birden fazla oturum çalışıyor ve kesintiye uğrayacak",
"settings.saveBar.saveAnyway": "Yine de kaydet",
"settings.saveBar.cancel": "İptal",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts
index 57039a79909..ca2b27cb8ff 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts
@@ -740,6 +740,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "显示名称",
"provider.custom.models.remove": "移除模型",
"provider.custom.models.add": "添加模型",
+ "provider.custom.models.fetch": "获取模型",
+ "provider.custom.models.fetching": "获取中\u2026",
+ "provider.custom.models.fetch.error": "获取模型失败:{{error}}",
+ "provider.custom.models.fetch.authError": "认证失败。请检查上方的 API 密钥后重试。",
+ "provider.custom.models.fetch.empty": "此服务器上未找到模型。",
+ "provider.custom.models.fetch.added": "已添加 {{count}} 个模型。",
+ "provider.custom.models.fetch.allExist": "获取的所有模型均已添加。",
+ "provider.custom.models.fetch.selectAll": "全选",
+ "provider.custom.models.fetch.deselectAll": "取消全选",
+ "provider.custom.models.fetch.found": "找到 {{count}} 个模型",
+ "provider.custom.models.fetch.showing": "显示 {{shown}}/{{total}}",
+ "provider.custom.models.fetch.search": "搜索模型\u2026",
+ "provider.custom.models.fetch.add": "添加 {{count}} 个模型",
+ "provider.custom.edit.title": "编辑提供商",
"provider.custom.headers.label": "请求头(可选)",
"provider.custom.headers.key.label": "请求头",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -812,8 +826,11 @@ export const dict = {
"session.delete.button": "删除会话",
"session.untitled": "无标题",
"session.recent": "最近",
+ "session.showHistory": "显示历史",
"session.search.placeholder": "搜索会话...",
"session.empty": "暂无会话。点击 + 开始新的对话。",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "仅此仓库",
"session.cloud.import": "从云端导入",
"feedback.button": "反馈与支持",
@@ -894,8 +911,7 @@ export const dict = {
"settings.autocomplete.title": "自动补全",
"settings.notifications.title": "通知",
"settings.context.title": "上下文",
- "settings.terminal.title": "终端",
- "settings.prompts.title": "提示词",
+
"settings.experimental.title": "实验性功能",
"settings.language.title": "语言",
"settings.aboutKiloCode.title": "关于 Kilo Code",
@@ -945,6 +961,15 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"将所有 Kilo Code 扩展设置重置为默认值。这不会影响 CLI 或后端配置。",
"settings.aboutKiloCode.resetSettings.button": "重置所有设置",
+ "settings.aboutKiloCode.settingsTransfer.title": "设置迁移",
+ "settings.aboutKiloCode.settingsTransfer.description": "导出或导入设置,以便在 VS Code 实例之间传输。",
+ "settings.aboutKiloCode.exportSettings": "导出",
+ "settings.aboutKiloCode.importSettings": "导入",
+ "settings.aboutKiloCode.importSettings.invalidJson": "无效的 JSON 文件。请选择有效的设置文件。",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "文件不包含有效的 Kilo 设置。",
+ "settings.aboutKiloCode.importSettings.tooLarge": "文件过大。设置文件必须小于 1 MB。",
+ "settings.aboutKiloCode.importSettings.newerVersion": "此文件由较新版本的 Kilo 导出。部分设置可能会被忽略。",
+ "settings.aboutKiloCode.importSettings.success": "设置已导入。请查看上方的更改,然后点击保存。",
"settings.agentBehaviour.subtab.modes": "模式",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -968,9 +993,7 @@ export const dict = {
"common.add": "添加",
"common.choose": "选择…",
- "settings.notImplemented": "此部分尚未实现。",
- "settings.notImplemented.description":
- "此处将包含与所选设置类别相关的配置选项和说明文字。在重新实现期间,可使用此空间验证布局、间距、滚动行为和导航状态,然后再接入实际控件。",
+
"settings.autocomplete.autoTrigger.title": "启用自动内联补全",
"settings.autocomplete.autoTrigger.description": "在您输入时自动显示内联补全建议",
"settings.autocomplete.smartKeybinding.title": "启用智能内联任务快捷键",
@@ -1028,26 +1051,84 @@ export const dict = {
"settings.agentBehaviour.topP.description": "核采样参数(0-1)",
"settings.agentBehaviour.maxSteps.title": "最大步数",
"settings.agentBehaviour.maxSteps.description": "最大智能体迭代次数",
+ "settings.agentBehaviour.hidden.title": "隐藏",
+ "settings.agentBehaviour.hidden.description": "在聊天输入的模式切换器中隐藏此智能体",
+ "settings.agentBehaviour.disable.title": "已禁用",
+ "settings.agentBehaviour.disable.description": "完全禁用此智能体 — 它将不会出现在任何地方",
+ "settings.agentBehaviour.badge.hidden": "隐藏",
+ "settings.agentBehaviour.badge.disabled": "已禁用",
"settings.agentBehaviour.discoveredSkills": "已发现的技能",
"settings.agentBehaviour.noSkillsFound": "未发现任何技能。请在下方添加技能文件夹路径或 URL 以使技能可用。",
"settings.agentBehaviour.availableModes": "可用自定义模式",
"settings.agentBehaviour.noModesFound": "未找到模式。",
+ "settings.agentBehaviour.createMode": "创建新模式",
+ "settings.agentBehaviour.createMode.name": "名称",
+ "settings.agentBehaviour.createMode.name.placeholder": "例如:reviewer",
+ "settings.agentBehaviour.createMode.name.description": "模式的唯一标识符。仅使用小写字母、数字和连字符。",
+ "settings.agentBehaviour.createMode.description": "描述",
+ "settings.agentBehaviour.createMode.description.placeholder": "例如:审查代码质量和最佳实践",
+ "settings.agentBehaviour.createMode.description.help": "对该模式功能的简短描述。",
+ "settings.agentBehaviour.createMode.prompt": "系统提示",
+ "settings.agentBehaviour.createMode.prompt.placeholder": "例如:您是代码审查员。关注代码质量、最佳实践和潜在错误。",
+ "settings.agentBehaviour.createMode.prompt.help": "使用此模式时给 AI 代理的指令。",
+ "settings.agentBehaviour.createMode.button": "创建",
+ "settings.agentBehaviour.createMode.cancel": "取消",
+ "settings.agentBehaviour.createMode.nameRequired": "名称为必填项",
+ "settings.agentBehaviour.createMode.nameInvalid": "名称只能包含小写字母、数字和连字符",
+ "settings.agentBehaviour.createMode.nameTaken": "此名称的模式已存在",
+ "settings.agentBehaviour.importMode": "导入",
+ "settings.agentBehaviour.importMode.invalidName":
+ "文件中的模式名称无效。名称必须以小写字母开头,且仅包含小写字母、数字和连字符。",
+ "settings.agentBehaviour.importMode.nameTaken": "此名称的模式已存在。",
+ "settings.agentBehaviour.importMode.invalidJson": "无效的 JSON 文件。请选择有效的代理定义文件。",
+ "settings.agentBehaviour.importMode.tooLarge": "文件太大。代理定义必须小于 1 MB。",
+ "settings.agentBehaviour.exportMode": "导出代理定义",
+ "settings.agentBehaviour.editMode": "编辑模式",
+ "settings.agentBehaviour.editMode.description": "描述",
+ "settings.agentBehaviour.editMode.prompt": "系统提示",
+ "settings.agentBehaviour.editMode.save": "完成",
+ "settings.agentBehaviour.editMode.back": "返回列表",
+ "settings.agentBehaviour.editMode.native": "内置模式(只读定义)",
+ "settings.agentBehaviour.editMode.promptOverride": "此内置模式的自定义提示覆盖",
"settings.agentBehaviour.removeMode.title": "移除模式",
"settings.agentBehaviour.removeMode.confirm": '移除模式 "{{name}}" 吗?这将通过更新配置来禁用该模式。',
"settings.agentBehaviour.removeMode.button": "移除",
"settings.agentBehaviour.removeMcp.title": "移除 MCP 服务器",
"settings.agentBehaviour.removeMcp.confirm": '移除 MCP 服务器 "{{name}}" 吗?这将从您的配置中移除它。',
"settings.agentBehaviour.removeMcp.button": "移除",
+ "settings.agentBehaviour.editMcp": "编辑 MCP 服务器",
+ "settings.agentBehaviour.editMcp.transportLocal": "本地服务器(stdio 传输)",
+ "settings.agentBehaviour.editMcp.transportRemote": "远程服务器(SSE/HTTP 传输)",
+ "settings.agentBehaviour.editMcp.env": "环境变量",
+ "settings.agentBehaviour.editMcp.env.help": "传递给 MCP 服务器进程的变量。",
+ "settings.agentBehaviour.addMcp.command": "命令",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "参数",
+ "settings.agentBehaviour.addMcp.args.help": "每行一个参数。包含空格的路径将按原样保留。",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "服务器 URL",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "技能文件夹路径",
"settings.agentBehaviour.skillUrls": "技能 URL",
"settings.agentBehaviour.removeSkill.title": "移除技能",
"settings.agentBehaviour.removeSkill.confirm": '移除技能 "{{name}}" 吗?这会从磁盘中删除该技能文件。',
"settings.agentBehaviour.removeSkill.button": "移除",
+ "settings.agentBehaviour.rules.description":
+ "规则是指导代理行为的指令文件。它们会被包含在每次对话的系统提示词中。在下方添加文件路径以包含额外的规则。",
"settings.agentBehaviour.instructionFiles": "附加指令文件",
"settings.agentBehaviour.instructionFiles.description": "包含在系统提示词中的附加指令文件路径",
+ "settings.agentBehaviour.mcpDetail.command": "命令",
+ "settings.agentBehaviour.mcpDetail.args": "参数",
+ "settings.agentBehaviour.mcpDetail.env": "环境",
+ "settings.agentBehaviour.mcpDetail.disabled": "此服务器已禁用。",
"settings.agentBehaviour.mcpEmpty": "未配置 MCP 服务器。编辑 opencode 配置文件以添加 MCP 服务器。",
"settings.agentBehaviour.workflowsPlaceholder": "工作流通过工作区中的工作流文件管理。",
- "settings.agentBehaviour.notImplemented": "尚未实现。",
+ "settings.agentBehaviour.workflows.description":
+ "工作流是在配置中定义的自定义斜杠命令。在聊天中输入 /command-name 来调用它们。命令在 opencode.json 的 'command' 部分中配置。",
+ "settings.agentBehaviour.workflows.empty": "未配置自定义命令。将命令添加到 opencode.json 即可在此处看到。",
+ "settings.agentBehaviour.workflows.detail.description": "描述",
+ "settings.agentBehaviour.workflows.detail.template": "模板",
+
"settings.autoApprove.description":
"定义工具的运行权限。大多数工具默认为「允许」。doom_loop 和 external_directory 默认为「询问」。",
"settings.autoApprove.level.allow": "允许",
@@ -1091,7 +1172,7 @@ export const dict = {
"settings.providers.defaultModel.title": "默认模型",
"settings.providers.defaultModel.description": "对话的主要模型",
"settings.providers.smallModel.title": "小模型",
- "settings.providers.smallModel.description": "用于标题生成和其他快速任务的轻量模型",
+ "settings.providers.smallModel.description": "用于标题生成、提交信息生成、提示词增强和其他快速任务的轻量模型",
"settings.providers.disabled": "已禁用的提供者",
"settings.providers.disabled.description": "从提供者列表中隐藏的提供者",
"settings.providers.enabled": "已启用的提供者(白名单)",
@@ -1171,4 +1252,8 @@ export const dict = {
"settings.saveBar.warning.many": "多个会话正在运行,将被中断",
"settings.saveBar.saveAnyway": "仍然保存",
"settings.saveBar.cancel": "取消",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
} satisfies Partial>
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts
index 461c0f25cf7..635328ba589 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts
@@ -742,6 +742,20 @@ export const dict = {
"provider.custom.models.name.placeholder": "顯示名稱",
"provider.custom.models.remove": "移除模型",
"provider.custom.models.add": "新增模型",
+ "provider.custom.models.fetch": "取得模型",
+ "provider.custom.models.fetching": "取得中\u2026",
+ "provider.custom.models.fetch.error": "取得模型失敗:{{error}}",
+ "provider.custom.models.fetch.authError": "驗證失敗。請檢查上方的 API 金鑰後重試。",
+ "provider.custom.models.fetch.empty": "此伺服器上未找到模型。",
+ "provider.custom.models.fetch.added": "已新增 {{count}} 個模型。",
+ "provider.custom.models.fetch.allExist": "取得的所有模型均已新增。",
+ "provider.custom.models.fetch.selectAll": "全選",
+ "provider.custom.models.fetch.deselectAll": "取消全選",
+ "provider.custom.models.fetch.found": "找到 {{count}} 個模型",
+ "provider.custom.models.fetch.showing": "顯示 {{shown}}/{{total}}",
+ "provider.custom.models.fetch.search": "搜尋模型\u2026",
+ "provider.custom.models.fetch.add": "新增 {{count}} 個模型",
+ "provider.custom.edit.title": "編輯供應商",
"provider.custom.headers.label": "標頭(選填)",
"provider.custom.headers.key.label": "標頭",
"provider.custom.headers.key.placeholder": "Header-Name",
@@ -814,8 +828,11 @@ export const dict = {
"session.delete.button": "刪除工作階段",
"session.untitled": "未命名",
"session.recent": "最近",
+ "session.showHistory": "顯示歷史",
"session.search.placeholder": "搜尋工作階段...",
"session.empty": "尚無工作階段。點選 + 以開始新的對話。",
+ "session.tab.local": "Local",
+ "session.tab.cloud": "Cloud",
"session.cloud.repoOnly": "僅此儲存庫",
"session.cloud.import": "從雲端匯入",
"feedback.button": "意見回饋與支援",
@@ -896,8 +913,7 @@ export const dict = {
"settings.autocomplete.title": "自動完成",
"settings.notifications.title": "通知",
"settings.context.title": "上下文",
- "settings.terminal.title": "終端機",
- "settings.prompts.title": "提示",
+
"settings.experimental.title": "實驗性功能",
"settings.language.title": "語言",
"settings.aboutKiloCode.title": "關於 Kilo Code",
@@ -947,6 +963,15 @@ export const dict = {
"settings.aboutKiloCode.resetSettings.description":
"將所有 Kilo Code 擴充功能設定重置為預設值。這不會影響 CLI 或後端設定。",
"settings.aboutKiloCode.resetSettings.button": "重置所有設定",
+ "settings.aboutKiloCode.settingsTransfer.title": "設定轉移",
+ "settings.aboutKiloCode.settingsTransfer.description": "匯出或匯入設定,以便在 VS Code 實例之間轉移。",
+ "settings.aboutKiloCode.exportSettings": "匯出",
+ "settings.aboutKiloCode.importSettings": "匯入",
+ "settings.aboutKiloCode.importSettings.invalidJson": "無效的 JSON 檔案。請選擇有效的設定檔。",
+ "settings.aboutKiloCode.importSettings.invalidConfig": "檔案不包含有效的 Kilo 設定。",
+ "settings.aboutKiloCode.importSettings.tooLarge": "檔案過大。設定檔必須小於 1 MB。",
+ "settings.aboutKiloCode.importSettings.newerVersion": "此檔案由較新版本的 Kilo 匯出。部分設定可能會被忽略。",
+ "settings.aboutKiloCode.importSettings.success": "設定已匯入。請檢視上方的變更,然後點擊儲存。",
"settings.agentBehaviour.subtab.modes": "模式",
"settings.agentBehaviour.subtab.agents": "Agents",
@@ -970,8 +995,7 @@ export const dict = {
"common.add": "新增",
"common.choose": "選擇…",
- "settings.notImplemented": "此部分尚未實作。",
- "settings.notImplemented.description": "此處將包含與所選設定類別相關的設定選項和說明文字。",
+
"settings.autocomplete.autoTrigger.title": "啟用自動內嵌補全",
"settings.autocomplete.autoTrigger.description": "在您輸入時自動顯示內嵌補全建議",
"settings.autocomplete.smartKeybinding.title": "啟用智慧內嵌任務快捷鍵",
@@ -1029,26 +1053,85 @@ export const dict = {
"settings.agentBehaviour.topP.description": "核取樣參數(0-1)",
"settings.agentBehaviour.maxSteps.title": "最大步數",
"settings.agentBehaviour.maxSteps.description": "最大 Agent 迭代次數",
+ "settings.agentBehaviour.hidden.title": "隱藏",
+ "settings.agentBehaviour.hidden.description": "在聊天輸入的模式切換器中隱藏此 Agent",
+ "settings.agentBehaviour.disable.title": "已停用",
+ "settings.agentBehaviour.disable.description": "完全停用此 Agent — 它將不會出現在任何地方",
+ "settings.agentBehaviour.badge.hidden": "隱藏",
+ "settings.agentBehaviour.badge.disabled": "已停用",
"settings.agentBehaviour.discoveredSkills": "已發現的 Skill",
"settings.agentBehaviour.noSkillsFound": "未發現任何 Skill。請在下方新增 Skill 資料夾路徑或 URL 以使 Skill 可用。",
"settings.agentBehaviour.availableModes": "可用自訂模式",
"settings.agentBehaviour.noModesFound": "未找到模式。",
+ "settings.agentBehaviour.createMode": "建立新模式",
+ "settings.agentBehaviour.createMode.name": "名稱",
+ "settings.agentBehaviour.createMode.name.placeholder": "例如:reviewer",
+ "settings.agentBehaviour.createMode.name.description": "模式的唯一識別碼。僅使用小寫字母、數字和連字號。",
+ "settings.agentBehaviour.createMode.description": "描述",
+ "settings.agentBehaviour.createMode.description.placeholder": "例如:審查程式碼品質和最佳實踐",
+ "settings.agentBehaviour.createMode.description.help": "對該模式功能的簡短描述。",
+ "settings.agentBehaviour.createMode.prompt": "系統提示",
+ "settings.agentBehaviour.createMode.prompt.placeholder":
+ "例如:您是程式碼審查員。關注程式碼品質、最佳實踐和潛在錯誤。",
+ "settings.agentBehaviour.createMode.prompt.help": "使用此模式時給 AI 代理的指令。",
+ "settings.agentBehaviour.createMode.button": "建立",
+ "settings.agentBehaviour.createMode.cancel": "取消",
+ "settings.agentBehaviour.createMode.nameRequired": "名稱為必填項",
+ "settings.agentBehaviour.createMode.nameInvalid": "名稱只能包含小寫字母、數字和連字號",
+ "settings.agentBehaviour.createMode.nameTaken": "此名稱的模式已存在",
+ "settings.agentBehaviour.importMode": "匯入",
+ "settings.agentBehaviour.importMode.invalidName":
+ "檔案中的模式名稱無效。名稱必須以小寫字母開頭,且僅包含小寫字母、數字和連字號。",
+ "settings.agentBehaviour.importMode.nameTaken": "此名稱的模式已存在。",
+ "settings.agentBehaviour.importMode.invalidJson": "無效的 JSON 檔案。請選擇有效的代理定義檔案。",
+ "settings.agentBehaviour.importMode.tooLarge": "檔案太大。代理定義必須小於 1 MB。",
+ "settings.agentBehaviour.exportMode": "匯出代理定義",
+ "settings.agentBehaviour.editMode": "編輯模式",
+ "settings.agentBehaviour.editMode.description": "描述",
+ "settings.agentBehaviour.editMode.prompt": "系統提示",
+ "settings.agentBehaviour.editMode.save": "完成",
+ "settings.agentBehaviour.editMode.back": "返回列表",
+ "settings.agentBehaviour.editMode.native": "內建模式(唯讀定義)",
+ "settings.agentBehaviour.editMode.promptOverride": "此內建模式的自訂提示覆寫",
"settings.agentBehaviour.removeMode.title": "移除模式",
"settings.agentBehaviour.removeMode.confirm": '要移除模式 "{{name}}" 嗎?這將透過更新設定來停用該模式。',
"settings.agentBehaviour.removeMode.button": "移除",
"settings.agentBehaviour.removeMcp.title": "移除 MCP 伺服器",
"settings.agentBehaviour.removeMcp.confirm": '要移除 MCP 伺服器 "{{name}}" 嗎?這將從您的設定中移除它。',
"settings.agentBehaviour.removeMcp.button": "移除",
+ "settings.agentBehaviour.editMcp": "編輯 MCP 伺服器",
+ "settings.agentBehaviour.editMcp.transportLocal": "本機伺服器(stdio 傳輸)",
+ "settings.agentBehaviour.editMcp.transportRemote": "遠端伺服器(SSE/HTTP 傳輸)",
+ "settings.agentBehaviour.editMcp.env": "環境變數",
+ "settings.agentBehaviour.editMcp.env.help": "傳遞給 MCP 伺服器處理程序的變數。",
+ "settings.agentBehaviour.addMcp.command": "命令",
+ "settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
+ "settings.agentBehaviour.addMcp.args": "引數",
+ "settings.agentBehaviour.addMcp.args.help": "每行一個引數。包含空格的路徑將按原樣保留。",
+ "settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
+ "settings.agentBehaviour.addMcp.url": "伺服器 URL",
+ "settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
"settings.agentBehaviour.skillPaths": "Skill 資料夾路徑",
"settings.agentBehaviour.skillUrls": "Skill URL",
"settings.agentBehaviour.removeSkill.title": "移除技能",
"settings.agentBehaviour.removeSkill.confirm": '要移除技能 "{{name}}" 嗎?這將從磁碟中刪除該技能檔案。',
"settings.agentBehaviour.removeSkill.button": "移除",
+ "settings.agentBehaviour.rules.description":
+ "規則是引導代理行為的指令檔案。它們會被包含在每次對話的系統提示詞中。在下方新增檔案路徑以包含額外的規則。",
"settings.agentBehaviour.instructionFiles": "附加指令檔案",
"settings.agentBehaviour.instructionFiles.description": "包含在系統提示詞中的附加指令檔案路徑",
+ "settings.agentBehaviour.mcpDetail.command": "指令",
+ "settings.agentBehaviour.mcpDetail.args": "引數",
+ "settings.agentBehaviour.mcpDetail.env": "環境",
+ "settings.agentBehaviour.mcpDetail.disabled": "此伺服器已停用。",
"settings.agentBehaviour.mcpEmpty": "未設定 MCP 伺服器。編輯 opencode 設定檔以新增 MCP 伺服器。",
"settings.agentBehaviour.workflowsPlaceholder": "工作流程透過工作區中的工作流程檔案管理。",
- "settings.agentBehaviour.notImplemented": "尚未實作。",
+ "settings.agentBehaviour.workflows.description":
+ "工作流程是在設定中定義的自訂斜線命令。在聊天中輸入 /command-name 來呼叫它們。命令在 opencode.json 的 'command' 區段中設定。",
+ "settings.agentBehaviour.workflows.empty": "未設定自訂命令。將命令新增至 opencode.json 即可在此處看到。",
+ "settings.agentBehaviour.workflows.detail.description": "描述",
+ "settings.agentBehaviour.workflows.detail.template": "範本",
+
"settings.autoApprove.description":
"定義工具的執行權限。大多數工具預設為允許。doom_loop 與 external_directory 預設為詢問。",
"settings.autoApprove.level.allow": "允許",
@@ -1092,7 +1175,7 @@ export const dict = {
"settings.providers.defaultModel.title": "預設模型",
"settings.providers.defaultModel.description": "對話的主要模型",
"settings.providers.smallModel.title": "小模型",
- "settings.providers.smallModel.description": "用於標題產生和其他快速任務的輕量模型",
+ "settings.providers.smallModel.description": "用於標題產生、提交訊息產生、提示增強和其他快速任務的輕量模型",
"settings.providers.disabled": "已停用的供應商",
"settings.providers.disabled.description": "從供應商清單中隱藏的供應商",
"settings.providers.enabled": "已啟用的供應商(白名單)",
@@ -1172,4 +1255,8 @@ export const dict = {
"settings.saveBar.warning.many": "多個工作階段正在執行,將被中斷",
"settings.saveBar.saveAnyway": "仍然儲存",
"settings.saveBar.cancel": "取消",
+ "notifications.action.previous": "Previous",
+ "notifications.action.next": "Next",
+ "notifications.action.close": "Close",
+ "notifications.action.tryModel": "Try model",
} satisfies Partial>
diff --git a/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx b/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx
index f557c5554be..4c0f733837c 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx
@@ -26,13 +26,14 @@ import { Diff } from "@kilocode/kilo-ui/diff"
import { Code } from "@kilocode/kilo-ui/code"
import { File } from "@kilocode/kilo-ui/file"
import { SessionContext } from "../context/session"
+import { NotificationsContext } from "../context/notifications"
import { LanguageContext } from "../context/language"
import { dict as uiEn } from "@kilocode/kilo-ui/i18n/en"
import { dict as appEn } from "../i18n/en"
import { dict as amEn } from "../../agent-manager/i18n/en"
import { dict as kiloEn } from "@kilocode/kilo-i18n/en"
import { resolveTemplate } from "../context/language-utils"
-import type { Config, PermissionRequest, QuestionRequest } from "../types/messages"
+import type { Config, KilocodeNotification, PermissionRequest, QuestionRequest } from "../types/messages"
// Merged English dictionary (same merge order as the real LanguageProvider)
const dict: Record = { ...appEn, ...amEn, ...uiEn, ...kiloEn }
@@ -98,11 +99,23 @@ export const defaultMockData = {
}
// ---------------------------------------------------------------------------
-// Mock SessionContext value — only the subset used by components
+// Mock NotificationsContext value
// ---------------------------------------------------------------------------
function noop() {}
+function mockNotificationsValue(items: KilocodeNotification[] = []) {
+ return {
+ notifications: () => items,
+ filteredNotifications: () => items,
+ dismiss: noop,
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Mock SessionContext value — only the subset used by components
+// ---------------------------------------------------------------------------
+
export function mockSessionValue(overrides?: {
id?: string
permissions?: PermissionRequest[]
@@ -150,6 +163,10 @@ export function mockSessionValue(overrides?: {
totalCost: () => 0,
contextUsage: () => undefined,
agents: () => [{ name: "code", description: "Code mode", mode: "primary" as const }],
+ skills: () => [],
+ refreshSkills: noop,
+ removeSkill: noop,
+ removeMode: noop,
selectedAgent: () => "code",
selectAgent: noop,
getSessionAgent: () => "code",
@@ -159,6 +176,7 @@ export function mockSessionValue(overrides?: {
revert: () => undefined,
revertedCount: () => 0,
summary: () => undefined,
+ worktreeStats: () => undefined,
revertSession: noop,
unrevertSession: noop,
variantList: () => [],
@@ -190,6 +208,7 @@ interface StoryProvidersProps {
data?: any
permissions?: PermissionRequest[]
questions?: QuestionRequest[]
+ notifications?: KilocodeNotification[]
status?: string
sessionID?: string
/** When provided, injects a mock ConfigContext with this config instead of the real ConfigProvider. */
@@ -222,6 +241,7 @@ export const StoryProviders: ParentComponent = (props) => {
questions: props.questions,
status: props.status,
})
+ const notifications = mockNotificationsValue(props.notifications)
const [locale] = createSignal<"en">("en")
return (
@@ -239,23 +259,25 @@ export const StoryProviders: ParentComponent = (props) => {
}}
>
"en", t }}>
-
-
-
-
-
-
- {props.noPadding ? (
- props.children
- ) : (
- {props.children}
- )}
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {props.noPadding ? (
+ props.children
+ ) : (
+ {props.children}
+ )}
+
+
+
+
+
+
+
diff --git a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
index 4e6cd27824d..61fa53e7a21 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
@@ -170,6 +170,7 @@ const defaultProps = {
renaming: false,
renameValue: "",
closeKeybind: "⌘⇧W",
+ openKeybind: "⌘⇧O",
onClick: noop,
onDelete: noop,
onStartRename: noop,
@@ -177,6 +178,8 @@ const defaultProps = {
onCommitRename: noop,
onCancelRename: noop,
onRemoveStale: noop,
+ onCopyPath: noop,
+ onOpen: noop,
}
// ---------------------------------------------------------------------------
diff --git a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
index 18a9cd55e79..2372b719e4a 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
@@ -13,6 +13,7 @@ import { ChatView } from "../components/chat/ChatView"
import { TaskHeader } from "../components/chat/TaskHeader"
import { QuestionDock } from "../components/chat/QuestionDock"
import { SessionContext } from "../context/session"
+import { ServerContext } from "../context/server"
import type { QuestionRequest, TodoItem } from "../types/messages"
const SESSION_ID = "story-session-chat-001"
@@ -237,3 +238,51 @@ export const TaskHeaderWithTodosAllDone: Story = {
)
},
}
+
+// ---------------------------------------------------------------------------
+// Welcome screen with AccountSwitcher + KiloNotifications
+// ---------------------------------------------------------------------------
+
+const MOCK_NOTIFICATION = {
+ id: "notif-1",
+ title: "Try BYOK for Kilo Gateway",
+ message: "Bring your own API key for even more flexibility with Kilo Gateway models.",
+ action: { actionText: "Learn more", actionURL: "https://kilo.ai/docs" },
+}
+
+/** Mock server context with profile data so AccountSwitcher is visible */
+const mockServer = {
+ connectionState: () => "connected" as const,
+ serverInfo: () => undefined,
+ extensionVersion: () => "1.0.0",
+ errorMessage: () => undefined,
+ errorDetails: () => undefined,
+ isConnected: () => true,
+ profileData: () => ({
+ profile: {
+ email: "dev@kilo.dev",
+ name: "Dev User",
+ organizations: [{ id: "org-1", name: "Kilo Org", role: "member" }],
+ },
+ balance: { balance: 5.0 },
+ currentOrgId: "org-1",
+ }),
+ deviceAuth: () => ({ status: "idle" as const }),
+ startLogin: () => {},
+ vscodeLanguage: () => "en",
+ languageOverride: () => undefined,
+ workspaceDirectory: () => "/project",
+}
+
+export const WelcomeWithSwitcherAndNotification: Story = {
+ name: "Welcome — account switcher + notification",
+ render: () => (
+
+
+
+
+
+
+
+ ),
+}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
index 9355487c0d2..eac45bde9b6 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
@@ -3,12 +3,15 @@
* Stories for Settings and ProvidersTab components.
*/
+import { onMount } from "solid-js"
import type { Meta, StoryObj } from "storybook-solidjs-vite"
import { StoryProviders, mockSessionValue } from "./StoryProviders"
import { SessionContext } from "../context/session"
import Settings from "../components/settings/Settings"
import ProvidersTab from "../components/settings/ProvidersTab"
import AgentBehaviourTab from "../components/settings/AgentBehaviourTab"
+import ModeEditView from "../components/settings/ModeEditView"
+import type { AgentConfig, CommandConfig } from "../types/messages"
const meta: Meta = {
title: "Settings",
@@ -17,11 +20,25 @@ const meta: Meta = {
export default meta
type Story = StoryObj
+function noop() {}
+
+const MOCK_AGENTS = [
+ { name: "code", description: "General-purpose coding agent", mode: "primary" as const, native: true },
+ { name: "debug", description: "Diagnose and fix bugs", mode: "primary" as const, native: true },
+ { name: "architect", description: "Design systems and plan features", mode: "all" as const, native: true },
+ {
+ name: "reviewer",
+ description: "Review code for quality and best practices",
+ mode: "primary" as const,
+ native: false,
+ },
+]
+
export const SettingsPanel: Story = {
name: "Settings — full panel",
render: () => (
-
+
@@ -32,7 +49,7 @@ export const ProvidersConfigure: Story = {
name: "ProvidersTab — no providers configured",
render: () => (
-
+
@@ -44,17 +61,17 @@ export const AgentBehaviourAgents: Story = {
render: () => {
const session = {
...mockSessionValue({ id: "agents-story", status: "idle" }),
- agents: () => [
- { name: "code", description: "General-purpose coding agent", mode: "primary" },
- { name: "debug", description: "Diagnose and fix bugs", mode: "primary" },
- { name: "architect", description: "Design systems and plan features", mode: "all" },
- { name: "review", description: "Review code for issues and improvements", mode: "subagent" },
- ],
+ agents: () => MOCK_AGENTS,
+ removeMode: noop,
+ removeMcp: noop,
+ skills: () => [],
+ refreshSkills: noop,
+ removeSkill: noop,
}
return (
-
+
@@ -62,3 +79,171 @@ export const AgentBehaviourAgents: Story = {
)
},
}
+
+export const AgentBehaviourEditCustomMode: Story = {
+ name: "AgentBehaviourTab — edit custom mode",
+ render: () => {
+ const session = {
+ ...mockSessionValue({ id: "edit-mode-story", status: "idle" }),
+ agents: () => MOCK_AGENTS,
+ removeMode: noop,
+ removeMcp: noop,
+ skills: () => [],
+ refreshSkills: noop,
+ removeSkill: noop,
+ }
+ const cfg: Record
= {
+ reviewer: {
+ description: "Review code for quality and best practices",
+ prompt: "You are a code reviewer. Focus on code quality, best practices, and potential bugs.",
+ model: "anthropic/claude-sonnet-4-20250514",
+ temperature: 0.3,
+ },
+ }
+ return (
+
+
+
+
+
+ )
+ },
+}
+
+/**
+ * Renders AgentBehaviourTab and clicks into the "reviewer" custom mode's
+ * edit view on mount. Uses requestAnimationFrame to ensure the DOM is
+ * fully rendered before querying for the list item.
+ */
+function EditModeWrapper() {
+ let ref: HTMLDivElement | undefined
+ onMount(() => {
+ requestAnimationFrame(() => {
+ if (!ref) return
+ const items = Array.from(ref.querySelectorAll("[style*='cursor: pointer']"))
+ for (const item of items) {
+ if (item.textContent?.includes("reviewer")) {
+ item.click()
+ return
+ }
+ }
+ })
+ })
+ return (
+
+ )
+}
+
+/** Clicks the given subtab button on mount. */
+function SubtabWrapper(props: { tab: string }) {
+ let ref: HTMLDivElement | undefined
+ onMount(() => {
+ requestAnimationFrame(() => {
+ if (!ref) return
+ const buttons = Array.from(ref.querySelectorAll("button"))
+ for (const btn of buttons) {
+ if (btn.textContent?.toLowerCase().includes(props.tab.toLowerCase())) {
+ btn.click()
+ return
+ }
+ }
+ })
+ })
+ return (
+
+ )
+}
+
+const MOCK_COMMANDS: Record = {
+ review: {
+ template: "Review the changes in the current branch and provide feedback on code quality.",
+ description: "Run a code review on the current branch",
+ },
+ deploy: {
+ template: "Build and deploy the application to the staging environment.",
+ description: "Deploy to staging",
+ },
+ test: {
+ template: "Run the full test suite and report any failures.",
+ },
+}
+
+export const AgentBehaviourWorkflows: Story = {
+ name: "AgentBehaviourTab — workflows with commands",
+ render: () => {
+ const session = {
+ ...mockSessionValue({ id: "workflows-story", status: "idle" }),
+ agents: () => MOCK_AGENTS,
+ removeMode: noop,
+ removeMcp: noop,
+ skills: () => [],
+ refreshSkills: noop,
+ removeSkill: noop,
+ }
+ return (
+
+
+
+
+
+ )
+ },
+}
+
+export const AgentBehaviourWorkflowsEmpty: Story = {
+ name: "AgentBehaviourTab — workflows empty state",
+ render: () => {
+ const session = {
+ ...mockSessionValue({ id: "workflows-empty-story", status: "idle" }),
+ agents: () => MOCK_AGENTS,
+ removeMode: noop,
+ removeMcp: noop,
+ skills: () => [],
+ refreshSkills: noop,
+ removeSkill: noop,
+ }
+ return (
+
+
+
+
+
+ )
+ },
+}
+
+export const ModeEditExport: Story = {
+ name: "ModeEditView — export button",
+ render: () => {
+ const cfg: Record = {
+ reviewer: {
+ description: "Review code for quality and best practices",
+ prompt: "You are a code reviewer. Focus on code quality, best practices, and potential bugs.",
+ model: "anthropic/claude-sonnet-4-20250514",
+ temperature: 0.3,
+ },
+ }
+ const session = {
+ ...mockSessionValue({ id: "export-story", status: "idle" }),
+ agents: () => MOCK_AGENTS,
+ removeMode: noop,
+ removeMcp: noop,
+ skills: () => [],
+ refreshSkills: noop,
+ removeSkill: noop,
+ }
+ return (
+
+
+
+
+
+
+
+ )
+ },
+}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/shell.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/shell.stories.tsx
index e47635d1179..08310d22c58 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/shell.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/shell.stories.tsx
@@ -36,7 +36,7 @@ const shellPart: ToolPart = {
description: "Check for migration message types",
command: `grep -n "migration\\|openMigration" packages/kilo-vscode/webview-ui/src/types/messages.ts`,
},
- output: `534: view: "newTask" | "marketplace" | "history" | "cloudHistory" | "profile" | "settings" | "migration" // legacy-migration`,
+ output: `534: view: "newTask" | "marketplace" | "history" | "profile" | "settings" | "migration" // legacy-migration`,
title: "Check for migration message types",
metadata: {},
time: { start: now - 5000, end: now - 4500 },
diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css
index cc6fc06855f..00f13417060 100644
--- a/packages/kilo-vscode/webview-ui/src/styles/chat.css
+++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css
@@ -174,6 +174,101 @@
/* New Task Button */
.new-task-button-wrapper {
padding: 8px 12px 0;
+ container-type: inline-size;
+}
+
+.session-actions-row {
+ display: flex;
+ gap: 4px;
+ align-items: center;
+}
+
+/* All direct children share extra space equally, but start from their content size */
+.session-actions-row > * {
+ flex: 1 1 auto;
+ min-width: 0;
+ overflow: hidden;
+}
+
+/* Tooltip wrappers fill their flex slot, buttons fill the tooltip wrapper */
+.session-actions-row > [data-component="tooltip-trigger"] {
+ display: flex;
+}
+
+.session-actions-row > [data-component="tooltip-trigger"] > [data-component="button"] {
+ flex: 1;
+}
+
+/* Diff wrapper also stretches evenly (same flex as buttons) */
+.session-diff-wrapper {
+ display: flex !important;
+}
+
+/* Diff stats badge — fills its flex slot, content centered */
+.session-diff-badge {
+ display: flex;
+ flex: 1;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ padding: 4px 8px;
+ border: none;
+ border-radius: 4px;
+ background: transparent;
+ color: var(--vscode-descriptionForeground);
+ font-family: var(--vscode-editor-font-family), monospace;
+ font-size: 11px;
+ font-variant-numeric: tabular-nums;
+ cursor: pointer;
+ white-space: nowrap;
+ position: relative;
+}
+
+.session-diff-badge--empty {
+ opacity: 0.5;
+}
+
+.session-diff-badge:hover {
+ background: var(--vscode-toolbar-hoverBackground);
+}
+
+.session-diff-badge [data-component="icon"] {
+ opacity: 0.7;
+ position: relative;
+ flex-shrink: 0;
+}
+
+.session-diff-add {
+ color: #34d399;
+}
+
+.session-diff-del {
+ color: #f87171;
+}
+
+/* Collapse diff stats to icon-only when truly narrow */
+@container (max-width: 300px) {
+ .session-diff-add,
+ .session-diff-del {
+ display: none;
+ }
+
+ .session-diff-badge {
+ padding: 4px 6px;
+ overflow: hidden;
+ }
+
+ /* Dot indicator on the icon when collapsed and there are changes */
+ .session-diff-badge--has-changes [data-component="icon"]::after {
+ content: "";
+ position: absolute;
+ top: -2px;
+ right: -2px;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: #34d399;
+ }
}
/* ============================================
@@ -277,10 +372,18 @@
border-color: var(--vscode-textLink-foreground, #3794ff);
}
+.feedback-button [data-component="icon"] {
+ --icon-base: currentColor;
+}
+
.feedback-button:hover [data-slot="icon-svg"] {
color: var(--vscode-editor-background, #1e1e1e);
}
+.feedback-button:hover [data-component="icon"] {
+ --icon-base: var(--vscode-editor-background, #1e1e1e);
+}
+
/* Feedback Dialog */
@keyframes feedback-dialog-enter {
@@ -416,6 +519,28 @@
min-width: 0;
}
+.show-history-btn {
+ appearance: none;
+ border: 1px solid var(--vscode-button-secondaryBorder, var(--vscode-contrastBorder, transparent));
+ border-radius: 4px;
+ background: var(--vscode-button-secondaryBackground, transparent);
+ color: var(--vscode-button-secondaryForeground, var(--vscode-foreground));
+ cursor: pointer;
+ font-family: inherit;
+ font-size: 12px;
+ padding: 5px 10px;
+ margin-top: 4px;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+}
+
+.show-history-btn:hover {
+ background: var(--vscode-button-secondaryHoverBackground, var(--vscode-list-hoverBackground));
+}
+
.recent-session-date {
font-size: 11px;
color: var(--vscode-descriptionForeground);
@@ -442,6 +567,10 @@
transition: background-color 0.2s;
}
+.scroll-to-bottom-button [data-component="icon"] {
+ --icon-base: var(--vscode-button-foreground);
+}
+
.scroll-to-bottom-button:hover {
background: var(--vscode-button-hoverBackground);
}
@@ -1580,12 +1709,19 @@
position: relative;
}
-.account-switcher-welcome {
+.welcome-header {
position: absolute;
top: 12px;
right: 12px;
- width: 160px;
z-index: 10;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 8px;
+}
+
+.account-switcher-welcome {
+ width: 160px;
}
.account-switcher-trigger {
@@ -2065,7 +2201,8 @@
}
/* Session List Items */
-.session-list [data-slot="list-item-title"] {
+.session-list [data-slot="list-item-title"],
+.cloud-session-list [data-slot="list-item-title"] {
flex: 1;
min-width: 0;
overflow: hidden;
@@ -2074,7 +2211,8 @@
text-align: left;
}
-.session-list [data-slot="list-item-description"] {
+.session-list [data-slot="list-item-description"],
+.cloud-session-list [data-slot="list-item-description"] {
flex-shrink: 0;
margin-left: 8px;
font-size: 12px;
@@ -2082,6 +2220,29 @@
white-space: nowrap;
}
+body.vscode-light .session-list [data-slot="list-item"][data-active="true"] [data-slot="list-item-description"],
+body.vscode-light .session-list [data-slot="list-item"][data-selected="true"] [data-slot="list-item-description"],
+body.vscode-light .cloud-session-list [data-slot="list-item"][data-active="true"] [data-slot="list-item-description"],
+body.vscode-light
+ .cloud-session-list
+ [data-slot="list-item"][data-selected="true"]
+ [data-slot="list-item-description"] {
+ color: var(--text-on-interactive-base, white);
+}
+
+body.vscode-light
+ .session-list
+ [data-slot="list-item"][data-active="true"]
+ [data-slot="session-delete-button"]
+ [data-slot="icon-svg"],
+body.vscode-light
+ .session-list
+ [data-slot="list-item"][data-selected="true"]
+ [data-slot="session-delete-button"]
+ [data-slot="icon-svg"] {
+ color: var(--text-on-interactive-base, white);
+}
+
.session-list [data-slot="session-delete-button"] {
flex-shrink: 0;
margin-left: 4px;
@@ -2094,18 +2255,100 @@
opacity: 1;
}
-/* Cloud Session List */
-.cloud-session-list {
+/* History View (unified Local + Cloud tabs) */
+.history-view {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
-.cloud-session-filters {
+.history-view-header {
flex-shrink: 0;
- padding: 8px 12px;
- border-bottom: 1px solid var(--vscode-panel-border);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 8px;
+ min-height: 40px;
+}
+
+.history-view-tabs {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ flex: 1;
+}
+
+.history-tab-btn {
+ appearance: none;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ padding: 3px 10px;
+ border-radius: 4px;
+ font-size: 12px;
+ color: var(--vscode-foreground);
+ opacity: 0.6;
+ transition:
+ opacity 0.1s,
+ background 0.1s;
+ white-space: nowrap;
+}
+
+.history-tab-btn:hover {
+ opacity: 1;
+ background: var(--vscode-toolbar-hoverBackground, rgba(128, 128, 128, 0.1));
+}
+
+.history-tab-btn--active {
+ opacity: 1;
+ background: var(--vscode-toolbar-activeBackground, rgba(128, 128, 128, 0.2));
+ font-weight: 600;
+}
+
+.history-import-btn {
+ flex-shrink: 0;
+}
+
+.history-view-content {
+ flex: 1;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Cloud Session List (inside History View) */
+.cloud-session-list {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-height: 0;
+}
+
+/* Make the list search wrapper wrap so the repo filter appears below search */
+.cloud-session-list [data-slot="list-search-wrapper"] {
+ flex-wrap: wrap;
+}
+
+.cloud-session-repo-filter {
+ width: 100%;
+ padding: 4px 10px 2px;
+
+ [data-component="checkbox"] {
+ gap: 8px;
+
+ [data-slot="checkbox-checkbox-label"] {
+ padding-inline-start: 0;
+ margin-inline-end: 0;
+ }
+ }
+}
+
+[data-component="input"][data-variant="normal"] [data-slot="input-wrapper"]:focus-within:not(:has([data-readonly])) {
+ border-color: transparent;
+ box-shadow: none;
+ outline: 1px solid var(--vscode-focusBorder);
+ outline-offset: -1px;
}
.cloud-session-load-more {
@@ -2115,35 +2358,51 @@
padding: 8px;
}
+.cloud-session-load-more-btn {
+ appearance: none;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ font-size: 12px;
+ color: var(--vscode-foreground);
+ opacity: 0.6;
+ padding: 4px 8px;
+ border-radius: 4px;
+}
+
+.cloud-session-load-more-btn:hover {
+ opacity: 1;
+ background: var(--vscode-toolbar-hoverBackground, rgba(128, 128, 128, 0.1));
+}
+
/* ============================================
Kilo Notifications
============================================ */
.kilo-notifications {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- z-index: 10;
+ width: 280px;
display: flex;
flex-direction: column;
- border-bottom: 1px solid var(--vscode-panel-border);
+ border-radius: 6px;
animation: kilo-notifications-slide-in 0.3s ease-out;
+ overflow: hidden;
}
@keyframes kilo-notifications-slide-in {
from {
- transform: translateY(-100%);
+ transform: translateX(20px);
opacity: 0;
}
to {
- transform: translateY(0);
+ transform: translateX(0);
opacity: 1;
}
}
.kilo-notifications-card {
background-color: var(--vscode-editor-background);
+ border: 1px solid var(--vscode-panel-border);
+ border-radius: 6px;
padding: 12px;
display: flex;
flex-direction: column;
@@ -2185,6 +2444,65 @@
gap: 2px;
}
+.kilo-notifications-next-group {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.kilo-notifications-action-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ height: 24px;
+ padding: 0 8px;
+ background: none;
+ border: 1px solid var(--vscode-focusBorder);
+ border-radius: 3px;
+ color: var(--vscode-focusBorder);
+ font-size: 12px;
+ font-family: var(--vscode-font-family);
+ cursor: pointer;
+ white-space: nowrap;
+}
+
+.kilo-notifications-action-btn:hover {
+ background-color: var(--vscode-toolbar-hoverBackground);
+}
+
+.kilo-notifications-primary-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ height: 24px;
+ padding: 0 8px;
+ background-color: var(--vscode-button-background);
+ border: 1px solid transparent;
+ border-radius: 3px;
+ color: var(--vscode-button-foreground);
+ font-size: 12px;
+ font-family: var(--vscode-font-family);
+ cursor: pointer;
+ white-space: nowrap;
+}
+
+.kilo-notifications-primary-btn:hover {
+ background-color: var(--vscode-button-hoverBackground);
+}
+
+.kilo-notifications-back-link {
+ background: none;
+ border: none;
+ padding: 0;
+ cursor: pointer;
+ color: var(--vscode-descriptionForeground);
+ font-size: 12px;
+}
+
+.kilo-notifications-back-link:hover {
+ color: var(--vscode-foreground);
+}
+
.kilo-notifications-nav-btn {
background: none;
border: none;
@@ -2685,6 +3003,35 @@
animation: enhance-spin 1s linear infinite;
}
+/* Settings save bar */
+.settings-save-bar {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+ padding: 0 16px;
+ border-top: 1px solid var(--border-weak-base);
+ overflow: hidden;
+ max-height: 0;
+ opacity: 0;
+ transition:
+ max-height 0.2s ease,
+ opacity 0.2s ease,
+ padding 0.2s ease;
+}
+
+.settings-save-bar--visible {
+ max-height: 52px;
+ opacity: 1;
+ padding: 8px 16px;
+}
+
+.settings-save-bar-label {
+ font-size: 12px;
+ color: var(--foreground-secondary);
+ margin-right: auto;
+}
+
[data-slot="settings-row"] {
display: flex;
flex-wrap: wrap;
@@ -2719,6 +3066,12 @@
}
}
+/* Wider inputs inside cards with data-variant="wide-input" (e.g. edit mode config) */
+[data-variant="wide-input"] [data-slot="settings-row-input"] {
+ flex: 1 1 0%;
+ max-width: none;
+}
+
/* ============================================
High Contrast Theme Support
--vscode-contrastBorder is only defined in HC themes,
diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts
index 6a51ec31edf..ccec9dd04de 100644
--- a/packages/kilo-vscode/webview-ui/src/types/messages.ts
+++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts
@@ -214,6 +214,7 @@ export interface SlashCommandInfo {
// Agent/mode info from CLI backend
export interface AgentInfo {
name: string
+ displayName?: string
description?: string
mode: "subagent" | "primary" | "all"
native?: boolean
@@ -321,6 +322,10 @@ export type PermissionConfig = Partial>
export interface AgentConfig {
model?: string | null
prompt?: string
+ description?: string
+ mode?: "subagent" | "primary" | "all"
+ hidden?: boolean
+ disable?: boolean
temperature?: number
top_p?: number
steps?: number
@@ -349,8 +354,10 @@ export interface McpConfig {
}
export interface CommandConfig {
- command: string
+ template: string
description?: string
+ agent?: string
+ model?: string
}
export interface SkillsConfig {
@@ -627,7 +634,7 @@ export interface DeviceAuthCancelledMessage {
export interface NavigateMessage {
type: "navigate"
- view: "newTask" | "marketplace" | "history" | "cloudHistory" | "profile" | "settings" | "migration" | "subAgentViewer" // legacy-migration: "migration"
+ view: "newTask" | "marketplace" | "history" | "profile" | "settings" | "migration" | "subAgentViewer" // legacy-migration: "migration"
tab?: string
}
@@ -715,6 +722,11 @@ export interface ConfigUpdatedMessage {
config: Config
}
+export interface GlobalConfigLoadedMessage {
+ type: "globalConfigLoaded"
+ config: Config
+}
+
export interface NotificationSettingsLoadedMessage {
type: "notificationSettingsLoaded"
settings: {
@@ -956,6 +968,14 @@ export interface AgentManagerLocalStatsMessage {
stats: LocalGitStats
}
+// Sidebar: Live worktree diff stats (extension → webview)
+export interface WorktreeStatsLoadedMessage {
+ type: "worktreeStatsLoaded"
+ files: number
+ additions: number
+ deletions: number
+}
+
// Set the model for a session (extension → webview, used during multi-version creation)
export interface AgentManagerSetSessionModelMessage {
type: "agentManager.setSessionModel"
@@ -1195,6 +1215,15 @@ export interface ProviderActionErrorMessage {
message: string
}
+export interface CustomProviderModelsFetchedMessage {
+ type: "customProviderModelsFetched"
+ requestId: string
+ models?: Array<{ id: string; name: string }>
+ error?: string
+ /** True when error was HTTP 401/403 — hints the user to check their API key */
+ auth?: boolean
+}
+
export type ExtensionMessage =
| ReadyMessage
| ConnectionStateMessage
@@ -1236,6 +1265,7 @@ export type ExtensionMessage =
| BrowserSettingsLoadedMessage
| ConfigLoadedMessage
| ConfigUpdatedMessage
+ | GlobalConfigLoadedMessage
| NotificationSettingsLoadedMessage
| NotificationsLoadedMessage
| AgentManagerSessionMetaMessage
@@ -1284,8 +1314,11 @@ export type ExtensionMessage =
| ProviderConnectedMessage
| ProviderDisconnectedMessage
| ProviderActionErrorMessage
+ | CustomProviderModelsFetchedMessage
| RecentsLoadedMessage
| LanguageChangedMessage
+ | ContinueInWorktreeProgressMessage
+ | WorktreeStatsLoadedMessage
// ============================================
// Messages FROM webview TO extension
@@ -1541,6 +1574,10 @@ export interface RequestConfigMessage {
type: "requestConfig"
}
+export interface RequestGlobalConfigMessage {
+ type: "requestGlobalConfig"
+}
+
export interface UpdateConfigMessage {
type: "updateConfig"
config: Partial
@@ -1615,6 +1652,12 @@ export interface PromoteSessionRequest {
sessionId: string
}
+// Open an unassigned session locally (clear any worktree directory override)
+export interface OpenLocallyRequest {
+ type: "agentManager.openLocally"
+ sessionId: string
+}
+
// Add a new session to an existing worktree
export interface AddSessionToWorktreeRequest {
type: "agentManager.addSessionToWorktree"
@@ -1882,6 +1925,14 @@ export interface SaveCustomProviderMessage {
apiKey?: string
}
+export interface FetchCustomProviderModelsMessage {
+ type: "fetchCustomProviderModels"
+ requestId: string
+ baseURL: string
+ apiKey?: string
+ headers?: Record
+}
+
export interface PersistRecentsRequest {
type: "persistRecents"
recents: ModelSelection[]
@@ -1891,6 +1942,29 @@ export interface RequestRecentsMessage {
type: "requestRecents"
}
+// Continue in Worktree: transfer sidebar session + git state to an isolated worktree
+export interface ContinueInWorktreeRequest {
+ type: "continueInWorktree"
+ sessionId: string
+}
+
+export type ContinueInWorktreeStatus =
+ | "capturing"
+ | "creating"
+ | "setup"
+ | "transferring"
+ | "forking"
+ | "done"
+ | "error"
+
+// Continue in Worktree: progress updates (extension → webview)
+export interface ContinueInWorktreeProgressMessage {
+ type: "continueInWorktreeProgress"
+ status: ContinueInWorktreeStatus
+ detail?: string
+ error?: string
+}
+
export type WebviewMessage =
| SendMessageRequest
| AbortRequest
@@ -1934,6 +2008,7 @@ export type WebviewMessage =
| UpdateSettingRequest
| RequestBrowserSettingsMessage
| RequestConfigMessage
+ | RequestGlobalConfigMessage
| UpdateConfigMessage
| RequestNotificationSettingsMessage
| ResetAllSettingsRequest
@@ -1946,6 +2021,7 @@ export type WebviewMessage =
| DeleteWorktreeRequest
| RemoveStaleWorktreeRequest
| PromoteSessionRequest
+ | OpenLocallyRequest
| AddSessionToWorktreeRequest
| ForkSessionRequest
| CloseSessionRequest
@@ -2000,8 +2076,10 @@ export type WebviewMessage =
| CompleteProviderOAuthMessage
| DisconnectProviderMessage
| SaveCustomProviderMessage
+ | FetchCustomProviderModelsMessage
| PersistRecentsRequest
| RequestRecentsMessage
+ | ContinueInWorktreeRequest
// ============================================
// VS Code API type
diff --git a/packages/kilo-vscode/webview-ui/src/utils/config-utils.ts b/packages/kilo-vscode/webview-ui/src/utils/config-utils.ts
new file mode 100644
index 00000000000..f4f3880a800
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/utils/config-utils.ts
@@ -0,0 +1,96 @@
+import type { Config } from "../types/messages"
+
+export function isRecord(value: unknown): value is Record {
+ return !!value && typeof value === "object" && !Array.isArray(value)
+}
+
+/** Deep merge two objects, with source values overriding target values. */
+export function deepMerge(target: Config, source: Partial): Config {
+ const result: Record = { ...target }
+ for (const [key, value] of Object.entries(source)) {
+ if (isRecord(value) && isRecord(result[key])) {
+ result[key] = deepMerge(result[key] as Config, value as Partial)
+ } else {
+ result[key] = value
+ }
+ }
+ return result as Config
+}
+
+/** Recursively remove keys whose value is null (null = "deleted"). */
+export function stripNulls(obj: Config): Config {
+ const result: Record = {}
+ for (const [key, value] of Object.entries(obj)) {
+ if (value === null || value === undefined) continue
+ if (isRecord(value)) {
+ result[key] = stripNulls(value as Config)
+ } else {
+ result[key] = value
+ }
+ }
+ return result as Config
+}
+
+/**
+ * Resolve the visible config when a configLoaded/configUpdated message arrives.
+ * If the user has pending draft changes, re-apply the draft on top of the
+ * incoming server config so pending toggles don't snap back.
+ */
+export function resolveConfig(server: Config, draft: Partial, dirty: boolean): Config {
+ if (dirty) return stripNulls(deepMerge(server, draft))
+ return server
+}
+
+/**
+ * Plain-object config state machine — mirrors the SolidJS ConfigProvider
+ * logic without signals so the message-handling behavior is unit-testable.
+ */
+export class ConfigState {
+ config: Config = {}
+ saved: Config = {}
+ draft: Partial = {}
+ dirty = false
+ saving = false
+ loading = true
+
+ /** Accumulate a partial change (same as the toggle click path). */
+ updateConfig(partial: Partial) {
+ this.config = stripNulls(deepMerge(this.config, partial))
+ this.draft = deepMerge(this.draft as Config, partial)
+ this.dirty = true
+ }
+
+ /** Handle an incoming configLoaded push from the extension. */
+ handleConfigLoaded(server: Config) {
+ if (this.saving) return
+ this.config = resolveConfig(server, this.draft, this.dirty)
+ this.saved = server
+ this.loading = false
+ }
+
+ /** Handle an incoming configUpdated push from the extension. */
+ handleConfigUpdated(server: Config) {
+ if (this.saving) {
+ this.saving = false
+ this.draft = {}
+ this.dirty = false
+ this.config = server
+ } else {
+ this.config = resolveConfig(server, this.draft, this.dirty)
+ }
+ this.saved = server
+ }
+
+ /** Send the draft to the backend. */
+ saveConfig() {
+ if (Object.keys(this.draft).length === 0) return
+ this.saving = true
+ }
+
+ /** Discard pending changes. */
+ discardConfig() {
+ this.config = this.saved
+ this.draft = {}
+ this.dirty = false
+ }
+}
diff --git a/packages/kilo-vscode/webview-ui/src/utils/path-mentions.ts b/packages/kilo-vscode/webview-ui/src/utils/path-mentions.ts
new file mode 100644
index 00000000000..ba991d001b0
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/utils/path-mentions.ts
@@ -0,0 +1,85 @@
+/**
+ * Convert a dropped file URI or absolute path into a relative workspace path.
+ * Strips file:// and vscode-remote:// protocols, decodes URI components,
+ * and produces a relative path (e.g. "src/index.ts") when the file is inside
+ * the workspace. Returns the cleaned absolute path for files outside the workspace.
+ *
+ * The returned path does NOT include the "@" prefix — callers add that when
+ * inserting into the textarea so the path can also be registered in mentionedPaths.
+ */
+export function convertToMentionPath(path: string, cwd: string): string {
+ let cleaned = path
+
+ if (cleaned.startsWith("file://")) {
+ cleaned = cleaned.substring(7)
+ } else if (cleaned.startsWith("vscode-remote://")) {
+ const rest = cleaned.substring("vscode-remote://".length)
+ const idx = rest.indexOf("/")
+ cleaned = idx !== -1 ? rest.substring(idx) : ""
+ }
+
+ try {
+ cleaned = decodeURIComponent(cleaned)
+ // Remove leading slash for Windows paths like /d:/...
+ if (cleaned.startsWith("/") && cleaned[2] === ":") {
+ cleaned = cleaned.substring(1)
+ }
+ } catch (err) {
+ console.error("[Kilo New] Failed to decode dropped URI:", err, cleaned)
+ }
+
+ const normalized = cleaned.replace(/\\/g, "/")
+ let root = cwd.replace(/\\/g, "/")
+ if (root.endsWith("/")) root = root.slice(0, -1)
+
+ if (!root) return cleaned
+
+ if (normalized.toLowerCase().startsWith(root.toLowerCase())) {
+ const tail = normalized.substring(root.length)
+ // Boundary check: next char must be "/" or end of string to avoid
+ // /workspace/app matching /workspace/app2/file.ts
+ if (tail === "" || tail.startsWith("/")) {
+ const relative = tail.startsWith("/") ? tail.substring(1) : tail
+ return relative || cleaned
+ }
+ }
+
+ return cleaned
+}
+
+/** Returns true when the line looks like a file URI or absolute path. */
+function isFilePath(line: string): boolean {
+ if (line.startsWith("file://") || line.startsWith("vscode-remote://")) return true
+ // Unix absolute path
+ if (line.startsWith("/")) return true
+ // Windows absolute path (e.g. C:\, D:/)
+ if (/^[A-Za-z]:[\\/]/.test(line)) return true
+ return false
+}
+
+/**
+ * Extract file paths from a drop's DataTransfer.
+ * Only considers the URI-list data type (set by VS Code when dragging files
+ * from the explorer or editor tabs). Falls back to text/plain but only when
+ * every non-empty line looks like a file URI or absolute path, to avoid
+ * intercepting normal text drags from the editor.
+ *
+ * Returns null if no file paths are found.
+ */
+export function extractDropPaths(dt: DataTransfer): string[] | null {
+ // Prefer the VS Code-specific URI list — this is always file paths
+ const uri = dt.getData("application/vnd.code.uri-list")
+ if (uri) {
+ const paths = uri.split(/\r?\n/).filter((line) => line.trim() !== "")
+ if (paths.length > 0) return paths
+ }
+
+ // Fall back to text/plain only if every line is a recognizable file path
+ const text = dt.getData("text")
+ if (text) {
+ const lines = text.split(/\r?\n/).filter((line) => line.trim() !== "")
+ if (lines.length > 0 && lines.every(isFilePath)) return lines
+ }
+
+ return null
+}
diff --git a/packages/opencode/AGENTS.md b/packages/opencode/AGENTS.md
index 216a931712f..5e409636e15 100644
--- a/packages/opencode/AGENTS.md
+++ b/packages/opencode/AGENTS.md
@@ -49,6 +49,12 @@ export const get = fn(z.object({ id: z.string() }), async (input) => { ... })
**Logging** -- Use `Log.create({ service: "name" })` pattern.
+## Process Spawning (Windows)
+
+On Windows, any `spawn`/`execFile` call without `windowsHide: true` will flash a cmd.exe console window at the user. Use `Process.spawn` from `src/util/process.ts` — it enforces `windowsHide: true` automatically. For `Bun.spawn`/`Bun.spawnSync`, pass `windowsHide` via the options object if the subprocess could create a visible console.
+
+The MCP `StdioClientTransport` (third-party SDK) is handled separately via a process shim in `src/mcp/index.ts` that sets `process.type = "browser"` when running inside the VS Code extension (`KILO_PLATFORM=vscode`), which causes the SDK's internal `isElectron()` check to return `true` and enable `windowsHide`.
+
## Storage
Filesystem-based JSON, not a database. Data lives in `~/.local/share/kilo/storage/`. Keys are path arrays: `Storage.write(["session", projectID, sessionID], data)`.
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index c098d010b83..eea2feef59b 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
- "version": "7.1.2",
+ "version": "7.1.8",
"name": "@kilocode/cli",
"type": "module",
"license": "MIT",
@@ -69,7 +69,7 @@
"@ai-sdk/google-vertex": "3.0.106",
"@ai-sdk/groq": "2.0.34",
"@ai-sdk/mistral": "2.0.27",
- "@ai-sdk/openai": "2.0.89",
+ "@ai-sdk/openai": "2.0.101",
"@ai-sdk/openai-compatible": "1.0.32",
"@ai-sdk/perplexity": "2.0.23",
"@ai-sdk/provider": "2.0.1",
@@ -83,14 +83,16 @@
"@gitlab/opencode-gitlab-auth": "1.3.3",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
+ "@kilocode/kilo-gateway": "workspace:*",
+ "@kilocode/kilo-telemetry": "workspace:*",
+ "@kilocode/plugin": "workspace:*",
+ "@kilocode/sdk": "workspace:*",
"@modelcontextprotocol/sdk": "1.25.2",
"@morphllm/morphsdk": "0.2.148",
"@octokit/graphql": "9.0.2",
"@octokit/rest": "catalog:",
"@openauthjs/openauth": "catalog:",
- "@kilocode/plugin": "workspace:*",
"@opencode-ai/script": "workspace:*",
- "@kilocode/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.5.4",
"@opentui/core": "0.1.87",
@@ -124,6 +126,7 @@
"opentui-spinner": "0.0.6",
"partial-json": "0.1.7",
"remeda": "catalog:",
+ "simple-git": "3.31.1",
"solid-js": "catalog:",
"strip-ansi": "7.1.2",
"tree-sitter-bash": "0.25.0",
@@ -135,10 +138,7 @@
"xdg-basedir": "5.1.0",
"yargs": "18.0.0",
"zod": "catalog:",
- "zod-to-json-schema": "3.24.5",
- "@kilocode/kilo-gateway": "workspace:*",
- "@kilocode/kilo-telemetry": "workspace:*",
- "simple-git": "3.31.1"
+ "zod-to-json-schema": "3.24.5"
},
"overrides": {
"drizzle-orm": "1.0.0-beta.16-ea816b6"
diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts
index 59ecfcc6dbe..ac103a7ecfb 100644
--- a/packages/opencode/src/acp/agent.ts
+++ b/packages/opencode/src/acp/agent.ts
@@ -1592,8 +1592,16 @@ export namespace ACP {
if (specified) return specified
// kilocode_change start
- const freeModel = await fetchDefaultModel()
- return { providerID: "kilo", modelID: freeModel }
+ // Only fall back to the Kilo provider if it was present in the available
+ // providers list. When teams configure enabled_providers to use only their
+ // own models, this prevents silently routing requests to an external API.
+ // Note: LiteLLM / custom provider users won't reach here — the function
+ // returns earlier via `specified` (config.model) or the sorted providers list.
+ if (providers.some((p) => p.id === "kilo")) {
+ const freeModel = await fetchDefaultModel()
+ return { providerID: "kilo", modelID: freeModel }
+ }
+ throw new Error("no model available: no providers are configured and no default model is set")
// kilocode_change end
}
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index a7a8a490bbc..16305db1286 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -32,6 +32,7 @@ export namespace Agent {
export const Info = z
.object({
name: z.string(),
+ displayName: z.string().optional(), // kilocode_change - human-readable name for org modes
description: z.string().optional(),
mode: z.enum(["subagent", "primary", "all"]),
native: z.boolean().optional(),
@@ -61,9 +62,58 @@ export namespace Agent {
const skillDirs = await Skill.dirs()
const whitelistedDirs = [Truncate.GLOB, ...skillDirs.map((dir) => path.join(dir, "*"))]
+ // kilocode_change start — safe bash commands that don't need user approval.
+ // only commands that cannot execute arbitrary code or subprocesses.
+ const bash: Record = {
+ "*": "ask",
+ // read-only / informational
+ "cat *": "allow",
+ "head *": "allow",
+ "tail *": "allow",
+ "less *": "allow",
+ "ls *": "allow",
+ "tree *": "allow",
+ "pwd *": "allow",
+ "echo *": "allow",
+ "wc *": "allow",
+ "which *": "allow",
+ "type *": "allow",
+ "file *": "allow",
+ "diff *": "allow",
+ "du *": "allow",
+ "df *": "allow",
+ "date *": "allow",
+ "uname *": "allow",
+ "whoami *": "allow",
+ "printenv *": "allow",
+ "man *": "allow",
+ // text processing
+ "grep *": "allow",
+ "rg *": "allow",
+ "ag *": "allow",
+ "sort *": "allow",
+ "uniq *": "allow",
+ "cut *": "allow",
+ "tr *": "allow",
+ "jq *": "allow",
+ // file operations
+ "touch *": "allow",
+ "mkdir *": "allow",
+ "cp *": "allow",
+ "mv *": "allow",
+ // compilers (no script execution)
+ "tsc *": "allow",
+ "tsgo *": "allow",
+ // archive
+ "tar *": "allow",
+ "unzip *": "allow",
+ "gzip *": "allow",
+ "gunzip *": "allow",
+ }
+ // kilocode_change end
const defaults = PermissionNext.fromConfig({
"*": "allow",
- bash: "ask",
+ bash, // kilocode_change
doom_loop: "ask",
external_directory: {
"*": "ask",
@@ -154,7 +204,7 @@ export namespace Agent {
grep: "allow",
glob: "allow",
list: "allow",
- bash: "allow",
+ // bash: "allow", // kilocode_change - disabled to prevent orchestrator from writing files via shell commands instead of delegating to sub-agents
question: "allow",
task: "allow",
todoread: "allow",
@@ -168,6 +218,11 @@ export namespace Agent {
},
}),
user,
+ // kilocode_change start - enforce bash deny after user so user config cannot re-enable shell
+ PermissionNext.fromConfig({
+ bash: "deny",
+ }),
+ // kilocode_change end
),
mode: "primary",
native: true,
@@ -329,6 +384,11 @@ export namespace Agent {
item.name = value.name ?? item.name
item.steps = value.steps ?? item.steps
item.options = mergeDeep(item.options, value.options ?? {})
+ // kilocode_change start - populate displayName from org mode options
+ if (item.options?.displayName && typeof item.options.displayName === "string") {
+ item.displayName = item.options.displayName
+ }
+ // kilocode_change end
item.permission = PermissionNext.merge(item.permission, PermissionNext.fromConfig(value.permission ?? {}))
}
@@ -469,6 +529,10 @@ export namespace Agent {
const agent = agents[name]
if (!agent) throw new RemoveError({ name, message: "agent not found" })
if (agent.native) throw new RemoveError({ name, message: "cannot remove native agent" })
+ // kilocode_change start - prevent removal of organization-managed agents
+ if (agent.options?.source === "organization")
+ throw new RemoveError({ name, message: "cannot remove organization agent — manage it from the cloud dashboard" })
+ // kilocode_change end
const { unlink, readFile, writeFile } = await import("fs/promises")
let found = false
diff --git a/packages/opencode/src/cli/cmd/remote.ts b/packages/opencode/src/cli/cmd/remote.ts
new file mode 100644
index 00000000000..f3ea09aea18
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/remote.ts
@@ -0,0 +1,31 @@
+// kilocode_change - new file
+import { cmd } from "./cmd"
+import { bootstrap } from "../bootstrap"
+import { KiloSessions } from "@/kilo-sessions/kilo-sessions"
+import { Instance } from "@/project/instance"
+
+export const RemoteCommand = cmd({
+ command: "remote",
+ describe: "enable remote connection for real-time session relay",
+ builder: (yargs) => yargs,
+ handler: async () => {
+ await bootstrap(process.cwd(), async () => {
+ await KiloSessions.enableRemote()
+ console.log("Remote connection enabled.")
+
+ const abort = new AbortController()
+ const shutdown = async () => {
+ try {
+ KiloSessions.disableRemote()
+ await Instance.dispose()
+ } finally {
+ abort.abort()
+ }
+ }
+ process.on("SIGTERM", shutdown)
+ process.on("SIGINT", shutdown)
+ process.on("SIGHUP", shutdown)
+ await new Promise((resolve) => abort.signal.addEventListener("abort", resolve))
+ })
+ },
+})
diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx
index e1eb218015e..2fdc41b3934 100644
--- a/packages/opencode/src/cli/cmd/tui/app.tsx
+++ b/packages/opencode/src/cli/cmd/tui/app.tsx
@@ -261,9 +261,23 @@ function App() {
}
const [terminalTitleEnabled, setTerminalTitleEnabled] = createSignal(kv.get("terminal_title_enabled", true))
+ // kilocode_change start — notify server which session the user is viewing (for live session indicators)
createEffect(() => {
- console.log(JSON.stringify(route.data))
+ const sessionID = route.data.type === "session" ? route.data.sessionID : undefined
+ sdk.client.session.viewed({ sessionID }).catch(() => {})
})
+ // kilocode_change end
+
+ // kilocode_change start — evict per-session data from store when navigating away
+ createEffect(
+ on(
+ () => (route.data.type === "session" ? route.data.sessionID : undefined),
+ (current, prev) => {
+ if (prev && prev !== current) sync.session.evict(prev)
+ },
+ ),
+ )
+ // kilocode_change end
// Update terminal window title based on current route and session
createEffect(() => {
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-agent.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-agent.tsx
index 365a22445b4..24be2167d2b 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-agent.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-agent.tsx
@@ -11,7 +11,7 @@ export function DialogAgent() {
local.agent.list().map((item) => {
return {
value: item.name,
- title: item.name,
+ title: item.displayName ?? item.name, // kilocode_change
description: item.native ? "native" : item.description,
}
}),
@@ -20,7 +20,7 @@ export function DialogAgent() {
return (
{
local.agent.set(option.value)
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
index 40b985b31a7..68660bb9ed1 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
@@ -575,7 +575,7 @@ export function Prompt(props: PromptProps) {
if (store.mode === "shell") {
sdk.client.session.shell({
sessionID,
- agent: local.agent.current().name,
+ agent: local.agent.current()?.name ?? "", // kilocode_change
model: {
providerID: selectedModel.providerID,
modelID: selectedModel.modelID,
@@ -602,7 +602,7 @@ export function Prompt(props: PromptProps) {
sessionID,
command: command.slice(1),
arguments: args,
- agent: local.agent.current().name,
+ agent: local.agent.current()?.name ?? "", // kilocode_change
model: `${selectedModel.providerID}/${selectedModel.modelID}`,
messageID,
variant,
@@ -619,7 +619,7 @@ export function Prompt(props: PromptProps) {
sessionID,
...selectedModel,
messageID,
- agent: local.agent.current().name,
+ agent: local.agent.current()?.name ?? "", // kilocode_change
model: selectedModel,
variant,
parts: [
@@ -740,7 +740,7 @@ export function Prompt(props: PromptProps) {
const highlight = createMemo(() => {
if (keybind.leader) return theme.border
if (store.mode === "shell") return theme.primary
- return local.agent.color(local.agent.current().name)
+ return local.agent.color(local.agent.current()?.name ?? "") // kilocode_change
})
const showVariant = createMemo(() => {
@@ -760,7 +760,7 @@ export function Prompt(props: PromptProps) {
})
const spinnerDef = createMemo(() => {
- const color = local.agent.color(local.agent.current().name)
+ const color = local.agent.color(local.agent.current()?.name ?? "") // kilocode_change
return {
frames: createFrames({
color,
@@ -1018,7 +1018,11 @@ export function Prompt(props: PromptProps) {
/>
- {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
+ {/* kilocode_change start */}
+ {store.mode === "shell"
+ ? "Shell"
+ : (local.agent.current()?.displayName ?? Locale.titlecase(local.agent.current()?.name ?? ""))}{" "}
+ {/* kilocode_change end */}
diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx
index 9fd6bb4f1bc..d5c503ea796 100644
--- a/packages/opencode/src/cli/cmd/tui/context/local.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx
@@ -57,7 +57,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
return agents()
},
current() {
- return agents().find((x) => x.name === agentStore.current)!
+ // kilocode_change start - fall back to first agent when current is removed (e.g. org switch)
+ const found = agents().find((x) => x.name === agentStore.current)
+ if (found) return found
+ const fallback = agents()[0]
+ if (fallback) setAgentStore("current", fallback.name)
+ return fallback
+ // kilocode_change end
},
set(name: string) {
if (!agents().some((x) => x.name === name))
@@ -74,6 +80,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
if (next < 0) next = agents().length - 1
if (next >= agents().length) next = 0
const value = agents()[next]
+ if (!value) return // kilocode_change - guard against empty agent list during org switch
setAgentStore("current", value.name)
})
},
@@ -194,6 +201,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const currentModel = createMemo(() => {
const a = agent.current()
+ if (!a) return fallbackModel() // kilocode_change - guard against empty agent list
return (
getFirstValidModel(
() => modelStore.model[a.name],
@@ -242,8 +250,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
if (next >= recent.length) next = 0
const val = recent[next]
if (!val) return
- setModelStore("model", agent.current().name, { ...val })
- save() // kilocode_change
+ // kilocode_change start
+ const cur = agent.current()
+ if (!cur) return
+ setModelStore("model", cur.name, { ...val })
+ save()
+ // kilocode_change end
},
cycleFavorite(direction: 1 | -1) {
const favorites = modelStore.favorite.filter((item) => isModelValid(item))
@@ -269,7 +281,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
const next = favorites[index]
if (!next) return
- setModelStore("model", agent.current().name, { ...next })
+ // kilocode_change start
+ const cur = agent.current()
+ if (!cur) return
+ setModelStore("model", cur.name, { ...next })
+ // kilocode_change end
const uniq = uniqueBy([next, ...modelStore.recent], (x) => `${x.providerID}/${x.modelID}`)
if (uniq.length > 10) uniq.pop()
setModelStore(
@@ -288,7 +304,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
})
return
}
- setModelStore("model", agent.current().name, model)
+ // kilocode_change start
+ const cur = agent.current()
+ if (!cur) return
+ setModelStore("model", cur.name, model)
+ // kilocode_change end
if (options?.recent) {
const uniq = uniqueBy([model, ...modelStore.recent], (x) => `${x.providerID}/${x.modelID}`)
if (uniq.length > 10) uniq.pop()
@@ -384,6 +404,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
// Automatically update model when agent changes
createEffect(() => {
const value = agent.current()
+ if (!value) return // kilocode_change - guard against empty agent list during org switch
if (value.model) {
if (isModelValid(value.model))
model.set({
diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
index 7d0d8f62ab5..e7049fcbe4e 100644
--- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
@@ -53,7 +53,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
[sessionID: string]: SessionStatus
}
session_diff: {
- [sessionID: string]: Snapshot.FileDiff[]
+ [sessionID: string]: Omit[] // kilocode_change
}
todo: {
[sessionID: string]: Todo[]
@@ -107,12 +107,42 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const sdk = useSDK()
+ const fullSyncedSessions = new Set() // kilocode_change
+
async function syncWorkspaces() {
const result = await sdk.client.experimental.workspace.list().catch(() => undefined)
if (!result?.data) return
setStore("workspaceList", reconcile(result.data))
}
+ // kilocode_change start
+ function evict(sessionID: string) {
+ // Collect child session IDs so we can evict them too.
+ const children = store.session.filter((s) => s.parentID === sessionID).map((s) => s.id)
+ setStore(
+ produce((draft) => {
+ const messages = draft.message[sessionID]
+ if (messages) {
+ for (const msg of messages) delete draft.part[msg.id]
+ }
+ delete draft.message[sessionID]
+ delete draft.session_diff[sessionID]
+ delete draft.session_status[sessionID]
+ delete draft.todo[sessionID]
+ }),
+ )
+ fullSyncedSessions.delete(sessionID)
+ for (const child of children) evict(child)
+ }
+
+ // Strip summary.diffs from user messages — the TUI never reads them
+ // and they can carry multi-MB before/after file content strings.
+ function strip(msg: Message): Message {
+ if (msg.role !== "user" || !msg.summary?.diffs) return msg
+ return { ...msg, summary: { ...msg.summary, diffs: [] } } as Message
+ }
+ // kilocode_change end
+
sdk.event.listen((e) => {
const event = e.details
switch (event.type) {
@@ -199,21 +229,29 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
break
case "session.diff":
- setStore("session_diff", event.properties.sessionID, event.properties.diff)
+ setStore(
+ "session_diff",
+ event.properties.sessionID,
+ event.properties.diff.map(({ before: _, after: __, ...rest }) => rest),
+ )
break
+ // kilocode_change start
case "session.deleted": {
- const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
- if (result.found) {
+ const sid = event.properties.info.id
+ const match = Binary.search(store.session, sid, (s) => s.id)
+ if (match.found) {
setStore(
"session",
produce((draft) => {
- draft.splice(result.index, 1)
+ draft.splice(match.index, 1)
}),
)
}
+ evict(sid)
break
}
+ // kilocode_change end
case "session.updated": {
const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
if (result.found) {
@@ -234,31 +272,33 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
break
}
+ // kilocode_change start
case "message.updated": {
- const messages = store.message[event.properties.info.sessionID]
+ const info = strip(event.properties.info)
+ const messages = store.message[info.sessionID]
if (!messages) {
- setStore("message", event.properties.info.sessionID, [event.properties.info])
+ setStore("message", info.sessionID, [info])
break
}
- const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
+ const result = Binary.search(messages, info.id, (m) => m.id)
if (result.found) {
- setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
+ setStore("message", info.sessionID, result.index, reconcile(info))
break
}
setStore(
"message",
- event.properties.info.sessionID,
+ info.sessionID,
produce((draft) => {
- draft.splice(result.index, 0, event.properties.info)
+ draft.splice(result.index, 0, info)
}),
)
- const updated = store.message[event.properties.info.sessionID]
+ const updated = store.message[info.sessionID]
if (updated.length > 100) {
const oldest = updated[0]
batch(() => {
setStore(
"message",
- event.properties.info.sessionID,
+ info.sessionID,
produce((draft) => {
draft.shift()
}),
@@ -273,6 +313,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
}
break
}
+ // kilocode_change end
case "message.removed": {
const messages = store.message[event.properties.sessionID]
const result = Binary.search(messages, event.properties.messageID, (m) => m.id)
@@ -441,7 +482,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
bootstrap()
})
- const fullSyncedSessions = new Set()
const result = {
data: store,
set: setStore,
@@ -481,15 +521,16 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
if (match.found) draft.session[match.index] = session.data!
if (!match.found) draft.session.splice(match.index, 0, session.data!)
draft.todo[sessionID] = todo.data ?? []
- draft.message[sessionID] = messages.data!.map((x) => x.info)
+ draft.message[sessionID] = messages.data!.map((x) => strip(x.info)) // kilocode_change
for (const message of messages.data!) {
draft.part[message.info.id] = message.parts
}
- draft.session_diff[sessionID] = diff.data ?? []
+ draft.session_diff[sessionID] = (diff.data ?? []).map(({ before: _, after: __, ...rest }) => rest)
}),
)
fullSyncedSessions.add(sessionID)
},
+ evict, // kilocode_change
},
workspace: {
get(workspaceID: string) {
diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx
index 9cf976e4da6..2dbc34e1888 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx
@@ -16,6 +16,8 @@ import { useKV } from "../context/kv"
import { useCommandDialog } from "../component/dialog-command"
import { KiloNews } from "@/kilocode/components/kilo-news" // kilocode_change
import { useConnected } from "../component/dialog-model" // kilocode_change
+import { RemoteIndicator } from "@/kilocode/remote-tui" // kilocode_change
+import { useSDK } from "../context/sdk" // kilocode_change
// TODO: what is the best way to do this?
let once = false
@@ -27,6 +29,7 @@ export function Home() {
const route = useRouteData("home")
const promptRef = usePromptRef()
const command = useCommandDialog()
+ const sdk = useSDK() // kilocode_change
const mcp = createMemo(() => Object.keys(sync.data.mcp).length > 0)
const mcpError = createMemo(() => {
return Object.values(sync.data.mcp).some((x) => x.status === "failed")
@@ -149,6 +152,7 @@ export function Home() {
{directory()}
+
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx
index 8ace2fff372..801281bd148 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx
@@ -3,8 +3,10 @@ import { useTheme } from "../../context/theme"
import { useSync } from "../../context/sync"
import { useDirectory } from "../../context/directory"
import { useConnected } from "../../component/dialog-model"
+import { useSDK } from "../../context/sdk" // kilocode_change
import { createStore } from "solid-js/store"
import { useRoute } from "../../context/route"
+import { RemoteIndicator } from "@/kilocode/remote-tui" // kilocode_change
export function Footer() {
const { theme } = useTheme()
@@ -19,6 +21,7 @@ export function Footer() {
})
const directory = useDirectory()
const connected = useConnected()
+ const sdk = useSDK() // kilocode_change
const [store, setStore] = createStore({
welcome: false,
@@ -53,6 +56,7 @@ export function Footer() {
{directory()}
+
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index 289be84da67..20117cc48e2 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -1222,6 +1222,8 @@ export function Session() {
+ {/* kilocode_change */}
+
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
index c2cd4d17d9c..667761cd890 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx
@@ -16,6 +16,7 @@ import { Locale } from "@/util/locale"
import { Global } from "@/global"
import { useDialog } from "../../ui/dialog"
import { useTuiConfig } from "../../context/tui-config"
+import { ConfigProtection } from "@/kilocode/permission/config-paths" // kilocode_change
type PermissionStage = "permission" | "always" | "reject"
@@ -428,12 +429,18 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
)
+ // kilocode_change start — hide "Always allow" for config file edits
+ const options: Record = props.request.metadata?.[ConfigProtection.DISABLE_ALWAYS_KEY]
+ ? { once: "Allow once", reject: "Reject" }
+ : { once: "Allow once", always: "Allow always", reject: "Reject" }
+ // kilocode_change end
+
const body = (
{
diff --git a/packages/opencode/src/commit-message/generate.ts b/packages/opencode/src/commit-message/generate.ts
index f5321ace7b3..9dbbb246756 100644
--- a/packages/opencode/src/commit-message/generate.ts
+++ b/packages/opencode/src/commit-message/generate.ts
@@ -110,6 +110,11 @@ function clean(text: string): string {
return result.trim()
}
+// Maximum time (ms) to wait for the LLM to produce a commit message before
+// aborting. Prevents the HTTP request from hanging indefinitely when the
+// provider is slow or the stream stalls (e.g. due to config state races).
+const TIMEOUT_MS = 30_000
+
export async function generateCommitMessage(request: CommitMessageRequest): Promise {
const ctx = await getGitContext(request.path, request.selectedFiles)
if (ctx.files.length === 0) {
@@ -141,38 +146,59 @@ export async function generateCommitMessage(request: CommitMessageRequest): Prom
userMessage = `IMPORTANT: Generate a COMPLETELY DIFFERENT commit message from the previous one. The previous message was: "${request.previousMessage}". Use a different type, scope, or description approach.\n\n${userMessage}`
}
- const stream = await LLM.stream({
- agent,
- user: {
- id: "commit-message",
+ const controller = new AbortController()
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS)
+
+ try {
+ const stream = await LLM.stream({
+ agent,
+ user: {
+ id: "commit-message",
+ sessionID: "commit-message",
+ role: "user",
+ model: {
+ providerID: model.providerID,
+ modelID: model.id,
+ },
+ time: {
+ created: Date.now(),
+ completed: Date.now(),
+ },
+ } as any,
+ tools: {},
+ model,
+ small: true,
+ messages: [
+ {
+ role: "user" as const,
+ content: userMessage,
+ },
+ ],
+ abort: controller.signal,
sessionID: "commit-message",
- role: "user",
- model: {
- providerID: model.providerID,
- modelID: model.id,
- },
- time: {
- created: Date.now(),
- completed: Date.now(),
- },
- } as any,
- tools: {},
- model,
- small: true,
- messages: [
- {
- role: "user" as const,
- content: userMessage,
- },
- ],
- abort: new AbortController().signal,
- sessionID: "commit-message",
- system: [],
- retries: 3,
- })
+ system: [],
+ retries: 3,
+ })
- const result = await stream.text
- log.info("generated", { message: result })
+ // Consume the stream explicitly so that stream-level errors surface
+ // immediately instead of leaving the .text promise hanging (issue #7345).
+ // With some providers/versions of the Vercel AI SDK, `await stream.text`
+ // never resolves when the underlying stream errors out early.
+ let result = ""
+ for await (const chunk of stream.textStream) {
+ result += chunk
+ }
- return { message: clean(result) }
+ log.info("generated", { message: result })
+ return { message: clean(result) }
+ } catch (err) {
+ if (controller.signal.aborted) {
+ throw new Error("Commit message generation timed out after 30 seconds")
+ }
+ const msg = err instanceof Error ? err.message : String(err)
+ log.error("generation failed", { error: msg })
+ throw new Error(`Failed to generate commit message: ${msg}`)
+ } finally {
+ clearTimeout(timer)
+ }
}
diff --git a/packages/opencode/src/commit-message/git-context.ts b/packages/opencode/src/commit-message/git-context.ts
index 3b97f44c58b..d81e8a62cfb 100644
--- a/packages/opencode/src/commit-message/git-context.ts
+++ b/packages/opencode/src/commit-message/git-context.ts
@@ -133,6 +133,7 @@ function git(args: string[], cwd: string): string {
cwd,
stdout: "pipe",
stderr: "pipe",
+ windowsHide: true, // kilocode_change - prevent cmd.exe flash on Windows
})
return result.stdout.toString().trimEnd()
}
@@ -144,7 +145,7 @@ function parseNameStatus(output: string): Array<{ status: string; path: string }
let path: string
if (status!.startsWith("R")) {
// Rename: rest = ["old.ts", "new.ts"], use the new path
- path = rest[1] ?? rest[0]
+ path = rest[1] ?? rest[0] ?? ""
} else {
path = rest.join("\t")
}
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 210d680e9c4..eff231452e0 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -41,6 +41,7 @@ import { ConfigPaths } from "./paths"
import { Filesystem } from "@/util/filesystem"
import { ModesMigrator } from "../kilocode/modes-migrator" // kilocode_change
+import { fetchOrganizationModes } from "@kilocode/kilo-gateway" // kilocode_change
import { RulesMigrator } from "../kilocode/rules-migrator" // kilocode_change
import { WorkflowsMigrator } from "../kilocode/workflows-migrator" // kilocode_change
import { McpMigrator } from "../kilocode/mcp-migrator" // kilocode_change
@@ -174,6 +175,26 @@ export namespace Config {
}
// kilocode_change end
+ // kilocode_change start - Load organization custom modes from Kilo Cloud API
+ // These override legacy Kilocode modes but are overridden by well-known, global, and project config
+ try {
+ const kilo = auth["kilo"]
+ if (kilo?.type === "oauth" && kilo.access && kilo.accountId) {
+ const modes = await fetchOrganizationModes(kilo.access, kilo.accountId)
+ if (modes.length > 0) {
+ const agents = ModesMigrator.convertOrganizationModes(modes)
+ result = mergeConfigConcatArrays(result, { agent: agents })
+ log.debug("loaded organization custom modes", {
+ count: modes.length,
+ modes: modes.map((m) => m.slug),
+ })
+ }
+ }
+ } catch (err) {
+ log.warn("failed to load organization custom modes", { error: err })
+ }
+ // kilocode_change end
+
// Load remote/well-known config (overrides Kilocode legacy configs)
// This allows organizations to provide default configs that users can override
for (const [key, value] of Object.entries(auth)) {
@@ -1314,7 +1335,54 @@ export namespace Config {
export type Info = z.output
+ // kilocode_change start — migrate bash permission for existing users before config is consumed
+ const GLOBAL_CONFIG_FILES = ["config.json", "kilo.json", "kilo.jsonc", "opencode.json", "opencode.jsonc"]
+
+ async function migrateBashPermission() {
+ const files = GLOBAL_CONFIG_FILES.map((file) => path.join(Global.Path.config, file))
+ // also check legacy TOML config — its presence means existing user
+ const legacy = path.join(Global.Path.config, "config")
+ const existing = files.filter((file) => existsSync(file))
+ const hasLegacy = existsSync(legacy)
+ // no global config → new user, they'll get the new bash:ask default
+ if (existing.length === 0 && !hasLegacy) return
+ // check if any config file already has an explicit bash permission
+ for (const file of existing) {
+ const text = await Bun.file(file)
+ .text()
+ .catch(() => "")
+ const data = parseJsonc(text) ?? {}
+ if (data.permission?.bash) return
+ }
+ // also check legacy TOML config for bash permission
+ if (hasLegacy) {
+ const toml = await import(pathToFileURL(legacy).href, { with: { type: "toml" } }).catch(() => undefined)
+ if (toml?.default?.permission?.bash) return
+ }
+ // existing user without bash permission in any file → write bash:allow to the
+ // highest-precedence existing file to preserve their current behavior.
+ // if only the legacy TOML file exists, write to config.json (the TOML migration will merge into it)
+ const target = existing.length > 0 ? existing[existing.length - 1] : path.join(Global.Path.config, "config.json")
+ const text = await Bun.file(target)
+ .text()
+ .catch(() => "{}")
+ if (target.endsWith(".jsonc")) {
+ const edits = modify(text, ["permission", "bash"], "allow", {
+ formattingOptions: { insertSpaces: true, tabSize: 2 },
+ })
+ await Bun.write(target, applyEdits(text, edits))
+ log.info("migrated bash permission to allow for existing user", { path: target })
+ return
+ }
+ const data = parseJsonc(text) ?? {}
+ const merged = { ...data, permission: { ...data.permission, bash: "allow" } }
+ await Bun.write(target, JSON.stringify(merged, null, 2))
+ log.info("migrated bash permission to allow for existing user", { path: target })
+ }
+ // kilocode_change end
+
export const global = lazy(async () => {
+ await migrateBashPermission() // kilocode_change — run before config is read
let result: Info = pipe(
{},
mergeDeep(await loadFile(path.join(Global.Path.config, "config.json"))),
diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts
index 01f07c9afaa..2c181479be0 100644
--- a/packages/opencode/src/file/index.ts
+++ b/packages/opencode/src/file/index.ts
@@ -596,12 +596,15 @@ export namespace File {
const fullPath = path.join(resolved, entry.name)
const relativePath = path.relative(Instance.directory, fullPath)
const type = entry.isDirectory() ? "directory" : "file"
+ // On Windows, path.relative() across drives returns an absolute path;
+ // skip the gitignore check in that case to avoid a RangeError from `ignore`.
+ const canIgnore = !path.isAbsolute(relativePath)
nodes.push({
name: entry.name,
path: relativePath,
absolute: fullPath,
type,
- ignored: ignored(type === "directory" ? relativePath + "/" : relativePath),
+ ignored: canIgnore && ignored(type === "directory" ? relativePath + "/" : relativePath),
})
}
return nodes.sort((a, b) => {
diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts
index 1ece6b10cbe..52c38e27817 100644
--- a/packages/opencode/src/flag/flag.ts
+++ b/packages/opencode/src/flag/flag.ts
@@ -65,6 +65,8 @@ export namespace Flag {
const parsed = Number(value)
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined
}
+
+ export const KILO_SESSION_RETRY_LIMIT = number("KILO_SESSION_RETRY_LIMIT")
}
// Dynamic getter for KILO_DISABLE_PROJECT_CONFIG
diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts
index f32d00fd98c..688bc25b742 100644
--- a/packages/opencode/src/index.ts
+++ b/packages/opencode/src/index.ts
@@ -28,6 +28,7 @@ import { EOL } from "os"
// import { WebCommand } from "./cli/cmd/web" // kilocode_change (Disabled unsupported opencode web UI)
import { PrCommand } from "./cli/cmd/pr"
import { SessionCommand } from "./cli/cmd/session"
+import { RemoteCommand } from "./cli/cmd/remote" // kilocode_change
// kilocode_change start - Import telemetry, instance disposal, and legacy migration
import { Telemetry } from "@kilocode/kilo-telemetry"
import { Instance } from "./project/instance" // kilocode_change
@@ -192,6 +193,7 @@ let cli = yargs(hideBin(process.argv))
// .command(GithubCommand) // kilocode_change (Disabled until backend is ready)
.command(PrCommand)
.command(SessionCommand)
+ .command(RemoteCommand) // kilocode_change
.command(DbCommand)
if (Installation.isLocal()) {
diff --git a/packages/opencode/src/kilo-sessions/kilo-sessions.ts b/packages/opencode/src/kilo-sessions/kilo-sessions.ts
index 62288eba6f8..28c1f4f94e7 100644
--- a/packages/opencode/src/kilo-sessions/kilo-sessions.ts
+++ b/packages/opencode/src/kilo-sessions/kilo-sessions.ts
@@ -13,6 +13,9 @@ import { KILO_API_BASE } from "@kilocode/kilo-gateway"
import { Instance } from "@/project/instance"
import { Vcs } from "@/project/vcs"
import simpleGit from "simple-git"
+import { RemoteWS } from "@/kilo-sessions/remote-ws"
+import { RemoteSender } from "@/kilo-sessions/remote-sender"
+import { SessionStatus } from "@/session/status"
export namespace KiloSessions {
const log = Log.create({ service: "kilo-sessions" })
@@ -127,6 +130,12 @@ export namespace KiloSessions {
},
})
+ const remoteEnabled = process.env["KILO_REMOTE"] === "1"
+ let remote: { conn: RemoteWS.Connection; sender: RemoteSender.Sender; heartbeat: () => Promise } | undefined
+ let enabling: Promise | undefined
+ let remoteSeq = 0
+ let viewedSessionId: string | undefined
+
export async function init() {
if (ingestDisabled) return
@@ -195,6 +204,118 @@ export namespace KiloSessions {
Bus.subscribe(Session.Event.TurnClose, async (evt) => {
await ingest.sync(evt.properties.sessionID, [{ type: "session_close", data: { reason: evt.properties.reason } }])
})
+
+ if (remoteEnabled) enableRemote().catch((err) => log.warn("remote not enabled", { error: String(err) }))
+ Bus.subscribe(Bus.InstanceDisposed, () => disableRemote())
+ }
+
+ export async function enableRemote() {
+ if (remote) return
+ if (ingestDisabled) return
+ if (enabling) return enabling
+ const seq = ++remoteSeq
+ enabling = (async () => {
+ const token = await kilocodeToken()
+ if (!token) {
+ throw new Error("Unable to enable remote: no Kilo credentials found. Run `kilo auth login`.")
+ }
+
+ const valid = await authValid(token)
+ if (valid === false) {
+ throw new Error("Unable to enable remote: invalid or expired Kilo credentials. Run `kilo auth login`.")
+ }
+ if (valid === undefined) throw new Error("Unable to enable remote: failed to verify Kilo credentials.")
+
+ const url = (process.env["KILO_SESSION_INGEST_URL"] ?? "https://ingest.kilosessions.ai")
+ .replace(/^https:\/\//, "wss://")
+ .replace(/^http:\/\//, "ws://")
+
+ // Capture directory so the heartbeat timer can re-enter the Instance context
+ // (setInterval runs outside AsyncLocalStorage scope)
+ const directory = Instance.directory
+ const getSessions = async () => {
+ const [gitUrl, gitBranch] = await Promise.all([
+ getGitUrl().catch(() => undefined),
+ Vcs.branch().catch(() => undefined),
+ ])
+ const statuses = SessionStatus.list()
+ const ids = new Set(Object.keys(statuses))
+ if (viewedSessionId) ids.add(viewedSessionId)
+ const results = await Promise.all(
+ [...ids].map(async (id) => {
+ const session = await Session.get(id).catch(() => undefined)
+ if (!session) return undefined
+ return {
+ id,
+ status: statuses[id]?.type ?? "idle",
+ title: session.title,
+ parentSessionId: session.parentID,
+ gitUrl,
+ gitBranch,
+ }
+ }),
+ )
+ return results.filter((r): r is NonNullable => !!r)
+ }
+
+ const conn = RemoteWS.connect({
+ url,
+ getToken: kilocodeToken,
+ withContext: (fn) => Instance.provide({ directory, fn }),
+ getSessions,
+ log,
+ onMessage: (msg) => {
+ // Must run inside Instance.provide so Bus.subscribeAll can access
+ // the instance-scoped subscription map via Instance.state().
+ void Instance.provide({ directory, fn: () => sender.handle(msg) })
+ },
+ onClose: () => disableRemote(),
+ })
+
+ const sender = RemoteSender.create({
+ conn,
+ directory: Instance.directory,
+ log,
+ })
+
+ const heartbeat = async () => {
+ conn.send({ type: "heartbeat", sessions: await getSessions() })
+ }
+
+ if (seq !== remoteSeq) {
+ sender.dispose()
+ conn.close()
+ return
+ }
+
+ remote = { conn, sender, heartbeat }
+ log.info("remote connection enabled")
+ })().finally(() => {
+ if (remoteSeq === seq) enabling = undefined
+ })
+
+ return enabling
+ }
+
+ export function disableRemote() {
+ remoteSeq += 1
+ enabling = undefined
+ if (!remote) return
+ remote.sender.dispose()
+ remote.conn.close()
+ remote = undefined
+ log.info("remote connection disabled")
+ }
+
+ export function remoteStatus() {
+ return {
+ enabled: !!remote,
+ connected: remote?.conn.connected ?? false,
+ }
+ }
+ export function setViewedSession(sessionID: string | undefined) {
+ viewedSessionId = sessionID
+ if (remote) void remote.heartbeat().catch((err) => log.warn("heartbeat failed", { error: String(err) }))
}
export async function create(sessionId: string) {
diff --git a/packages/opencode/src/kilo-sessions/remote-protocol.ts b/packages/opencode/src/kilo-sessions/remote-protocol.ts
new file mode 100644
index 00000000000..ed8e19348e5
--- /dev/null
+++ b/packages/opencode/src/kilo-sessions/remote-protocol.ts
@@ -0,0 +1,79 @@
+import z from "zod"
+
+export namespace RemoteProtocol {
+ // --- Shared ---
+
+ export const SessionInfo = z.object({
+ id: z.string(),
+ status: z.string(),
+ title: z.string(),
+ parentSessionId: z.string().optional(),
+ gitUrl: z.string().optional(),
+ gitBranch: z.string().optional(),
+ })
+ export type SessionInfo = z.infer
+
+ // --- CLI → DO (Outbound) ---
+
+ export const Heartbeat = z.object({
+ type: z.literal("heartbeat"),
+ sessions: z.array(SessionInfo),
+ })
+ export type Heartbeat = z.infer
+
+ export const Event = z.object({
+ type: z.literal("event"),
+ sessionId: z.string(),
+ parentSessionId: z.string().optional(),
+ event: z.string(),
+ data: z.unknown(),
+ })
+ export type Event = z.infer
+
+ export const Response = z.object({
+ type: z.literal("response"),
+ id: z.string(),
+ result: z.unknown().optional(),
+ error: z.unknown().optional(),
+ })
+ export type Response = z.infer
+
+ export const Outbound = z.discriminatedUnion("type", [Heartbeat, Event, Response])
+ export type Outbound = z.infer
+
+ // --- DO → CLI (Inbound) ---
+
+ export const Subscribe = z.object({
+ type: z.literal("subscribe"),
+ sessionId: z.string(),
+ })
+ export type Subscribe = z.infer
+
+ export const Unsubscribe = z.object({
+ type: z.literal("unsubscribe"),
+ sessionId: z.string(),
+ })
+ export type Unsubscribe = z.infer
+
+ export const Command = z.object({
+ type: z.literal("command"),
+ id: z.string(),
+ command: z.string(),
+ sessionId: z.string().optional(),
+ data: z.unknown(),
+ })
+ export type Command = z.infer
+
+ export const System = z.object({
+ type: z.literal("system"),
+ event: z.string(),
+ data: z.unknown(),
+ })
+ export type System = z.infer
+
+ export const Inbound = z.discriminatedUnion("type", [Subscribe, Unsubscribe, Command, System])
+ export type Inbound = z.infer
+
+ /** Lightweight schema for diagnostic logging before full parse. */
+ export const Preview = z.object({ type: z.string(), id: z.string().optional() })
+}
diff --git a/packages/opencode/src/kilo-sessions/remote-sender.ts b/packages/opencode/src/kilo-sessions/remote-sender.ts
new file mode 100644
index 00000000000..02f74d064a0
--- /dev/null
+++ b/packages/opencode/src/kilo-sessions/remote-sender.ts
@@ -0,0 +1,331 @@
+import { RemoteProtocol } from "@/kilo-sessions/remote-protocol"
+import type { RemoteWS } from "@/kilo-sessions/remote-ws"
+import { Bus } from "@/bus"
+import { Instance } from "@/project/instance"
+import { Session } from "@/session"
+import { SessionPrompt } from "@/session/prompt"
+import { Question } from "@/question"
+import { PermissionNext } from "@/permission/next"
+import { Log } from "@/util/log"
+import z from "zod"
+
+const QuestionData = z.object({
+ requestID: z.string(),
+ answers: z.array(z.array(z.string())),
+})
+
+const PermissionData = z.object({
+ requestID: z.string(),
+ reply: z.enum(["once", "always", "reject"]),
+ message: z.string().optional(),
+})
+
+const RemotePromptInput = SessionPrompt.PromptInput.extend({
+ model: z.string().optional(),
+})
+
+function normalizeModel(model: z.infer) {
+ if (!model) return undefined
+ return {
+ providerID: "kilo",
+ modelID: model.startsWith("kilocode/") ? model.slice("kilocode/".length) : model,
+ }
+}
+
+function normalizePrompt(input: z.infer): SessionPrompt.PromptInput {
+ return {
+ ...input,
+ model: normalizeModel(input.model),
+ }
+}
+
+export namespace RemoteSender {
+ export type Options = {
+ conn: RemoteWS.Connection
+ directory: string
+ log: {
+ info: (...args: any[]) => void
+ error: (...args: any[]) => void
+ warn: (...args: any[]) => void
+ }
+ subscribe?: typeof Bus.subscribeAll
+ provide?: typeof Instance.provide
+ }
+
+ export type Sender = {
+ handle(msg: RemoteProtocol.Inbound): void
+ dispose(): void
+ }
+
+ export function create(options: Options): Sender {
+ const sessions = new Set()
+ const children = new Map() // childId → parentId
+ let unsub: (() => void) | undefined
+
+ const sub = options.subscribe ?? Bus.subscribeAll
+
+ function subscribed(sid: string) {
+ if (sessions.has(sid)) return true
+ const root = rootOf(sid)
+ return root ? sessions.has(root) : false
+ }
+
+ function rootOf(sid: string): string | undefined {
+ const parent = children.get(sid)
+ if (!parent) return undefined
+ return rootOf(parent) ?? parent
+ }
+
+ async function backfillChildren(parentId: string) {
+ const provide = options.provide ?? Instance.provide
+ try {
+ await provide({
+ directory: options.directory,
+ fn: async () => {
+ await discoverChildren(parentId)
+ },
+ })
+ } catch (e) {
+ options.log.error("backfill children failed", { parentId, error: String(e) })
+ }
+ }
+
+ // Replay pending questions/permissions so a newly-subscribed web client
+ // sees state that was asked before it connected — analogous to the Cloud
+ // Agent's `connected` event carrying pending question/permission fields.
+ async function replay(sessionId: string) {
+ const [questions, permissions] = await Promise.all([Question.list(), PermissionNext.list()])
+ for (const q of questions) {
+ if (q.sessionID !== sessionId) continue
+ options.conn.send({
+ type: "event",
+ sessionId,
+ event: "question.asked",
+ data: q,
+ })
+ }
+ for (const p of permissions) {
+ if (p.sessionID !== sessionId) continue
+ options.conn.send({
+ type: "event",
+ sessionId,
+ event: "permission.asked",
+ data: p,
+ })
+ }
+ }
+
+ async function backfillPendingState(sessionId: string) {
+ const provide = options.provide ?? Instance.provide
+ try {
+ await provide({
+ directory: options.directory,
+ fn: () => replay(sessionId),
+ })
+ } catch (e) {
+ options.log.error("backfill pending state failed", { sessionId, error: String(e) })
+ }
+ }
+
+ async function discoverChildren(parentId: string) {
+ const childSessions = await Session.children(parentId)
+ for (const child of childSessions) {
+ children.set(child.id, parentId)
+ const root = rootOf(child.id) ?? parentId
+ options.conn.send({
+ type: "event",
+ sessionId: child.id,
+ parentSessionId: root,
+ event: "session.created",
+ data: { info: child },
+ })
+ await replay(child.id)
+ await discoverChildren(child.id)
+ }
+ }
+
+ // Extract session ID from the correct nested location depending on event type.
+ // Different events store the session ID in different places:
+ // - Top-level: session.diff, session.turn.*, message.part.delta, session.status, session.idle
+ // - info.sessionID: message.updated
+ // - info.id: session.created, session.updated (the session's own ID)
+ // - part.sessionID: message.part.updated, message.part.removed
+ function extractSessionId(props: any): string | undefined {
+ if (!props) return undefined
+ if (typeof props.sessionID === "string") return props.sessionID
+ if (typeof props.info?.sessionID === "string") return props.info.sessionID
+ if (typeof props.info?.id === "string") return props.info.id
+ if (typeof props.part?.sessionID === "string") return props.part.sessionID
+ return undefined
+ }
+
+ function forwarder(event: { type: string; properties?: any }) {
+ // Track child sessions as they're created
+ if (event.type === "session.created") {
+ const parent = event.properties?.info?.parentID
+ const child = event.properties?.info?.id
+ if (parent && child) children.set(child, parent)
+ }
+
+ const sid = extractSessionId(event.properties)
+ if (!sid || !subscribed(sid)) return
+ const root = rootOf(sid)
+ options.conn.send({
+ type: "event",
+ sessionId: sid,
+ ...(root ? { parentSessionId: root } : {}),
+ event: event.type,
+ data: event.properties,
+ })
+ }
+
+ function dispatchLongRunning(msg: RemoteProtocol.Command, work: () => Promise) {
+ const provide = options.provide ?? Instance.provide
+ options.conn.send({ type: "response", id: msg.id, result: {} })
+ void (async () => {
+ try {
+ await provide({ directory: options.directory, fn: work })
+ } catch (e) {
+ options.log.error("long-running command failed after ACK", {
+ id: msg.id,
+ command: msg.command,
+ error: String(e),
+ })
+ }
+ })()
+ }
+
+ function dispatchQuick(msg: RemoteProtocol.Command, work: () => Promise) {
+ const provide = options.provide ?? Instance.provide
+ void (async () => {
+ try {
+ await provide({ directory: options.directory, fn: work })
+ options.conn.send({ type: "response", id: msg.id, result: {} })
+ } catch (e) {
+ options.conn.send({ type: "response", id: msg.id, error: String(e) })
+ }
+ })()
+ }
+
+ function dispatch(msg: RemoteProtocol.Command) {
+ if (msg.command === "send_message") {
+ const parsed = RemotePromptInput.safeParse(msg.data)
+ if (!parsed.success) {
+ options.conn.send({
+ type: "response",
+ id: msg.id,
+ error: "invalid send_message data: " + parsed.error.message,
+ })
+ return
+ }
+ const input = SessionPrompt.PromptInput.safeParse(normalizePrompt(parsed.data))
+ if (!input.success) {
+ options.conn.send({
+ type: "response",
+ id: msg.id,
+ error: "invalid send_message data: " + input.error.message,
+ })
+ return
+ }
+ dispatchLongRunning(msg, async () => {
+ await SessionPrompt.prompt(input.data)
+ })
+ return
+ }
+ if (msg.command === "question_reply") {
+ const parsed = QuestionData.safeParse(msg.data)
+ if (!parsed.success) {
+ options.conn.send({
+ type: "response",
+ id: msg.id,
+ error: "invalid question_reply data: " + parsed.error.message,
+ })
+ return
+ }
+ dispatchQuick(msg, () => Question.reply(parsed.data))
+ return
+ }
+ if (msg.command === "question_reject") {
+ const parsed = z.object({ requestID: z.string() }).safeParse(msg.data)
+ if (!parsed.success) {
+ options.conn.send({
+ type: "response",
+ id: msg.id,
+ error: "invalid question_reject data: " + parsed.error.message,
+ })
+ return
+ }
+ dispatchQuick(msg, () => Question.reject(parsed.data.requestID))
+ return
+ }
+ if (msg.command === "permission_respond") {
+ const parsed = PermissionData.safeParse(msg.data)
+ if (!parsed.success) {
+ options.conn.send({
+ type: "response",
+ id: msg.id,
+ error: "invalid permission_respond data: " + parsed.error.message,
+ })
+ return
+ }
+ dispatchQuick(msg, () => PermissionNext.reply(parsed.data))
+ return
+ }
+ options.conn.send({
+ type: "response",
+ id: msg.id,
+ error: `unknown command: ${msg.command}`,
+ })
+ options.log.warn("unknown command", { command: msg.command })
+ }
+
+ function handle(msg: RemoteProtocol.Inbound) {
+ if (msg.type === "subscribe") {
+ if (sessions.has(msg.sessionId)) return
+ sessions.add(msg.sessionId)
+ void backfillChildren(msg.sessionId)
+ void backfillPendingState(msg.sessionId)
+ if (!unsub) unsub = sub(forwarder)
+ return
+ }
+ if (msg.type === "unsubscribe") {
+ sessions.delete(msg.sessionId)
+ const queue = [msg.sessionId]
+ while (queue.length) {
+ const id = queue.pop()!
+ for (const [child, parent] of children) {
+ if (parent === id) {
+ children.delete(child)
+ queue.push(child)
+ }
+ }
+ }
+ if (sessions.size === 0 && unsub) {
+ unsub()
+ unsub = undefined
+ }
+ return
+ }
+ if (msg.type === "command") {
+ options.log.info("received command", { id: msg.id, command: msg.command })
+ dispatch(msg)
+ return
+ }
+ if (msg.type === "system") {
+ options.log.info("system event", { event: msg.event })
+ return
+ }
+ }
+
+ function dispose() {
+ if (unsub) {
+ unsub()
+ unsub = undefined
+ }
+ sessions.clear()
+ children.clear()
+ }
+
+ return { handle, dispose }
+ }
+}
diff --git a/packages/opencode/src/kilo-sessions/remote-ws.ts b/packages/opencode/src/kilo-sessions/remote-ws.ts
new file mode 100644
index 00000000000..88a1f2f36db
--- /dev/null
+++ b/packages/opencode/src/kilo-sessions/remote-ws.ts
@@ -0,0 +1,152 @@
+import { RemoteProtocol } from "@/kilo-sessions/remote-protocol"
+
+export namespace RemoteWS {
+ export type SessionInfo = RemoteProtocol.SessionInfo
+
+ export type Options = {
+ url: string
+ getToken: () => Promise
+ getSessions: () => SessionInfo[] | Promise
+ log: {
+ info: (...args: any[]) => void
+ error: (...args: any[]) => void
+ warn: (...args: any[]) => void
+ }
+ onMessage?: (msg: RemoteProtocol.Inbound) => void
+ heartbeat?: number
+ /** Wraps callbacks that need to run in a specific async context (e.g. Instance.provide) */
+ withContext?: (fn: () => R) => Promise | R
+ /** Called when the server permanently closes the connection (e.g. auth failure, conflict) */
+ onClose?: (code: number, reason: string) => void
+ }
+
+ export type Connection = {
+ readonly connectionId: string
+ send(msg: RemoteProtocol.Outbound): void
+ close(): void
+ readonly connected: boolean
+ }
+
+ type Timer = ReturnType
+
+ export function connect(options: Options): Connection {
+ const interval = options.heartbeat ?? 10_000
+ const connectionId = crypto.randomUUID()
+ const withContext = options.withContext ?? ((fn) => fn())
+ let ws: WebSocket | undefined
+ let backoff = 1000
+ let timer: Timer | undefined
+ let beat: Timer | undefined
+ let closed = false
+ const buffer: string[] = []
+
+ function startHeartbeat() {
+ stopHeartbeat()
+ beat = setInterval(() => {
+ void withContext(async () => {
+ send({ type: "heartbeat", sessions: await options.getSessions() })
+ }).catch((err) => {
+ options.log.error("remote-ws heartbeat failed", { error: String(err) })
+ })
+ }, interval)
+ }
+
+ function stopHeartbeat() {
+ if (beat) clearInterval(beat)
+ beat = undefined
+ }
+
+ async function open() {
+ if (closed) return
+ const token = await options.getToken()
+ if (!token) {
+ options.log.warn("remote-ws no token, will retry")
+ schedule()
+ return
+ }
+ const endpoint = `${options.url}/api/user/cli?token=${encodeURIComponent(token)}&connectionId=${connectionId}`
+ options.log.info("remote-ws connecting", { connectionId, endpoint: endpoint.replace(/token=[^&]+/, "token=***") })
+ ws = new WebSocket(endpoint)
+
+ ws.onopen = () => {
+ options.log.info("remote-ws connected", { buffered: buffer.length })
+ backoff = 1000
+ for (const msg of buffer) ws!.send(msg)
+ buffer.length = 0
+ startHeartbeat()
+ }
+
+ ws.onmessage = (event) => {
+ const raw = String(event.data)
+ let json: unknown
+ try {
+ json = JSON.parse(raw)
+ } catch {
+ options.log.warn("remote-ws invalid JSON", { bytes: raw.length })
+ return
+ }
+ const preview = RemoteProtocol.Preview.safeParse(json)
+ options.log.info("remote-ws received", { bytes: raw.length, ...preview.data })
+ const parsed = RemoteProtocol.Inbound.safeParse(json)
+ if (!parsed.success) {
+ options.log.warn("remote-ws message parse failed", { error: parsed.error })
+ return
+ }
+ options.onMessage?.(parsed.data)
+ }
+
+ ws.onclose = (event) => {
+ options.log.info("remote-ws closed", { code: event.code, reason: event.reason })
+ ws = undefined
+ stopHeartbeat()
+ if (event.code === 4401 || event.code === 4403 || event.code === 4409) {
+ options.log.warn("remote-ws closed permanently", {
+ code: event.code,
+ reason: event.reason,
+ })
+ options.onClose?.(event.code, event.reason)
+ return
+ }
+ schedule()
+ }
+
+ ws.onerror = (event) => {
+ options.log.error("remote-ws error", { error: event })
+ }
+ }
+
+ function schedule() {
+ if (closed) return
+ timer = setTimeout(() => open(), backoff)
+ backoff = Math.min(backoff * 2, 60000)
+ }
+
+ function send(msg: RemoteProtocol.Outbound) {
+ const raw = JSON.stringify(msg)
+ if (ws?.readyState === WebSocket.OPEN) {
+ ws.send(raw)
+ return
+ }
+ buffer.push(raw)
+ if (buffer.length > 200) buffer.shift()
+ }
+
+ function close() {
+ closed = true
+ stopHeartbeat()
+ if (timer) clearTimeout(timer)
+ if (ws) ws.close()
+ }
+
+ open()
+
+ return {
+ connectionId,
+ send,
+ close,
+ get connected() {
+ return ws?.readyState === WebSocket.OPEN
+ },
+ }
+ }
+}
diff --git a/packages/opencode/src/kilocode/kilo-commands.tsx b/packages/opencode/src/kilocode/kilo-commands.tsx
index 25d5c11f2fa..6d725d2b0f2 100644
--- a/packages/opencode/src/kilocode/kilo-commands.tsx
+++ b/packages/opencode/src/kilocode/kilo-commands.tsx
@@ -37,6 +37,45 @@ export function registerKiloCommands(useSDK: () => UseSDK) {
})
command.register(() => [
+ // /remote command
+ {
+ value: "remote.toggle",
+ title: "Toggle remote",
+ description: "Enable or disable remote session relay",
+ category: "Kilo",
+ slash: { name: "remote" },
+ enabled: isKiloConnected(),
+ hidden: !isKiloConnected(),
+ onSelect: async () => {
+ try {
+ const current = await sdk.client.remote.status()
+
+ if (current.error || !current.data) {
+ dialog.replace(() => )
+ return
+ }
+
+ if (current.data.enabled) {
+ await sdk.client.remote.disable()
+ toast.show({ message: "Remote disabled", variant: "success" })
+ } else {
+ const result = await sdk.client.remote.enable()
+ if (result.error) {
+ const err = result.error as { error?: string }
+ const msg = err?.error ?? "Failed to enable remote."
+ dialog.replace(() => )
+ return
+ }
+ toast.show({ message: "Remote enabled", variant: "success" })
+ }
+
+ dialog.clear()
+ } catch (error) {
+ dialog.replace(() => )
+ }
+ },
+ },
+
// /profile command
{
value: "kilo.profile",
diff --git a/packages/opencode/src/kilocode/modes-migrator.ts b/packages/opencode/src/kilocode/modes-migrator.ts
index 1060ec5a08f..72aacbe8572 100644
--- a/packages/opencode/src/kilocode/modes-migrator.ts
+++ b/packages/opencode/src/kilocode/modes-migrator.ts
@@ -4,6 +4,7 @@ import * as path from "path"
import os from "os"
import { Config } from "../config/config"
import { KilocodePaths } from "./paths"
+import type { OrganizationMode } from "@kilocode/kilo-gateway"
export namespace ModesMigrator {
// Kilocode mode structure
@@ -88,6 +89,42 @@ export namespace ModesMigrator {
}
}
+ /**
+ * Convert a cloud OrganizationMode to a Config.Agent.
+ * Unlike legacy convertMode(), this does NOT skip default slugs —
+ * organization admins can intentionally override built-in agents.
+ */
+ export function convertOrganizationMode(mode: OrganizationMode): Config.Agent {
+ const cfg = mode.config
+ const prompt = [cfg.roleDefinition, cfg.customInstructions].filter(Boolean).join("\n\n")
+ const groups = cfg.groups ?? []
+ if (groups.length === 0) {
+ console.warn(
+ `[ModesMigrator] Organization mode "${mode.slug}" has no groups configured — all tool permissions will be denied`,
+ )
+ }
+
+ return {
+ mode: "primary",
+ description: cfg.description ?? cfg.whenToUse ?? mode.name,
+ prompt: prompt || undefined,
+ permission: convertPermissions(groups),
+ options: { source: "organization", displayName: mode.name },
+ }
+ }
+
+ /**
+ * Convert an array of cloud OrganizationModes to a Config.Agent record
+ * keyed by slug. All modes are included (no default-slug filtering).
+ */
+ export function convertOrganizationModes(modes: OrganizationMode[]): Record {
+ const result: Record = {}
+ for (const mode of modes) {
+ result[mode.slug] = convertOrganizationMode(mode)
+ }
+ return result
+ }
+
export async function readModesFile(filepath: string): Promise {
try {
const content = await fs.readFile(filepath, "utf-8")
diff --git a/packages/opencode/src/kilocode/permission/config-paths.ts b/packages/opencode/src/kilocode/permission/config-paths.ts
new file mode 100644
index 00000000000..facd70987ca
--- /dev/null
+++ b/packages/opencode/src/kilocode/permission/config-paths.ts
@@ -0,0 +1,129 @@
+import path from "path"
+import { Global } from "@/global"
+import { KilocodePaths } from "@/kilocode/paths"
+
+export namespace ConfigProtection {
+ /**
+ * Config directory prefixes (relative paths, forward-slash normalized).
+ * Matches .kilo/, .kilocode/, .opencode/ at any depth within the project.
+ */
+ const CONFIG_DIRS = [".kilo/", ".kilocode/", ".opencode/"]
+
+ /**
+ * Subdirectories under CONFIG_DIRS that are NOT config files (e.g. plan files).
+ * Paths under these subdirs are exempt from config protection.
+ */
+ const EXCLUDED_SUBDIRS = ["plans/"]
+
+ /**
+ * Root-level config files that must be protected.
+ * Matched only when the relative path has no directory component.
+ */
+ const CONFIG_ROOT_FILES = new Set(["kilo.json", "kilo.jsonc", "opencode.json", "opencode.jsonc", "AGENTS.md"])
+
+ /** Metadata key used to signal the UI to hide the "Allow always" option. */
+ export const DISABLE_ALWAYS_KEY = "disableAlways" as const
+
+ function normalize(p: string): string {
+ return path.posix.normalize(p.replaceAll("\\", "/"))
+ }
+
+ /** Return the remainder after the config dir prefix, or undefined if excluded. */
+ function excluded(remainder: string): boolean {
+ return EXCLUDED_SUBDIRS.some((sub) => remainder.startsWith(sub))
+ }
+
+ /** Check if a project-relative path points to a config file or directory. */
+ export function isRelative(pattern: string): boolean {
+ const normalized = normalize(pattern)
+ for (const dir of CONFIG_DIRS) {
+ const bare = dir.slice(0, -1) // e.g. ".kilo"
+ // Match at root (e.g. ".kilo/foo") or nested (e.g. "packages/sub/.kilo/foo")
+ if (normalized === bare || normalized.endsWith("/" + bare)) return true
+ if (normalized.startsWith(dir)) {
+ if (excluded(normalized.slice(dir.length))) continue
+ return true
+ }
+ const nested = normalized.indexOf("/" + dir)
+ if (nested !== -1) {
+ if (excluded(normalized.slice(nested + 1 + dir.length))) continue
+ return true
+ }
+ }
+ return CONFIG_ROOT_FILES.has(normalized)
+ }
+
+ /** Check if `child` is equal to or nested inside `parent`. */
+ function within(child: string, parent: string): boolean {
+ return child === parent || child.startsWith(parent + path.sep)
+ }
+
+ /** Check if an absolute path is inside a known CLI config directory. */
+ export function isAbsolute(filepath: string): boolean {
+ const resolved = path.resolve(filepath)
+
+ // ~/.config/kilo/ (XDG config)
+ if (within(resolved, path.resolve(Global.Path.config))) return true
+
+ // ~/.kilo/ and ~/.kilocode/ (legacy global dirs)
+ for (const dir of KilocodePaths.globalDirs()) {
+ if (within(resolved, path.resolve(dir))) return true
+ }
+
+ return false
+ }
+
+ /** Check a single path (absolute or relative) against config protection. */
+ function protected_(p: string): boolean {
+ return path.isAbsolute(p) ? isAbsolute(p) : isRelative(p)
+ }
+
+ /**
+ * Determine if a permission request targets config files.
+ * Checks `edit` and `external_directory` permissions — read access is not restricted.
+ */
+ export function isRequest(request: {
+ permission: string
+ patterns: string[]
+ metadata?: Record
+ }): boolean {
+ // external_directory patterns are absolute globs like "/Users/alex/.config/kilo/*"
+ if (request.permission === "external_directory") {
+ for (const pattern of request.patterns) {
+ const dir = pattern.replace(/\/\*$/, "")
+ if (isAbsolute(dir)) return true
+ }
+ return false
+ }
+
+ if (request.permission !== "edit") return false
+
+ // Check patterns — handle both relative and absolute
+ for (const pattern of request.patterns) {
+ if (protected_(pattern)) return true
+ }
+
+ // Check metadata.filepath (absolute for edit, comma-joined relative for apply_patch)
+ const fp = request.metadata?.filepath
+ if (typeof fp === "string") {
+ // apply_patch joins relative paths with ", "
+ const parts = fp.includes(", ") ? fp.split(", ") : [fp]
+ for (const part of parts) {
+ if (protected_(part)) return true
+ }
+ }
+
+ // Check metadata.files[] (apply_patch file objects with absolute filePath/movePath)
+ const files = request.metadata?.files
+ if (Array.isArray(files)) {
+ for (const file of files) {
+ for (const key of ["filePath", "movePath"] as const) {
+ const val = file?.[key]
+ if (typeof val === "string" && protected_(val)) return true
+ }
+ }
+ }
+
+ return false
+ }
+}
diff --git a/packages/opencode/src/kilocode/permission/drain.ts b/packages/opencode/src/kilocode/permission/drain.ts
index 608932a4f0b..cfd6949c45d 100644
--- a/packages/opencode/src/kilocode/permission/drain.ts
+++ b/packages/opencode/src/kilocode/permission/drain.ts
@@ -1,6 +1,7 @@
import { Bus } from "@/bus"
import { Wildcard } from "@/util/wildcard"
import type { PermissionNext } from "@/permission/next"
+import { ConfigProtection } from "@/kilocode/permission/config-paths"
/**
* Auto-resolve pending permissions now fully covered by approved or denied rules.
@@ -25,6 +26,8 @@ export async function drainCovered(
) {
for (const [id, entry] of Object.entries(pending)) {
if (id === exclude) continue
+ // Never auto-resolve config file edit permissions
+ if (ConfigProtection.isRequest(entry.info)) continue
const actions = entry.info.patterns.map((pattern) =>
evaluate(entry.info.permission, pattern, entry.ruleset, approved),
)
diff --git a/packages/opencode/src/kilocode/plan-followup.ts b/packages/opencode/src/kilocode/plan-followup.ts
index ab11e366f0c..caf2df67db3 100644
--- a/packages/opencode/src/kilocode/plan-followup.ts
+++ b/packages/opencode/src/kilocode/plan-followup.ts
@@ -2,6 +2,8 @@ import { Telemetry } from "@kilocode/kilo-telemetry"
import { Agent } from "@/agent/agent"
import { Bus } from "@/bus"
import { TuiEvent } from "@/cli/cmd/tui/event"
+import { Flag } from "@/flag/flag"
+import { Global } from "@/global"
import { Identifier } from "@/id/id"
import { Provider } from "@/provider/provider"
import { Question } from "@/question"
@@ -10,6 +12,7 @@ import { LLM } from "@/session/llm"
import { MessageV2 } from "@/session/message-v2"
import { Todo } from "@/session/todo"
import { Log } from "@/util/log"
+import path from "path"
function toText(item: MessageV2.WithParts): string {
return item.parts
@@ -111,6 +114,51 @@ export namespace PlanFollowup {
export const ANSWER_NEW_SESSION = "Start new session"
export const ANSWER_CONTINUE = "Continue here"
+ function resolveVariant(value: string | undefined, model: Provider.Model | undefined) {
+ if (!value) return undefined
+ if (!model?.variants?.[value]) return undefined
+ return value
+ }
+
+ async function resolveCodeModel(input: Pick) {
+ const state =
+ Flag.KILO_CLIENT === "cli"
+ ? await Bun.file(path.join(Global.Path.state, "model.json"))
+ .text()
+ .then(
+ (raw) =>
+ JSON.parse(raw) as {
+ model?: Record
+ variant?: Record
+ },
+ )
+ .catch(() => undefined)
+ : undefined
+ const saved = state?.model?.code
+ if (saved) {
+ const full = await Provider.getModel(saved.providerID, saved.modelID).catch(() => undefined)
+ if (full) {
+ const key = `${saved.providerID}/${saved.modelID}`
+ return {
+ model: saved,
+ variant: resolveVariant(state?.variant?.[key], full),
+ }
+ }
+ }
+
+ const agent = await Agent.get("code")
+ if (agent?.model) {
+ const full = await Provider.getModel(agent.model.providerID, agent.model.modelID).catch(() => undefined)
+ if (full) {
+ return {
+ model: agent.model,
+ variant: resolveVariant(agent.variant, full),
+ }
+ }
+ }
+ return input
+ }
+
async function resolvePlan(input: {
assistant?: MessageV2.WithParts
messages: MessageV2.WithParts[]
@@ -141,6 +189,7 @@ export namespace PlanFollowup {
sessionID: string
agent: string
model: MessageV2.User["model"]
+ variant?: MessageV2.User["variant"]
text: string
synthetic?: boolean
}) {
@@ -153,6 +202,7 @@ export namespace PlanFollowup {
},
agent: input.agent,
model: input.model,
+ variant: input.variant,
}
await Session.updateMessage(msg)
await Session.updatePart({
@@ -209,8 +259,13 @@ export namespace PlanFollowup {
plan: string
messages: MessageV2.WithParts[]
model: MessageV2.User["model"]
+ variant?: MessageV2.User["variant"]
abort?: AbortSignal
}) {
+ const code = await resolveCodeModel({
+ model: input.model,
+ variant: input.variant,
+ })
const [handover, todos] = await Promise.all([
generateHandover({ messages: input.messages, model: input.model, abort: input.abort }),
Todo.get(input.sessionID),
@@ -231,7 +286,8 @@ export namespace PlanFollowup {
await inject({
sessionID: next.id,
agent: "code",
- model: input.model,
+ model: code.model,
+ variant: code.variant,
text: sections.join("\n\n"),
synthetic: false,
})
@@ -282,6 +338,7 @@ export namespace PlanFollowup {
plan,
messages: input.messages,
model: user.model,
+ variant: user.variant,
abort: input.abort,
})
return "break"
@@ -289,10 +346,15 @@ export namespace PlanFollowup {
if (answer === ANSWER_CONTINUE) {
Telemetry.trackPlanFollowup(input.sessionID, "continue")
+ const code = await resolveCodeModel({
+ model: user.model,
+ variant: user.variant,
+ })
await inject({
sessionID: input.sessionID,
agent: "code",
- model: user.model,
+ model: code.model,
+ variant: code.variant,
text: "Implement the plan above.",
})
return "continue"
@@ -303,6 +365,7 @@ export namespace PlanFollowup {
sessionID: input.sessionID,
agent: "plan",
model: user.model,
+ variant: user.variant,
text: answer,
})
return "continue"
diff --git a/packages/opencode/src/kilocode/provider-options.ts b/packages/opencode/src/kilocode/provider-options.ts
new file mode 100644
index 00000000000..e9b59e31ba3
--- /dev/null
+++ b/packages/opencode/src/kilocode/provider-options.ts
@@ -0,0 +1,29 @@
+import type { AnthropicProviderOptions } from "@ai-sdk/anthropic"
+import type { OpenAIResponsesProviderOptions } from "@ai-sdk/openai"
+import type { OpenAICompatibleProviderOptions } from "@ai-sdk/openai-compatible"
+import type { OpenRouterProviderOptions } from "@openrouter/ai-sdk-provider"
+
+export function kiloProviderOptions(options: { [x: string]: any }) {
+ const result: Record = {}
+ const openrouter = options as OpenRouterProviderOptions & {
+ verbosity?: "high" | "medium" | "low"
+ }
+ result.openrouter = openrouter
+ result.openai = {
+ reasoningEffort:
+ openrouter.reasoning && "effort" in openrouter.reasoning ? openrouter.reasoning?.effort : undefined,
+ textVerbosity: openrouter.verbosity,
+ store: false,
+ //forceReasoning: openrouter.reasoning?.enabled, // ai sdk v6
+ } satisfies OpenAIResponsesProviderOptions
+ result.anthropic = {
+ thinking: { type: openrouter.reasoning?.enabled ? "adaptive" : "disabled" },
+ effort: openrouter.verbosity,
+ } satisfies AnthropicProviderOptions
+ result.openaiCompatible = {
+ reasoningEffort:
+ openrouter.reasoning && "effort" in openrouter.reasoning ? openrouter.reasoning?.effort : undefined,
+ textVerbosity: openrouter.verbosity,
+ } satisfies OpenAICompatibleProviderOptions
+ return result
+}
diff --git a/packages/opencode/src/kilocode/remote-tui.tsx b/packages/opencode/src/kilocode/remote-tui.tsx
new file mode 100644
index 00000000000..08d97f8f54b
--- /dev/null
+++ b/packages/opencode/src/kilocode/remote-tui.tsx
@@ -0,0 +1,36 @@
+/**
+ * Remote Session Relay TUI indicator
+ *
+ * RemoteIndicator component for the footer status bar.
+ */
+
+import { createSignal, onMount, onCleanup, Show } from "solid-js"
+
+/**
+ * Footer indicator showing remote connection status.
+ * Polls every 5 seconds. Only renders when kilo gateway is connected and remote is enabled.
+ */
+export function RemoteIndicator(props: { sdk: any; theme: any; kilo: boolean }) {
+ const [status, setStatus] = createSignal<{
+ enabled: boolean
+ connected: boolean
+ } | null>(null)
+
+ onMount(() => {
+ const poll = async () => {
+ const res = await props.sdk.client.remote.status().catch(() => null)
+ if (res?.data) setStatus(res.data)
+ }
+ poll()
+ const timer = setInterval(poll, 5000)
+ onCleanup(() => clearInterval(timer))
+ })
+
+ return (
+
+
+ ◆ Remote{status()?.connected ? "" : " …"}
+
+
+ )
+}
diff --git a/packages/opencode/src/kilocode/skills/builtin.ts b/packages/opencode/src/kilocode/skills/builtin.ts
new file mode 100644
index 00000000000..9172a39cf95
--- /dev/null
+++ b/packages/opencode/src/kilocode/skills/builtin.ts
@@ -0,0 +1,21 @@
+// kilocode_change - new file
+// Built-in skills that ship inside the CLI binary.
+// Content is inlined at compile time via Bun's static import of .md files.
+// Registered before all discovery phases so user skills with the same name override.
+
+import KILO_CONFIG from "./kilo-config.md"
+
+export interface BuiltinSkill {
+ name: string
+ description: string
+ content: string
+}
+
+export const BUILTIN_SKILLS: BuiltinSkill[] = [
+ {
+ name: "kilo-config",
+ description:
+ "Guide for configuring Kilo CLI: commands, agents, MCP servers, skills, permissions, instructions, plugins, providers, all kilo.json fields, and TUI settings (themes, appearance, keybinds, ctrl+p commands). Use when the user asks about configuring, customizing, or changing settings in Kilo.",
+ content: KILO_CONFIG,
+ },
+]
diff --git a/packages/opencode/src/kilocode/skills/kilo-config.md b/packages/opencode/src/kilocode/skills/kilo-config.md
new file mode 100644
index 00000000000..70289d08dcd
--- /dev/null
+++ b/packages/opencode/src/kilocode/skills/kilo-config.md
@@ -0,0 +1,211 @@
+# Kilo CLI Configuration Reference
+
+All config lives in `kilo.json` (or `kilo.jsonc`). Precedence low-to-high: remote well-known, global (`~/.config/kilo/kilo.json`), env `KILO_CONFIG`, project `./kilo.json`, `.kilo/kilo.json`, `KILO_CONFIG_CONTENT`, managed (see Config File Locations). Deep-merged; later wins.
+
+## Commands (`.kilo/command/*.md`)
+
+Markdown files with YAML frontmatter. The filename (minus `.md`) becomes the command name invoked via `/name`.
+
+```yaml
+---
+description: Run tests # optional, shown in command list
+agent: code # optional, route to a specific agent
+model: anthropic/claude-sonnet # optional, override model
+subtask: true # optional, run as subtask
+---
+Run all tests in $1 and fix failures.
+Use $ARGUMENTS for the full arg string.
+Reference files with @file and shell output with !`cmd`.
+```
+
+Template variables: `$1`-`$N` (positional args), `$ARGUMENTS` (full string), `@file` (file contents), `` !`cmd` `` (shell output).
+
+## Agents (`.kilo/agent/*.md`)
+
+```yaml
+---
+description: When to use this agent
+mode: primary # primary | subagent | all
+model: anthropic/claude-sonnet # optional override
+steps: 25 # max agentic iterations
+hidden: false # hide from @ menu (subagent only)
+color: "#FF5733" # hex or theme name
+permission: # optional, agent-level permissions
+ bash: allow
+ edit:
+ "src/**": allow
+ "*": ask
+---
+System prompt for this agent.
+```
+
+`mode` values: `primary` = selectable as main agent, `subagent` = only via Task tool, `all` = both.
+
+## Permissions
+
+Scalar form applies to all patterns. Object form maps glob patterns to actions. Evaluated top-to-bottom; first match wins.
+
+```jsonc
+{
+ "permission": {
+ "bash": "allow", // scalar: allow all bash
+ "edit": {
+ // object: pattern-matched
+ "src/**": "allow",
+ "*.lock": "deny",
+ "*": "ask", // fallback
+ },
+ "read": "ask",
+ "skill": { "my-skill": "allow" },
+ "external_directory": "deny",
+ },
+}
+```
+
+Actions: `"allow"`, `"ask"`, `"deny"`. Set `null` to delete an inherited key.
+
+Tool permissions: `read`, `edit`, `glob`, `grep`, `list`, `bash`, `task`, `webfetch`, `websearch`, `codesearch`, `lsp`, `skill`, `external_directory`, `todowrite`, `todoread`, `question`, `doom_loop`.
+
+## MCP Servers
+
+```jsonc
+{
+ "mcp": {
+ "local-server": {
+ "type": "local",
+ "command": ["node", "server.js"],
+ "environment": { "PORT": "3000" },
+ "enabled": true,
+ "timeout": 10000,
+ },
+ "remote-server": {
+ "type": "remote",
+ "url": "https://mcp.example.com",
+ "headers": { "Authorization": "Bearer ..." },
+ "oauth": { "clientId": "...", "scope": "read" },
+ "enabled": true,
+ },
+ },
+}
+```
+
+Disable an inherited server: `{ "server-name": { "enabled": false } }`.
+
+## Providers
+
+```jsonc
+{
+ "provider": {
+ "anthropic": {
+ "options": {
+ "apiKey": "sk-...",
+ "baseURL": "https://custom.endpoint/v1",
+ "timeout": 300000,
+ },
+ "models": {
+ "custom-model": { "name": "My Model" },
+ },
+ "whitelist": ["claude-*"],
+ "blacklist": ["claude-2*"],
+ },
+ },
+ "disabled_providers": ["openai"],
+ "enabled_providers": ["anthropic"],
+}
+```
+
+## Skills
+
+Additional skill directories and remote URLs:
+
+```jsonc
+{
+ "skills": {
+ "paths": ["./my-skills", "~/shared-skills"],
+ "urls": ["https://example.com/.well-known/skills/"],
+ },
+}
+```
+
+Skills are markdown files at `skills//SKILL.md` with `name` and `description` in frontmatter.
+
+## Other Top-Level Fields
+
+| Field | Type | Description |
+| ------------------ | ------------------------------ | --------------------------------------------------- |
+| `model` | `"provider/model"` | Default model |
+| `small_model` | `"provider/model"` | Model for titles/summaries |
+| `default_agent` | `string` | Default primary agent (fallback: `code`) |
+| `instructions` | `string[]` | Glob patterns for additional instruction files |
+| `plugin` | `string[]` | Plugin specifiers (npm packages or `file://` paths) |
+| `snapshot` | `boolean` | Enable git snapshots |
+| `share` | `"manual"\|"auto"\|"disabled"` | Session sharing mode |
+| `autoupdate` | `boolean\|"notify"` | Auto-update behavior |
+| `username` | `string` | Display name override |
+| `compaction.auto` | `boolean` | Auto-compact when context full (default: true) |
+| `compaction.prune` | `boolean` | Prune old tool outputs (default: true) |
+
+## TUI Settings (Ctrl+P Command Palette)
+
+The CLI TUI has runtime settings accessible via `Ctrl+P` (command palette) or slash commands. **These are user-interactive only — the agent cannot change them programmatically.** When users ask to change these settings, tell them which command palette entry, keybind, or slash command to use.
+
+Leader key default: `ctrl+x`. Keybinds below use `` prefix (e.g. `t` = `ctrl+x` then `t`).
+
+### Theme & Appearance
+
+| Action | Keybind | Slash | Notes |
+| ------------------------------ | ----------- | --------- | -------------------------------------------------------------------------------------------------- |
+| Switch theme | `t` | `/themes` | Pick from 35+ built-in themes (kilo, catppuccin, dracula, github, gruvbox, nord, tokyonight, etc.) |
+| Toggle appearance (dark/light) | — | — | Ctrl+P → "Toggle appearance" |
+
+Custom themes: place JSON files in `~/.config/kilo/themes/` or `.kilo/themes/`.
+
+### Session
+
+| Action | Keybind | Slash |
+| ------------------ | ----------- | ------------------------ |
+| List sessions | `l` | `/sessions` |
+| New session | `n` | `/new`, `/clear` |
+| Share session | — | `/share` |
+| Rename session | `ctrl+r` | `/rename` |
+| Jump to message | `g` | `/timeline` |
+| Fork from message | — | `/fork` |
+| Compact/summarize | `c` | `/compact`, `/summarize` |
+| Undo message | `u` | `/undo` |
+| Redo | `r` | `/redo` |
+| Copy last response | `y` | — |
+| Copy transcript | — | `/copy` |
+
+### Agent & Model
+
+| Action | Keybind | Slash |
+| ------------ | ------------------- | --------- |
+| Switch model | `m` | `/models` |
+| Switch agent | `a` | `/agents` |
+| Toggle MCPs | — | `/mcps` |
+| Cycle agent | `tab` / `shift+tab` | — |
+
+### Display Toggles (via Ctrl+P)
+
+Toggle notifications, Toggle animations, Toggle diff wrapping, Toggle sidebar (`b`), Toggle thinking (`/thinking`), Toggle tool details, Toggle timestamps (`/timestamps`), Toggle scrollbar, Toggle header, Toggle code concealment (`h`).
+
+### System
+
+| Action | Slash |
+| ----------- | ---------------------- |
+| View status | `/status` |
+| Help | `/help` |
+| Exit | `/exit`, `/quit`, `/q` |
+| Open editor | `/editor` |
+
+## Config File Locations
+
+| Scope | Path |
+| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Project | `./kilo.json`, `./.kilo/kilo.json` |
+| Global | `~/.config/kilo/kilo.json` |
+| Managed | Linux: `/etc/kilo/kilo.json`, macOS: `/Library/Application Support/kilo/kilo.json`, Windows: `%ProgramData%\kilo\kilo.json` (enterprise, highest priority) |
+| Commands | `.kilo/command/*.md` (project), `~/.config/kilo/command/*.md` (global) |
+| Agents | `.kilo/agent/*.md` (project), `~/.config/kilo/agent/*.md` (global) |
+| Skills | `.kilo/skill/*/SKILL.md`, `.kilo/skills/*/SKILL.md` |
+| Instructions | `AGENTS.md`, `.kilo/instructions.md`, glob patterns from `instructions` |
diff --git a/packages/opencode/src/kilocode/snapshot.ts b/packages/opencode/src/kilocode/snapshot.ts
new file mode 100644
index 00000000000..3790bc370f5
--- /dev/null
+++ b/packages/opencode/src/kilocode/snapshot.ts
@@ -0,0 +1,62 @@
+import { $ } from "bun"
+import path from "path"
+import fs from "fs/promises"
+import { Filesystem } from "../util/filesystem"
+import { Log } from "../util/log"
+import { Global } from "../global"
+import { Instance } from "../project/instance"
+
+const log = Log.create({ service: "snapshot" })
+
+/** Worktree-scoped snapshot git dir (isolated per worktree). */
+export function gitdir() {
+ const project = Instance.project
+ const workhash = Bun.hash(Instance.worktree).toString(36)
+ return path.join(Global.Path.data, "snapshot", project.id, workhash)
+}
+
+/** Original project-scoped snapshot git dir (pre-isolation layout). */
+function legacydir() {
+ const project = Instance.project
+ return path.join(Global.Path.data, "snapshot", project.id)
+}
+
+/** Initialize a bare snapshot git repo if it doesn't exist yet. */
+export async function ensureGit(git: string) {
+ if (!(await fs.mkdir(git, { recursive: true }))) return
+ await $`git init`
+ .env({
+ ...process.env,
+ GIT_DIR: git,
+ GIT_WORK_TREE: Instance.worktree,
+ })
+ .quiet()
+ .nothrow()
+ await $`git --git-dir ${git} config core.autocrlf false`.quiet().nothrow()
+ await $`git --git-dir ${git} config core.longpaths true`.quiet().nothrow()
+ await $`git --git-dir ${git} config core.symlinks true`.quiet().nothrow()
+ await $`git --git-dir ${git} config core.fsmonitor false`.quiet().nothrow()
+ log.info("initialized")
+}
+
+/** Point the worktree repo's alternates at the legacy object store. */
+export async function syncAlternates(git: string) {
+ const legacy = legacydir()
+ if (legacy === git) return
+ const objects = path.join(legacy, "objects")
+ const exists = await fs
+ .stat(objects)
+ .then(() => true)
+ .catch(() => false)
+ const target = path.join(git, "objects", "info", "alternates")
+ await fs.mkdir(path.join(git, "objects", "info"), { recursive: true })
+ await Filesystem.write(target, exists ? objects + "\n" : "")
+}
+
+/** Return a ready-to-use snapshot git dir (init + alternates). */
+export async function prepare() {
+ const git = gitdir()
+ await ensureGit(git)
+ await syncAlternates(git)
+ return git
+}
diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts
index 49607716a3a..9ffbce1a0ba 100644
--- a/packages/opencode/src/mcp/index.ts
+++ b/packages/opencode/src/mcp/index.ts
@@ -3,6 +3,15 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
+// kilocode_change start
+// The MCP SDK only sets windowsHide:true in Electron (checks `'type' in process`).
+// When running inside the VS Code extension on Windows, set process.type so the SDK
+// hides cmd.exe windows when spawning MCP servers. The extension passes KILO_PLATFORM=vscode
+// so we use that to scope this shim to the vscode context only.
+if (process.platform === "win32" && process.env.KILO_PLATFORM === "vscode" && !("type" in process)) {
+ ;(process as NodeJS.Process & { type: string }).type = "browser"
+}
+// kilocode_change end
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
import {
CallToolResultSchema,
diff --git a/packages/opencode/src/md.d.ts b/packages/opencode/src/md.d.ts
new file mode 100644
index 00000000000..eb3e3b92d66
--- /dev/null
+++ b/packages/opencode/src/md.d.ts
@@ -0,0 +1,4 @@
+declare module "*.md" {
+ const content: string
+ export default content
+}
diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts
index 5534572da26..b68fafd1f8a 100644
--- a/packages/opencode/src/permission/next.ts
+++ b/packages/opencode/src/permission/next.ts
@@ -9,6 +9,7 @@ import { fn } from "@/util/fn"
import { Log } from "@/util/log"
import { Wildcard } from "@/util/wildcard"
import { drainCovered } from "@/kilocode/permission/drain" // kilocode_change
+import { ConfigProtection } from "@/kilocode/permission/config-paths" // kilocode_change
import os from "os"
import z from "zod"
@@ -188,18 +189,27 @@ export namespace PermissionNext {
async (input) => {
const s = await state()
const { ruleset, ...request } = input
+ // kilocode_change start — force "ask" for config file edits
+ const protected_ = ConfigProtection.isRequest(request)
+ // kilocode_change end
for (const pattern of request.patterns ?? []) {
const rule = evaluate(request.permission, pattern, ruleset, s.approved)
log.info("evaluated", { permission: request.permission, pattern, action: rule })
if (rule.action === "deny")
throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission)))
- if (rule.action === "ask") {
+ // kilocode_change start — override "allow" to "ask" for config paths
+ if (rule.action === "ask" || (rule.action === "allow" && protected_)) {
const id = input.id ?? Identifier.ascending("permission")
return new Promise((resolve, reject) => {
const info: Request = {
id,
...request,
+ metadata: {
+ ...request.metadata,
+ ...(protected_ ? { [ConfigProtection.DISABLE_ALWAYS_KEY]: true } : {}),
+ },
}
+ // kilocode_change end
s.pending[id] = {
info,
ruleset, // kilocode_change
@@ -227,6 +237,10 @@ export namespace PermissionNext {
const existing = s.pending[input.requestID]
if (!existing) throw new NotFoundError({ message: `Permission request ${input.requestID} not found` })
+ // kilocode_change start — skip rule persistence for config file edits
+ if (ConfigProtection.isRequest(existing.info)) return
+ // kilocode_change end
+
// Combine metadata.rules (bash hierarchy) and always (all tools).
// Set preserves insertion order and deduplicates.
const validRules = new Set([...(existing.info.metadata?.rules ?? []), ...existing.info.always])
@@ -289,6 +303,13 @@ export namespace PermissionNext {
return
}
if (input.reply === "always") {
+ // kilocode_change start — downgrade "always" to "once" for config file edits
+ if (ConfigProtection.isRequest(existing.info)) {
+ existing.resolve()
+ return
+ }
+ // kilocode_change end
+
for (const pattern of existing.info.always) {
s.approved.push({
permission: existing.info.permission,
diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts
index 2751547c675..ceb7db4b7e6 100644
--- a/packages/opencode/src/provider/models.ts
+++ b/packages/opencode/src/provider/models.ts
@@ -8,7 +8,7 @@ import { lazy } from "@/util/lazy"
import { Config } from "../config/config" // kilocode_change
import { ModelCache } from "./model-cache" // kilocode_change
import { Auth } from "../auth" // kilocode_change
-import { KILO_OPENROUTER_BASE } from "@kilocode/kilo-gateway" // kilocode_change
+import { AI_SDK_PROVIDERS, KILO_OPENROUTER_BASE, PROMPTS } from "@kilocode/kilo-gateway" // kilocode_change
import { Filesystem } from "../util/filesystem"
// Try to import bundled snapshot (generated at build time)
@@ -29,7 +29,9 @@ const normalizeKiloBaseURL = (baseURL: string | undefined, orgId: string | undef
return `${trimmed}/api/openrouter`
}
-export const Prompt = z.enum(["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"])
+export const Prompt = z.enum(PROMPTS)
+
+export const AiSdkProvider = z.enum(AI_SDK_PROVIDERS)
// kilocode_change end
export namespace ModelsDev {
@@ -87,6 +89,7 @@ export namespace ModelsDev {
recommendedIndex: z.number().optional(),
prompt: Prompt.optional().catch(undefined),
isFree: z.boolean().optional(),
+ ai_sdk_provider: AiSdkProvider.optional().catch(undefined),
// kilocode_change end
experimental: z.boolean().optional(),
@@ -136,8 +139,15 @@ export namespace ModelsDev {
}
// Inject kilo provider with dynamic model fetching
- if (!providers["kilo"]) {
- const config = await Config.get()
+ // Skip injection entirely when enabled_providers is set and doesn't include "kilo",
+ // or when "kilo" is in disabled_providers. This prevents unnecessary network calls
+ // to the Kilo API for teams using only their own providers (e.g. LiteLLM).
+ const config = await Config.get()
+ const disabled = new Set(config.disabled_providers ?? [])
+ const enabled = config.enabled_providers ? new Set(config.enabled_providers) : null
+ const kiloAllowed = (!enabled || enabled.has("kilo")) && !disabled.has("kilo")
+
+ if (kiloAllowed && !providers["kilo"]) {
const kiloOptions = config.provider?.kilo?.options
// kilocode_change start - resolve org ID from auth (OAuth accountId) not just config
const kiloAuth = await Auth.get("kilo")
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 61af731828c..f26f78426a3 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -9,7 +9,7 @@ import { BunProc } from "../bun"
import { Hash } from "../util/hash"
import { Plugin } from "../plugin"
import { NamedError } from "@opencode-ai/util/error"
-import { ModelsDev, Prompt } from "./models" // kilocode_change
+import { AiSdkProvider, ModelsDev, Prompt } from "./models" // kilocode_change
import { Auth } from "../auth"
import { Env } from "../env"
import { Instance } from "../project/instance"
@@ -30,7 +30,7 @@ import { createOpenAI } from "@ai-sdk/openai"
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
import { createOpenRouter, type LanguageModelV2 } from "@openrouter/ai-sdk-provider"
import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from "./sdk/copilot"
-import { createKilo } from "@kilocode/kilo-gateway" // kilocode_change
+import { createKilo, type KiloProvider } from "@kilocode/kilo-gateway" // kilocode_change
import { createXai } from "@ai-sdk/xai"
import { createMistral } from "@ai-sdk/mistral"
import { createGroq } from "@ai-sdk/groq"
@@ -616,6 +616,19 @@ export namespace Provider {
return {
autoload: Object.keys(input.models).length > 0,
options,
+ async getModel(sdk: KiloProvider, modelID: string) {
+ const aiSdkProvider = input.models[modelID]?.ai_sdk_provider
+ if (aiSdkProvider === "anthropic") {
+ return sdk.anthropic(modelID)
+ }
+ if (aiSdkProvider === "openai") {
+ return sdk.openai(modelID)
+ }
+ if (aiSdkProvider === "openai-compatible") {
+ return sdk.openaiCompatible(modelID)
+ }
+ return sdk.languageModel(modelID)
+ },
}
},
// kilocode_change end
@@ -691,6 +704,7 @@ export namespace Provider {
recommendedIndex: z.number().optional(),
prompt: Prompt.optional().catch(undefined),
isFree: z.boolean().optional(),
+ ai_sdk_provider: AiSdkProvider.optional(),
// kilocode_change end
})
.meta({
@@ -778,6 +792,7 @@ export namespace Provider {
recommendedIndex: model.recommendedIndex,
prompt: model.prompt,
isFree: model.isFree,
+ ai_sdk_provider: model.ai_sdk_provider,
// kilocode_change end
}
@@ -927,6 +942,7 @@ export namespace Provider {
recommendedIndex: model.recommendedIndex ?? existingModel?.recommendedIndex,
prompt: model.prompt ?? existingModel?.prompt,
isFree: model.isFree ?? existingModel?.isFree,
+ ai_sdk_provider: model.ai_sdk_provider ?? existingModel?.ai_sdk_provider,
// kilocode_change end
}
const merged = mergeDeep(ProviderTransform.variants(parsedModel), model.variants ?? {})
diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts
index 4a0d9dd1500..9e1ba8290a8 100644
--- a/packages/opencode/src/provider/transform.ts
+++ b/packages/opencode/src/provider/transform.ts
@@ -6,6 +6,7 @@ import type { Provider } from "./provider"
import type { ModelsDev } from "./models"
import { iife } from "@/util/iife"
import { Flag } from "@/flag/flag"
+import { kiloProviderOptions } from "@/kilocode/provider-options"
type Modality = NonNullable["input"][number]
@@ -936,6 +937,12 @@ export namespace ProviderTransform {
return result
}
+ // kilocode_change start
+ if (model.api.npm === "@kilocode/kilo-gateway") {
+ return kiloProviderOptions(options)
+ }
+ // kilocode_change end
+
const key = sdkKey(model.api.npm) ?? model.providerID
return { [key]: options }
}
diff --git a/packages/opencode/src/server/routes/config.ts b/packages/opencode/src/server/routes/config.ts
index e6ac26bb0dc..f9ac7e42976 100644
--- a/packages/opencode/src/server/routes/config.ts
+++ b/packages/opencode/src/server/routes/config.ts
@@ -89,10 +89,16 @@ export const ConfigRoutes = lazy(() =>
const providers = await Provider.list()
// kilocode_change start - Fetch default model from Kilo API
- const kiloAuth = await Auth.get("kilo")
- const token = kiloAuth?.type === "oauth" ? kiloAuth.access : kiloAuth?.key
- const organizationId = kiloAuth?.type === "oauth" ? kiloAuth.accountId : undefined
- const kiloApiDefault = await fetchDefaultModel(token, organizationId)
+ // Only call the Kilo API when the kilo provider is actually available.
+ // This prevents unnecessary network calls for teams using only their
+ // own providers (e.g. LiteLLM) via enabled_providers config.
+ let kiloApiDefault: string | undefined
+ if (providers["kilo"]) {
+ const kiloAuth = await Auth.get("kilo")
+ const token = kiloAuth?.type === "oauth" ? kiloAuth.access : kiloAuth?.key
+ const organizationId = kiloAuth?.type === "oauth" ? kiloAuth.accountId : undefined
+ kiloApiDefault = await fetchDefaultModel(token, organizationId)
+ }
// kilocode_change end
// kilocode_change start - Use API default for Kilo provider if valid
diff --git a/packages/opencode/src/server/routes/remote.ts b/packages/opencode/src/server/routes/remote.ts
new file mode 100644
index 00000000000..79bde73bb85
--- /dev/null
+++ b/packages/opencode/src/server/routes/remote.ts
@@ -0,0 +1,84 @@
+// kilocode_change - new file
+import { Hono } from "hono"
+import { describeRoute, resolver } from "hono-openapi"
+import z from "zod"
+import { KiloSessions } from "@/kilo-sessions/kilo-sessions"
+import { lazy } from "../../util/lazy"
+
+const Status = z.object({
+ enabled: z.boolean(),
+ connected: z.boolean(),
+})
+
+export const RemoteRoutes = lazy(() =>
+ new Hono()
+ .post(
+ "/enable",
+ describeRoute({
+ summary: "Enable remote connection",
+ description: "Enable WebSocket connection to UserConnectionDO for real-time session relay and commands.",
+ operationId: "remote.enable",
+ responses: {
+ 200: {
+ description: "Remote connection enabled",
+ content: {
+ "application/json": {
+ schema: resolver(Status),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => {
+ try {
+ await KiloSessions.enableRemote()
+ } catch (err) {
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 401)
+ }
+ return c.json(KiloSessions.remoteStatus())
+ },
+ )
+ .post(
+ "/disable",
+ describeRoute({
+ summary: "Disable remote connection",
+ description: "Close the remote WebSocket connection to UserConnectionDO.",
+ operationId: "remote.disable",
+ responses: {
+ 200: {
+ description: "Remote connection disabled",
+ content: {
+ "application/json": {
+ schema: resolver(Status),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => {
+ KiloSessions.disableRemote()
+ return c.json(KiloSessions.remoteStatus())
+ },
+ )
+ .get(
+ "/status",
+ describeRoute({
+ summary: "Get remote connection status",
+ description: "Get the current state of the remote WebSocket connection.",
+ operationId: "remote.status",
+ responses: {
+ 200: {
+ description: "Remote connection status",
+ content: {
+ "application/json": {
+ schema: resolver(Status),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => {
+ return c.json(KiloSessions.remoteStatus())
+ },
+ ),
+)
diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts
index 12938aeaba0..7a6d9bd6a1f 100644
--- a/packages/opencode/src/server/routes/session.ts
+++ b/packages/opencode/src/server/routes/session.ts
@@ -968,5 +968,25 @@ export const SessionRoutes = lazy(() =>
})
return c.json(true)
},
+ )
+ .post(
+ "/viewed",
+ describeRoute({
+ summary: "Set viewed session",
+ description: "Notify the server which session the user is currently viewing, or clear it.",
+ operationId: "session.viewed",
+ responses: {
+ 200: {
+ description: "Viewed session updated",
+ content: { "application/json": { schema: resolver(z.boolean()) } },
+ },
+ },
+ }),
+ validator("json", z.object({ sessionID: z.string().optional() })),
+ async (c) => {
+ const { KiloSessions } = await import("../../kilo-sessions/kilo-sessions")
+ await KiloSessions.setViewedSession(c.req.valid("json").sessionID)
+ return c.json(true)
+ },
),
)
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index b5b224e7400..c5cb6490d8f 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -51,6 +51,7 @@ import { KilocodeRoutes } from "./routes/kilocode" // kilocode_change
import { Filesystem } from "@/util/filesystem"
import { QuestionRoutes } from "./routes/question"
import { PermissionRoutes } from "./routes/permission"
+import { RemoteRoutes } from "./routes/remote" // kilocode_change
import { GlobalRoutes } from "./routes/global"
import { MDNS } from "./mdns"
@@ -270,6 +271,7 @@ export namespace Server {
.route("/question", QuestionRoutes())
.route("/provider", ProviderRoutes())
.route("/telemetry", TelemetryRoutes()) // kilocode_change
+ .route("/remote", RemoteRoutes()) // kilocode_change
.route("/commit-message", CommitMessageRoutes()) // kilocode_change
.route("/enhance-prompt", EnhancePromptRoutes()) // kilocode_change
.route("/kilocode", KilocodeRoutes()) // kilocode_change
diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts
index e468d1f2604..1e84d3433e6 100644
--- a/packages/opencode/src/session/message-v2.ts
+++ b/packages/opencode/src/session/message-v2.ts
@@ -503,6 +503,62 @@ export namespace MessageV2 {
})
export type WithParts = z.infer
+ // kilocode_change start - strip bloated metadata fields from stored parts to prevent multi-MB payloads
+ // This handles both legacy data that was stored with full file contents and keeps the API response lean.
+ function stripPartMetadata(part: Part): Part {
+ if (part.type !== "tool") return part
+ const { state } = part
+ if (state.status !== "completed" && state.status !== "running") return part
+ const meta = state.metadata
+ if (!meta) return part
+
+ let changed = false
+ let next = meta
+
+ // Strip edit tool's filediff.before/after (full file contents)
+ if (meta.filediff && (meta.filediff.before || meta.filediff.after)) {
+ const { before, after, ...rest } = meta.filediff
+ next = { ...next, filediff: rest }
+ changed = true
+ }
+
+ // Strip apply_patch tool's files[].before/after (full file contents per file)
+ if (Array.isArray(meta.files) && meta.files.length > 0 && meta.files[0]?.before !== undefined) {
+ next = {
+ ...next,
+ files: meta.files.map((f: Record) => {
+ const { before, after, ...rest } = f
+ return rest
+ }),
+ }
+ changed = true
+ }
+
+ if (!changed) return part
+ return { ...part, state: { ...state, metadata: next } } as Part
+ }
+
+ function stripMessageMetadata(info: Info): Info {
+ // Strip summary.diffs before/after from user messages (can be 20+ MB)
+ if (info.role !== "user") return info
+ const user = info as User
+ if (!user.summary?.diffs?.length) return info
+ const has = user.summary.diffs.some((d: Snapshot.FileDiff) => d.before || d.after)
+ if (!has) return info
+ return {
+ ...user,
+ summary: {
+ ...user.summary,
+ diffs: user.summary.diffs.map(({ before, after, ...rest }: Snapshot.FileDiff) => ({
+ ...rest,
+ before: "",
+ after: "",
+ })),
+ },
+ } as Info
+ }
+ // kilocode_change end
+
export function toModelMessages(
input: WithParts[],
model: Provider.Model,
@@ -766,12 +822,12 @@ export namespace MessageV2 {
.all(),
)
for (const row of partRows) {
- const part = {
+ const part = stripPartMetadata({
...row.data,
id: row.id,
sessionID: row.session_id,
messageID: row.message_id,
- } as MessageV2.Part
+ } as MessageV2.Part) // kilocode_change - strip bloated metadata on read
const list = partsByMessage.get(row.message_id)
if (list) list.push(part)
else partsByMessage.set(row.message_id, [part])
@@ -779,7 +835,7 @@ export namespace MessageV2 {
}
for (const row of rows) {
- const info = { ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info
+ const info = stripMessageMetadata({ ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info) // kilocode_change
yield {
info,
parts: partsByMessage.get(row.id) ?? [],
@@ -796,7 +852,13 @@ export namespace MessageV2 {
db.select().from(PartTable).where(eq(PartTable.message_id, message_id)).orderBy(PartTable.id).all(),
)
return rows.map(
- (row) => ({ ...row.data, id: row.id, sessionID: row.session_id, messageID: row.message_id }) as MessageV2.Part,
+ (row) =>
+ stripPartMetadata({
+ ...row.data,
+ id: row.id,
+ sessionID: row.session_id,
+ messageID: row.message_id,
+ } as MessageV2.Part), // kilocode_change - strip bloated metadata on read
)
})
@@ -808,7 +870,7 @@ export namespace MessageV2 {
async (input): Promise => {
const row = Database.use((db) => db.select().from(MessageTable).where(eq(MessageTable.id, input.messageID)).get())
if (!row) throw new Error(`Message not found: ${input.messageID}`)
- const info = { ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info
+ const info = stripMessageMetadata({ ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info) // kilocode_change
return {
info,
parts: await parts(input.messageID),
diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts
index 585facce3d4..abf1707f4b6 100644
--- a/packages/opencode/src/session/processor.ts
+++ b/packages/opencode/src/session/processor.ts
@@ -16,6 +16,7 @@ import { SessionCompaction } from "./compaction"
import { PermissionNext } from "@/permission/next"
import { Question } from "@/question"
import { Telemetry } from "@kilocode/kilo-telemetry" // kilocode_change
+import { Flag } from "@/flag/flag" // kilocode_change
export namespace SessionProcessor {
const DOOM_LOOP_THRESHOLD = 3
@@ -387,7 +388,11 @@ export namespace SessionProcessor {
})
} else {
const retry = SessionRetry.retryable(error)
- if (retry !== undefined) {
+ if (
+ retry !== undefined &&
+ (Flag.KILO_SESSION_RETRY_LIMIT === undefined || attempt < Flag.KILO_SESSION_RETRY_LIMIT)
+ ) {
+ // kilocode_change
attempt++
const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
SessionStatus.set(input.sessionID, {
@@ -438,6 +443,13 @@ export namespace SessionProcessor {
})
}
}
+ // kilocode_change start — guard empty tool-calls (#7756)
+ const empty = input.assistantMessage.finish === "tool-calls" && !p.some((part) => part.type === "tool")
+ if (empty) {
+ log.warn("empty tool-calls", { messageID: input.assistantMessage.id })
+ input.assistantMessage.finish = "stop"
+ }
+ // kilocode_change end
input.assistantMessage.time.completed = Date.now()
await Session.updateMessage(input.assistantMessage)
if (needsCompaction) return "compact"
diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts
index 7eb8d2fa71f..ec27107d917 100644
--- a/packages/opencode/src/session/retry.ts
+++ b/packages/opencode/src/session/retry.ts
@@ -67,8 +67,11 @@ export namespace SessionRetry {
if (isKiloError(error)) return undefined
// kilocode_change end
if (!error.data.isRetryable) return undefined
- if (error.data.responseBody?.includes("FreeUsageLimitError"))
- return `Free usage exceeded, add credits https://app.kilo.ai` // kilocode_change
+ // kilocode_change start - FreeUsageLimitError is not retryable: retrying the same
+ // capped model is futile and the backoff loop cannot be broken by switching
+ // models in the chat selector (the retry loop holds a stale model ref).
+ if (error.data.responseBody?.includes("FreeUsageLimitError")) return undefined
+ // kilocode_change end
return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message
}
diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts
index f724067f665..288e5f22c2b 100644
--- a/packages/opencode/src/session/summary.ts
+++ b/packages/opencode/src/session/summary.ts
@@ -121,16 +121,23 @@ export namespace SessionSummary {
}),
async (input) => {
const diffs = await Storage.read(["session_diff", input.sessionID]).catch(() => [])
+ // kilocode_change start — scrub oversized diffs from stored session_diff
const next = diffs.map((item) => {
const file = unquoteGitPath(item.file)
- if (file === item.file) return item
+ const oversized =
+ Buffer.byteLength(item.before) > Snapshot.MAX_DIFF_SIZE ||
+ Buffer.byteLength(item.after) > Snapshot.MAX_DIFF_SIZE
+ if (file === item.file && !oversized) return item
return {
...item,
file,
+ before: oversized ? "" : item.before,
+ after: oversized ? "" : item.after,
}
})
- const changed = next.some((item, i) => item.file !== diffs[i]?.file)
+ const changed = next.some((item, i) => item !== diffs[i])
if (changed) Storage.write(["session_diff", input.sessionID], next).catch(() => {})
+ // kilocode_change end
return next
},
)
diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts
index d4c1d97144b..54ef122a721 100644
--- a/packages/opencode/src/skill/skill.ts
+++ b/packages/opencode/src/skill/skill.ts
@@ -16,6 +16,7 @@ import { Discovery } from "./discovery"
import { Glob } from "../util/glob"
import { KilocodePaths } from "../kilocode/paths" // kilocode_change
+import { BUILTIN_SKILLS } from "../kilocode/skills/builtin" // kilocode_change
export namespace Skill {
const log = Log.create({ service: "skill" })
@@ -52,10 +53,23 @@ export namespace Skill {
const KILO_SKILL_PATTERN = "{skill,skills}/**/SKILL.md"
const SKILL_PATTERN = "**/SKILL.md"
+ export const BUILTIN_LOCATION = "builtin" // kilocode_change
+
export const state = Instance.state(async () => {
const skills: Record = {}
const dirs = new Set()
+ // kilocode_change start - Register built-in skills first (lowest precedence, any external skill with same name overrides)
+ for (const skill of BUILTIN_SKILLS) {
+ skills[skill.name] = {
+ name: skill.name,
+ description: skill.description,
+ location: BUILTIN_LOCATION,
+ content: skill.content,
+ }
+ }
+ // kilocode_change end
+
const addSkill = async (match: string) => {
const md = await ConfigMarkdown.parse(match).catch((err) => {
const message = ConfigMarkdown.FrontmatterError.isInstance(err)
@@ -225,12 +239,18 @@ export namespace Skill {
)
export async function remove(location: string) {
+ if (location === BUILTIN_LOCATION) {
+ throw new RemoveError({ location, message: "cannot remove built-in skill" })
+ }
const resolved = path.resolve(location)
const s = await state()
const name = Object.keys(s.skills).find((k) => path.resolve(s.skills[k].location) === resolved)
if (!name) {
throw new RemoveError({ location: resolved, message: "skill not found in registry" })
}
+ if (s.skills[name].location === BUILTIN_LOCATION) {
+ throw new RemoveError({ location, message: "cannot remove built-in skill" })
+ }
const dir = path.dirname(resolved)
await rm(dir, { recursive: true, force: true })
delete s.skills[name]
diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts
index 9aade734eee..92b6317eeec 100644
--- a/packages/opencode/src/snapshot/index.ts
+++ b/packages/opencode/src/snapshot/index.ts
@@ -9,11 +9,13 @@ import z from "zod"
import { Config } from "../config/config"
import { Instance } from "../project/instance"
import { Scheduler } from "../scheduler"
+import * as KiloSnapshot from "../kilocode/snapshot" // kilocode_change
export namespace Snapshot {
const log = Log.create({ service: "snapshot" })
const hour = 60 * 60 * 1000
const prune = "7.days"
+ export const MAX_DIFF_SIZE = 256 * 1024 // kilocode_change
export function init() {
Scheduler.register({
@@ -34,10 +36,11 @@ export namespace Snapshot {
.then(() => true)
.catch(() => false)
if (!exists) return
- const result = await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} gc --prune=${prune}`
- .quiet()
- .cwd(Instance.directory)
- .nothrow()
+ const result =
+ await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} gc --prune=${prune}`
+ .quiet()
+ .cwd(Instance.directory)
+ .nothrow()
if (result.exitCode !== 0) {
log.warn("cleanup failed", {
exitCode: result.exitCode,
@@ -53,23 +56,7 @@ export namespace Snapshot {
if (Instance.project.vcs !== "git" || Flag.KILO_CLIENT === "acp") return
const cfg = await Config.get()
if (cfg.snapshot === false) return
- const git = gitdir()
- if (await fs.mkdir(git, { recursive: true })) {
- await $`git init`
- .env({
- ...process.env,
- GIT_DIR: git,
- GIT_WORK_TREE: Instance.worktree,
- })
- .quiet()
- .nothrow()
- // Configure git to not convert line endings on Windows
- await $`git --git-dir ${git} config core.autocrlf false`.quiet().nothrow()
- await $`git --git-dir ${git} config core.longpaths true`.quiet().nothrow()
- await $`git --git-dir ${git} config core.symlinks true`.quiet().nothrow()
- await $`git --git-dir ${git} config core.fsmonitor false`.quiet().nothrow()
- log.info("initialized")
- }
+ const git = await KiloSnapshot.prepare() // kilocode_change
await add(git)
const hash = await $`git --git-dir ${git} --work-tree ${Instance.worktree} write-tree`
.quiet()
@@ -87,7 +74,7 @@ export namespace Snapshot {
export type Patch = z.infer
export async function patch(hash: string): Promise {
- const git = gitdir()
+ const git = await KiloSnapshot.prepare() // kilocode_change
await add(git)
const result =
await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --name-only ${hash} -- .`
@@ -115,7 +102,7 @@ export namespace Snapshot {
export async function restore(snapshot: string) {
log.info("restore", { commit: snapshot })
- const git = gitdir()
+ const git = await KiloSnapshot.prepare() // kilocode_change
const result =
await $`git -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} read-tree ${snapshot} && git -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} checkout-index -a -f`
.quiet()
@@ -134,8 +121,8 @@ export namespace Snapshot {
export async function revert(patches: Patch[]) {
const files = new Set()
- const git = gitdir()
for (const item of patches) {
+ const git = await KiloSnapshot.prepare() // kilocode_change
for (const file of item.files) {
if (files.has(file)) continue
log.info("reverting", { file, hash: item.hash })
@@ -166,7 +153,7 @@ export namespace Snapshot {
}
export async function diff(hash: string) {
- const git = gitdir()
+ const git = await KiloSnapshot.prepare() // kilocode_change
await add(git)
const result =
await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff ${hash} -- .`
@@ -201,7 +188,7 @@ export namespace Snapshot {
})
export type FileDiff = z.infer
export async function diffFull(from: string, to: string): Promise {
- const git = gitdir()
+ const git = await KiloSnapshot.prepare() // kilocode_change
const result: FileDiff[] = []
const status = new Map()
@@ -228,13 +215,22 @@ export namespace Snapshot {
if (!line) continue
const [additions, deletions, file] = line.split("\t")
const isBinaryFile = additions === "-" && deletions === "-"
- const before = isBinaryFile
+ // kilocode_change start
+ const oversized =
+ !isBinaryFile &&
+ ((parseInt(await $`git --git-dir ${git} cat-file -s ${from}:${file}`.quiet().nothrow().text()) || 0) >
+ MAX_DIFF_SIZE ||
+ (parseInt(await $`git --git-dir ${git} cat-file -s ${to}:${file}`.quiet().nothrow().text()) || 0) >
+ MAX_DIFF_SIZE)
+ const skip = isBinaryFile || oversized
+ // kilocode_change end
+ const before = skip
? ""
: await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} show ${from}:${file}`
.quiet()
.nothrow()
.text()
- const after = isBinaryFile
+ const after = skip
? ""
: await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} show ${to}:${file}`
.quiet()
@@ -255,8 +251,7 @@ export namespace Snapshot {
}
function gitdir() {
- const project = Instance.project
- return path.join(Global.Path.data, "snapshot", project.id)
+ return KiloSnapshot.gitdir() // kilocode_change
}
async function add(git: string) {
diff --git a/packages/opencode/src/tool/skill.ts b/packages/opencode/src/tool/skill.ts
index 8fcfb592dee..b092c6e8bfb 100644
--- a/packages/opencode/src/tool/skill.ts
+++ b/packages/opencode/src/tool/skill.ts
@@ -7,6 +7,8 @@ import { PermissionNext } from "../permission/next"
import { Ripgrep } from "../file/ripgrep"
import { iife } from "@/util/iife"
+const BUILTIN = Skill.BUILTIN_LOCATION // kilocode_change
+
export const SkillTool = Tool.define("skill", async (ctx) => {
const skills = await Skill.all()
@@ -35,13 +37,18 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
"Invoke this tool to load a skill when a task matches one of the available skills listed below:",
"",
"",
- ...accessibleSkills.flatMap((skill) => [
- ` `,
- ` ${skill.name} `,
- ` ${skill.description} `,
- ` ${pathToFileURL(skill.location).href} `,
- ` `,
- ]),
+ // kilocode_change start - guard pathToFileURL for builtin skills
+ ...accessibleSkills.flatMap((skill) => {
+ const loc = skill.location === BUILTIN ? BUILTIN : pathToFileURL(skill.location).href
+ return [
+ ` `,
+ ` ${skill.name} `,
+ ` ${skill.description} `,
+ ` ${loc} `,
+ ` `,
+ ]
+ }),
+ // kilocode_change end
" ",
].join("\n")
@@ -73,6 +80,25 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
metadata: {},
})
+ // kilocode_change start - built-in skills have no filesystem directory
+ if (skill.location === BUILTIN) {
+ return {
+ title: `Loaded skill: ${skill.name}`,
+ output: [
+ ``,
+ `# Skill: ${skill.name}`,
+ "",
+ skill.content.trim(),
+ " ",
+ ].join("\n"),
+ metadata: {
+ name: skill.name,
+ dir: BUILTIN,
+ },
+ }
+ }
+ // kilocode_change end
+
const dir = path.dirname(skill.location)
const base = pathToFileURL(dir).href
diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts
index 8c8cf827aba..7b7eafdcb84 100644
--- a/packages/opencode/src/tool/task.ts
+++ b/packages/opencode/src/tool/task.ts
@@ -61,7 +61,7 @@ export const TaskTool = Tool.define("task", async (ctx) => {
const agent = await Agent.get(params.subagent_type)
if (!agent) throw new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`)
- const hasTaskPermission = agent.permission.some((rule) => rule.permission === "task")
+ const allowsTask = agent.permission.some((rule) => rule.permission === "task" && rule.action === "allow") // kilocode_change
const session = await iife(async () => {
if (params.task_id) {
@@ -83,7 +83,7 @@ export const TaskTool = Tool.define("task", async (ctx) => {
pattern: "*",
action: "deny",
},
- ...(hasTaskPermission
+ ...(allowsTask
? []
: [
{
@@ -136,7 +136,7 @@ export const TaskTool = Tool.define("task", async (ctx) => {
tools: {
todowrite: false,
todoread: false,
- ...(hasTaskPermission ? {} : { task: false }),
+ ...(allowsTask ? {} : { task: false }),
...Object.fromEntries((config.experimental?.primary_tools ?? []).map((t) => [t, false])),
},
parts: promptParts,
diff --git a/packages/opencode/test/kilo-sessions/kilo-sessions-enable-remote.test.ts b/packages/opencode/test/kilo-sessions/kilo-sessions-enable-remote.test.ts
new file mode 100644
index 00000000000..f59946a8c7e
--- /dev/null
+++ b/packages/opencode/test/kilo-sessions/kilo-sessions-enable-remote.test.ts
@@ -0,0 +1,202 @@
+import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"
+import { tmpdir } from "../fixture/fixture"
+
+const state = {
+ connects: 0,
+ closes: 0,
+ disposes: 0,
+ userStatus: 200,
+ gate: Promise.resolve(),
+ userError: false,
+}
+
+mock.module("../../src/auth", () => ({
+ OAUTH_DUMMY_KEY: "opencode-oauth-dummy-key",
+ Auth: {
+ get: async () => ({ type: "api", key: "tok" }),
+ },
+}))
+
+mock.module("../../src/project/vcs", () => ({
+ Vcs: {
+ branch: async () => "main",
+ },
+}))
+
+mock.module("simple-git", () => ({
+ default: () => ({
+ remote: async () => "origin\thttps://example.com/repo.git (fetch)",
+ }),
+}))
+
+mock.module("../../src/kilo-sessions/remote-sender", () => ({
+ RemoteSender: {
+ create: () => ({
+ handle() {},
+ dispose() {
+ state.disposes += 1
+ },
+ }),
+ },
+}))
+
+mock.module("../../src/kilo-sessions/remote-ws", () => ({
+ RemoteWS: {
+ connect: () => {
+ state.connects += 1
+ return {
+ connectionId: `conn-${state.connects}`,
+ send() {},
+ close() {
+ state.closes += 1
+ },
+ get connected() {
+ return true
+ },
+ }
+ },
+ },
+}))
+
+describe("KiloSessions.enableRemote", () => {
+ beforeEach(() => {
+ state.connects = 0
+ state.closes = 0
+ state.disposes = 0
+ state.userStatus = 200
+ state.gate = Promise.resolve()
+ state.userError = false
+ process.env["KILO_DISABLE_SESSION_INGEST"] = "0"
+ delete process.env["KILO_SESSION_INGEST_URL"]
+ globalThis.fetch = mock(async (input) => {
+ await state.gate
+ if (String(input).endsWith("/api/user")) {
+ if (state.userError) throw new Error("network down")
+ return new Response(null, { status: state.userStatus })
+ }
+ return new Response(null, { status: 200 })
+ }) as unknown as typeof fetch
+ })
+
+ afterEach(async () => {
+ const { KiloSessions } = await import("../../src/kilo-sessions/kilo-sessions")
+ KiloSessions.disableRemote()
+ })
+
+ test("concurrent enableRemote shares one connection", async () => {
+ await using tmp = await tmpdir({ git: true })
+ const { Instance } = await import("../../src/project/instance")
+
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const { KiloSessions } = await import("../../src/kilo-sessions/kilo-sessions")
+ await Promise.all([KiloSessions.enableRemote(), KiloSessions.enableRemote(), KiloSessions.enableRemote()])
+ expect(state.connects).toBe(1)
+ expect(KiloSessions.remoteStatus()).toEqual({ enabled: true, connected: true })
+ },
+ })
+ })
+
+ test("enableRemote fails when token is invalid", async () => {
+ state.userStatus = 401
+ await using tmp = await tmpdir({ git: true })
+ const { Instance } = await import("../../src/project/instance")
+ const { KiloSessions } = await import("../../src/kilo-sessions/kilo-sessions")
+ const key = "kilo-sessions:token-valid:tok"
+ const { clearInFlightCache } = await import("../../src/kilo-sessions/inflight-cache")
+
+ KiloSessions.disableRemote()
+ clearInFlightCache(key)
+
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ await expect(KiloSessions.enableRemote()).rejects.toThrow(
+ "Unable to enable remote: invalid or expired Kilo credentials. Run `kilo auth login`.",
+ )
+ expect(state.connects).toBe(0)
+ expect(KiloSessions.remoteStatus()).toEqual({ enabled: false, connected: false })
+ },
+ })
+ })
+
+ test("disableRemote cancels in-flight enableRemote", async () => {
+ let release = () => {}
+ state.gate = new Promise((resolve) => {
+ release = resolve
+ })
+
+ await using tmp = await tmpdir({ git: true })
+ const { Instance } = await import("../../src/project/instance")
+ const { clearInFlightCache } = await import("../../src/kilo-sessions/inflight-cache")
+
+ clearInFlightCache("kilo-sessions:token-valid:tok")
+
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const { KiloSessions } = await import("../../src/kilo-sessions/kilo-sessions")
+ const pending = KiloSessions.enableRemote()
+ KiloSessions.disableRemote()
+ release()
+ await pending
+ expect(state.connects).toBe(1)
+ expect(state.disposes).toBe(1)
+ expect(state.closes).toBe(1)
+ expect(KiloSessions.remoteStatus()).toEqual({ enabled: false, connected: false })
+ },
+ })
+ })
+
+ test("transient auth check failure is retryable and does not connect", async () => {
+ state.userError = true
+ await using tmp = await tmpdir({ git: true })
+ const { Instance } = await import("../../src/project/instance")
+ const { KiloSessions } = await import("../../src/kilo-sessions/kilo-sessions")
+ const { clearInFlightCache } = await import("../../src/kilo-sessions/inflight-cache")
+
+ KiloSessions.disableRemote()
+ clearInFlightCache("kilo-sessions:token-valid:tok")
+
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ await expect(KiloSessions.enableRemote()).rejects.toThrow(
+ "Unable to enable remote: failed to verify Kilo credentials.",
+ )
+ expect(state.connects).toBe(0)
+ expect(KiloSessions.remoteStatus()).toEqual({ enabled: false, connected: false })
+ },
+ })
+ })
+
+ test("disable then re-enable replaces stale pending connection", async () => {
+ let release = () => {}
+ state.gate = new Promise((resolve) => {
+ release = resolve
+ })
+
+ await using tmp = await tmpdir({ git: true })
+ const { Instance } = await import("../../src/project/instance")
+ const { clearInFlightCache } = await import("../../src/kilo-sessions/inflight-cache")
+
+ clearInFlightCache("kilo-sessions:token-valid:tok")
+
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const { KiloSessions } = await import("../../src/kilo-sessions/kilo-sessions")
+ const first = KiloSessions.enableRemote()
+ KiloSessions.disableRemote()
+ const second = KiloSessions.enableRemote()
+ release()
+ await Promise.all([first, second])
+ expect(state.connects).toBe(2)
+ expect(state.disposes).toBe(1)
+ expect(state.closes).toBe(1)
+ expect(KiloSessions.remoteStatus()).toEqual({ enabled: true, connected: true })
+ },
+ })
+ })
+})
diff --git a/packages/opencode/test/kilo-sessions/remote-protocol.test.ts b/packages/opencode/test/kilo-sessions/remote-protocol.test.ts
new file mode 100644
index 00000000000..f61f3f40886
--- /dev/null
+++ b/packages/opencode/test/kilo-sessions/remote-protocol.test.ts
@@ -0,0 +1,240 @@
+import { describe, expect, test } from "bun:test"
+import { RemoteProtocol } from "../../src/kilo-sessions/remote-protocol"
+
+describe("RemoteProtocol", () => {
+ // --- Outbound (CLI → DO) ---
+
+ test("valid heartbeat parses", () => {
+ const msg = {
+ type: "heartbeat",
+ sessions: [{ id: "ses_1", status: "busy", title: "Fix auth" }],
+ }
+ const result = RemoteProtocol.Heartbeat.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.sessions).toHaveLength(1)
+ expect(result.data.sessions[0].id).toBe("ses_1")
+ }
+ })
+
+ test("heartbeat with parentSessionId parses", () => {
+ const msg = {
+ type: "heartbeat",
+ sessions: [{ id: "ses_child", status: "busy", title: "Sub task", parentSessionId: "ses_root" }],
+ }
+ const result = RemoteProtocol.Heartbeat.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.sessions[0].parentSessionId).toBe("ses_root")
+ }
+ })
+
+ test("valid event parses", () => {
+ const msg = {
+ type: "event",
+ sessionId: "ses_1",
+ event: "message.updated",
+ data: { text: "hello" },
+ }
+ const result = RemoteProtocol.Event.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.sessionId).toBe("ses_1")
+ expect(result.data.event).toBe("message.updated")
+ }
+ })
+
+ test("valid response parses", () => {
+ const msg = { type: "response", id: "req_1", result: { ok: true } }
+ const result = RemoteProtocol.Response.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.id).toBe("req_1")
+ expect(result.data.result).toEqual({ ok: true })
+ expect(result.data.error).toBeUndefined()
+ }
+ })
+
+ test("response with error parses", () => {
+ const msg = { type: "response", id: "req_2", error: "not found" }
+ const result = RemoteProtocol.Response.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.error).toBe("not found")
+ expect(result.data.result).toBeUndefined()
+ }
+ })
+
+ // --- Inbound (DO → CLI) ---
+
+ test("valid subscribe parses", () => {
+ const msg = { type: "subscribe", sessionId: "ses_1" }
+ const result = RemoteProtocol.Subscribe.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.sessionId).toBe("ses_1")
+ }
+ })
+
+ test("valid unsubscribe parses", () => {
+ const msg = { type: "unsubscribe", sessionId: "ses_1" }
+ const result = RemoteProtocol.Unsubscribe.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.sessionId).toBe("ses_1")
+ }
+ })
+
+ test("valid command parses", () => {
+ const msg = {
+ type: "command",
+ id: "cmd_1",
+ command: "send_message",
+ sessionId: "ses_1",
+ data: { text: "hi" },
+ }
+ const result = RemoteProtocol.Command.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.id).toBe("cmd_1")
+ expect(result.data.command).toBe("send_message")
+ expect(result.data.sessionId).toBe("ses_1")
+ }
+ })
+
+ test("command without sessionId parses", () => {
+ const msg = {
+ type: "command",
+ id: "cmd_2",
+ command: "list_sessions",
+ data: null,
+ }
+ const result = RemoteProtocol.Command.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.sessionId).toBeUndefined()
+ }
+ })
+
+ test("valid system parses", () => {
+ const msg = {
+ type: "system",
+ event: "cli.connected",
+ data: { pid: 1234 },
+ }
+ const result = RemoteProtocol.System.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.event).toBe("cli.connected")
+ }
+ })
+
+ // --- Discriminated unions ---
+
+ test("outbound union picks heartbeat", () => {
+ const msg = { type: "heartbeat", sessions: [] }
+ const result = RemoteProtocol.Outbound.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.type).toBe("heartbeat")
+ }
+ })
+
+ test("outbound union picks event", () => {
+ const msg = {
+ type: "event",
+ sessionId: "ses_1",
+ event: "session.updated",
+ data: {},
+ }
+ const result = RemoteProtocol.Outbound.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.type).toBe("event")
+ }
+ })
+
+ test("outbound union picks response", () => {
+ const msg = { type: "response", id: "r1" }
+ const result = RemoteProtocol.Outbound.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.type).toBe("response")
+ }
+ })
+
+ test("inbound union picks subscribe", () => {
+ const msg = { type: "subscribe", sessionId: "ses_1" }
+ const result = RemoteProtocol.Inbound.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.type).toBe("subscribe")
+ }
+ })
+
+ test("inbound union picks command", () => {
+ const msg = {
+ type: "command",
+ id: "c1",
+ command: "ping",
+ data: null,
+ }
+ const result = RemoteProtocol.Inbound.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.type).toBe("command")
+ }
+ })
+
+ test("inbound union picks system", () => {
+ const msg = { type: "system", event: "shutdown", data: null }
+ const result = RemoteProtocol.Inbound.safeParse(msg)
+ expect(result.success).toBe(true)
+ if (result.success) {
+ expect(result.data.type).toBe("system")
+ }
+ })
+
+ // --- Rejection ---
+
+ test("outbound rejects unknown type", () => {
+ const msg = { type: "bogus", data: 1 }
+ const result = RemoteProtocol.Outbound.safeParse(msg)
+ expect(result.success).toBe(false)
+ })
+
+ test("inbound rejects unknown type", () => {
+ const msg = { type: "bogus", data: 1 }
+ const result = RemoteProtocol.Inbound.safeParse(msg)
+ expect(result.success).toBe(false)
+ })
+
+ test("heartbeat rejects missing sessions", () => {
+ const msg = { type: "heartbeat" }
+ const result = RemoteProtocol.Heartbeat.safeParse(msg)
+ expect(result.success).toBe(false)
+ })
+
+ test("event rejects missing sessionId", () => {
+ const msg = { type: "event", event: "x", data: null }
+ const result = RemoteProtocol.Event.safeParse(msg)
+ expect(result.success).toBe(false)
+ })
+
+ test("command rejects missing id", () => {
+ const msg = { type: "command", command: "ping", data: null }
+ const result = RemoteProtocol.Command.safeParse(msg)
+ expect(result.success).toBe(false)
+ })
+
+ test("subscribe rejects missing sessionId", () => {
+ const msg = { type: "subscribe" }
+ const result = RemoteProtocol.Subscribe.safeParse(msg)
+ expect(result.success).toBe(false)
+ })
+
+ test("session info rejects missing fields", () => {
+ const result = RemoteProtocol.SessionInfo.safeParse({ id: "x" })
+ expect(result.success).toBe(false)
+ })
+})
diff --git a/packages/opencode/test/kilo-sessions/remote-sender.test.ts b/packages/opencode/test/kilo-sessions/remote-sender.test.ts
new file mode 100644
index 00000000000..987328860ae
--- /dev/null
+++ b/packages/opencode/test/kilo-sessions/remote-sender.test.ts
@@ -0,0 +1,947 @@
+import { describe, expect, test } from "bun:test"
+// kilocode_change start
+import { afterEach, mock, spyOn } from "bun:test"
+// kilocode_change end
+import { RemoteSender } from "../../src/kilo-sessions/remote-sender"
+import type { RemoteWS } from "../../src/kilo-sessions/remote-ws"
+import type { RemoteProtocol } from "../../src/kilo-sessions/remote-protocol"
+// kilocode_change start
+import { SessionPrompt } from "../../src/session/prompt"
+import { Question } from "../../src/question"
+import { PermissionNext } from "../../src/permission/next"
+// kilocode_change end
+
+function fakeConn() {
+ const sent: any[] = []
+ return {
+ conn: {
+ send(msg: any) {
+ sent.push(msg)
+ },
+ close() {},
+ get connected() {
+ return true
+ },
+ } as RemoteWS.Connection,
+ sent,
+ }
+}
+
+function fakeBus() {
+ const handlers: ((event: any) => void)[] = []
+ const subscribe = (cb: (event: any) => void) => {
+ handlers.push(cb)
+ return () => {
+ const idx = handlers.indexOf(cb)
+ if (idx >= 0) handlers.splice(idx, 1)
+ }
+ }
+ return {
+ subscribe,
+ fire: (event: any) => handlers.forEach((h) => h(event)),
+ count: () => handlers.length,
+ }
+}
+
+const nolog = {
+ info: () => {},
+ error: () => {},
+ warn: () => {},
+}
+
+// kilocode_change start
+afterEach(() => {
+ mock.restore()
+})
+// kilocode_change end
+
+describe("RemoteSender", () => {
+ test("subscribe adds bus subscription, event forwarded", () => {
+ const { conn, sent } = fakeConn()
+ const bus = fakeBus()
+ const sender = RemoteSender.create({
+ conn,
+ directory: "/tmp/test",
+ log: nolog,
+ subscribe: bus.subscribe,
+ })
+
+ sender.handle({ type: "subscribe", sessionId: "ses_abc" })
+
+ bus.fire({
+ type: "message.updated",
+ properties: { sessionID: "ses_abc", text: "hello" },
+ })
+
+ expect(sent).toHaveLength(1)
+ expect(sent[0]).toEqual({
+ type: "event",
+ sessionId: "ses_abc",
+ event: "message.updated",
+ data: { sessionID: "ses_abc", text: "hello" },
+ })
+ })
+
+ test("unsubscribe removes subscription, events stop", () => {
+ const { conn, sent } = fakeConn()
+ const bus = fakeBus()
+ const sender = RemoteSender.create({
+ conn,
+ directory: "/tmp/test",
+ log: nolog,
+ subscribe: bus.subscribe,
+ })
+
+ sender.handle({ type: "subscribe", sessionId: "ses_abc" })
+ sender.handle({ type: "unsubscribe", sessionId: "ses_abc" })
+
+ bus.fire({
+ type: "message.updated",
+ properties: { sessionID: "ses_abc", text: "hello" },
+ })
+
+ expect(sent).toHaveLength(0)
+ expect(bus.count()).toBe(0)
+ })
+
+ test("only forwards for subscribed sessions", () => {
+ const { conn, sent } = fakeConn()
+ const bus = fakeBus()
+ const sender = RemoteSender.create({
+ conn,
+ directory: "/tmp/test",
+ log: nolog,
+ subscribe: bus.subscribe,
+ })
+
+ sender.handle({ type: "subscribe", sessionId: "ses_a" })
+
+ bus.fire({
+ type: "session.updated",
+ properties: { sessionID: "ses_b", title: "other" },
+ })
+
+ expect(sent).toHaveLength(0)
+ })
+
+ test("duplicate subscribe is idempotent", () => {
+ const { conn } = fakeConn()
+ const bus = fakeBus()
+ const sender = RemoteSender.create({
+ conn,
+ directory: "/tmp/test",
+ log: nolog,
+ subscribe: bus.subscribe,
+ })
+
+ sender.handle({ type: "subscribe", sessionId: "ses_a" })
+ sender.handle({ type: "subscribe", sessionId: "ses_a" })
+
+ expect(bus.count()).toBe(1)
+ })
+
+ test("single bus subscription for multiple sessions", () => {
+ const { conn, sent } = fakeConn()
+ const bus = fakeBus()
+ const sender = RemoteSender.create({
+ conn,
+ directory: "/tmp/test",
+ log: nolog,
+ subscribe: bus.subscribe,
+ })
+
+ sender.handle({ type: "subscribe", sessionId: "ses_a" })
+ sender.handle({ type: "subscribe", sessionId: "ses_b" })
+
+ expect(bus.count()).toBe(1)
+
+ bus.fire({
+ type: "message.updated",
+ properties: { sessionID: "ses_a", text: "a" },
+ })
+ bus.fire({
+ type: "message.updated",
+ properties: { sessionID: "ses_b", text: "b" },
+ })
+
+ expect(sent).toHaveLength(2)
+ expect(sent[0].sessionId).toBe("ses_a")
+ expect(sent[1].sessionId).toBe("ses_b")
+ })
+
+ test("unsubscribe one session keeps bus alive for others", () => {
+ const { conn, sent } = fakeConn()
+ const bus = fakeBus()
+ const sender = RemoteSender.create({
+ conn,
+ directory: "/tmp/test",
+ log: nolog,
+ subscribe: bus.subscribe,
+ })
+
+ sender.handle({ type: "subscribe", sessionId: "ses_a" })
+ sender.handle({ type: "subscribe", sessionId: "ses_b" })
+ sender.handle({ type: "unsubscribe", sessionId: "ses_a" })
+
+ expect(bus.count()).toBe(1)
+
+ bus.fire({
+ type: "session.updated",
+ properties: { sessionID: "ses_b", title: "still here" },
+ })
+
+ expect(sent).toHaveLength(1)
+ expect(sent[0].sessionId).toBe("ses_b")
+ })
+
+ test("send_message sends ACK immediately before provide resolves", async () => {
+ const { conn, sent } = fakeConn()
+ let resolveProvide: () => void
+ const provideStarted = new Promise((r) => {
+ resolveProvide = r
+ })
+ const sender = RemoteSender.create({
+ conn,
+ directory: "/tmp/test",
+ log: nolog,
+ subscribe: fakeBus().subscribe,
+ provide: async (input: any) => {
+ resolveProvide!()
+ // Simulate long-running work — never resolves during this test
+ await new Promise(() => {})
+ return {} as any
+ },
+ })
+
+ sender.handle({
+ type: "command",
+ id: "req_1",
+ command: "send_message",
+ data: { sessionID: "ses_x", parts: [{ type: "text", text: "hi" }] },
+ })
+
+ // ACK is sent synchronously before provide even starts
+ expect(sent).toHaveLength(1)
+ expect(sent[0]).toEqual({ type: "response", id: "req_1", result: {} })
+
+ // provide was still called
+ await provideStarted
+ })
+
+ test("send_message with invalid data sends error response immediately", () => {
+ const { conn, sent } = fakeConn()
+ const sender = RemoteSender.create({
+ conn,
+ directory: "/tmp/test",
+ log: nolog,
+ subscribe: fakeBus().subscribe,
+ provide: async () => ({}) as any,
+ })
+
+ sender.handle({
+ type: "command",
+ id: "req_bad",
+ command: "send_message",
+ data: { invalid: true },
+ })
+
+ expect(sent).toHaveLength(1)
+ expect(sent[0].type).toBe("response")
+ expect(sent[0].id).toBe("req_bad")
+ expect(sent[0].error).toContain("invalid send_message data")
+ })
+
+ test("unknown command sends error response with matching id", () => {
+ const { conn, sent } = fakeConn()
+ const sender = RemoteSender.create({
+ conn,
+ directory: "/tmp/test",
+ log: nolog,
+ subscribe: fakeBus().subscribe,
+ provide: async () => ({}) as any,
+ })
+
+ sender.handle({
+ type: "command",
+ id: "req_unknown",
+ command: "unknown_command",
+ data: { foo: "bar" },
+ } as RemoteProtocol.Command)
+
+ expect(sent).toHaveLength(1)
+ expect(sent[0]).toEqual({
+ type: "response",
+ id: "req_unknown",
+ error: "unknown command: unknown_command",
+ })
+ })
+
+ test("send_message with agent is accepted", async () => {
+ const { conn, sent } = fakeConn()
+ let resolveProvide: () => void
+ const provideStarted = new Promise((r) => {
+ resolveProvide = r
+ })
+ const sender = RemoteSender.create({
+ conn,
+ directory: "/tmp/test",
+ log: nolog,
+ subscribe: fakeBus().subscribe,
+ provide: async (input: any) => {
+ resolveProvide!()
+ await new Promise(() => {})
+ return {} as any
+ },
+ })
+
+ sender.handle({
+ type: "command",
+ id: "req_model",
+ command: "send_message",
+ data: {
+ sessionID: "ses_x",
+ parts: [{ type: "text", text: "hello" }],
+ agent: "plan",
+ },
+ })
+
+ // ACK sent (not error) — model and agent were accepted by PromptInput validation
+ expect(sent).toHaveLength(1)
+ expect(sent[0]).toEqual({ type: "response", id: "req_model", result: {} })
+
+ await provideStarted
+ })
+
+ // kilocode_change start
+ test("send_message normalizes string model without prefix", async () => {
+ const { conn, sent } = fakeConn()
+ const prompt = spyOn(SessionPrompt, "prompt").mockResolvedValue({} as never)
+ const sender = RemoteSender.create({
+ conn,
+ directory: "/tmp/test",
+ log: nolog,
+ subscribe: fakeBus().subscribe,
+ provide: async