wtagent 0.3.0 → 1.0.0
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.
- package/README.md +163 -9
- package/package.json +9 -8
- package/src/artifacts/artifact-store.js +154 -0
- package/src/audio/music-artifact.js +66 -0
- package/src/audio/native-music-receiver.js +91 -0
- package/src/browser/base-web-adapter.js +2418 -201
- package/src/browser/cdp-browser.js +223 -25
- package/src/browser/cdp-state.js +8 -5
- package/src/browser/chatgpt-dom.js +179 -0
- package/src/browser/chatgpt-web-adapter.js +831 -69
- package/src/browser/claude-web-adapter.js +13 -0
- package/src/browser/fake-web-model-adapter.js +152 -3
- package/src/browser/gemini-web-adapter.js +71 -20
- package/src/browser/glm-web-adapter.js +2 -2
- package/src/browser/grok-web-adapter.js +35 -3
- package/src/browser/rendered-text.js +11 -0
- package/src/cli/i18n.js +50 -20
- package/src/cli/main.js +124 -40
- package/src/cli/prompt-input.js +68 -0
- package/src/image/browser-image-driver.js +238 -0
- package/src/image/browser-image-session-pool.js +96 -0
- package/src/image/image-generation-service.js +101 -0
- package/src/image/image-provider-driver.js +22 -0
- package/src/image/native-image-receiver.js +66 -0
- package/src/image/provider-registry.js +23 -0
- package/src/image/providers/chatgpt-image-driver.js +211 -0
- package/src/image/providers/gemini-image-driver.js +100 -0
- package/src/image/providers/grok-image-driver.js +193 -0
- package/src/platform/command-launcher.js +1 -1
- package/src/platform/paths.js +19 -0
- package/src/platform/windows-diagnostics.js +9 -16
- package/src/policy/path-guard.js +5 -1
- package/src/policy/policy-engine.js +10 -5
- package/src/protocol/envelope.js +74 -0
- package/src/protocol/markers.js +16 -0
- package/src/protocol/prompt-builder.js +12 -9
- package/src/protocol/xml-protocol.js +36 -18
- package/src/runtime/agent-runtime.js +1383 -238
- package/src/session/agent-session.js +1047 -121
- package/src/tools/default-tools.js +1 -1
- package/src/tools/image-tools.js +92 -0
- package/src/tools/registry.js +13 -1
- package/docs/technical-design.md +0 -866
package/src/cli/i18n.js
CHANGED
|
@@ -20,6 +20,7 @@ const MESSAGES = Object.freeze({
|
|
|
20
20
|
"option.project": "Project directory",
|
|
21
21
|
"option.model": "Web AI provider: {providers} (default: {defaultProvider})",
|
|
22
22
|
"option.once": "Run a single request and exit instead of a conversation",
|
|
23
|
+
"option.type": "Request type: agent (default) or music (Gemini only)",
|
|
23
24
|
"option.timeout": "Maximum wait for one model response (default: 10 minutes)",
|
|
24
25
|
"option.noMinimize": "Keep the Chrome window visible instead of minimizing it",
|
|
25
26
|
"option.debug": "Write browser diagnostics",
|
|
@@ -42,7 +43,9 @@ const MESSAGES = Object.freeze({
|
|
|
42
43
|
"login.savingProfile": "Closing native Chrome and saving the dedicated profile...",
|
|
43
44
|
"login.verified": "{provider} login verified through a fresh CDP connection.",
|
|
44
45
|
"login.stillGuest": "{provider} is still in guest mode. Reopening native Chrome; complete the final {provider} sign-in/continue step.",
|
|
45
|
-
"model.chooseInBrowser": "{provider} is signed in
|
|
46
|
+
"model.chooseInBrowser": "{provider} is signed in.\nYou can change the model in the browser.\nPress Enter to continue, or wait 5 seconds.",
|
|
47
|
+
"model.continueCountdown": "Continuing in {seconds}s…",
|
|
48
|
+
"model.continuing": "Continuing…",
|
|
46
49
|
"model.setupCancelled": "Model selection confirmation cancelled.",
|
|
47
50
|
"logout.none": "No Chrome profile found at {profileDir}; already logged out.",
|
|
48
51
|
"logout.refuse": "Refusing to delete {profileDir}: it does not look like a wtagent Chrome profile.",
|
|
@@ -68,7 +71,7 @@ const MESSAGES = Object.freeze({
|
|
|
68
71
|
"session.savedAt": "Session saved at: {directory}",
|
|
69
72
|
"session.ctrlC": "Ctrl+C — cancelling. Press again to force quit.",
|
|
70
73
|
"session.cancelledHint": "Turn cancelled. Type a new message or quit.",
|
|
71
|
-
"session.recoveryHint": "Type /retry to
|
|
74
|
+
"session.recoveryHint": "Type /retry to recover the pending {provider} turn, or quit",
|
|
72
75
|
"session.processesRunning": "Managed processes still running:",
|
|
73
76
|
"session.resumeWith": "Resume this conversation with: wtagent resume {sessionId}",
|
|
74
77
|
"attachment.attaching": "Attaching: {files}",
|
|
@@ -106,6 +109,7 @@ const MESSAGES = Object.freeze({
|
|
|
106
109
|
"option.project": "项目目录",
|
|
107
110
|
"option.model": "网页 AI 提供商:{providers}(默认:{defaultProvider})",
|
|
108
111
|
"option.once": "只执行一次请求并退出,不进入连续对话",
|
|
112
|
+
"option.type": "请求类型:agent(默认)或 music(仅 Gemini)",
|
|
109
113
|
"option.timeout": "单次模型响应最大等待时间(默认:10 分钟)",
|
|
110
114
|
"option.noMinimize": "保持 Chrome 窗口可见,不自动最小化",
|
|
111
115
|
"option.debug": "写入浏览器诊断信息",
|
|
@@ -128,7 +132,9 @@ const MESSAGES = Object.freeze({
|
|
|
128
132
|
"login.savingProfile": "正在关闭原生 Chrome 并保存专用配置...",
|
|
129
133
|
"login.verified": "已通过新的 CDP 连接验证 {provider} 登录状态。",
|
|
130
134
|
"login.stillGuest": "{provider} 仍处于访客模式。正在重新打开 Chrome;请完成最后的登录/继续步骤。",
|
|
131
|
-
"model.chooseInBrowser": "{provider}
|
|
135
|
+
"model.chooseInBrowser": "{provider} 已登录。\n如需切换模型,请在浏览器中选择。\n按 Enter 立即继续,或等待 5 秒自动继续。",
|
|
136
|
+
"model.continueCountdown": "{seconds} 秒后自动继续…",
|
|
137
|
+
"model.continuing": "正在继续…",
|
|
132
138
|
"model.setupCancelled": "模型选择确认已取消。",
|
|
133
139
|
"logout.none": "在 {profileDir} 未找到 Chrome 配置;当前已是登出状态。",
|
|
134
140
|
"logout.refuse": "拒绝删除 {profileDir}:它看起来不是 wtagent 的 Chrome 配置目录。",
|
|
@@ -154,7 +160,7 @@ const MESSAGES = Object.freeze({
|
|
|
154
160
|
"session.savedAt": "会话已保存到:{directory}",
|
|
155
161
|
"session.ctrlC": "Ctrl+C — 正在取消。再次按下可强制退出。",
|
|
156
162
|
"session.cancelledHint": "本轮已取消。请输入新消息或退出。",
|
|
157
|
-
"session.recoveryHint": "输入 /retry
|
|
163
|
+
"session.recoveryHint": "输入 /retry 恢复待处理的 {provider} 回合,或退出",
|
|
158
164
|
"session.processesRunning": "仍在运行的托管进程:",
|
|
159
165
|
"session.resumeWith": "继续此对话:wtagent resume {sessionId}",
|
|
160
166
|
"attachment.attaching": "正在附加:{files}",
|
|
@@ -192,6 +198,7 @@ const MESSAGES = Object.freeze({
|
|
|
192
198
|
"option.project": "專案目錄",
|
|
193
199
|
"option.model": "網頁 AI 提供者:{providers}(預設:{defaultProvider})",
|
|
194
200
|
"option.once": "只執行一次請求並退出,不進入連續對話",
|
|
201
|
+
"option.type": "請求類型:agent(預設)或 music(僅 Gemini)",
|
|
195
202
|
"option.timeout": "單次模型回應最大等待時間(預設:10 分鐘)",
|
|
196
203
|
"option.noMinimize": "保持 Chrome 視窗可見,不自動最小化",
|
|
197
204
|
"option.debug": "寫入瀏覽器診斷資訊",
|
|
@@ -209,7 +216,9 @@ const MESSAGES = Object.freeze({
|
|
|
209
216
|
"login.savingProfile": "正在關閉原生 Chrome 並儲存專用設定檔...",
|
|
210
217
|
"login.verified": "已透過新的 CDP 連線驗證 {provider} 登入狀態。",
|
|
211
218
|
"login.stillGuest": "{provider} 仍處於訪客模式。正在重新開啟 Chrome;請完成最後的登入/繼續步驟。",
|
|
212
|
-
"model.chooseInBrowser": "{provider}
|
|
219
|
+
"model.chooseInBrowser": "{provider} 已登入。\n如需切換模型,請在瀏覽器中選擇。\n按 Enter 立即繼續,或等待 5 秒自動繼續。",
|
|
220
|
+
"model.continueCountdown": "{seconds} 秒後自動繼續…",
|
|
221
|
+
"model.continuing": "正在繼續…",
|
|
213
222
|
"model.setupCancelled": "模型選擇確認已取消。",
|
|
214
223
|
"task.youPrompt": "你 ›",
|
|
215
224
|
"option.yes": "略過確認提示",
|
|
@@ -241,7 +250,7 @@ const MESSAGES = Object.freeze({
|
|
|
241
250
|
"session.savedAt": "工作階段已儲存到:{directory}",
|
|
242
251
|
"session.ctrlC": "Ctrl+C — 正在取消。再次按下可強制退出。",
|
|
243
252
|
"session.cancelledHint": "本輪已取消。請輸入新訊息或退出。",
|
|
244
|
-
"session.recoveryHint": "輸入 /retry
|
|
253
|
+
"session.recoveryHint": "輸入 /retry 恢復待處理的 {provider} 回合,或退出",
|
|
245
254
|
"session.processesRunning": "仍在執行的受管程序:",
|
|
246
255
|
"session.resumeWith": "繼續此對話:wtagent resume {sessionId}",
|
|
247
256
|
"attachment.attaching": "正在附加:{files}",
|
|
@@ -278,6 +287,7 @@ const MESSAGES = Object.freeze({
|
|
|
278
287
|
"option.project": "プロジェクトディレクトリ",
|
|
279
288
|
"option.model": "Web AI プロバイダー: {providers}(既定: {defaultProvider})",
|
|
280
289
|
"option.once": "会話を継続せず、1 回のリクエストを実行して終了",
|
|
290
|
+
"option.type": "リクエストの種類: agent(既定)または music(Gemini のみ)",
|
|
281
291
|
"option.timeout": "1 回のモデル応答の最大待機時間(既定: 10 分)",
|
|
282
292
|
"option.noMinimize": "Chrome ウィンドウを最小化せず表示したままにする",
|
|
283
293
|
"option.debug": "ブラウザー診断を書き出す",
|
|
@@ -295,7 +305,9 @@ const MESSAGES = Object.freeze({
|
|
|
295
305
|
"login.savingProfile": "ネイティブ Chrome を閉じ、専用プロファイルを保存しています...",
|
|
296
306
|
"login.verified": "新しい CDP 接続で {provider} のログインを確認しました。",
|
|
297
307
|
"login.stillGuest": "{provider} はまだゲスト状態です。Chrome を再度開きますので、最後のサインイン/続行手順を完了してください。",
|
|
298
|
-
"model.chooseInBrowser": "{provider}
|
|
308
|
+
"model.chooseInBrowser": "{provider} にログイン済みです。\n必要ならブラウザーでモデルを変更できます。\nEnter で続行、または 5 秒後に自動で続行します。",
|
|
309
|
+
"model.continueCountdown": "{seconds} 秒後に続行…",
|
|
310
|
+
"model.continuing": "続行中…",
|
|
299
311
|
"model.setupCancelled": "モデル選択の確認をキャンセルしました。",
|
|
300
312
|
"task.youPrompt": "あなた ›",
|
|
301
313
|
"option.yes": "確認プロンプトを省略",
|
|
@@ -327,7 +339,7 @@ const MESSAGES = Object.freeze({
|
|
|
327
339
|
"session.savedAt": "セッション保存先: {directory}",
|
|
328
340
|
"session.ctrlC": "Ctrl+C — キャンセル中です。もう一度押すと強制終了します。",
|
|
329
341
|
"session.cancelledHint": "このターンはキャンセルされました。新しいメッセージを入力するか終了してください。",
|
|
330
|
-
"session.recoveryHint": "/retry
|
|
342
|
+
"session.recoveryHint": "/retry で保留中の {provider} ターンを復旧するか、終了してください",
|
|
331
343
|
"session.processesRunning": "管理対象プロセスが実行中です:",
|
|
332
344
|
"session.resumeWith": "この会話を再開: wtagent resume {sessionId}",
|
|
333
345
|
"attachment.attaching": "添付中: {files}",
|
|
@@ -364,6 +376,7 @@ const MESSAGES = Object.freeze({
|
|
|
364
376
|
"option.project": "프로젝트 디렉터리",
|
|
365
377
|
"option.model": "웹 AI 제공자: {providers} (기본값: {defaultProvider})",
|
|
366
378
|
"option.once": "대화를 계속하지 않고 요청 한 번만 실행한 뒤 종료",
|
|
379
|
+
"option.type": "요청 유형: agent(기본값) 또는 music(Gemini 전용)",
|
|
367
380
|
"option.timeout": "모델 응답 1회의 최대 대기 시간 (기본값: 10분)",
|
|
368
381
|
"option.noMinimize": "Chrome 창을 최소화하지 않고 표시",
|
|
369
382
|
"option.debug": "브라우저 진단 정보 기록",
|
|
@@ -381,7 +394,9 @@ const MESSAGES = Object.freeze({
|
|
|
381
394
|
"login.savingProfile": "네이티브 Chrome을 닫고 전용 프로필을 저장하는 중...",
|
|
382
395
|
"login.verified": "새 CDP 연결을 통해 {provider} 로그인을 확인했습니다.",
|
|
383
396
|
"login.stillGuest": "{provider}가 아직 게스트 모드입니다. Chrome을 다시 열고 마지막 로그인/계속 단계를 완료하세요.",
|
|
384
|
-
"model.chooseInBrowser": "{provider}에
|
|
397
|
+
"model.chooseInBrowser": "{provider}에 로그인되었습니다.\n필요하면 브라우저에서 모델을 변경하세요.\nEnter를 누르거나 5초 후 자동으로 계속합니다.",
|
|
398
|
+
"model.continueCountdown": "{seconds}초 후 계속…",
|
|
399
|
+
"model.continuing": "계속하는 중…",
|
|
385
400
|
"model.setupCancelled": "모델 선택 확인이 취소되었습니다.",
|
|
386
401
|
"task.youPrompt": "나 ›",
|
|
387
402
|
"option.yes": "확인 프롬프트 건너뛰기",
|
|
@@ -413,7 +428,7 @@ const MESSAGES = Object.freeze({
|
|
|
413
428
|
"session.savedAt": "세션 저장 위치: {directory}",
|
|
414
429
|
"session.ctrlC": "Ctrl+C — 취소 중입니다. 다시 누르면 강제 종료합니다.",
|
|
415
430
|
"session.cancelledHint": "이번 턴이 취소되었습니다. 새 메시지를 입력하거나 종료하세요.",
|
|
416
|
-
"session.recoveryHint": "/retry로 {provider}
|
|
431
|
+
"session.recoveryHint": "/retry로 보류 중인 {provider} 턴을 복구하거나 종료하세요",
|
|
417
432
|
"session.processesRunning": "관리 중인 프로세스가 실행 중입니다:",
|
|
418
433
|
"session.resumeWith": "이 대화 재개: wtagent resume {sessionId}",
|
|
419
434
|
"attachment.attaching": "첨부 중: {files}",
|
|
@@ -450,6 +465,7 @@ const MESSAGES = Object.freeze({
|
|
|
450
465
|
"option.project": "Directorio del proyecto",
|
|
451
466
|
"option.model": "Proveedor de IA web: {providers} (predeterminado: {defaultProvider})",
|
|
452
467
|
"option.once": "Ejecutar una sola solicitud y salir en lugar de mantener una conversación",
|
|
468
|
+
"option.type": "Tipo de solicitud: agent (predeterminado) o music (solo Gemini)",
|
|
453
469
|
"option.timeout": "Espera máxima por respuesta del modelo (predeterminado: 10 minutos)",
|
|
454
470
|
"option.noMinimize": "Mantener visible la ventana de Chrome",
|
|
455
471
|
"option.debug": "Guardar diagnósticos del navegador",
|
|
@@ -467,7 +483,9 @@ const MESSAGES = Object.freeze({
|
|
|
467
483
|
"login.savingProfile": "Cerrando Chrome y guardando el perfil dedicado...",
|
|
468
484
|
"login.verified": "Inicio de sesión de {provider} verificado mediante una nueva conexión CDP.",
|
|
469
485
|
"login.stillGuest": "{provider} sigue en modo invitado. Se volverá a abrir Chrome; completa el último paso de inicio de sesión/continuación.",
|
|
470
|
-
"model.chooseInBrowser": "{provider} ha iniciado sesión
|
|
486
|
+
"model.chooseInBrowser": "{provider} ha iniciado sesión.\nPuedes cambiar el modelo en el navegador.\nPulsa Enter para continuar o espera 5 segundos.",
|
|
487
|
+
"model.continueCountdown": "Continuando en {seconds}s…",
|
|
488
|
+
"model.continuing": "Continuando…",
|
|
471
489
|
"model.setupCancelled": "Se canceló la confirmación de selección del modelo.",
|
|
472
490
|
"task.youPrompt": "tú ›",
|
|
473
491
|
"option.yes": "Omitir la confirmación",
|
|
@@ -499,7 +517,7 @@ const MESSAGES = Object.freeze({
|
|
|
499
517
|
"session.savedAt": "Sesión guardada en: {directory}",
|
|
500
518
|
"session.ctrlC": "Ctrl+C — cancelando. Pulsa de nuevo para forzar la salida.",
|
|
501
519
|
"session.cancelledHint": "Turno cancelado. Escribe un nuevo mensaje o sal.",
|
|
502
|
-
"session.recoveryHint": "Escribe /retry para
|
|
520
|
+
"session.recoveryHint": "Escribe /retry para recuperar el turno pendiente de {provider}, o sal",
|
|
503
521
|
"session.processesRunning": "Procesos administrados aún en ejecución:",
|
|
504
522
|
"session.resumeWith": "Reanuda esta conversación con: wtagent resume {sessionId}",
|
|
505
523
|
"attachment.attaching": "Adjuntando: {files}",
|
|
@@ -536,6 +554,7 @@ const MESSAGES = Object.freeze({
|
|
|
536
554
|
"option.project": "Répertoire du projet",
|
|
537
555
|
"option.model": "Fournisseur d’IA web : {providers} (par défaut : {defaultProvider})",
|
|
538
556
|
"option.once": "Exécuter une seule requête puis quitter",
|
|
557
|
+
"option.type": "Type de requête : agent (par défaut) ou music (Gemini uniquement)",
|
|
539
558
|
"option.timeout": "Attente maximale d’une réponse du modèle (10 minutes par défaut)",
|
|
540
559
|
"option.noMinimize": "Garder la fenêtre Chrome visible",
|
|
541
560
|
"option.debug": "Écrire les diagnostics du navigateur",
|
|
@@ -553,7 +572,9 @@ const MESSAGES = Object.freeze({
|
|
|
553
572
|
"login.savingProfile": "Fermeture de Chrome et enregistrement du profil dédié...",
|
|
554
573
|
"login.verified": "Connexion à {provider} vérifiée via une nouvelle connexion CDP.",
|
|
555
574
|
"login.stillGuest": "{provider} est encore en mode invité. Chrome va être rouvert ; terminez la dernière étape de connexion/continuation.",
|
|
556
|
-
"model.chooseInBrowser": "{provider} est connect
|
|
575
|
+
"model.chooseInBrowser": "{provider} est connecté.\nVous pouvez changer de modèle dans le navigateur.\nAppuyez sur Entrée pour continuer ou attendez 5 secondes.",
|
|
576
|
+
"model.continueCountdown": "Reprise dans {seconds}s…",
|
|
577
|
+
"model.continuing": "Reprise…",
|
|
557
578
|
"model.setupCancelled": "La confirmation du choix du modèle a été annulée.",
|
|
558
579
|
"task.youPrompt": "vous ›",
|
|
559
580
|
"option.yes": "Ignorer la demande de confirmation",
|
|
@@ -585,7 +606,7 @@ const MESSAGES = Object.freeze({
|
|
|
585
606
|
"session.savedAt": "Session enregistrée dans : {directory}",
|
|
586
607
|
"session.ctrlC": "Ctrl+C — annulation en cours. Appuyez encore pour forcer l’arrêt.",
|
|
587
608
|
"session.cancelledHint": "Tour annulé. Saisissez un nouveau message ou quittez.",
|
|
588
|
-
"session.recoveryHint": "Saisissez /retry pour
|
|
609
|
+
"session.recoveryHint": "Saisissez /retry pour récupérer le tour {provider} en attente, ou quittez",
|
|
589
610
|
"session.processesRunning": "Processus gérés encore actifs :",
|
|
590
611
|
"session.resumeWith": "Reprendre cette conversation avec : wtagent resume {sessionId}",
|
|
591
612
|
"attachment.attaching": "Ajout de pièces jointes : {files}",
|
|
@@ -622,6 +643,7 @@ const MESSAGES = Object.freeze({
|
|
|
622
643
|
"option.project": "Projektverzeichnis",
|
|
623
644
|
"option.model": "Web-AI-Anbieter: {providers} (Standard: {defaultProvider})",
|
|
624
645
|
"option.once": "Nur eine Anfrage ausführen und danach beenden",
|
|
646
|
+
"option.type": "Anfragetyp: agent (Standard) oder music (nur Gemini)",
|
|
625
647
|
"option.timeout": "Maximale Wartezeit pro Modellantwort (Standard: 10 Minuten)",
|
|
626
648
|
"option.noMinimize": "Chrome-Fenster sichtbar lassen",
|
|
627
649
|
"option.debug": "Browserdiagnose schreiben",
|
|
@@ -639,7 +661,9 @@ const MESSAGES = Object.freeze({
|
|
|
639
661
|
"login.savingProfile": "Chrome wird geschlossen und das dedizierte Profil gespeichert...",
|
|
640
662
|
"login.verified": "{provider}-Anmeldung über eine neue CDP-Verbindung bestätigt.",
|
|
641
663
|
"login.stillGuest": "{provider} befindet sich noch im Gastmodus. Chrome wird erneut geöffnet; schließe den letzten Anmelde-/Fortsetzen-Schritt ab.",
|
|
642
|
-
"model.chooseInBrowser": "{provider} ist angemeldet
|
|
664
|
+
"model.chooseInBrowser": "{provider} ist angemeldet.\nDu kannst das Modell im Browser ändern.\nDrücke Enter zum Fortfahren oder warte 5 Sekunden.",
|
|
665
|
+
"model.continueCountdown": "Weiter in {seconds}s…",
|
|
666
|
+
"model.continuing": "Weiter…",
|
|
643
667
|
"model.setupCancelled": "Bestätigung der Modellauswahl abgebrochen.",
|
|
644
668
|
"task.youPrompt": "du ›",
|
|
645
669
|
"option.yes": "Bestätigungsabfrage überspringen",
|
|
@@ -671,7 +695,7 @@ const MESSAGES = Object.freeze({
|
|
|
671
695
|
"session.savedAt": "Sitzung gespeichert unter: {directory}",
|
|
672
696
|
"session.ctrlC": "Ctrl+C — wird abgebrochen. Erneut drücken, um das Beenden zu erzwingen.",
|
|
673
697
|
"session.cancelledHint": "Durchgang abgebrochen. Neue Nachricht eingeben oder beenden.",
|
|
674
|
-
"session.recoveryHint": "Gib /retry ein, um {provider}
|
|
698
|
+
"session.recoveryHint": "Gib /retry ein, um den ausstehenden {provider}-Durchlauf wiederherzustellen, oder beende",
|
|
675
699
|
"session.processesRunning": "Verwaltete Prozesse laufen noch:",
|
|
676
700
|
"session.resumeWith": "Diese Unterhaltung fortsetzen mit: wtagent resume {sessionId}",
|
|
677
701
|
"attachment.attaching": "Anhängen: {files}",
|
|
@@ -708,6 +732,7 @@ const MESSAGES = Object.freeze({
|
|
|
708
732
|
"option.project": "Diretório do projeto",
|
|
709
733
|
"option.model": "Provedor de IA web: {providers} (padrão: {defaultProvider})",
|
|
710
734
|
"option.once": "Executar apenas uma solicitação e sair",
|
|
735
|
+
"option.type": "Tipo de solicitação: agent (padrão) ou music (somente Gemini)",
|
|
711
736
|
"option.timeout": "Espera máxima por uma resposta do modelo (padrão: 10 minutos)",
|
|
712
737
|
"option.noMinimize": "Manter a janela do Chrome visível",
|
|
713
738
|
"option.debug": "Gravar diagnósticos do navegador",
|
|
@@ -725,7 +750,9 @@ const MESSAGES = Object.freeze({
|
|
|
725
750
|
"login.savingProfile": "Fechando o Chrome e salvando o perfil dedicado...",
|
|
726
751
|
"login.verified": "Login de {provider} verificado por uma nova conexão CDP.",
|
|
727
752
|
"login.stillGuest": "{provider} ainda está no modo visitante. O Chrome será reaberto; conclua a última etapa de login/continuação.",
|
|
728
|
-
"model.chooseInBrowser": "{provider} está conectado
|
|
753
|
+
"model.chooseInBrowser": "{provider} está conectado.\nVocê pode alterar o modelo no navegador.\nPressione Enter para continuar ou aguarde 5 segundos.",
|
|
754
|
+
"model.continueCountdown": "Continuando em {seconds}s…",
|
|
755
|
+
"model.continuing": "Continuando…",
|
|
729
756
|
"model.setupCancelled": "A confirmação da seleção do modelo foi cancelada.",
|
|
730
757
|
"task.youPrompt": "você ›",
|
|
731
758
|
"option.yes": "Ignorar a confirmação",
|
|
@@ -757,7 +784,7 @@ const MESSAGES = Object.freeze({
|
|
|
757
784
|
"session.savedAt": "Sessão salva em: {directory}",
|
|
758
785
|
"session.ctrlC": "Ctrl+C — cancelando. Pressione novamente para forçar a saída.",
|
|
759
786
|
"session.cancelledHint": "Turno cancelado. Digite uma nova mensagem ou saia.",
|
|
760
|
-
"session.recoveryHint": "Digite /retry para
|
|
787
|
+
"session.recoveryHint": "Digite /retry para recuperar o turno pendente do {provider}, ou saia",
|
|
761
788
|
"session.processesRunning": "Processos gerenciados ainda em execução:",
|
|
762
789
|
"session.resumeWith": "Retome esta conversa com: wtagent resume {sessionId}",
|
|
763
790
|
"attachment.attaching": "Anexando: {files}",
|
|
@@ -794,6 +821,7 @@ const MESSAGES = Object.freeze({
|
|
|
794
821
|
"option.project": "Каталог проекта",
|
|
795
822
|
"option.model": "Провайдер веб-ИИ: {providers} (по умолчанию: {defaultProvider})",
|
|
796
823
|
"option.once": "Выполнить один запрос и завершить работу",
|
|
824
|
+
"option.type": "Тип запроса: agent (по умолчанию) или music (только Gemini)",
|
|
797
825
|
"option.timeout": "Максимальное ожидание ответа модели (по умолчанию: 10 минут)",
|
|
798
826
|
"option.noMinimize": "Не сворачивать окно Chrome",
|
|
799
827
|
"option.debug": "Записывать диагностику браузера",
|
|
@@ -811,7 +839,9 @@ const MESSAGES = Object.freeze({
|
|
|
811
839
|
"login.savingProfile": "Chrome закрывается, отдельный профиль сохраняется...",
|
|
812
840
|
"login.verified": "Вход в {provider} подтверждён через новое соединение CDP.",
|
|
813
841
|
"login.stillGuest": "{provider} всё ещё работает в гостевом режиме. Chrome будет открыт снова; завершите последний шаг входа/продолжения.",
|
|
814
|
-
"model.chooseInBrowser": "{provider}: вход
|
|
842
|
+
"model.chooseInBrowser": "{provider}: вход выполнен.\nПри необходимости смените модель в браузере.\nНажмите Enter или подождите 5 секунд для продолжения.",
|
|
843
|
+
"model.continueCountdown": "Продолжение через {seconds} с…",
|
|
844
|
+
"model.continuing": "Продолжение…",
|
|
815
845
|
"model.setupCancelled": "Подтверждение выбора модели отменено.",
|
|
816
846
|
"task.youPrompt": "вы ›",
|
|
817
847
|
"option.yes": "Пропустить запрос подтверждения",
|
|
@@ -843,7 +873,7 @@ const MESSAGES = Object.freeze({
|
|
|
843
873
|
"session.savedAt": "Сессия сохранена в: {directory}",
|
|
844
874
|
"session.ctrlC": "Ctrl+C — отмена. Нажмите ещё раз для принудительного выхода.",
|
|
845
875
|
"session.cancelledHint": "Ход отменён. Введите новое сообщение или выйдите.",
|
|
846
|
-
"session.recoveryHint": "Введите /retry, чтобы
|
|
876
|
+
"session.recoveryHint": "Введите /retry, чтобы восстановить ожидающий ход {provider}, или выйдите",
|
|
847
877
|
"session.processesRunning": "Управляемые процессы всё ещё запущены:",
|
|
848
878
|
"session.resumeWith": "Продолжить этот диалог: wtagent resume {sessionId}",
|
|
849
879
|
"attachment.attaching": "Прикрепление: {files}",
|
package/src/cli/main.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
import { confirm, select } from "@inquirer/prompts";
|
|
6
7
|
import { launchNativeLoginBrowser } from "../browser/native-login.js";
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
} from "../browser/provider-registry.js";
|
|
16
17
|
import {
|
|
17
18
|
ensureDirectory,
|
|
19
|
+
formatHomePath,
|
|
18
20
|
getAppDataDir,
|
|
19
21
|
getSessionsDir,
|
|
20
22
|
getTasksDir,
|
|
@@ -31,13 +33,17 @@ import { PolicyEngine } from "../policy/policy-engine.js";
|
|
|
31
33
|
import { ApprovalStore } from "../policy/approval-store.js";
|
|
32
34
|
import { createDefaultToolRegistry } from "../tools/default-tools.js";
|
|
33
35
|
import { ProcessManager } from "../tools/process-manager.js";
|
|
36
|
+
import { NativeImageReceiver } from "../image/native-image-receiver.js";
|
|
37
|
+
import { NativeMusicReceiver, validateGenerationType } from "../audio/native-music-receiver.js";
|
|
34
38
|
import { resolveLimits } from "../shared/limits.js";
|
|
39
|
+
import { BrowserAdapterError } from "../shared/errors.js";
|
|
35
40
|
import { EXPORTERS } from "../session/session-export.js";
|
|
36
41
|
import { getPackageVersion } from "../shared/package-info.js";
|
|
37
42
|
import { extractAtMentions } from "./at-files.js";
|
|
38
43
|
import {
|
|
39
44
|
classifyChatInput,
|
|
40
45
|
promptForConfirm,
|
|
46
|
+
promptForContinue,
|
|
41
47
|
promptForText,
|
|
42
48
|
promptForSelect,
|
|
43
49
|
readChatMessage,
|
|
@@ -238,6 +244,9 @@ class ConversationRunner {
|
|
|
238
244
|
// interactive TTY sessions where stdin is available to listen on.
|
|
239
245
|
cancelOnEsc: interactive,
|
|
240
246
|
});
|
|
247
|
+
const generationType = validateGenerationType(session.state.generationType, provider.id);
|
|
248
|
+
this.nativeMusicReceiver = generationType === "music" ? new NativeMusicReceiver({ adapter: this.adapter }) : null;
|
|
249
|
+
this.nativeImageReceiver = this.nativeMusicReceiver ? null : NativeImageReceiver.forAdapter(provider.id, this.adapter);
|
|
241
250
|
this.interrupted = false;
|
|
242
251
|
this.closed = false;
|
|
243
252
|
}
|
|
@@ -245,6 +254,8 @@ class ConversationRunner {
|
|
|
245
254
|
#buildRuntime() {
|
|
246
255
|
return new AgentRuntime({
|
|
247
256
|
adapter: this.adapter,
|
|
257
|
+
nativeImageReceiver: this.nativeImageReceiver,
|
|
258
|
+
nativeMusicReceiver: this.nativeMusicReceiver,
|
|
248
259
|
registry: createDefaultToolRegistry({
|
|
249
260
|
processManager: this.processManager,
|
|
250
261
|
limits: this.limits,
|
|
@@ -258,10 +269,12 @@ class ConversationRunner {
|
|
|
258
269
|
this.renderer.stopSpinner();
|
|
259
270
|
await adapter.restoreWindow?.();
|
|
260
271
|
try {
|
|
261
|
-
const answer = await
|
|
272
|
+
const answer = await promptForContinue({
|
|
262
273
|
message: t("model.chooseInBrowser", {
|
|
263
274
|
provider: this.provider.label,
|
|
264
275
|
}),
|
|
276
|
+
countdown: (seconds) => t(seconds > 0
|
|
277
|
+
? "model.continueCountdown" : "model.continuing", { seconds }),
|
|
265
278
|
});
|
|
266
279
|
if (answer == null) {
|
|
267
280
|
throw new Error(t("model.setupCancelled"));
|
|
@@ -301,13 +314,13 @@ class ConversationRunner {
|
|
|
301
314
|
await this.approvalStore.save();
|
|
302
315
|
console.log(t("approval.savedTool", {
|
|
303
316
|
tool: toolCall.name,
|
|
304
|
-
file: this.approvalStore.filePath,
|
|
317
|
+
file: formatHomePath(this.approvalStore.filePath),
|
|
305
318
|
}));
|
|
306
319
|
} else if (choice === "always-all") {
|
|
307
320
|
this.approvalStore.setAlwaysAllowAll();
|
|
308
321
|
await this.approvalStore.save();
|
|
309
322
|
console.log(t("approval.savedAll", {
|
|
310
|
-
file: this.approvalStore.filePath,
|
|
323
|
+
file: formatHomePath(this.approvalStore.filePath),
|
|
311
324
|
}));
|
|
312
325
|
}
|
|
313
326
|
return true;
|
|
@@ -435,8 +448,14 @@ async function waitForProcessToExit(pid, timeoutMs = 60_000) {
|
|
|
435
448
|
// session, ask the user how to proceed instead of failing outright: kill the
|
|
436
449
|
// other session, or have the user close its Chrome window so that session
|
|
437
450
|
// fails and releases the profile. Returns true when the turn should be retried.
|
|
438
|
-
async function promptToResolveProfileLock(error) {
|
|
451
|
+
export async function promptToResolveProfileLock(error) {
|
|
439
452
|
const pid = Number(error.details?.pid);
|
|
453
|
+
if (pid === process.pid) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
"The current WTAgent process still holds its browser profile lock. Refusing to terminate itself.",
|
|
456
|
+
{ cause: error },
|
|
457
|
+
);
|
|
458
|
+
}
|
|
440
459
|
console.log(`\n${"\x1b[33m"}${error.message}${"\x1b[0m"}`);
|
|
441
460
|
const choice = await promptForSelect({
|
|
442
461
|
message: t("profileLock.how"),
|
|
@@ -470,7 +489,7 @@ async function promptToResolveProfileLock(error) {
|
|
|
470
489
|
return true;
|
|
471
490
|
}
|
|
472
491
|
|
|
473
|
-
async function executeSession({
|
|
492
|
+
export async function executeSession({
|
|
474
493
|
session,
|
|
475
494
|
options,
|
|
476
495
|
resume = false,
|
|
@@ -478,11 +497,14 @@ async function executeSession({
|
|
|
478
497
|
files = [],
|
|
479
498
|
chatInput = null,
|
|
480
499
|
mode = null,
|
|
481
|
-
}
|
|
500
|
+
}, {
|
|
501
|
+
interactive = !options.once && process.stdin.isTTY && process.stdout.isTTY,
|
|
502
|
+
createRunner = (config) => new ConversationRunner(config),
|
|
503
|
+
readNextMessage = promptForNextMessage,
|
|
504
|
+
} = {}) {
|
|
482
505
|
const paths = resolveRuntimePaths(options);
|
|
483
506
|
await ensureDirectory(paths.sessionsDir);
|
|
484
|
-
const
|
|
485
|
-
const runner = new ConversationRunner({ session, options, interactive });
|
|
507
|
+
const runner = createRunner({ session, options, interactive });
|
|
486
508
|
|
|
487
509
|
const onInterrupt = () => {
|
|
488
510
|
if (runner.interrupted) {
|
|
@@ -545,9 +567,8 @@ async function executeSession({
|
|
|
545
567
|
throw result.error;
|
|
546
568
|
}
|
|
547
569
|
runner.renderer.hint(t("session.cancelledHint"));
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
continue;
|
|
570
|
+
// Fall through to the same input prompt as a completed turn. Never
|
|
571
|
+
// resume automatically or reuse the cancelled instruction/attachments.
|
|
551
572
|
}
|
|
552
573
|
if (result?.recoveryRequired) {
|
|
553
574
|
if (!interactive) {
|
|
@@ -558,11 +579,30 @@ async function executeSession({
|
|
|
558
579
|
provider: runner.renderer.providerLabel,
|
|
559
580
|
}),
|
|
560
581
|
);
|
|
561
|
-
const
|
|
582
|
+
const pendingRecovery = session.state.pendingOutbound != null
|
|
583
|
+
|| session.state.pendingAssistantTurn != null;
|
|
584
|
+
let next;
|
|
585
|
+
let retryOnly = false;
|
|
586
|
+
for (;;) {
|
|
587
|
+
next = await readNextMessage(runner, activeChatInput, {
|
|
588
|
+
resolveAttachments: !pendingRecovery,
|
|
589
|
+
});
|
|
590
|
+
if (next == null) {
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
retryOnly = next.text.trim().toLowerCase() === "/retry";
|
|
594
|
+
if (!pendingRecovery || retryOnly) {
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
runner.renderer.hint(
|
|
598
|
+
t("session.recoveryHint", {
|
|
599
|
+
provider: runner.renderer.providerLabel,
|
|
600
|
+
}),
|
|
601
|
+
);
|
|
602
|
+
}
|
|
562
603
|
if (next == null) {
|
|
563
604
|
break;
|
|
564
605
|
}
|
|
565
|
-
const retryOnly = next.text.trim().toLowerCase() === "/retry";
|
|
566
606
|
if (!retryOnly) {
|
|
567
607
|
await session.appendInstruction(next.text, { files: next.files });
|
|
568
608
|
}
|
|
@@ -593,7 +633,7 @@ async function executeSession({
|
|
|
593
633
|
}
|
|
594
634
|
}
|
|
595
635
|
|
|
596
|
-
const next = await
|
|
636
|
+
const next = await readNextMessage(runner, activeChatInput);
|
|
597
637
|
if (next == null) {
|
|
598
638
|
break;
|
|
599
639
|
}
|
|
@@ -630,7 +670,11 @@ function printResumeHint(sessionId) {
|
|
|
630
670
|
// re-prompts. Returns null for an explicit exit command, Ctrl+C, Ctrl+D, or EOF;
|
|
631
671
|
// otherwise returns
|
|
632
672
|
// { text, files } where files are resolved @file attachments.
|
|
633
|
-
async function promptForNextMessage(
|
|
673
|
+
async function promptForNextMessage(
|
|
674
|
+
runner,
|
|
675
|
+
chatInput,
|
|
676
|
+
{ resolveAttachments = true } = {},
|
|
677
|
+
) {
|
|
634
678
|
for (;;) {
|
|
635
679
|
const answer = chatInput
|
|
636
680
|
? await chatInput.read()
|
|
@@ -651,7 +695,9 @@ async function promptForNextMessage(runner, chatInput) {
|
|
|
651
695
|
return null;
|
|
652
696
|
}
|
|
653
697
|
const { text } = classified;
|
|
654
|
-
const files =
|
|
698
|
+
const files = resolveAttachments
|
|
699
|
+
? await resolveMessageAttachments(runner, text)
|
|
700
|
+
: [];
|
|
655
701
|
return { text, files };
|
|
656
702
|
}
|
|
657
703
|
}
|
|
@@ -713,6 +759,8 @@ async function runAgent(taskParts, options) {
|
|
|
713
759
|
// Fail fast on an unknown/unsupported --model before any startup work.
|
|
714
760
|
const provider = resolveProvider(options.model ?? DEFAULT_PROVIDER);
|
|
715
761
|
|
|
762
|
+
const generationType = validateGenerationType(options.type, provider.id);
|
|
763
|
+
|
|
716
764
|
const startup = await maybeRunStartupChecks(options);
|
|
717
765
|
if (startup === "updated" || startup === "aborted") {
|
|
718
766
|
return null;
|
|
@@ -753,6 +801,7 @@ async function runAgent(taskParts, options) {
|
|
|
753
801
|
projectRoot,
|
|
754
802
|
provider: provider.id,
|
|
755
803
|
mode: null,
|
|
804
|
+
generationType,
|
|
756
805
|
});
|
|
757
806
|
|
|
758
807
|
// Resolve @file attachments in the opening task, if any. The task itself is
|
|
@@ -782,7 +831,7 @@ function printChatBanner(projectRoot, provider) {
|
|
|
782
831
|
const DIM = "\x1b[2m";
|
|
783
832
|
const RESET = "\x1b[0m";
|
|
784
833
|
console.log("");
|
|
785
|
-
console.log(`${CYAN}WTAgent${RESET} ${DIM}· ${provider.label} · ${projectRoot}${RESET}`);
|
|
834
|
+
console.log(`${CYAN}WTAgent${RESET} ${DIM}· ${provider.label} · ${formatHomePath(projectRoot)}${RESET}`);
|
|
786
835
|
console.log(`${DIM}${t("banner.controls")}${RESET}`);
|
|
787
836
|
console.log("");
|
|
788
837
|
}
|
|
@@ -812,8 +861,24 @@ async function runResume(sessionId, instructionParts, options) {
|
|
|
812
861
|
await ensureDirectory(paths.sessionsDir);
|
|
813
862
|
const session = await loadSession(paths, sessionId);
|
|
814
863
|
await assertDirectory(session.state.projectRoot);
|
|
864
|
+
const instruction = instructionParts.join(" ").trim();
|
|
865
|
+
if (
|
|
866
|
+
instruction
|
|
867
|
+
&& (
|
|
868
|
+
session.state.pendingOutbound != null
|
|
869
|
+
|| session.state.pendingAssistantTurn != null
|
|
870
|
+
)
|
|
871
|
+
) {
|
|
872
|
+
throw new BrowserAdapterError(
|
|
873
|
+
"This session has a pending browser handoff. Resume it without an instruction or attachment first.",
|
|
874
|
+
{ code: "RECOVERY_REQUIRES_BARE_RESUME", recoverable: false },
|
|
875
|
+
);
|
|
876
|
+
}
|
|
815
877
|
|
|
816
878
|
const provider = resolveProvider(session.state.provider ?? DEFAULT_PROVIDER);
|
|
879
|
+
if (options.type != null && validateGenerationType(options.type, provider.id) !== (session.state.generationType ?? "agent")) {
|
|
880
|
+
throw new Error("--type cannot change the generation type of an existing session.");
|
|
881
|
+
}
|
|
817
882
|
// A conversation belongs to one provider. --model on resume is only allowed
|
|
818
883
|
// if it names the same provider; switching mid-conversation is rejected.
|
|
819
884
|
if (options.model != null && getProvider(options.model).id !== provider.id) {
|
|
@@ -828,7 +893,6 @@ async function runResume(sessionId, instructionParts, options) {
|
|
|
828
893
|
return null;
|
|
829
894
|
}
|
|
830
895
|
|
|
831
|
-
const instruction = instructionParts.join(" ").trim();
|
|
832
896
|
let files = [];
|
|
833
897
|
if (instruction) {
|
|
834
898
|
const projectRoot = session.state.projectRoot;
|
|
@@ -940,6 +1004,7 @@ const program = new Command()
|
|
|
940
1004
|
}),
|
|
941
1005
|
)
|
|
942
1006
|
.option("--once", t("option.once"), false)
|
|
1007
|
+
.option("--type <type>", t("option.type"))
|
|
943
1008
|
.option("--model-turn-timeout-ms <milliseconds>", t("option.timeout"))
|
|
944
1009
|
.option("--no-minimize", t("option.noMinimize"))
|
|
945
1010
|
.option("--debug", t("option.debug"), false)
|
|
@@ -1002,26 +1067,45 @@ program
|
|
|
1002
1067
|
await runExport(sessionId, { ...command.optsWithGlobals(), ...options });
|
|
1003
1068
|
});
|
|
1004
1069
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1070
|
+
// Resolve npm's executable symlink as well as direct `node main.js` invocations.
|
|
1071
|
+
// Importing the session runner in tests must not start the CLI.
|
|
1072
|
+
const entryPath = process.argv[1]
|
|
1073
|
+
? await fs.realpath(process.argv[1]).catch(() => null)
|
|
1074
|
+
: null;
|
|
1075
|
+
const modulePath = await fs.realpath(fileURLToPath(import.meta.url)).catch(() => null);
|
|
1076
|
+
// npm's Windows shim can use a short path or differently cased path. Compare
|
|
1077
|
+
// canonical filesystem paths rather than independently encoded file URLs.
|
|
1078
|
+
const isEntryPoint = entryPath && modulePath && (process.platform === "win32"
|
|
1079
|
+
? entryPath.toLowerCase() === modulePath.toLowerCase()
|
|
1080
|
+
: entryPath === modulePath);
|
|
1081
|
+
if (isEntryPoint) {
|
|
1082
|
+
await program.parseAsync().catch((error) => {
|
|
1083
|
+
// Expected, actionable failures carry a plain message instead of a stack
|
|
1084
|
+
// trace (e.g. the provider's Chrome profile is locked by another session).
|
|
1085
|
+
if (error?.code === "PROFILE_LOCKED") {
|
|
1086
|
+
console.error(`Error: ${error.message}`);
|
|
1087
|
+
process.exitCode = 1;
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
console.error(error.stack ?? error.message);
|
|
1091
|
+
if (error.details?.diagnosticFile) {
|
|
1092
|
+
const dom = error.details.dom;
|
|
1093
|
+
const explanation = dom?.issues?.length ? dom.issues.join(", ") : dom?.status ?? "unavailable";
|
|
1094
|
+
console.error(`Page compatibility: ${explanation}`);
|
|
1095
|
+
console.error(`Diagnostic report: ${error.details.diagnosticFile}`);
|
|
1096
|
+
}
|
|
1010
1097
|
process.exitCode = 1;
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
process.exit(process.exitCode);
|
|
1026
|
-
}
|
|
1027
|
-
});
|
|
1098
|
+
}).finally(() => {
|
|
1099
|
+
// By the time every command finishes, all per-command cleanup (browser
|
|
1100
|
+
// detach/close, stdin raw-mode restore, process stop) has already run. A
|
|
1101
|
+
// stray tty handle (e.g. a keypress-machinery listener left behind on a
|
|
1102
|
+
// failed turn) can still pin the event loop, leaving a dead prompt that
|
|
1103
|
+
// never returns to the shell — exit hard instead of relying on the loop
|
|
1104
|
+
// to drain naturally.
|
|
1105
|
+
if (process.exitCode != null && process.exitCode !== 0) {
|
|
1106
|
+
process.stdin.setRawMode?.(false);
|
|
1107
|
+
process.stdin.pause?.();
|
|
1108
|
+
process.exit(process.exitCode);
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
}
|