@ganglion/xacpx 0.24.5 → 0.24.6-beta.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,12 +2,12 @@ export declare const MANAGED_ADAPTERS: {
2
2
  readonly codex: {
3
3
  readonly packageName: "@agentclientprotocol/codex-acp";
4
4
  readonly binName: "codex-acp";
5
- readonly defaultVersion: "1.10.0";
5
+ readonly defaultVersion: "1.12.0";
6
6
  };
7
7
  readonly claude: {
8
8
  readonly packageName: "@agentclientprotocol/claude-agent-acp";
9
9
  readonly binName: "claude-agent-acp";
10
- readonly defaultVersion: "0.75.1";
10
+ readonly defaultVersion: "0.78.0";
11
11
  };
12
12
  };
13
13
  export type ManagedAdapterId = keyof typeof MANAGED_ADAPTERS;
@@ -1,4 +1,26 @@
1
1
  export type BotRuntimeScope = "bot-direct" | "group-member" | "group-controller";
2
+ export interface BotProfilePresentation {
3
+ name: string;
4
+ avatar?: string;
5
+ role?: string;
6
+ }
7
+ export interface BotProfileBehavior {
8
+ instructions?: string;
9
+ }
10
+ export interface BotProfileExecution {
11
+ agent: string;
12
+ workspace: string;
13
+ model?: string;
14
+ effort?: string;
15
+ }
16
+ /** Durable linearization of the Bot fields a Run was accepted against. */
17
+ export interface BotProfileSnapshot {
18
+ revision: number;
19
+ capturedAt: string;
20
+ presentation: BotProfilePresentation;
21
+ behavior: BotProfileBehavior;
22
+ execution: BotProfileExecution;
23
+ }
2
24
  export interface BotProfile {
3
25
  id: string;
4
26
  name: string;
@@ -11,9 +33,18 @@ export interface BotProfile {
11
33
  model?: string;
12
34
  effort?: string;
13
35
  enabled: boolean;
36
+ /** Monotonic Bot-config generation. Missing on PR2 records; parse defaults to 1. */
37
+ profileRevision: number;
14
38
  createdAt: string;
15
39
  updatedAt: string;
16
40
  }
41
+ export declare function sessionMatchesExecution(session: {
42
+ agent: string;
43
+ workspace: string;
44
+ model?: string;
45
+ effort?: string;
46
+ }, execution: BotProfileExecution): boolean;
47
+ export declare function snapshotBotProfile(bot: BotProfile, capturedAt: string): BotProfileSnapshot;
17
48
  interface BotRuntimeBindingBase {
18
49
  id: string;
19
50
  conversationId: string;
@@ -7254,12 +7254,12 @@ var init_adapter_catalog = __esm(() => {
7254
7254
  codex: {
7255
7255
  packageName: "@agentclientprotocol/codex-acp",
7256
7256
  binName: "codex-acp",
7257
- defaultVersion: "1.10.0"
7257
+ defaultVersion: "1.12.0"
7258
7258
  },
7259
7259
  claude: {
7260
7260
  packageName: "@agentclientprotocol/claude-agent-acp",
7261
7261
  binName: "claude-agent-acp",
7262
- defaultVersion: "0.75.1"
7262
+ defaultVersion: "0.78.0"
7263
7263
  }
7264
7264
  };
7265
7265
  EXACT_SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
@@ -14539,8 +14539,9 @@ class RuntimeEngine {
14539
14539
  }
14540
14540
  } else if (event.type === "status") {
14541
14541
  if (event.tag === "plan") {
14542
- sink({ type: "prompt.segment", text: `${event.text}
14543
- ` });
14542
+ const entries = toPromptPlanEntries(event.entries);
14543
+ if (entries)
14544
+ sink({ type: "prompt.plan", entries });
14544
14545
  return;
14545
14546
  }
14546
14547
  if (typeof event.used === "number" && typeof event.size === "number") {
@@ -16255,6 +16256,31 @@ async function buildRuntimeAttachments(media) {
16255
16256
  }
16256
16257
  return attachments;
16257
16258
  }
16259
+ var PROMPT_PLAN_STATUSES = new Set(["pending", "in_progress", "completed"]);
16260
+ var PROMPT_PLAN_PRIORITIES = new Set(["high", "medium", "low"]);
16261
+ function toPromptPlanEntries(value) {
16262
+ if (!Array.isArray(value))
16263
+ return;
16264
+ if (value.length === 0)
16265
+ return [];
16266
+ const entries = [];
16267
+ for (const item of value) {
16268
+ if (typeof item !== "object" || item === null)
16269
+ continue;
16270
+ const record = item;
16271
+ const content = typeof record.content === "string" ? record.content.trim() : "";
16272
+ const status = typeof record.status === "string" ? record.status.trim() : "";
16273
+ if (!content || !PROMPT_PLAN_STATUSES.has(status))
16274
+ continue;
16275
+ const priority = typeof record.priority === "string" ? record.priority.trim() : "";
16276
+ entries.push({
16277
+ content,
16278
+ status,
16279
+ ...PROMPT_PLAN_PRIORITIES.has(priority) ? { priority } : {}
16280
+ });
16281
+ }
16282
+ return entries.length > 0 ? entries : undefined;
16283
+ }
16258
16284
  function mapRuntimeToolEvent(event) {
16259
16285
  const toolCallId = event.toolCallId || `tc-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
16260
16286
  const title = (event.title ?? "").trim();
@@ -5872,12 +5872,12 @@ var init_adapter_catalog = __esm(() => {
5872
5872
  codex: {
5873
5873
  packageName: "@agentclientprotocol/codex-acp",
5874
5874
  binName: "codex-acp",
5875
- defaultVersion: "1.10.0"
5875
+ defaultVersion: "1.12.0"
5876
5876
  },
5877
5877
  claude: {
5878
5878
  packageName: "@agentclientprotocol/claude-agent-acp",
5879
5879
  binName: "claude-agent-acp",
5880
- defaultVersion: "0.75.1"
5880
+ defaultVersion: "0.78.0"
5881
5881
  }
5882
5882
  };
5883
5883
  EXACT_SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
@@ -7157,6 +7157,7 @@ async function* mapEvents(events) {
7157
7157
  };
7158
7158
  }
7159
7159
  } else if (event.type === "status") {
7160
+ const planEntries = normalizeAdapterPlanEntries(event.entries);
7160
7161
  yield {
7161
7162
  type: "status",
7162
7163
  text: event.text,
@@ -7165,7 +7166,8 @@ async function* mapEvents(events) {
7165
7166
  ...event.size !== undefined ? { size: event.size } : {},
7166
7167
  ...event.cost ? { cost: event.cost } : {},
7167
7168
  ...event.breakdown ? { breakdown: event.breakdown } : {},
7168
- ...event.availableCommands ? { availableCommands: event.availableCommands } : {}
7169
+ ...event.availableCommands ? { availableCommands: event.availableCommands } : {},
7170
+ ...planEntries !== undefined ? { entries: planEntries } : {}
7169
7171
  };
7170
7172
  } else if (event.type === "tool_call") {
7171
7173
  const isInitialToolEvent = typeof event.toolCallId === "string" ? !toolCalls.has(event.toolCallId) : event.tag !== "tool_call_update";
@@ -7188,6 +7190,31 @@ async function* mapEvents(events) {
7188
7190
  }
7189
7191
  }
7190
7192
  }
7193
+ var ADAPTER_PLAN_STATUSES = new Set(["pending", "in_progress", "completed"]);
7194
+ var ADAPTER_PLAN_PRIORITIES = new Set(["high", "medium", "low"]);
7195
+ function normalizeAdapterPlanEntries(value) {
7196
+ if (!Array.isArray(value))
7197
+ return;
7198
+ if (value.length === 0)
7199
+ return [];
7200
+ const entries = [];
7201
+ for (const entry of value) {
7202
+ if (typeof entry !== "object" || entry === null)
7203
+ continue;
7204
+ const record = entry;
7205
+ const content = typeof record.content === "string" ? record.content.trim() : "";
7206
+ const status = typeof record.status === "string" ? record.status.trim() : "";
7207
+ if (!content || !ADAPTER_PLAN_STATUSES.has(status))
7208
+ continue;
7209
+ const priority = typeof record.priority === "string" ? record.priority.trim() : "";
7210
+ entries.push({
7211
+ content,
7212
+ status,
7213
+ ...ADAPTER_PLAN_PRIORITIES.has(priority) ? { priority } : {}
7214
+ });
7215
+ }
7216
+ return entries.length > 0 ? entries : undefined;
7217
+ }
7191
7218
  async function mapResult(result) {
7192
7219
  const settled = await result;
7193
7220
  if (settled.status === "failed") {
package/dist/cli.js CHANGED
@@ -4355,12 +4355,12 @@ var init_adapter_catalog = __esm(() => {
4355
4355
  codex: {
4356
4356
  packageName: "@agentclientprotocol/codex-acp",
4357
4357
  binName: "codex-acp",
4358
- defaultVersion: "1.10.0"
4358
+ defaultVersion: "1.12.0"
4359
4359
  },
4360
4360
  claude: {
4361
4361
  packageName: "@agentclientprotocol/claude-agent-acp",
4362
4362
  binName: "claude-agent-acp",
4363
- defaultVersion: "0.75.1"
4363
+ defaultVersion: "0.78.0"
4364
4364
  }
4365
4365
  };
4366
4366
  EXACT_SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
@@ -30740,7 +30740,11 @@ function isLogicalSessionOwner(value) {
30740
30740
  if (!isRecord4(value)) {
30741
30741
  return false;
30742
30742
  }
30743
- return (value.kind === "bot-direct" || value.kind === "group-member" || value.kind === "group-controller") && isString(value.bindingId) && value.bindingId.length > 0;
30743
+ if (value.kind !== "bot-direct" && value.kind !== "group-member" && value.kind !== "group-controller" || !isString(value.bindingId) || value.bindingId.length === 0) {
30744
+ return false;
30745
+ }
30746
+ const optional3 = (field) => value[field] === undefined || isString(value[field]) && value[field].length > 0;
30747
+ return optional3("botId") && optional3("conversationId") && optional3("topicId");
30744
30748
  }
30745
30749
  function parseSessions(raw, dropped, migrated) {
30746
30750
  const sessions = {};
@@ -30836,7 +30840,7 @@ function isBotProfile(value) {
30836
30840
  if (!isRecord4(value)) {
30837
30841
  return false;
30838
30842
  }
30839
- return isString(value.id) && isString(value.name) && isOptionalString(value.avatar) && isOptionalString(value.role) && isOptionalString(value.instructions) && isString(value.agent) && isString(value.workspace) && isOptionalString(value.cwd) && isOptionalString(value.model) && isOptionalString(value.effort) && typeof value.enabled === "boolean" && isString(value.createdAt) && isString(value.updatedAt);
30843
+ return isString(value.id) && isString(value.name) && isOptionalString(value.avatar) && isOptionalString(value.role) && isOptionalString(value.instructions) && isString(value.agent) && isString(value.workspace) && isOptionalString(value.cwd) && isOptionalString(value.model) && isOptionalString(value.effort) && typeof value.enabled === "boolean" && (value.profileRevision === undefined || typeof value.profileRevision === "number" && Number.isInteger(value.profileRevision) && value.profileRevision >= 1) && isString(value.createdAt) && isString(value.updatedAt);
30840
30844
  }
30841
30845
  function parseBotProfiles(raw, dropped) {
30842
30846
  const source = sectionRecord(raw, "bots", dropped);
@@ -30846,7 +30850,10 @@ function parseBotProfiles(raw, dropped) {
30846
30850
  dropped.push({ section: "bots", key: id, reason: "malformed bot profile" });
30847
30851
  continue;
30848
30852
  }
30849
- bots[id] = value;
30853
+ bots[id] = {
30854
+ ...value,
30855
+ profileRevision: value.profileRevision ?? 1
30856
+ };
30850
30857
  }
30851
30858
  return bots;
30852
30859
  }
@@ -30860,6 +30867,9 @@ function isConversationRecord(value) {
30860
30867
  if (!isString(value.id) || !isString(value.title) || !isOptionalString(value.description) || !isUniqueStringArray(value.botIds) || !isOptionalString(value.leadBotId) || !isString(value.createdAt) || !isString(value.updatedAt)) {
30861
30868
  return false;
30862
30869
  }
30870
+ if (value.lifecycle !== undefined && value.lifecycle !== "active" && value.lifecycle !== "deleting") {
30871
+ return false;
30872
+ }
30863
30873
  if (value.leadBotId !== undefined && !value.botIds.includes(value.leadBotId)) {
30864
30874
  return false;
30865
30875
  }
@@ -30887,7 +30897,7 @@ function isConversationTopic(value) {
30887
30897
  if (!isRecord4(value)) {
30888
30898
  return false;
30889
30899
  }
30890
- return isString(value.id) && isString(value.conversationId) && isString(value.title) && (value.status === "active" || value.status === "archived") && isString(value.createdAt) && isString(value.updatedAt);
30900
+ return isString(value.id) && isString(value.conversationId) && isString(value.title) && (value.status === "active" || value.status === "archived" || value.status === "deleting") && isString(value.createdAt) && isString(value.updatedAt);
30891
30901
  }
30892
30902
  function parseConversationTopics(raw, dropped) {
30893
30903
  const source = sectionRecord(raw, "conversation_topics", dropped);
@@ -42581,6 +42591,7 @@ function lockForPhysicalKey(physicalKey) {
42581
42591
  }
42582
42592
  async function removeAliasWithPhysicalLifecycle(options) {
42583
42593
  const { sessions, transport, session: session3, internalAlias } = options;
42594
+ const policy = options.physicalFailurePolicy ?? "legacy-cli-best-effort";
42584
42595
  const groupKey = physicalLifecycleKeyForResolvedSession(session3);
42585
42596
  const isRuntime = session3.transportEngine === "runtime";
42586
42597
  return lockForPhysicalKey(groupKey).run(async () => {
@@ -42610,9 +42621,14 @@ async function removeAliasWithPhysicalLifecycle(options) {
42610
42621
  await transport.deleteSession(session3);
42611
42622
  action = "deleted";
42612
42623
  } catch (error2) {
42624
+ if (policy === "strict") {
42625
+ throw error2;
42626
+ }
42613
42627
  transportTeardownWarning = error2 instanceof Error ? error2.message : String(error2);
42614
42628
  action = "logical-only";
42615
42629
  }
42630
+ } else if (remaining === 0 && policy === "strict") {
42631
+ throw new Error(`cannot hard-delete last CLI alias "${internalAlias}": transport has no deleteSession operation`);
42616
42632
  } else {
42617
42633
  action = "logical-only";
42618
42634
  }
@@ -63134,6 +63150,7 @@ ${envelope}` : envelope;
63134
63150
  return {
63135
63151
  ok: false,
63136
63152
  errorMessage,
63153
+ ...!timedOut && signal.aborted ? { cancelled: true } : {},
63137
63154
  ...internalAlias && priorTransportSession ? { postTurnDetection: { internalAlias, priorTransportSession } } : {}
63138
63155
  };
63139
63156
  }
@@ -63558,10 +63575,13 @@ class TurnQueue {
63558
63575
  return {
63559
63576
  ok: result.ok,
63560
63577
  ...result.text !== undefined ? { text: result.text } : {},
63561
- ...result.errorMessage !== undefined ? { errorMessage: result.errorMessage } : {}
63578
+ ...result.errorMessage !== undefined ? { errorMessage: result.errorMessage } : {},
63579
+ ...result.cancelled ? { cancelled: true } : {}
63562
63580
  };
63563
63581
  }
63564
63582
  advanceQueue(key) {
63583
+ const finished = this.inFlight.get(key);
63584
+ this.recordSettledRequestId(finished?.promptRequestId);
63565
63585
  const interrupt = this.pendingInterrupts.get(key);
63566
63586
  if (interrupt) {
63567
63587
  this.pendingInterrupts.delete(key);
@@ -63616,6 +63636,26 @@ class TurnQueue {
63616
63636
  entry.controller.abort();
63617
63637
  return true;
63618
63638
  }
63639
+ cancelTurnForPromptRequest(chatKey, sessionAlias, promptRequestId, concurrencyKey) {
63640
+ const key = this.resolveKey(chatKey, sessionAlias, concurrencyKey);
63641
+ const entry = this.inFlight.get(key);
63642
+ if (!entry || entry.promptRequestId !== promptRequestId) {
63643
+ return false;
63644
+ }
63645
+ entry.controller.abort();
63646
+ return true;
63647
+ }
63648
+ inspectPromptRequest(chatKey, sessionAlias, promptRequestId, concurrencyKey) {
63649
+ const key = this.resolveKey(chatKey, sessionAlias, concurrencyKey);
63650
+ const entry = this.inFlight.get(key);
63651
+ if (entry?.promptRequestId === promptRequestId) {
63652
+ return "in-flight";
63653
+ }
63654
+ if (this.hasSettledRequestId(promptRequestId)) {
63655
+ return "settled";
63656
+ }
63657
+ return "absent";
63658
+ }
63619
63659
  async clearSession(chatKey, sessionAlias, concurrencyKey) {
63620
63660
  const key = this.resolveKey(chatKey, sessionAlias, concurrencyKey);
63621
63661
  const guardPreexisting = this.removing.has(key);
@@ -64202,26 +64242,40 @@ class ControlService {
64202
64242
  return task;
64203
64243
  }
64204
64244
  async prompt(input) {
64245
+ return this.submitHumanPrompt(input, true);
64246
+ }
64247
+ async promptImmediate(input) {
64248
+ return this.submitHumanPrompt(input, false);
64249
+ }
64250
+ submitHumanPrompt(input, queueable) {
64205
64251
  const channelId = getChannelIdFromChatKey(input.chatKey);
64206
64252
  const internalAlias = this.deps.sessions.getResolvedSessionByInternalAlias?.(input.sessionAlias)?.alias ?? this.deps.sessions.getResolvedSessionByInternalAlias?.(toInternalSessionAlias(channelId, input.sessionAlias))?.alias ?? scopeDisplayAliasToInternal(channelId, input.sessionAlias);
64207
64253
  const configTail = this.sessionConfigSetTails.get(internalAlias) ?? this.sessionConfigSetTails.get(input.sessionAlias);
64254
+ const turnOrigin = queueable ? "human" : input.executionOrigin === "human" ? "human" : "orchestration";
64255
+ const submit = () => {
64256
+ if (input.abortSignal?.aborted) {
64257
+ return Promise.resolve({ ok: false, cancelled: true, errorMessage: "cancelled" });
64258
+ }
64259
+ return this.turnQueue.submit({
64260
+ chatKey: input.chatKey,
64261
+ sessionAlias: input.sessionAlias,
64262
+ concurrencyKey: internalAlias,
64263
+ text: input.text,
64264
+ senderId: input.senderId,
64265
+ turnOrigin,
64266
+ queueable,
64267
+ ...input.isOwner !== undefined ? { isOwner: input.isOwner } : {},
64268
+ ...input.accountId !== undefined ? { accountId: input.accountId } : {},
64269
+ ...input.media !== undefined ? { media: input.media } : {},
64270
+ ...input.agentMentions !== undefined ? { agentMentions: input.agentMentions } : {},
64271
+ ...input.promptRequestId !== undefined ? { promptRequestId: input.promptRequestId } : {},
64272
+ ...input.abortSignal !== undefined ? { abortSignal: input.abortSignal } : {}
64273
+ });
64274
+ };
64208
64275
  if (configTail) {
64209
- await configTail.catch(() => {});
64276
+ return configTail.catch(() => {}).then(submit);
64210
64277
  }
64211
- return this.turnQueue.submit({
64212
- chatKey: input.chatKey,
64213
- sessionAlias: input.sessionAlias,
64214
- concurrencyKey: internalAlias,
64215
- text: input.text,
64216
- senderId: input.senderId,
64217
- turnOrigin: "human",
64218
- queueable: true,
64219
- ...input.isOwner !== undefined ? { isOwner: input.isOwner } : {},
64220
- ...input.accountId !== undefined ? { accountId: input.accountId } : {},
64221
- ...input.media !== undefined ? { media: input.media } : {},
64222
- ...input.agentMentions !== undefined ? { agentMentions: input.agentMentions } : {},
64223
- ...input.promptRequestId !== undefined ? { promptRequestId: input.promptRequestId } : {}
64224
- });
64278
+ return submit();
64225
64279
  }
64226
64280
  async runScheduledTurn(input) {
64227
64281
  const channelId = getChannelIdFromChatKey(input.chatKey);
@@ -64260,6 +64314,16 @@ class ControlService {
64260
64314
  const internalAlias = this.deps.sessions.getResolvedSessionByInternalAlias?.(sessionAlias)?.alias ?? this.deps.sessions.getResolvedSessionByInternalAlias?.(toInternalSessionAlias(channelId, sessionAlias))?.alias ?? scopeDisplayAliasToInternal(channelId, sessionAlias);
64261
64315
  return this.turnQueue.cancelTurn(chatKey, sessionAlias, internalAlias);
64262
64316
  }
64317
+ cancelTurnForPromptRequest(chatKey, sessionAlias, promptRequestId) {
64318
+ const channelId = getChannelIdFromChatKey(chatKey);
64319
+ const internalAlias = this.deps.sessions.getResolvedSessionByInternalAlias?.(sessionAlias)?.alias ?? this.deps.sessions.getResolvedSessionByInternalAlias?.(toInternalSessionAlias(channelId, sessionAlias))?.alias ?? scopeDisplayAliasToInternal(channelId, sessionAlias);
64320
+ return this.turnQueue.cancelTurnForPromptRequest(chatKey, sessionAlias, promptRequestId, internalAlias);
64321
+ }
64322
+ inspectPromptRequest(chatKey, sessionAlias, promptRequestId) {
64323
+ const channelId = getChannelIdFromChatKey(chatKey);
64324
+ const internalAlias = this.deps.sessions.getResolvedSessionByInternalAlias?.(sessionAlias)?.alias ?? this.deps.sessions.getResolvedSessionByInternalAlias?.(toInternalSessionAlias(channelId, sessionAlias))?.alias ?? scopeDisplayAliasToInternal(channelId, sessionAlias);
64325
+ return this.turnQueue.inspectPromptRequest(chatKey, sessionAlias, promptRequestId, internalAlias);
64326
+ }
64263
64327
  async submitPeerTurn(input) {
64264
64328
  const channelId = getChannelIdFromChatKey(input.chatKey);
64265
64329
  const internalAlias = input.boundSessionAlias ?? this.deps.sessions.getResolvedSessionByInternalAlias?.(input.sessionAlias)?.alias ?? this.deps.sessions.getResolvedSessionByInternalAlias?.(toInternalSessionAlias(channelId, input.sessionAlias))?.alias ?? scopeDisplayAliasToInternal(channelId, input.sessionAlias);
@@ -1,5 +1,6 @@
1
1
  import type { SessionService } from "../sessions/session-service";
2
2
  import type { ResolvedSession, SessionTransport } from "../transport/types";
3
+ export type PhysicalFailurePolicy = "legacy-cli-best-effort" | "strict";
3
4
  export interface PhysicalRemoveOutcome {
4
5
  wasActive: boolean;
5
6
  /**
@@ -26,6 +27,14 @@ export declare function removeAliasWithPhysicalLifecycle(options: {
26
27
  transport: Pick<SessionTransport, "releaseLogicalSession" | "deleteSession">;
27
28
  session: ResolvedSession;
28
29
  internalAlias: string;
30
+ /**
31
+ * Default `legacy-cli-best-effort` keeps `/session rm` semantics: a CLI
32
+ * last-owner `deleteSession` failure records a warning and still removes
33
+ * the LogicalSession. `strict` is the Bot/Conversation owned-session
34
+ * path: any Runtime or CLI physical failure throws BEFORE the logical row
35
+ * disappears so callers keep the retry handle.
36
+ */
37
+ physicalFailurePolicy?: PhysicalFailurePolicy;
29
38
  }): Promise<PhysicalRemoveOutcome>;
30
39
  export interface ProvisionalSessionCleanup {
31
40
  sessions: SessionService;
@@ -71,7 +71,7 @@ export interface TransportConfig {
71
71
  */
72
72
  turnIdleTimeoutSeconds?: number;
73
73
  /**
74
- * Advanced acpx embedding-host ceilings (acpx 0.15.1, plan B5).
74
+ * Advanced acpx embedding-host ceilings (acpx 0.15.1+, plan B5).
75
75
  * `acpxMaxIncomingMessageBytes` overrides the 64 MiB default ceiling on
76
76
  * agent → acpx inbound ACP messages (`0` = unlimited — avoid unless a real
77
77
  * workload needs it); `acpxTerminalMaxOutputBytes` caps retained
@@ -7,6 +7,7 @@ import type { ScheduledTaskRecord } from "../scheduled/scheduled-types";
7
7
  import type { CancelTaskInput, OrchestrationService, OrchestrationTaskFilter } from "../orchestration/orchestration-service";
8
8
  import type { OrchestrationTaskRecord } from "../orchestration/orchestration-types";
9
9
  import type { AgentMessageCompletion, AgentMessageMode } from "../orchestration/agent-messaging-types";
10
+ import type { PermissionInteractionOrigin } from "../permissions/permission-types.js";
10
11
  import type { ControlEventBus } from "./control-event-bus";
11
12
  import type { AgentCatalogEntry } from "../config/agent-catalog";
12
13
  import { type BrowseDirsResult, type DirListing, type FileContent, type SearchOptions, type SearchResult, type WorkspaceDiff } from "./workspace-fs";
@@ -221,11 +222,23 @@ export interface ControlPromptInput {
221
222
  * turn-started so the hub can tie a queued prompt back to its pre-written inbound
222
223
  * row (see PromptPayload.promptRequestId). */
223
224
  promptRequestId?: string;
225
+ /** Conversation pre-admission cancel. Checked after any config-tail wait and
226
+ * immediately before TurnQueue.submit so a cancelled Run never starts. */
227
+ abortSignal?: AbortSignal;
228
+ /**
229
+ * Conversation execution provenance, derived by ConversationStore at claim
230
+ * from the live authority epoch. `promptImmediate` fail-closes to
231
+ * orchestration unless this is exactly `"human"`. Interactive `prompt()`
232
+ * ignores it and is always human. Omitting it cannot mint human authority.
233
+ */
234
+ executionOrigin?: PermissionInteractionOrigin;
224
235
  }
225
236
  export interface ControlPromptResult {
226
237
  ok: boolean;
227
238
  text?: string;
228
239
  errorMessage?: string;
240
+ /** Proven cancellation (AbortSignal / user Stop), not an error string match. */
241
+ cancelled?: boolean;
229
242
  /** True when this prompt did not run immediately and was instead appended to the
230
243
  * per-session server-side queue (a turn was already in flight). */
231
244
  queued?: boolean;
@@ -386,6 +399,14 @@ export declare class ControlService {
386
399
  getOrchestrationTask(taskId: string): Promise<OrchestrationTaskRecord | null>;
387
400
  cancelOrchestrationTask(input: CancelTaskInput): Promise<OrchestrationTaskRecord>;
388
401
  prompt(input: ControlPromptInput): Promise<ControlPromptResult>;
402
+ /**
403
+ * Conversation execution seam: same TurnQueue / SessionTurnRunner path as
404
+ * `prompt()`, but never FIFO-enqueues when the session lane is busy.
405
+ * Turn origin is the store-derived `executionOrigin` (fail-closed to
406
+ * orchestration). ConversationStore already owns durable queuing.
407
+ */
408
+ promptImmediate(input: ControlPromptInput): Promise<ControlPromptResult>;
409
+ private submitHumanPrompt;
389
410
  /** Run a fired scheduled task as a real turn through the same machinery as a manual
390
411
  * prompt — so it streams live and persists to history — while tagging turn-started
391
412
  * with the prompt text + schedule origin so the hub records the inbound message and
@@ -395,6 +416,8 @@ export declare class ControlService {
395
416
  isBusy(chatKey: string, sessionAlias: string): boolean;
396
417
  isSessionBusy(internalAlias: string): boolean;
397
418
  cancelTurn(chatKey: string, sessionAlias: string): boolean;
419
+ cancelTurnForPromptRequest(chatKey: string, sessionAlias: string, promptRequestId: string): boolean;
420
+ inspectPromptRequest(chatKey: string, sessionAlias: string, promptRequestId: string): "in-flight" | "settled" | "absent";
398
421
  submitPeerTurn(input: {
399
422
  chatKey: string;
400
423
  sessionAlias: string;
@@ -36,6 +36,8 @@ export interface TurnResult {
36
36
  ok: boolean;
37
37
  text?: string;
38
38
  errorMessage?: string;
39
+ /** Proven user-Stop / abort cancellation. Idle-timeout aborts omit this. */
40
+ cancelled?: boolean;
39
41
  postTurnDetection?: {
40
42
  internalAlias: string;
41
43
  priorTransportSession: string;
@@ -150,6 +150,14 @@ export declare class TurnQueue {
150
150
  private advanceQueue;
151
151
  private drainQueuedPrompt;
152
152
  cancelTurn(chatKey: string, sessionAlias: string, concurrencyKey?: string): boolean;
153
+ /**
154
+ * Abort the in-flight turn only when it is the given promptRequestId.
155
+ * A minted Conversation correlation id is not a lane-wide cancel: a later
156
+ * prompt on the same session must not be aborted, and a settled request
157
+ * must not be treated as cancelled just because Stop was pressed late.
158
+ */
159
+ cancelTurnForPromptRequest(chatKey: string, sessionAlias: string, promptRequestId: string, concurrencyKey?: string): boolean;
160
+ inspectPromptRequest(chatKey: string, sessionAlias: string, promptRequestId: string, concurrencyKey?: string): "in-flight" | "settled" | "absent";
153
161
  /** Tear down all turn state for a session that is being removed or archived: drop every
154
162
  * queued prompt, abort a running turn, and wait (bounded) for it to unwind. The queue is
155
163
  * cleared BEFORE the abort so the aborting turn's finally sees it empty and releases the
@@ -1,8 +1,15 @@
1
+ import type { BotProfileSnapshot } from "../bots/bot-types";
1
2
  export type ConversationKind = "bot" | "group";
2
- export type ConversationTopicStatus = "active" | "archived";
3
+ export type ConversationLifecycle = "active" | "deleting";
4
+ export type ConversationTopicStatus = "active" | "archived" | "deleting";
3
5
  export type ConversationMessageRole = "human" | "bot" | "system";
4
6
  export type GroupTurnOrigin = "human-explicit" | "controller" | "handoff" | "recovery";
5
7
  export type GroupTurnState = "queued" | "running" | "completed" | "failed" | "cancelled";
8
+ export type ConversationRunMode = "explicit";
9
+ export type ConversationRunState = "queued" | "running" | "waiting-human" | "completed" | "failed" | "cancelled" | "indeterminate";
10
+ export type MemberTurnOrigin = "human" | "followup" | "retry" | "recovery";
11
+ export type MemberTurnState = "queued" | "dispatched" | "running" | "completed" | "failed" | "cancelled" | "indeterminate";
12
+ export type PendingDispatchState = "pending" | "claimed" | "completed";
6
13
  export interface ConversationRecord {
7
14
  id: string;
8
15
  kind: ConversationKind;
@@ -10,6 +17,8 @@ export interface ConversationRecord {
10
17
  description?: string;
11
18
  botIds: string[];
12
19
  leadBotId?: string;
20
+ /** Bounded AppState lifecycle flag. SQLite conversation_lifecycle is authoritative for dispatch. */
21
+ lifecycle?: ConversationLifecycle;
13
22
  createdAt: string;
14
23
  updatedAt: string;
15
24
  }
@@ -25,17 +34,71 @@ export interface ConversationMessage {
25
34
  id: string;
26
35
  conversationId: string;
27
36
  topicId: string;
37
+ seq: number;
28
38
  role: ConversationMessageRole;
29
39
  senderBotId?: string;
30
40
  recipients?: string[];
31
41
  content: string;
32
42
  replyTo?: string;
43
+ runId?: string;
33
44
  createdAt: string;
34
45
  sourceTurn?: {
35
46
  sessionAlias: string;
36
47
  turnId?: string;
37
48
  };
38
49
  }
50
+ export interface ConversationRun {
51
+ id: string;
52
+ conversationId: string;
53
+ topicId: string;
54
+ requestMessageId: string;
55
+ requestId: string;
56
+ mode: ConversationRunMode;
57
+ state: ConversationRunState;
58
+ completionReason?: string;
59
+ generation: number;
60
+ maxMemberTurns: number;
61
+ consumedMemberTurns: number;
62
+ profileRevision: number;
63
+ profileSnapshot: BotProfileSnapshot;
64
+ createdAt: string;
65
+ startedAt?: string;
66
+ finishedAt?: string;
67
+ }
68
+ export interface MemberTurnRecord {
69
+ id: string;
70
+ runId: string;
71
+ conversationId: string;
72
+ topicId: string;
73
+ botId: string;
74
+ sessionAlias?: string;
75
+ logicalSessionId?: string;
76
+ sourceTurnId?: string;
77
+ queueItemId?: string;
78
+ batch: number;
79
+ attempt: number;
80
+ origin: MemberTurnOrigin;
81
+ state: MemberTurnState;
82
+ triggerMessageIds: string[];
83
+ createdAt: string;
84
+ startedAt?: string;
85
+ finishedAt?: string;
86
+ }
87
+ export interface PendingDispatch {
88
+ id: string;
89
+ runId: string;
90
+ memberTurnId: string;
91
+ generation: number;
92
+ state: PendingDispatchState;
93
+ owner?: string;
94
+ leaseExpiresAt?: string;
95
+ /** Live dispatcher epoch that accepted this work. Matching claim keeps human
96
+ * permission authority; mismatch or revoked epoch is recovery/orchestration. */
97
+ authorityEpoch?: string;
98
+ createdAt: string;
99
+ claimedAt?: string;
100
+ completedAt?: string;
101
+ }
39
102
  export interface GroupTurnRecord {
40
103
  id: string;
41
104
  conversationId: string;
@@ -49,3 +112,6 @@ export interface GroupTurnRecord {
49
112
  startedAt?: string;
50
113
  finishedAt?: string;
51
114
  }
115
+ export declare const ACTIVE_RUN_STATES: readonly ConversationRunState[];
116
+ export declare const TERMINAL_RUN_STATES: readonly ConversationRunState[];
117
+ export declare const TERMINAL_MEMBER_STATES: readonly MemberTurnState[];
@@ -7,7 +7,20 @@ export type LogicalSessionSource = "xacpx" | "agent-side";
7
7
  export interface LogicalSessionOwner {
8
8
  kind: "bot-direct" | "group-member" | "group-controller";
9
9
  bindingId: string;
10
+ /**
11
+ * Explicit Bot id for scoped bot-direct ownership. Optional on PR2 records
12
+ * that only stored `bindingId` (the legacy deterministic default-Topic id).
13
+ */
14
+ botId?: string;
15
+ conversationId?: string;
16
+ topicId?: string;
10
17
  }
18
+ export declare function createBotDirectOwner(input: {
19
+ bindingId: string;
20
+ botId: string;
21
+ conversationId: string;
22
+ topicId: string;
23
+ }): LogicalSessionOwner;
11
24
  export interface NativeSessionCacheEntry {
12
25
  session_id: string;
13
26
  cwd?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganglion/xacpx",
3
- "version": "0.24.5",
3
+ "version": "0.24.6-beta.0",
4
4
  "description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",
@@ -86,12 +86,13 @@
86
86
  "test:unit": "node ./scripts/run-tests.mjs tests/unit",
87
87
  "test:smoke": "node ./scripts/run-tests.mjs tests/smoke",
88
88
  "test:compat:acpx": "node ./scripts/run-tests.mjs tests/compat",
89
+ "test:release-boundary": "node ./scripts/release-plan-boundary.mjs",
89
90
  "lint:acpx-imports": "node ./scripts/lint-acpx-imports.mjs",
90
91
  "smoke:local-install": "node ./scripts/smoke-local-install.mjs"
91
92
  },
92
93
  "dependencies": {
93
94
  "@modelcontextprotocol/sdk": "^1.29.0",
94
- "acpx": "0.15.1",
95
+ "acpx": "0.16.0",
95
96
  "node-pty": "^1.1.0",
96
97
  "proper-lockfile": "^4.1.2",
97
98
  "protobufjs": "^7.5.6",