返回 DeepSeek-Reasonix
rename.go
根目录 / internal / cli / rename.go
1 package cli
2
3 import (
4 "context"
5 "fmt"
6 "strconv"
7 "strings"
8
9 "reasonix/internal/agent"
10 "reasonix/internal/control"
11 "reasonix/internal/i18n"
12 "reasonix/internal/session"
13 )
14
15 // sessionTitleWriter is the controller's canonical title owner for the
16 // session it is bound to.
17 type sessionTitleWriter interface {
18 SetSessionTitle(context.Context, string) error
19 }
20
21 // runRenameCommand handles "/rename": with no argument it shows usage;
22 // "/rename <new title>" renames the current session;
23 // "/rename <n> <new title>" renames session #n from the /resume list.
24 func (m *chatTUI) runRenameCommand(input string) {
25 args := tokenizeArgs(input) // args[0] == "/rename"
26
27 if len(args) < 2 {
28 m.notice(i18n.M.RenameUsage)
29 return
30 }
31
32 var target cliResumeTarget
33 title := ""
34
35 // Check if the first arg after /rename is a session index (a number).
36 idx, err := strconv.Atoi(args[1])
37 if err == nil && len(args) >= 3 {
38 // "/rename <n> <new title>"
39 sessions := mergedResumeEntries(m.ctrl.SessionDir(), resumeListCap)
40 if idx < 1 || idx > len(sessions) {
41 m.notice(fmt.Sprintf(i18n.M.ResumeBadIndexFmt, len(sessions)))
42 return
43 }
44 picked := sessions[idx-1]
45 target = picked.target
46 if !target.canonical() {
47 target.path = picked.session.Path
48 }
49 title = strings.TrimSpace(strings.TrimPrefix(input, args[0]+" "+args[1]))
50 } else {
51 // "/rename <new title>" -- rename the current session.
52 target = m.currentRenameTarget()
53 if target.empty() {
54 m.notice(i18n.M.RenameNoSession)
55 return
56 }
57 title = strings.TrimSpace(strings.TrimPrefix(input, args[0]))
58 }
59
60 if title == "" {
61 m.notice(i18n.M.RenameUsage)
62 return
63 }
64
65 if err := m.renameSession(target, title); err != nil {
66 m.notice("rename: " + err.Error())
67 return
68 }
69
70 m.notice(fmt.Sprintf(i18n.M.RenameDoneFmt, title))
71 }
72
73 // currentRenameTarget names the session the controller writes: its canonical
74 // identity when bound to one, otherwise its legacy transcript path.
75 func (m *chatTUI) currentRenameTarget() cliResumeTarget {
76 if identity, ok := m.ctrl.(control.IdentityLifecycle); ok {
77 if ref, bound := identity.SessionRef(); bound {
78 return cliResumeTarget{ref: ref}
79 }
80 }
81 return cliResumeTarget{path: m.ctrl.SessionPath()}
82 }
83
84 // renameSession writes the title to whoever owns it: the bound controller for
85 // its own session, the session service for any other canonical session, and
86 // the legacy sidecar for a transcript path.
87 func (m *chatTUI) renameSession(target cliResumeTarget, title string) error {
88 if !target.canonical() {
89 return agent.RenameSession(target.path, title)
90 }
91 ctx := context.Background()
92 if identity, ok := m.ctrl.(control.IdentityLifecycle); ok {
93 if ref, bound := identity.SessionRef(); bound && ref == target.ref && identity.UsesExclusiveSession() {
94 if writer, ok := m.ctrl.(sessionTitleWriter); ok {
95 return writer.SetSessionTitle(ctx, title)
96 }
97 }
98 if service := identity.SessionService(); service != nil {
99 return service.SetTitle(ctx, target.ref, title)
100 }
101 }
102 if service := cliSessionService(m.ctrl.SessionDir()); service != nil {
103 return service.SetTitle(ctx, target.ref, title)
104 }
105 return session.ErrSessionNotRunning
106 }
107
107 lines GO