agent-shell-chat 1.2.2

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.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/dist/bin/agent-shell.d.ts +15 -0
  4. package/dist/bin/agent-shell.d.ts.map +1 -0
  5. package/dist/bin/agent-shell.js +816 -0
  6. package/dist/bin/agent-shell.js.map +1 -0
  7. package/dist/package.json +54 -0
  8. package/dist/src/acp/agent-manager.d.ts +22 -0
  9. package/dist/src/acp/agent-manager.d.ts.map +1 -0
  10. package/dist/src/acp/agent-manager.js +79 -0
  11. package/dist/src/acp/agent-manager.js.map +1 -0
  12. package/dist/src/acp/client.d.ts +64 -0
  13. package/dist/src/acp/client.d.ts.map +1 -0
  14. package/dist/src/acp/client.js +265 -0
  15. package/dist/src/acp/client.js.map +1 -0
  16. package/dist/src/acp/session.d.ts +81 -0
  17. package/dist/src/acp/session.d.ts.map +1 -0
  18. package/dist/src/acp/session.js +339 -0
  19. package/dist/src/acp/session.js.map +1 -0
  20. package/dist/src/adapter/inbound.d.ts +39 -0
  21. package/dist/src/adapter/inbound.d.ts.map +1 -0
  22. package/dist/src/adapter/inbound.js +264 -0
  23. package/dist/src/adapter/inbound.js.map +1 -0
  24. package/dist/src/bridge.d.ts +115 -0
  25. package/dist/src/bridge.d.ts.map +1 -0
  26. package/dist/src/bridge.js +969 -0
  27. package/dist/src/bridge.js.map +1 -0
  28. package/dist/src/config.d.ts +155 -0
  29. package/dist/src/config.d.ts.map +1 -0
  30. package/dist/src/config.js +265 -0
  31. package/dist/src/config.js.map +1 -0
  32. package/dist/src/index.d.ts +9 -0
  33. package/dist/src/index.d.ts.map +1 -0
  34. package/dist/src/index.js +7 -0
  35. package/dist/src/index.js.map +1 -0
  36. package/dist/src/inject/monitor.d.ts +24 -0
  37. package/dist/src/inject/monitor.d.ts.map +1 -0
  38. package/dist/src/inject/monitor.js +149 -0
  39. package/dist/src/inject/monitor.js.map +1 -0
  40. package/dist/src/inject/queue.d.ts +13 -0
  41. package/dist/src/inject/queue.d.ts.map +1 -0
  42. package/dist/src/inject/queue.js +35 -0
  43. package/dist/src/inject/queue.js.map +1 -0
  44. package/dist/src/inject/types.d.ts +10 -0
  45. package/dist/src/inject/types.d.ts.map +1 -0
  46. package/dist/src/inject/types.js +2 -0
  47. package/dist/src/inject/types.js.map +1 -0
  48. package/dist/src/storage/accounts.d.ts +43 -0
  49. package/dist/src/storage/accounts.d.ts.map +1 -0
  50. package/dist/src/storage/accounts.js +289 -0
  51. package/dist/src/storage/accounts.js.map +1 -0
  52. package/dist/src/storage/runtime.d.ts +23 -0
  53. package/dist/src/storage/runtime.d.ts.map +1 -0
  54. package/dist/src/storage/runtime.js +104 -0
  55. package/dist/src/storage/runtime.js.map +1 -0
  56. package/dist/src/storage/state.d.ts +17 -0
  57. package/dist/src/storage/state.d.ts.map +1 -0
  58. package/dist/src/storage/state.js +78 -0
  59. package/dist/src/storage/state.js.map +1 -0
  60. package/dist/src/telemetry/index.d.ts +33 -0
  61. package/dist/src/telemetry/index.d.ts.map +1 -0
  62. package/dist/src/telemetry/index.js +167 -0
  63. package/dist/src/telemetry/index.js.map +1 -0
  64. package/dist/src/weixin/api.d.ts +50 -0
  65. package/dist/src/weixin/api.d.ts.map +1 -0
  66. package/dist/src/weixin/api.js +90 -0
  67. package/dist/src/weixin/api.js.map +1 -0
  68. package/dist/src/weixin/auth.d.ts +26 -0
  69. package/dist/src/weixin/auth.d.ts.map +1 -0
  70. package/dist/src/weixin/auth.js +103 -0
  71. package/dist/src/weixin/auth.js.map +1 -0
  72. package/dist/src/weixin/media.d.ts +24 -0
  73. package/dist/src/weixin/media.d.ts.map +1 -0
  74. package/dist/src/weixin/media.js +64 -0
  75. package/dist/src/weixin/media.js.map +1 -0
  76. package/dist/src/weixin/monitor.d.ts +16 -0
  77. package/dist/src/weixin/monitor.d.ts.map +1 -0
  78. package/dist/src/weixin/monitor.js +113 -0
  79. package/dist/src/weixin/monitor.js.map +1 -0
  80. package/dist/src/weixin/send.d.ts +28 -0
  81. package/dist/src/weixin/send.d.ts.map +1 -0
  82. package/dist/src/weixin/send.js +162 -0
  83. package/dist/src/weixin/send.js.map +1 -0
  84. package/dist/src/weixin/types.d.ts +149 -0
  85. package/dist/src/weixin/types.d.ts.map +1 -0
  86. package/dist/src/weixin/types.js +33 -0
  87. package/dist/src/weixin/types.js.map +1 -0
  88. package/package.json +54 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Per-user ACP session manager.
3
+ *
4
+ * Each WeChat user gets their own agent subprocess + ACP session.
5
+ * Messages are queued per-user to ensure serialized processing.
6
+ */
7
+ import type * as acp from "@agentclientprotocol/sdk";
8
+ import { AgentShellClient } from "./client.js";
9
+ import { type AgentProcessInfo } from "./agent-manager.js";
10
+ export interface PendingMessage {
11
+ prompt: acp.ContentBlock[];
12
+ contextToken: string;
13
+ completion?: {
14
+ resolve: () => void;
15
+ reject: (err: unknown) => void;
16
+ };
17
+ }
18
+ export interface UserSession {
19
+ userId: string;
20
+ contextToken: string;
21
+ client: AgentShellClient;
22
+ agentInfo: AgentProcessInfo;
23
+ configOptions: acp.SessionConfigOption[];
24
+ queue: PendingMessage[];
25
+ processing: boolean;
26
+ lastActivity: number;
27
+ createdAt: number;
28
+ systemPromptSent: boolean;
29
+ }
30
+ export interface SessionManagerOpts {
31
+ agentCommand: string;
32
+ agentArgs: string[];
33
+ agentCwd: string;
34
+ agentEnv?: Record<string, string>;
35
+ agentPreset?: string;
36
+ /** System prompt prepended to the first message of every new session. */
37
+ agentSystemPrompt?: string;
38
+ idleTimeoutMs: number;
39
+ maxConcurrentUsers: number;
40
+ showThoughts: boolean;
41
+ showDiffs?: boolean;
42
+ log: (msg: string) => void;
43
+ onReply: (userId: string, contextToken: string, text: string) => Promise<void>;
44
+ sendTyping: (userId: string, contextToken: string) => Promise<void>;
45
+ }
46
+ export declare class SessionManager {
47
+ private sessions;
48
+ private cleanupTimer;
49
+ private opts;
50
+ private aborted;
51
+ constructor(opts: SessionManagerOpts);
52
+ start(): void;
53
+ stop(): Promise<void>;
54
+ enqueue(userId: string, message: PendingMessage): Promise<void>;
55
+ enqueueAndWait(userId: string, message: Omit<PendingMessage, "completion">): Promise<void>;
56
+ getSession(userId: string): UserSession | undefined;
57
+ getSessionConfigOptions(userId: string): acp.SessionConfigOption[] | undefined;
58
+ setSessionConfigOption(userId: string, configId: string, value: string | boolean): Promise<acp.SessionConfigOption[]>;
59
+ /**
60
+ * Cancel the in-flight ACP prompt turn for a user, optionally also dropping
61
+ * any messages that were queued behind it.
62
+ *
63
+ * The ACP `session/cancel` notification is fire-and-forget; the in-flight
64
+ * `prompt()` call will resolve naturally with `stopReason: "cancelled"` and
65
+ * the existing `processQueue` loop will flush whatever output was already
66
+ * streamed back to WeChat (with a `[cancelled]` suffix).
67
+ */
68
+ cancelCurrent(userId: string, opts?: {
69
+ drainQueue?: boolean;
70
+ }): Promise<{
71
+ cancelledTurn: boolean;
72
+ droppedQueueCount: number;
73
+ }>;
74
+ get activeCount(): number;
75
+ private createSession;
76
+ private processQueue;
77
+ private cleanupIdleSessions;
78
+ private evictOldest;
79
+ private rejectQueuedCompletions;
80
+ }
81
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/acp/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Per-user ACP session manager.
3
+ *
4
+ * Each WeChat user gets their own agent subprocess + ACP session.
5
+ * Messages are queued per-user to ensure serialized processing.
6
+ */
7
+ import { AgentShellClient } from "./client.js";
8
+ import { spawnAgent, killAgent } from "./agent-manager.js";
9
+ import { trackEvent, trackException, hashUserId } from "../telemetry/index.js";
10
+ /**
11
+ * Build a short, user-friendly notice for a turn that ended without the
12
+ * agent producing any textual reply. The raw `stopReason` enum is kept
13
+ * out of the user-facing text (it is still logged) so users see a
14
+ * meaningful message rather than an internal token like `max_tokens`.
15
+ */
16
+ function emptyTurnNotice(stopReason) {
17
+ switch (stopReason) {
18
+ case "max_tokens":
19
+ return "ℹ️ The agent stopped at its output length limit before sending a reply. Try a more specific or shorter request.";
20
+ case "max_turn_requests":
21
+ return "ℹ️ The agent reached its tool-call limit before sending a reply. Try again or narrow the task.";
22
+ case "refusal":
23
+ return "ℹ️ The agent declined to respond to this request.";
24
+ case "cancelled":
25
+ return "ℹ️ The request was cancelled before the agent sent a reply.";
26
+ default:
27
+ return "ℹ️ The agent finished without sending a reply. Try rephrasing your request.";
28
+ }
29
+ }
30
+ export class SessionManager {
31
+ sessions = new Map();
32
+ cleanupTimer = null;
33
+ opts;
34
+ aborted = false;
35
+ constructor(opts) {
36
+ this.opts = opts;
37
+ }
38
+ start() {
39
+ // Run cleanup every 2 minutes
40
+ this.cleanupTimer = setInterval(() => this.cleanupIdleSessions(), 2 * 60_000);
41
+ this.cleanupTimer.unref();
42
+ }
43
+ async stop() {
44
+ this.aborted = true;
45
+ if (this.cleanupTimer) {
46
+ clearInterval(this.cleanupTimer);
47
+ this.cleanupTimer = null;
48
+ }
49
+ // Kill all agent processes
50
+ for (const [userId, session] of this.sessions) {
51
+ this.opts.log(`Stopping session for ${userId}`);
52
+ this.rejectQueuedCompletions(session, new Error("Session stopped before queued message was processed"));
53
+ killAgent(session.agentInfo.process);
54
+ }
55
+ this.sessions.clear();
56
+ }
57
+ async enqueue(userId, message) {
58
+ if (this.aborted) {
59
+ throw new Error("Session manager is stopped");
60
+ }
61
+ let session = this.sessions.get(userId);
62
+ if (!session) {
63
+ if (this.sessions.size >= this.opts.maxConcurrentUsers) {
64
+ // Evict oldest idle session
65
+ this.evictOldest();
66
+ }
67
+ session = await this.createSession(userId, message.contextToken);
68
+ this.sessions.set(userId, session);
69
+ // Inject system prompt into the first message of a new session
70
+ if (this.opts.agentSystemPrompt && !session.systemPromptSent) {
71
+ message.prompt = [
72
+ { type: "text", text: this.opts.agentSystemPrompt },
73
+ ...message.prompt,
74
+ ];
75
+ session.systemPromptSent = true;
76
+ }
77
+ }
78
+ // Always update contextToken to the latest
79
+ session.contextToken = message.contextToken;
80
+ session.lastActivity = Date.now();
81
+ session.queue.push(message);
82
+ if (!session.processing) {
83
+ // Fire-and-forget processing loop for this user
84
+ session.processing = true;
85
+ this.processQueue(session).catch((err) => {
86
+ this.opts.log(`[${userId}] queue processing error: ${String(err)}`);
87
+ });
88
+ }
89
+ }
90
+ async enqueueAndWait(userId, message) {
91
+ await new Promise((resolve, reject) => {
92
+ this.enqueue(userId, {
93
+ ...message,
94
+ completion: { resolve, reject },
95
+ }).catch(reject);
96
+ });
97
+ }
98
+ getSession(userId) {
99
+ return this.sessions.get(userId);
100
+ }
101
+ getSessionConfigOptions(userId) {
102
+ return this.sessions.get(userId)?.configOptions;
103
+ }
104
+ async setSessionConfigOption(userId, configId, value) {
105
+ const session = this.sessions.get(userId);
106
+ if (!session) {
107
+ throw new Error("No active ACP session for this chat yet. Send a normal message first.");
108
+ }
109
+ session.lastActivity = Date.now();
110
+ const response = await session.agentInfo.connection.setSessionConfigOption(typeof value === "boolean"
111
+ ? { sessionId: session.agentInfo.sessionId, configId, type: "boolean", value }
112
+ : { sessionId: session.agentInfo.sessionId, configId, value });
113
+ session.configOptions = response.configOptions;
114
+ session.agentInfo.configOptions = response.configOptions;
115
+ return response.configOptions;
116
+ }
117
+ /**
118
+ * Cancel the in-flight ACP prompt turn for a user, optionally also dropping
119
+ * any messages that were queued behind it.
120
+ *
121
+ * The ACP `session/cancel` notification is fire-and-forget; the in-flight
122
+ * `prompt()` call will resolve naturally with `stopReason: "cancelled"` and
123
+ * the existing `processQueue` loop will flush whatever output was already
124
+ * streamed back to WeChat (with a `[cancelled]` suffix).
125
+ */
126
+ async cancelCurrent(userId, opts) {
127
+ const session = this.sessions.get(userId);
128
+ if (!session) {
129
+ return { cancelledTurn: false, droppedQueueCount: 0 };
130
+ }
131
+ session.lastActivity = Date.now();
132
+ let droppedQueueCount = 0;
133
+ if (opts?.drainQueue && session.queue.length > 0) {
134
+ const dropped = session.queue.splice(0);
135
+ droppedQueueCount = dropped.length;
136
+ const err = new Error("Cancelled before queued message was processed");
137
+ for (const pending of dropped) {
138
+ pending.completion?.reject(err);
139
+ }
140
+ }
141
+ if (!session.processing) {
142
+ return { cancelledTurn: false, droppedQueueCount };
143
+ }
144
+ try {
145
+ await session.agentInfo.connection.cancel({ sessionId: session.agentInfo.sessionId });
146
+ }
147
+ catch (err) {
148
+ this.opts.log(`[${userId}] cancel notification failed: ${String(err)}`);
149
+ }
150
+ return { cancelledTurn: true, droppedQueueCount };
151
+ }
152
+ get activeCount() {
153
+ return this.sessions.size;
154
+ }
155
+ async createSession(userId, contextToken) {
156
+ this.opts.log(`Creating new session for ${userId}`);
157
+ const client = new AgentShellClient({
158
+ sendTyping: () => this.opts.sendTyping(userId, contextToken),
159
+ onThoughtFlush: (text) => this.opts.onReply(userId, contextToken, text),
160
+ onMessageFlush: (text) => this.opts.onReply(userId, contextToken, text),
161
+ onConfigOptionsUpdate: (configOptions) => {
162
+ const session = this.sessions.get(userId);
163
+ if (!session || session.client !== client)
164
+ return;
165
+ session.configOptions = configOptions;
166
+ session.agentInfo.configOptions = configOptions;
167
+ },
168
+ log: (msg) => this.opts.log(`[${userId}] ${msg}`),
169
+ showThoughts: this.opts.showThoughts,
170
+ showDiffs: this.opts.showDiffs ?? false,
171
+ });
172
+ const agentInfo = await spawnAgent({
173
+ command: this.opts.agentCommand,
174
+ args: this.opts.agentArgs,
175
+ cwd: this.opts.agentCwd,
176
+ env: this.opts.agentEnv,
177
+ client,
178
+ log: (msg) => this.opts.log(`[${userId}] ${msg}`),
179
+ });
180
+ trackEvent("session.created", {
181
+ userIdHash: hashUserId(userId),
182
+ agentPreset: this.opts.agentPreset ?? "raw",
183
+ activeSessions: this.sessions.size + 1,
184
+ }, hashUserId(userId));
185
+ // If agent process exits, clean up the session
186
+ agentInfo.process.on("exit", () => {
187
+ const s = this.sessions.get(userId);
188
+ if (s && s.agentInfo.process === agentInfo.process) {
189
+ this.opts.log(`Agent process for ${userId} exited, removing session`);
190
+ this.rejectQueuedCompletions(s, new Error("Agent process exited before queued message was processed"));
191
+ this.sessions.delete(userId);
192
+ }
193
+ });
194
+ return {
195
+ userId,
196
+ contextToken,
197
+ client,
198
+ agentInfo,
199
+ configOptions: agentInfo.configOptions,
200
+ queue: [],
201
+ processing: false,
202
+ lastActivity: Date.now(),
203
+ createdAt: Date.now(),
204
+ systemPromptSent: false,
205
+ };
206
+ }
207
+ async processQueue(session) {
208
+ try {
209
+ while (session.queue.length > 0 && !this.aborted) {
210
+ const pending = session.queue.shift();
211
+ let completionError;
212
+ // Keep the ACP client instance stable because the connection is bound to it.
213
+ session.client.updateCallbacks({
214
+ sendTyping: () => this.opts.sendTyping(session.userId, pending.contextToken),
215
+ onThoughtFlush: (text) => this.opts.onReply(session.userId, pending.contextToken, text),
216
+ onMessageFlush: (text) => this.opts.onReply(session.userId, pending.contextToken, text),
217
+ });
218
+ // Reset chunks for the new turn
219
+ await session.client.flush();
220
+ session.client.newTurn();
221
+ const promptStartedAt = Date.now();
222
+ try {
223
+ // Send typing immediately so user knows the prompt was received
224
+ this.opts.sendTyping(session.userId, pending.contextToken).catch(() => { });
225
+ // Send ACP prompt
226
+ this.opts.log(`[${session.userId}] Sending prompt to agent...`);
227
+ const result = await session.agentInfo.connection.prompt({
228
+ sessionId: session.agentInfo.sessionId,
229
+ prompt: pending.prompt,
230
+ });
231
+ // Collect accumulated text
232
+ let replyText = await session.client.flush();
233
+ if (result.stopReason === "cancelled") {
234
+ replyText += "\n[cancelled]";
235
+ }
236
+ else if (result.stopReason === "refusal") {
237
+ replyText += "\n[agent refused to continue]";
238
+ }
239
+ this.opts.log(`[${session.userId}] Agent done (${result.stopReason}), reply ${replyText.length} chars`);
240
+ trackEvent("prompt.completed", {
241
+ userIdHash: hashUserId(session.userId),
242
+ agentPreset: this.opts.agentPreset ?? "raw",
243
+ stopReason: String(result.stopReason),
244
+ success: true,
245
+ durationMs: Date.now() - promptStartedAt,
246
+ replyChars: replyText.length,
247
+ }, hashUserId(session.userId));
248
+ // Send reply back to WeChat
249
+ if (replyText.trim()) {
250
+ await this.opts.onReply(session.userId, pending.contextToken, replyText);
251
+ }
252
+ else if (!session.client.hasProducedMessage) {
253
+ // The turn ended without the agent ever producing a textual reply
254
+ // (e.g. it stopped after thoughts or a tool call). Surface a minimal
255
+ // notice so a turn never ends with zero user-facing output.
256
+ this.opts.log(`[${session.userId}] Empty reply with no message produced (${result.stopReason}); sending fallback notice`);
257
+ await this.opts.onReply(session.userId, pending.contextToken, emptyTurnNotice(result.stopReason));
258
+ }
259
+ }
260
+ catch (err) {
261
+ completionError = err;
262
+ this.opts.log(`[${session.userId}] Agent prompt error: ${String(err)}`);
263
+ trackException(err, "prompt", hashUserId(session.userId));
264
+ trackEvent("prompt.completed", {
265
+ userIdHash: hashUserId(session.userId),
266
+ agentPreset: this.opts.agentPreset ?? "raw",
267
+ stopReason: "error",
268
+ success: false,
269
+ durationMs: Date.now() - promptStartedAt,
270
+ replyChars: 0,
271
+ }, hashUserId(session.userId));
272
+ // Check if agent died
273
+ if (session.agentInfo.process.killed || session.agentInfo.process.exitCode !== null) {
274
+ this.opts.log(`[${session.userId}] Agent process died, removing session`);
275
+ this.rejectQueuedCompletions(session, err);
276
+ this.sessions.delete(session.userId);
277
+ return;
278
+ }
279
+ // Send error message to user
280
+ try {
281
+ await this.opts.onReply(session.userId, pending.contextToken, `⚠️ Agent error: ${String(err)}`);
282
+ }
283
+ catch {
284
+ // best effort
285
+ }
286
+ }
287
+ finally {
288
+ if (pending.completion) {
289
+ if (completionError) {
290
+ pending.completion.reject(completionError);
291
+ }
292
+ else {
293
+ pending.completion.resolve();
294
+ }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ finally {
300
+ session.processing = false;
301
+ }
302
+ }
303
+ cleanupIdleSessions() {
304
+ if (this.opts.idleTimeoutMs <= 0) {
305
+ return;
306
+ }
307
+ const now = Date.now();
308
+ for (const [userId, session] of this.sessions) {
309
+ if (now - session.lastActivity > this.opts.idleTimeoutMs && !session.processing) {
310
+ this.opts.log(`Session for ${userId} idle for ${Math.round((now - session.lastActivity) / 60_000)}min, removing`);
311
+ killAgent(session.agentInfo.process);
312
+ this.sessions.delete(userId);
313
+ }
314
+ }
315
+ }
316
+ evictOldest() {
317
+ let oldest = null;
318
+ for (const [userId, session] of this.sessions) {
319
+ if (!session.processing && (!oldest || session.lastActivity < oldest.lastActivity)) {
320
+ oldest = { userId, lastActivity: session.lastActivity };
321
+ }
322
+ }
323
+ if (oldest) {
324
+ this.opts.log(`Evicting oldest idle session: ${oldest.userId}`);
325
+ const session = this.sessions.get(oldest.userId);
326
+ if (session) {
327
+ this.rejectQueuedCompletions(session, new Error("Session evicted before queued message was processed"));
328
+ killAgent(session.agentInfo.process);
329
+ this.sessions.delete(oldest.userId);
330
+ }
331
+ }
332
+ }
333
+ rejectQueuedCompletions(session, err) {
334
+ for (const pending of session.queue.splice(0)) {
335
+ pending.completion?.reject(err);
336
+ }
337
+ }
338
+ }
339
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/acp/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Inbound adapter: convert WeChat messages to ACP ContentBlock[].
3
+ */
4
+ import type * as acp from "@agentclientprotocol/sdk";
5
+ import type { WeixinMessage } from "../weixin/types.js";
6
+ /**
7
+ * Convert a WeChat message to ACP ContentBlock[] for use in session/prompt.
8
+ */
9
+ export declare function weixinMessageToPrompt(msg: WeixinMessage, cdnBaseUrl: string, log: (msg: string) => void, inboxDir?: string | null): Promise<acp.ContentBlock[]>;
10
+ /**
11
+ * Write `buffer` into `inboxDir` and return the absolute path.
12
+ *
13
+ * Filename convention: `${ISO-timestamp}-${safeName}` for the first
14
+ * attempt, `${ISO-timestamp}-${N}-${safeName}` on the Nth retry, where
15
+ * - the timestamp uses ISO 8601 with colons and dots replaced by
16
+ * dashes (so the name avoids characters reserved on Windows),
17
+ * - `safeName` is `fileName` with path separators stripped, ASCII
18
+ * control + Windows-reserved chars (`<>:"/\|?*`) replaced by `_`,
19
+ * leading dots and trailing dots/spaces (which Windows silently
20
+ * trims) normalized to `_`, while Unicode is preserved so Chinese
21
+ * filenames stay readable,
22
+ * - if the sanitized name is empty, it falls back to `"file"`.
23
+ *
24
+ * Writes use the `wx` flag (fail-if-exists) so a collision — two
25
+ * bridge instances sharing an inbox, the user re-sending the same
26
+ * file twice in quick succession, a stubbed/frozen clock — never
27
+ * silently overwrites an existing file. On `EEXIST` we keep the
28
+ * original timestamp and bump a deterministic numeric suffix
29
+ * (`-1-`, `-2-`, …) capped at `INBOX_MAX_COLLISION_RETRIES`.
30
+ *
31
+ * Reserved-name corner case (Windows `CON`, `PRN`, `NUL`, `COM1`, …):
32
+ * Windows matches reserved device names against the basename exactly
33
+ * (case-insensitive, with or without extension). Because we always
34
+ * prefix the saved name with a timestamp like `2026-05-21T...Z-`,
35
+ * the basename is never one of those reserved tokens, so no extra
36
+ * handling is needed here.
37
+ */
38
+ export declare function saveToInbox(buffer: Buffer, fileName: string, inboxDir: string): Promise<string>;
39
+ //# sourceMappingURL=inbound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/adapter/inbound.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,KAAK,GAAG,MAAM,0BAA0B,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,oBAAoB,CAAC;AA4CrE;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,aAAa,EAClB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAiC7B;AAkID;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAiCjB"}