@ganglion/xacpx 0.17.0 → 0.18.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.
|
@@ -2197,7 +2197,7 @@ function resolveClaudeConfigDir(env, homeDir, platform) {
|
|
|
2197
2197
|
function installSettingsProfile(env, sourceConfigDir, policy, settings, options, platform) {
|
|
2198
2198
|
const serialized = `${JSON.stringify(settings, null, 2)}
|
|
2199
2199
|
`;
|
|
2200
|
-
const digest = createHash("sha256").update(sourceConfigDir).update("\x00").update(policy).update("\x00").update(serialized).digest("hex").slice(0, 20);
|
|
2200
|
+
const digest = createHash("sha256").update(sourceConfigDir).update("\x00").update(policy).update("\x00").update(CLAUDE_SETTINGS_PROFILE_LAYOUT_VERSION).update("\x00").update(serialized).digest("hex").slice(0, 20);
|
|
2201
2201
|
const profileDir = join(options.profileRoot ?? join(tmpdir(), "xacpx-claude-profiles"), digest);
|
|
2202
2202
|
const settingsPath = join(profileDir, "settings.json");
|
|
2203
2203
|
(options.writeProfile ?? writePrivateFileSync)(settingsPath, serialized);
|
|
@@ -2205,7 +2205,7 @@ function installSettingsProfile(env, sourceConfigDir, policy, settings, options,
|
|
|
2205
2205
|
setEnvironmentValue(env, "CLAUDE_CONFIG_DIR", profileDir, platform);
|
|
2206
2206
|
}
|
|
2207
2207
|
function linkClaudeSessionState(sourceConfigDir, profileDir, platform) {
|
|
2208
|
-
for (const name of
|
|
2208
|
+
for (const name of CLAUDE_PERSISTENT_SESSION_STATE_DIRS) {
|
|
2209
2209
|
const source = join(sourceConfigDir, name);
|
|
2210
2210
|
mkdirSync2(source, { recursive: true });
|
|
2211
2211
|
const target = join(profileDir, name);
|
|
@@ -2302,20 +2302,18 @@ function setEnvironmentValue(env, name, value, platform) {
|
|
|
2302
2302
|
deleteEnvironmentValue(env, name, platform);
|
|
2303
2303
|
env[name] = value;
|
|
2304
2304
|
}
|
|
2305
|
-
var DEFAULT_CLAUDE_SETTINGS_POLICY = "provider-only",
|
|
2305
|
+
var DEFAULT_CLAUDE_SETTINGS_POLICY = "provider-only", CLAUDE_SETTINGS_PROFILE_LAYOUT_VERSION = "persistent-state-links-v1", CLAUDE_PERSISTENT_SESSION_STATE_DIRS;
|
|
2306
2306
|
var init_claude_settings_policy = __esm(() => {
|
|
2307
2307
|
init_private_file();
|
|
2308
|
-
|
|
2308
|
+
CLAUDE_PERSISTENT_SESSION_STATE_DIRS = [
|
|
2309
2309
|
"projects",
|
|
2310
2310
|
"file-history",
|
|
2311
2311
|
"plans",
|
|
2312
2312
|
"todos",
|
|
2313
|
-
"session-env",
|
|
2314
2313
|
"tasks",
|
|
2315
2314
|
"teams",
|
|
2316
2315
|
"sessions",
|
|
2317
|
-
"transcripts"
|
|
2318
|
-
"shell-snapshots"
|
|
2316
|
+
"transcripts"
|
|
2319
2317
|
];
|
|
2320
2318
|
});
|
|
2321
2319
|
|
|
@@ -5700,6 +5698,18 @@ function createDefaultQueueOwnerTerminator(_acpxCommand) {
|
|
|
5700
5698
|
await terminateAcpxQueueOwner(sessionId);
|
|
5701
5699
|
};
|
|
5702
5700
|
}
|
|
5701
|
+
async function readQueueOwnerPid(sessionId) {
|
|
5702
|
+
let owner;
|
|
5703
|
+
try {
|
|
5704
|
+
owner = JSON.parse(await readFile(queueLockFilePath(sessionId), "utf8"));
|
|
5705
|
+
} catch {
|
|
5706
|
+
return;
|
|
5707
|
+
}
|
|
5708
|
+
if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0) {
|
|
5709
|
+
return owner.pid;
|
|
5710
|
+
}
|
|
5711
|
+
return;
|
|
5712
|
+
}
|
|
5703
5713
|
async function terminateAcpxQueueOwner(sessionId) {
|
|
5704
5714
|
const lockPath = queueLockFilePath(sessionId);
|
|
5705
5715
|
let owner;
|
|
@@ -5742,6 +5752,88 @@ var init_acpx_queue_owner_launcher = __esm(() => {
|
|
|
5742
5752
|
init_i18n();
|
|
5743
5753
|
});
|
|
5744
5754
|
|
|
5755
|
+
// src/runtime/core-home.ts
|
|
5756
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
5757
|
+
import { join as join4 } from "node:path";
|
|
5758
|
+
function coreHomeDir(home) {
|
|
5759
|
+
const primary = join4(home, CORE_HOME_DIR_NAME);
|
|
5760
|
+
if (existsSync2(primary))
|
|
5761
|
+
return primary;
|
|
5762
|
+
const legacy = join4(home, CORE_HOME_LEGACY_DIR_NAME);
|
|
5763
|
+
if (existsSync2(legacy))
|
|
5764
|
+
return legacy;
|
|
5765
|
+
return primary;
|
|
5766
|
+
}
|
|
5767
|
+
function coreHomeDisplayPath(...segments) {
|
|
5768
|
+
return ["~", CORE_HOME_DIR_NAME, ...segments].join("/");
|
|
5769
|
+
}
|
|
5770
|
+
var CORE_HOME_DIR_NAME = ".xacpx", CORE_HOME_LEGACY_DIR_NAME = ".weacpx";
|
|
5771
|
+
var init_core_home = () => {};
|
|
5772
|
+
|
|
5773
|
+
// src/orchestration/orchestration-ipc.ts
|
|
5774
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
5775
|
+
import { join as join5 } from "node:path";
|
|
5776
|
+
function resolveOrchestrationEndpoint(runtimeDir, platform = process.platform) {
|
|
5777
|
+
if (platform === "win32") {
|
|
5778
|
+
const suffix = createHash3("sha256").update(runtimeDir).digest("hex").slice(0, 12);
|
|
5779
|
+
return {
|
|
5780
|
+
kind: "named-pipe",
|
|
5781
|
+
path: `\\\\.\\pipe\\xacpx-orchestration-${suffix}`
|
|
5782
|
+
};
|
|
5783
|
+
}
|
|
5784
|
+
return {
|
|
5785
|
+
kind: "unix",
|
|
5786
|
+
path: join5(runtimeDir, "orchestration.sock")
|
|
5787
|
+
};
|
|
5788
|
+
}
|
|
5789
|
+
function createOrchestrationEndpoint(path2, platform = process.platform) {
|
|
5790
|
+
return {
|
|
5791
|
+
kind: platform === "win32" || path2.startsWith("\\\\.\\pipe\\") ? "named-pipe" : "unix",
|
|
5792
|
+
path: path2
|
|
5793
|
+
};
|
|
5794
|
+
}
|
|
5795
|
+
function encodeOrchestrationRpcRequest(request) {
|
|
5796
|
+
return `${JSON.stringify(request)}
|
|
5797
|
+
`;
|
|
5798
|
+
}
|
|
5799
|
+
function encodeOrchestrationRpcResponse(response) {
|
|
5800
|
+
return `${JSON.stringify(response)}
|
|
5801
|
+
`;
|
|
5802
|
+
}
|
|
5803
|
+
var init_orchestration_ipc = () => {};
|
|
5804
|
+
|
|
5805
|
+
// src/daemon/daemon-files.ts
|
|
5806
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
5807
|
+
function resolveDaemonPaths(options) {
|
|
5808
|
+
const runtimeDir = options.runtimeDir ?? (options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : join6(coreHomeDir(options.home), "runtime"));
|
|
5809
|
+
return {
|
|
5810
|
+
runtimeDir,
|
|
5811
|
+
pidFile: join6(runtimeDir, "daemon.pid"),
|
|
5812
|
+
statusFile: join6(runtimeDir, "status.json"),
|
|
5813
|
+
stdoutLog: join6(runtimeDir, "stdout.log"),
|
|
5814
|
+
stderrLog: join6(runtimeDir, "stderr.log"),
|
|
5815
|
+
appLog: join6(runtimeDir, "app.log")
|
|
5816
|
+
};
|
|
5817
|
+
}
|
|
5818
|
+
function resolveRuntimeDirFromConfigPath(configPath) {
|
|
5819
|
+
return join6(dirname3(configPath), "runtime");
|
|
5820
|
+
}
|
|
5821
|
+
function resolveDaemonOrchestrationSocketPath(runtimeDir, platform = process.platform) {
|
|
5822
|
+
return resolveOrchestrationEndpoint(runtimeDir, platform).path;
|
|
5823
|
+
}
|
|
5824
|
+
function isProcessAlive(pid) {
|
|
5825
|
+
try {
|
|
5826
|
+
process.kill(pid, 0);
|
|
5827
|
+
return true;
|
|
5828
|
+
} catch (error) {
|
|
5829
|
+
return error.code === "EPERM";
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
var init_daemon_files = __esm(() => {
|
|
5833
|
+
init_core_home();
|
|
5834
|
+
init_orchestration_ipc();
|
|
5835
|
+
});
|
|
5836
|
+
|
|
5745
5837
|
// src/util/path.ts
|
|
5746
5838
|
import path2 from "node:path";
|
|
5747
5839
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -5784,10 +5876,10 @@ var init_path = __esm(() => {
|
|
|
5784
5876
|
// src/transport/codex-subagent-filter.ts
|
|
5785
5877
|
import { closeSync, openSync, readdirSync, readSync, statSync } from "node:fs";
|
|
5786
5878
|
import { homedir as homedir5 } from "node:os";
|
|
5787
|
-
import { join as
|
|
5879
|
+
import { join as join7 } from "node:path";
|
|
5788
5880
|
function resolveCodexHome(env = process.env) {
|
|
5789
5881
|
const fromEnv = env.CODEX_HOME?.trim();
|
|
5790
|
-
return fromEnv ? fromEnv :
|
|
5882
|
+
return fromEnv ? fromEnv : join7(homedir5(), ".codex");
|
|
5791
5883
|
}
|
|
5792
5884
|
function isPlainObject(value) {
|
|
5793
5885
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -5852,7 +5944,7 @@ function filterSubagentSessions(result, isSubagent) {
|
|
|
5852
5944
|
};
|
|
5853
5945
|
}
|
|
5854
5946
|
function nodeRolloutReader(home) {
|
|
5855
|
-
const root =
|
|
5947
|
+
const root = join7(home, "sessions");
|
|
5856
5948
|
return {
|
|
5857
5949
|
listRolloutPaths() {
|
|
5858
5950
|
const out = [];
|
|
@@ -5864,7 +5956,7 @@ function nodeRolloutReader(home) {
|
|
|
5864
5956
|
return;
|
|
5865
5957
|
}
|
|
5866
5958
|
for (const entry of entries) {
|
|
5867
|
-
const full =
|
|
5959
|
+
const full = join7(dir, entry.name);
|
|
5868
5960
|
if (entry.isDirectory())
|
|
5869
5961
|
walk(full);
|
|
5870
5962
|
else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl"))
|
|
@@ -5980,11 +6072,11 @@ var init_agent_session_list = __esm(() => {
|
|
|
5980
6072
|
// src/transport/acpx-session-files.ts
|
|
5981
6073
|
import { readdir, unlink as unlink2 } from "node:fs/promises";
|
|
5982
6074
|
import { homedir as homedir6 } from "node:os";
|
|
5983
|
-
import { join as
|
|
6075
|
+
import { join as join8 } from "node:path";
|
|
5984
6076
|
async function deleteAcpxSessionFiles(options) {
|
|
5985
|
-
const dir = options.sessionsDir ??
|
|
6077
|
+
const dir = options.sessionsDir ?? join8(homedir6(), ".acpx", "sessions");
|
|
5986
6078
|
const safeId = encodeURIComponent(options.acpxRecordId);
|
|
5987
|
-
await unlink2(
|
|
6079
|
+
await unlink2(join8(dir, `${safeId}.json`)).catch(() => {
|
|
5988
6080
|
return;
|
|
5989
6081
|
});
|
|
5990
6082
|
let entries;
|
|
@@ -5995,7 +6087,7 @@ async function deleteAcpxSessionFiles(options) {
|
|
|
5995
6087
|
}
|
|
5996
6088
|
const streamFiles = entries.filter((name) => name.startsWith(`${safeId}.stream.`));
|
|
5997
6089
|
for (const name of streamFiles) {
|
|
5998
|
-
await unlink2(
|
|
6090
|
+
await unlink2(join8(dir, name)).catch(() => {
|
|
5999
6091
|
return;
|
|
6000
6092
|
});
|
|
6001
6093
|
}
|
|
@@ -6262,7 +6354,7 @@ init_prompt_media();
|
|
|
6262
6354
|
init_streaming_prompt();
|
|
6263
6355
|
import { copyFile, readdir as readdir2 } from "node:fs/promises";
|
|
6264
6356
|
import { homedir as homedir7 } from "node:os";
|
|
6265
|
-
import { dirname as
|
|
6357
|
+
import { dirname as dirname4, join as join9, win32 } from "node:path";
|
|
6266
6358
|
import { spawn as spawn4 } from "node:child_process";
|
|
6267
6359
|
|
|
6268
6360
|
// src/bridge/parse-missing-optional-dep.ts
|
|
@@ -6281,6 +6373,7 @@ function parseMissingOptionalDep(text) {
|
|
|
6281
6373
|
// src/bridge/bridge-runtime.ts
|
|
6282
6374
|
init_discover_parent_package_paths();
|
|
6283
6375
|
init_acpx_queue_owner_launcher();
|
|
6376
|
+
init_daemon_files();
|
|
6284
6377
|
init_agent_session_list();
|
|
6285
6378
|
init_codex_subagent_filter();
|
|
6286
6379
|
init_acpx_session_files();
|
|
@@ -6345,6 +6438,7 @@ class BridgeRuntime {
|
|
|
6345
6438
|
});
|
|
6346
6439
|
}
|
|
6347
6440
|
async resumeAgentSession(input) {
|
|
6441
|
+
this.invalidateRecordIdCache(input);
|
|
6348
6442
|
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
6349
6443
|
"sessions",
|
|
6350
6444
|
"new",
|
|
@@ -6414,6 +6508,7 @@ class BridgeRuntime {
|
|
|
6414
6508
|
throw new Error(message);
|
|
6415
6509
|
}
|
|
6416
6510
|
async ensureSession(input, onProgress) {
|
|
6511
|
+
this.invalidateRecordIdCache(input);
|
|
6417
6512
|
try {
|
|
6418
6513
|
return await this.attemptEnsureSession(input, onProgress);
|
|
6419
6514
|
} catch (error) {
|
|
@@ -6528,7 +6623,7 @@ class BridgeRuntime {
|
|
|
6528
6623
|
const resolved = __require.resolve(`${candidate}/package.json`, {
|
|
6529
6624
|
paths: [process.cwd(), ...__require.resolve.paths(candidate) ?? []]
|
|
6530
6625
|
});
|
|
6531
|
-
return
|
|
6626
|
+
return dirname4(resolved);
|
|
6532
6627
|
} catch {
|
|
6533
6628
|
continue;
|
|
6534
6629
|
}
|
|
@@ -6715,6 +6810,7 @@ class BridgeRuntime {
|
|
|
6715
6810
|
};
|
|
6716
6811
|
}
|
|
6717
6812
|
async removeSession(input) {
|
|
6813
|
+
this.invalidateRecordIdCache(input);
|
|
6718
6814
|
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
6719
6815
|
"sessions",
|
|
6720
6816
|
"close",
|
|
@@ -6753,6 +6849,27 @@ class BridgeRuntime {
|
|
|
6753
6849
|
await terminateAcpxQueueOwner(acpxRecordId);
|
|
6754
6850
|
return {};
|
|
6755
6851
|
}
|
|
6852
|
+
recordIdCache = new Map;
|
|
6853
|
+
recordIdCacheKey(input) {
|
|
6854
|
+
return JSON.stringify([input.agent, input.agentCommand ?? null, input.cwd, input.name]);
|
|
6855
|
+
}
|
|
6856
|
+
invalidateRecordIdCache(input) {
|
|
6857
|
+
this.recordIdCache.delete(this.recordIdCacheKey(input));
|
|
6858
|
+
}
|
|
6859
|
+
async isSessionWarm(input) {
|
|
6860
|
+
const cacheKey = this.recordIdCacheKey(input);
|
|
6861
|
+
let acpxRecordId = this.recordIdCache.get(cacheKey);
|
|
6862
|
+
if (!acpxRecordId) {
|
|
6863
|
+
try {
|
|
6864
|
+
({ acpxRecordId } = await this.readSessionRecord(input));
|
|
6865
|
+
} catch {
|
|
6866
|
+
return { warm: false };
|
|
6867
|
+
}
|
|
6868
|
+
this.recordIdCache.set(cacheKey, acpxRecordId);
|
|
6869
|
+
}
|
|
6870
|
+
const pid = await readQueueOwnerPid(acpxRecordId);
|
|
6871
|
+
return { warm: pid !== undefined && isProcessAlive(pid) };
|
|
6872
|
+
}
|
|
6756
6873
|
async shutdown() {
|
|
6757
6874
|
return {};
|
|
6758
6875
|
}
|
|
@@ -6943,7 +7060,7 @@ async function tryRepairAcpxSessionIndex(deps = {}) {
|
|
|
6943
7060
|
if (!home) {
|
|
6944
7061
|
return false;
|
|
6945
7062
|
}
|
|
6946
|
-
const pathJoin = platform === "win32" ? win32.join :
|
|
7063
|
+
const pathJoin = platform === "win32" ? win32.join : join9;
|
|
6947
7064
|
const sessionsDir = pathJoin(home, ".acpx", "sessions");
|
|
6948
7065
|
const indexPath = pathJoin(sessionsDir, "index.json");
|
|
6949
7066
|
const readdirFn = deps.readdirFn ?? readdir2;
|
|
@@ -6993,6 +7110,7 @@ var BRIDGE_METHODS = new Set([
|
|
|
6993
7110
|
"removeSession",
|
|
6994
7111
|
"deleteSession",
|
|
6995
7112
|
"freeWarmProcess",
|
|
7113
|
+
"isSessionWarm",
|
|
6996
7114
|
"getAgentSessionId"
|
|
6997
7115
|
]);
|
|
6998
7116
|
var SESSION_SCOPED_METHODS = new Set([
|
|
@@ -7010,6 +7128,7 @@ var SESSION_SCOPED_METHODS = new Set([
|
|
|
7010
7128
|
"removeSession",
|
|
7011
7129
|
"deleteSession",
|
|
7012
7130
|
"freeWarmProcess",
|
|
7131
|
+
"isSessionWarm",
|
|
7013
7132
|
"getAgentSessionId"
|
|
7014
7133
|
]);
|
|
7015
7134
|
|
|
@@ -7070,7 +7189,7 @@ class BridgeServer {
|
|
|
7070
7189
|
if (!sessionKey) {
|
|
7071
7190
|
return await this.dispatch(requestId, method, params, writeLine);
|
|
7072
7191
|
}
|
|
7073
|
-
const lane = method === "cancel" ? "control" : "normal";
|
|
7192
|
+
const lane = method === "cancel" || method === "isSessionWarm" ? "control" : "normal";
|
|
7074
7193
|
return await this.scheduler.run(sessionKey, lane, () => this.dispatch(requestId, method, params, writeLine));
|
|
7075
7194
|
}
|
|
7076
7195
|
async dispatch(requestId, method, params, writeLine) {
|
|
@@ -7281,6 +7400,14 @@ class BridgeServer {
|
|
|
7281
7400
|
cwd: requireString(params, "cwd"),
|
|
7282
7401
|
name: requireString(params, "name")
|
|
7283
7402
|
});
|
|
7403
|
+
case "isSessionWarm":
|
|
7404
|
+
return await this.runtime.isSessionWarm({
|
|
7405
|
+
agent: requireString(params, "agent"),
|
|
7406
|
+
...agentExecutionSettings(params),
|
|
7407
|
+
agentCommand: asOptionalString(params.agentCommand),
|
|
7408
|
+
cwd: requireString(params, "cwd"),
|
|
7409
|
+
name: requireString(params, "name")
|
|
7410
|
+
});
|
|
7284
7411
|
case "getAgentSessionId":
|
|
7285
7412
|
return await this.runtime.getAgentSessionId({
|
|
7286
7413
|
agent: requireString(params, "agent"),
|
package/dist/cli.js
CHANGED
|
@@ -4624,7 +4624,7 @@ function resolveClaudeConfigDir(env, homeDir, platform) {
|
|
|
4624
4624
|
function installSettingsProfile(env, sourceConfigDir, policy, settings, options, platform) {
|
|
4625
4625
|
const serialized = `${JSON.stringify(settings, null, 2)}
|
|
4626
4626
|
`;
|
|
4627
|
-
const digest = createHash("sha256").update(sourceConfigDir).update("\x00").update(policy).update("\x00").update(serialized).digest("hex").slice(0, 20);
|
|
4627
|
+
const digest = createHash("sha256").update(sourceConfigDir).update("\x00").update(policy).update("\x00").update(CLAUDE_SETTINGS_PROFILE_LAYOUT_VERSION).update("\x00").update(serialized).digest("hex").slice(0, 20);
|
|
4628
4628
|
const profileDir = join3(options.profileRoot ?? join3(tmpdir(), "xacpx-claude-profiles"), digest);
|
|
4629
4629
|
const settingsPath = join3(profileDir, "settings.json");
|
|
4630
4630
|
(options.writeProfile ?? writePrivateFileSync)(settingsPath, serialized);
|
|
@@ -4632,7 +4632,7 @@ function installSettingsProfile(env, sourceConfigDir, policy, settings, options,
|
|
|
4632
4632
|
setEnvironmentValue(env, "CLAUDE_CONFIG_DIR", profileDir, platform);
|
|
4633
4633
|
}
|
|
4634
4634
|
function linkClaudeSessionState(sourceConfigDir, profileDir, platform) {
|
|
4635
|
-
for (const name of
|
|
4635
|
+
for (const name of CLAUDE_PERSISTENT_SESSION_STATE_DIRS) {
|
|
4636
4636
|
const source = join3(sourceConfigDir, name);
|
|
4637
4637
|
mkdirSync3(source, { recursive: true });
|
|
4638
4638
|
const target = join3(profileDir, name);
|
|
@@ -4729,20 +4729,18 @@ function setEnvironmentValue(env, name, value, platform) {
|
|
|
4729
4729
|
deleteEnvironmentValue(env, name, platform);
|
|
4730
4730
|
env[name] = value;
|
|
4731
4731
|
}
|
|
4732
|
-
var DEFAULT_CLAUDE_SETTINGS_POLICY = "provider-only",
|
|
4732
|
+
var DEFAULT_CLAUDE_SETTINGS_POLICY = "provider-only", CLAUDE_SETTINGS_PROFILE_LAYOUT_VERSION = "persistent-state-links-v1", CLAUDE_PERSISTENT_SESSION_STATE_DIRS;
|
|
4733
4733
|
var init_claude_settings_policy = __esm(() => {
|
|
4734
4734
|
init_private_file();
|
|
4735
|
-
|
|
4735
|
+
CLAUDE_PERSISTENT_SESSION_STATE_DIRS = [
|
|
4736
4736
|
"projects",
|
|
4737
4737
|
"file-history",
|
|
4738
4738
|
"plans",
|
|
4739
4739
|
"todos",
|
|
4740
|
-
"session-env",
|
|
4741
4740
|
"tasks",
|
|
4742
4741
|
"teams",
|
|
4743
4742
|
"sessions",
|
|
4744
|
-
"transcripts"
|
|
4745
|
-
"shell-snapshots"
|
|
4743
|
+
"transcripts"
|
|
4746
4744
|
];
|
|
4747
4745
|
});
|
|
4748
4746
|
|
|
@@ -32135,6 +32133,18 @@ function createDefaultQueueOwnerTerminator(_acpxCommand) {
|
|
|
32135
32133
|
await terminateAcpxQueueOwner(sessionId);
|
|
32136
32134
|
};
|
|
32137
32135
|
}
|
|
32136
|
+
async function readQueueOwnerPid(sessionId) {
|
|
32137
|
+
let owner;
|
|
32138
|
+
try {
|
|
32139
|
+
owner = JSON.parse(await readFile13(queueLockFilePath(sessionId), "utf8"));
|
|
32140
|
+
} catch {
|
|
32141
|
+
return;
|
|
32142
|
+
}
|
|
32143
|
+
if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0) {
|
|
32144
|
+
return owner.pid;
|
|
32145
|
+
}
|
|
32146
|
+
return;
|
|
32147
|
+
}
|
|
32138
32148
|
async function terminateAcpxQueueOwner(sessionId) {
|
|
32139
32149
|
const lockPath = queueLockFilePath(sessionId);
|
|
32140
32150
|
let owner;
|
|
@@ -32991,6 +33001,10 @@ ${result.text}` : "" };
|
|
|
32991
33001
|
async freeWarmProcess(session3) {
|
|
32992
33002
|
await this.client.request("freeWarmProcess", this.toParams(session3));
|
|
32993
33003
|
}
|
|
33004
|
+
async isSessionWarm(session3) {
|
|
33005
|
+
const result = await this.client.request("isSessionWarm", this.toParams(session3));
|
|
33006
|
+
return result.warm === true;
|
|
33007
|
+
}
|
|
32994
33008
|
async getAgentSessionId(session3) {
|
|
32995
33009
|
const result = await this.client.request("getAgentSessionId", this.toParams(session3));
|
|
32996
33010
|
return result.agentSessionId;
|
|
@@ -34107,6 +34121,7 @@ class AcpxCliTransport {
|
|
|
34107
34121
|
this.resolveSpawnEnvironment = options.resolveSpawnEnvironment ?? resolveClaudeSpawnEnvironment;
|
|
34108
34122
|
}
|
|
34109
34123
|
async ensureSession(session3, _onProgress) {
|
|
34124
|
+
this.invalidateRecordIdCache(session3);
|
|
34110
34125
|
try {
|
|
34111
34126
|
await this.runEnsureSession(session3);
|
|
34112
34127
|
} catch (error2) {
|
|
@@ -34319,6 +34334,7 @@ ${baseText}` : "" };
|
|
|
34319
34334
|
};
|
|
34320
34335
|
}
|
|
34321
34336
|
async resumeAgentSession(session3, agentSessionId) {
|
|
34337
|
+
this.invalidateRecordIdCache(session3);
|
|
34322
34338
|
const args = this.buildArgs(session3, [
|
|
34323
34339
|
"sessions",
|
|
34324
34340
|
"new",
|
|
@@ -34339,6 +34355,7 @@ ${baseText}` : "" };
|
|
|
34339
34355
|
this.permissionPolicy = policy.permissionPolicy;
|
|
34340
34356
|
}
|
|
34341
34357
|
async removeSession(session3) {
|
|
34358
|
+
this.invalidateRecordIdCache(session3);
|
|
34342
34359
|
const result = await this.runCommandWithTimeout(this.runCommand, this.buildArgs(session3, [
|
|
34343
34360
|
"sessions",
|
|
34344
34361
|
"close",
|
|
@@ -34375,6 +34392,30 @@ ${baseText}` : "" };
|
|
|
34375
34392
|
}
|
|
34376
34393
|
await terminateAcpxQueueOwner(acpxRecordId);
|
|
34377
34394
|
}
|
|
34395
|
+
recordIdCache = new Map;
|
|
34396
|
+
recordIdCacheKey(session3) {
|
|
34397
|
+
return JSON.stringify([session3.agent, session3.agentCommand ?? null, session3.cwd, session3.transportSession]);
|
|
34398
|
+
}
|
|
34399
|
+
invalidateRecordIdCache(session3) {
|
|
34400
|
+
this.recordIdCache.delete(this.recordIdCacheKey(session3));
|
|
34401
|
+
}
|
|
34402
|
+
async isSessionWarm(session3) {
|
|
34403
|
+
const cacheKey = this.recordIdCacheKey(session3);
|
|
34404
|
+
let acpxRecordId = this.recordIdCache.get(cacheKey);
|
|
34405
|
+
if (!acpxRecordId) {
|
|
34406
|
+
try {
|
|
34407
|
+
({ acpxRecordId } = await this.readSessionRecord(session3));
|
|
34408
|
+
} catch {
|
|
34409
|
+
return false;
|
|
34410
|
+
}
|
|
34411
|
+
this.recordIdCache.set(cacheKey, acpxRecordId);
|
|
34412
|
+
}
|
|
34413
|
+
const pid = await readQueueOwnerPid(acpxRecordId);
|
|
34414
|
+
if (pid === undefined) {
|
|
34415
|
+
return false;
|
|
34416
|
+
}
|
|
34417
|
+
return isProcessAlive(pid);
|
|
34418
|
+
}
|
|
34378
34419
|
async hasSession(session3) {
|
|
34379
34420
|
const result = await this.runCommandWithTimeout(this.runCommand, this.buildArgs(session3, [
|
|
34380
34421
|
"sessions",
|
|
@@ -34707,6 +34748,7 @@ var init_acpx_cli_transport = __esm(() => {
|
|
|
34707
34748
|
init_node_pty_helper();
|
|
34708
34749
|
init_terminate_process_tree();
|
|
34709
34750
|
init_acpx_queue_owner_launcher();
|
|
34751
|
+
init_daemon_files();
|
|
34710
34752
|
init_agent_session_list();
|
|
34711
34753
|
init_codex_subagent_filter();
|
|
34712
34754
|
init_acpx_session_files();
|
|
@@ -36446,7 +36488,7 @@ class WorkspaceFs {
|
|
|
36446
36488
|
}
|
|
36447
36489
|
const files = [];
|
|
36448
36490
|
try {
|
|
36449
|
-
const { stdout: stdout2 } = await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "status", "--porcelain", "-z"], { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" });
|
|
36491
|
+
const { stdout: stdout2 } = await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "status", "--porcelain", "-z", "--untracked-files=all"], { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" });
|
|
36450
36492
|
const fields = stdout2.split("\x00");
|
|
36451
36493
|
for (let i = 0;i < fields.length; i++) {
|
|
36452
36494
|
const field = fields[i];
|
|
@@ -36674,9 +36716,10 @@ class WorkspaceGit {
|
|
|
36674
36716
|
return (await this.runRaw(root, args)).trim();
|
|
36675
36717
|
}
|
|
36676
36718
|
async runRaw(root, args) {
|
|
36719
|
+
const fullArgs = ["-C", root, "-c", "gc.auto=0", ...args];
|
|
36677
36720
|
if (this.runGitOverride)
|
|
36678
|
-
return await this.runGitOverride(root,
|
|
36679
|
-
const result = await execFileAsync2("git",
|
|
36721
|
+
return await this.runGitOverride(root, fullArgs);
|
|
36722
|
+
const result = await execFileAsync2("git", fullArgs, {
|
|
36680
36723
|
timeout: GIT_TIMEOUT_MS2,
|
|
36681
36724
|
killSignal: "SIGKILL",
|
|
36682
36725
|
maxBuffer: GIT_MAX_BUFFER2
|
|
@@ -36952,19 +36995,21 @@ class WorkspaceGit {
|
|
|
36952
36995
|
...worktreePath ? { worktreePath } : {}
|
|
36953
36996
|
};
|
|
36954
36997
|
});
|
|
36955
|
-
const statusRaw = await this.runRaw(root, ["-c", "core.quotePath=false", "status", "--porcelain", "-z"]);
|
|
36956
36998
|
const files = [];
|
|
36957
|
-
|
|
36958
|
-
|
|
36959
|
-
const
|
|
36960
|
-
|
|
36961
|
-
|
|
36962
|
-
|
|
36963
|
-
|
|
36964
|
-
|
|
36965
|
-
|
|
36966
|
-
|
|
36967
|
-
|
|
36999
|
+
try {
|
|
37000
|
+
const statusRaw = await this.runRaw(root, ["-c", "core.quotePath=false", "status", "--porcelain", "-z", "--untracked-files=all"]);
|
|
37001
|
+
const fields = statusRaw.split("\x00");
|
|
37002
|
+
for (let index = 0;index < fields.length; index += 1) {
|
|
37003
|
+
const field = fields[index];
|
|
37004
|
+
if (!field)
|
|
37005
|
+
continue;
|
|
37006
|
+
const status = field.slice(0, 2);
|
|
37007
|
+
const path14 = field.slice(3);
|
|
37008
|
+
if (status[0] === "R" || status[0] === "C")
|
|
37009
|
+
index += 1;
|
|
37010
|
+
files.push({ path: path14, status });
|
|
37011
|
+
}
|
|
37012
|
+
} catch {}
|
|
36968
37013
|
files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
36969
37014
|
return {
|
|
36970
37015
|
workspace: workspace3,
|
|
@@ -37071,6 +37116,11 @@ class SessionTurnRunner {
|
|
|
37071
37116
|
if (wasArchived) {
|
|
37072
37117
|
this.deps.events.emit({ type: "sessions-changed" });
|
|
37073
37118
|
}
|
|
37119
|
+
if (internalAlias && this.deps.sessionWarmth) {
|
|
37120
|
+
const bound = await this.deps.sessions.getSession(internalAlias).catch(() => null);
|
|
37121
|
+
if (bound)
|
|
37122
|
+
this.deps.sessionWarmth.markWarm(bound);
|
|
37123
|
+
}
|
|
37074
37124
|
this.deps.events.emit({
|
|
37075
37125
|
type: "turn-started",
|
|
37076
37126
|
chatKey: req.chatKey,
|
|
@@ -37687,6 +37737,7 @@ class ControlService {
|
|
|
37687
37737
|
if (!session3)
|
|
37688
37738
|
throw new Error("session not found");
|
|
37689
37739
|
await this.deps.sessions.setDisplayName(session3.alias, displayName);
|
|
37740
|
+
this.deps.events.emit({ type: "sessions-changed" });
|
|
37690
37741
|
}
|
|
37691
37742
|
async resolveControlSession(chatKey, alias) {
|
|
37692
37743
|
const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
|
|
@@ -37697,17 +37748,22 @@ class ControlService {
|
|
|
37697
37748
|
}
|
|
37698
37749
|
listSessions(chatKey) {
|
|
37699
37750
|
const channelId = getChannelIdFromChatKey(chatKey);
|
|
37700
|
-
return this.deps.sessions.listAllResolvedSessions().filter((session3) => isSessionAliasVisibleInChannel(session3.alias, channelId)).map((session3) =>
|
|
37701
|
-
|
|
37702
|
-
|
|
37703
|
-
|
|
37704
|
-
|
|
37705
|
-
|
|
37706
|
-
|
|
37707
|
-
|
|
37708
|
-
|
|
37709
|
-
|
|
37710
|
-
|
|
37751
|
+
return this.deps.sessions.listAllResolvedSessions().filter((session3) => isSessionAliasVisibleInChannel(session3.alias, channelId)).map((session3) => {
|
|
37752
|
+
const running = this.deps.activeTurns.isActiveAnywhere(session3.alias);
|
|
37753
|
+
const warm = running ? true : this.deps.sessionWarmth?.isWarm(session3);
|
|
37754
|
+
return {
|
|
37755
|
+
alias: toDisplaySessionAlias(session3.alias),
|
|
37756
|
+
agent: session3.agent,
|
|
37757
|
+
workspace: session3.workspace,
|
|
37758
|
+
transportSession: session3.transportSession,
|
|
37759
|
+
running,
|
|
37760
|
+
archived: session3.archived === true,
|
|
37761
|
+
...warm !== undefined ? { warm } : {},
|
|
37762
|
+
...session3.source === "agent-side" ? { native: true } : {},
|
|
37763
|
+
...session3.agentCommand ? { agentCommand: session3.agentCommand } : {},
|
|
37764
|
+
...session3.displayName ? { displayName: session3.displayName } : {}
|
|
37765
|
+
};
|
|
37766
|
+
});
|
|
37711
37767
|
}
|
|
37712
37768
|
async listNativeSessions(_chatKey, agent3, workspace3) {
|
|
37713
37769
|
const sessions = await this.deps.listNativeSessions(agent3, workspace3);
|
|
@@ -37749,6 +37805,11 @@ class ControlService {
|
|
|
37749
37805
|
async archiveSession(chatKey, alias) {
|
|
37750
37806
|
const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
|
|
37751
37807
|
await this.deps.archiveSessionWithTransport(internalAlias);
|
|
37808
|
+
const session3 = await this.deps.sessions.getSession(internalAlias).catch(() => {
|
|
37809
|
+
return;
|
|
37810
|
+
});
|
|
37811
|
+
if (session3)
|
|
37812
|
+
this.deps.sessionWarmth?.markCold(session3);
|
|
37752
37813
|
this.deps.events.emit({ type: "sessions-changed" });
|
|
37753
37814
|
}
|
|
37754
37815
|
async unarchiveSession(chatKey, alias) {
|
|
@@ -37896,6 +37957,98 @@ var init_control_service = __esm(() => {
|
|
|
37896
37957
|
MODEL_SET_SETTLE_BUDGET_MS = 2 * (DEFAULT_MANAGEMENT_COMMAND_TIMEOUT_MS + BRIDGE_REQUEST_TIMEOUT_GRACE_MS);
|
|
37897
37958
|
});
|
|
37898
37959
|
|
|
37960
|
+
// src/control/session-warmth-tracker.ts
|
|
37961
|
+
class SessionWarmthTracker {
|
|
37962
|
+
listSessions;
|
|
37963
|
+
checkWarm;
|
|
37964
|
+
events;
|
|
37965
|
+
logger;
|
|
37966
|
+
intervalMs;
|
|
37967
|
+
setIntervalFn;
|
|
37968
|
+
clearIntervalFn;
|
|
37969
|
+
warmth = new Map;
|
|
37970
|
+
intervalHandle = null;
|
|
37971
|
+
ticking = false;
|
|
37972
|
+
constructor(deps) {
|
|
37973
|
+
this.listSessions = deps.listSessions;
|
|
37974
|
+
this.checkWarm = deps.isWarm;
|
|
37975
|
+
this.events = deps.events;
|
|
37976
|
+
this.logger = deps.logger;
|
|
37977
|
+
this.intervalMs = deps.intervalMs ?? 60000;
|
|
37978
|
+
this.setIntervalFn = deps.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
|
|
37979
|
+
this.clearIntervalFn = deps.clearIntervalFn ?? ((timer) => clearInterval(timer));
|
|
37980
|
+
}
|
|
37981
|
+
start() {
|
|
37982
|
+
if (this.intervalHandle !== null)
|
|
37983
|
+
return;
|
|
37984
|
+
this.intervalHandle = this.setIntervalFn(() => {
|
|
37985
|
+
this.tick();
|
|
37986
|
+
}, this.intervalMs);
|
|
37987
|
+
this.tick();
|
|
37988
|
+
}
|
|
37989
|
+
stop() {
|
|
37990
|
+
if (this.intervalHandle !== null) {
|
|
37991
|
+
this.clearIntervalFn(this.intervalHandle);
|
|
37992
|
+
this.intervalHandle = null;
|
|
37993
|
+
}
|
|
37994
|
+
}
|
|
37995
|
+
isWarm(session3) {
|
|
37996
|
+
return this.warmth.get(warmthKey(session3));
|
|
37997
|
+
}
|
|
37998
|
+
markWarm(session3) {
|
|
37999
|
+
this.warmth.set(warmthKey(session3), true);
|
|
38000
|
+
}
|
|
38001
|
+
markCold(session3) {
|
|
38002
|
+
this.warmth.set(warmthKey(session3), false);
|
|
38003
|
+
}
|
|
38004
|
+
async tick() {
|
|
38005
|
+
if (this.ticking)
|
|
38006
|
+
return;
|
|
38007
|
+
this.ticking = true;
|
|
38008
|
+
try {
|
|
38009
|
+
const sessions = this.listSessions();
|
|
38010
|
+
const seen = new Set;
|
|
38011
|
+
let flipped = false;
|
|
38012
|
+
for (const session3 of sessions) {
|
|
38013
|
+
const key = warmthKey(session3);
|
|
38014
|
+
if (seen.has(key))
|
|
38015
|
+
continue;
|
|
38016
|
+
seen.add(key);
|
|
38017
|
+
let warm;
|
|
38018
|
+
try {
|
|
38019
|
+
warm = await this.checkWarm(session3);
|
|
38020
|
+
} catch (error2) {
|
|
38021
|
+
await this.logger?.error("warmth.check_failed", "session warmth check threw; keeping previous value", {
|
|
38022
|
+
transportSession: session3.transportSession,
|
|
38023
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
38024
|
+
});
|
|
38025
|
+
continue;
|
|
38026
|
+
}
|
|
38027
|
+
if (this.warmth.get(key) !== warm) {
|
|
38028
|
+
this.warmth.set(key, warm);
|
|
38029
|
+
flipped = true;
|
|
38030
|
+
}
|
|
38031
|
+
}
|
|
38032
|
+
for (const key of [...this.warmth.keys()]) {
|
|
38033
|
+
if (!seen.has(key))
|
|
38034
|
+
this.warmth.delete(key);
|
|
38035
|
+
}
|
|
38036
|
+
if (flipped) {
|
|
38037
|
+
this.events.emit({ type: "sessions-changed" });
|
|
38038
|
+
}
|
|
38039
|
+
} catch (error2) {
|
|
38040
|
+
await this.logger?.error("warmth.tick_failed", "session warmth tick threw", {
|
|
38041
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
38042
|
+
});
|
|
38043
|
+
} finally {
|
|
38044
|
+
this.ticking = false;
|
|
38045
|
+
}
|
|
38046
|
+
}
|
|
38047
|
+
}
|
|
38048
|
+
function warmthKey(session3) {
|
|
38049
|
+
return JSON.stringify([session3.agent, session3.agentCommand ?? null, session3.cwd, session3.transportSession]);
|
|
38050
|
+
}
|
|
38051
|
+
|
|
37899
38052
|
// src/control/terminal-service.ts
|
|
37900
38053
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
37901
38054
|
import { createRequire as createRequire6 } from "node:module";
|
|
@@ -38800,6 +38953,13 @@ async function buildApp(paths, deps = {}) {
|
|
|
38800
38953
|
const loadAgentRegistry = createAcpxAgentRegistryLoader({ logger });
|
|
38801
38954
|
uploadStore.cleanup();
|
|
38802
38955
|
const uploadCleanupInterval = setInterval(() => void uploadStore.cleanup().catch(() => {}), 60 * 60 * 1000);
|
|
38956
|
+
const sessionWarmth = transport.isSessionWarm ? new SessionWarmthTracker({
|
|
38957
|
+
listSessions: () => sessions.listAllResolvedSessions(),
|
|
38958
|
+
isWarm: (session3) => transport.isSessionWarm(session3),
|
|
38959
|
+
events: controlEvents,
|
|
38960
|
+
logger
|
|
38961
|
+
}) : undefined;
|
|
38962
|
+
sessionWarmth?.start();
|
|
38803
38963
|
const control = new ControlService({
|
|
38804
38964
|
logger,
|
|
38805
38965
|
agent: agent3,
|
|
@@ -38812,6 +38972,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
38812
38972
|
archiveSessionWithTransport: (internalAlias) => router3.archiveSessionWithTransport(internalAlias),
|
|
38813
38973
|
unarchiveSession: (internalAlias) => router3.unarchiveSession(internalAlias),
|
|
38814
38974
|
activeTurns,
|
|
38975
|
+
...sessionWarmth ? { sessionWarmth } : {},
|
|
38815
38976
|
scheduled: scheduledService,
|
|
38816
38977
|
orchestration: orchestration3,
|
|
38817
38978
|
events: controlEvents,
|
|
@@ -38947,6 +39108,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
38947
39108
|
reapStaleQueueOwners: () => reapWarmQueueOwners("startup"),
|
|
38948
39109
|
dispose: async () => {
|
|
38949
39110
|
scheduledScheduler.stop();
|
|
39111
|
+
sessionWarmth?.stop();
|
|
38950
39112
|
configWatcher.close();
|
|
38951
39113
|
clearInterval(uploadCleanupInterval);
|
|
38952
39114
|
terminalService.disposeAll();
|
|
@@ -25,6 +25,10 @@ export interface ControlSessionInfo {
|
|
|
25
25
|
transportSession: string;
|
|
26
26
|
running: boolean;
|
|
27
27
|
archived: boolean;
|
|
28
|
+
/** Whether the session's agent process is currently alive (next prompt responds
|
|
29
|
+
* without a cold start). Omitted when unknown — e.g. the transport can't observe
|
|
30
|
+
* liveness or the warmth tracker hasn't sampled this session yet. */
|
|
31
|
+
warm?: boolean;
|
|
28
32
|
/** True when this logical session was attached to an existing agent-side (native) rollout
|
|
29
33
|
* rather than freshly created. Mirrors LogicalSession.source === "agent-side"; omitted for
|
|
30
34
|
* fresh xacpx sessions so the wire stays minimal. */
|
|
@@ -72,6 +76,8 @@ export interface ControlServiceDeps {
|
|
|
72
76
|
updatedAt?: string;
|
|
73
77
|
}) => Promise<ResolvedSession>;
|
|
74
78
|
activeTurns: Pick<ActiveTurnRegistry, "isActiveAnywhere">;
|
|
79
|
+
/** Warmth tracker view for the cold-session indicator; absent ⇒ `warm` omitted from listings. */
|
|
80
|
+
sessionWarmth?: Pick<import("./session-warmth-tracker").SessionWarmthTracker, "isWarm" | "markWarm" | "markCold">;
|
|
75
81
|
scheduled: Pick<ScheduledTaskService, "listPending" | "listRecentForChat" | "createTask" | "cancelPending">;
|
|
76
82
|
orchestration: Pick<OrchestrationService, "listTasks" | "getTask" | "requestTaskCancellation">;
|
|
77
83
|
events: ControlEventBus;
|
|
@@ -26,7 +26,7 @@ export interface TurnResult {
|
|
|
26
26
|
}
|
|
27
27
|
export declare class SessionTurnRunner {
|
|
28
28
|
private readonly deps;
|
|
29
|
-
constructor(deps: Pick<ControlServiceDeps, "agent" | "sessions" | "events" | "uploadStore">);
|
|
29
|
+
constructor(deps: Pick<ControlServiceDeps, "agent" | "sessions" | "events" | "uploadStore" | "sessionWarmth">);
|
|
30
30
|
run(req: TurnRequest, signal: AbortSignal, onActivity?: () => void): Promise<TurnResult>;
|
|
31
31
|
/** Resolve a chat-scoped display alias to its ResolvedSession and read whether it is
|
|
32
32
|
* in stream reply mode. Best-effort: any resolution failure falls back to batched
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AppLogger } from "../logging/app-logger";
|
|
2
|
+
import type { ResolvedSession } from "../transport/types";
|
|
3
|
+
import type { ControlEventBus } from "./control-event-bus";
|
|
4
|
+
export interface SessionWarmthTrackerDeps {
|
|
5
|
+
listSessions: () => ResolvedSession[];
|
|
6
|
+
isWarm: (session: ResolvedSession) => Promise<boolean>;
|
|
7
|
+
events: ControlEventBus;
|
|
8
|
+
logger?: AppLogger;
|
|
9
|
+
intervalMs?: number;
|
|
10
|
+
setIntervalFn?: (fn: () => void | Promise<void>, delay: number) => unknown;
|
|
11
|
+
clearIntervalFn?: (timer: unknown) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Polls queue-owner process liveness for every logical session and emits a
|
|
15
|
+
* payload-free `sessions-changed` control event whenever any session's warmth
|
|
16
|
+
* flips (e.g. a silent TTL expiry), so connected dashboards re-fetch the list
|
|
17
|
+
* and refresh their cold indicators. `isWarm` gives the last observed value
|
|
18
|
+
* synchronously for `control.sessions.list`.
|
|
19
|
+
*/
|
|
20
|
+
export declare class SessionWarmthTracker {
|
|
21
|
+
private readonly listSessions;
|
|
22
|
+
private readonly checkWarm;
|
|
23
|
+
private readonly events;
|
|
24
|
+
private readonly logger?;
|
|
25
|
+
private readonly intervalMs;
|
|
26
|
+
private readonly setIntervalFn;
|
|
27
|
+
private readonly clearIntervalFn;
|
|
28
|
+
private readonly warmth;
|
|
29
|
+
private intervalHandle;
|
|
30
|
+
private ticking;
|
|
31
|
+
constructor(deps: SessionWarmthTrackerDeps);
|
|
32
|
+
start(): void;
|
|
33
|
+
stop(): void;
|
|
34
|
+
/** Last observed warmth for this session's transport; undefined until first checked. */
|
|
35
|
+
isWarm(session: ResolvedSession): boolean | undefined;
|
|
36
|
+
/** Immediate corrections from call sites that just changed warmth themselves
|
|
37
|
+
* (archive kills the owner; a starting turn warms it). No event — the caller's
|
|
38
|
+
* own flow already emits one. */
|
|
39
|
+
markWarm(session: ResolvedSession): void;
|
|
40
|
+
markCold(session: ResolvedSession): void;
|
|
41
|
+
tick(): Promise<void>;
|
|
42
|
+
}
|
|
@@ -232,6 +232,13 @@ export interface SessionTransport {
|
|
|
232
232
|
* Optional: transports that can't reap omit it.
|
|
233
233
|
*/
|
|
234
234
|
freeWarmProcess?(session: ResolvedSession): Promise<void>;
|
|
235
|
+
/**
|
|
236
|
+
* Whether this session's warm queue-owner process is currently alive (=
|
|
237
|
+
* the next prompt responds immediately instead of cold-starting). False
|
|
238
|
+
* when the owner exited via TTL, archive, or any other reason. Optional:
|
|
239
|
+
* transports that can't observe process liveness omit it.
|
|
240
|
+
*/
|
|
241
|
+
isSessionWarm?(session: ResolvedSession): Promise<boolean>;
|
|
235
242
|
/**
|
|
236
243
|
* Read the underlying agent-native session id for an existing transport
|
|
237
244
|
* session. Used by `/clear` to keep a native session native: the fresh
|