(in string)
| 835 | } |
| 836 | |
| 837 | func (cli *ChatCLI) executor(in string) { |
| 838 | in = strings.TrimSpace(in) |
| 839 | |
| 840 | // Drain any pending park resumes BEFORE processing the user's |
| 841 | // command. Slash commands like /parked, /jobs and chat input all |
| 842 | // route through this executor without ever exiting p.Run(); without |
| 843 | // this hook a resume that fires during interactive use would only |
| 844 | // trigger when the user later runs /coder or /agent. The drain runs |
| 845 | // foreground and may take a while (it re-enters the agent loop) — |
| 846 | // that is exactly the desired UX: the user sees the agent continue, |
| 847 | // then their original input is processed afterwards. |
| 848 | if cli.drainPendingResumes(context.Background()) { |
| 849 | // Flush any cosmetic state the resume left behind. |
| 850 | _ = os.Stdout.Sync() |
| 851 | } |
| 852 | |
| 853 | // Auto-triggered agent runs queued by the MCP channel trigger |
| 854 | // engine fire here, BEFORE the user's command. Symmetric with |
| 855 | // park resume — the user sees the agent investigate the event, |
| 856 | // then their typed input is processed. Notify/confirm banners |
| 857 | // are rendered separately below so they appear even on turns |
| 858 | // that have no queued auto-trigger. |
| 859 | if cli.drainPendingAutoTriggers(cli.sessionCtx) { |
| 860 | _ = os.Stdout.Sync() |
| 861 | } |
| 862 | cli.renderChannelTriggerBanner() |
| 863 | |
| 864 | // Flush any memory notices the background worker produced since the |
| 865 | // last tick, so "memory updated" feedback is visible instead of silent. |
| 866 | cli.drainMemoryNotices() |
| 867 | |
| 868 | // Handle paste: replace placeholder with real content and show notification |
| 869 | if cli.lastPasteInfo != nil { |
| 870 | info := cli.lastPasteInfo |
| 871 | cli.lastPasteInfo = nil |
| 872 | // If a placeholder was used (large paste), swap it for the real content |
| 873 | if info.Placeholder != "" && strings.Contains(in, info.Placeholder) { |
| 874 | in = strings.Replace(in, info.Placeholder, info.Content, 1) |
| 875 | } |
| 876 | if info.LineCount > 1 { |
| 877 | fmt.Printf(" %s\n", i18n.T("paste.detected", info.CharCount, info.LineCount)) |
| 878 | } else { |
| 879 | fmt.Printf(" %s\n", i18n.T("paste.detected.short", info.CharCount)) |
| 880 | } |
| 881 | } |
| 882 | |
| 883 | // Multiline input: accumulate lines between --- delimiters. |
| 884 | // While active, each Enter adds a line instead of submitting. |
| 885 | wasActive := cli.multilineBuf.Active() |
| 886 | complete, fullText := cli.multilineBuf.ProcessLine(in) |
| 887 | if !complete { |
| 888 | if !wasActive { |
| 889 | // Just entered multiline mode — show hint (plain text, go-prompt can't render ANSI) |
| 890 | fmt.Printf(" %s\n", i18n.T("multiline.hint", cli.multilineBuf.Delimiter())) |
| 891 | } |
| 892 | return // still accumulating — changeLivePrefix shows "... [N]" |
| 893 | } |
| 894 | // Use the assembled text (single-line returns as-is; multiline returns joined) |
nothing calls this directly
no test coverage detected