dsh-email 0.17.2 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.en.md +7 -4
- package/README.md +7 -4
- package/docs/AGENTMAIL.en.md +37 -0
- package/docs/AGENTMAIL.md +41 -0
- package/docs/USAGE.en.md +13 -1
- package/docs/USAGE.md +11 -1
- package/docs/VALIDATION.md +2 -0
- package/docs/validation/0.18.0.md +23 -0
- package/lib/agentmail.d.ts +33 -0
- package/lib/agentmail.js +407 -0
- package/lib/client.js +129 -31
- package/lib/config.d.ts +4 -0
- package/lib/config.js +12 -2
- package/lib/draft-store.d.ts +5 -0
- package/lib/draft-store.js +4 -0
- package/lib/draft-web.js +15 -6
- package/lib/host-config.js +1 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +12 -2
- package/lib/mail-client.d.ts +8 -3
- package/lib/mail-client.js +83 -20
- package/lib/mail-service.d.ts +50 -0
- package/lib/mail-service.js +128 -0
- package/lib/mailbox-web.d.ts +11 -0
- package/lib/mailbox-web.js +219 -0
- package/lib/public-download.d.ts +3 -0
- package/lib/public-download.js +77 -0
- package/lib/runtime.d.ts +2 -2
- package/lib/runtime.js +7 -3
- package/lib/settings.js +1 -1
- package/lib/tool-contract.d.ts +37 -8
- package/lib/tool-contract.js +48 -18
- package/lib/tools.js +14 -10
- package/lib/types.d.ts +44 -12
- package/lib/web.d.ts +9 -1
- package/lib/web.js +20 -2
- package/package.json +4 -3
package/lib/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface AddressEntry {
|
|
|
5
5
|
}
|
|
6
6
|
/** One listed message: everything cheap to fetch without the body. */
|
|
7
7
|
export interface ListedMessage {
|
|
8
|
-
uid: number;
|
|
8
|
+
uid: number | string;
|
|
9
9
|
/** ISO 8601 string, or '' when unknown. */
|
|
10
10
|
date: string;
|
|
11
11
|
from: AddressEntry[];
|
|
@@ -36,6 +36,9 @@ export interface ReadMessageBody {
|
|
|
36
36
|
truncated: boolean;
|
|
37
37
|
}
|
|
38
38
|
export interface EmailListResult {
|
|
39
|
+
countExact?: boolean;
|
|
40
|
+
hasMore?: boolean;
|
|
41
|
+
order?: 'relevance';
|
|
39
42
|
account: string;
|
|
40
43
|
count: number;
|
|
41
44
|
folder: string;
|
|
@@ -45,22 +48,48 @@ export interface EmailListResult {
|
|
|
45
48
|
}
|
|
46
49
|
export interface EmailReadResult extends ReadMessageBody {
|
|
47
50
|
account: string;
|
|
48
|
-
uid: number;
|
|
51
|
+
uid: number | string;
|
|
49
52
|
folder: string;
|
|
53
|
+
uidValidity?: number;
|
|
54
|
+
}
|
|
55
|
+
export interface EmailSearchOptions {
|
|
56
|
+
/** Local header window; independent of result offset and bodySearchLimit. */
|
|
57
|
+
headerScanLimit?: number;
|
|
58
|
+
headerScanOffset?: number;
|
|
59
|
+
}
|
|
60
|
+
export interface EmailHeaderScan {
|
|
61
|
+
mailboxTotal: number;
|
|
62
|
+
scanned: number;
|
|
63
|
+
matched: number;
|
|
64
|
+
scanOffset: number;
|
|
65
|
+
scanLimit: number;
|
|
66
|
+
complete: boolean;
|
|
67
|
+
nextScanOffset?: number;
|
|
50
68
|
}
|
|
51
69
|
export interface EmailSearchResult {
|
|
70
|
+
countExact?: boolean;
|
|
71
|
+
hasMore?: boolean;
|
|
72
|
+
order?: 'relevance';
|
|
52
73
|
account: string;
|
|
53
74
|
query: string;
|
|
54
|
-
/** Server
|
|
75
|
+
/** Server candidates, local header-window matches, or body-fallback page rows; inspect searchSource and coverage. */
|
|
55
76
|
count: number;
|
|
56
77
|
folder: string;
|
|
57
78
|
/** How many newest matches the caller skipped (0 on the first page). */
|
|
58
79
|
offset: number;
|
|
59
80
|
messages: ListedMessage[];
|
|
60
|
-
/**
|
|
81
|
+
/** Local scans cannot claim a full mailbox/body match total. */
|
|
61
82
|
countKind?: 'scanned';
|
|
62
|
-
/**
|
|
83
|
+
/** Header rows actually scanned, or the configured body window for legacy renderers. */
|
|
63
84
|
scannedLimit?: number;
|
|
85
|
+
searchSource?: 'server' | 'local-headers' | 'local-body' | 'api';
|
|
86
|
+
serverMatchCount?: number;
|
|
87
|
+
serverSearchFailed?: boolean;
|
|
88
|
+
headerScan?: EmailHeaderScan;
|
|
89
|
+
bodyScan?: {
|
|
90
|
+
scanned: number;
|
|
91
|
+
scanLimit: number;
|
|
92
|
+
};
|
|
64
93
|
}
|
|
65
94
|
export interface EmailSendResult {
|
|
66
95
|
account: string;
|
|
@@ -81,7 +110,7 @@ export interface EmailFoldersResult {
|
|
|
81
110
|
}
|
|
82
111
|
export interface EmailAttachmentResult {
|
|
83
112
|
account: string;
|
|
84
|
-
uid: number;
|
|
113
|
+
uid: number | string;
|
|
85
114
|
filename: string;
|
|
86
115
|
contentType: string;
|
|
87
116
|
size: number;
|
|
@@ -101,7 +130,7 @@ export interface EmailListArgs extends AccountArg {
|
|
|
101
130
|
until?: string;
|
|
102
131
|
}
|
|
103
132
|
export interface EmailReadArgs extends AccountArg {
|
|
104
|
-
uid: number;
|
|
133
|
+
uid: number | string;
|
|
105
134
|
folder?: string;
|
|
106
135
|
}
|
|
107
136
|
export interface EmailSearchArgs extends AccountArg {
|
|
@@ -111,6 +140,8 @@ export interface EmailSearchArgs extends AccountArg {
|
|
|
111
140
|
offset?: number;
|
|
112
141
|
since?: string;
|
|
113
142
|
until?: string;
|
|
143
|
+
headerScanLimit?: number;
|
|
144
|
+
headerScanOffset?: number;
|
|
114
145
|
}
|
|
115
146
|
export interface EmailSendArgs extends AccountArg {
|
|
116
147
|
to: string;
|
|
@@ -124,7 +155,7 @@ export interface EmailFoldersArgs extends AccountArg {
|
|
|
124
155
|
subscribedOnly?: boolean;
|
|
125
156
|
}
|
|
126
157
|
export interface EmailAttachmentArgs extends AccountArg {
|
|
127
|
-
uid: number;
|
|
158
|
+
uid: number | string;
|
|
128
159
|
/** 0-based index into the attachments of email_read. Default 0. */
|
|
129
160
|
index?: number;
|
|
130
161
|
folder?: string;
|
|
@@ -136,7 +167,7 @@ export interface EmailWatchArgs extends AccountArg {
|
|
|
136
167
|
}
|
|
137
168
|
export type EmailMarkAction = 'read' | 'unread' | 'star' | 'unstar' | 'move';
|
|
138
169
|
export interface EmailMarkArgs extends AccountArg {
|
|
139
|
-
uid: number;
|
|
170
|
+
uid: number | string;
|
|
140
171
|
action: EmailMarkAction;
|
|
141
172
|
/** Target folder path; only used (and required) when action is 'move'. */
|
|
142
173
|
toFolder?: string;
|
|
@@ -144,7 +175,7 @@ export interface EmailMarkArgs extends AccountArg {
|
|
|
144
175
|
}
|
|
145
176
|
export interface EmailMarkResult {
|
|
146
177
|
account: string;
|
|
147
|
-
uid: number;
|
|
178
|
+
uid: number | string;
|
|
148
179
|
folder: string;
|
|
149
180
|
action: EmailMarkAction;
|
|
150
181
|
seen: boolean;
|
|
@@ -156,7 +187,7 @@ export interface EmailMarkResult {
|
|
|
156
187
|
}
|
|
157
188
|
export type EmailReplyMode = 'reply' | 'reply-all' | 'forward';
|
|
158
189
|
export interface EmailReplyArgs extends AccountArg {
|
|
159
|
-
uid: number;
|
|
190
|
+
uid: number | string;
|
|
160
191
|
/** The new text to write; the original is quoted below it automatically. */
|
|
161
192
|
text: string;
|
|
162
193
|
mode?: EmailReplyMode;
|
|
@@ -169,7 +200,7 @@ export interface EmailReplyResult {
|
|
|
169
200
|
account: string;
|
|
170
201
|
mode: EmailReplyMode;
|
|
171
202
|
/** uid of the original message that was answered/forwarded. */
|
|
172
|
-
originalUid: number;
|
|
203
|
+
originalUid: number | string;
|
|
173
204
|
messageId: string;
|
|
174
205
|
accepted: string[];
|
|
175
206
|
rejected: string[];
|
|
@@ -178,6 +209,7 @@ export interface EmailReplyResult {
|
|
|
178
209
|
subject: string;
|
|
179
210
|
}
|
|
180
211
|
export interface EmailWatchResult {
|
|
212
|
+
totalUnreadExact?: boolean;
|
|
181
213
|
account: string;
|
|
182
214
|
folder: string;
|
|
183
215
|
/** True on the first call for this account+folder: it sets the baseline. */
|
package/lib/web.d.ts
CHANGED
|
@@ -175,7 +175,8 @@ export declare class EmailSettingsBackend {
|
|
|
175
175
|
private readonly ctx;
|
|
176
176
|
private readonly scope;
|
|
177
177
|
private readonly rowConfig;
|
|
178
|
-
|
|
178
|
+
private readonly apiFetch?;
|
|
179
|
+
constructor(ctx: any, scope: any, rowConfig: EmailConfig, apiFetch?: typeof fetch | undefined);
|
|
179
180
|
/** Wired by apply(): the email_watch core; 'web' keeps its own cursor scope. */
|
|
180
181
|
watchImpl?: (account: string, folder: string, limit: number, scope: string) => Promise<EmailWatchResult>;
|
|
181
182
|
private get namespace();
|
|
@@ -262,11 +263,18 @@ export declare class EmailSettingsBackend {
|
|
|
262
263
|
* what was actually tried — including on failure.
|
|
263
264
|
*/
|
|
264
265
|
test(value: EmailSettingsValue, accountName?: string): Promise<{
|
|
266
|
+
ok: boolean;
|
|
267
|
+
account: string;
|
|
268
|
+
apiHost: "https://api.agentmail.to" | "https://api.agentmail.eu" | undefined;
|
|
269
|
+
message: string;
|
|
270
|
+
} | {
|
|
265
271
|
account: string;
|
|
266
272
|
imapHost: string;
|
|
267
273
|
imapPort: number;
|
|
268
274
|
ok: boolean;
|
|
269
275
|
ms: number;
|
|
276
|
+
apiHost?: undefined;
|
|
277
|
+
message?: undefined;
|
|
270
278
|
}>;
|
|
271
279
|
/**
|
|
272
280
|
* Resolve one named account of the *stored* settings — the same accounts the
|
package/lib/web.js
CHANGED
|
@@ -6,6 +6,7 @@ import { SETTINGS_NAMESPACE, toEmailConfig, validateSettingsValue } from './sett
|
|
|
6
6
|
import { isOAuth2Account, OUTLOOK_OAUTH2_CLIENT_ID, parseAccountsYaml, parseServerPresets, presetNamesIn, PROVIDER_PRESETS, resolveEmailSettings, serializeAccountsYaml, } from './config.js';
|
|
7
7
|
import { clearTokenFor, getFreshAccessToken, oauth2StateOf, pollDeviceFlow, startDeviceFlow, } from './oauth2.js';
|
|
8
8
|
import { EmailPool, messageOf, redactCredentials } from './mail-client.js';
|
|
9
|
+
import { AgentMailClient } from './agentmail.js';
|
|
9
10
|
/** Same-origin route the browser settings section talks to. */
|
|
10
11
|
export const SETTINGS_ROUTE = '/_dsh/dsh-email/settings';
|
|
11
12
|
/** Same-origin route serving the whale-girl courier image to the widget. */
|
|
@@ -98,7 +99,7 @@ function buildAccountCards(raw, defaultAccount, presets, tokens = () => ({ state
|
|
|
98
99
|
provider: providerName,
|
|
99
100
|
...(preset?.label !== undefined && preset.label !== '' ? { providerLabel: preset.label } : {}),
|
|
100
101
|
user,
|
|
101
|
-
hasPassword: typeof account.password === 'string' && account.password !== '',
|
|
102
|
+
hasPassword: typeof account.password === 'string' && account.password !== '' || typeof account.apiKey === 'string' && account.apiKey !== '',
|
|
102
103
|
authKind,
|
|
103
104
|
...(pinned === 'oauth2' || pinned === 'password' ? { authKindDeclared: pinned } : {}),
|
|
104
105
|
...(clientId !== '' ? { clientId } : {}),
|
|
@@ -308,6 +309,8 @@ function washEndpoint(account, key) {
|
|
|
308
309
|
function persistedProvider(provider, customNames) {
|
|
309
310
|
if (typeof provider !== 'string' || provider === '')
|
|
310
311
|
return undefined;
|
|
312
|
+
if (provider === 'agentmail' || provider === 'agentmail-eu')
|
|
313
|
+
return provider;
|
|
311
314
|
if (Object.prototype.hasOwnProperty.call(PROVIDER_PRESETS, provider))
|
|
312
315
|
return provider;
|
|
313
316
|
return customNames.has(provider) ? provider : undefined;
|
|
@@ -520,6 +523,15 @@ function serializeAccountsDraft(source, cards, defaultAccount, customNames) {
|
|
|
520
523
|
// never written — a stored copy is washed out below instead.
|
|
521
524
|
const previousProvider = account.get('provider');
|
|
522
525
|
const nextProvider = persistedProvider(card.provider, customNames);
|
|
526
|
+
const apiProvider = (value) => value === 'agentmail' || value === 'agentmail-eu';
|
|
527
|
+
if (apiProvider(previousProvider) !== apiProvider(nextProvider)) {
|
|
528
|
+
account.delete('password');
|
|
529
|
+
account.delete('apiKey');
|
|
530
|
+
account.delete('authPassword');
|
|
531
|
+
account.delete('authKind');
|
|
532
|
+
}
|
|
533
|
+
if (card.password !== undefined && apiProvider(nextProvider))
|
|
534
|
+
account.delete('apiKey');
|
|
523
535
|
writeField(account, 'provider', nextProvider);
|
|
524
536
|
writeField(account, 'user', card.user);
|
|
525
537
|
// senderName / authUser are plain three-state fields (undefined = 保持原样,
|
|
@@ -749,10 +761,12 @@ export class EmailSettingsBackend {
|
|
|
749
761
|
ctx;
|
|
750
762
|
scope;
|
|
751
763
|
rowConfig;
|
|
752
|
-
|
|
764
|
+
apiFetch;
|
|
765
|
+
constructor(ctx, scope, rowConfig, apiFetch) {
|
|
753
766
|
this.ctx = ctx;
|
|
754
767
|
this.scope = scope;
|
|
755
768
|
this.rowConfig = rowConfig;
|
|
769
|
+
this.apiFetch = apiFetch;
|
|
756
770
|
}
|
|
757
771
|
/** Wired by apply(): the email_watch core; 'web' keeps its own cursor scope. */
|
|
758
772
|
watchImpl;
|
|
@@ -886,6 +900,10 @@ export class EmailSettingsBackend {
|
|
|
886
900
|
if (cfg === undefined) {
|
|
887
901
|
throw new Error(`未知账号 "${name}",可用:${available.join('、')}`);
|
|
888
902
|
}
|
|
903
|
+
if (cfg.transport === 'agentmail') {
|
|
904
|
+
await new AgentMailClient(settings, this.apiFetch).test(name);
|
|
905
|
+
return { ok: true, account: name, apiHost: cfg.apiBase, message: 'AgentMail API 连接成功 / AgentMail API connection successful' };
|
|
906
|
+
}
|
|
889
907
|
const target = { account: name, imapHost: cfg.imap.host, imapPort: cfg.imap.port };
|
|
890
908
|
const targetDescription = '账号 "' + name + '" · IMAP ' + cfg.imap.host + ':' + cfg.imap.port;
|
|
891
909
|
// An OAuth2 account has no password to check: without a token there is
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-email",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "在 DSH
|
|
3
|
+
"version": "0.18.0",
|
|
4
|
+
"description": "在 DSH 中浏览、收发、搜索和整理邮件,支持 IMAP/SMTP 与 AgentMail 多账号。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"dsh",
|
|
35
35
|
"email",
|
|
36
36
|
"imap",
|
|
37
|
-
"smtp"
|
|
37
|
+
"smtp",
|
|
38
|
+
"agentmail"
|
|
38
39
|
],
|
|
39
40
|
"author": "stardustlc",
|
|
40
41
|
"repository": {
|