Skip to content

Commit 8a6761c

Browse files
committed
feat: restore codex context per project
1 parent d915bae commit 8a6761c

7 files changed

Lines changed: 381 additions & 40 deletions

File tree

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ General:
127127
- `/repo <keyword>` - fuzzy match projects; switch if only one match, otherwise list candidates
128128
- `/repo recent` - show recent projects for the current chat
129129
- `/repo -` - switch back to the previous project
130-
- `/new` - close current session and start fresh on the next message
130+
- `/new` - clear the saved Codex conversation for the current project and start fresh on the next message
131131
- `/exec <task>` - force a one-off `codex exec`
132132
- `/auto <task>` - force a one-off `codex exec --full-auto`
133133
- `/plan <task>` - ask Codex for a plan only, without direct file modification intent
@@ -167,6 +167,7 @@ Telegram adaptation notes:
167167
- `/exec` behaves like `codex exec "task"`
168168
- `/auto` behaves like `codex exec --full-auto "task"`
169169
- `/new` is implemented by the bot and resets the current chat session
170+
- `/new` only clears the current project's saved Codex conversation slot
170171
- `/status` is implemented by the bot and reports local runtime state
171172
- `/repo` is implemented by the bot and switches the per-chat working directory inside `WORKSPACE_ROOT`
172173
- `/skill` is implemented by the bot and keeps per-chat skill switches in runtime state
@@ -186,6 +187,16 @@ PTY output is streamed with throttled `editMessageText` updates.
186187
- quote block (if `REASONING_RENDER_MODE=quote`)
187188
- If `node-pty` cannot spawn on the current host, the runner falls back to `codex exec` for per-request execution
188189

190+
## Project-Scoped Conversation State
191+
192+
Conversation state is now tracked per `chat + project`, not just per chat.
193+
194+
- When you switch with `/repo <name>`, the bot keeps that project's last Codex session id in runtime state
195+
- When you switch back to the same project later, the next plain-text task resumes that project's Codex conversation
196+
- `/new` clears only the current project's saved conversation slot; other projects in the same Telegram chat are untouched
197+
- `/exec`, `/auto`, and `/plan` stay one-off by design and do not replace the saved project conversation
198+
- On hosts where PTY is unavailable, project restore still works through `codex exec resume`
199+
189200
## Event-Driven Automation
190201

191202
`node-cron` is built in for proactive behavior:
@@ -278,7 +289,7 @@ Telegram can manage runtime usage of Bot-side MCP and skills, but not install ar
278289
- MCP servers are process-level runtime resources: list, inspect, reconnect, enable, disable
279290
- Skills are chat-level routing switches: each chat can enable or disable `github` and `mcp` independently
280291
- Codex's own MCP remains separate and is not managed through these bot commands
281-
- Runtime state is persisted to `STATE_FILE`, so `/mcp enable|disable` and `/skill on|off` survive bot restarts
292+
- Runtime state is persisted to `STATE_FILE`, so `/mcp enable|disable`, `/skill on|off`, and per-project Codex conversation slots survive bot restarts
282293

283294
## Troubleshooting
284295

src/bot/handlers.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export function registerHandlers({
125125
`workspace root: ${status.workspaceRoot}`,
126126
`workdir: ${status.workdir}`,
127127
`recent projects: ${ptyManager.getRecentProjects(ctx.chat.id).map((item) => item.relativePath).join(", ") || "."}`,
128+
`project context: ${status.projectSessionId ? `resumable (${status.projectSessionId})` : "fresh"}`,
128129
`safe shell: ${
129130
shellManager.isEnabled()
130131
? `enabled, ${shellManager.isReadOnly() ? "read-only" : "writable"} (${shellManager.getAllowedCommands().length} prefixes)`
@@ -298,12 +299,12 @@ export function registerHandlers({
298299
});
299300

300301
bot.command("new", async (ctx) => {
301-
const closed = ptyManager.closeSession(ctx.chat.id);
302+
const result = ptyManager.resetCurrentProjectConversation(ctx.chat.id);
302303
await sendChunkedMarkdown(
303304
ctx,
304-
closed
305-
? "当前会话已关闭。下一条消息会启动一个新的 Codex 会话。"
306-
: "当前没有活动会话。下一条消息会启动新的 Codex 会话。"
305+
result.closed
306+
? "当前项目的会话上下文已清空,并关闭了活动会话。下一条消息会在当前项目启动全新 Codex 会话。"
307+
: "当前项目的会话上下文已清空。下一条消息会在当前项目启动全新 Codex 会话。"
307308
);
308309
});
309310

src/index.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ const bot = new Telegraf(config.telegram.botToken, {
2121
const stateStore = new RuntimeStateStore({ config });
2222
let mcpClient;
2323
let skillRegistry;
24+
let ptyManager;
2425

2526
async function saveRuntimeState() {
26-
if (!mcpClient || !skillRegistry) return;
27+
if (!mcpClient || !skillRegistry || !ptyManager) return;
2728
await stateStore.save({
2829
mcp: mcpClient.exportState(),
29-
skills: skillRegistry.exportState()
30+
skills: skillRegistry.exportState(),
31+
runner: ptyManager.exportState()
3032
});
3133
}
3234

@@ -79,10 +81,12 @@ const router = new Router({
7981
isSkillEnabled: (chatId, skillName) => skillRegistry.isEnabled(chatId, skillName)
8082
});
8183

82-
const ptyManager = new PtyManager({
84+
ptyManager = new PtyManager({
8385
bot,
84-
config
86+
config,
87+
onChange: () => void saveRuntimeState()
8588
});
89+
ptyManager.restoreState(runtimeState.runner);
8690
const shellManager = new ShellManager({
8791
config
8892
});

0 commit comments

Comments
 (0)