weacpx 0.2.1 → 0.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/dist/bridge/bridge-main.js +117 -13
- package/dist/cli.js +54 -1
- package/package.json +1 -1
|
@@ -276,6 +276,39 @@ function normalizeBridgeNonInteractivePermissions(value) {
|
|
|
276
276
|
// src/bridge/bridge-server.ts
|
|
277
277
|
init_prompt_output();
|
|
278
278
|
|
|
279
|
+
// src/bridge/bridge-request-scheduler.ts
|
|
280
|
+
class BridgeRequestScheduler {
|
|
281
|
+
sessions = new Map;
|
|
282
|
+
run(sessionName, lane, task) {
|
|
283
|
+
if (lane === "control") {
|
|
284
|
+
return Promise.resolve().then(task);
|
|
285
|
+
}
|
|
286
|
+
const state = this.sessions.get(sessionName) ?? this.createSessionState(sessionName);
|
|
287
|
+
state.pendingNormals += 1;
|
|
288
|
+
const result = state.tail.then(() => task());
|
|
289
|
+
state.tail = result.then(() => {
|
|
290
|
+
return;
|
|
291
|
+
}, () => {
|
|
292
|
+
return;
|
|
293
|
+
});
|
|
294
|
+
return result.finally(() => {
|
|
295
|
+
state.pendingNormals -= 1;
|
|
296
|
+
if (state.pendingNormals === 0 && this.sessions.get(sessionName) === state) {
|
|
297
|
+
this.sessions.delete(sessionName);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
createSessionState(sessionName) {
|
|
302
|
+
const state = {
|
|
303
|
+
pendingNormals: 0,
|
|
304
|
+
tail: Promise.resolve()
|
|
305
|
+
};
|
|
306
|
+
this.sessions.set(sessionName, state);
|
|
307
|
+
return state;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/bridge/bridge-server.ts
|
|
279
312
|
class BridgeInvalidRequestError extends Error {
|
|
280
313
|
}
|
|
281
314
|
var BRIDGE_METHODS = new Set([
|
|
@@ -288,9 +321,11 @@ var BRIDGE_METHODS = new Set([
|
|
|
288
321
|
"setMode",
|
|
289
322
|
"cancel"
|
|
290
323
|
]);
|
|
324
|
+
var SESSION_SCOPED_METHODS = new Set(["hasSession", "ensureSession", "prompt", "setMode", "cancel"]);
|
|
291
325
|
|
|
292
326
|
class BridgeServer {
|
|
293
327
|
runtime;
|
|
328
|
+
scheduler = new BridgeRequestScheduler;
|
|
294
329
|
constructor(runtime) {
|
|
295
330
|
this.runtime = runtime;
|
|
296
331
|
}
|
|
@@ -299,7 +334,7 @@ class BridgeServer {
|
|
|
299
334
|
try {
|
|
300
335
|
const request = parseBridgeRequest(line);
|
|
301
336
|
requestId = request.id;
|
|
302
|
-
const result = await this.
|
|
337
|
+
const result = await this.dispatchRequest(request.id, request.method, request.params, writeLine);
|
|
303
338
|
return `${JSON.stringify({
|
|
304
339
|
id: request.id,
|
|
305
340
|
ok: true,
|
|
@@ -326,6 +361,21 @@ class BridgeServer {
|
|
|
326
361
|
`;
|
|
327
362
|
}
|
|
328
363
|
}
|
|
364
|
+
async dispatchRequest(requestId, method, params, writeLine) {
|
|
365
|
+
if (!SESSION_SCOPED_METHODS.has(method)) {
|
|
366
|
+
return await this.dispatch(requestId, method, params, writeLine);
|
|
367
|
+
}
|
|
368
|
+
const sessionName = getSessionName(params);
|
|
369
|
+
if (!sessionName) {
|
|
370
|
+
return await this.dispatch(requestId, method, params, writeLine);
|
|
371
|
+
}
|
|
372
|
+
const sessionKey = getSessionScheduleKey(params);
|
|
373
|
+
if (!sessionKey) {
|
|
374
|
+
return await this.dispatch(requestId, method, params, writeLine);
|
|
375
|
+
}
|
|
376
|
+
const lane = method === "cancel" ? "control" : "normal";
|
|
377
|
+
return await this.scheduler.run(sessionKey, lane, () => this.dispatch(requestId, method, params, writeLine));
|
|
378
|
+
}
|
|
329
379
|
async dispatch(requestId, method, params, writeLine) {
|
|
330
380
|
switch (method) {
|
|
331
381
|
case "ping":
|
|
@@ -431,6 +481,24 @@ function parseBridgeRequest(line) {
|
|
|
431
481
|
params
|
|
432
482
|
};
|
|
433
483
|
}
|
|
484
|
+
function getSessionName(params) {
|
|
485
|
+
return asNonEmptyString(params.name);
|
|
486
|
+
}
|
|
487
|
+
function getSessionScheduleKey(params) {
|
|
488
|
+
const name = asNonEmptyString(params.name);
|
|
489
|
+
const cwd = asNonEmptyString(params.cwd);
|
|
490
|
+
const agentIdentity = asNonEmptyString(params.agentCommand) ?? asNonEmptyString(params.agent);
|
|
491
|
+
if (!name || !cwd || !agentIdentity) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
return JSON.stringify([agentIdentity, cwd, name]);
|
|
495
|
+
}
|
|
496
|
+
function asNonEmptyString(value) {
|
|
497
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
return value;
|
|
501
|
+
}
|
|
434
502
|
function requireString(params, key) {
|
|
435
503
|
const value = params[key];
|
|
436
504
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -733,17 +801,53 @@ async function tryRepairAcpxSessionIndex() {
|
|
|
733
801
|
}
|
|
734
802
|
|
|
735
803
|
// src/bridge/bridge-main.ts
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
});
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
804
|
+
async function processBridgeInput(options) {
|
|
805
|
+
const pendingWrites = new Set;
|
|
806
|
+
let firstError;
|
|
807
|
+
for await (const line of options.input) {
|
|
808
|
+
const pendingWrite = (async () => {
|
|
809
|
+
const response = await options.server.handleLine(line, (chunk) => {
|
|
810
|
+
options.write(chunk);
|
|
811
|
+
});
|
|
812
|
+
options.write(response);
|
|
813
|
+
})();
|
|
814
|
+
const observedPendingWrite = pendingWrite.catch((error) => {
|
|
815
|
+
if (firstError === undefined) {
|
|
816
|
+
firstError = error;
|
|
817
|
+
options.input.close();
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
pendingWrites.add(pendingWrite);
|
|
821
|
+
observedPendingWrite.finally(() => {
|
|
822
|
+
pendingWrites.delete(pendingWrite);
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
await Promise.allSettled(pendingWrites);
|
|
826
|
+
if (firstError !== undefined) {
|
|
827
|
+
throw firstError;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
async function runBridgeMain() {
|
|
831
|
+
const server = new BridgeServer(new BridgeRuntime(process.env.WEACPX_BRIDGE_ACPX_COMMAND ?? "acpx", undefined, undefined, {
|
|
832
|
+
permissionMode: normalizeBridgePermissionMode(process.env.WEACPX_BRIDGE_PERMISSION_MODE),
|
|
833
|
+
nonInteractivePermissions: normalizeBridgeNonInteractivePermissions(process.env.WEACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS)
|
|
834
|
+
}));
|
|
835
|
+
const input = createInterface({
|
|
836
|
+
input: process.stdin,
|
|
837
|
+
crlfDelay: Infinity
|
|
838
|
+
});
|
|
839
|
+
await processBridgeInput({
|
|
840
|
+
input,
|
|
841
|
+
server,
|
|
842
|
+
write: (chunk) => {
|
|
843
|
+
process.stdout.write(chunk);
|
|
844
|
+
}
|
|
747
845
|
});
|
|
748
|
-
process.stdout.write(response);
|
|
749
846
|
}
|
|
847
|
+
if (__require.main == __require.module) {
|
|
848
|
+
await runBridgeMain();
|
|
849
|
+
}
|
|
850
|
+
export {
|
|
851
|
+
runBridgeMain,
|
|
852
|
+
processBridgeInput
|
|
853
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -2423,6 +2423,47 @@ var init_session_guard = __esm(() => {
|
|
|
2423
2423
|
pauseUntilMap = new Map;
|
|
2424
2424
|
});
|
|
2425
2425
|
|
|
2426
|
+
// src/weixin/messaging/conversation-executor.ts
|
|
2427
|
+
function createConversationExecutor() {
|
|
2428
|
+
const states = new Map;
|
|
2429
|
+
const getState = (conversationId) => {
|
|
2430
|
+
const existing = states.get(conversationId);
|
|
2431
|
+
if (existing)
|
|
2432
|
+
return existing;
|
|
2433
|
+
const created = { activeControls: 0 };
|
|
2434
|
+
states.set(conversationId, created);
|
|
2435
|
+
return created;
|
|
2436
|
+
};
|
|
2437
|
+
const cleanupState = (conversationId, state) => {
|
|
2438
|
+
if (!state.normalTail && state.activeControls === 0) {
|
|
2439
|
+
states.delete(conversationId);
|
|
2440
|
+
}
|
|
2441
|
+
};
|
|
2442
|
+
return {
|
|
2443
|
+
run(conversationId, lane, task) {
|
|
2444
|
+
const state = getState(conversationId);
|
|
2445
|
+
if (lane === "control") {
|
|
2446
|
+
state.activeControls += 1;
|
|
2447
|
+
return Promise.resolve().then(task).finally(() => {
|
|
2448
|
+
state.activeControls -= 1;
|
|
2449
|
+
cleanupState(conversationId, state);
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
const previous = state.normalTail ?? Promise.resolve();
|
|
2453
|
+
const next = previous.catch(() => {
|
|
2454
|
+
return;
|
|
2455
|
+
}).then(task);
|
|
2456
|
+
state.normalTail = next;
|
|
2457
|
+
return next.finally(() => {
|
|
2458
|
+
if (state.normalTail === next) {
|
|
2459
|
+
state.normalTail = undefined;
|
|
2460
|
+
}
|
|
2461
|
+
cleanupState(conversationId, state);
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2426
2467
|
// src/weixin/api/types.ts
|
|
2427
2468
|
var UploadMediaType, MessageType, MessageItemType, MessageState, TypingStatus;
|
|
2428
2469
|
var init_types = __esm(() => {
|
|
@@ -3360,6 +3401,10 @@ function extractTextBody(itemList) {
|
|
|
3360
3401
|
}
|
|
3361
3402
|
return "";
|
|
3362
3403
|
}
|
|
3404
|
+
function getWeixinMessageTurnLane(full) {
|
|
3405
|
+
const textBody = extractTextBody(full.item_list).trim().toLowerCase();
|
|
3406
|
+
return textBody === "/cancel" || textBody === "/stop" ? "control" : "normal";
|
|
3407
|
+
}
|
|
3363
3408
|
function findMediaItem(itemList) {
|
|
3364
3409
|
if (!itemList?.length)
|
|
3365
3410
|
return;
|
|
@@ -3611,6 +3656,7 @@ async function monitorWeixinProvider(opts) {
|
|
|
3611
3656
|
log(`[weixin] no previous sync buf, starting fresh`);
|
|
3612
3657
|
}
|
|
3613
3658
|
const configManager = new WeixinConfigManager({ baseUrl, token }, log);
|
|
3659
|
+
const conversationExecutor = createConversationExecutor();
|
|
3614
3660
|
const seenMessageIds = new Set;
|
|
3615
3661
|
const messageIdOrder = [];
|
|
3616
3662
|
const DEDUP_WINDOW = 100;
|
|
@@ -3672,7 +3718,7 @@ async function monitorWeixinProvider(opts) {
|
|
|
3672
3718
|
aLog.info(`inbound: from=${full.from_user_id} types=${full.item_list?.map((i) => i.type).join(",") ?? "none"}`);
|
|
3673
3719
|
const fromUserId = full.from_user_id ?? "";
|
|
3674
3720
|
const cachedConfig = await configManager.getForUser(fromUserId, full.context_token);
|
|
3675
|
-
|
|
3721
|
+
conversationExecutor.run(full.from_user_id ?? "", getWeixinMessageTurnLane(full), () => handleWeixinMessageTurn(full, {
|
|
3676
3722
|
accountId,
|
|
3677
3723
|
agent,
|
|
3678
3724
|
baseUrl,
|
|
@@ -3681,6 +3727,8 @@ async function monitorWeixinProvider(opts) {
|
|
|
3681
3727
|
typingTicket: cachedConfig.typingTicket,
|
|
3682
3728
|
log,
|
|
3683
3729
|
errLog
|
|
3730
|
+
})).catch((err) => {
|
|
3731
|
+
errLog(`[weixin] message turn failed: ${String(err)}`);
|
|
3684
3732
|
});
|
|
3685
3733
|
}
|
|
3686
3734
|
} catch (err) {
|
|
@@ -4852,6 +4900,11 @@ async function handleCancel(context, chatKey) {
|
|
|
4852
4900
|
const result = await context.interaction.cancelTransportSession(session);
|
|
4853
4901
|
return { text: result.message || "cancelled" };
|
|
4854
4902
|
} catch (error) {
|
|
4903
|
+
const recovered = await context.recovery.tryRecoverMissingSession(session, error);
|
|
4904
|
+
if (recovered) {
|
|
4905
|
+
const result = await context.interaction.cancelTransportSession(recovered);
|
|
4906
|
+
return { text: result.message || "cancelled" };
|
|
4907
|
+
}
|
|
4855
4908
|
return context.recovery.renderTransportError(session, error);
|
|
4856
4909
|
}
|
|
4857
4910
|
}
|