@axhub/genie 0.2.11 → 0.2.12
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/api-docs.html +2 -2
- package/dist/assets/App-Clb2COtW.js +274 -0
- package/dist/assets/ImagePlaygroundPage-DqhMSbM8.js +106 -0
- package/dist/assets/ImagePlaygroundPage-MEn3NN80.css +1 -0
- package/dist/assets/ReviewApp-CDcLYe-u.js +1 -0
- package/dist/assets/{_basePickBy-BDnj7-0Z.js → _basePickBy-jUZsM51q.js} +1 -1
- package/dist/assets/{_baseUniq-Bl0JKOyl.js → _baseUniq-BXglE6_v.js} +1 -1
- package/dist/assets/{arc-DY-4Kev3.js → arc-D-oFCFBv.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-qw7crNVd.js → architectureDiagram-2XIMDMQ5-DC8bAnQt.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-B9xg7ep3.js → blockDiagram-WCTKOSBZ-C4semIRc.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-H9xp3ytb.js → c4Diagram-IC4MRINW-FHj1QO3y.js} +1 -1
- package/dist/assets/channel-BF4woPXX.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-B3EVDUxI.js → chunk-4BX2VUAB-D-LjsQ_s.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-CGv945ef.js → chunk-55IACEB6-DI3j_d7A.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-uAT4CKWM.js → chunk-FMBD7UC4-BEVnaLFN.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-Cbvlpkf7.js → chunk-JSJVCQXG-CSxpcErk.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-CcqIuGat.js → chunk-KX2RTZJC-BbuhDN4h.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-CgrcsRuX.js → chunk-NQ4KR5QH-C3x61XQa.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Cx0APOoV.js → chunk-QZHKN3VN-DxWOFtPh.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-BbZirvBk.js → chunk-WL4C6EOR-Bt2OauD2.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-D2kHlnQ7.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-D2kHlnQ7.js +1 -0
- package/dist/assets/clone-CqBvwCJW.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-CrvmGFLD.js → cose-bilkent-S5V4N54A-Dexadrue.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-C-W6VPjS.js → dagre-KLK3FWXG-F9U4X2xC.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-IP2q3bL0.js → diagram-E7M64L7V-B3V17aH3.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-CQaL-XyV.js → diagram-IFDJBPK2-CdHAmLL1.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-BxBLThfv.js → diagram-P4PSJMXO-CrTNfk8K.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-Dyl7bJTt.js → erDiagram-INFDFZHY-vDh9SWK9.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-B7NFMgFK.js → flowDiagram-PKNHOUZH-DpltMg7L.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-hReWSDu2.js → ganttDiagram-A5KZAMGK-COTk2xur.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-gVgcr0ST.js → gitGraphDiagram-K3NZZRJ6-BNV7bvvj.js} +1 -1
- package/dist/assets/{graph-DNDiJhTn.js → graph-Dkeg9oys.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-DclLmTou.js → highlighted-body-TPN3WLV5-DaiQEBwR.js} +1 -1
- package/dist/assets/index-DgGmiqsP.css +1 -0
- package/dist/assets/index-DvA901Vs.js +2 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-CqQOOzDA.js → infoDiagram-LFFYTUFH-CZioW3Gt.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-CZ0iLiHg.js → ishikawaDiagram-PHBUUO56-BbqR3i1B.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-DdfYKfNh.js → journeyDiagram-4ABVD52K-wfb-WHzl.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-C5Vf32u6.js → kanban-definition-K7BYSVSG-B3c4y3VN.js} +1 -1
- package/dist/assets/{layout-rvTEu2KS.js → layout-Xr9Z2VGF.js} +1 -1
- package/dist/assets/{linear-CD9SiYze.js → linear-JBmzAJtl.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-OZ8qWWwa.js → mermaid-O7DHMXV3-fDuyNLKe.js} +230 -222
- package/dist/assets/{mindmap-definition-YRQLILUH-CQxrLNVc.js → mindmap-definition-YRQLILUH-B5NTN_jD.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-XgAUByWg.js → pieDiagram-SKSYHLDU-CuO98GVu.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-CH16ls7G.js → quadrantDiagram-337W2JSQ-LL3f4vLf.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-B_kQO06L.js → requirementDiagram-Z7DCOOCP-Di-2O6LH.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-ofe78CyS.js → sankeyDiagram-WA2Y5GQK-9lHqrXqR.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-Ckbxwny6.js → sequenceDiagram-2WXFIKYE-BQu-SoGr.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-DNtzCk14.js → stateDiagram-RAJIS63D-BUxvd2BC.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-CDVexTiR.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-zT6CklKt.js → timeline-definition-YZTLITO2-oP47UEU6.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-y0U2c3xG.js → treemap-KZPCXAKY-BRjDo2aE.js} +1 -1
- package/dist/assets/{vendor-codemirror-CMHSJ_9p.js → vendor-codemirror-BiCeS-y4.js} +1 -1
- package/dist/assets/{vendor-react-xmA_f8ig.js → vendor-react-DVlYPmi3.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-xKj3SjYG.js → vennDiagram-LZ73GAT5-DrRqcDqo.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-Da_qyEoX.js → xychartDiagram-JWTSCODW-DUXrymAi.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +25 -6
- package/scripts/refresh-acp-default-capabilities.mjs +160 -0
- package/server/acp-runtime/client.js +1137 -181
- package/server/acp-runtime/command-overrides.js +48 -0
- package/server/acp-runtime/index.js +576 -16
- package/server/acp-runtime/registry.js +6 -4
- package/server/acp-runtime/session-store.js +235 -92
- package/server/database/db.js +12 -3
- package/server/external-agent/ws.js +212 -11
- package/server/index.js +145 -52
- package/server/projects-watcher-config.js +4 -0
- package/server/projects.js +466 -125
- package/server/routes/cc-connect.js +5 -4
- package/server/routes/codex.js +24 -0
- package/server/routes/commands.js +144 -1
- package/server/routes/runs.js +641 -0
- package/server/routes/session-core.js +357 -109
- package/server/session-core/eventStore.js +0 -121
- package/server/session-core/providerAdapters.js +644 -163
- package/server/session-core/providerDiscovery.js +66 -38
- package/server/session-core/runRegistry.js +244 -0
- package/server/session-core/runtimeState.js +75 -3
- package/server/session-core/runtimeWriter.js +132 -10
- package/server/utils/codexImagePlayground.js +479 -0
- package/server/utils/localTerminal.js +56 -0
- package/server/utils/shellCommand.js +70 -0
- package/shared/acpCapabilities.js +393 -0
- package/shared/acpDefaultCapabilities.generated.json +141 -0
- package/shared/conversationEvents.js +425 -121
- package/dist/assets/App-VH1wNUHs.js +0 -259
- package/dist/assets/ReviewApp-D_9EN4TM.js +0 -1
- package/dist/assets/channel-CyNUnRfc.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-DxBtyz2A.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-DxBtyz2A.js +0 -1
- package/dist/assets/clone-C341l3d0.js +0 -1
- package/dist/assets/index-DBkz_W_P.css +0 -1
- package/dist/assets/index-DdRyoXKh.js +0 -2
- package/dist/assets/stateDiagram-v2-FVOUBMTO-B3VPhiE1.js +0 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
const ACP_ADAPTER_PACKAGE_RANGES = {
|
|
2
|
-
claude: '^0.
|
|
3
|
-
codex: '^0.
|
|
2
|
+
claude: '^0.37.0',
|
|
3
|
+
codex: '^0.15.0',
|
|
4
|
+
gemini: '^0.43.0',
|
|
5
|
+
opencode: '^1.15.10'
|
|
4
6
|
};
|
|
5
7
|
|
|
6
8
|
export const AGENT_REGISTRY = {
|
|
7
9
|
claude: `npx -y @agentclientprotocol/claude-agent-acp@${ACP_ADAPTER_PACKAGE_RANGES.claude}`,
|
|
8
10
|
codex: `npx @zed-industries/codex-acp@${ACP_ADAPTER_PACKAGE_RANGES.codex}`,
|
|
9
|
-
gemini:
|
|
10
|
-
opencode:
|
|
11
|
+
gemini: `npx -y @google/gemini-cli@${ACP_ADAPTER_PACKAGE_RANGES.gemini} --acp --skip-trust`,
|
|
12
|
+
opencode: `npx -y opencode-ai@${ACP_ADAPTER_PACKAGE_RANGES.opencode} acp`
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
const AGENT_ALIASES = {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { readMirroredConversationEvents } from '../session-core/eventStore.js';
|
|
5
4
|
|
|
6
5
|
function parsePositiveInteger(value, fallback) {
|
|
7
6
|
const parsed = parseInt(value, 10);
|
|
@@ -9,6 +8,10 @@ function parsePositiveInteger(value, fallback) {
|
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
const DEFAULT_SESSION_GC_MAX_AGE_DAYS = parsePositiveInteger(process.env.ACP_SESSION_GC_MAX_AGE_DAYS, 30);
|
|
11
|
+
const sessionRecordWriteQueues = new Map();
|
|
12
|
+
let sessionIndexMutationQueue = Promise.resolve();
|
|
13
|
+
const SUPPORTED_SESSION_PROVIDERS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
14
|
+
const SESSION_INDEX_VERSION = 1;
|
|
12
15
|
|
|
13
16
|
function normalizeProvider(value) {
|
|
14
17
|
return String(value || '').trim().toLowerCase();
|
|
@@ -19,13 +22,21 @@ function normalizeSessionId(value) {
|
|
|
19
22
|
return normalized || null;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
function
|
|
23
|
-
|
|
25
|
+
function getSessionRoot() {
|
|
26
|
+
return process.env.AXHUB_GENIE_ACP_SESSION_ROOT
|
|
24
27
|
? path.resolve(process.env.AXHUB_GENIE_ACP_SESSION_ROOT)
|
|
25
28
|
: path.join(os.homedir(), '.axhub-genie', 'acp-sessions');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getProviderDir(provider) {
|
|
32
|
+
const sessionRoot = getSessionRoot();
|
|
26
33
|
return path.join(sessionRoot, normalizeProvider(provider));
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
function getSessionIndexFilePath() {
|
|
37
|
+
return path.join(getSessionRoot(), 'index.json');
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
function getSessionFilePath(provider, sessionId) {
|
|
30
41
|
return path.join(getProviderDir(provider), `${encodeURIComponent(sessionId)}.json`);
|
|
31
42
|
}
|
|
@@ -42,6 +53,10 @@ async function ensureProviderDir(provider) {
|
|
|
42
53
|
await fs.mkdir(getProviderDir(provider), { recursive: true });
|
|
43
54
|
}
|
|
44
55
|
|
|
56
|
+
async function ensureSessionRootDir() {
|
|
57
|
+
await fs.mkdir(getSessionRoot(), { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
45
60
|
async function listProviderSessionFiles(provider) {
|
|
46
61
|
try {
|
|
47
62
|
const entries = await fs.readdir(getProviderDir(provider), { withFileTypes: true });
|
|
@@ -64,6 +79,26 @@ async function readJsonFile(filePath) {
|
|
|
64
79
|
if (error?.code === 'ENOENT') {
|
|
65
80
|
return null;
|
|
66
81
|
}
|
|
82
|
+
if (error instanceof SyntaxError) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function writeJsonFileAtomically(filePath, content) {
|
|
90
|
+
const tempPath = [
|
|
91
|
+
filePath,
|
|
92
|
+
process.pid,
|
|
93
|
+
Date.now(),
|
|
94
|
+
Math.random().toString(36).slice(2)
|
|
95
|
+
].join('.') + '.tmp';
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await fs.writeFile(tempPath, content, 'utf8');
|
|
99
|
+
await fs.rename(tempPath, filePath);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
await fs.rm(tempPath, { force: true }).catch(() => {});
|
|
67
102
|
throw error;
|
|
68
103
|
}
|
|
69
104
|
}
|
|
@@ -73,15 +108,26 @@ function createSessionSummary(record) {
|
|
|
73
108
|
return null;
|
|
74
109
|
}
|
|
75
110
|
|
|
111
|
+
const provider = normalizeProvider(record.provider);
|
|
112
|
+
const sessionId = normalizeSessionId(record.sessionId || record.id);
|
|
113
|
+
if (!provider || !sessionId) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const projectPath = typeof record.projectPath === 'string' && record.projectPath.trim()
|
|
118
|
+
? record.projectPath
|
|
119
|
+
: (typeof record.cwd === 'string' && record.cwd.trim() ? record.cwd : null);
|
|
120
|
+
|
|
76
121
|
return {
|
|
77
|
-
id:
|
|
78
|
-
sessionId
|
|
79
|
-
provider
|
|
122
|
+
id: sessionId,
|
|
123
|
+
sessionId,
|
|
124
|
+
provider,
|
|
125
|
+
__provider: provider,
|
|
80
126
|
source: 'acp',
|
|
81
127
|
runtime: 'acp',
|
|
82
|
-
projectPath
|
|
83
|
-
cwd:
|
|
84
|
-
title: record.title ||
|
|
128
|
+
projectPath,
|
|
129
|
+
cwd: projectPath,
|
|
130
|
+
title: record.title || sessionId,
|
|
85
131
|
summary: record.summary || null,
|
|
86
132
|
model: record.model || null,
|
|
87
133
|
createdAt: record.createdAt || null,
|
|
@@ -93,71 +139,132 @@ function createSessionSummary(record) {
|
|
|
93
139
|
};
|
|
94
140
|
}
|
|
95
141
|
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return '';
|
|
100
|
-
}
|
|
142
|
+
function normalizeIndexSession(value) {
|
|
143
|
+
return createSessionSummary(value);
|
|
144
|
+
}
|
|
101
145
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
) {
|
|
108
|
-
return '';
|
|
109
|
-
}
|
|
146
|
+
function getIndexSessionKey(session) {
|
|
147
|
+
const provider = normalizeProvider(session?.provider || session?.__provider);
|
|
148
|
+
const sessionId = normalizeSessionId(session?.sessionId || session?.id);
|
|
149
|
+
return provider && sessionId ? `${provider}\u0000${sessionId}` : null;
|
|
150
|
+
}
|
|
110
151
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
152
|
+
function uniqueSessionSummaries(sessions = []) {
|
|
153
|
+
const byKey = new Map();
|
|
154
|
+
for (const session of sessions) {
|
|
155
|
+
const summary = normalizeIndexSession(session);
|
|
156
|
+
const key = getIndexSessionKey(summary);
|
|
157
|
+
if (!key) {
|
|
158
|
+
continue;
|
|
116
159
|
}
|
|
117
|
-
|
|
160
|
+
byKey.set(key, summary);
|
|
118
161
|
}
|
|
162
|
+
return sortSessionsByActivity(Array.from(byKey.values()));
|
|
163
|
+
}
|
|
119
164
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (dynamicContextMatch) {
|
|
124
|
-
return String(dynamicContextMatch[1] || '').trim();
|
|
165
|
+
function normalizeSessionIndex(index) {
|
|
166
|
+
if (!index || typeof index !== 'object' || !Array.isArray(index.sessions)) {
|
|
167
|
+
return null;
|
|
125
168
|
}
|
|
126
169
|
|
|
127
|
-
return
|
|
170
|
+
return {
|
|
171
|
+
version: SESSION_INDEX_VERSION,
|
|
172
|
+
updatedAt: typeof index.updatedAt === 'string' ? index.updatedAt : null,
|
|
173
|
+
sessions: uniqueSessionSummaries(index.sessions)
|
|
174
|
+
};
|
|
128
175
|
}
|
|
129
176
|
|
|
130
|
-
function
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
177
|
+
async function writeSessionIndex(sessions = []) {
|
|
178
|
+
const index = {
|
|
179
|
+
version: SESSION_INDEX_VERSION,
|
|
180
|
+
updatedAt: new Date().toISOString(),
|
|
181
|
+
sessions: uniqueSessionSummaries(sessions)
|
|
182
|
+
};
|
|
135
183
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
184
|
+
await ensureSessionRootDir();
|
|
185
|
+
await writeJsonFileAtomically(getSessionIndexFilePath(), `${JSON.stringify(index, null, 2)}\n`);
|
|
186
|
+
return index;
|
|
139
187
|
}
|
|
140
188
|
|
|
141
|
-
async function
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
189
|
+
async function readSessionIndex() {
|
|
190
|
+
const index = await readJsonFile(getSessionIndexFilePath());
|
|
191
|
+
return normalizeSessionIndex(index);
|
|
192
|
+
}
|
|
145
193
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
194
|
+
async function scanSessionRecordSummaries({ provider = null } = {}) {
|
|
195
|
+
const providers = provider ? [normalizeProvider(provider)] : SUPPORTED_SESSION_PROVIDERS;
|
|
196
|
+
const sessions = [];
|
|
197
|
+
|
|
198
|
+
for (const currentProvider of providers) {
|
|
199
|
+
if (!currentProvider) {
|
|
149
200
|
continue;
|
|
150
201
|
}
|
|
151
202
|
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
203
|
+
const filePaths = await listProviderSessionFiles(currentProvider);
|
|
204
|
+
for (const filePath of filePaths) {
|
|
205
|
+
const record = await readJsonFile(filePath);
|
|
206
|
+
const summary = createSessionSummary(record);
|
|
207
|
+
if (summary) {
|
|
208
|
+
sessions.push(summary);
|
|
209
|
+
}
|
|
157
210
|
}
|
|
158
211
|
}
|
|
159
212
|
|
|
160
|
-
return
|
|
213
|
+
return uniqueSessionSummaries(sessions);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function rebuildSessionIndex() {
|
|
217
|
+
const sessions = await scanSessionRecordSummaries();
|
|
218
|
+
return writeSessionIndex(sessions);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function readOrRebuildSessionIndex() {
|
|
222
|
+
const index = await readSessionIndex();
|
|
223
|
+
return index || rebuildSessionIndex();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function queueSessionIndexMutation(operation) {
|
|
227
|
+
const currentMutation = sessionIndexMutationQueue
|
|
228
|
+
.catch(() => {})
|
|
229
|
+
.then(operation);
|
|
230
|
+
|
|
231
|
+
sessionIndexMutationQueue = currentMutation.catch(() => {});
|
|
232
|
+
return currentMutation;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function upsertSessionIndexEntry(record) {
|
|
236
|
+
return queueSessionIndexMutation(async () => {
|
|
237
|
+
const summary = createSessionSummary(record);
|
|
238
|
+
if (!summary) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const index = await readOrRebuildSessionIndex();
|
|
243
|
+
const key = getIndexSessionKey(summary);
|
|
244
|
+
const nextSessions = (index.sessions || []).filter((session) => getIndexSessionKey(session) !== key);
|
|
245
|
+
nextSessions.push(summary);
|
|
246
|
+
await writeSessionIndex(nextSessions);
|
|
247
|
+
return summary;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export async function removeAcpSessionIndexEntry(provider, sessionId) {
|
|
252
|
+
return queueSessionIndexMutation(async () => {
|
|
253
|
+
const normalizedProvider = normalizeProvider(provider);
|
|
254
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
255
|
+
if (!normalizedProvider || !normalizedSessionId) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const index = await readOrRebuildSessionIndex();
|
|
260
|
+
const removeKey = `${normalizedProvider}\u0000${normalizedSessionId}`;
|
|
261
|
+
const nextSessions = (index.sessions || []).filter((session) => getIndexSessionKey(session) !== removeKey);
|
|
262
|
+
const removed = nextSessions.length !== index.sessions.length;
|
|
263
|
+
if (removed) {
|
|
264
|
+
await writeSessionIndex(nextSessions);
|
|
265
|
+
}
|
|
266
|
+
return removed;
|
|
267
|
+
});
|
|
161
268
|
}
|
|
162
269
|
|
|
163
270
|
function getSessionActivityTime(record) {
|
|
@@ -181,14 +288,7 @@ export async function readAcpSessionRecord(provider, sessionId) {
|
|
|
181
288
|
return record && typeof record === 'object' ? record : null;
|
|
182
289
|
}
|
|
183
290
|
|
|
184
|
-
|
|
185
|
-
const normalizedProvider = normalizeProvider(record?.provider);
|
|
186
|
-
const normalizedSessionId = normalizeSessionId(record?.sessionId);
|
|
187
|
-
|
|
188
|
-
if (!normalizedProvider || !normalizedSessionId) {
|
|
189
|
-
throw new Error('provider and sessionId are required to persist an ACP session');
|
|
190
|
-
}
|
|
191
|
-
|
|
291
|
+
async function writeAcpSessionRecordUnlocked(record, normalizedProvider, normalizedSessionId) {
|
|
192
292
|
const now = new Date().toISOString();
|
|
193
293
|
const existing = await readAcpSessionRecord(normalizedProvider, normalizedSessionId);
|
|
194
294
|
const nextRecord = {
|
|
@@ -211,11 +311,37 @@ export async function writeAcpSessionRecord(record) {
|
|
|
211
311
|
|
|
212
312
|
await ensureProviderDir(normalizedProvider);
|
|
213
313
|
const filePath = getSessionFilePath(normalizedProvider, normalizedSessionId);
|
|
214
|
-
await
|
|
314
|
+
await writeJsonFileAtomically(filePath, `${JSON.stringify(nextRecord, null, 2)}\n`);
|
|
315
|
+
await upsertSessionIndexEntry(nextRecord);
|
|
215
316
|
|
|
216
317
|
return nextRecord;
|
|
217
318
|
}
|
|
218
319
|
|
|
320
|
+
export async function writeAcpSessionRecord(record) {
|
|
321
|
+
const normalizedProvider = normalizeProvider(record?.provider);
|
|
322
|
+
const normalizedSessionId = normalizeSessionId(record?.sessionId);
|
|
323
|
+
|
|
324
|
+
if (!normalizedProvider || !normalizedSessionId) {
|
|
325
|
+
throw new Error('provider and sessionId are required to persist an ACP session');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const queueKey = `${normalizedProvider}\u0000${normalizedSessionId}`;
|
|
329
|
+
const previousWrite = sessionRecordWriteQueues.get(queueKey) || Promise.resolve();
|
|
330
|
+
const currentWrite = previousWrite
|
|
331
|
+
.catch(() => {})
|
|
332
|
+
.then(() => writeAcpSessionRecordUnlocked(record, normalizedProvider, normalizedSessionId));
|
|
333
|
+
|
|
334
|
+
sessionRecordWriteQueues.set(queueKey, currentWrite);
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
return await currentWrite;
|
|
338
|
+
} finally {
|
|
339
|
+
if (sessionRecordWriteQueues.get(queueKey) === currentWrite) {
|
|
340
|
+
sessionRecordWriteQueues.delete(queueKey);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
219
345
|
export async function touchAcpSessionRecord(record) {
|
|
220
346
|
return writeAcpSessionRecord(record);
|
|
221
347
|
}
|
|
@@ -232,13 +358,45 @@ export async function closeAcpSessionRecord(provider, sessionId, updates = {}) {
|
|
|
232
358
|
});
|
|
233
359
|
}
|
|
234
360
|
|
|
361
|
+
async function deleteAcpSessionRecordUnlocked(normalizedProvider, normalizedSessionId) {
|
|
362
|
+
const existing = await readAcpSessionRecord(normalizedProvider, normalizedSessionId);
|
|
363
|
+
await fs.rm(getSessionFilePath(normalizedProvider, normalizedSessionId), { force: true });
|
|
364
|
+
await removeAcpSessionIndexEntry(normalizedProvider, normalizedSessionId);
|
|
365
|
+
return Boolean(existing);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export async function deleteAcpSessionRecord(provider, sessionId) {
|
|
369
|
+
const normalizedProvider = normalizeProvider(provider);
|
|
370
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
371
|
+
|
|
372
|
+
if (!normalizedProvider || !normalizedSessionId) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const queueKey = `${normalizedProvider}\u0000${normalizedSessionId}`;
|
|
377
|
+
const previousWrite = sessionRecordWriteQueues.get(queueKey) || Promise.resolve();
|
|
378
|
+
const currentWrite = previousWrite
|
|
379
|
+
.catch(() => {})
|
|
380
|
+
.then(() => deleteAcpSessionRecordUnlocked(normalizedProvider, normalizedSessionId));
|
|
381
|
+
|
|
382
|
+
sessionRecordWriteQueues.set(queueKey, currentWrite);
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
return await currentWrite;
|
|
386
|
+
} finally {
|
|
387
|
+
if (sessionRecordWriteQueues.get(queueKey) === currentWrite) {
|
|
388
|
+
sessionRecordWriteQueues.delete(queueKey);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
235
393
|
export async function gcOldAcpSessions({ provider = null, maxAgeDays = DEFAULT_SESSION_GC_MAX_AGE_DAYS, includeOpen = false } = {}) {
|
|
236
394
|
const normalizedMaxAgeDays = Number(maxAgeDays);
|
|
237
395
|
if (!Number.isFinite(normalizedMaxAgeDays) || normalizedMaxAgeDays <= 0) {
|
|
238
396
|
return { removed: 0 };
|
|
239
397
|
}
|
|
240
398
|
|
|
241
|
-
const providers = provider ? [normalizeProvider(provider)] :
|
|
399
|
+
const providers = provider ? [normalizeProvider(provider)] : SUPPORTED_SESSION_PROVIDERS;
|
|
242
400
|
const cutoffTime = Date.now() - (normalizedMaxAgeDays * 24 * 60 * 60 * 1000);
|
|
243
401
|
let removed = 0;
|
|
244
402
|
|
|
@@ -264,6 +422,7 @@ export async function gcOldAcpSessions({ provider = null, maxAgeDays = DEFAULT_S
|
|
|
264
422
|
}
|
|
265
423
|
|
|
266
424
|
await fs.rm(filePath, { force: true });
|
|
425
|
+
await removeAcpSessionIndexEntry(currentProvider, record.sessionId).catch(() => {});
|
|
267
426
|
removed += 1;
|
|
268
427
|
}
|
|
269
428
|
}
|
|
@@ -273,35 +432,19 @@ export async function gcOldAcpSessions({ provider = null, maxAgeDays = DEFAULT_S
|
|
|
273
432
|
|
|
274
433
|
export async function listAcpSessions({ provider = null, projectPath = null } = {}) {
|
|
275
434
|
await gcOldAcpSessions({ provider }).catch(() => {});
|
|
276
|
-
const providers = provider ? [normalizeProvider(provider)] :
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
continue;
|
|
435
|
+
const providers = new Set(provider ? [normalizeProvider(provider)] : SUPPORTED_SESSION_PROVIDERS);
|
|
436
|
+
const index = await readOrRebuildSessionIndex();
|
|
437
|
+
const sessions = (index.sessions || []).filter((session) => {
|
|
438
|
+
if (!providers.has(normalizeProvider(session.provider))) {
|
|
439
|
+
return false;
|
|
282
440
|
}
|
|
283
441
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const record = await readJsonFile(filePath);
|
|
287
|
-
const hydratedRecord = record && !record.summary
|
|
288
|
-
? {
|
|
289
|
-
...record,
|
|
290
|
-
summary: await deriveSummaryFromMirroredEvents(record)
|
|
291
|
-
}
|
|
292
|
-
: record;
|
|
293
|
-
const summary = createSessionSummary(hydratedRecord);
|
|
294
|
-
if (!summary) {
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (projectPath && summary.projectPath !== projectPath) {
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
sessions.push(summary);
|
|
442
|
+
if (projectPath && session.projectPath !== projectPath) {
|
|
443
|
+
return false;
|
|
303
444
|
}
|
|
304
|
-
|
|
445
|
+
|
|
446
|
+
return true;
|
|
447
|
+
});
|
|
305
448
|
|
|
306
449
|
return sortSessionsByActivity(sessions);
|
|
307
450
|
}
|
|
@@ -316,7 +459,7 @@ export async function findAcpSessionRecord(sessionId, provider = null) {
|
|
|
316
459
|
return readAcpSessionRecord(provider, normalizedSessionId);
|
|
317
460
|
}
|
|
318
461
|
|
|
319
|
-
const providers =
|
|
462
|
+
const providers = SUPPORTED_SESSION_PROVIDERS;
|
|
320
463
|
for (const currentProvider of providers) {
|
|
321
464
|
const record = await readAcpSessionRecord(currentProvider, normalizedSessionId);
|
|
322
465
|
if (record) {
|
package/server/database/db.js
CHANGED
|
@@ -79,9 +79,18 @@ function normalizeState(rawState) {
|
|
|
79
79
|
|
|
80
80
|
function persistStateSync(nextState) {
|
|
81
81
|
ensureDataDirectory();
|
|
82
|
-
const tempPath = `${DATA_FILE_PATH}.tmp`;
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const tempPath = `${DATA_FILE_PATH}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
83
|
+
try {
|
|
84
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(nextState, null, 2)}\n`, 'utf8');
|
|
85
|
+
fs.renameSync(tempPath, DATA_FILE_PATH);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
try {
|
|
88
|
+
fs.rmSync(tempPath, { force: true });
|
|
89
|
+
} catch {
|
|
90
|
+
// Best-effort cleanup for failed atomic writes.
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
85
94
|
stateCache = nextState;
|
|
86
95
|
}
|
|
87
96
|
|