@ganglion/xacpx 0.15.2 → 0.15.4

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.
@@ -820,6 +820,13 @@ var init_streaming_prompt = __esm(() => {
820
820
  ];
821
821
  });
822
822
 
823
+ // src/transport/model-not-advertised.ts
824
+ function isModelNotAdvertisedError(text) {
825
+ if (!text)
826
+ return false;
827
+ return /Cannot (?:apply --model|replay saved model)\b/i.test(text) || /did not advertise (?:that model|model support)/i.test(text);
828
+ }
829
+
823
830
  // src/recovery/discover-parent-package-paths.ts
824
831
  import { spawn as spawn2 } from "node:child_process";
825
832
  import { createRequire as createRequire2 } from "node:module";
@@ -3913,6 +3920,21 @@ class BridgeRuntime {
3913
3920
  throw new Error(message);
3914
3921
  }
3915
3922
  async ensureSession(input, onProgress) {
3923
+ try {
3924
+ return await this.attemptEnsureSession(input, onProgress);
3925
+ } catch (error) {
3926
+ const requestedModel = input.model?.trim();
3927
+ if (requestedModel && error instanceof EnsureSessionFailedError && isModelNotAdvertisedError(error.message)) {
3928
+ onProgress?.({
3929
+ kind: "note",
3930
+ text: `agent did not advertise model "${requestedModel}"; retrying with the agent's default model`
3931
+ });
3932
+ return await this.attemptEnsureSession({ ...input, model: undefined }, onProgress);
3933
+ }
3934
+ throw error;
3935
+ }
3936
+ }
3937
+ async attemptEnsureSession(input, onProgress) {
3916
3938
  onProgress?.("spawn");
3917
3939
  const timeoutMs = this.sessionInitTimeoutMs();
3918
3940
  const now = this.options.now ?? Date.now;
package/dist/cli.js CHANGED
@@ -20164,7 +20164,7 @@ async function resolveSessionAgentCommandFromIndex(session3) {
20164
20164
  const raw = await readFile12(resolve2(home, ".acpx", "sessions", "index.json"), "utf8");
20165
20165
  const parsed = JSON.parse(raw);
20166
20166
  const targetCwd = resolve2(session3.cwd);
20167
- const match = parsed.entries?.find((entry) => entry.name === session3.transportSession && entry.cwd === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
20167
+ const match = parsed.entries?.find((entry) => entry.name === session3.transportSession && typeof entry.cwd === "string" && resolve2(entry.cwd) === targetCwd && typeof entry.agentCommand === "string" && entry.agentCommand.trim().length > 0);
20168
20168
  return match?.agentCommand?.trim();
20169
20169
  } catch {
20170
20170
  return;
@@ -24733,7 +24733,7 @@ class CommandRouter {
24733
24733
  async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan, onPlan, onUsage, onCommands) {
24734
24734
  const startedAt = Date.now();
24735
24735
  let command = parseCommand(input);
24736
- if (metadata?.channel === "control" && command.kind !== "prompt") {
24736
+ if (metadata?.channel === "control" && command.kind !== "prompt" && command.kind !== "session.reset") {
24737
24737
  command = { kind: "prompt", text: input.trim() };
24738
24738
  }
24739
24739
  await this.logger.debug("command.parsed", "parsed inbound command", {
@@ -31587,6 +31587,13 @@ var init_acpx_bridge_transport = __esm(() => {
31587
31587
  init_quota_gated_reply_sink();
31588
31588
  });
31589
31589
 
31590
+ // src/transport/model-not-advertised.ts
31591
+ function isModelNotAdvertisedError(text) {
31592
+ if (!text)
31593
+ return false;
31594
+ return /Cannot (?:apply --model|replay saved model)\b/i.test(text) || /did not advertise (?:that model|model support)/i.test(text);
31595
+ }
31596
+
31590
31597
  // src/transport/prompt-media.ts
31591
31598
  import { mkdtemp, open as open4, rm as rm9, writeFile as writeFile7 } from "node:fs/promises";
31592
31599
  import { tmpdir as defaultTmpdir } from "node:os";
@@ -32481,6 +32488,18 @@ class AcpxCliTransport {
32481
32488
  this.streamingHooks = streamingHooks;
32482
32489
  }
32483
32490
  async ensureSession(session3, _onProgress) {
32491
+ try {
32492
+ await this.runEnsureSession(session3);
32493
+ } catch (error2) {
32494
+ const requestedModel = session3.model?.trim();
32495
+ if (requestedModel && isModelNotAdvertisedError(error2 instanceof Error ? error2.message : null)) {
32496
+ await this.runEnsureSession({ ...session3, model: undefined });
32497
+ return;
32498
+ }
32499
+ throw error2;
32500
+ }
32501
+ }
32502
+ async runEnsureSession(session3) {
32484
32503
  const args = this.buildArgs(session3, [
32485
32504
  "sessions",
32486
32505
  "new",
@@ -33841,7 +33860,9 @@ class ControlService {
33841
33860
  workspace: session3.workspace,
33842
33861
  transportSession: session3.transportSession,
33843
33862
  running: this.deps.activeTurns.isActiveAnywhere(session3.alias),
33844
- archived: session3.archived === true
33863
+ archived: session3.archived === true,
33864
+ ...session3.source === "agent-side" ? { native: true } : {},
33865
+ ...session3.agentCommand ? { agentCommand: session3.agentCommand } : {}
33845
33866
  }));
33846
33867
  }
33847
33868
  async listNativeSessions(_chatKey, agent3, workspace3) {
@@ -33992,10 +34013,14 @@ class ControlService {
33992
34013
  resolveSettled = resolve4;
33993
34014
  });
33994
34015
  this.inFlight.set(key, { controller, settled });
34016
+ let internalAlias;
33995
34017
  let wasArchived = false;
34018
+ let priorTransportSession;
33996
34019
  try {
33997
- const internalAlias = await this.deps.sessions.resolveAliasForChat(params.chatKey, params.sessionAlias);
33998
- wasArchived = (await this.deps.sessions.getSession(internalAlias))?.archived === true;
34020
+ internalAlias = await this.deps.sessions.resolveAliasForChat(params.chatKey, params.sessionAlias);
34021
+ const prior = await this.deps.sessions.getSession(internalAlias);
34022
+ wasArchived = prior?.archived === true;
34023
+ priorTransportSession = prior?.transportSession;
33999
34024
  } catch {}
34000
34025
  try {
34001
34026
  await this.deps.sessions.useSession(params.chatKey, params.sessionAlias);
@@ -34138,6 +34163,14 @@ ${chunk}` : chunk
34138
34163
  } finally {
34139
34164
  this.inFlight.delete(key);
34140
34165
  resolveSettled();
34166
+ if (internalAlias && priorTransportSession) {
34167
+ try {
34168
+ const after = await this.deps.sessions.getSession(internalAlias);
34169
+ if (after && after.transportSession !== priorTransportSession) {
34170
+ this.deps.events.emit({ type: "sessions-changed" });
34171
+ }
34172
+ } catch {}
34173
+ }
34141
34174
  }
34142
34175
  }
34143
34176
  cancelTurn(chatKey, sessionAlias) {
@@ -18,6 +18,15 @@ export interface ControlSessionInfo {
18
18
  transportSession: string;
19
19
  running: boolean;
20
20
  archived: boolean;
21
+ /** True when this logical session was attached to an existing agent-side (native) rollout
22
+ * rather than freshly created. Mirrors LogicalSession.source === "agent-side"; omitted for
23
+ * fresh xacpx sessions so the wire stays minimal. */
24
+ native?: boolean;
25
+ /** The agent adapter command this session runs (acpx-recorded, or the agent's resolved
26
+ * default). Surfaced so the web can avoid seeding a new session's model picker from a
27
+ * session on a different adapter version (whose advertised model ids may be in an
28
+ * incompatible format). Omitted when unknown. */
29
+ agentCommand?: string;
21
30
  }
22
31
  export interface ControlAgentInfo {
23
32
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganglion/xacpx",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
4
4
  "description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",