feat: Use IDLE by default to keep a long-lived connection with the mail server; allow forced polling for servers with poor IDLE/NOOP support.

This commit is contained in:
zhouliang 2026-02-18 22:17:22 +08:00
parent a78c92448e
commit cb19ee997d
4 changed files with 18 additions and 16 deletions

View file

@ -490,7 +490,7 @@ Use an inbox as a channel: PicoClaw connects via IMAP to read new mail and (opti
"password": "YOUR_APP_PASSWORD", "password": "YOUR_APP_PASSWORD",
"mailbox": "INBOX", "mailbox": "INBOX",
"check_interval": 30, "check_interval": 30,
"use_idle": true, "forced_polling": false,
"use_tls": true, "use_tls": true,
"allow_from": ["allowed-sender@example.com"], "allow_from": ["allowed-sender@example.com"],
"attachment_dir": "/path/to/save/attachments", "attachment_dir": "/path/to/save/attachments",
@ -507,8 +507,8 @@ Use an inbox as a channel: PicoClaw connects via IMAP to read new mail and (opti
| `imap_server`, `imap_port` | IMAP server (e.g. 993 with TLS). | | `imap_server`, `imap_port` | IMAP server (e.g. 993 with TLS). |
| `username`, `password` | Mailbox login; 163/QQ require app password. | | `username`, `password` | Mailbox login; 163/QQ require app password. |
| `mailbox` | Folder to watch (default `INBOX`). | | `mailbox` | Folder to watch (default `INBOX`). |
| `use_idle` | `true`: use IMAP IDLE when supported (push); `false`: poll every `check_interval` seconds. | | `forced_polling` | When the server does not implement IDLE/NOOP per spec, set `true` to use application-level polling; leave `false` normally. |
| `check_interval` | Polling interval in seconds when IDLE is off or unsupported (default 30). | | `check_interval` | Polling interval in seconds when using forced polling or when IDLE is unsupported (default 30). |
| `allow_from` | Allowed sender addresses; only their mails trigger the agent. | | `allow_from` | Allowed sender addresses; only their mails trigger the agent. |
| `attachment_dir` | Directory to save attachments; leave empty to skip saving. | | `attachment_dir` | Directory to save attachments; leave empty to skip saving. |
| `smtp_server`, `smtp_port`, `smtp_use_tls` | Optional; if set, the agent can send replies. | | `smtp_server`, `smtp_port`, `smtp_use_tls` | Optional; if set, the agent can send replies. |

View file

@ -79,10 +79,10 @@
"password": "your_password", "password": "your_password",
"mailbox": "INBOX", "mailbox": "INBOX",
"check_interval": 30, "check_interval": 30,
"forced_polling": false,
"use_tls": true, "use_tls": true,
"allow_from": ["allowed_email@example.com"], "allow_from": ["allowed_email@example.com"],
"attachment_dir": "~/.picoclaw/workspace/attachments", "attachment_dir": "~/.picoclaw/workspace/attachments",
"use_idle": true,
"smtp_server": "smtp.example.com--eg:smtp.qq.com", "smtp_server": "smtp.example.com--eg:smtp.qq.com",
"smtp_port": 465, "smtp_port": 465,
"smtp_use_tls": true "smtp_use_tls": true

View file

@ -310,12 +310,13 @@ func (c *EmailChannel) checkLoop(ctx context.Context) {
// Run one check immediately // Run one check immediately
c.checkNewEmails() c.checkNewEmails()
if c.config.UseIdle { if !c.config.ForcedPolling {
// support IDLE user idle loop, waiting for server push update
c.runIdleLoop(ctx, interval) c.runIdleLoop(ctx, interval)
return return
} }
// Polling mode // not support IDLE user polling mode, check new emails every interval
c.mu.Lock() c.mu.Lock()
c.checkTicker = time.NewTicker(interval) c.checkTicker = time.NewTicker(interval)
ticker := c.checkTicker ticker := c.checkTicker

View file

@ -149,15 +149,16 @@ type LINEConfig struct {
} }
type EmailConfig struct { type EmailConfig struct {
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_EMAIL_ENABLED"` Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_EMAIL_ENABLED"`
IMAPServer string `json:"imap_server" env:"PICOCLAW_CHANNELS_EMAIL_IMAP_SERVER"` IMAPServer string `json:"imap_server" env:"PICOCLAW_CHANNELS_EMAIL_IMAP_SERVER"`
IMAPPort int `json:"imap_port" env:"PICOCLAW_CHANNELS_EMAIL_IMAP_PORT"` IMAPPort int `json:"imap_port" env:"PICOCLAW_CHANNELS_EMAIL_IMAP_PORT"`
Username string `json:"username" env:"PICOCLAW_CHANNELS_EMAIL_USERNAME"` Username string `json:"username" env:"PICOCLAW_CHANNELS_EMAIL_USERNAME"`
Password string `json:"password" env:"PICOCLAW_CHANNELS_EMAIL_PASSWORD"` Password string `json:"password" env:"PICOCLAW_CHANNELS_EMAIL_PASSWORD"`
Mailbox string `json:"mailbox" env:"PICOCLAW_CHANNELS_EMAIL_MAILBOX"` // 默认 "INBOX" Mailbox string `json:"mailbox" env:"PICOCLAW_CHANNELS_EMAIL_MAILBOX"` // 默认 "INBOX"
CheckInterval int `json:"check_interval" env:"PICOCLAW_CHANNELS_EMAIL_CHECK_INTERVAL"` // seconds, default 30; used for polling when IDLE disabled or unsupported CheckInterval int `json:"check_interval" env:"PICOCLAW_CHANNELS_EMAIL_CHECK_INTERVAL"` // seconds, default 30; used for polling when IDLE disabled or unsupported
UseIdle bool `json:"use_idle" env:"PICOCLAW_CHANNELS_EMAIL_USE_IDLE"` // use IMAP IDLE (push) when server supports it; otherwise poll UseTLS bool `json:"use_tls" env:"PICOCLAW_CHANNELS_EMAIL_USE_TLS"`
UseTLS bool `json:"use_tls" env:"PICOCLAW_CHANNELS_EMAIL_USE_TLS"` // ForcedPolling: when the mail server does not implement IDLE/NOOP per spec (e.g. NOOP does not return * EXISTS), set true to use application-level polling (check new mail at CheckInterval). Leave false under normal conditions.
ForcedPolling bool `json:"forced_polling" env:"PICOCLAW_CHANNELS_EMAIL_FORCED_POLLING"`
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_EMAIL_ALLOW_FROM"` AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_EMAIL_ALLOW_FROM"`
AttachmentDir string `json:"attachment_dir" env:"PICOCLAW_CHANNELS_EMAIL_ATTACHMENT_DIR"` AttachmentDir string `json:"attachment_dir" env:"PICOCLAW_CHANNELS_EMAIL_ATTACHMENT_DIR"`
// SMTP send (optional, if not configured, Send is not available) // SMTP send (optional, if not configured, Send is not available)
@ -332,7 +333,7 @@ func DefaultConfig() *Config {
Password: "", Password: "",
Mailbox: "INBOX", Mailbox: "INBOX",
CheckInterval: 30, CheckInterval: 30,
UseIdle: true, ForcedPolling: false,
UseTLS: true, UseTLS: true,
AllowFrom: FlexibleStringSlice{}, AllowFrom: FlexibleStringSlice{},
SMTPServer: "", SMTPServer: "",