@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.
- package/dist/adapters/adapter-catalog.d.ts +2 -2
- package/dist/bridge/bridge-main.js +2 -2
- package/dist/bridge/engine/runtime/runtime-worker-main.js +2 -2
- package/dist/cli.js +140 -14
- package/dist/daemon/daemon-files.d.ts +28 -0
- package/dist/orchestration/orchestration-ipc.d.ts +86 -0
- package/dist/orchestration/orchestration-service.d.ts +10 -0
- package/dist/orchestration/orchestration-types.d.ts +11 -0
- package/dist/orchestration/worker-binding-retirement.d.ts +3 -0
- package/dist/orchestration/worker-launch.d.ts +68 -0
- package/dist/process/spawn-command.d.ts +5 -0
- package/dist/scheduled/scheduled-route-create.d.ts +25 -0
- package/dist/scheduled/scheduled-route-manage.d.ts +26 -0
- package/dist/sessions/session-service.d.ts +16 -0
- package/dist/transport/acpx-queue-owner-launcher.d.ts +163 -0
- package/dist/transport/acpx-session-files.d.ts +31 -0
- package/dist/transport/queue-owner-reaper.d.ts +45 -0
- package/dist/util/async.d.ts +1 -0
- package/package.json +1 -1
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,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>;
|