weacpx 0.6.0 → 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.
Files changed (63) hide show
  1. package/dist/bridge/bridge-main.js +66 -24
  2. package/dist/channels/types.d.ts +14 -0
  3. package/dist/cli.js +1268 -407
  4. package/dist/commands/command-hints.d.ts +11 -0
  5. package/dist/commands/command-list.d.ts +3 -0
  6. package/dist/commands/config-clone.d.ts +2 -0
  7. package/dist/commands/handlers/agent-handler.d.ts +6 -0
  8. package/dist/commands/handlers/config-handler.d.ts +5 -0
  9. package/dist/commands/handlers/later-handler.d.ts +21 -0
  10. package/dist/commands/handlers/orchestration-handler.d.ts +16 -0
  11. package/dist/commands/handlers/permission-handler.d.ts +9 -0
  12. package/dist/commands/handlers/session-handler.d.ts +39 -0
  13. package/dist/commands/handlers/session-list-marker.d.ts +1 -0
  14. package/dist/commands/handlers/workspace-handler.d.ts +8 -0
  15. package/dist/commands/help/help-registry.d.ts +4 -0
  16. package/dist/commands/help/help-types.d.ts +12 -0
  17. package/dist/commands/parse-command.d.ts +178 -0
  18. package/dist/commands/router-types.d.ts +144 -0
  19. package/dist/commands/workspace-name.d.ts +4 -0
  20. package/dist/commands/workspace-path.d.ts +4 -0
  21. package/dist/config/agent-templates.d.ts +4 -0
  22. package/dist/config/config-store.d.ts +13 -0
  23. package/dist/config/load-config.d.ts +10 -0
  24. package/dist/config/resolve-agent-command.d.ts +2 -0
  25. package/dist/formatting/render-text.d.ts +23 -0
  26. package/dist/orchestration/async-mutex.d.ts +4 -0
  27. package/dist/orchestration/build-coordinator-prompt.d.ts +66 -0
  28. package/dist/orchestration/orchestration-service.d.ts +471 -0
  29. package/dist/orchestration/progress-line-parser.d.ts +19 -0
  30. package/dist/orchestration/render-delegate-group-result.d.ts +6 -0
  31. package/dist/orchestration/render-delegate-question-package.d.ts +21 -0
  32. package/dist/orchestration/render-delegate-result.d.ts +2 -0
  33. package/dist/orchestration/task-watch-timeouts.d.ts +5 -0
  34. package/dist/plugin-api.d.ts +10 -0
  35. package/dist/plugin-api.js +157 -0
  36. package/dist/{weixin/messaging → runtime}/conversation-executor.d.ts +1 -1
  37. package/dist/runtime/core-home.d.ts +26 -0
  38. package/dist/runtime/turn-lane.d.ts +2 -0
  39. package/dist/scheduled/parse-later-time.d.ts +11 -0
  40. package/dist/scheduled/scheduled-render.d.ts +7 -0
  41. package/dist/scheduled/scheduled-service.d.ts +41 -0
  42. package/dist/scheduled/scheduled-types.d.ts +29 -0
  43. package/dist/sessions/active-turn-registry.d.ts +6 -0
  44. package/dist/sessions/session-service.d.ts +118 -0
  45. package/dist/state/state-store.d.ts +8 -0
  46. package/dist/state/types.d.ts +51 -0
  47. package/dist/transport/tool-event-mode.d.ts +14 -0
  48. package/dist/transport/types.d.ts +129 -0
  49. package/dist/util/path.d.ts +4 -0
  50. package/dist/util/sanitize.d.ts +10 -0
  51. package/dist/util/text.d.ts +3 -0
  52. package/dist/version.d.ts +2 -0
  53. package/dist/weixin/agent/interface.d.ts +1 -0
  54. package/dist/weixin/api/config-cache.d.ts +18 -1
  55. package/dist/weixin/auth/accounts.d.ts +0 -1
  56. package/dist/weixin/bot.d.ts +11 -0
  57. package/dist/weixin/messaging/completion-notice.d.ts +2 -0
  58. package/dist/weixin/messaging/foreground-gate.d.ts +3 -0
  59. package/dist/weixin/messaging/handle-weixin-message-turn.d.ts +4 -0
  60. package/dist/weixin/messaging/inbound.d.ts +7 -0
  61. package/dist/weixin/messaging/quota-manager.d.ts +15 -1
  62. package/dist/weixin/monitor/monitor.d.ts +8 -0
  63. package/package.json +1 -1
@@ -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,11 @@
1
+ export type LaterTimeParseErrorCode = "missing_time" | "unrecognized_time" | "missing_message" | "past_today_time" | "too_soon" | "out_of_range";
2
+ export type LaterTimeParseResult = {
3
+ ok: true;
4
+ executeAt: Date;
5
+ messageStartIndex: number;
6
+ } | {
7
+ ok: false;
8
+ code: LaterTimeParseErrorCode;
9
+ value?: string;
10
+ };
11
+ export declare function parseLaterTime(tokens: string[], now?: Date): LaterTimeParseResult;
@@ -0,0 +1,7 @@
1
+ import type { ScheduledTaskRecord } from "./scheduled-types";
2
+ export declare function renderLaterHelp(): string;
3
+ export declare function renderLaterUnsupportedChannel(): string;
4
+ export declare function renderTaskCreated(task: ScheduledTaskRecord, displaySession: string): string;
5
+ export declare function renderLaterList(tasks: ScheduledTaskRecord[], displaySession: (internalAlias: string) => string): string;
6
+ export declare function preview(text: string): string;
7
+ export declare function formatLocalDateTime(date: Date): string;
@@ -0,0 +1,41 @@
1
+ import { AsyncMutex } from "../orchestration/async-mutex";
2
+ import type { StateStore } from "../state/state-store";
3
+ import type { AppState } from "../state/types";
4
+ import type { ScheduledSessionMode, ScheduledTaskRecord } from "./scheduled-types";
5
+ export interface CreateScheduledTaskInput {
6
+ chatKey: string;
7
+ sessionAlias: string;
8
+ executeAt: Date;
9
+ message: string;
10
+ sessionMode?: ScheduledSessionMode;
11
+ agent?: string;
12
+ workspace?: string;
13
+ accountId?: string;
14
+ replyContextToken?: string;
15
+ sourceLabel?: string;
16
+ }
17
+ export interface ScheduledTaskServiceOptions {
18
+ now?: () => Date;
19
+ generateId?: () => string;
20
+ stateMutex?: AsyncMutex;
21
+ }
22
+ export declare class ScheduledTaskService {
23
+ private readonly state;
24
+ private readonly stateStore;
25
+ private readonly now;
26
+ private readonly generateId;
27
+ private readonly stateMutex;
28
+ private readonly claimedInThisSession;
29
+ constructor(state: AppState, stateStore: Pick<StateStore, "save">, options?: ScheduledTaskServiceOptions);
30
+ createTask(input: CreateScheduledTaskInput): Promise<ScheduledTaskRecord>;
31
+ listPending(): ScheduledTaskRecord[];
32
+ cancelPending(inputId: string): Promise<boolean>;
33
+ markStartupMissed(): Promise<void>;
34
+ claimDueTasks(): Promise<ScheduledTaskRecord[]>;
35
+ markExecuted(id: string): Promise<void>;
36
+ markFailed(id: string, error: unknown): Promise<void>;
37
+ private nextId;
38
+ private mutate;
39
+ private save;
40
+ }
41
+ export declare function normalizeId(input: string): string;
@@ -0,0 +1,29 @@
1
+ export declare const LATER_MIN_DELAY_MS = 10000;
2
+ export declare const LATER_MAX_DELAY_MS: number;
3
+ export declare const LATER_MESSAGE_PREVIEW_CHARS = 120;
4
+ export type ScheduledTaskStatus = "pending" | "triggering" | "executed" | "cancelled" | "missed" | "failed";
5
+ export type ScheduledSessionMode = "temp" | "bound";
6
+ export interface ScheduledTaskRecord {
7
+ id: string;
8
+ chat_key: string;
9
+ session_alias: string;
10
+ /** Absent ⇒ "bound" (legacy tasks created before temp mode existed). */
11
+ session_mode?: ScheduledSessionMode;
12
+ /** Agent snapshot at creation; only set for "temp" tasks. */
13
+ agent?: string;
14
+ /** Workspace snapshot at creation; only set for "temp" tasks. */
15
+ workspace?: string;
16
+ execute_at: string;
17
+ message: string;
18
+ status: ScheduledTaskStatus;
19
+ created_at: string;
20
+ account_id?: string;
21
+ reply_context_token?: string;
22
+ source_label?: string;
23
+ triggered_at?: string;
24
+ executed_at?: string;
25
+ cancelled_at?: string;
26
+ missed_at?: string;
27
+ failed_at?: string;
28
+ last_error?: string;
29
+ }
@@ -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;
@@ -0,0 +1,118 @@
1
+ import type { AppConfig } from "../config/types";
2
+ import { AsyncMutex } from "../orchestration/async-mutex";
3
+ import type { StateStore } from "../state/state-store";
4
+ import type { AppState, BackgroundResult } from "../state/types";
5
+ import type { AgentSession, ResolvedSession } from "../transport/types";
6
+ interface SessionListItem {
7
+ alias: string;
8
+ internalAlias: string;
9
+ agent: string;
10
+ workspace: string;
11
+ isCurrent: boolean;
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
+ };
32
+ interface NativeSessionAttachmentInput {
33
+ alias: string;
34
+ agent: string;
35
+ workspace: string;
36
+ transportSession: string;
37
+ transportAgentCommand?: string;
38
+ agentSessionId: string;
39
+ title?: string | null;
40
+ updatedAt?: string;
41
+ }
42
+ interface NativeSessionListInput {
43
+ agent: string;
44
+ workspace?: string;
45
+ cwd: string;
46
+ sessions: AgentSession[];
47
+ nextCursor?: string | null;
48
+ }
49
+ interface NativeSessionListResult {
50
+ agent: string;
51
+ workspace?: string;
52
+ cwd: string;
53
+ sessions: AgentSession[];
54
+ nextCursor?: string | null;
55
+ }
56
+ interface SessionServiceOptions {
57
+ stateMutex?: AsyncMutex;
58
+ now?: () => number;
59
+ }
60
+ export declare class SessionService {
61
+ private readonly config;
62
+ private readonly stateStore;
63
+ private readonly state;
64
+ private readonly stateMutex;
65
+ private readonly now;
66
+ constructor(config: AppConfig, stateStore: Pick<StateStore, "save">, state: AppState, options?: SessionServiceOptions);
67
+ createSession(alias: string, agent: string, workspace: string): Promise<ResolvedSession>;
68
+ /**
69
+ * All currently-known logical sessions resolved to transport sessions, deduped by
70
+ * transport session. Sessions whose agent or workspace is no longer registered are
71
+ * skipped (toResolvedSession would throw). Used by shutdown cleanup to reap warm
72
+ * acpx queue owners; never throws.
73
+ */
74
+ listAllResolvedSessions(): ResolvedSession[];
75
+ resolveSession(alias: string, agent: string, workspace: string, transportSession: string): ResolvedSession;
76
+ attachSession(alias: string, agent: string, workspace: string, transportSession: string, transportAgentCommand?: string): Promise<ResolvedSession>;
77
+ attachNativeSession(input: NativeSessionAttachmentInput): Promise<ResolvedSession>;
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;
89
+ getPreferredSessionForTransport(transportSession: string): Promise<ResolvedSession | null>;
90
+ findAttachedNativeSession(chatKey: string, agent: string, agentSessionId: string): Promise<ResolvedSession | null>;
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;
97
+ resolveAliasForChat(chatKey: string, displayAlias: string): Promise<string>;
98
+ buildDefaultTransportSessionForChat(chatKey: string, displayAlias: string): string;
99
+ listInternalAliases(): string[];
100
+ setCurrentSessionMode(chatKey: string, modeId: string | undefined): Promise<void>;
101
+ setCurrentSessionReplyMode(chatKey: string, replyMode: "stream" | "final" | "verbose" | undefined): Promise<void>;
102
+ getCurrentSession(chatKey: string): Promise<ResolvedSession | null>;
103
+ listSessions(chatKey: string): Promise<SessionListItem[]>;
104
+ countAliasesSharingTransport(transportSession: string, excludeAlias?: string): number;
105
+ removeSession(alias: string): Promise<{
106
+ wasActive: boolean;
107
+ }>;
108
+ cacheNativeSessionList(chatKey: string, input: NativeSessionListInput): Promise<void>;
109
+ getNativeSessionList(chatKey: string, ttlMs?: number): Promise<NativeSessionListResult | null>;
110
+ private deleteNativeSessionListIfCurrent;
111
+ private toResolvedSession;
112
+ setSessionTransportAgentCommand(alias: string, transportAgentCommand: string | undefined): Promise<void>;
113
+ private mutate;
114
+ private persist;
115
+ private createLogicalSession;
116
+ private validateSession;
117
+ }
118
+ export {};
@@ -0,0 +1,8 @@
1
+ import { type AppState } from "./types";
2
+ export declare function parseState(raw: unknown, path: string): AppState;
3
+ export declare class StateStore {
4
+ private readonly path;
5
+ constructor(path: string);
6
+ load(): Promise<AppState>;
7
+ save(state: AppState): Promise<void>;
8
+ }
@@ -0,0 +1,51 @@
1
+ import { type OrchestrationState } from "../orchestration/orchestration-types";
2
+ import type { ScheduledTaskRecord } from "../scheduled/scheduled-types";
3
+ export type LogicalSessionSource = "weacpx" | "agent-side";
4
+ export interface NativeSessionCacheEntry {
5
+ session_id: string;
6
+ cwd?: string;
7
+ title?: string | null;
8
+ updated_at?: string;
9
+ }
10
+ export interface NativeSessionListCacheRecord {
11
+ created_at: string;
12
+ agent: string;
13
+ workspace?: string;
14
+ cwd: string;
15
+ sessions: NativeSessionCacheEntry[];
16
+ next_cursor?: string | null;
17
+ }
18
+ export interface LogicalSession {
19
+ alias: string;
20
+ agent: string;
21
+ workspace: string;
22
+ transport_session: string;
23
+ source?: LogicalSessionSource;
24
+ agent_session_id?: string;
25
+ agent_session_title?: string;
26
+ agent_session_updated_at?: string;
27
+ attached_at?: string;
28
+ transport_agent_command?: string;
29
+ mode_id?: string;
30
+ reply_mode?: "stream" | "final" | "verbose";
31
+ created_at: string;
32
+ last_used_at: string;
33
+ }
34
+ export interface BackgroundResult {
35
+ text: string;
36
+ status: "done" | "error";
37
+ finished_at: string;
38
+ }
39
+ export interface ChatContextState {
40
+ current_session: string;
41
+ previous_session?: string;
42
+ background_results?: Record<string, BackgroundResult>;
43
+ }
44
+ export interface AppState {
45
+ sessions: Record<string, LogicalSession>;
46
+ chat_contexts: Record<string, ChatContextState>;
47
+ native_session_lists: Record<string, NativeSessionListCacheRecord>;
48
+ orchestration: OrchestrationState;
49
+ scheduled_tasks: Record<string, ScheduledTaskRecord>;
50
+ }
51
+ export declare function createEmptyState(): AppState;
@@ -0,0 +1,14 @@
1
+ import type { ToolUseEvent } from "../channels/types.js";
2
+ export type ToolEventMode = "text" | "structured" | "both";
3
+ /**
4
+ * Resolves the effective tool-event rendering mode.
5
+ *
6
+ * Resolution order (hard contract — preserves Phase 0 invariant):
7
+ * 1. Explicit `toolEventMode` always wins.
8
+ * 2. `onToolEvent` present → "structured" (structured consumer, suppress text tool calls).
9
+ * 3. Default → "text" (legacy text tool calls; no structured consumer).
10
+ */
11
+ export declare function resolveToolEventMode(input?: {
12
+ toolEventMode?: ToolEventMode;
13
+ onToolEvent?: (event: ToolUseEvent) => void | Promise<void>;
14
+ }): ToolEventMode;
@@ -0,0 +1,129 @@
1
+ import type { NonInteractivePermissions, PermissionMode } from "../config/types";
2
+ import type { QuotaManager } from "../weixin/messaging/quota-manager.js";
3
+ import type { ToolUseEvent } from "../channels/types.js";
4
+ import type { ToolEventMode } from "./tool-event-mode.js";
5
+ export type { ToolEventMode } from "./tool-event-mode.js";
6
+ export interface ReplyQuotaContext {
7
+ chatKey: string;
8
+ quota: QuotaManager;
9
+ }
10
+ export interface PromptMedia {
11
+ type: "image" | "audio" | "video" | "file";
12
+ filePath: string;
13
+ mimeType: string;
14
+ fileName?: string;
15
+ }
16
+ export interface PermissionPolicy {
17
+ permissionMode: PermissionMode;
18
+ nonInteractivePermissions: NonInteractivePermissions;
19
+ permissionPolicy?: string;
20
+ }
21
+ export interface ResolvedSession {
22
+ alias: string;
23
+ agent: string;
24
+ agentCommand?: string;
25
+ workspace: string;
26
+ transportSession: string;
27
+ source?: "weacpx" | "agent-side";
28
+ agentSessionId?: string;
29
+ agentSessionTitle?: string;
30
+ agentSessionUpdatedAt?: string;
31
+ attachedAt?: string;
32
+ mcpCoordinatorSession?: string;
33
+ mcpSourceHandle?: string;
34
+ modeId?: string;
35
+ replyMode?: "stream" | "final" | "verbose";
36
+ cwd: string;
37
+ /**
38
+ * True for a non-persisted, single-use session (e.g. a `/later` temp-mode
39
+ * scheduled run). Transport errors for such a session must not suggest
40
+ * `/session new`/`attach`, and missing-session recovery (which mutates
41
+ * persisted state by alias) does not apply.
42
+ */
43
+ transient?: boolean;
44
+ }
45
+ export interface AgentSession {
46
+ sessionId: string;
47
+ cwd?: string;
48
+ title?: string | null;
49
+ updatedAt?: string;
50
+ _meta?: Record<string, unknown>;
51
+ }
52
+ export interface AgentSessionListQuery {
53
+ agent: string;
54
+ agentCommand?: string;
55
+ cwd: string;
56
+ cursor?: string;
57
+ filterCwd?: string;
58
+ }
59
+ export interface AgentSessionListResult {
60
+ source: "agent";
61
+ sessions: AgentSession[];
62
+ cursor?: string;
63
+ nextCursor?: string | null;
64
+ cwd?: string;
65
+ }
66
+ export type EnsureSessionProgressStage = "spawn" | "initializing" | "ready";
67
+ export type EnsureSessionProgress = EnsureSessionProgressStage | {
68
+ kind: "note";
69
+ text: string;
70
+ };
71
+ export type PromptMediaInput = PromptMedia | PromptMedia[];
72
+ export interface PromptOptions {
73
+ onSegment?: (text: string) => void | Promise<void>;
74
+ /**
75
+ * Structured side-channel for tool calls. See `toolEventMode` for routing.
76
+ *
77
+ * Async semantics: callbacks are invoked in event order and serialized —
78
+ * each invocation is awaited before the next is dispatched. The transport
79
+ * waits for all callbacks to settle before resolving the prompt. If any
80
+ * invocation throws or returns a rejected promise, the prompt rejects
81
+ * with the first observed error (matching `onSegment` behavior).
82
+ */
83
+ onToolEvent?: (event: ToolUseEvent) => void | Promise<void>;
84
+ /**
85
+ * Optional structured side-channel for the agent's thinking/reasoning.
86
+ *
87
+ * Each acpx `agent_thought_chunk` is forwarded raw (no buffering, no
88
+ * paragraph splitting). Channels that register this callback opt in to
89
+ * receiving thoughts and are responsible for their own accumulation /
90
+ * rendering. When omitted, thought chunks are dropped at the transport
91
+ * boundary — the built-in WeChat channel does not register it.
92
+ *
93
+ * Async semantics match `onSegment`: invocations are serialized and the
94
+ * transport awaits all of them before resolving the prompt; the first
95
+ * error observed rejects the prompt.
96
+ */
97
+ onThought?: (chunk: string) => void | Promise<void>;
98
+ /**
99
+ * How tool_call / tool_call_update events are surfaced for this prompt.
100
+ *
101
+ * - "text" (default when no handler): legacy emoji-prefixed segments in the reply stream.
102
+ * - "structured" (default when a handler is provided): events go to `onToolEvent` only.
103
+ * - "both": events go to `onToolEvent` AND legacy text segments — useful for migration.
104
+ *
105
+ * Resolved at the transport boundary via `resolveToolEventMode`.
106
+ */
107
+ toolEventMode?: ToolEventMode;
108
+ media?: PromptMediaInput;
109
+ }
110
+ export interface SessionTransport {
111
+ ensureSession(session: ResolvedSession, onProgress?: (progress: EnsureSessionProgress) => void): Promise<void>;
112
+ tailSessionHistory(session: ResolvedSession, lines: number): Promise<{
113
+ text: string;
114
+ }>;
115
+ prompt(session: ResolvedSession, text: string, reply?: (text: string) => Promise<void>, replyContext?: ReplyQuotaContext, options?: PromptOptions): Promise<{
116
+ text: string;
117
+ }>;
118
+ setMode(session: ResolvedSession, modeId: string): Promise<void>;
119
+ cancel(session: ResolvedSession): Promise<{
120
+ cancelled: boolean;
121
+ message: string;
122
+ }>;
123
+ hasSession(session: ResolvedSession): Promise<boolean>;
124
+ listAgentSessions?(query: AgentSessionListQuery): Promise<AgentSessionListResult | undefined>;
125
+ resumeAgentSession?(session: ResolvedSession, agentSessionId: string): Promise<void>;
126
+ removeSession?(session: ResolvedSession): Promise<void>;
127
+ updatePermissionPolicy?(policy: PermissionPolicy): Promise<void>;
128
+ dispose?(): Promise<void>;
129
+ }
@@ -0,0 +1,4 @@
1
+ export declare function normalizePath(input: string): string;
2
+ export declare function basenameForPath(input: string): string;
3
+ export declare function isSamePath(left: string, right: string): boolean;
4
+ export declare function isWindowsLikePath(input: string): boolean;
@@ -0,0 +1,10 @@
1
+ export interface SanitizeOptions {
2
+ allow?: RegExp;
3
+ deny?: RegExp;
4
+ replacement?: string;
5
+ collapse?: boolean;
6
+ trim?: boolean;
7
+ lowercase?: boolean;
8
+ fallback?: string;
9
+ }
10
+ export declare function sanitizeString(input: string, options?: SanitizeOptions): string;
@@ -0,0 +1,3 @@
1
+ export declare function truncateText(text: string, maxLength: number, ellipsis?: string): string;
2
+ export declare function escapeForDoubleQuotes(input: string): string;
3
+ export declare function quoteIfNeeded(input: string): string;
package/dist/version.d.ts CHANGED
@@ -1 +1,3 @@
1
1
  export declare function readVersion(moduleUrl?: string): string;
2
+ /** weacpx 核心版本,派生自 package.json(经 readVersion 动态读取,无硬编码漂移)。 */
3
+ export declare const WEACPX_CORE_VERSION: string;
@@ -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
  /**