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.
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/dist/bin/agent-shell.d.ts +15 -0
- package/dist/bin/agent-shell.d.ts.map +1 -0
- package/dist/bin/agent-shell.js +816 -0
- package/dist/bin/agent-shell.js.map +1 -0
- package/dist/package.json +54 -0
- package/dist/src/acp/agent-manager.d.ts +22 -0
- package/dist/src/acp/agent-manager.d.ts.map +1 -0
- package/dist/src/acp/agent-manager.js +79 -0
- package/dist/src/acp/agent-manager.js.map +1 -0
- package/dist/src/acp/client.d.ts +64 -0
- package/dist/src/acp/client.d.ts.map +1 -0
- package/dist/src/acp/client.js +265 -0
- package/dist/src/acp/client.js.map +1 -0
- package/dist/src/acp/session.d.ts +81 -0
- package/dist/src/acp/session.d.ts.map +1 -0
- package/dist/src/acp/session.js +339 -0
- package/dist/src/acp/session.js.map +1 -0
- package/dist/src/adapter/inbound.d.ts +39 -0
- package/dist/src/adapter/inbound.d.ts.map +1 -0
- package/dist/src/adapter/inbound.js +264 -0
- package/dist/src/adapter/inbound.js.map +1 -0
- package/dist/src/bridge.d.ts +115 -0
- package/dist/src/bridge.d.ts.map +1 -0
- package/dist/src/bridge.js +969 -0
- package/dist/src/bridge.js.map +1 -0
- package/dist/src/config.d.ts +155 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +265 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/inject/monitor.d.ts +24 -0
- package/dist/src/inject/monitor.d.ts.map +1 -0
- package/dist/src/inject/monitor.js +149 -0
- package/dist/src/inject/monitor.js.map +1 -0
- package/dist/src/inject/queue.d.ts +13 -0
- package/dist/src/inject/queue.d.ts.map +1 -0
- package/dist/src/inject/queue.js +35 -0
- package/dist/src/inject/queue.js.map +1 -0
- package/dist/src/inject/types.d.ts +10 -0
- package/dist/src/inject/types.d.ts.map +1 -0
- package/dist/src/inject/types.js +2 -0
- package/dist/src/inject/types.js.map +1 -0
- package/dist/src/storage/accounts.d.ts +43 -0
- package/dist/src/storage/accounts.d.ts.map +1 -0
- package/dist/src/storage/accounts.js +289 -0
- package/dist/src/storage/accounts.js.map +1 -0
- package/dist/src/storage/runtime.d.ts +23 -0
- package/dist/src/storage/runtime.d.ts.map +1 -0
- package/dist/src/storage/runtime.js +104 -0
- package/dist/src/storage/runtime.js.map +1 -0
- package/dist/src/storage/state.d.ts +17 -0
- package/dist/src/storage/state.d.ts.map +1 -0
- package/dist/src/storage/state.js +78 -0
- package/dist/src/storage/state.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +33 -0
- package/dist/src/telemetry/index.d.ts.map +1 -0
- package/dist/src/telemetry/index.js +167 -0
- package/dist/src/telemetry/index.js.map +1 -0
- package/dist/src/weixin/api.d.ts +50 -0
- package/dist/src/weixin/api.d.ts.map +1 -0
- package/dist/src/weixin/api.js +90 -0
- package/dist/src/weixin/api.js.map +1 -0
- package/dist/src/weixin/auth.d.ts +26 -0
- package/dist/src/weixin/auth.d.ts.map +1 -0
- package/dist/src/weixin/auth.js +103 -0
- package/dist/src/weixin/auth.js.map +1 -0
- package/dist/src/weixin/media.d.ts +24 -0
- package/dist/src/weixin/media.d.ts.map +1 -0
- package/dist/src/weixin/media.js +64 -0
- package/dist/src/weixin/media.js.map +1 -0
- package/dist/src/weixin/monitor.d.ts +16 -0
- package/dist/src/weixin/monitor.d.ts.map +1 -0
- package/dist/src/weixin/monitor.js +113 -0
- package/dist/src/weixin/monitor.js.map +1 -0
- package/dist/src/weixin/send.d.ts +28 -0
- package/dist/src/weixin/send.d.ts.map +1 -0
- package/dist/src/weixin/send.js +162 -0
- package/dist/src/weixin/send.js.map +1 -0
- package/dist/src/weixin/types.d.ts +149 -0
- package/dist/src/weixin/types.d.ts.map +1 -0
- package/dist/src/weixin/types.js +33 -0
- package/dist/src/weixin/types.js.map +1 -0
- 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"}
|