wechat-acp 0.1.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/LICENSE +21 -0
- package/README.md +185 -0
- package/dist/bin/wechat-acp.d.ts +14 -0
- package/dist/bin/wechat-acp.d.ts.map +1 -0
- package/dist/bin/wechat-acp.js +270 -0
- package/dist/bin/wechat-acp.js.map +1 -0
- package/dist/package.json +55 -0
- package/dist/src/acp/agent-manager.d.ts +21 -0
- package/dist/src/acp/agent-manager.d.ts.map +1 -0
- package/dist/src/acp/agent-manager.js +73 -0
- package/dist/src/acp/agent-manager.js.map +1 -0
- package/dist/src/acp/client.d.ts +28 -0
- package/dist/src/acp/client.d.ts.map +1 -0
- package/dist/src/acp/client.js +121 -0
- package/dist/src/acp/client.js.map +1 -0
- package/dist/src/acp/session.d.ts +51 -0
- package/dist/src/acp/session.d.ts.map +1 -0
- package/dist/src/acp/session.js +173 -0
- package/dist/src/acp/session.js.map +1 -0
- package/dist/src/adapter/inbound.d.ts +10 -0
- package/dist/src/adapter/inbound.d.ts.map +1 -0
- package/dist/src/adapter/inbound.js +146 -0
- package/dist/src/adapter/inbound.js.map +1 -0
- package/dist/src/adapter/outbound.d.ts +9 -0
- package/dist/src/adapter/outbound.d.ts.map +1 -0
- package/dist/src/adapter/outbound.js +25 -0
- package/dist/src/adapter/outbound.js.map +1 -0
- package/dist/src/bridge.d.ts +28 -0
- package/dist/src/bridge.d.ts.map +1 -0
- package/dist/src/bridge.js +171 -0
- package/dist/src/bridge.js.map +1 -0
- package/dist/src/config.d.ts +61 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +114 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/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 +84 -0
- package/dist/src/weixin/api.js.map +1 -0
- package/dist/src/weixin/auth.d.ts +20 -0
- package/dist/src/weixin/auth.d.ts.map +1 -0
- package/dist/src/weixin/auth.js +84 -0
- package/dist/src/weixin/auth.js.map +1 -0
- package/dist/src/weixin/media.d.ts +23 -0
- package/dist/src/weixin/media.d.ts.map +1 -0
- package/dist/src/weixin/media.js +58 -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 +111 -0
- package/dist/src/weixin/monitor.js.map +1 -0
- package/dist/src/weixin/send.d.ts +14 -0
- package/dist/src/weixin/send.d.ts.map +1 -0
- package/dist/src/weixin/send.js +51 -0
- package/dist/src/weixin/send.js.map +1 -0
- package/dist/src/weixin/types.d.ts +148 -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 +55 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP Client implementation for WeChat.
|
|
3
|
+
*
|
|
4
|
+
* Implements the acp.Client interface: handles session updates (accumulates
|
|
5
|
+
* text chunks), auto-allows all permission requests, and provides filesystem
|
|
6
|
+
* access for the agent.
|
|
7
|
+
*/
|
|
8
|
+
import type * as acp from "@agentclientprotocol/sdk";
|
|
9
|
+
export interface WeChatAcpClientOpts {
|
|
10
|
+
sendTyping: () => Promise<void>;
|
|
11
|
+
log: (msg: string) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare class WeChatAcpClient implements acp.Client {
|
|
14
|
+
private chunks;
|
|
15
|
+
private opts;
|
|
16
|
+
private lastTypingAt;
|
|
17
|
+
private static readonly TYPING_INTERVAL_MS;
|
|
18
|
+
constructor(opts: WeChatAcpClientOpts);
|
|
19
|
+
updateSendTyping(sendTyping: () => Promise<void>): void;
|
|
20
|
+
requestPermission(params: acp.RequestPermissionRequest): Promise<acp.RequestPermissionResponse>;
|
|
21
|
+
sessionUpdate(params: acp.SessionNotification): Promise<void>;
|
|
22
|
+
readTextFile(params: acp.ReadTextFileRequest): Promise<acp.ReadTextFileResponse>;
|
|
23
|
+
writeTextFile(params: acp.WriteTextFileRequest): Promise<acp.WriteTextFileResponse>;
|
|
24
|
+
/** Get accumulated text and reset the buffer. */
|
|
25
|
+
flush(): string;
|
|
26
|
+
private maybeSendTyping;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/acp/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,KAAK,GAAG,MAAM,0BAA0B,CAAC;AAErD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5B;AAED,qBAAa,eAAgB,YAAW,GAAG,CAAC,MAAM;IAChD,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAS;gBAEvC,IAAI,EAAE,mBAAmB;IAIrC,gBAAgB,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAOjD,iBAAiB,CACrB,MAAM,EAAE,GAAG,CAAC,wBAAwB,GACnC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;IAiBnC,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmD7D,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,mBAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAShF,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,oBAAoB,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IASzF,iDAAiD;IACjD,KAAK,IAAI,MAAM;YAOD,eAAe;CAU9B"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP Client implementation for WeChat.
|
|
3
|
+
*
|
|
4
|
+
* Implements the acp.Client interface: handles session updates (accumulates
|
|
5
|
+
* text chunks), auto-allows all permission requests, and provides filesystem
|
|
6
|
+
* access for the agent.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
export class WeChatAcpClient {
|
|
10
|
+
chunks = [];
|
|
11
|
+
opts;
|
|
12
|
+
lastTypingAt = 0;
|
|
13
|
+
static TYPING_INTERVAL_MS = 5_000;
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
this.opts = opts;
|
|
16
|
+
}
|
|
17
|
+
updateSendTyping(sendTyping) {
|
|
18
|
+
this.opts = {
|
|
19
|
+
...this.opts,
|
|
20
|
+
sendTyping,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async requestPermission(params) {
|
|
24
|
+
// Auto-allow: find first "allow" option
|
|
25
|
+
const allowOpt = params.options.find((o) => o.kind === "allow_once" || o.kind === "allow_always");
|
|
26
|
+
const optionId = allowOpt?.optionId ?? params.options[0]?.optionId ?? "allow";
|
|
27
|
+
this.opts.log(`[permission] auto-allowed: ${params.toolCall?.title ?? "unknown"} → ${optionId}`);
|
|
28
|
+
return {
|
|
29
|
+
outcome: {
|
|
30
|
+
outcome: "selected",
|
|
31
|
+
optionId,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async sessionUpdate(params) {
|
|
36
|
+
const update = params.update;
|
|
37
|
+
switch (update.sessionUpdate) {
|
|
38
|
+
case "agent_message_chunk":
|
|
39
|
+
if (update.content.type === "text") {
|
|
40
|
+
this.chunks.push(update.content.text);
|
|
41
|
+
}
|
|
42
|
+
// Throttle typing indicators
|
|
43
|
+
await this.maybeSendTyping();
|
|
44
|
+
break;
|
|
45
|
+
case "tool_call":
|
|
46
|
+
this.opts.log(`[tool] ${update.title} (${update.status})`);
|
|
47
|
+
await this.maybeSendTyping();
|
|
48
|
+
break;
|
|
49
|
+
case "tool_call_update":
|
|
50
|
+
if (update.status === "completed" && update.content) {
|
|
51
|
+
for (const c of update.content) {
|
|
52
|
+
if (c.type === "diff") {
|
|
53
|
+
const diff = c;
|
|
54
|
+
const header = `--- ${diff.path}`;
|
|
55
|
+
const lines = [header];
|
|
56
|
+
if (diff.oldText != null) {
|
|
57
|
+
for (const l of diff.oldText.split("\n"))
|
|
58
|
+
lines.push(`- ${l}`);
|
|
59
|
+
}
|
|
60
|
+
if (diff.newText != null) {
|
|
61
|
+
for (const l of diff.newText.split("\n"))
|
|
62
|
+
lines.push(`+ ${l}`);
|
|
63
|
+
}
|
|
64
|
+
this.chunks.push("\n```diff\n" + lines.join("\n") + "\n```\n");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (update.status) {
|
|
69
|
+
this.opts.log(`[tool] ${update.toolCallId} → ${update.status}`);
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
case "plan":
|
|
73
|
+
// Log plan entries
|
|
74
|
+
if (update.entries) {
|
|
75
|
+
const items = update.entries
|
|
76
|
+
.map((e, i) => ` ${i + 1}. [${e.status}] ${e.content}`)
|
|
77
|
+
.join("\n");
|
|
78
|
+
this.opts.log(`[plan]\n${items}`);
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async readTextFile(params) {
|
|
84
|
+
try {
|
|
85
|
+
const content = await fs.promises.readFile(params.path, "utf-8");
|
|
86
|
+
return { content };
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
throw new Error(`Failed to read file ${params.path}: ${String(err)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async writeTextFile(params) {
|
|
93
|
+
try {
|
|
94
|
+
await fs.promises.writeFile(params.path, params.content, "utf-8");
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
throw new Error(`Failed to write file ${params.path}: ${String(err)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Get accumulated text and reset the buffer. */
|
|
102
|
+
flush() {
|
|
103
|
+
const text = this.chunks.join("");
|
|
104
|
+
this.chunks = [];
|
|
105
|
+
this.lastTypingAt = 0;
|
|
106
|
+
return text;
|
|
107
|
+
}
|
|
108
|
+
async maybeSendTyping() {
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
if (now - this.lastTypingAt < WeChatAcpClient.TYPING_INTERVAL_MS)
|
|
111
|
+
return;
|
|
112
|
+
this.lastTypingAt = now;
|
|
113
|
+
try {
|
|
114
|
+
await this.opts.sendTyping();
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// typing is best-effort
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/acp/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AAQzB,MAAM,OAAO,eAAe;IAClB,MAAM,GAAa,EAAE,CAAC;IACtB,IAAI,CAAsB;IAC1B,YAAY,GAAG,CAAC,CAAC;IACjB,MAAM,CAAU,kBAAkB,GAAG,KAAK,CAAC;IAEnD,YAAY,IAAyB;QACnC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,gBAAgB,CAAC,UAA+B;QAC9C,IAAI,CAAC,IAAI,GAAG;YACV,GAAG,IAAI,CAAC,IAAI;YACZ,UAAU;SACX,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,iBAAiB,CACrB,MAAoC;QAEpC,wCAAwC;QACxC,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAClC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,CAAC,IAAI,KAAK,cAAc,CAC5D,CAAC;QACF,MAAM,QAAQ,GAAG,QAAQ,EAAE,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,OAAO,CAAC;QAE9E,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,8BAA8B,MAAM,CAAC,QAAQ,EAAE,KAAK,IAAI,SAAS,MAAM,QAAQ,EAAE,CAAC,CAAC;QAEjG,OAAO;YACL,OAAO,EAAE;gBACP,OAAO,EAAE,UAAU;gBACnB,QAAQ;aACT;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MAA+B;QACjD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAE7B,QAAQ,MAAM,CAAC,aAAa,EAAE,CAAC;YAC7B,KAAK,qBAAqB;gBACxB,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBACnC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBACxC,CAAC;gBACD,6BAA6B;gBAC7B,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC7B,MAAM;YAER,KAAK,WAAW;gBACd,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;gBAC3D,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC7B,MAAM;YAER,KAAK,kBAAkB;gBACrB,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;oBACpD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;wBAC/B,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;4BACtB,MAAM,IAAI,GAAG,CAAa,CAAC;4BAC3B,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;4BAClC,MAAM,KAAK,GAAa,CAAC,MAAM,CAAC,CAAC;4BACjC,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;gCACzB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;oCAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;4BACjE,CAAC;4BACD,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;gCACzB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;oCAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;4BACjE,CAAC;4BACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;wBACjE,CAAC;oBACH,CAAC;gBACH,CAAC;gBACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;oBAClB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,UAAU,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBAClE,CAAC;gBACD,MAAM;YAER,KAAK,MAAM;gBACT,mBAAmB;gBACnB,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;oBACnB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO;yBACzB,GAAG,CAAC,CAAC,CAAgB,EAAE,CAAS,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;yBAC9E,IAAI,CAAC,IAAI,CAAC,CAAC;oBACd,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC;gBACpC,CAAC;gBACD,MAAM;QACV,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAA+B;QAChD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACjE,OAAO,EAAE,OAAO,EAAE,CAAC;QACrB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MAAgC;QAClD,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAClE,OAAO,EAAE,CAAC;QACZ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,wBAAwB,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAED,iDAAiD;IACjD,KAAK;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,KAAK,CAAC,eAAe;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,IAAI,CAAC,YAAY,GAAG,eAAe,CAAC,kBAAkB;YAAE,OAAO;QACzE,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
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 { WeChatAcpClient } from "./client.js";
|
|
9
|
+
import { type AgentProcessInfo } from "./agent-manager.js";
|
|
10
|
+
export interface PendingMessage {
|
|
11
|
+
prompt: acp.ContentBlock[];
|
|
12
|
+
contextToken: string;
|
|
13
|
+
}
|
|
14
|
+
export interface UserSession {
|
|
15
|
+
userId: string;
|
|
16
|
+
contextToken: string;
|
|
17
|
+
client: WeChatAcpClient;
|
|
18
|
+
agentInfo: AgentProcessInfo;
|
|
19
|
+
queue: PendingMessage[];
|
|
20
|
+
processing: boolean;
|
|
21
|
+
lastActivity: number;
|
|
22
|
+
createdAt: number;
|
|
23
|
+
}
|
|
24
|
+
export interface SessionManagerOpts {
|
|
25
|
+
agentCommand: string;
|
|
26
|
+
agentArgs: string[];
|
|
27
|
+
agentCwd: string;
|
|
28
|
+
agentEnv?: Record<string, string>;
|
|
29
|
+
idleTimeoutMs: number;
|
|
30
|
+
maxConcurrentUsers: number;
|
|
31
|
+
log: (msg: string) => void;
|
|
32
|
+
onReply: (userId: string, contextToken: string, text: string) => Promise<void>;
|
|
33
|
+
sendTyping: (userId: string, contextToken: string) => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare class SessionManager {
|
|
36
|
+
private sessions;
|
|
37
|
+
private cleanupTimer;
|
|
38
|
+
private opts;
|
|
39
|
+
private aborted;
|
|
40
|
+
constructor(opts: SessionManagerOpts);
|
|
41
|
+
start(): void;
|
|
42
|
+
stop(): Promise<void>;
|
|
43
|
+
enqueue(userId: string, message: PendingMessage): Promise<void>;
|
|
44
|
+
getSession(userId: string): UserSession | undefined;
|
|
45
|
+
get activeCount(): number;
|
|
46
|
+
private createSession;
|
|
47
|
+
private processQueue;
|
|
48
|
+
private cleanupIdleSessions;
|
|
49
|
+
private evictOldest;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/acp/session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,KAAK,GAAG,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAElF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,SAAS,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,YAAY,CAA+C;IACnE,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,kBAAkB;IAIpC,KAAK,IAAI,IAAI;IAMP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAcrB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BrE,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAInD,IAAI,WAAW,IAAI,MAAM,CAExB;YAEa,aAAa;YAsCb,YAAY;IA+D1B,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,WAAW;CAcpB"}
|
|
@@ -0,0 +1,173 @@
|
|
|
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 { WeChatAcpClient } from "./client.js";
|
|
8
|
+
import { spawnAgent, killAgent } from "./agent-manager.js";
|
|
9
|
+
export class SessionManager {
|
|
10
|
+
sessions = new Map();
|
|
11
|
+
cleanupTimer = null;
|
|
12
|
+
opts;
|
|
13
|
+
aborted = false;
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
this.opts = opts;
|
|
16
|
+
}
|
|
17
|
+
start() {
|
|
18
|
+
// Run cleanup every 2 minutes
|
|
19
|
+
this.cleanupTimer = setInterval(() => this.cleanupIdleSessions(), 2 * 60_000);
|
|
20
|
+
this.cleanupTimer.unref();
|
|
21
|
+
}
|
|
22
|
+
async stop() {
|
|
23
|
+
this.aborted = true;
|
|
24
|
+
if (this.cleanupTimer) {
|
|
25
|
+
clearInterval(this.cleanupTimer);
|
|
26
|
+
this.cleanupTimer = null;
|
|
27
|
+
}
|
|
28
|
+
// Kill all agent processes
|
|
29
|
+
for (const [userId, session] of this.sessions) {
|
|
30
|
+
this.opts.log(`Stopping session for ${userId}`);
|
|
31
|
+
killAgent(session.agentInfo.process);
|
|
32
|
+
}
|
|
33
|
+
this.sessions.clear();
|
|
34
|
+
}
|
|
35
|
+
async enqueue(userId, message) {
|
|
36
|
+
let session = this.sessions.get(userId);
|
|
37
|
+
if (!session) {
|
|
38
|
+
if (this.sessions.size >= this.opts.maxConcurrentUsers) {
|
|
39
|
+
// Evict oldest idle session
|
|
40
|
+
this.evictOldest();
|
|
41
|
+
}
|
|
42
|
+
session = await this.createSession(userId, message.contextToken);
|
|
43
|
+
this.sessions.set(userId, session);
|
|
44
|
+
}
|
|
45
|
+
// Always update contextToken to the latest
|
|
46
|
+
session.contextToken = message.contextToken;
|
|
47
|
+
session.lastActivity = Date.now();
|
|
48
|
+
session.queue.push(message);
|
|
49
|
+
if (!session.processing) {
|
|
50
|
+
// Fire-and-forget processing loop for this user
|
|
51
|
+
session.processing = true;
|
|
52
|
+
this.processQueue(session).catch((err) => {
|
|
53
|
+
this.opts.log(`[${userId}] queue processing error: ${String(err)}`);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
getSession(userId) {
|
|
58
|
+
return this.sessions.get(userId);
|
|
59
|
+
}
|
|
60
|
+
get activeCount() {
|
|
61
|
+
return this.sessions.size;
|
|
62
|
+
}
|
|
63
|
+
async createSession(userId, contextToken) {
|
|
64
|
+
this.opts.log(`Creating new session for ${userId}`);
|
|
65
|
+
const client = new WeChatAcpClient({
|
|
66
|
+
sendTyping: () => this.opts.sendTyping(userId, contextToken),
|
|
67
|
+
log: (msg) => this.opts.log(`[${userId}] ${msg}`),
|
|
68
|
+
});
|
|
69
|
+
const agentInfo = await spawnAgent({
|
|
70
|
+
command: this.opts.agentCommand,
|
|
71
|
+
args: this.opts.agentArgs,
|
|
72
|
+
cwd: this.opts.agentCwd,
|
|
73
|
+
env: this.opts.agentEnv,
|
|
74
|
+
client,
|
|
75
|
+
log: (msg) => this.opts.log(`[${userId}] ${msg}`),
|
|
76
|
+
});
|
|
77
|
+
// If agent process exits, clean up the session
|
|
78
|
+
agentInfo.process.on("exit", () => {
|
|
79
|
+
const s = this.sessions.get(userId);
|
|
80
|
+
if (s && s.agentInfo.process === agentInfo.process) {
|
|
81
|
+
this.opts.log(`Agent process for ${userId} exited, removing session`);
|
|
82
|
+
this.sessions.delete(userId);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
userId,
|
|
87
|
+
contextToken,
|
|
88
|
+
client,
|
|
89
|
+
agentInfo,
|
|
90
|
+
queue: [],
|
|
91
|
+
processing: false,
|
|
92
|
+
lastActivity: Date.now(),
|
|
93
|
+
createdAt: Date.now(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async processQueue(session) {
|
|
97
|
+
try {
|
|
98
|
+
while (session.queue.length > 0 && !this.aborted) {
|
|
99
|
+
const pending = session.queue.shift();
|
|
100
|
+
// Keep the ACP client instance stable because the connection is bound to it.
|
|
101
|
+
session.client.updateSendTyping(() => this.opts.sendTyping(session.userId, pending.contextToken));
|
|
102
|
+
// Reset chunks for the new turn
|
|
103
|
+
session.client.flush();
|
|
104
|
+
try {
|
|
105
|
+
// Send ACP prompt
|
|
106
|
+
this.opts.log(`[${session.userId}] Sending prompt to agent...`);
|
|
107
|
+
const result = await session.agentInfo.connection.prompt({
|
|
108
|
+
sessionId: session.agentInfo.sessionId,
|
|
109
|
+
prompt: pending.prompt,
|
|
110
|
+
});
|
|
111
|
+
// Collect accumulated text
|
|
112
|
+
let replyText = session.client.flush();
|
|
113
|
+
if (result.stopReason === "cancelled") {
|
|
114
|
+
replyText += "\n[cancelled]";
|
|
115
|
+
}
|
|
116
|
+
else if (result.stopReason === "refusal") {
|
|
117
|
+
replyText += "\n[agent refused to continue]";
|
|
118
|
+
}
|
|
119
|
+
this.opts.log(`[${session.userId}] Agent done (${result.stopReason}), reply ${replyText.length} chars`);
|
|
120
|
+
// Send reply back to WeChat
|
|
121
|
+
if (replyText.trim()) {
|
|
122
|
+
await this.opts.onReply(session.userId, pending.contextToken, replyText);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
this.opts.log(`[${session.userId}] Agent prompt error: ${String(err)}`);
|
|
127
|
+
// Check if agent died
|
|
128
|
+
if (session.agentInfo.process.killed || session.agentInfo.process.exitCode !== null) {
|
|
129
|
+
this.opts.log(`[${session.userId}] Agent process died, removing session`);
|
|
130
|
+
this.sessions.delete(session.userId);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Send error message to user
|
|
134
|
+
try {
|
|
135
|
+
await this.opts.onReply(session.userId, pending.contextToken, `⚠️ Agent error: ${String(err)}`);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// best effort
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
session.processing = false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
cleanupIdleSessions() {
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
for (const [userId, session] of this.sessions) {
|
|
150
|
+
if (now - session.lastActivity > this.opts.idleTimeoutMs && !session.processing) {
|
|
151
|
+
this.opts.log(`Session for ${userId} idle for ${Math.round((now - session.lastActivity) / 60_000)}min, removing`);
|
|
152
|
+
killAgent(session.agentInfo.process);
|
|
153
|
+
this.sessions.delete(userId);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
evictOldest() {
|
|
158
|
+
let oldest = null;
|
|
159
|
+
for (const [userId, session] of this.sessions) {
|
|
160
|
+
if (!session.processing && (!oldest || session.lastActivity < oldest.lastActivity)) {
|
|
161
|
+
oldest = { userId, lastActivity: session.lastActivity };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (oldest) {
|
|
165
|
+
this.opts.log(`Evicting oldest idle session: ${oldest.userId}`);
|
|
166
|
+
const session = this.sessions.get(oldest.userId);
|
|
167
|
+
if (session)
|
|
168
|
+
killAgent(session.agentInfo.process);
|
|
169
|
+
this.sessions.delete(oldest.userId);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//# 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,10 @@
|
|
|
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): Promise<acp.ContentBlock[]>;
|
|
10
|
+
//# 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;AAGH,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,GACzB,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAiC7B"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound adapter: convert WeChat messages to ACP ContentBlock[].
|
|
3
|
+
*/
|
|
4
|
+
import { MessageItemType } from "../weixin/types.js";
|
|
5
|
+
import { parseAesKey, downloadAndDecrypt } from "../weixin/media.js";
|
|
6
|
+
/**
|
|
7
|
+
* Extract text body from a WeChat message's item_list.
|
|
8
|
+
*/
|
|
9
|
+
function extractText(itemList) {
|
|
10
|
+
if (!itemList?.length)
|
|
11
|
+
return "";
|
|
12
|
+
for (const item of itemList) {
|
|
13
|
+
if (item.type === MessageItemType.TEXT && item.text_item?.text != null) {
|
|
14
|
+
const text = String(item.text_item.text);
|
|
15
|
+
const ref = item.ref_msg;
|
|
16
|
+
if (!ref)
|
|
17
|
+
return text;
|
|
18
|
+
// Build quoted context
|
|
19
|
+
const parts = [];
|
|
20
|
+
if (ref.title)
|
|
21
|
+
parts.push(ref.title);
|
|
22
|
+
if (ref.message_item?.text_item?.text)
|
|
23
|
+
parts.push(ref.message_item.text_item.text);
|
|
24
|
+
if (!parts.length)
|
|
25
|
+
return text;
|
|
26
|
+
return `[引用: ${parts.join(" | ")}]\n${text}`;
|
|
27
|
+
}
|
|
28
|
+
// Voice transcription
|
|
29
|
+
if (item.type === MessageItemType.VOICE && item.voice_item?.text) {
|
|
30
|
+
return item.voice_item.text;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Find the first media item in a message.
|
|
37
|
+
*/
|
|
38
|
+
function findMediaItem(itemList) {
|
|
39
|
+
if (!itemList)
|
|
40
|
+
return undefined;
|
|
41
|
+
return (itemList.find((i) => i.type === MessageItemType.IMAGE && i.image_item?.media?.encrypt_query_param) ??
|
|
42
|
+
itemList.find((i) => i.type === MessageItemType.VIDEO && i.video_item?.media?.encrypt_query_param) ??
|
|
43
|
+
itemList.find((i) => i.type === MessageItemType.FILE && i.file_item?.media?.encrypt_query_param) ??
|
|
44
|
+
itemList.find((i) => i.type === MessageItemType.VOICE && i.voice_item?.media?.encrypt_query_param && !i.voice_item.text));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Convert a WeChat message to ACP ContentBlock[] for use in session/prompt.
|
|
48
|
+
*/
|
|
49
|
+
export async function weixinMessageToPrompt(msg, cdnBaseUrl, log) {
|
|
50
|
+
const blocks = [];
|
|
51
|
+
// Extract text
|
|
52
|
+
const text = extractText(msg.item_list);
|
|
53
|
+
if (text) {
|
|
54
|
+
blocks.push({ type: "text", text });
|
|
55
|
+
}
|
|
56
|
+
// Try to download and attach media
|
|
57
|
+
const mediaItem = findMediaItem(msg.item_list);
|
|
58
|
+
if (mediaItem) {
|
|
59
|
+
try {
|
|
60
|
+
const attached = await convertMediaItem(mediaItem, cdnBaseUrl, log);
|
|
61
|
+
if (attached)
|
|
62
|
+
blocks.push(attached);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
log(`Media download failed, skipping: ${String(err)}`);
|
|
66
|
+
// Add a text note about the media
|
|
67
|
+
const mediaType = mediaItem.type === MessageItemType.IMAGE ? "image"
|
|
68
|
+
: mediaItem.type === MessageItemType.VIDEO ? "video"
|
|
69
|
+
: mediaItem.type === MessageItemType.FILE ? `file (${mediaItem.file_item?.file_name ?? "unknown"})`
|
|
70
|
+
: mediaItem.type === MessageItemType.VOICE ? "voice"
|
|
71
|
+
: "media";
|
|
72
|
+
blocks.push({ type: "text", text: `[Received ${mediaType} - download failed]` });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Fallback: always have at least one content block
|
|
76
|
+
if (blocks.length === 0) {
|
|
77
|
+
blocks.push({ type: "text", text: "[empty message]" });
|
|
78
|
+
}
|
|
79
|
+
return blocks;
|
|
80
|
+
}
|
|
81
|
+
async function convertMediaItem(item, cdnBaseUrl, log) {
|
|
82
|
+
if (item.type === MessageItemType.IMAGE && item.image_item?.media) {
|
|
83
|
+
const media = item.image_item.media;
|
|
84
|
+
const aesKey = parseAesKey(media);
|
|
85
|
+
if (!aesKey || !media.encrypt_query_param)
|
|
86
|
+
return null;
|
|
87
|
+
log("Downloading image from CDN...");
|
|
88
|
+
const buffer = await downloadAndDecrypt(media.encrypt_query_param, aesKey, cdnBaseUrl);
|
|
89
|
+
const base64 = buffer.toString("base64");
|
|
90
|
+
return {
|
|
91
|
+
type: "image",
|
|
92
|
+
data: base64,
|
|
93
|
+
mimeType: "image/jpeg",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (item.type === MessageItemType.FILE && item.file_item?.media) {
|
|
97
|
+
const media = item.file_item.media;
|
|
98
|
+
const aesKey = parseAesKey(media);
|
|
99
|
+
if (!aesKey || !media.encrypt_query_param)
|
|
100
|
+
return null;
|
|
101
|
+
log(`Downloading file "${item.file_item.file_name}" from CDN...`);
|
|
102
|
+
const buffer = await downloadAndDecrypt(media.encrypt_query_param, aesKey, cdnBaseUrl);
|
|
103
|
+
// For text-like files, send as resource; for binary, describe it
|
|
104
|
+
const fileName = item.file_item.file_name ?? "file";
|
|
105
|
+
if (isTextFile(fileName)) {
|
|
106
|
+
const content = buffer.toString("utf-8");
|
|
107
|
+
return {
|
|
108
|
+
type: "resource",
|
|
109
|
+
resource: {
|
|
110
|
+
uri: `file:///${fileName}`,
|
|
111
|
+
mimeType: guessMimeType(fileName),
|
|
112
|
+
text: content,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return { type: "text", text: `[Received file: ${fileName}, ${buffer.length} bytes]` };
|
|
117
|
+
}
|
|
118
|
+
if (item.type === MessageItemType.VOICE && item.voice_item?.media) {
|
|
119
|
+
// If there's a transcription, it was already handled in extractText
|
|
120
|
+
// Otherwise, note we received voice
|
|
121
|
+
return { type: "text", text: "[Received voice message - no transcription available]" };
|
|
122
|
+
}
|
|
123
|
+
if (item.type === MessageItemType.VIDEO) {
|
|
124
|
+
return { type: "text", text: "[Received video message]" };
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
function isTextFile(name) {
|
|
129
|
+
const ext = name.split(".").pop()?.toLowerCase() ?? "";
|
|
130
|
+
return [
|
|
131
|
+
"txt", "md", "json", "js", "ts", "py", "java", "c", "cpp", "h",
|
|
132
|
+
"css", "html", "xml", "yaml", "yml", "toml", "ini", "cfg", "sh",
|
|
133
|
+
"bash", "rs", "go", "rb", "php", "sql", "csv", "log", "env",
|
|
134
|
+
].includes(ext);
|
|
135
|
+
}
|
|
136
|
+
function guessMimeType(name) {
|
|
137
|
+
const ext = name.split(".").pop()?.toLowerCase() ?? "";
|
|
138
|
+
const map = {
|
|
139
|
+
txt: "text/plain", md: "text/markdown", json: "application/json",
|
|
140
|
+
js: "text/javascript", ts: "text/typescript", py: "text/x-python",
|
|
141
|
+
html: "text/html", css: "text/css", xml: "text/xml",
|
|
142
|
+
yaml: "text/yaml", yml: "text/yaml", csv: "text/csv",
|
|
143
|
+
};
|
|
144
|
+
return map[ext] ?? "text/plain";
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=inbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.js","sourceRoot":"","sources":["../../../src/adapter/inbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound adapter: format ACP output for WeChat delivery.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Strip markdown formatting for cleaner WeChat display.
|
|
6
|
+
* Preserves code blocks (as they're useful even in plain text).
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatForWeChat(text: string): string;
|
|
9
|
+
//# sourceMappingURL=outbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../../src/adapter/outbound.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqBpD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound adapter: format ACP output for WeChat delivery.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Strip markdown formatting for cleaner WeChat display.
|
|
6
|
+
* Preserves code blocks (as they're useful even in plain text).
|
|
7
|
+
*/
|
|
8
|
+
export function formatForWeChat(text) {
|
|
9
|
+
// Remove image references 
|
|
10
|
+
let out = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "[$1]");
|
|
11
|
+
// Convert links [text](url) → text (url)
|
|
12
|
+
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
|
|
13
|
+
// Remove bold/italic markers but keep text
|
|
14
|
+
out = out.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
|
|
15
|
+
out = out.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
16
|
+
out = out.replace(/\*(.+?)\*/g, "$1");
|
|
17
|
+
out = out.replace(/__(.+?)__/g, "$1");
|
|
18
|
+
out = out.replace(/_(.+?)_/g, "$1");
|
|
19
|
+
// Remove heading markers
|
|
20
|
+
out = out.replace(/^#{1,6}\s+/gm, "");
|
|
21
|
+
// Clean up excessive blank lines
|
|
22
|
+
out = out.replace(/\n{3,}/g, "\n\n");
|
|
23
|
+
return out.trim();
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=outbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.js","sourceRoot":"","sources":["../../../src/adapter/outbound.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,sCAAsC;IACtC,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,yBAAyB,EAAE,MAAM,CAAC,CAAC;IAE1D,yCAAyC;IACzC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,0BAA0B,EAAE,SAAS,CAAC,CAAC;IAEzD,2CAA2C;IAC3C,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAC;IAC9C,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;IAC1C,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IACtC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IACtC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAEpC,yBAAyB;IACzB,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IAEtC,iCAAiC;IACjC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAErC,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChatAcpBridge — the main orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Connects WeChat's iLink long-poll to ACP agent subprocesses.
|
|
5
|
+
* One bridge = one WeChat bot account → many users → many agent sessions.
|
|
6
|
+
*/
|
|
7
|
+
import type { WeChatAcpConfig } from "./config.js";
|
|
8
|
+
export declare class WeChatAcpBridge {
|
|
9
|
+
private config;
|
|
10
|
+
private abortController;
|
|
11
|
+
private sessionManager;
|
|
12
|
+
private tokenData;
|
|
13
|
+
private typingTickets;
|
|
14
|
+
private log;
|
|
15
|
+
constructor(config: WeChatAcpConfig, log?: (msg: string) => void);
|
|
16
|
+
start(opts?: {
|
|
17
|
+
forceLogin?: boolean;
|
|
18
|
+
renderQrUrl?: (url: string) => void;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
stop(): Promise<void>;
|
|
21
|
+
private handleMessage;
|
|
22
|
+
private enqueueMessage;
|
|
23
|
+
private sendReply;
|
|
24
|
+
private sendTypingIndicator;
|
|
25
|
+
private getTypingTicket;
|
|
26
|
+
private previewMessage;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=bridge.d.ts.map
|