@ganglion/xacpx 0.10.0 → 0.11.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.
@@ -2,5 +2,5 @@ import type { HelpTopicMetadata } from "../help/help-types";
2
2
  import type { CommandRouterContext, RouterResponse } from "../router-types";
3
3
  export declare function agentHelp(): HelpTopicMetadata;
4
4
  export declare function handleAgents(context: CommandRouterContext): RouterResponse;
5
- export declare function handleAgentAdd(context: CommandRouterContext, templateName: string): Promise<RouterResponse>;
5
+ export declare function handleAgentAdd(context: CommandRouterContext, templateName: string, model?: string): Promise<RouterResponse>;
6
6
  export declare function handleAgentRemove(context: CommandRouterContext, agentName: string): Promise<RouterResponse>;
@@ -1,6 +1,6 @@
1
1
  import type { CommandRouterContext, RouterResponse, SessionInteractionOps, SessionLifecycleOps, SessionRenderRecoveryOps } from "../router-types";
2
2
  import type { PromptMediaInput, ResolvedSession } from "../../transport/types";
3
- import type { ToolUseEvent } from "../../channels/types.js";
3
+ import type { PlanEntry, ToolUseEvent } from "../../channels/types.js";
4
4
  import type { PerfSpan } from "../../perf/perf-tracer";
5
5
  import type { HelpTopicMetadata } from "../help/help-types";
6
6
  import type { ChatRequestMetadata } from "../../weixin/agent/interface";
@@ -13,11 +13,12 @@ export interface SessionHandlerContext extends CommandRouterContext {
13
13
  export declare function sessionHelp(): HelpTopicMetadata;
14
14
  export declare function nativeSessionHelp(): HelpTopicMetadata;
15
15
  export declare function modeHelp(): HelpTopicMetadata;
16
+ export declare function modelHelp(): HelpTopicMetadata;
16
17
  export declare function replyModeHelp(): HelpTopicMetadata;
17
18
  export declare function statusHelp(): HelpTopicMetadata;
18
19
  export declare function cancelHelp(): HelpTopicMetadata;
19
20
  export declare function handleSessions(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
20
- export declare function handleSessionNew(context: SessionHandlerContext, chatKey: string, alias: string, agent: string, workspace: string): Promise<RouterResponse>;
21
+ export declare function handleSessionNew(context: SessionHandlerContext, chatKey: string, alias: string, agent: string, workspace: string, model?: string): Promise<RouterResponse>;
21
22
  export declare function handleSessionShortcut(context: SessionHandlerContext, chatKey: string, agent: string, target: {
22
23
  cwd?: string;
23
24
  workspace?: string;
@@ -27,6 +28,8 @@ export declare function handleSessionUse(context: SessionHandlerContext, chatKey
27
28
  export declare function handleSessionUsePrevious(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
28
29
  export declare function handleModeShow(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
29
30
  export declare function handleModeSet(context: SessionHandlerContext, chatKey: string, modeId: string): Promise<RouterResponse>;
31
+ export declare function handleModelShow(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
32
+ export declare function handleModelSet(context: SessionHandlerContext, chatKey: string, modelId: string): Promise<RouterResponse>;
30
33
  export declare function handleReplyModeShow(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
31
34
  export declare function handleReplyModeSet(context: SessionHandlerContext, chatKey: string, replyMode: "stream" | "final" | "verbose"): Promise<RouterResponse>;
32
35
  export declare function handleReplyModeReset(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
@@ -35,5 +38,5 @@ export declare function handleCancel(context: SessionHandlerContext, chatKey: st
35
38
  export declare function handleSessionReset(context: SessionHandlerContext, chatKey: string): Promise<RouterResponse>;
36
39
  export declare function handleSessionTail(context: SessionHandlerContext, chatKey: string, lines?: number): Promise<RouterResponse>;
37
40
  export declare function handleSessionRemove(context: SessionHandlerContext, chatKey: string, alias: string): Promise<RouterResponse>;
38
- export declare function handlePromptWithSession(context: SessionHandlerContext, session: ResolvedSession, chatKey: string, text: string, reply?: (text: string) => Promise<void>, replyContextToken?: string, accountId?: string, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan, metadata?: ChatRequestMetadata): Promise<RouterResponse>;
39
- export declare function handlePrompt(context: SessionHandlerContext, chatKey: string, text: string, reply?: (text: string) => Promise<void>, replyContextToken?: string, accountId?: string, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan, metadata?: ChatRequestMetadata): Promise<RouterResponse>;
41
+ export declare function handlePromptWithSession(context: SessionHandlerContext, session: ResolvedSession, chatKey: string, text: string, reply?: (text: string) => Promise<void>, replyContextToken?: string, accountId?: string, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan, metadata?: ChatRequestMetadata, onPlan?: (entries: PlanEntry[]) => void | Promise<void>): Promise<RouterResponse>;
42
+ export declare function handlePrompt(context: SessionHandlerContext, chatKey: string, text: string, reply?: (text: string) => Promise<void>, replyContextToken?: string, accountId?: string, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan, metadata?: ChatRequestMetadata, onPlan?: (entries: PlanEntry[]) => void | Promise<void>): Promise<RouterResponse>;
@@ -19,6 +19,7 @@ export type ParsedCommand = {
19
19
  } | {
20
20
  kind: "agent.add";
21
21
  template: string;
22
+ model?: string;
22
23
  } | {
23
24
  kind: "agent.rm";
24
25
  name: string;
@@ -109,6 +110,11 @@ export type ParsedCommand = {
109
110
  } | {
110
111
  kind: "mode.set";
111
112
  modeId: string;
113
+ } | {
114
+ kind: "model.show";
115
+ } | {
116
+ kind: "model.set";
117
+ modelId: string;
112
118
  } | {
113
119
  kind: "replymode.show";
114
120
  } | {
@@ -126,6 +132,7 @@ export type ParsedCommand = {
126
132
  alias: string;
127
133
  agent: string;
128
134
  workspace: string;
135
+ model?: string;
129
136
  } | {
130
137
  kind: "session.shortcut";
131
138
  agent: string;
@@ -5,7 +5,7 @@ import type { OrchestrationService } from "../orchestration/orchestration-servic
5
5
  import type { SessionService } from "../sessions/session-service";
6
6
  import type { PromptMediaInput, ReplyQuotaContext, SessionTransport } from "../transport/types";
7
7
  import type { QuotaManager } from "../weixin/messaging/quota-manager.js";
8
- import type { ToolUseEvent } from "../channels/types.js";
8
+ import type { PlanEntry, ToolUseEvent } from "../channels/types.js";
9
9
  import type { PerfSpan } from "../perf/perf-tracer";
10
10
  export interface RouterResponse {
11
11
  text?: string;
@@ -104,11 +104,16 @@ export interface SessionLifecycleOps {
104
104
  }
105
105
  export interface SessionInteractionOps {
106
106
  setModeTransportSession: (session: import("../transport/types").ResolvedSession, modeId: string) => Promise<void>;
107
+ setModelTransportSession: (session: import("../transport/types").ResolvedSession, modelId: string) => Promise<void>;
108
+ getModelTransportSession: (session: import("../transport/types").ResolvedSession) => Promise<{
109
+ current?: string;
110
+ available: string[];
111
+ }>;
107
112
  cancelTransportSession: (session: import("../transport/types").ResolvedSession) => Promise<{
108
113
  cancelled: boolean;
109
114
  message: string;
110
115
  }>;
111
- promptTransportSession: (session: import("../transport/types").ResolvedSession, text: string, reply?: (text: string) => Promise<void>, replyContext?: ReplyQuotaContext, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan) => Promise<{
116
+ promptTransportSession: (session: import("../transport/types").ResolvedSession, text: string, reply?: (text: string) => Promise<void>, replyContext?: ReplyQuotaContext, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan, onPlan?: (entries: PlanEntry[]) => void | Promise<void>) => Promise<{
112
117
  text: string;
113
118
  }>;
114
119
  }
@@ -0,0 +1,14 @@
1
+ import type { AppConfig } from "./types";
2
+ export interface AgentCatalogEntry {
3
+ driver: string;
4
+ configured: boolean;
5
+ installed: "builtin" | "yes" | "unknown";
6
+ }
7
+ /** True if `binary` is found in any PATH directory (no extension assumptions on POSIX). */
8
+ export declare function isBinaryOnPath(binary: string): boolean;
9
+ /**
10
+ * Catalog of every acpx driver xacpx knows (from `listAgentTemplates()`), each
11
+ * tagged with whether it's already configured and a best-effort install hint.
12
+ * `probe` is injectable for tests; defaults to a real PATH lookup.
13
+ */
14
+ export declare function listAgentCatalog(config: AppConfig, probe?: (binary: string) => boolean): AgentCatalogEntry[];
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Executable-file extensions to try for a bare command name. POSIX: just the name;
3
+ * Windows: the PATHEXT list (executability there is decided by extension, not a mode bit).
4
+ */
5
+ export declare function executableExtensions(platform: NodeJS.Platform, env: NodeJS.ProcessEnv): string[];
6
+ /**
7
+ * Is `name` an executable on PATH? Cross-platform: honours PATHEXT on Windows so
8
+ * `opencode` matches `opencode.cmd`/`.exe`. `env`/`isExecutableFile` are injectable for tests.
9
+ */
10
+ export declare function isExecutableOnPath(name: string, env?: NodeJS.ProcessEnv, isExecutableFile?: (p: string) => boolean): boolean;
11
+ /**
12
+ * If `driver` is a known npx-fallback agent AND its native CLI is on PATH, return the
13
+ * native command (e.g. `"opencode acp"`) to hand acpx via `--agent`; otherwise undefined
14
+ * (let acpx fall back to its npx default). Returns the bare bin name — acpx resolves it
15
+ * on PATH at spawn time, sidestepping any path-with-spaces quoting concerns.
16
+ */
17
+ export declare function resolveLocalAgentCommand(driver: string, onPath?: (name: string) => boolean): string | undefined;
@@ -1,2 +1,13 @@
1
1
  export declare function resolveAgentCommand(driver: string, command: string | undefined): string | undefined;
2
+ /**
3
+ * Agent command for the RUNTIME spawn/reap paths. An explicit per-agent `command`
4
+ * always wins; otherwise, when `preferLocal` (default true), prefer a locally-installed
5
+ * native CLI over acpx's npx fallback; otherwise undefined (acpx resolves the driver).
6
+ *
7
+ * Use this — not bare resolveAgentCommand — wherever an agentCommand is built to spawn
8
+ * OR reap a queue owner, so both resolve identically (a mismatch would orphan the owner).
9
+ * It is deliberately NOT used on the config-persistence paths (load/ensure-config), so a
10
+ * machine-specific local binary is never baked into the shareable config file.
11
+ */
12
+ export declare function resolveRuntimeAgentCommand(driver: string, command: string | undefined, preferLocal?: boolean): string | undefined;
2
13
  export declare function isLegacyCodexCommand(command: string): boolean;
@@ -33,6 +33,14 @@ export interface TransportConfig {
33
33
  * `0` keeps the owner alive forever. Defaults to 1800 (30 min).
34
34
  */
35
35
  queueOwnerTtlSeconds?: number;
36
+ /**
37
+ * Prefer a locally-installed native agent CLI over acpx's `npx -y <pkg>` fallback
38
+ * when one is on PATH (currently the unpinned-npx drivers: opencode, kilocode). This
39
+ * avoids a per-cold-start npm-registry fetch — faster and immune to network blips
40
+ * (e.g. ECONNRESET during agent init). Defaults to `true`; set `false` to always use
41
+ * acpx's default resolution. A per-agent `command` override still takes precedence.
42
+ */
43
+ preferLocalAgents?: boolean;
36
44
  }
37
45
  export type LoggingLevel = "error" | "info" | "debug";
38
46
  export interface PerfLogConfig {
@@ -51,6 +59,8 @@ export interface LoggingConfig {
51
59
  export interface AgentConfig {
52
60
  driver: string;
53
61
  command?: string;
62
+ /** Default LLM model id for sessions of this agent (e.g. `gpt-5.2[high]`); a session-level model overrides it. */
63
+ model?: string;
54
64
  }
55
65
  export interface WorkspaceConfig {
56
66
  cwd: string;
@@ -0,0 +1,59 @@
1
+ import type { AppLogger } from "../logging/app-logger";
2
+ import type { ToolUseEvent, PlanEntry } from "../channels/types";
3
+ import type { NativeHistoryMessage } from "../transport/native-session-history";
4
+ export interface ScheduledOrigin {
5
+ taskId: string;
6
+ executeAt: string;
7
+ }
8
+ export type ControlEvent = {
9
+ type: "turn-output";
10
+ chatKey: string;
11
+ sessionAlias: string;
12
+ chunk: string;
13
+ } | {
14
+ type: "turn-started";
15
+ chatKey: string;
16
+ sessionAlias: string;
17
+ prompt?: string;
18
+ scheduled?: ScheduledOrigin;
19
+ } | {
20
+ type: "tool-event";
21
+ chatKey: string;
22
+ sessionAlias: string;
23
+ event: ToolUseEvent;
24
+ } | {
25
+ type: "turn-thought";
26
+ chatKey: string;
27
+ sessionAlias: string;
28
+ chunk: string;
29
+ } | {
30
+ type: "plan";
31
+ chatKey: string;
32
+ sessionAlias: string;
33
+ entries: PlanEntry[];
34
+ } | {
35
+ type: "turn-finished";
36
+ chatKey: string;
37
+ sessionAlias: string;
38
+ ok: boolean;
39
+ errorMessage?: string;
40
+ cancelled?: boolean;
41
+ } | {
42
+ type: "sessions-changed";
43
+ } | {
44
+ type: "scheduled-changed";
45
+ chatKey: string;
46
+ } | {
47
+ type: "session-history";
48
+ chatKey: string;
49
+ sessionAlias: string;
50
+ messages: NativeHistoryMessage[];
51
+ } | {
52
+ type: "orchestration-changed";
53
+ };
54
+ export type ControlEventListener = (event: ControlEvent) => void;
55
+ export interface ControlEventBus {
56
+ subscribe(listener: ControlEventListener): () => void;
57
+ emit(event: ControlEvent): void;
58
+ }
59
+ export declare function createControlEventBus(logger?: AppLogger): ControlEventBus;
@@ -0,0 +1,147 @@
1
+ import type { Agent as ChatAgent } from "../weixin/agent/interface";
2
+ import type { SessionService } from "../sessions/session-service";
3
+ import type { AgentSession, ResolvedSession, SessionTransport } from "../transport/types";
4
+ import type { ActiveTurnRegistry } from "../sessions/active-turn-registry";
5
+ import type { CreateScheduledTaskInput, ScheduledTaskService } from "../scheduled/scheduled-service";
6
+ import type { ScheduledTaskRecord } from "../scheduled/scheduled-types";
7
+ import type { CancelTaskInput, OrchestrationService, OrchestrationTaskFilter } from "../orchestration/orchestration-service";
8
+ import type { OrchestrationTaskRecord } from "../orchestration/orchestration-types";
9
+ import type { ControlEventBus } from "./control-event-bus";
10
+ import type { AgentCatalogEntry } from "../config/agent-catalog";
11
+ import { type DirListing, type FileContent, type SearchResult, type WorkspaceDiff } from "./workspace-fs";
12
+ export interface ControlSessionInfo {
13
+ alias: string;
14
+ agent: string;
15
+ workspace: string;
16
+ transportSession: string;
17
+ running: boolean;
18
+ }
19
+ export interface ControlAgentInfo {
20
+ name: string;
21
+ driver: string;
22
+ }
23
+ /** An agent-native (acpx-owned) session available to attach as a new logical session. */
24
+ export interface ControlNativeSessionInfo {
25
+ sessionId: string;
26
+ title?: string | null;
27
+ updatedAt?: string;
28
+ cwd?: string;
29
+ }
30
+ export interface ControlWorkspaceInfo {
31
+ name: string;
32
+ cwd: string;
33
+ description?: string;
34
+ }
35
+ export interface ControlServiceDeps {
36
+ agent: Pick<ChatAgent, "chat">;
37
+ sessions: Pick<SessionService, "listAllResolvedSessions" | "removeSession" | "useSession" | "resolveAliasForChat" | "getSession" | "setSessionModel">;
38
+ transport: Pick<SessionTransport, "setModel" | "getSessionModel">;
39
+ createSessionWithTransport: (internalAlias: string, agent: string, workspace: string, model?: string) => Promise<ResolvedSession>;
40
+ listNativeSessions: (agent: string, workspace: string) => Promise<AgentSession[]>;
41
+ attachNativeSessionWithTransport: (internalAlias: string, agent: string, workspace: string, agentSessionId: string, nativeMeta?: {
42
+ title?: string | null;
43
+ updatedAt?: string;
44
+ }) => Promise<ResolvedSession>;
45
+ activeTurns: Pick<ActiveTurnRegistry, "isActiveAnywhere">;
46
+ scheduled: Pick<ScheduledTaskService, "listPending" | "listRecentForChat" | "createTask" | "cancelPending">;
47
+ orchestration: Pick<OrchestrationService, "listTasks" | "getTask" | "requestTaskCancellation">;
48
+ events: ControlEventBus;
49
+ agents: {
50
+ list(): ControlAgentInfo[];
51
+ catalog(): AgentCatalogEntry[];
52
+ create(name: string, driver: string): Promise<ControlAgentInfo>;
53
+ remove(name: string): Promise<void>;
54
+ };
55
+ workspaces: {
56
+ list(): ControlWorkspaceInfo[];
57
+ create(name: string, cwd: string, description?: string): Promise<ControlWorkspaceInfo>;
58
+ remove(name: string): Promise<void>;
59
+ };
60
+ }
61
+ export interface ControlPromptInput {
62
+ chatKey: string;
63
+ sessionAlias: string;
64
+ text: string;
65
+ accountId?: string;
66
+ senderId: string;
67
+ isOwner?: boolean;
68
+ }
69
+ export interface ControlPromptResult {
70
+ ok: boolean;
71
+ text?: string;
72
+ errorMessage?: string;
73
+ }
74
+ /** A turn started by a fired scheduled task. Runs through the same agent + turn-event
75
+ * machinery as a normal prompt, so it streams live and persists to history — but it
76
+ * also carries the prompt text + schedule origin in turn-started, so the hub can
77
+ * persist the inbound message and the web can badge the run. */
78
+ export interface ControlScheduledTurnInput {
79
+ chatKey: string;
80
+ sessionAlias: string;
81
+ promptText: string;
82
+ taskId: string;
83
+ executeAt: string;
84
+ accountId?: string;
85
+ abortSignal?: AbortSignal;
86
+ }
87
+ export interface ControlExecuteCommandInput {
88
+ chatKey: string;
89
+ text: string;
90
+ accountId?: string;
91
+ senderId: string;
92
+ isOwner?: boolean;
93
+ }
94
+ export declare class ControlService {
95
+ private readonly deps;
96
+ constructor(deps: ControlServiceDeps);
97
+ private readonly workspaceFs;
98
+ listDirectory(workspace: string, path?: string): Promise<DirListing>;
99
+ readWorkspaceFile(workspace: string, path: string): Promise<FileContent>;
100
+ workspaceGitDiff(workspace: string, path?: string): Promise<WorkspaceDiff>;
101
+ searchWorkspace(workspace: string, query: string): Promise<SearchResult>;
102
+ /** Read a session's current model and the agent-advertised available ids. */
103
+ getSessionModel(chatKey: string, alias: string): Promise<{
104
+ current?: string;
105
+ available: string[];
106
+ }>;
107
+ /** Switch a session's model (acpx validates the id) and persist the override. */
108
+ setSessionModel(chatKey: string, alias: string, modelId: string): Promise<void>;
109
+ /** Resolve a chat-scoped display alias to its ResolvedSession, or null. */
110
+ private resolveControlSession;
111
+ get events(): ControlEventBus;
112
+ listSessions(chatKey: string): ControlSessionInfo[];
113
+ /**
114
+ * List the agent-native (acpx-owned) sessions for an agent + workspace, so the web
115
+ * add-session dialog can offer "attach an existing native session". These are the
116
+ * agent's own rollouts on disk (per-cwd), not chat-scoped — chatKey is accepted only
117
+ * for call-shape symmetry with the other session control methods.
118
+ */
119
+ listNativeSessions(_chatKey: string, agent: string, workspace: string): Promise<ControlNativeSessionInfo[]>;
120
+ createSession(chatKey: string, alias: string, agent: string, workspace: string, agentSessionId?: string, model?: string): Promise<ControlSessionInfo>;
121
+ removeSession(chatKey: string, alias: string): Promise<{
122
+ wasActive: boolean;
123
+ }>;
124
+ listAgents(): ControlAgentInfo[];
125
+ listWorkspaces(): ControlWorkspaceInfo[];
126
+ createWorkspace(name: string, cwd: string, description?: string): Promise<ControlWorkspaceInfo>;
127
+ listAgentCatalog(): AgentCatalogEntry[];
128
+ createAgent(name: string, driver: string): Promise<ControlAgentInfo>;
129
+ removeAgent(name: string): Promise<void>;
130
+ removeWorkspace(name: string): Promise<void>;
131
+ listScheduledTasks(chatKey: string): ScheduledTaskRecord[];
132
+ createScheduledTask(input: CreateScheduledTaskInput): Promise<ScheduledTaskRecord>;
133
+ cancelScheduledTask(id: string, chatKey: string): Promise<boolean>;
134
+ listOrchestrationTasks(filter?: OrchestrationTaskFilter): Promise<OrchestrationTaskRecord[]>;
135
+ getOrchestrationTask(taskId: string): Promise<OrchestrationTaskRecord | null>;
136
+ cancelOrchestrationTask(input: CancelTaskInput): Promise<OrchestrationTaskRecord>;
137
+ private readonly inFlight;
138
+ prompt(input: ControlPromptInput): Promise<ControlPromptResult>;
139
+ /** Run a fired scheduled task as a real turn through the same machinery as a manual
140
+ * prompt — so it streams live and persists to history — while tagging turn-started
141
+ * with the prompt text + schedule origin so the hub records the inbound message and
142
+ * the web can badge it. Owner-authorized: the task was owner-gated at creation. */
143
+ runScheduledTurn(input: ControlScheduledTurnInput): Promise<ControlPromptResult>;
144
+ private executeTurn;
145
+ cancelTurn(chatKey: string, sessionAlias: string): boolean;
146
+ executeCommand(input: ControlExecuteCommandInput): Promise<string>;
147
+ }
@@ -0,0 +1,52 @@
1
+ export interface FsEntry {
2
+ name: string;
3
+ type: "dir" | "file";
4
+ size?: number;
5
+ }
6
+ export interface DirListing {
7
+ workspace: string;
8
+ path: string;
9
+ entries: FsEntry[];
10
+ }
11
+ export interface FileContent {
12
+ workspace: string;
13
+ path: string;
14
+ content: string;
15
+ size: number;
16
+ truncated: boolean;
17
+ binary: boolean;
18
+ }
19
+ export interface DiffFile {
20
+ path: string;
21
+ status: string;
22
+ }
23
+ export interface SearchResult {
24
+ workspace: string;
25
+ query: string;
26
+ matches: string[];
27
+ truncated: boolean;
28
+ }
29
+ export interface WorkspaceDiff {
30
+ workspace: string;
31
+ files: DiffFile[];
32
+ diff: string;
33
+ truncated: boolean;
34
+ }
35
+ export interface WorkspaceRef {
36
+ name: string;
37
+ cwd: string;
38
+ }
39
+ export declare class WorkspaceFs {
40
+ private readonly listWorkspaces;
41
+ constructor(listWorkspaces: () => WorkspaceRef[]);
42
+ /** Resolve `relPath` within a named workspace, symlink-safe and contained. Throws
43
+ * on unknown workspace, missing target, or any path that escapes the root. */
44
+ private resolve;
45
+ listDirectory(workspace: string, relPath?: string): Promise<DirListing>;
46
+ readFile(workspace: string, relPath: string): Promise<FileContent>;
47
+ /** Find files whose relative path contains `query` (case-insensitive). Walks the
48
+ * tree breadth-first, skipping `.git`/`node_modules` and never following symlinks
49
+ * (so it stays contained), bounded by a scan budget and a result cap. */
50
+ search(workspace: string, query: string): Promise<SearchResult>;
51
+ gitDiff(workspace: string, relPath?: string): Promise<WorkspaceDiff>;
52
+ }
@@ -25,6 +25,13 @@ export interface SessionMessages {
25
25
  modeModeLabel: (modeId: string) => string;
26
26
  modeNotSet: string;
27
27
  modeSet: (modeId: string) => string;
28
+ modelHeader: string;
29
+ modelSessionLabel: (alias: string) => string;
30
+ modelModelLabel: (modelId: string) => string;
31
+ modelNotSet: string;
32
+ modelAvailableLabel: (models: string) => string;
33
+ modelSet: (modelId: string) => string;
34
+ modelSetFailed: (modelId: string, detail: string) => string;
28
35
  replyModeHeader: string;
29
36
  replyModeSessionLabel: (alias: string) => string;
30
37
  replyModeGlobalDefault: (value: string) => string;
@@ -101,6 +108,11 @@ export interface SessionMessages {
101
108
  modeHelpCmdShowDesc: string;
102
109
  modeHelpCmdSet: string;
103
110
  modeHelpCmdSetDesc: string;
111
+ modelHelpSummary: string;
112
+ modelHelpCmdShow: string;
113
+ modelHelpCmdShowDesc: string;
114
+ modelHelpCmdSet: string;
115
+ modelHelpCmdSetDesc: string;
104
116
  replyModeHelpSummary: string;
105
117
  replyModeHelpCmdShow: string;
106
118
  replyModeHelpCmdShowDesc: string;
@@ -18,3 +18,6 @@ export type { BackgroundResult } from "./state/types.js";
18
18
  export type { ChatRequestMetadata } from "./weixin/agent/interface.js";
19
19
  export { getLocale } from "./i18n/index.js";
20
20
  export type { Locale } from "./i18n/index.js";
21
+ export type { ControlExecuteCommandInput, ControlPromptInput, ControlPromptResult, ControlService, ControlSessionInfo, } from "./control/control-service.js";
22
+ export type { ControlEvent, ControlEventBus, ControlEventListener } from "./control/control-event-bus.js";
23
+ export { coreHomeDir } from "./runtime/core-home.js";
@@ -90,6 +90,14 @@ var init_session = __esm(() => {
90
90
  modeModeLabel: (modeId) => `- mode: ${modeId}`,
91
91
  modeNotSet: "not set",
92
92
  modeSet: (modeId) => `Current session mode set to: ${modeId}`,
93
+ modelHeader: "Current model:",
94
+ modelSessionLabel: (alias) => `- Session: ${alias}`,
95
+ modelModelLabel: (modelId) => `- model: ${modelId}`,
96
+ modelNotSet: "not set (using agent default)",
97
+ modelAvailableLabel: (models) => `- available: ${models}`,
98
+ modelSet: (modelId) => `Current session model switched to: ${modelId}`,
99
+ modelSetFailed: (modelId, detail) => `Failed to switch model: ${modelId}
100
+ ${detail}`,
93
101
  replyModeHeader: "Current reply mode:",
94
102
  replyModeSessionLabel: (alias) => `- Session: ${alias}`,
95
103
  replyModeGlobalDefault: (value) => `- Global default: ${value}`,
@@ -166,6 +174,11 @@ var init_session = __esm(() => {
166
174
  modeHelpCmdShowDesc: "Show the saved mode of the current session",
167
175
  modeHelpCmdSet: "/mode <id>",
168
176
  modeHelpCmdSetDesc: "Set the current session mode",
177
+ modelHelpSummary: "View or switch the LLM model for the current session.",
178
+ modelHelpCmdShow: "/model",
179
+ modelHelpCmdShowDesc: "Show the current session model and the available ones",
180
+ modelHelpCmdSet: "/model <id>",
181
+ modelHelpCmdSetDesc: "Switch the current session model (e.g. gpt-5.2[high])",
169
182
  replyModeHelpSummary: "View or set the reply output mode for the current logical session.",
170
183
  replyModeHelpCmdShow: "/replymode",
171
184
  replyModeHelpCmdShowDesc: "Show global default, current override, and effective value",
@@ -1169,6 +1182,14 @@ var init_session2 = __esm(() => {
1169
1182
  modeModeLabel: (modeId) => `- mode:${modeId}`,
1170
1183
  modeNotSet: "未设置",
1171
1184
  modeSet: (modeId) => `已设置当前会话 mode:${modeId}`,
1185
+ modelHeader: "当前 model:",
1186
+ modelSessionLabel: (alias) => `- 会话:${alias}`,
1187
+ modelModelLabel: (modelId) => `- model:${modelId}`,
1188
+ modelNotSet: "未设置(使用 agent 默认)",
1189
+ modelAvailableLabel: (models) => `- 可选:${models}`,
1190
+ modelSet: (modelId) => `已切换当前会话 model:${modelId}`,
1191
+ modelSetFailed: (modelId, detail) => `切换 model 失败:${modelId}
1192
+ ${detail}`,
1172
1193
  replyModeHeader: "当前 reply mode:",
1173
1194
  replyModeSessionLabel: (alias) => `- 会话:${alias}`,
1174
1195
  replyModeGlobalDefault: (value) => `- 全局默认:${value}`,
@@ -1245,6 +1266,11 @@ var init_session2 = __esm(() => {
1245
1266
  modeHelpCmdShowDesc: "查看当前会话已保存的 mode",
1246
1267
  modeHelpCmdSet: "/mode <id>",
1247
1268
  modeHelpCmdSetDesc: "设置当前会话 mode",
1269
+ modelHelpSummary: "查看或切换当前会话的 LLM model。",
1270
+ modelHelpCmdShow: "/model",
1271
+ modelHelpCmdShowDesc: "查看当前会话 model 及可选项",
1272
+ modelHelpCmdSet: "/model <id>",
1273
+ modelHelpCmdSetDesc: "切换当前会话 model(如 gpt-5.2[high])",
1248
1274
  replyModeHelpSummary: "查看或设置当前逻辑会话的回复输出模式。",
1249
1275
  replyModeHelpCmdShow: "/replymode",
1250
1276
  replyModeHelpCmdShowDesc: "查看全局默认、当前覆盖和实际生效值",
@@ -2444,6 +2470,13 @@ function createActiveTurnRegistry() {
2444
2470
  },
2445
2471
  isActive(chatKey, alias) {
2446
2472
  return byChat.get(chatKey)?.has(alias) ?? false;
2473
+ },
2474
+ isActiveAnywhere(alias) {
2475
+ for (const set of byChat.values()) {
2476
+ if (set.has(alias))
2477
+ return true;
2478
+ }
2479
+ return false;
2447
2480
  }
2448
2481
  };
2449
2482
  }
@@ -2527,6 +2560,24 @@ var init_channel_scope = __esm(() => {
2527
2560
  KNOWN_CHANNEL_IDS = new Set(["weixin"]);
2528
2561
  });
2529
2562
 
2563
+ // src/runtime/core-home.ts
2564
+ import { existsSync } from "node:fs";
2565
+ import { join } from "node:path";
2566
+ function coreHomeDir(home) {
2567
+ const primary = join(home, CORE_HOME_DIR_NAME);
2568
+ if (existsSync(primary))
2569
+ return primary;
2570
+ const legacy = join(home, CORE_HOME_LEGACY_DIR_NAME);
2571
+ if (existsSync(legacy))
2572
+ return legacy;
2573
+ return primary;
2574
+ }
2575
+ function coreHomeDisplayPath(...segments) {
2576
+ return ["~", CORE_HOME_DIR_NAME, ...segments].join("/");
2577
+ }
2578
+ var CORE_HOME_DIR_NAME = ".xacpx", CORE_HOME_LEGACY_DIR_NAME = ".weacpx";
2579
+ var init_core_home = () => {};
2580
+
2530
2581
  // src/plugin-api.ts
2531
2582
  init_types();
2532
2583
 
@@ -2540,12 +2591,14 @@ function resolveTurnLane(text) {
2540
2591
  // src/plugin-api.ts
2541
2592
  init_channel_scope();
2542
2593
  init_i18n();
2594
+ init_core_home();
2543
2595
  export {
2544
2596
  toDisplaySessionAlias,
2545
2597
  resolveTurnLane,
2546
2598
  getLocale,
2547
2599
  createConversationExecutor,
2548
2600
  createActiveTurnRegistry,
2601
+ coreHomeDir,
2549
2602
  XACPX_PLUGIN_MIN_CORE_VERSION,
2550
2603
  XACPX_PLUGIN_API_VERSION,
2551
2604
  XACPX_PLUGIN_API_SUPPORTED_VERSIONS,
@@ -29,6 +29,9 @@ export declare class ScheduledTaskService {
29
29
  constructor(state: AppState, stateStore: Pick<StateStore, "save">, options?: ScheduledTaskServiceOptions);
30
30
  createTask(input: CreateScheduledTaskInput): Promise<ScheduledTaskRecord>;
31
31
  listPending(chatKey: string): ScheduledTaskRecord[];
32
+ listRecentForChat(chatKey: string, opts?: {
33
+ terminalLimit?: number;
34
+ }): ScheduledTaskRecord[];
32
35
  listPendingAllChats(): ScheduledTaskRecord[];
33
36
  cancelPending(inputId: string, chatKey: string): Promise<boolean>;
34
37
  cancelPendingAnyChat(inputId: string): Promise<boolean>;
@@ -2,5 +2,7 @@ export interface ActiveTurnRegistry {
2
2
  markActive(chatKey: string, alias: string): void;
3
3
  markInactive(chatKey: string, alias: string): void;
4
4
  isActive(chatKey: string, alias: string): boolean;
5
+ /** True when any chat currently has a turn running for this alias. */
6
+ isActiveAnywhere(alias: string): boolean;
5
7
  }
6
8
  export declare function createActiveTurnRegistry(): ActiveTurnRegistry;
@@ -113,6 +113,10 @@ export declare class SessionService {
113
113
  getNativeSessionList(chatKey: string, ttlMs?: number): Promise<NativeSessionListResult | null>;
114
114
  private deleteNativeSessionListIfCurrent;
115
115
  private toResolvedSession;
116
+ /** Persist (or clear) a session's model override by internal alias. */
117
+ setSessionModel(alias: string, modelId: string | undefined): Promise<void>;
118
+ /** Persist (or clear) the model override of the chat's current session. */
119
+ setCurrentSessionModel(chatKey: string, modelId: string | undefined): Promise<void>;
116
120
  setSessionTransportAgentCommand(alias: string, transportAgentCommand: string | undefined): Promise<void>;
117
121
  private mutate;
118
122
  private persist;
@@ -27,6 +27,8 @@ export interface LogicalSession {
27
27
  attached_at?: string;
28
28
  transport_agent_command?: string;
29
29
  mode_id?: string;
30
+ /** Per-session LLM model override (e.g. `gpt-5.2[high]`); falls back to the agent config default. */
31
+ model?: string;
30
32
  reply_mode?: "stream" | "final" | "verbose";
31
33
  created_at: string;
32
34
  last_used_at: string;
@@ -0,0 +1,34 @@
1
+ import type { ToolUseEvent } from "../channels/types";
2
+ export type NativeHistoryPart = {
3
+ kind: "text";
4
+ text: string;
5
+ } | {
6
+ kind: "reasoning";
7
+ text: string;
8
+ } | {
9
+ kind: "tool";
10
+ tool: ToolUseEvent;
11
+ };
12
+ export interface NativeHistoryMessage {
13
+ role: "user" | "agent";
14
+ /** Flattened text (the `out`/`in` body); `parts` carries the ordered transcript for agents. */
15
+ text: string;
16
+ /** Ordered transcript for agent turns (text / reasoning / tool), as the agent produced it. */
17
+ parts?: NativeHistoryPart[];
18
+ }
19
+ /** Map an acpx SessionMessage[] (the persisted conversation) to neutral history. The
20
+ * `"Resume"` marker entries are dropped. Pure + defensive: unknown shapes are skipped. */
21
+ export declare function mapAcpxMessagesToHistory(raw: unknown): NativeHistoryMessage[];
22
+ export interface ReadNativeHistoryOptions {
23
+ agentSessionId: string;
24
+ agentCommand?: string;
25
+ /** Override for the acpx sessions dir (tests). Defaults to `<home>/.acpx/sessions`. */
26
+ sessionsDir?: string;
27
+ homeDir?: string;
28
+ }
29
+ /** Recover a native (agent-side) session's prior conversation from acpx's own persisted
30
+ * record. Best-effort: any I/O or shape problem yields `[]` so a native attach never
31
+ * fails just because history couldn't be read. When several records share the same
32
+ * acp_session_id (the source record + the freshly-created empty attach record), the
33
+ * one with the most messages wins — i.e. the real history, not the empty stub. */
34
+ export declare function readNativeSessionHistory(opts: ReadNativeHistoryOptions): Promise<NativeHistoryMessage[]>;