@ganglion/xacpx 0.24.3-beta.1 → 0.24.4-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.1.9";
5
+ readonly defaultVersion: "1.10.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.64.2";
10
+ readonly defaultVersion: "0.75.1";
11
11
  };
12
12
  };
13
13
  export type ManagedAdapterId = keyof typeof MANAGED_ADAPTERS;
@@ -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.1.9"
7257
+ defaultVersion: "1.10.0"
7258
7258
  },
7259
7259
  claude: {
7260
7260
  packageName: "@agentclientprotocol/claude-agent-acp",
7261
7261
  binName: "claude-agent-acp",
7262
- defaultVersion: "0.64.2"
7262
+ defaultVersion: "0.75.1"
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-]+)*)?$/;
@@ -5789,12 +5789,12 @@ var init_adapter_catalog = __esm(() => {
5789
5789
  codex: {
5790
5790
  packageName: "@agentclientprotocol/codex-acp",
5791
5791
  binName: "codex-acp",
5792
- defaultVersion: "1.1.9"
5792
+ defaultVersion: "1.10.0"
5793
5793
  },
5794
5794
  claude: {
5795
5795
  packageName: "@agentclientprotocol/claude-agent-acp",
5796
5796
  binName: "claude-agent-acp",
5797
- defaultVersion: "0.64.2"
5797
+ defaultVersion: "0.75.1"
5798
5798
  }
5799
5799
  };
5800
5800
  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-]+)*)?$/;
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.1.9"
4358
+ defaultVersion: "1.10.0"
4359
4359
  },
4360
4360
  claude: {
4361
4361
  packageName: "@agentclientprotocol/claude-agent-acp",
4362
4362
  binName: "claude-agent-acp",
4363
- defaultVersion: "0.64.2"
4363
+ defaultVersion: "0.75.1"
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-]+)*)?$/;
@@ -30465,7 +30465,7 @@ function isWorkerBindingRecord(value) {
30465
30465
  if (!isRecord4(value)) {
30466
30466
  return false;
30467
30467
  }
30468
- return isString(value.sourceHandle) && (value.agentEndpointId === undefined || isAgentEndpointId(value.agentEndpointId)) && isString(value.coordinatorSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.role) && isOptionalBoolean(value.ephemeral) && isOptionalBoolean(value.guardAcpOutput) && (value.logicalSessionId === undefined || isLogicalSessionId(value.logicalSessionId)) && (value.transportEngine === undefined || value.transportEngine === "cli" || value.transportEngine === "runtime");
30468
+ return isString(value.sourceHandle) && (value.agentEndpointId === undefined || isAgentEndpointId(value.agentEndpointId)) && isString(value.coordinatorSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.launchAgentCommand) && isOptionalString(value.launchAcpxAgent) && isOptionalString(value.launchRawCommand) && isOptionalString(value.role) && isOptionalBoolean(value.ephemeral) && isOptionalBoolean(value.guardAcpOutput) && (value.logicalSessionId === undefined || isLogicalSessionId(value.logicalSessionId)) && (value.transportEngine === undefined || value.transportEngine === "cli" || value.transportEngine === "runtime");
30469
30469
  }
30470
30470
  function isGroupRecord(value) {
30471
30471
  if (!isRecord4(value)) {
@@ -47989,7 +47989,10 @@ async function retireWorkerBinding(env, workerSession) {
47989
47989
  ...claimed.cwd ? { cwd: claimed.cwd } : {},
47990
47990
  ...claimed.role ? { role: claimed.role } : {},
47991
47991
  logicalSessionId: claimed.logicalSessionId,
47992
- transportEngine: claimed.transportEngine
47992
+ transportEngine: claimed.transportEngine,
47993
+ ...claimed.launchAgentCommand ? { launchAgentCommand: claimed.launchAgentCommand } : {},
47994
+ ...claimed.launchAcpxAgent ? { launchAcpxAgent: claimed.launchAcpxAgent } : {},
47995
+ ...claimed.launchRawCommand ? { launchRawCommand: claimed.launchRawCommand } : {}
47993
47996
  });
47994
47997
  } catch {
47995
47998
  return "retained";
@@ -48030,6 +48033,15 @@ function shouldGuardWorkerAcpOutput(binding) {
48030
48033
  function workerBindingGuardFields(previousBinding) {
48031
48034
  return previousBinding && previousBinding.guardAcpOutput !== true ? {} : { guardAcpOutput: true };
48032
48035
  }
48036
+ function workerBindingLaunchFields(previousBinding) {
48037
+ if (!previousBinding)
48038
+ return {};
48039
+ return {
48040
+ ...previousBinding.launchAgentCommand ? { launchAgentCommand: previousBinding.launchAgentCommand } : {},
48041
+ ...previousBinding.launchAcpxAgent ? { launchAcpxAgent: previousBinding.launchAcpxAgent } : {},
48042
+ ...previousBinding.launchRawCommand ? { launchRawCommand: previousBinding.launchRawCommand } : {}
48043
+ };
48044
+ }
48033
48045
  function workerBindingEndpointIdentityFields(previousBinding, createId) {
48034
48046
  if (previousBinding?.agentEndpointId) {
48035
48047
  return { agentEndpointId: previousBinding.agentEndpointId };
@@ -48097,6 +48109,63 @@ async function persistWorkerBindingIdentity(state, input, deps) {
48097
48109
  deps.publish(staged.nextState);
48098
48110
  });
48099
48111
  }
48112
+ function stageWorkerBindingLaunch(state, input, launch) {
48113
+ const binding = state.orchestration.workerBindings[input.workerSession];
48114
+ if (!binding) {
48115
+ return { changed: false };
48116
+ }
48117
+ const snapshot = {
48118
+ ...launch.agentCommand ? { launchAgentCommand: launch.agentCommand } : {},
48119
+ ...launch.acpxAgent ? { launchAcpxAgent: launch.acpxAgent } : {},
48120
+ ...launch.rawCommand ? { launchRawCommand: launch.rawCommand } : {}
48121
+ };
48122
+ if (binding.launchAgentCommand === snapshot.launchAgentCommand && binding.launchAcpxAgent === snapshot.launchAcpxAgent && binding.launchRawCommand === snapshot.launchRawCommand) {
48123
+ return { changed: false };
48124
+ }
48125
+ const nextState = structuredClone(state);
48126
+ const nextBinding = nextState.orchestration.workerBindings[input.workerSession];
48127
+ if (!nextBinding) {
48128
+ return { changed: false };
48129
+ }
48130
+ delete nextBinding.launchAgentCommand;
48131
+ delete nextBinding.launchAcpxAgent;
48132
+ delete nextBinding.launchRawCommand;
48133
+ Object.assign(nextBinding, snapshot);
48134
+ return { changed: true, nextState };
48135
+ }
48136
+ function historicalWorkerReleaseTarget(current, snapshot) {
48137
+ const historicalCommand = snapshot?.launchAgentCommand ?? snapshot?.launchRawCommand;
48138
+ if (!historicalCommand) {
48139
+ return;
48140
+ }
48141
+ if (historicalCommand === current.agentCommand || historicalCommand === current.rawCommand) {
48142
+ return;
48143
+ }
48144
+ return { agentCommand: historicalCommand };
48145
+ }
48146
+ async function releaseWorkerOwnerSessions(removeSession, current, snapshot) {
48147
+ await removeSession(current);
48148
+ const historical = historicalWorkerReleaseTarget(current, snapshot);
48149
+ if (historical) {
48150
+ await removeSession({
48151
+ ...current,
48152
+ agentCommand: historical.agentCommand,
48153
+ acpxAgent: undefined,
48154
+ rawCommand: undefined,
48155
+ agentArgv: undefined
48156
+ });
48157
+ }
48158
+ }
48159
+ async function persistWorkerBindingLaunch(state, input, launch, deps) {
48160
+ await deps.runExclusive(async () => {
48161
+ const staged = stageWorkerBindingLaunch(state, input, launch);
48162
+ if (!staged.changed) {
48163
+ return;
48164
+ }
48165
+ await deps.saveNow(staged.nextState);
48166
+ deps.publish(staged.nextState);
48167
+ });
48168
+ }
48100
48169
  function resolveWorkerAgentLaunch(agent3, transport, binding) {
48101
48170
  return resolveConfiguredAgentLaunch(agent3, transport, {
48102
48171
  guardAcpOutput: shouldGuardWorkerAcpOutput(binding)
@@ -48202,6 +48271,7 @@ class HumanDelegationService {
48202
48271
  targetAgent: input.targetAgent,
48203
48272
  ...role ? { role } : {},
48204
48273
  ...workerBindingGuardFields(previousBinding),
48274
+ ...workerBindingLaunchFields(previousBinding),
48205
48275
  ...workerBindingEndpointIdentityFields(previousBinding, this.deps.createAgentEndpointId),
48206
48276
  ...identity,
48207
48277
  ...input.parallel ? { ephemeral: true } : {}
@@ -48279,6 +48349,7 @@ class HumanDelegationService {
48279
48349
  targetAgent: input.targetAgent,
48280
48350
  role,
48281
48351
  ...workerBindingGuardFields(previousBinding),
48352
+ ...workerBindingLaunchFields(previousBinding),
48282
48353
  ...workerBindingEndpointIdentityFields(previousBinding, this.deps.createAgentEndpointId),
48283
48354
  ...workerBindingEngineFields(previousBinding),
48284
48355
  ...input.parallel ? { ephemeral: true } : {}
@@ -49928,6 +49999,7 @@ class RpcDelegationService {
49928
49999
  targetAgent: input.targetAgent,
49929
50000
  role: preflight.role,
49930
50001
  ...workerBindingGuardFields(previousBinding),
50002
+ ...workerBindingLaunchFields(previousBinding),
49931
50003
  ...workerBindingEndpointIdentityFields(previousBinding, this.deps.createAgentEndpointId),
49932
50004
  ...identity,
49933
50005
  ...input.parallel ? { ephemeral: true } : {}
@@ -50430,6 +50502,7 @@ class TaskApprovalService {
50430
50502
  targetAgent: currentTask.targetAgent,
50431
50503
  ...currentTask.role ? { role: currentTask.role } : {},
50432
50504
  ...workerBindingGuardFields(previousBinding),
50505
+ ...workerBindingLaunchFields(previousBinding),
50433
50506
  ...workerBindingEndpointIdentityFields(previousBinding, this.deps.createAgentEndpointId),
50434
50507
  ...identity,
50435
50508
  ...currentTask.ephemeralWorkerSession ? { ephemeral: true } : {}
@@ -50485,6 +50558,7 @@ class TaskApprovalService {
50485
50558
  targetAgent: task.targetAgent,
50486
50559
  role: task.role,
50487
50560
  ...workerBindingGuardFields(previousBinding),
50561
+ ...workerBindingLaunchFields(previousBinding),
50488
50562
  ...workerBindingEndpointIdentityFields(previousBinding, this.deps.createAgentEndpointId),
50489
50563
  ...workerBindingEngineFields(previousBinding),
50490
50564
  ...task.ephemeralWorkerSession ? { ephemeral: true } : {}
@@ -51564,6 +51638,7 @@ class WorkerSessionManager {
51564
51638
  ...task.role ? { role: task.role } : {},
51565
51639
  ephemeral: true,
51566
51640
  guardAcpOutput: true,
51641
+ ...workerBindingLaunchFields(previousBinding),
51567
51642
  ...identity
51568
51643
  };
51569
51644
  await this.deps.saveState(state);
@@ -54680,6 +54755,43 @@ class SessionService {
54680
54755
  }
54681
54756
  return resolved;
54682
54757
  }
54758
+ listReapTargets() {
54759
+ const seen = new Set;
54760
+ const targets = [];
54761
+ const push = (target) => {
54762
+ const key = JSON.stringify([target.agent, target.agentCommand ?? null, target.cwd, target.transportSession]);
54763
+ if (seen.has(key)) {
54764
+ return;
54765
+ }
54766
+ seen.add(key);
54767
+ targets.push(target);
54768
+ };
54769
+ for (const session3 of Object.values(this.state.sessions)) {
54770
+ let candidate;
54771
+ try {
54772
+ candidate = this.toResolvedSession(session3);
54773
+ } catch {
54774
+ continue;
54775
+ }
54776
+ push({
54777
+ agent: candidate.agent,
54778
+ ...candidate.agentCommand ? { agentCommand: candidate.agentCommand } : {},
54779
+ ...candidate.acpxAgent ? { acpxAgent: candidate.acpxAgent } : {},
54780
+ ...candidate.rawCommand ? { rawCommand: candidate.rawCommand } : {},
54781
+ cwd: candidate.cwd,
54782
+ transportSession: candidate.transportSession
54783
+ });
54784
+ if (session3.transport_agent_command) {
54785
+ push({
54786
+ agent: candidate.agent,
54787
+ agentCommand: session3.transport_agent_command,
54788
+ cwd: candidate.cwd,
54789
+ transportSession: candidate.transportSession
54790
+ });
54791
+ }
54792
+ }
54793
+ return targets;
54794
+ }
54683
54795
  listAllResolvedLogicalSessions() {
54684
54796
  const resolved = [];
54685
54797
  for (const session3 of Object.values(this.state.sessions)) {
@@ -60658,14 +60770,7 @@ var init_queue_owner_reaper = __esm(() => {
60658
60770
  // src/transport/collect-reap-targets.ts
60659
60771
  function collectReapTargets(sessions, orchestration3, config4) {
60660
60772
  return [
60661
- ...sessions.listAllResolvedSessions().map((session3) => ({
60662
- agent: session3.agent,
60663
- ...session3.agentCommand ? { agentCommand: session3.agentCommand } : {},
60664
- ...session3.acpxAgent ? { acpxAgent: session3.acpxAgent } : {},
60665
- ...session3.rawCommand ? { rawCommand: session3.rawCommand } : {},
60666
- cwd: session3.cwd,
60667
- transportSession: session3.transportSession
60668
- })),
60773
+ ...sessions.listReapTargets(),
60669
60774
  ...workerBindingReapTargets(orchestration3, config4)
60670
60775
  ];
60671
60776
  }
@@ -60689,6 +60794,15 @@ function workerBindingReapTargets(orchestration3, config4) {
60689
60794
  cwd,
60690
60795
  transportSession: workerSession
60691
60796
  });
60797
+ const historicalCommand = binding.launchAgentCommand ?? binding.launchRawCommand;
60798
+ if (historicalCommand && historicalCommand !== launch.agentCommand && historicalCommand !== launch.rawCommand) {
60799
+ targets.push({
60800
+ agent: binding.targetAgent,
60801
+ agentCommand: historicalCommand,
60802
+ cwd,
60803
+ transportSession: workerSession
60804
+ });
60805
+ }
60692
60806
  }
60693
60807
  return targets;
60694
60808
  }
@@ -65203,6 +65317,15 @@ async function buildApp(paths, deps = {}) {
65203
65317
  publish: (nextState) => replaceRuntimeState(state, nextState),
65204
65318
  runExclusive: (critical) => stateMutex.run(critical)
65205
65319
  });
65320
+ const agentConfig = config4.agents[input.targetAgent];
65321
+ if (agentConfig) {
65322
+ const launch = resolveWorkerAgentLaunch(agentConfig, config4.transport, state.orchestration.workerBindings[input.workerSession]);
65323
+ await persistWorkerBindingLaunch(state, { workerSession: input.workerSession }, launch, {
65324
+ saveNow: (nextState) => debouncedStateStore.saveNow(nextState),
65325
+ publish: (nextState) => replaceRuntimeState(state, nextState),
65326
+ runExclusive: (critical) => stateMutex.run(critical)
65327
+ });
65328
+ }
65206
65329
  };
65207
65330
  const resolveWorkerRuntimeSession = (input, bindingSnapshot) => {
65208
65331
  const binding = bindingSnapshot ?? state.orchestration.workerBindings[input.workerSession];
@@ -65437,7 +65560,10 @@ async function buildApp(paths, deps = {}) {
65437
65560
  workspace: workspace3,
65438
65561
  cwd,
65439
65562
  logicalSessionId,
65440
- transportEngine
65563
+ transportEngine,
65564
+ launchAgentCommand,
65565
+ launchAcpxAgent,
65566
+ launchRawCommand
65441
65567
  }) => {
65442
65568
  const session3 = resolveWorkerRuntimeSession({
65443
65569
  workerSession,
@@ -65457,7 +65583,7 @@ async function buildApp(paths, deps = {}) {
65457
65583
  if (!transport.removeSession) {
65458
65584
  throw new Error(`transport cannot converge worker "${workerSession}": no removeSession operation`);
65459
65585
  }
65460
- await transport.removeSession(session3);
65586
+ await releaseWorkerOwnerSessions((candidate) => transport.removeSession(candidate), session3, { launchAgentCommand, launchAcpxAgent, launchRawCommand });
65461
65587
  },
65462
65588
  resumeWorkerTask: async ({
65463
65589
  taskId,
@@ -0,0 +1,28 @@
1
+ export declare const RUNTIME_CONSUMER_LOCK_FILE = "runtime-consumer.lock.json";
2
+ export interface DaemonPaths {
3
+ runtimeDir: string;
4
+ pidFile: string;
5
+ statusFile: string;
6
+ stdoutLog: string;
7
+ stderrLog: string;
8
+ appLog: string;
9
+ }
10
+ interface ResolveDaemonPathsOptions {
11
+ home: string;
12
+ runtimeDir?: string;
13
+ configPath?: string;
14
+ }
15
+ export declare function resolveDaemonPaths(options: ResolveDaemonPathsOptions): DaemonPaths;
16
+ export declare function resolveRuntimeDirFromConfigPath(configPath: string): string;
17
+ /** Stable metadata path for the OS-held, channel-independent runtime lock. */
18
+ export declare function resolveRuntimeConsumerLockPath(runtimeDir: string): string;
19
+ export declare function resolveDaemonOrchestrationSocketPath(runtimeDir: string, platform?: NodeJS.Platform): string;
20
+ /**
21
+ * Liveness probe via signal 0: returns true when the pid can be signalled.
22
+ * EPERM also reads as ALIVE — it means the signal was denied but the process
23
+ * exists (typically owned by another user). Doctor uses this to gate
24
+ * state-mutating repairs, where the unsafe direction is reporting a live
25
+ * process as dead; only a definitive "no such process" (ESRCH) reads as dead.
26
+ */
27
+ export declare function isProcessAlive(pid: number): boolean;
28
+ export {};
@@ -0,0 +1,86 @@
1
+ import type { CancelTaskInput, CoordinatorRequestHumanInputResult, CoordinatorTaskQuestionRef, OrchestrationTaskFilter, RecordWorkerReplyInput, RegisterExternalCoordinatorInput, RequestDelegateRpcInput, RequestDelegateRpcResult, WatchTaskInput, WatchTaskResult, WorkerRaiseQuestionInput } from "./orchestration-service";
2
+ import type { ExternalCoordinatorRecord, OrchestrationGroupRecord, OrchestrationTaskRecord } from "./orchestration-types";
3
+ import type { ScheduledCreateFromRouteInput } from "../scheduled/scheduled-route-create";
4
+ import type { ScheduledCancelFromRouteInput, ScheduledListFromRouteInput } from "../scheduled/scheduled-route-manage";
5
+ import type { ScheduledTaskRecord } from "../scheduled/scheduled-types";
6
+ import type { AgentMessagingErrorCode } from "./agent-messaging-error";
7
+ export type OrchestrationRpcMethod = "agent.list" | "agent.send" | "coordinator.register_external" | "delegate.request" | "task.get" | "task.list" | "task.watch" | "task.approve" | "task.cancel" | "worker.reply" | "worker.raise_question" | "coordinator.answer_question" | "coordinator.retract_answer" | "coordinator.request_human_input" | "coordinator.review_contested_result" | "scheduled.create" | "scheduled.list" | "scheduled.cancel" | "group.new";
8
+ export interface OrchestrationIpcEndpoint {
9
+ kind: "unix" | "named-pipe";
10
+ path: string;
11
+ }
12
+ export interface OrchestrationRpcRequest {
13
+ id: string;
14
+ method: OrchestrationRpcMethod;
15
+ params: Record<string, unknown>;
16
+ }
17
+ export interface OrchestrationRpcSuccessResponse<Result = unknown> {
18
+ id: string;
19
+ ok: true;
20
+ result: Result;
21
+ }
22
+ export type OrchestrationRpcErrorCode = "ORCHESTRATION_INVALID_REQUEST" | "ORCHESTRATION_INTERNAL_ERROR" | AgentMessagingErrorCode;
23
+ export interface OrchestrationRpcErrorResponse {
24
+ id: string;
25
+ ok: false;
26
+ error: {
27
+ code: OrchestrationRpcErrorCode;
28
+ message: string;
29
+ };
30
+ }
31
+ export type OrchestrationRpcResponse<Result = unknown> = OrchestrationRpcSuccessResponse<Result> | OrchestrationRpcErrorResponse;
32
+ export interface OrchestrationRpcHandlers {
33
+ registerExternalCoordinator: (input: RegisterExternalCoordinatorInput) => Promise<ExternalCoordinatorRecord>;
34
+ requestDelegate: (input: RequestDelegateRpcInput) => Promise<RequestDelegateRpcResult>;
35
+ getTask: (taskId: string) => Promise<OrchestrationTaskRecord | null>;
36
+ listTasks: (filter?: OrchestrationTaskFilter) => Promise<OrchestrationTaskRecord[]>;
37
+ watchTask: (input: WatchTaskInput) => Promise<WatchTaskResult>;
38
+ approveTask: (input: {
39
+ taskId: string;
40
+ coordinatorSession: string;
41
+ }) => Promise<OrchestrationTaskRecord>;
42
+ cancelTask: (input: CancelTaskInput) => Promise<OrchestrationTaskRecord>;
43
+ recordWorkerReply: (input: RecordWorkerReplyInput) => Promise<OrchestrationTaskRecord>;
44
+ workerRaiseQuestion: (input: WorkerRaiseQuestionInput) => Promise<{
45
+ taskId: string;
46
+ questionId: string;
47
+ status: "blocked";
48
+ }>;
49
+ coordinatorAnswerQuestion: (input: {
50
+ coordinatorSession: string;
51
+ taskId: string;
52
+ questionId: string;
53
+ answer: string;
54
+ }) => Promise<OrchestrationTaskRecord>;
55
+ coordinatorRetractAnswer: (input: {
56
+ coordinatorSession: string;
57
+ taskId: string;
58
+ questionId: string;
59
+ }) => Promise<OrchestrationTaskRecord>;
60
+ coordinatorRequestHumanInput: (input: {
61
+ coordinatorSession: string;
62
+ taskQuestions: CoordinatorTaskQuestionRef[];
63
+ promptText: string;
64
+ expectedActivePackageId?: string;
65
+ }) => Promise<CoordinatorRequestHumanInputResult>;
66
+ coordinatorReviewContestedResult: (input: {
67
+ coordinatorSession: string;
68
+ taskId: string;
69
+ reviewId: string;
70
+ decision: "accept" | "discard";
71
+ }) => Promise<OrchestrationTaskRecord>;
72
+ createGroup: (input: {
73
+ coordinatorSession: string;
74
+ title: string;
75
+ }) => Promise<OrchestrationGroupRecord>;
76
+ createScheduledTaskFromRoute?: (input: ScheduledCreateFromRouteInput) => Promise<ScheduledTaskRecord>;
77
+ listScheduledTasksFromRoute?: (input: ScheduledListFromRouteInput) => Promise<ScheduledTaskRecord[]>;
78
+ cancelScheduledTaskFromRoute?: (input: ScheduledCancelFromRouteInput) => Promise<{
79
+ id: string;
80
+ cancelled: boolean;
81
+ }>;
82
+ }
83
+ export declare function resolveOrchestrationEndpoint(runtimeDir: string, platform?: NodeJS.Platform): OrchestrationIpcEndpoint;
84
+ export declare function createOrchestrationEndpoint(path: string, platform?: NodeJS.Platform): OrchestrationIpcEndpoint;
85
+ export declare function encodeOrchestrationRpcRequest(request: OrchestrationRpcRequest): string;
86
+ export declare function encodeOrchestrationRpcResponse(response: OrchestrationRpcResponse): string;
@@ -187,6 +187,16 @@ export interface OrchestrationServiceDeps {
187
187
  role?: string;
188
188
  logicalSessionId: string;
189
189
  transportEngine: SessionTransportEngine;
190
+ /**
191
+ * Last-dispatched launch snapshot (claim-time copy). The CLI teardown
192
+ * converges this identity alongside the current resolution: after a
193
+ * managed-pin/config change the current identity cannot locate the
194
+ * previous owner, and the binding is deleted right after release
195
+ * returns — this is the last chance to close the old record.
196
+ */
197
+ launchAgentCommand?: string;
198
+ launchAcpxAgent?: string;
199
+ launchRawCommand?: string;
190
200
  }) => Promise<void>;
191
201
  wakeCoordinatorSession?: (request: WakeCoordinatorRequest) => Promise<void>;
192
202
  deliverCoordinatorMessage?: (request: DeliverCoordinatorMessageRequest) => Promise<FrozenCoordinatorDeliveryRoute | void>;
@@ -116,6 +116,17 @@ export interface WorkerBindingRecord {
116
116
  coordinatorSession: string;
117
117
  workspace: string;
118
118
  cwd?: string;
119
+ /**
120
+ * Launch identity last used to dispatch this worker (persist-before-owner,
121
+ * refreshed on every dispatch). Queue-owner reaping needs the PREVIOUS
122
+ * identity after a managed-pin/config change recomputes the current launch:
123
+ * without it a crashed-then-upgraded daemon cannot locate the old owner.
124
+ * Absent on bindings created before this field existed (reap falls back to
125
+ * the current resolution) and on bare-driver launches (no command to record).
126
+ */
127
+ launchAgentCommand?: string;
128
+ launchAcpxAgent?: string;
129
+ launchRawCommand?: string;
119
130
  targetAgent: string;
120
131
  role?: string;
121
132
  /** True for ephemeral parallel-slot sessions; excluded from findReusableWorkerSession matching. */
@@ -24,6 +24,9 @@ export interface WorkerBindingRetirementEnv {
24
24
  role?: string;
25
25
  logicalSessionId: string;
26
26
  transportEngine: SessionTransportEngine;
27
+ launchAgentCommand?: string;
28
+ launchAcpxAgent?: string;
29
+ launchRawCommand?: string;
27
30
  }) => Promise<void>) | undefined;
28
31
  isTerminalStatus: (status: OrchestrationTaskStatus) => boolean;
29
32
  /**
@@ -1,6 +1,7 @@
1
1
  import type { AppState, SessionTransportEngine } from "../state/types";
2
2
  import type { AgentConfig, TransportConfig } from "../config/types";
3
3
  import type { WorkerBindingRecord } from "./orchestration-types";
4
+ import type { ResolvedSession } from "../transport/types";
4
5
  /**
5
6
  * A missing binding means this is a first launch and should opt into the new
6
7
  * guard. A persisted binding without the marker predates the rollout and must
@@ -12,6 +13,21 @@ export declare function shouldGuardWorkerAcpOutput(binding: Pick<WorkerBindingRe
12
13
  export declare function workerBindingGuardFields(previousBinding: Pick<WorkerBindingRecord, "guardAcpOutput"> | undefined): {
13
14
  guardAcpOutput?: true;
14
15
  };
16
+ /**
17
+ * Preserve a reusable worker's last-dispatched launch snapshot across
18
+ * whole-object binding reconstructions (reuse shells, post-ensure rebuilds).
19
+ * Every site that rebuilds an existing binding MUST spread this alongside
20
+ * the guard/endpoint/engine helpers: dropping the snapshot deletes the only
21
+ * evidence the reaper and retirement convergence have for the previous
22
+ * owner's identity after a managed-pin/config change. A crash between the
23
+ * rebuild's saveState and the next launch snapshot would otherwise orphan
24
+ * the old owner permanently (ttl=0).
25
+ */
26
+ export declare function workerBindingLaunchFields(previousBinding: Pick<WorkerBindingRecord, "launchAgentCommand" | "launchAcpxAgent" | "launchRawCommand"> | undefined): {
27
+ launchAgentCommand?: string;
28
+ launchAcpxAgent?: string;
29
+ launchRawCommand?: string;
30
+ };
15
31
  /** Preserve a reusable worker's endpoint identity; mint one for a new binding. */
16
32
  export declare function workerBindingEndpointIdentityFields(previousBinding: Pick<WorkerBindingRecord, "agentEndpointId"> | undefined, createId?: () => string): {
17
33
  agentEndpointId?: string;
@@ -128,4 +144,56 @@ export declare function persistWorkerBindingIdentity(state: AppState, input: {
128
144
  targetAgent: string;
129
145
  workspace: string;
130
146
  }, deps: WorkerBindingIdentityPersistence): Promise<void>;
147
+ /** Launch identity resolved for one worker dispatch (reap snapshot source). */
148
+ export interface WorkerBindingLaunchSnapshot {
149
+ agentCommand?: string;
150
+ acpxAgent?: string;
151
+ rawCommand?: string;
152
+ }
153
+ /**
154
+ * Stage a worker binding's launch snapshot onto a copy-on-write clone.
155
+ * Unlike the immutable LID/engine identity above, the snapshot is refreshed
156
+ * whenever the resolved launch differs, so a later restart can reap the
157
+ * previous owner after a managed-pin/config change. Returns
158
+ * `{ changed: false }` when the binding is missing or the snapshot already
159
+ * matches (steady-state dispatches skip the save). The caller must
160
+ * `saveNow(nextState)` and publish ONLY on success, same G11 contract as
161
+ * the identity staging above. Stale keys the new launch no longer carries
162
+ * are cleared so they cannot reap a phantom identity forever.
163
+ */
164
+ export declare function stageWorkerBindingLaunch(state: AppState, input: {
165
+ workerSession: string;
166
+ }, launch: WorkerBindingLaunchSnapshot): {
167
+ changed: false;
168
+ } | {
169
+ changed: true;
170
+ nextState: AppState;
171
+ };
172
+ /**
173
+ * The extra owner identity a worker retirement must converge besides the
174
+ * current resolution: the last-dispatched launch snapshot when it names a
175
+ * different acpx record. Command-only (no alias): the transport passes it as
176
+ * `--agent`, which acpx matches verbatim against the old record. Returns
177
+ * undefined when there is no snapshot or it already matches the current
178
+ * resolution (same pin, sticky custom, or bare launch).
179
+ */
180
+ export declare function historicalWorkerReleaseTarget(current: Pick<ResolvedSession, "agentCommand" | "rawCommand">, snapshot: Pick<WorkerBindingRecord, "launchAgentCommand" | "launchAcpxAgent" | "launchRawCommand"> | undefined): {
181
+ agentCommand: string;
182
+ } | undefined;
183
+ /**
184
+ * Converge a retiring worker's engine-side owner(s): always the current
185
+ * resolution, then the historical snapshot target when it names a different
186
+ * record. A throw from either step retains the binding (fail-closed retry),
187
+ * so the old owner is never stranded by a deleted binding. Missing records
188
+ * read as success on both steps (transport contract).
189
+ */
190
+ export declare function releaseWorkerOwnerSessions(removeSession: (session: ResolvedSession) => Promise<void>, current: ResolvedSession, snapshot: Pick<WorkerBindingRecord, "launchAgentCommand" | "launchAcpxAgent" | "launchRawCommand"> | undefined): Promise<void>;
191
+ /**
192
+ * Persist a worker binding's launch snapshot as one atomic transaction on the
193
+ * shared state mutex (stage on a clone, saveNow, then publish to live state
194
+ * ONLY on success). Callers must run outside the non-reentrant mutex.
195
+ */
196
+ export declare function persistWorkerBindingLaunch(state: AppState, input: {
197
+ workerSession: string;
198
+ }, launch: WorkerBindingLaunchSnapshot, deps: Pick<WorkerBindingIdentityPersistence, "saveNow" | "publish" | "runExclusive">): Promise<void>;
131
199
  export declare function resolveWorkerAgentLaunch(agent: Pick<AgentConfig, "driver" | "command" | "argv">, transport: Pick<TransportConfig, "preferLocalAgents" | "adapterVersions" | "adapterRegistry"> | undefined, binding: Pick<WorkerBindingRecord, "guardAcpOutput"> | undefined): import("../config/agent-launch").AgentLaunchSpec;
@@ -0,0 +1,5 @@
1
+ export interface SpawnCommandSpec {
2
+ command: string;
3
+ args: string[];
4
+ }
5
+ export declare function resolveSpawnCommand(command: string, args: string[]): SpawnCommandSpec;
@@ -0,0 +1,25 @@
1
+ import type { AppConfig } from "../config/types";
2
+ import type { AppState } from "../state/types";
3
+ import type { ResolvedSession } from "../transport/types";
4
+ import type { CreateScheduledTaskInput } from "./scheduled-service";
5
+ import type { ScheduledSessionMode, ScheduledTaskRecord } from "./scheduled-types";
6
+ export interface ScheduledCreateFromRouteInput {
7
+ coordinatorSession: string;
8
+ timeText: string;
9
+ message: string;
10
+ mode?: ScheduledSessionMode;
11
+ }
12
+ export interface ScheduledCreateFromRouteDeps {
13
+ state: Pick<AppState, "orchestration">;
14
+ config: Pick<AppConfig, "later">;
15
+ sessions: {
16
+ getSession: (alias: string) => Promise<ResolvedSession | null>;
17
+ getPreferredSessionForTransport: (transportSession: string) => Promise<ResolvedSession | null>;
18
+ };
19
+ scheduled: {
20
+ createTask: (input: CreateScheduledTaskInput) => Promise<ScheduledTaskRecord>;
21
+ };
22
+ supportsScheduledMessages?: (chatKey: string) => boolean;
23
+ now?: () => Date;
24
+ }
25
+ export declare function createScheduledTaskFromRoute(input: ScheduledCreateFromRouteInput, deps: ScheduledCreateFromRouteDeps): Promise<ScheduledTaskRecord>;
@@ -0,0 +1,26 @@
1
+ import type { AppState } from "../state/types";
2
+ import type { ScheduledTaskRecord } from "./scheduled-types";
3
+ export interface ScheduledListFromRouteInput {
4
+ coordinatorSession: string;
5
+ }
6
+ export interface ScheduledListFromRouteDeps {
7
+ state: Pick<AppState, "orchestration">;
8
+ scheduled: {
9
+ listPending: (chatKey: string) => ScheduledTaskRecord[];
10
+ };
11
+ }
12
+ export interface ScheduledCancelFromRouteInput {
13
+ coordinatorSession: string;
14
+ id: string;
15
+ }
16
+ export interface ScheduledCancelFromRouteDeps {
17
+ state: Pick<AppState, "orchestration">;
18
+ scheduled: {
19
+ cancelPending: (id: string, chatKey: string) => Promise<boolean>;
20
+ };
21
+ }
22
+ export declare function listScheduledTasksFromRoute(input: ScheduledListFromRouteInput, deps: ScheduledListFromRouteDeps): Promise<ScheduledTaskRecord[]>;
23
+ export declare function cancelScheduledTaskFromRoute(input: ScheduledCancelFromRouteInput, deps: ScheduledCancelFromRouteDeps): Promise<{
24
+ id: string;
25
+ cancelled: boolean;
26
+ }>;
@@ -4,6 +4,7 @@ import type { StateStore } from "../state/state-store";
4
4
  import type { AppState, BackgroundResult, LogicalSession, SessionTransportEngine } from "../state/types";
5
5
  import type { SessionResourceLifecyclePublishInput } from "./session-resource-catalog";
6
6
  import type { AgentSession, ResolvedSession } from "../transport/types";
7
+ import type { ReapTarget } from "../transport/queue-owner-reaper";
7
8
  interface SessionListItem {
8
9
  alias: string;
9
10
  internalAlias: string;
@@ -136,6 +137,21 @@ export declare class SessionService {
136
137
  * never throws.
137
138
  */
138
139
  listAllResolvedSessions(): ResolvedSession[];
140
+ /**
141
+ * Warm acpx queue-owner reap targets, including HISTORICAL identities.
142
+ * Derived recorded commands (managed adapter pins, hermes shim, preinstalled
143
+ * adapters) are recomputed from the CURRENT pin on restart, so a current-only
144
+ * target cannot locate an owner spawned under a previous pin: after a crash
145
+ * + pin upgrade, the old owner would survive startup/shutdown reaping, and
146
+ * with `queueOwnerTtlSeconds=0` it would linger forever. The persisted
147
+ * `transport_agent_command` is byte-identical to the acpx record's
148
+ * `agent_command`, so it is emitted command-only (no alias): the reaper
149
+ * passes it as `--agent`, which acpx matches verbatim with no
150
+ * alias-registry round trip. Sticky custom commands resolve to themselves
151
+ * and dedupe away here. Sessions whose agent/workspace are de-registered
152
+ * are skipped, same as listAllResolvedSessions; never throws.
153
+ */
154
+ listReapTargets(): ReapTarget[];
139
155
  /**
140
156
  * One entry per persisted logical session — NO physical dedupe. This is the
141
157
  * recovery catalog for the Runtime durable queue (`runtime-queue/<LID>.json`
@@ -0,0 +1,163 @@
1
+ import type { NonInteractivePermissions, PermissionMode } from "../config/types";
2
+ import { terminateProcessTree } from "../process/terminate-process-tree";
3
+ import { probeWindowsProcessIdentity } from "../process/windows-process-tree";
4
+ export interface AcpxMcpServerSpec {
5
+ name: string;
6
+ type: "stdio";
7
+ command: string;
8
+ args: string[];
9
+ }
10
+ export interface QueueOwnerPayload {
11
+ sessionId: string;
12
+ /** Command selected by the durable adapter transaction for this owner. */
13
+ agentCommand?: string;
14
+ permissionMode: PermissionMode;
15
+ nonInteractivePermissions: NonInteractivePermissions;
16
+ ttlMs: number;
17
+ maxQueueDepth: number;
18
+ promptRetries?: number;
19
+ sessionOptions?: {
20
+ model?: string;
21
+ allowedTools?: string[];
22
+ maxTurns?: number;
23
+ systemPrompt?: string | {
24
+ append: string;
25
+ };
26
+ };
27
+ mcpServers: AcpxMcpServerSpec[];
28
+ }
29
+ export type QueueOwnerSpawner = (command: string, args: string[], options: {
30
+ env: Record<string, string>;
31
+ }) => Promise<number>;
32
+ export type QueueOwnerTerminator = (sessionId: string) => Promise<void>;
33
+ export interface AcpxQueueOwnerLauncherOptions {
34
+ acpxCommand: string;
35
+ xacpxCommand?: string;
36
+ spawnOwner?: QueueOwnerSpawner;
37
+ terminateOwner?: QueueOwnerTerminator;
38
+ /**
39
+ * Liveness check for an existing warm owner. When it reports alive, launch()
40
+ * REUSES the running owner instead of kill+respawn — consecutive turns of a
41
+ * coordinator session share one warm agent instead of cold-starting each turn.
42
+ * Defaults to reading the acpx queue lock pid.
43
+ */
44
+ isOwnerAlive?: (sessionId: string) => Promise<boolean>;
45
+ baseEnv?: NodeJS.ProcessEnv;
46
+ ttlMs?: number;
47
+ maxQueueDepth?: number;
48
+ readOwnerPid?: (sessionId: string) => Promise<number | undefined>;
49
+ probeSpawnedProcess?: (pid: number) => Promise<"alive" | "exited" | "unknown">;
50
+ handshakeTimeoutMs?: number;
51
+ handshakePollMs?: number;
52
+ uuid?: () => string;
53
+ sleep?: (ms: number) => Promise<void>;
54
+ }
55
+ export interface QueueOwnerAdapterContext {
56
+ id: "codex" | "claude";
57
+ sessionKey: string;
58
+ agentCommand: string;
59
+ platform: NodeJS.Platform;
60
+ prepare(intentToken: string): Promise<{
61
+ agentCommand: string;
62
+ generationId?: string;
63
+ }>;
64
+ isGenerationCurrent(generationId: string): Promise<boolean>;
65
+ spawned(intentToken: string): Promise<void>;
66
+ cancel(intentToken: string): Promise<void>;
67
+ settle(input: {
68
+ intentToken: string;
69
+ outcome: "owner-committed" | "launch-failed";
70
+ ownerPid?: number;
71
+ ownerAcpxRecordId?: string;
72
+ }): Promise<void>;
73
+ }
74
+ export interface LaunchQueueOwnerInput {
75
+ acpxRecordId: string;
76
+ coordinatorSession: string;
77
+ sourceHandle?: string;
78
+ permissionMode: PermissionMode;
79
+ nonInteractivePermissions: NonInteractivePermissions;
80
+ /** Session options forwarded to the warm queue owner (e.g. the resolved model id). */
81
+ sessionOptions?: QueueOwnerPayload["sessionOptions"];
82
+ /** Per-agent environment inherited by the queue owner and its ACP adapter. */
83
+ env?: NodeJS.ProcessEnv;
84
+ /** Required whenever agentCommand has the managed preinstall shape. */
85
+ agentCommand?: string;
86
+ adapterContext?: QueueOwnerAdapterContext;
87
+ }
88
+ export declare function buildXacpxMcpServerSpec(input: {
89
+ xacpxCommand: string;
90
+ coordinatorSession: string;
91
+ sourceHandle?: string;
92
+ }): AcpxMcpServerSpec;
93
+ export declare function buildQueueOwnerPayload(input: {
94
+ sessionId: string;
95
+ agentCommand?: string;
96
+ permissionMode: PermissionMode;
97
+ nonInteractivePermissions: NonInteractivePermissions;
98
+ mcpServers: AcpxMcpServerSpec[];
99
+ ttlMs?: number;
100
+ maxQueueDepth?: number;
101
+ promptRetries?: number;
102
+ sessionOptions?: QueueOwnerPayload["sessionOptions"];
103
+ }): QueueOwnerPayload;
104
+ export declare class AcpxQueueOwnerLauncher {
105
+ private readonly acpxCommand;
106
+ private readonly xacpxCommand;
107
+ private readonly spawnOwner;
108
+ private readonly terminateOwner;
109
+ private readonly isOwnerAlive;
110
+ private readonly baseEnv;
111
+ private readonly ttlMs?;
112
+ private readonly maxQueueDepth?;
113
+ private readonly readOwnerPid;
114
+ private readonly probeSpawnedProcess;
115
+ private readonly handshakeTimeoutMs;
116
+ private readonly handshakePollMs;
117
+ private readonly uuid;
118
+ private readonly sleep;
119
+ /** Per-session mutex: serializes terminate+spawn to prevent concurrent clobbering. */
120
+ private readonly launchLocks;
121
+ /** Fingerprint of the parameters the CURRENT owner was spawned with. */
122
+ private readonly lastLaunchFingerprint;
123
+ constructor(options: AcpxQueueOwnerLauncherOptions);
124
+ cool(acpxRecordId: string): Promise<void>;
125
+ private doCool;
126
+ wouldReuse(input: LaunchQueueOwnerInput): Promise<boolean>;
127
+ launch(input: LaunchQueueOwnerInput): Promise<{
128
+ agentCommand?: string;
129
+ }>;
130
+ private doLaunch;
131
+ private waitForOwnerPid;
132
+ }
133
+ export declare function readQueueOwnerPid(sessionId: string): Promise<number | undefined>;
134
+ export declare function terminateAcpxQueueOwner(sessionId: string): Promise<void>;
135
+ export interface QueueOwnerTerminationDeps {
136
+ platform?: NodeJS.Platform;
137
+ lockPath?: string;
138
+ probeIdentity?: typeof probeWindowsProcessIdentity;
139
+ terminate?: typeof terminateProcessTree;
140
+ }
141
+ export declare function terminateAcpxQueueOwnerWithDeps(sessionId: string, deps: QueueOwnerTerminationDeps): Promise<void>;
142
+ export interface VerifiedOwnerTerminationDeps {
143
+ lockPath?: string;
144
+ /** Defaults to terminateAcpxQueueOwner (a no-op for unverifiable Windows locks). */
145
+ terminate?: () => Promise<void>;
146
+ delay?: (ms: number) => Promise<void>;
147
+ /** Test seam for lock-liveness checks; defaults to fs access(). */
148
+ accessFn?: (path: string) => Promise<void>;
149
+ }
150
+ /**
151
+ * Terminate the warm owner for a session AND prove it is gone before returning.
152
+ * Used before mutating a session record: if the owner cannot be shown to be
153
+ * terminated (the lock still exists), writing the record could race the
154
+ * owner's own writes. Fails closed instead.
155
+ *
156
+ * Note: on Windows terminateAcpxQueueOwner deliberately does not consume the
157
+ * queue lock (no verifiable provenance yet), so a live owner there makes the
158
+ * migration unsafe — callers surface the error and the operator frees the
159
+ * owner (or waits for the daemon's orphan sweep) before retrying.
160
+ */
161
+ export declare function terminateAcpxQueueOwnerVerified(sessionId: string): Promise<void>;
162
+ export declare function terminateAcpxQueueOwnerVerifiedWithDeps(sessionId: string, deps: VerifiedOwnerTerminationDeps): Promise<void>;
163
+ export declare function resolveDefaultXacpxCommand(env: NodeJS.ProcessEnv): string;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * acpx's home dir. Env-first (mirrors acpx, which reads HOME/USERPROFILE at
3
+ * startup) so tests can isolate `~/.acpx` by overriding the env, and so the
4
+ * daemon never disagrees with the acpx child it spawns.
5
+ */
6
+ export declare function resolveAcpxHomeDir(): string;
7
+ export interface DeleteAcpxSessionFilesOptions {
8
+ acpxRecordId: string;
9
+ /** Override for the acpx sessions dir (tests). Defaults to `<home>/.acpx/sessions`. */
10
+ sessionsDir?: string;
11
+ }
12
+ /** Best-effort delete of a single acpx session's on-disk files: the record json and
13
+ * its event-stream artifacts. Mirrors acpx's per-record file layout
14
+ * (`<encodeURIComponent(acpxRecordId)>.json` + `<safeId>.stream.*`). Idempotent —
15
+ * missing files are ignored.
16
+ *
17
+ * Deleting the `.json` record leaves a dangling index.json entry, but acpx rebuilds
18
+ * its index from the surviving `.json` files on its next `sessions` operation, so we
19
+ * do not rewrite the index ourselves.
20
+ *
21
+ * The `.stream.*` unlinks are NOT guaranteed to take effect: acpx keeps a warm
22
+ * queue-owner alive via `--ttl`, so `sessions close` returning does not mean the
23
+ * backing process has released those files. On POSIX the unlink drops the directory
24
+ * entry and the owner keeps writing to the now-unlinked inode until `--ttl` expires
25
+ * (no visible orphan). On Windows the unlink of a still-open file rejects and is
26
+ * swallowed below, leaving the `.stream.*` file as a true orphan. acpx does NOT
27
+ * auto-reclaim such an orphan — index rebuild scans only `.json` files, and
28
+ * `sessions prune` skips stream files unless run with `--include-history` — so a
29
+ * Windows orphan persists until a manual prune. This is an accepted residual risk:
30
+ * the queue-owner is a third-party process we cannot reap or await from here. */
31
+ export declare function deleteAcpxSessionFiles(options: DeleteAcpxSessionFilesOptions): Promise<void>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Minimal session identity needed to locate an acpx queue owner: enough to run
3
+ * `acpx sessions show <name>` and resolve the session's stable record id, which
4
+ * is the key acpx uses for the queue lock at `~/.acpx/queues/<hash>.lock`.
5
+ */
6
+ export interface ReapTarget {
7
+ agent: string;
8
+ agentCommand?: string;
9
+ /** Positional acpx agent (overlay alias or bare driver) for structured launches. */
10
+ acpxAgent?: string;
11
+ /** Unix-only legacy raw override passed as acpx `--agent`. */
12
+ rawCommand?: string;
13
+ cwd: string;
14
+ transportSession: string;
15
+ }
16
+ export interface ReapQueueOwnersDeps {
17
+ /** Resolve a target to its acpx record id (queue key), or null when unknown. */
18
+ resolveRecordId?: (acpxCommand: string, target: ReapTarget) => Promise<string | null>;
19
+ /** Terminate the queue owner process for a record id (kills process, keeps session). */
20
+ terminate?: (acpxRecordId: string) => Promise<void>;
21
+ /** Overall budget for the whole sweep; on expiry we return what finished. */
22
+ timeoutMs?: number;
23
+ platform?: NodeJS.Platform;
24
+ onError?: (target: ReapTarget, error: unknown) => void;
25
+ }
26
+ export declare class LegacyOwnerUnverifiableError extends Error {
27
+ readonly code = "legacy-owner-unverifiable";
28
+ constructor();
29
+ }
30
+ /**
31
+ * Terminate the warm acpx queue owner processes for the given sessions on daemon
32
+ * stop. Each owner holds a live ACP agent kept warm by `--ttl`; without this they
33
+ * would linger until the TTL expires (or forever when ttl=0) after the daemon is
34
+ * gone. This kills only the owner process tree — it does NOT close the acpx session
35
+ * (no `closed` flag, no metadata change), so sessions resume normally on next start.
36
+ *
37
+ * Best-effort by design: per-target failures are swallowed and the whole sweep is
38
+ * bounded by `timeoutMs`. If resolution fails or times out, the owner simply lingers
39
+ * until its TTL — i.e. the pre-existing behaviour, never a regression or a hang.
40
+ */
41
+ export declare function reapQueueOwners(acpxCommand: string, targets: ReapTarget[], deps?: ReapQueueOwnersDeps): Promise<{
42
+ terminated: number;
43
+ attempted: number;
44
+ }>;
45
+ export declare function parseRecordId(stdout: string): string | null;
@@ -0,0 +1 @@
1
+ export declare function settleWithinTimeout(work: Promise<unknown>, timeoutMs: number): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganglion/xacpx",
3
- "version": "0.24.3-beta.1",
3
+ "version": "0.24.4-beta.0",
4
4
  "description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",