termux-dev 1.8.0 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -207,4 +207,59 @@ export const fr = {
207
207
  'rewind.filesNo': 'Non, seulement la conversation',
208
208
  'rewind.done': 'De retour à votre message. Ce qui a suivi a disparu, pour le modèle aussi. Modifiez le message et envoyez-le.',
209
209
  'rewind.doneImages': 'De retour à votre message (ses images n’ont pas été gardées, joignez-les de nouveau). Ce qui a suivi a disparu, pour le modèle aussi.',
210
+ // ── first run and the prompts around it
211
+ 'welcome.tagline': 'Un agent de programmation IA pour le terminal',
212
+ 'welcome.intro': 'Configurons tout. Cela prend environ une minute.',
213
+ 'welcome.step.language': 'Langue',
214
+ 'welcome.step.provider': 'Fournisseur',
215
+ 'welcome.step.key': 'Clé API',
216
+ 'welcome.step.model': 'Modèle',
217
+ 'welcome.keyNote': 'Ta clé reste sur cet ordinateur, dans ~/.devxrc.json.',
218
+ 'onboard.language.title': 'langue',
219
+ 'onboard.language.note': 'Tu pourras la changer plus tard dans /settings.',
220
+ 'onboard.provider.title': 'fournisseur',
221
+ 'onboard.provider.placeholder': 'tape pour trouver un fournisseur',
222
+ 'onboard.provider.note': 'Le service qui te donne les modèles. Pas dans la liste ? Choisis Adresse personnalisée.',
223
+ 'onboard.custom': 'Adresse personnalisée',
224
+ 'onboard.customDetail': 'Toute adresse compatible OpenAI',
225
+ 'onboard.baseUrl.title': 'adresse',
226
+ 'onboard.baseUrl.label': 'URL de base de l’API',
227
+ 'onboard.baseUrl.placeholder': 'https://api.example.com/v1',
228
+ 'onboard.key.title': 'clé api',
229
+ 'onboard.key.label': 'Colle ta clé API pour {provider}',
230
+ 'onboard.key.note': 'Ce que tu tapes est masqué. Échap revient en arrière.',
231
+ 'onboard.key.useSaved': 'Utiliser la clé enregistrée',
232
+ 'onboard.key.enterNew': 'Saisir une nouvelle clé',
233
+ 'onboard.key.savedTitle': 'une clé est enregistrée pour {provider}',
234
+ 'onboard.model.title': 'modèle',
235
+ 'onboard.model.placeholder': 'tape pour trouver un modèle',
236
+ 'onboard.model.custom': 'Saisir un nom de modèle',
237
+ 'onboard.model.customDetail': 'Tout identifiant de modèle',
238
+ 'onboard.model.label': 'Nom du modèle (par exemple gpt-4o)',
239
+ 'onboard.model.fetching': 'Récupération de la liste des modèles...',
240
+ 'onboard.model.manual': 'La liste des modèles n’a pas pu être récupérée, saisis le nom du modèle.',
241
+ 'onboard.model.context': 'contexte de {n}',
242
+ 'onboard.model.anthropic': 'format Anthropic',
243
+ 'onboard.back': 'Retour',
244
+ 'onboard.back.detail': 'Garder le réglage actuel',
245
+ 'onboard.done': 'Terminé. Tes choix sont enregistrés dans ~/.devxrc.json.',
246
+ 'finder.placeholder': 'tape pour filtrer',
247
+ 'finder.empty': 'Aucun résultat',
248
+ 'finder.hint.filter': 'filtrer',
249
+ 'picker.hint.move': 'déplacer',
250
+ 'picker.hint.select': 'choisir',
251
+ 'picker.hint.cancel': 'annuler',
252
+ 'picker.hint.confirm': 'confirmer',
253
+ 'welcome.step.theme': 'Thème',
254
+ 'onboard.theme.title': 'thème',
255
+ 'onboard.theme.note': 'Les couleurs de l’interface. Tu peux les changer à tout moment avec /theme.',
256
+ 'resume.title': 'reprendre',
257
+ 'resume.placeholder': 'tape pour filtrer les sessions',
258
+ 'resume.msgs': '{n} msgs',
259
+ 'sessions.deleteTitle': 'supprimer des sessions',
260
+ 'sessions.deleteAll': 'Supprimer les {n} sessions',
261
+ 'commands.title': 'commandes',
262
+ 'commands.placeholder': 'tape pour trouver une commande',
263
+ 'compact.done': 'Contexte compacté, résumé conservé',
264
+ 'compact.summary': 'ce que le modèle a écrit',
210
265
  };
@@ -207,4 +207,59 @@ export const ja = {
207
207
  'rewind.filesNo': 'いいえ、会話だけ',
208
208
  'rewind.done': 'あなたのメッセージに戻りました。以降の内容はモデルからも消えています。メッセージを編集して送信してください。',
209
209
  'rewind.doneImages': 'あなたのメッセージに戻りました(画像は保持されません。再度添付してください)。以降の内容はモデルからも消えています。',
210
+ // ── first run and the prompts around it
211
+ 'welcome.tagline': 'ターミナルで使える AI コーディングエージェント',
212
+ 'welcome.intro': '設定を済ませましょう。1 分ほどで終わります。',
213
+ 'welcome.step.language': '言語',
214
+ 'welcome.step.provider': 'プロバイダー',
215
+ 'welcome.step.key': 'API キー',
216
+ 'welcome.step.model': 'モデル',
217
+ 'welcome.keyNote': 'キーはこのコンピューターの ~/.devxrc.json にだけ保存されます。',
218
+ 'onboard.language.title': '言語',
219
+ 'onboard.language.note': 'あとで /settings から変更できます。',
220
+ 'onboard.provider.title': 'プロバイダー',
221
+ 'onboard.provider.placeholder': '入力してプロバイダーを探す',
222
+ 'onboard.provider.note': 'モデルを提供するサービスです。一覧にない場合は「独自のアドレス」を選んでください。',
223
+ 'onboard.custom': '独自のアドレス',
224
+ 'onboard.customDetail': 'OpenAI 互換の任意のアドレス',
225
+ 'onboard.baseUrl.title': 'アドレス',
226
+ 'onboard.baseUrl.label': 'API のベース URL',
227
+ 'onboard.baseUrl.placeholder': 'https://api.example.com/v1',
228
+ 'onboard.key.title': 'api キー',
229
+ 'onboard.key.label': '{provider} の API キーを貼り付けてください',
230
+ 'onboard.key.note': '入力内容は表示されません。Esc で戻ります。',
231
+ 'onboard.key.useSaved': '保存済みのキーを使う',
232
+ 'onboard.key.enterNew': '新しいキーを入力',
233
+ 'onboard.key.savedTitle': '{provider} のキーは保存済みです',
234
+ 'onboard.model.title': 'モデル',
235
+ 'onboard.model.placeholder': '入力してモデルを探す',
236
+ 'onboard.model.custom': 'モデル名を入力',
237
+ 'onboard.model.customDetail': '任意のモデル ID',
238
+ 'onboard.model.label': 'モデル名(例: gpt-4o)',
239
+ 'onboard.model.fetching': 'モデル一覧を取得しています...',
240
+ 'onboard.model.manual': 'モデル一覧を取得できませんでした。モデル名を入力してください。',
241
+ 'onboard.model.context': '{n} コンテキスト',
242
+ 'onboard.model.anthropic': 'Anthropic 形式',
243
+ 'onboard.back': '戻る',
244
+ 'onboard.back.detail': '現在の設定を維持',
245
+ 'onboard.done': '完了です。選択内容は ~/.devxrc.json に保存されました。',
246
+ 'finder.placeholder': '入力して絞り込み',
247
+ 'finder.empty': '一致なし',
248
+ 'finder.hint.filter': '絞り込み',
249
+ 'picker.hint.move': '移動',
250
+ 'picker.hint.select': '選択',
251
+ 'picker.hint.cancel': 'キャンセル',
252
+ 'picker.hint.confirm': '確認',
253
+ 'welcome.step.theme': 'テーマ',
254
+ 'onboard.theme.title': 'テーマ',
255
+ 'onboard.theme.note': 'インターフェースの配色です。/theme でいつでも変更できます。',
256
+ 'resume.title': '再開',
257
+ 'resume.placeholder': '入力してセッションを絞り込み',
258
+ 'resume.msgs': '{n} 件',
259
+ 'sessions.deleteTitle': 'セッションの削除',
260
+ 'sessions.deleteAll': '{n} 件すべてのセッションを削除',
261
+ 'commands.title': 'コマンド',
262
+ 'commands.placeholder': '入力してコマンドを探す',
263
+ 'compact.done': 'コンテキスト圧縮済み、要約を保持',
264
+ 'compact.summary': 'モデルが書いた内容',
210
265
  };
@@ -207,4 +207,59 @@ export const pt = {
207
207
  'rewind.filesNo': 'Não, só a conversa',
208
208
  'rewind.done': 'De volta à sua mensagem. O que veio depois sumiu, para o modelo também. Edite a mensagem e envie.',
209
209
  'rewind.doneImages': 'De volta à sua mensagem (as imagens dela não foram mantidas, anexe de novo). O que veio depois sumiu, para o modelo também.',
210
+ // ── first run and the prompts around it
211
+ 'welcome.tagline': 'Um agente de programação com IA para o terminal',
212
+ 'welcome.intro': 'Vamos configurar tudo. Leva cerca de um minuto.',
213
+ 'welcome.step.language': 'Idioma',
214
+ 'welcome.step.provider': 'Provedor',
215
+ 'welcome.step.key': 'Chave de API',
216
+ 'welcome.step.model': 'Modelo',
217
+ 'welcome.keyNote': 'Sua chave fica neste computador, em ~/.devxrc.json.',
218
+ 'onboard.language.title': 'idioma',
219
+ 'onboard.language.note': 'Você pode mudar depois em /settings.',
220
+ 'onboard.provider.title': 'provedor',
221
+ 'onboard.provider.placeholder': 'digite para encontrar um provedor',
222
+ 'onboard.provider.note': 'O serviço que fornece os modelos. Não está na lista? Escolha Endereço próprio.',
223
+ 'onboard.custom': 'Endereço próprio',
224
+ 'onboard.customDetail': 'Qualquer endereço compatível com OpenAI',
225
+ 'onboard.baseUrl.title': 'endereço',
226
+ 'onboard.baseUrl.label': 'URL base da API',
227
+ 'onboard.baseUrl.placeholder': 'https://api.example.com/v1',
228
+ 'onboard.key.title': 'chave de api',
229
+ 'onboard.key.label': 'Cole sua chave de API para {provider}',
230
+ 'onboard.key.note': 'O que você digita fica oculto. Esc volta.',
231
+ 'onboard.key.useSaved': 'Usar a chave salva',
232
+ 'onboard.key.enterNew': 'Inserir uma nova chave',
233
+ 'onboard.key.savedTitle': 'há uma chave salva para {provider}',
234
+ 'onboard.model.title': 'modelo',
235
+ 'onboard.model.placeholder': 'digite para encontrar um modelo',
236
+ 'onboard.model.custom': 'Digitar o nome de um modelo',
237
+ 'onboard.model.customDetail': 'Qualquer id de modelo',
238
+ 'onboard.model.label': 'Nome do modelo (por exemplo gpt-4o)',
239
+ 'onboard.model.fetching': 'Buscando a lista de modelos...',
240
+ 'onboard.model.manual': 'Não foi possível obter a lista de modelos, digite o nome do modelo.',
241
+ 'onboard.model.context': 'contexto de {n}',
242
+ 'onboard.model.anthropic': 'formato Anthropic',
243
+ 'onboard.back': 'Voltar',
244
+ 'onboard.back.detail': 'Manter o que está definido',
245
+ 'onboard.done': 'Pronto. Suas escolhas estão salvas em ~/.devxrc.json.',
246
+ 'finder.placeholder': 'digite para filtrar',
247
+ 'finder.empty': 'Nada encontrado',
248
+ 'finder.hint.filter': 'filtrar',
249
+ 'picker.hint.move': 'mover',
250
+ 'picker.hint.select': 'escolher',
251
+ 'picker.hint.cancel': 'cancelar',
252
+ 'picker.hint.confirm': 'confirmar',
253
+ 'welcome.step.theme': 'Tema',
254
+ 'onboard.theme.title': 'tema',
255
+ 'onboard.theme.note': 'As cores da interface. Você pode mudá-las a qualquer momento com /theme.',
256
+ 'resume.title': 'retomar',
257
+ 'resume.placeholder': 'digite para filtrar sessões',
258
+ 'resume.msgs': '{n} msgs',
259
+ 'sessions.deleteTitle': 'excluir sessões',
260
+ 'sessions.deleteAll': 'Excluir todas as {n} sessões',
261
+ 'commands.title': 'comandos',
262
+ 'commands.placeholder': 'digite para encontrar um comando',
263
+ 'compact.done': 'Contexto compactado, resumo mantido',
264
+ 'compact.summary': 'o que o modelo escreveu',
210
265
  };
@@ -207,4 +207,59 @@ export const ru = {
207
207
  'rewind.filesNo': 'Нет, только разговор',
208
208
  'rewind.done': 'Вы вернулись к своему сообщению. Всё, что было после него, стёрто, и для модели тоже. Поправьте сообщение и отправьте.',
209
209
  'rewind.doneImages': 'Вы вернулись к своему сообщению (картинки из него не сохранились, приложите их снова). Всё, что было после, стёрто, и для модели тоже.',
210
+ // ── first run and the prompts around it
211
+ 'welcome.tagline': 'Терминальный ИИ-агент для программирования',
212
+ 'welcome.intro': 'Давай всё настроим, это займёт около минуты.',
213
+ 'welcome.step.language': 'Язык',
214
+ 'welcome.step.provider': 'Провайдер',
215
+ 'welcome.step.key': 'API-ключ',
216
+ 'welcome.step.model': 'Модель',
217
+ 'welcome.keyNote': 'Твой ключ остаётся на этом компьютере, в ~/.devxrc.json.',
218
+ 'onboard.language.title': 'язык',
219
+ 'onboard.language.note': 'Позже его можно сменить в /settings.',
220
+ 'onboard.provider.title': 'провайдер',
221
+ 'onboard.provider.placeholder': 'начни вводить название',
222
+ 'onboard.provider.note': 'Сервис, который даёт модели. Нет в списке? Выбери свой адрес.',
223
+ 'onboard.custom': 'Свой адрес',
224
+ 'onboard.customDetail': 'Любой адрес, совместимый с OpenAI',
225
+ 'onboard.baseUrl.title': 'адрес',
226
+ 'onboard.baseUrl.label': 'Базовый URL API',
227
+ 'onboard.baseUrl.placeholder': 'https://api.example.com/v1',
228
+ 'onboard.key.title': 'api-ключ',
229
+ 'onboard.key.label': 'Вставь API-ключ для {provider}',
230
+ 'onboard.key.note': 'Вводимое скрыто. Esc возвращает назад.',
231
+ 'onboard.key.useSaved': 'Использовать сохранённый ключ',
232
+ 'onboard.key.enterNew': 'Ввести новый ключ',
233
+ 'onboard.key.savedTitle': 'ключ для {provider} уже сохранён',
234
+ 'onboard.model.title': 'модель',
235
+ 'onboard.model.placeholder': 'начни вводить название',
236
+ 'onboard.model.custom': 'Ввести название модели',
237
+ 'onboard.model.customDetail': 'Любой id модели',
238
+ 'onboard.model.label': 'Название модели (например, gpt-4o)',
239
+ 'onboard.model.fetching': 'Загружаю список моделей...',
240
+ 'onboard.model.manual': 'Список моделей получить не удалось, введи название модели.',
241
+ 'onboard.model.context': 'контекст {n}',
242
+ 'onboard.model.anthropic': 'формат Anthropic',
243
+ 'onboard.back': 'Назад',
244
+ 'onboard.back.detail': 'Оставить как сейчас',
245
+ 'onboard.done': 'Готово. Выбор сохранён в ~/.devxrc.json.',
246
+ 'finder.placeholder': 'начни вводить для фильтра',
247
+ 'finder.empty': 'Ничего не найдено',
248
+ 'finder.hint.filter': 'фильтр',
249
+ 'picker.hint.move': 'движение',
250
+ 'picker.hint.select': 'выбрать',
251
+ 'picker.hint.cancel': 'отмена',
252
+ 'picker.hint.confirm': 'подтвердить',
253
+ 'welcome.step.theme': 'Тема',
254
+ 'onboard.theme.title': 'тема',
255
+ 'onboard.theme.note': 'Цвета интерфейса. Их можно сменить в любой момент командой /theme.',
256
+ 'resume.title': 'продолжить',
257
+ 'resume.placeholder': 'начни вводить для поиска сессии',
258
+ 'resume.msgs': 'сообщ.: {n}',
259
+ 'sessions.deleteTitle': 'удаление сессий',
260
+ 'sessions.deleteAll': 'Удалить все сессии ({n})',
261
+ 'commands.title': 'команды',
262
+ 'commands.placeholder': 'начни вводить название команды',
263
+ 'compact.done': 'Контекст сжат, краткое содержание сохранено',
264
+ 'compact.summary': 'что написала модель',
210
265
  };
@@ -207,4 +207,59 @@ export const uk = {
207
207
  'rewind.filesNo': 'Ні, лише розмову',
208
208
  'rewind.done': 'Ви повернулися до свого повідомлення. Усе, що було після нього, стерто, і для моделі теж. Поправте повідомлення й надішліть.',
209
209
  'rewind.doneImages': 'Ви повернулися до свого повідомлення (картинки з нього не збереглися, додайте їх знову). Усе, що було після, стерто, і для моделі теж.',
210
+ // ── first run and the prompts around it
211
+ 'welcome.tagline': 'Термінальний ШІ-агент для програмування',
212
+ 'welcome.intro': 'Налаштуймо все, це займе близько хвилини.',
213
+ 'welcome.step.language': 'Мова',
214
+ 'welcome.step.provider': 'Провайдер',
215
+ 'welcome.step.key': 'API-ключ',
216
+ 'welcome.step.model': 'Модель',
217
+ 'welcome.keyNote': 'Твій ключ залишається на цьому комп’ютері, у ~/.devxrc.json.',
218
+ 'onboard.language.title': 'мова',
219
+ 'onboard.language.note': 'Пізніше її можна змінити в /settings.',
220
+ 'onboard.provider.title': 'провайдер',
221
+ 'onboard.provider.placeholder': 'почни вводити назву',
222
+ 'onboard.provider.note': 'Сервіс, що надає моделі. Немає в списку? Обери власну адресу.',
223
+ 'onboard.custom': 'Власна адреса',
224
+ 'onboard.customDetail': 'Будь-яка адреса, сумісна з OpenAI',
225
+ 'onboard.baseUrl.title': 'адреса',
226
+ 'onboard.baseUrl.label': 'Базовий URL API',
227
+ 'onboard.baseUrl.placeholder': 'https://api.example.com/v1',
228
+ 'onboard.key.title': 'api-ключ',
229
+ 'onboard.key.label': 'Встав API-ключ для {provider}',
230
+ 'onboard.key.note': 'Введене приховано. Esc повертає назад.',
231
+ 'onboard.key.useSaved': 'Використати збережений ключ',
232
+ 'onboard.key.enterNew': 'Ввести новий ключ',
233
+ 'onboard.key.savedTitle': 'ключ для {provider} уже збережено',
234
+ 'onboard.model.title': 'модель',
235
+ 'onboard.model.placeholder': 'почни вводити назву',
236
+ 'onboard.model.custom': 'Ввести назву моделі',
237
+ 'onboard.model.customDetail': 'Будь-який id моделі',
238
+ 'onboard.model.label': 'Назва моделі (наприклад, gpt-4o)',
239
+ 'onboard.model.fetching': 'Завантажую список моделей...',
240
+ 'onboard.model.manual': 'Список моделей отримати не вдалося, введи назву моделі.',
241
+ 'onboard.model.context': 'контекст {n}',
242
+ 'onboard.model.anthropic': 'формат Anthropic',
243
+ 'onboard.back': 'Назад',
244
+ 'onboard.back.detail': 'Залишити як зараз',
245
+ 'onboard.done': 'Готово. Вибір збережено в ~/.devxrc.json.',
246
+ 'finder.placeholder': 'почни вводити для фільтра',
247
+ 'finder.empty': 'Нічого не знайдено',
248
+ 'finder.hint.filter': 'фільтр',
249
+ 'picker.hint.move': 'рух',
250
+ 'picker.hint.select': 'вибрати',
251
+ 'picker.hint.cancel': 'скасувати',
252
+ 'picker.hint.confirm': 'підтвердити',
253
+ 'welcome.step.theme': 'Тема',
254
+ 'onboard.theme.title': 'тема',
255
+ 'onboard.theme.note': 'Кольори інтерфейсу. Їх можна змінити будь-коли командою /theme.',
256
+ 'resume.title': 'продовжити',
257
+ 'resume.placeholder': 'почни вводити для пошуку сесії',
258
+ 'resume.msgs': 'повід.: {n}',
259
+ 'sessions.deleteTitle': 'видалення сесій',
260
+ 'sessions.deleteAll': 'Видалити всі сесії ({n})',
261
+ 'commands.title': 'команди',
262
+ 'commands.placeholder': 'почни вводити назву команди',
263
+ 'compact.done': 'Контекст стиснуто, виклад збережено',
264
+ 'compact.summary': 'що написала модель',
210
265
  };
@@ -207,4 +207,59 @@ export const zh = {
207
207
  'rewind.filesNo': '否,只回退对话',
208
208
  'rewind.done': '已回到你的消息。之后的内容已被清除,模型也看不到了。修改消息后发送。',
209
209
  'rewind.doneImages': '已回到你的消息(其中的图片未保留,请重新添加)。之后的内容已被清除,模型也看不到了。',
210
+ // ── first run and the prompts around it
211
+ 'welcome.tagline': '终端里的 AI 编程助手',
212
+ 'welcome.intro': '我们来完成设置,大约只需一分钟。',
213
+ 'welcome.step.language': '语言',
214
+ 'welcome.step.provider': '服务商',
215
+ 'welcome.step.key': 'API 密钥',
216
+ 'welcome.step.model': '模型',
217
+ 'welcome.keyNote': '你的密钥只保存在这台电脑上,位于 ~/.devxrc.json。',
218
+ 'onboard.language.title': '语言',
219
+ 'onboard.language.note': '之后可以在 /settings 中更改。',
220
+ 'onboard.provider.title': '服务商',
221
+ 'onboard.provider.placeholder': '输入以查找服务商',
222
+ 'onboard.provider.note': '提供模型的服务。没有找到?请选择自定义地址。',
223
+ 'onboard.custom': '自定义地址',
224
+ 'onboard.customDetail': '任何兼容 OpenAI 的地址',
225
+ 'onboard.baseUrl.title': '地址',
226
+ 'onboard.baseUrl.label': 'API 的基础 URL',
227
+ 'onboard.baseUrl.placeholder': 'https://api.example.com/v1',
228
+ 'onboard.key.title': 'api 密钥',
229
+ 'onboard.key.label': '粘贴 {provider} 的 API 密钥',
230
+ 'onboard.key.note': '输入的内容不会显示。按 Esc 返回。',
231
+ 'onboard.key.useSaved': '使用已保存的密钥',
232
+ 'onboard.key.enterNew': '输入新密钥',
233
+ 'onboard.key.savedTitle': '已保存 {provider} 的密钥',
234
+ 'onboard.model.title': '模型',
235
+ 'onboard.model.placeholder': '输入以查找模型',
236
+ 'onboard.model.custom': '输入模型名称',
237
+ 'onboard.model.customDetail': '任意模型 ID',
238
+ 'onboard.model.label': '模型名称(例如 gpt-4o)',
239
+ 'onboard.model.fetching': '正在获取模型列表...',
240
+ 'onboard.model.manual': '无法获取模型列表,请输入模型名称。',
241
+ 'onboard.model.context': '{n} 上下文',
242
+ 'onboard.model.anthropic': 'Anthropic 格式',
243
+ 'onboard.back': '返回',
244
+ 'onboard.back.detail': '保持当前设置',
245
+ 'onboard.done': '完成。你的选择已保存到 ~/.devxrc.json。',
246
+ 'finder.placeholder': '输入以筛选',
247
+ 'finder.empty': '没有匹配项',
248
+ 'finder.hint.filter': '筛选',
249
+ 'picker.hint.move': '移动',
250
+ 'picker.hint.select': '选择',
251
+ 'picker.hint.cancel': '取消',
252
+ 'picker.hint.confirm': '确认',
253
+ 'welcome.step.theme': '主题',
254
+ 'onboard.theme.title': '主题',
255
+ 'onboard.theme.note': '界面的配色。随时可以用 /theme 更改。',
256
+ 'resume.title': '恢复会话',
257
+ 'resume.placeholder': '输入以筛选会话',
258
+ 'resume.msgs': '{n} 条消息',
259
+ 'sessions.deleteTitle': '删除会话',
260
+ 'sessions.deleteAll': '删除全部 {n} 个会话',
261
+ 'commands.title': '命令',
262
+ 'commands.placeholder': '输入以查找命令',
263
+ 'compact.done': '上下文已压缩,摘要已保留',
264
+ 'compact.summary': '模型写的内容',
210
265
  };
package/dist/core/loop.js CHANGED
@@ -32,6 +32,17 @@ export function getActionDescription(toolName, args) {
32
32
  if (t === 'bash' || t === 'execute_command' || t === 'run_command' || t === 'exec') {
33
33
  return `→ Run: ${args.command || args.cmd || ''}`;
34
34
  }
35
+ if (t === 'background') {
36
+ const id = args.id ? ` ${args.id}` : '';
37
+ switch (args.action) {
38
+ case 'start': return `→ Background: ${args.label ? `${args.label} · ` : ''}${args.command || ''}`;
39
+ case 'list': return `→ Background: list tasks`;
40
+ case 'output': return `→ Background: output${id}`;
41
+ case 'wait': return `→ Background: wait${id}${args.pattern ? ` for /${args.pattern}/` : ''}`;
42
+ case 'stop': return `→ Background: stop${id}`;
43
+ default: return `→ Background`;
44
+ }
45
+ }
35
46
  if (t === 'todo_list' || t === 'update_todos') {
36
47
  return `→ Update Plan & Tasks`;
37
48
  }
@@ -83,6 +94,12 @@ export class Agent {
83
94
  this.history.addMessage(message);
84
95
  yield { type: 'user_message', content: message.content };
85
96
  }
97
+ // A background task ended or printed what was asked for: the model learns it before it decides.
98
+ const news = hooks?.takeNotes?.();
99
+ if (news) {
100
+ this.history.addMessage({ role: 'user', content: news });
101
+ yield { type: 'background_note', content: news };
102
+ }
86
103
  const toolDefinitions = Array.from(this.tools.values()).map((t) => t.definition);
87
104
  // Tool schemas are sent with every request and count against the context window.
88
105
  const schemaTokens = toolSchemaTokens(toolDefinitions);
@@ -207,7 +224,7 @@ export class Agent {
207
224
  }
208
225
  if (!response.toolCalls || response.toolCalls.length === 0) {
209
226
  // The user wrote something while the model was finishing: answer it instead of stopping.
210
- if (hooks?.hasPending?.()) {
227
+ if (hooks?.hasPending?.() || hooks?.hasNotes?.()) {
211
228
  iterations++;
212
229
  continue;
213
230
  }
@@ -1 +1 @@
1
- export const DEVX_VERSION = '1.8.0';
1
+ export const DEVX_VERSION = '1.8.2';
@@ -277,7 +277,7 @@ function hasComplexChainingOrRedirection(commandStr) {
277
277
  commandStr.includes('\r'));
278
278
  }
279
279
  // ───────────────────────── guard ─────────────────────────
280
- const SHELL_TOOLS = new Set(['bash', 'exec', 'run_command', 'execute_command']);
280
+ const SHELL_TOOLS = new Set(['bash', 'background', 'exec', 'run_command', 'execute_command']);
281
281
  const READ_TOOLS = new Set(['read_file', 'view_file', 'read', 'list_dir', 'list_files', 'ls', 'search', 'grep_search', 'grep', 'diagnose_code']);
282
282
  const WRITE_TOOLS = new Set(['write_file', 'edit_file', 'make_dir', 'mkdir', 'create_file', 'write_to_file', 'patch']);
283
283
  function pathArg(args) {
@@ -409,6 +409,8 @@ export function buildPermissionRequest(toolName, args) {
409
409
  notes.push('looks destructive or runs downloaded code');
410
410
  else if (shellTouchesSecrets(cmd))
411
411
  notes.push('reads a credentials / secrets file: its content would reach the model');
412
+ if (t === 'background')
413
+ notes.push('keeps running in the background until it ends or you stop it');
412
414
  return { title: t === 'bash' ? 'bash' : toolName, danger, lines: cmd.split('\n'), notes };
413
415
  }
414
416
  if (t === 'mcp_manage') {
@@ -34,7 +34,8 @@ export async function buildSystemPrompt(planMode) {
34
34
  prompt += `- Before using tools on a task, say in one short line what you are about to do. Do not narrate every step.\n`;
35
35
  prompt += `- Do the whole task step by step. For a task with several steps, keep the checklist with 'todo_list'.\n`;
36
36
  prompt += `- Read a file before you edit it. Change only what the task needs, with 'edit_file' for small changes. Do not rewrite a whole file or touch unrelated code.\n`;
37
- prompt += `- 'bash' stops after 120 s and cuts long output: give long jobs a timeout or run them in the background, and print only what you need (head, tail, grep).\n`;
37
+ prompt += `- 'bash' stops after 120 s and cuts long output: give a long job a timeout, and print only what you need (head, tail, grep).\n`;
38
+ prompt += `- Anything that keeps running (dev server, watcher, long test run or build) goes to the 'background' tool, not to 'bash': action 'start' returns at once and the work goes on while you continue. Set notify_on to a regex ('ready|listening|error|FAIL') to be told when such a line appears; you are always told when a task ends. Then do other work or finish your answer: do not poll. To wait for a line use action 'wait' with a pattern, to read output use 'output', to end it use 'stop'. The user sees the tasks under /tasks. Never start the same server twice: check 'list' first.\n`;
38
39
  prompt += `- After you change code, run 'diagnose_code' and fix what it reports. Install missing dependencies with 'install_package'.\n`;
39
40
  prompt += `- After you create or change a web page or frontend app, start the local preview with 'serve_preview' (action 'start').\n`;
40
41
  if (MemoryManager.enabled)
@@ -0,0 +1,118 @@
1
+ import { MAX_BACKGROUND_TASKS, formatDuration, getBackgroundManager } from '../core/background.js';
2
+ const ACTIONS = ['start', 'list', 'output', 'wait', 'stop'];
3
+ /** One line per task: `bg1 running 2m 03s npm run dev`. */
4
+ export function describeTask(info, now = Date.now()) {
5
+ const age = formatDuration((info.endedAt ?? now) - info.startedAt);
6
+ const state = info.status === 'running' ? 'running'
7
+ : info.status === 'stopped' ? 'stopped'
8
+ : info.status === 'exited' ? 'finished (exit 0)'
9
+ : `failed (exit ${info.code ?? '?'})`;
10
+ const watch = info.watching && info.status === 'running' ? ` [watching /${info.watching}/]` : '';
11
+ return `${info.id} ${state} ${age} ${info.label}${watch}`;
12
+ }
13
+ function outputBlock(text, omitted) {
14
+ if (!text)
15
+ return '(no output)';
16
+ return (omitted > 0 ? `[... ${omitted} earlier line${omitted === 1 ? '' : 's'} left out ...]\n` : '') + text;
17
+ }
18
+ export const backgroundTool = {
19
+ name: 'background',
20
+ definition: {
21
+ name: 'background',
22
+ description: 'Run a long-lived command in the background and keep working: dev servers, file watchers, long builds or test runs. ' +
23
+ 'Do NOT use it for commands that finish quickly (use bash). Actions: ' +
24
+ '"start" (command, optional label, notify_on, max_runtime_seconds) launches it and returns its id and first output; ' +
25
+ '"list" shows all tasks; ' +
26
+ '"output" (id) reads what the task printed since the last read (mode "tail" gives the last lines instead; "grep" filters lines); ' +
27
+ '"wait" (id, optional pattern, timeout_seconds) blocks until the task ends, a line matches the regex, or the time is up; ' +
28
+ '"stop" (id) ends the task. ' +
29
+ 'Set "notify_on" (a regex such as "ready|listening|error|FAIL") to be told when such a line appears, and you are always told when a task ends: ' +
30
+ 'then you do not need to poll. Never poll in a loop; wait with a pattern instead. ' +
31
+ `At most ${MAX_BACKGROUND_TASKS} tasks run at once, and all of them are stopped when devx exits.`,
32
+ parameters: {
33
+ type: 'object',
34
+ properties: {
35
+ action: { type: 'string', enum: [...ACTIONS], description: 'What to do' },
36
+ command: { type: 'string', description: 'start: the shell command' },
37
+ label: { type: 'string', description: 'start: a short name shown to the user, e.g. "dev server"' },
38
+ notify_on: { type: 'string', description: 'start: regex; a matching output line is reported to you' },
39
+ max_runtime_seconds: { type: 'number', description: 'start: stop the task after this many seconds (default: no limit)' },
40
+ id: { type: 'string', description: 'output / wait / stop: the task id (bg1, bg2, ...)' },
41
+ mode: { type: 'string', enum: ['new', 'tail'], description: 'output: "new" (default) = since the last read, "tail" = last lines' },
42
+ lines: { type: 'number', description: 'output: how many lines at most (default 200 for new, 40 for tail)' },
43
+ grep: { type: 'string', description: 'output: only lines that match this regex' },
44
+ pattern: { type: 'string', description: 'wait: regex of an output line to wait for (default: wait for the end)' },
45
+ timeout_seconds: { type: 'number', description: 'wait: give up after this long (default 60, max 600)' },
46
+ },
47
+ required: ['action'],
48
+ },
49
+ },
50
+ validateArgs(args) {
51
+ if (!args || !ACTIONS.includes(args.action))
52
+ throw new Error(`action must be one of: ${ACTIONS.join(', ')}`);
53
+ if (args.action === 'start' && (!args.command || typeof args.command !== 'string'))
54
+ throw new Error('command is required for "start"');
55
+ if (['output', 'wait', 'stop'].includes(args.action) && !args.id)
56
+ throw new Error(`id is required for "${args.action}" (see action "list")`);
57
+ },
58
+ async execute(args, _config, context) {
59
+ const manager = getBackgroundManager();
60
+ const action = args.action;
61
+ try {
62
+ if (action === 'start') {
63
+ const info = manager.start(args.command, {
64
+ label: args.label,
65
+ notifyOn: args.notify_on,
66
+ maxRuntimeSeconds: Number(args.max_runtime_seconds) || 0,
67
+ });
68
+ // Give it a moment: a command that dies at once (wrong script, busy port) should be seen now.
69
+ const first = await manager.wait(info.id, { timeoutSeconds: 1.5, signal: context?.signal });
70
+ const now = manager.get(info.id);
71
+ const out = manager.read(info.id, { mode: 'new', lines: 30 });
72
+ const head = now.status === 'running'
73
+ ? `Started ${now.id} (pid ${now.pid ?? '?'}): ${now.label}. It runs in the background.`
74
+ : `${describeTask(now)} - it ended right after the start.`;
75
+ const hint = now.status === 'running'
76
+ ? '\nYou will be told when it ends' + (now.watching ? ' or prints a line matching the pattern' : '') + '. Use "output" to read more, "wait" to wait for a line.'
77
+ : '';
78
+ return `${head}${hint}\n${outputBlock(out.text, out.omitted)}${first.reason === 'aborted' ? '\n[Operation cancelled by user]' : ''}`;
79
+ }
80
+ if (action === 'list') {
81
+ const tasks = manager.list();
82
+ return tasks.length === 0 ? 'No background tasks.' : tasks.map((t) => describeTask(t)).join('\n');
83
+ }
84
+ if (action === 'output') {
85
+ const info = manager.get(String(args.id));
86
+ const out = manager.read(String(args.id), {
87
+ mode: args.mode === 'tail' ? 'tail' : 'new',
88
+ lines: args.lines,
89
+ grep: args.grep,
90
+ });
91
+ return `${info ? describeTask(info) : args.id}\n${outputBlock(out.text, out.omitted)}`;
92
+ }
93
+ if (action === 'wait') {
94
+ const result = await manager.wait(String(args.id), {
95
+ pattern: args.pattern,
96
+ timeoutSeconds: Number(args.timeout_seconds) || 60,
97
+ signal: context?.signal,
98
+ });
99
+ const info = manager.get(String(args.id));
100
+ const out = manager.read(String(args.id), { mode: 'new', lines: 60 });
101
+ const why = result.reason === 'match' ? `A line matched: ${result.line}`
102
+ : result.reason === 'exit' ? 'The task has ended.'
103
+ : result.reason === 'timeout' ? 'Timed out; the task is still running.'
104
+ : '[Operation cancelled by user]';
105
+ return `${why}\n${describeTask(info)}\n${outputBlock(out.text, out.omitted)}`;
106
+ }
107
+ // stop
108
+ const before = manager.get(String(args.id));
109
+ if (before && before.status !== 'running')
110
+ return `${describeTask(before)} (already ended)`;
111
+ const info = await manager.stop(String(args.id));
112
+ return `Stopped. ${describeTask(info)}`;
113
+ }
114
+ catch (err) {
115
+ return `Error: ${err.message}`;
116
+ }
117
+ },
118
+ };
@@ -63,6 +63,8 @@ export function runShell(command, options = {}) {
63
63
  const stderrDecoder = new StringDecoder('utf8');
64
64
  let stopReason = '';
65
65
  let killTimer;
66
+ let killRepeat;
67
+ let settled = false;
66
68
  const killProcessTree = (force) => {
67
69
  if (!proc.pid)
68
70
  return;
@@ -90,6 +92,10 @@ export function runShell(command, options = {}) {
90
92
  killProcessTree(false);
91
93
  // Commands can ignore SIGTERM. Do not leave them running after cancellation.
92
94
  killTimer = setTimeout(() => killProcessTree(true), 1000);
95
+ // On Windows a command such as `npm test` starts its child a moment later, after the first
96
+ // kill has looked at the tree: kill again until the process is gone.
97
+ if (isWin)
98
+ killRepeat = setInterval(() => killProcessTree(true), 700);
93
99
  };
94
100
  const onAbort = () => stop('[Operation cancelled by user]');
95
101
  const timeout = setTimeout(() => stop(`[Process killed due to timeout after ${timeoutSeconds}s. ` +
@@ -108,14 +114,30 @@ export function runShell(command, options = {}) {
108
114
  const cleanup = () => {
109
115
  clearTimeout(timeout);
110
116
  clearTimeout(killTimer);
117
+ clearInterval(killRepeat);
111
118
  signal?.removeEventListener('abort', onAbort);
112
119
  };
113
- proc.on('close', (code) => {
120
+ const finish = (code) => {
121
+ if (settled)
122
+ return;
123
+ settled = true;
114
124
  if (stopReason)
115
125
  killProcessTree(true);
116
126
  output.push(stdoutDecoder.end() + stderrDecoder.end());
117
127
  cleanup();
118
128
  resolve({ output: output.toString(), code, stopReason });
129
+ };
130
+ proc.on('close', finish);
131
+ // After a cancel or a timeout the shell is gone, but a child that got away can keep the pipes
132
+ // open for good: do not wait for them for more than a moment.
133
+ proc.on('exit', (code) => {
134
+ if (!stopReason)
135
+ return;
136
+ setTimeout(() => {
137
+ proc.stdout?.destroy();
138
+ proc.stderr?.destroy();
139
+ finish(code);
140
+ }, 300);
119
141
  });
120
142
  proc.on('error', (err) => {
121
143
  cleanup();
@@ -1,5 +1,6 @@
1
1
  import { readFileTool, writeFileTool, editFileTool, listDirTool, mkdirTool } from './fs.js';
2
2
  import { bashTool } from './bash.js';
3
+ import { backgroundTool } from './background.js';
3
4
  import { searchTool } from './search.js';
4
5
  import { askQuestionsTool } from './questions.js';
5
6
  import { webSearchTool, fetchUrlTool } from './web.js';
@@ -17,6 +18,6 @@ export function getTools(planMode) {
17
18
  if (planMode) {
18
19
  return [...baseTools, ...mcpTools];
19
20
  }
20
- return [...baseTools, writeFileTool, editFileTool, mkdirTool, bashTool, diagnoseCodeTool, installPackageTool, servePreviewTool, mcpManageTool, ...mcpTools];
21
+ return [...baseTools, writeFileTool, editFileTool, mkdirTool, bashTool, backgroundTool, diagnoseCodeTool, installPackageTool, servePreviewTool, mcpManageTool, ...mcpTools];
21
22
  }
22
23
  export { webSearchTool, fetchUrlTool, diagnoseCodeTool, installPackageTool, saveMemoryTool, planReadyTool, lastPlanReady, resetPlanReady, todoListTool, currentTodoList, resetTodoList, servePreviewTool };