11import { Markup } from "telegraf" ;
2+ import { buildPlanPrompt , extractCommandPayload } from "./commandUtils.js" ;
23import { escapeMarkdownV2 , splitTelegramMessage } from "./formatter.js" ;
34
45async function sendChunkedMarkdown ( ctx , text , extra = { } ) {
@@ -44,6 +45,7 @@ export function registerHandlers({ bot, router, ptyManager, skills, scheduler })
4445 "codex-telegram-claws ready." ,
4546 "普通消息和编码任务会路由到 Codex。" ,
4647 "MCP 只在显式 /mcp 命令下调用。" ,
48+ "试试: /status, /exec, /auto, /plan, /model, /new" ,
4749 "GitHub 指令示例: /gh commit \"feat: init\""
4850 ] . join ( "\n" )
4951 ) ;
@@ -55,6 +57,12 @@ export function registerHandlers({ bot, router, ptyManager, skills, scheduler })
5557 [
5658 "Commands:" ,
5759 "/help - 显示帮助" ,
60+ "/status - 查看当前 chat 的运行状态" ,
61+ "/new - 新建会话并清空当前上下文" ,
62+ "/exec <task> - 强制用 codex exec 运行一次任务" ,
63+ "/auto <task> - 强制用 codex exec --full-auto 运行任务" ,
64+ "/plan <task> - 仅生成执行计划,不直接修改代码" ,
65+ "/model [name|reset] - 查看或设置当前 chat 的模型" ,
5866 "/interrupt - 向 Codex CLI 发送 Ctrl+C" ,
5967 "/stop - 终止当前 chat 的 PTY 会话" ,
6068 "/cron_now - 立即触发一次日报推送" ,
@@ -64,6 +72,131 @@ export function registerHandlers({ bot, router, ptyManager, skills, scheduler })
6472 ) ;
6573 } ) ;
6674
75+ bot . command ( "status" , async ( ctx ) => {
76+ const status = ptyManager . getStatus ( ctx . chat . id ) ;
77+ await sendChunkedMarkdown (
78+ ctx ,
79+ [
80+ "Status:" ,
81+ `active: ${ status . active ? "yes" : "no" } ` ,
82+ `active mode: ${ status . activeMode || "idle" } ` ,
83+ `last mode: ${ status . lastMode || "none" } ` ,
84+ `last exit: ${ status . lastExitCode === null ? "n/a" : status . lastExitCode } ` ,
85+ `pty supported: ${
86+ status . ptySupported === null ? "unknown" : status . ptySupported ? "yes" : "no (exec fallback)"
87+ } `,
88+ `preferred model: ${ status . preferredModel || "inherit codex default" } ` ,
89+ `command: ${ status . command } ` ,
90+ `workdir: ${ status . workdir } ` ,
91+ `mcp servers: ${ status . mcpServers . length ? status . mcpServers . join ( ", " ) : "none" } `
92+ ] . join ( "\n" )
93+ ) ;
94+ } ) ;
95+
96+ bot . command ( "new" , async ( ctx ) => {
97+ const closed = ptyManager . closeSession ( ctx . chat . id ) ;
98+ await sendChunkedMarkdown (
99+ ctx ,
100+ closed
101+ ? "当前会话已关闭。下一条消息会启动一个新的 Codex 会话。"
102+ : "当前没有活动会话。下一条消息会启动新的 Codex 会话。"
103+ ) ;
104+ } ) ;
105+
106+ bot . command ( "exec" , async ( ctx ) => {
107+ const task = extractCommandPayload ( ctx . message . text , "exec" ) ;
108+ if ( ! task ) {
109+ await sendChunkedMarkdown ( ctx , "用法: /exec <task>" ) ;
110+ return ;
111+ }
112+
113+ const result = await ptyManager . sendPrompt ( ctx , task , {
114+ forceExec : true ,
115+ notice : "Running one-off `codex exec` task..."
116+ } ) ;
117+
118+ if ( ! result . started ) {
119+ await sendChunkedMarkdown (
120+ ctx ,
121+ `当前已有 ${ result . activeMode || "unknown" } 任务在运行。请等待完成或先使用 /interrupt。`
122+ ) ;
123+ }
124+ } ) ;
125+
126+ bot . command ( "auto" , async ( ctx ) => {
127+ const task = extractCommandPayload ( ctx . message . text , "auto" ) ;
128+ if ( ! task ) {
129+ await sendChunkedMarkdown ( ctx , "用法: /auto <task>" ) ;
130+ return ;
131+ }
132+
133+ const result = await ptyManager . sendPrompt ( ctx , task , {
134+ forceExec : true ,
135+ fullAuto : true ,
136+ notice : "Running one-off `codex exec --full-auto` task..."
137+ } ) ;
138+
139+ if ( ! result . started ) {
140+ await sendChunkedMarkdown (
141+ ctx ,
142+ `当前已有 ${ result . activeMode || "unknown" } 任务在运行。请等待完成或先使用 /interrupt。`
143+ ) ;
144+ }
145+ } ) ;
146+
147+ bot . command ( "plan" , async ( ctx ) => {
148+ const task = extractCommandPayload ( ctx . message . text , "plan" ) ;
149+ if ( ! task ) {
150+ await sendChunkedMarkdown ( ctx , "用法: /plan <task>" ) ;
151+ return ;
152+ }
153+
154+ const result = await ptyManager . sendPrompt ( ctx , buildPlanPrompt ( task ) , {
155+ forceExec : true ,
156+ notice : "Running planning-only Codex task..."
157+ } ) ;
158+
159+ if ( ! result . started ) {
160+ await sendChunkedMarkdown (
161+ ctx ,
162+ `当前已有 ${ result . activeMode || "unknown" } 任务在运行。请等待完成或先使用 /interrupt。`
163+ ) ;
164+ }
165+ } ) ;
166+
167+ bot . command ( "model" , async ( ctx ) => {
168+ const value = extractCommandPayload ( ctx . message . text , "model" ) ;
169+ if ( ! value ) {
170+ const status = ptyManager . getStatus ( ctx . chat . id ) ;
171+ await sendChunkedMarkdown (
172+ ctx ,
173+ `当前模型: ${ status . preferredModel || "inherit codex default" } `
174+ ) ;
175+ return ;
176+ }
177+
178+ if ( / ^ ( r e s e t | d e f a u l t | i n h e r i t ) $ / i. test ( value ) ) {
179+ ptyManager . clearPreferredModel ( ctx . chat . id ) ;
180+ const closed = ptyManager . closeSession ( ctx . chat . id ) ;
181+ await sendChunkedMarkdown (
182+ ctx ,
183+ closed
184+ ? "模型已重置为 Codex 默认值,并重建了当前会话。"
185+ : "模型已重置为 Codex 默认值。"
186+ ) ;
187+ return ;
188+ }
189+
190+ ptyManager . setPreferredModel ( ctx . chat . id , value ) ;
191+ const closed = ptyManager . closeSession ( ctx . chat . id ) ;
192+ await sendChunkedMarkdown (
193+ ctx ,
194+ closed
195+ ? `模型已设置为 ${ value } ,并重建了当前会话。`
196+ : `模型已设置为 ${ value } 。`
197+ ) ;
198+ } ) ;
199+
67200 bot . command ( "interrupt" , async ( ctx ) => {
68201 const ok = ptyManager . interrupt ( ctx . chat . id ) ;
69202 await sendChunkedMarkdown ( ctx , ok ? "已发送 Ctrl+C。" : "当前 chat 没有活动 PTY 会话。" ) ;
@@ -85,7 +218,7 @@ export function registerHandlers({ bot, router, ptyManager, skills, scheduler })
85218
86219 bot . command ( "gh" , async ( ctx ) => {
87220 try {
88- const text = ctx . message . text . replace ( / ^ \/ g h ( @ \w + ) ? \s * / i , "" ) . trim ( ) || "help" ;
221+ const text = extractCommandPayload ( ctx . message . text , "gh" ) || "help" ;
89222 const result = await skills . github . execute ( { text : `/gh ${ text } ` , ctx } ) ;
90223 await sendSkillResult ( ctx , result ) ;
91224 } catch ( error ) {
@@ -126,7 +259,13 @@ export function registerHandlers({ bot, router, ptyManager, skills, scheduler })
126259 try {
127260 const route = await router . routeMessage ( text ) ;
128261 if ( route . target === "pty" ) {
129- await ptyManager . sendPrompt ( ctx , route . prompt ) ;
262+ const result = await ptyManager . sendPrompt ( ctx , route . prompt ) ;
263+ if ( ! result . started ) {
264+ await sendChunkedMarkdown (
265+ ctx ,
266+ `当前已有 ${ result . activeMode || "unknown" } 任务在运行。请等待完成或先使用 /interrupt。`
267+ ) ;
268+ }
130269 return ;
131270 }
132271
0 commit comments