weacpx 0.6.1 → 0.7.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.
@@ -8,6 +8,7 @@ export interface SessionHandlerContext extends CommandRouterContext {
8
8
  lifecycle: SessionLifecycleOps;
9
9
  interaction: SessionInteractionOps;
10
10
  recovery: SessionRenderRecoveryOps;
11
+ readonly activeTurns?: import("../../sessions/active-turn-registry.js").ActiveTurnRegistry;
11
12
  }
12
13
  export declare const sessionHelp: HelpTopicMetadata;
13
14
  export declare const nativeSessionHelp: HelpTopicMetadata;
@@ -22,14 +23,15 @@ export declare function handleSessionShortcut(context: SessionHandlerContext, ch
22
23
  workspace?: string;
23
24
  }, createNew: boolean): Promise<RouterResponse>;
24
25
  export declare function handleSessionAttach(context: SessionHandlerContext, chatKey: string, alias: string, agent: string, workspace: string, transportSession: string): Promise<RouterResponse>;
25
- export declare function handleSessionUse(context: SessionHandlerContext, chatKey: string, alias: string): Promise<RouterResponse>;
26
+ export declare function handleSessionUse(context: SessionHandlerContext, chatKey: string, input: string): Promise<RouterResponse>;
27
+ export declare function handleSessionUsePrevious(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
26
28
  export declare function handleModeShow(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
27
29
  export declare function handleModeSet(context: SessionHandlerContext, chatKey: string, modeId: string): Promise<RouterResponse>;
28
30
  export declare function handleReplyModeShow(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
29
31
  export declare function handleReplyModeSet(context: SessionHandlerContext, chatKey: string, replyMode: "stream" | "final" | "verbose"): Promise<RouterResponse>;
30
32
  export declare function handleReplyModeReset(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
31
33
  export declare function handleStatus(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
32
- export declare function handleCancel(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
34
+ export declare function handleCancel(context: SessionHandlerContext, chatKey: string, alias?: string): Promise<RouterResponse>;
33
35
  export declare function handleSessionReset(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
34
36
  export declare function handleSessionTail(context: SessionHandlerContext, chatKey: string, lines?: number): Promise<RouterResponse>;
35
37
  export declare function handleSessionRemove(context: SessionHandlerContext, chatKey: string, alias: string): Promise<RouterResponse>;
@@ -0,0 +1 @@
1
+ export declare function decorateUnread(label: string, hasUnread: boolean): string;
@@ -54,6 +54,7 @@ export type ParsedCommand = {
54
54
  kind: "status";
55
55
  } | {
56
56
  kind: "cancel";
57
+ alias?: string;
57
58
  } | {
58
59
  kind: "session.reset";
59
60
  } | {
@@ -118,6 +119,8 @@ export type ParsedCommand = {
118
119
  } | {
119
120
  kind: "session.use";
120
121
  alias: string;
122
+ } | {
123
+ kind: "session.use.previous";
121
124
  } | {
122
125
  kind: "session.new";
123
126
  alias: string;
@@ -7,3 +7,12 @@ export type { CommandHint } from "./commands/command-hints.js";
7
7
  export type { AppLogger } from "./logging/app-logger.js";
8
8
  export type { WeacpxPlugin } from "./plugins/types.js";
9
9
  export { WEACPX_PLUGIN_API_VERSION, WEACPX_PLUGIN_API_SUPPORTED_VERSIONS, WEACPX_PLUGIN_MIN_CORE_VERSION, } from "./plugins/types.js";
10
+ export { createConversationExecutor } from "./runtime/conversation-executor.js";
11
+ export type { ConversationExecutor, ConversationExecutorLane } from "./runtime/conversation-executor.js";
12
+ export { resolveTurnLane } from "./runtime/turn-lane.js";
13
+ export { createActiveTurnRegistry } from "./sessions/active-turn-registry.js";
14
+ export type { ActiveTurnRegistry } from "./sessions/active-turn-registry.js";
15
+ export { toDisplaySessionAlias } from "./channels/channel-scope.js";
16
+ export type { SessionService } from "./sessions/session-service.js";
17
+ export type { BackgroundResult } from "./state/types.js";
18
+ export type { ChatRequestMetadata } from "./weixin/agent/interface.js";
@@ -171,9 +171,166 @@ var init_types = __esm(() => {
171
171
  init_compatibility();
172
172
  });
173
173
 
174
+ // src/runtime/conversation-executor.ts
175
+ function createConversationExecutor() {
176
+ const states = new Map;
177
+ const getState = (conversationId) => {
178
+ const existing = states.get(conversationId);
179
+ if (existing)
180
+ return existing;
181
+ const created = { normalTails: new Map, activeControls: 0 };
182
+ states.set(conversationId, created);
183
+ return created;
184
+ };
185
+ const cleanupState = (conversationId, state) => {
186
+ if (state.normalTails.size === 0 && state.activeControls === 0) {
187
+ states.delete(conversationId);
188
+ }
189
+ };
190
+ return {
191
+ run(conversationId, lane, task, sessionKey) {
192
+ const state = getState(conversationId);
193
+ if (lane === "control") {
194
+ state.activeControls += 1;
195
+ return Promise.resolve().then(task).finally(() => {
196
+ state.activeControls -= 1;
197
+ cleanupState(conversationId, state);
198
+ });
199
+ }
200
+ const key = sessionKey ?? DEFAULT_SESSION_KEY;
201
+ const previous = state.normalTails.get(key) ?? Promise.resolve();
202
+ const next = previous.then(() => task(), () => task());
203
+ state.normalTails.set(key, next);
204
+ return next.finally(() => {
205
+ if (state.normalTails.get(key) === next) {
206
+ state.normalTails.delete(key);
207
+ }
208
+ cleanupState(conversationId, state);
209
+ });
210
+ }
211
+ };
212
+ }
213
+ var DEFAULT_SESSION_KEY = "__chat__";
214
+
215
+ // src/sessions/active-turn-registry.ts
216
+ function createActiveTurnRegistry() {
217
+ const byChat = new Map;
218
+ return {
219
+ markActive(chatKey, alias) {
220
+ const set = byChat.get(chatKey) ?? new Set;
221
+ set.add(alias);
222
+ byChat.set(chatKey, set);
223
+ },
224
+ markInactive(chatKey, alias) {
225
+ const set = byChat.get(chatKey);
226
+ if (!set)
227
+ return;
228
+ set.delete(alias);
229
+ if (set.size === 0)
230
+ byChat.delete(chatKey);
231
+ },
232
+ isActive(chatKey, alias) {
233
+ return byChat.get(chatKey)?.has(alias) ?? false;
234
+ }
235
+ };
236
+ }
237
+
238
+ // src/channels/channel-scope.ts
239
+ function registerKnownChannelId(channelId) {
240
+ const normalized = channelId.trim();
241
+ if (!normalized || normalized.includes(":")) {
242
+ throw new Error("channel id must be non-empty and must not contain ':'");
243
+ }
244
+ KNOWN_CHANNEL_IDS.add(normalized);
245
+ }
246
+ function listKnownChannelIds() {
247
+ return Array.from(KNOWN_CHANNEL_IDS);
248
+ }
249
+ function getChannelIdFromChatKey(chatKey) {
250
+ const first = chatKey.split(":", 1)[0];
251
+ return first && KNOWN_CHANNEL_IDS.has(first) ? first : "weixin";
252
+ }
253
+ function toInternalSessionAlias(channelId, displayAlias) {
254
+ const normalized = displayAlias.trim();
255
+ if (normalized.length === 0) {
256
+ throw new Error("display session alias must be non-empty");
257
+ }
258
+ if (normalized.startsWith(`${channelId}:`)) {
259
+ return normalized;
260
+ }
261
+ return `${channelId}:${normalized}`;
262
+ }
263
+ function toDisplaySessionAlias(internalAlias) {
264
+ const [first, ...rest] = internalAlias.split(":");
265
+ if (first && KNOWN_CHANNEL_IDS.has(first) && rest.length > 0) {
266
+ return rest.join(":");
267
+ }
268
+ return internalAlias;
269
+ }
270
+ function isSessionAliasVisibleInChannel(alias, channelId) {
271
+ const [first] = alias.split(":", 1);
272
+ if (first && KNOWN_CHANNEL_IDS.has(first)) {
273
+ return first === channelId;
274
+ }
275
+ return channelId === "weixin";
276
+ }
277
+ function resolveSessionAliasForInput(channelId, displayAlias, existingAliases) {
278
+ const normalized = displayAlias.trim();
279
+ if (normalized.length === 0) {
280
+ throw new Error("display session alias must be non-empty");
281
+ }
282
+ if (normalized.startsWith(`${channelId}:`)) {
283
+ return normalized;
284
+ }
285
+ const scopedAlias = toInternalSessionAlias(channelId, normalized);
286
+ for (const alias of existingAliases) {
287
+ if (alias === scopedAlias)
288
+ return scopedAlias;
289
+ }
290
+ if (channelId === "weixin") {
291
+ for (const alias of existingAliases) {
292
+ if (alias === normalized)
293
+ return alias;
294
+ }
295
+ }
296
+ return scopedAlias;
297
+ }
298
+ function scopeDisplayAliasToInternal(channelId, displayAlias) {
299
+ const normalized = displayAlias.trim();
300
+ if (normalized.length === 0) {
301
+ throw new Error("display session alias must be non-empty");
302
+ }
303
+ return channelId === "weixin" ? normalized : toInternalSessionAlias(channelId, normalized);
304
+ }
305
+ function buildDefaultTransportSession(channelId, displayAlias) {
306
+ const normalized = displayAlias.trim();
307
+ if (normalized.length === 0) {
308
+ throw new Error("display session alias must be non-empty");
309
+ }
310
+ return channelId === "weixin" ? normalized : toInternalSessionAlias(channelId, normalized);
311
+ }
312
+ var KNOWN_CHANNEL_IDS;
313
+ var init_channel_scope = __esm(() => {
314
+ KNOWN_CHANNEL_IDS = new Set(["weixin"]);
315
+ });
316
+
174
317
  // src/plugin-api.ts
175
318
  init_types();
319
+
320
+ // src/runtime/turn-lane.ts
321
+ var CONTROL_COMMANDS = new Set(["/use", "/ss", "/cancel", "/stop"]);
322
+ function resolveTurnLane(text) {
323
+ const command = text.trim().toLowerCase().split(/\s+/)[0] ?? "";
324
+ return CONTROL_COMMANDS.has(command) ? "control" : "normal";
325
+ }
326
+
327
+ // src/plugin-api.ts
328
+ init_channel_scope();
176
329
  export {
330
+ toDisplaySessionAlias,
331
+ resolveTurnLane,
332
+ createConversationExecutor,
333
+ createActiveTurnRegistry,
177
334
  WEACPX_PLUGIN_MIN_CORE_VERSION,
178
335
  WEACPX_PLUGIN_API_VERSION,
179
336
  WEACPX_PLUGIN_API_SUPPORTED_VERSIONS
@@ -1,7 +1,7 @@
1
1
  export type ConversationExecutorLane = "normal" | "control";
2
2
  type ConversationTask<T> = () => Promise<T>;
3
3
  export type ConversationExecutor = {
4
- run<T>(conversationId: string, lane: ConversationExecutorLane, task: ConversationTask<T>): Promise<T>;
4
+ run<T>(conversationId: string, lane: ConversationExecutorLane, task: ConversationTask<T>, sessionKey?: string): Promise<T>;
5
5
  };
6
6
  export declare function createConversationExecutor(): ConversationExecutor;
7
7
  export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The per-user state directory name (`<home>/.weacpx/`). This is the SINGLE
3
+ * source of truth for that name; every config / state / runtime / plugin path
4
+ * is built on top of it.
5
+ *
6
+ * The weacpx→xacpx rename (0.8.0) changes the name HERE — and adds any
7
+ * legacy-directory fallback inside {@link coreHomeDir} (e.g. prefer
8
+ * `~/.xacpx`, fall back to an existing `~/.weacpx`) — so the whole tree of
9
+ * derived paths follows from one place rather than ~11 scattered literals.
10
+ */
11
+ export declare const CORE_HOME_DIR_NAME = ".weacpx";
12
+ /**
13
+ * The core state root for a given user home: `<home>/.weacpx`.
14
+ *
15
+ * Pass the home directory the caller already resolved — this helper imposes no
16
+ * home-resolution policy of its own, so each caller's existing semantics are
17
+ * preserved (this is a pure refactor of the directory-name literal). It is the
18
+ * one function the 0.8.0 rename hooks into.
19
+ */
20
+ export declare function coreHomeDir(home: string): string;
21
+ /**
22
+ * Display form for user-facing hints (e.g. "请查看日志:~/.weacpx/runtime/...").
23
+ * Keeps printed paths single-sourced with the real directory name. Always uses
24
+ * "/" separators since it is for display, not filesystem access.
25
+ */
26
+ export declare function coreHomeDisplayPath(...segments: string[]): string;
@@ -0,0 +1,2 @@
1
+ import type { ConversationExecutorLane } from "./conversation-executor.js";
2
+ export declare function resolveTurnLane(text: string): ConversationExecutorLane;
@@ -0,0 +1,6 @@
1
+ export interface ActiveTurnRegistry {
2
+ markActive(chatKey: string, alias: string): void;
3
+ markInactive(chatKey: string, alias: string): void;
4
+ isActive(chatKey: string, alias: string): boolean;
5
+ }
6
+ export declare function createActiveTurnRegistry(): ActiveTurnRegistry;
@@ -1,7 +1,7 @@
1
1
  import type { AppConfig } from "../config/types";
2
2
  import { AsyncMutex } from "../orchestration/async-mutex";
3
3
  import type { StateStore } from "../state/state-store";
4
- import type { AppState } from "../state/types";
4
+ import type { AppState, BackgroundResult } from "../state/types";
5
5
  import type { AgentSession, ResolvedSession } from "../transport/types";
6
6
  interface SessionListItem {
7
7
  alias: string;
@@ -10,6 +10,25 @@ interface SessionListItem {
10
10
  workspace: string;
11
11
  isCurrent: boolean;
12
12
  }
13
+ export interface SessionSwitchResult {
14
+ alias: string;
15
+ agent: string;
16
+ workspace: string;
17
+ previousAlias?: string;
18
+ }
19
+ export type FuzzyAliasResult = {
20
+ kind: "match";
21
+ alias: string;
22
+ } | {
23
+ kind: "ambiguous";
24
+ candidates: Array<{
25
+ alias: string;
26
+ agent: string;
27
+ workspace: string;
28
+ }>;
29
+ } | {
30
+ kind: "none";
31
+ };
13
32
  interface NativeSessionAttachmentInput {
14
33
  alias: string;
15
34
  agent: string;
@@ -57,9 +76,24 @@ export declare class SessionService {
57
76
  attachSession(alias: string, agent: string, workspace: string, transportSession: string, transportAgentCommand?: string): Promise<ResolvedSession>;
58
77
  attachNativeSession(input: NativeSessionAttachmentInput): Promise<ResolvedSession>;
59
78
  getSession(alias: string): Promise<ResolvedSession | null>;
79
+ /**
80
+ * Synchronously resolve a session by its internal alias (as stored in state).
81
+ * Returns null if the alias is unknown or if the referenced agent/workspace is
82
+ * no longer registered (i.e. toResolvedSession would throw).
83
+ *
84
+ * Used by handlePrompt to honour a `boundSessionAlias` captured at dispatch
85
+ * time without requiring an async state mutation.
86
+ */
87
+ getResolvedSessionByInternalAlias(alias: string): ResolvedSession | null;
88
+ peekCurrentSessionAlias(chatKey: string): string | undefined;
60
89
  getPreferredSessionForTransport(transportSession: string): Promise<ResolvedSession | null>;
61
90
  findAttachedNativeSession(chatKey: string, agent: string, agentSessionId: string): Promise<ResolvedSession | null>;
62
- useSession(chatKey: string, alias: string): Promise<void>;
91
+ useSession(chatKey: string, alias: string): Promise<SessionSwitchResult>;
92
+ usePreviousSession(chatKey: string): Promise<SessionSwitchResult | null>;
93
+ setBackgroundResult(chatKey: string, alias: string, result: BackgroundResult): Promise<void>;
94
+ takeBackgroundResult(chatKey: string, alias: string): Promise<BackgroundResult | null>;
95
+ listBackgroundResultAliases(chatKey: string): string[];
96
+ resolveFuzzyAlias(chatKey: string, fragment: string): FuzzyAliasResult;
63
97
  resolveAliasForChat(chatKey: string, displayAlias: string): Promise<string>;
64
98
  buildDefaultTransportSessionForChat(chatKey: string, displayAlias: string): string;
65
99
  listInternalAliases(): string[];
@@ -73,6 +107,7 @@ export declare class SessionService {
73
107
  }>;
74
108
  cacheNativeSessionList(chatKey: string, input: NativeSessionListInput): Promise<void>;
75
109
  getNativeSessionList(chatKey: string, ttlMs?: number): Promise<NativeSessionListResult | null>;
110
+ private deleteNativeSessionListIfCurrent;
76
111
  private toResolvedSession;
77
112
  setSessionTransportAgentCommand(alias: string, transportAgentCommand: string | undefined): Promise<void>;
78
113
  private mutate;
@@ -31,8 +31,15 @@ export interface LogicalSession {
31
31
  created_at: string;
32
32
  last_used_at: string;
33
33
  }
34
+ export interface BackgroundResult {
35
+ text: string;
36
+ status: "done" | "error";
37
+ finished_at: string;
38
+ }
34
39
  export interface ChatContextState {
35
40
  current_session: string;
41
+ previous_session?: string;
42
+ background_results?: Record<string, BackgroundResult>;
36
43
  }
37
44
  export interface AppState {
38
45
  sessions: Record<string, LogicalSession>;
@@ -66,6 +66,7 @@ export interface ChatRequestMetadata {
66
66
  scheduledSessionAlias?: string;
67
67
  /** Transient session descriptor for temp-mode scheduled prompts (no persisted alias). */
68
68
  scheduledSessionDescriptor?: ScheduledSessionDescriptor;
69
+ boundSessionAlias?: string;
69
70
  }
70
71
  export interface ChatResponse {
71
72
  /**
@@ -1,7 +1,15 @@
1
+ import { getConfig } from "./api.js";
1
2
  /** Subset of getConfig fields that we actually need; add new fields here as needed. */
2
3
  export interface CachedConfig {
3
4
  typingTicket: string;
4
5
  }
6
+ type GetConfigFn = typeof getConfig;
7
+ export interface WeixinConfigManagerOptions {
8
+ maxEntries?: number;
9
+ entryTtlMs?: number;
10
+ now?: () => number;
11
+ getConfig?: GetConfigFn;
12
+ }
5
13
  /**
6
14
  * Per-user getConfig cache with periodic random refresh (within 24h) and
7
15
  * exponential-backoff retry (up to 1h) on failure.
@@ -10,9 +18,18 @@ export declare class WeixinConfigManager {
10
18
  private apiOpts;
11
19
  private log;
12
20
  private cache;
21
+ private readonly maxEntries;
22
+ private readonly entryTtlMs;
23
+ private readonly now;
24
+ private readonly fetchConfig;
13
25
  constructor(apiOpts: {
14
26
  baseUrl: string;
15
27
  token?: string;
16
- }, log: (msg: string) => void);
28
+ }, log: (msg: string) => void, options?: WeixinConfigManagerOptions);
17
29
  getForUser(userId: string, contextToken?: string): Promise<CachedConfig>;
30
+ cacheSizeForTests(): number;
31
+ hasCachedUserForTests(userId: string): boolean;
32
+ private prune;
33
+ private enforceMaxEntries;
18
34
  }
35
+ export {};
@@ -2,6 +2,7 @@ import type { Agent } from "./agent/interface.js";
2
2
  import type { PendingFinalChunk } from "./messaging/quota-manager.js";
3
3
  import type { RuntimeMediaStore } from "../channels/media-store.js";
4
4
  import type { PerfTracer } from "../perf/perf-tracer.js";
5
+ import type { ActiveTurnRegistry } from "../sessions/active-turn-registry.js";
5
6
  export type LoginOptions = {
6
7
  /** Override the API base URL. */
7
8
  baseUrl?: string;
@@ -31,6 +32,16 @@ export type StartOptions = {
31
32
  dropPendingFinal?: (chatKey: string) => void;
32
33
  mediaStore?: RuntimeMediaStore;
33
34
  perfTracer?: PerfTracer;
35
+ /** Read the chat's current session synchronously for dispatch-time binding. */
36
+ peekCurrentSessionAlias?: (chatKey: string) => string | undefined;
37
+ /** Persist a background turn's final result for later replay. */
38
+ setBackgroundResult?: (chatKey: string, alias: string, result: {
39
+ text: string;
40
+ status: "done" | "error";
41
+ finished_at: string;
42
+ }) => Promise<void>;
43
+ /** Shared in-flight turn registry for dispatch-time foreground tracking. */
44
+ activeTurns?: ActiveTurnRegistry;
34
45
  };
35
46
  /**
36
47
  * Interactive QR-code login. Prints the QR code to the terminal and waits
@@ -0,0 +1,2 @@
1
+ export declare function buildBackgroundCompletionNotice(internalAlias: string, status: "done" | "error"): string;
2
+ export declare function shouldSendBackgroundNotice(reserve: (() => boolean) | undefined): boolean;
@@ -0,0 +1,3 @@
1
+ export declare function shouldDeliverSegment(isForeground: (() => boolean) | undefined): boolean;
2
+ export type FinalDisposition = "send" | "store" | "drop";
3
+ export declare function resolveFinalDisposition(isForeground: boolean, canStore: boolean): FinalDisposition;
@@ -27,6 +27,10 @@ export type HandleWeixinMessageTurnDeps = {
27
27
  downloadMediaFromItemFn?: typeof downloadMediaFromItem;
28
28
  allowedMediaRoots?: string[];
29
29
  perfTracer?: PerfTracer;
30
+ isForeground?: () => boolean;
31
+ boundSessionAlias?: string;
32
+ onBackgroundFinal?: (alias: string, text: string, status: "done" | "error") => Promise<void>;
30
33
  };
31
34
  export declare function getWeixinMessageTurnLane(full: WeixinMessage): "normal" | "control";
35
+ export declare function buildWeixinChatKey(accountId: string, userId: string): string;
32
36
  export declare function handleWeixinMessageTurn(full: WeixinMessage, deps: HandleWeixinMessageTurnDeps): Promise<void>;
@@ -1,5 +1,11 @@
1
1
  import type { ChannelMediaKind } from "../../channels/media-types.js";
2
2
  import type { WeixinMessage, MessageItem } from "../api/types.js";
3
+ interface ContextTokenRetentionOptions {
4
+ maxTokensPerAccount?: number;
5
+ tokenTtlMs?: number;
6
+ now?: () => number;
7
+ }
8
+ export declare function configureContextTokenRetentionForTests(options?: ContextTokenRetentionOptions): void;
3
9
  /**
4
10
  * Restore the per-account context-token cache from disk into memory.
5
11
  * Called at bot startup (after login). Missing/unreadable files are tolerated
@@ -78,3 +84,4 @@ export interface WeixinInboundMediaDescriptor {
78
84
  fileName?: string;
79
85
  }
80
86
  export declare function extractWeixinMediaDescriptors(itemList?: MessageItem[]): WeixinInboundMediaDescriptor[];
87
+ export {};
@@ -24,11 +24,21 @@ export interface QuotaObserver {
24
24
  onFinalReserved?(chatKey: string, snapshot: QuotaSnapshot): void;
25
25
  onFinalRejected?(chatKey: string, snapshot: QuotaSnapshot): void;
26
26
  }
27
+ export interface QuotaManagerOptions {
28
+ maxStates?: number;
29
+ stateTtlMs?: number;
30
+ maxPendingFinalChunksPerChat?: number;
31
+ now?: () => number;
32
+ }
27
33
  export declare class QuotaManager {
28
34
  private readonly states;
29
35
  private readonly observer;
30
36
  private readonly normalizeKey;
31
- constructor(observer?: QuotaObserver, normalizeKey?: (key: string) => string);
37
+ private readonly maxStates;
38
+ private readonly stateTtlMs;
39
+ private readonly maxPendingFinalChunksPerChat;
40
+ private readonly now;
41
+ constructor(observer?: QuotaObserver, normalizeKey?: (key: string) => string, options?: QuotaManagerOptions);
32
42
  onInbound(chatKey: string): void;
33
43
  reserveMidSegment(chatKey: string): boolean;
34
44
  reserveFinal(chatKey: string): boolean;
@@ -41,4 +51,8 @@ export declare class QuotaManager {
41
51
  clearPendingFinal(chatKey: string): void;
42
52
  snapshot(chatKey: string): QuotaSnapshot;
43
53
  private getOrCreate;
54
+ private prune;
55
+ private enforceMaxStates;
56
+ private trimPendingFinalChunks;
57
+ private deleteIfEmpty;
44
58
  }
@@ -1,5 +1,6 @@
1
1
  import type { Agent } from "../agent/interface.js";
2
2
  import type { PendingFinalChunk } from "../messaging/quota-manager.js";
3
+ import type { ActiveTurnRegistry } from "../../sessions/active-turn-registry.js";
3
4
  import type { RuntimeMediaStore } from "../../channels/media-store.js";
4
5
  import type { PerfTracer } from "../../perf/perf-tracer.js";
5
6
  export type MonitorWeixinOpts = {
@@ -22,6 +23,13 @@ export type MonitorWeixinOpts = {
22
23
  mediaStore?: RuntimeMediaStore;
23
24
  allowedMediaRoots?: string[];
24
25
  perfTracer?: PerfTracer;
26
+ peekCurrentSessionAlias?: (chatKey: string) => string | undefined;
27
+ setBackgroundResult?: (chatKey: string, alias: string, result: {
28
+ text: string;
29
+ status: "done" | "error";
30
+ finished_at: string;
31
+ }) => Promise<void>;
32
+ activeTurns?: ActiveTurnRegistry;
25
33
  };
26
34
  /**
27
35
  * Long-poll loop: getUpdates → process message → call agent → send reply.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weacpx",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "使用微信 ClawBot 随时随地通过 `acpx` 控制 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",