@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,53 +1,64 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
-
import crypto from 'node:crypto';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
getGeminiSessionMetadata,
|
|
8
|
-
getOpencodeSessionMetadata,
|
|
9
|
-
getProjects,
|
|
4
|
+
getLocalProviderSessionMessages,
|
|
5
|
+
getLocalProviderSessions,
|
|
10
6
|
getProjectsList
|
|
11
7
|
} from '../projects.js';
|
|
12
8
|
import { discoverAllProviders, discoverProvider } from '../session-core/providerDiscovery.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
9
|
+
import { probeAgentCapabilities } from '../acp-runtime/index.js';
|
|
10
|
+
import { mergeAgentCommandOverrides } from '../acp-runtime/command-overrides.js';
|
|
11
|
+
import { findAcpSessionRecord } from '../acp-runtime/session-store.js';
|
|
12
|
+
import { runRegistry as defaultRunRegistry } from '../session-core/runRegistry.js';
|
|
13
|
+
import { normalizeLegacyHistoryEntries } from '../../shared/conversationEvents.js';
|
|
16
14
|
|
|
17
15
|
const router = express.Router();
|
|
16
|
+
const ASSISTANT_TEXT_EVENT_KINDS = new Set([
|
|
17
|
+
'assistant_text_start',
|
|
18
|
+
'assistant_text_delta',
|
|
19
|
+
'assistant_text_end'
|
|
20
|
+
]);
|
|
18
21
|
|
|
19
22
|
router.use((req, res, next) => {
|
|
20
23
|
res.setHeader('X-Runtime-Engine', 'acp');
|
|
21
24
|
next();
|
|
22
25
|
});
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
function getRequestAgentCommandOverrides(req) {
|
|
28
|
+
return mergeAgentCommandOverrides(
|
|
29
|
+
process.env.AXHUB_ACP_COMMAND_OVERRIDES,
|
|
30
|
+
req?.get?.('X-Axhub-Acp-Command-Overrides')
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function flattenProjectSessions(project, options = {}) {
|
|
25
35
|
const projectPath = project.fullPath || project.path || null;
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return mergeSessionLists(legacySessions, normalizedAcpSessions);
|
|
36
|
+
const sessionGroups = await Promise.all(
|
|
37
|
+
['claude', 'codex', 'gemini', 'opencode'].map(async (provider) => {
|
|
38
|
+
return getLocalProviderSessions({
|
|
39
|
+
provider,
|
|
40
|
+
projectName: project.name,
|
|
41
|
+
projectPath,
|
|
42
|
+
limit: 0
|
|
43
|
+
});
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return sessionGroups
|
|
48
|
+
.flat()
|
|
49
|
+
.sort((left, right) => {
|
|
50
|
+
const leftTime = new Date(left?.lastActivity || left?.updatedAt || left?.createdAt || 0).getTime();
|
|
51
|
+
const rightTime = new Date(right?.lastActivity || right?.updatedAt || right?.createdAt || 0).getTime();
|
|
52
|
+
return rightTime - leftTime;
|
|
53
|
+
});
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
async function resolveProviderSessionRoute({
|
|
48
57
|
provider,
|
|
49
58
|
sessionId,
|
|
50
|
-
|
|
59
|
+
projectPath = null,
|
|
60
|
+
projectList = null,
|
|
61
|
+
agentCommandOverrides = {}
|
|
51
62
|
}) {
|
|
52
63
|
const normalizedProvider = String(provider || '').trim().toLowerCase();
|
|
53
64
|
const normalizedSessionId = String(sessionId || '').trim();
|
|
@@ -69,8 +80,8 @@ async function resolveProviderSessionRoute({
|
|
|
69
80
|
return path.normalize(withoutWindowsLongPathPrefix);
|
|
70
81
|
};
|
|
71
82
|
|
|
72
|
-
const findProjectByPath = (
|
|
73
|
-
const normalizedProjectPath = normalizeComparableProjectPath(
|
|
83
|
+
const findProjectByPath = (candidateProjectPath) => {
|
|
84
|
+
const normalizedProjectPath = normalizeComparableProjectPath(candidateProjectPath);
|
|
74
85
|
if (!normalizedProjectPath) {
|
|
75
86
|
return null;
|
|
76
87
|
}
|
|
@@ -81,81 +92,60 @@ async function resolveProviderSessionRoute({
|
|
|
81
92
|
}) || null;
|
|
82
93
|
};
|
|
83
94
|
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
let resolvedSource = acpRecord ? 'acp' : 'legacy';
|
|
89
|
-
|
|
90
|
-
if (!matchedProject) {
|
|
91
|
-
switch (normalizedProvider) {
|
|
92
|
-
case 'claude': {
|
|
93
|
-
const metadata = await getClaudeSessionMetadata(normalizedSessionId);
|
|
94
|
-
matchedProject = findProjectByPath(metadata?.cwd || null);
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
case 'codex': {
|
|
98
|
-
const metadata = await getCodexSessionMetadata(normalizedSessionId);
|
|
99
|
-
matchedProject = findProjectByPath(metadata?.cwd || null);
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
case 'gemini': {
|
|
103
|
-
const metadata = await getGeminiSessionMetadata(normalizedSessionId);
|
|
104
|
-
const projectHash = String(metadata?.projectHash || '').trim();
|
|
105
|
-
|
|
106
|
-
if (projectHash) {
|
|
107
|
-
matchedProject = projects.find((project) => {
|
|
108
|
-
const projectPath = normalizeComparableProjectPath(project.fullPath || project.path || '');
|
|
109
|
-
if (!projectPath) {
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const candidateHash = crypto.createHash('sha256').update(projectPath).digest('hex');
|
|
114
|
-
return candidateHash === projectHash;
|
|
115
|
-
}) || null;
|
|
116
|
-
}
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
case 'opencode': {
|
|
120
|
-
const metadata = await getOpencodeSessionMetadata(normalizedSessionId);
|
|
121
|
-
matchedProject = findProjectByPath(metadata?.cwd || null);
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
default:
|
|
125
|
-
break;
|
|
95
|
+
const findProjectByRouteContext = (routeProjectContext) => {
|
|
96
|
+
const matchedByPath = findProjectByPath(routeProjectContext);
|
|
97
|
+
if (matchedByPath) {
|
|
98
|
+
return matchedByPath;
|
|
126
99
|
}
|
|
127
|
-
}
|
|
128
100
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
101
|
+
const normalizedContext = normalizeComparableProjectPath(routeProjectContext);
|
|
102
|
+
if (!normalizedContext) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
132
105
|
|
|
133
|
-
|
|
134
|
-
|
|
106
|
+
return projects.find((project) => [
|
|
107
|
+
project.name,
|
|
108
|
+
project.displayName
|
|
109
|
+
].some((candidate) => normalizeComparableProjectPath(candidate) === normalizedContext)) || null;
|
|
110
|
+
};
|
|
135
111
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
projectPath: matchedProject.fullPath || matchedProject.path,
|
|
140
|
-
limit: 1000,
|
|
141
|
-
offset: 0
|
|
142
|
-
});
|
|
143
|
-
} else {
|
|
144
|
-
sessions = await adapter.listSessions({
|
|
145
|
-
projectPath: matchedProject.fullPath || matchedProject.path,
|
|
146
|
-
limit: 0
|
|
147
|
-
});
|
|
148
|
-
}
|
|
112
|
+
const acpRecord = await findAcpSessionRecord(normalizedSessionId, normalizedProvider);
|
|
113
|
+
const matchedProject = findProjectByPath(acpRecord?.projectPath || null)
|
|
114
|
+
|| (!acpRecord ? findProjectByRouteContext(projectPath) : null);
|
|
149
115
|
|
|
150
|
-
|
|
151
|
-
if (!matchedSession) {
|
|
116
|
+
if (!matchedProject) {
|
|
152
117
|
return null;
|
|
153
118
|
}
|
|
154
119
|
|
|
120
|
+
const resolvedProjectPath = acpRecord?.projectPath
|
|
121
|
+
|| matchedProject.fullPath
|
|
122
|
+
|| matchedProject.path
|
|
123
|
+
|| projectPath
|
|
124
|
+
|| null;
|
|
125
|
+
const resolvedSession = {
|
|
126
|
+
id: normalizedSessionId,
|
|
127
|
+
sessionId: normalizedSessionId,
|
|
128
|
+
provider: normalizedProvider,
|
|
129
|
+
__provider: normalizedProvider,
|
|
130
|
+
source: 'acp',
|
|
131
|
+
runtime: 'acp',
|
|
132
|
+
projectPath: resolvedProjectPath,
|
|
133
|
+
cwd: resolvedProjectPath,
|
|
134
|
+
title: acpRecord?.title || acpRecord?.summary || normalizedSessionId,
|
|
135
|
+
summary: acpRecord?.summary || null,
|
|
136
|
+
model: acpRecord?.model || null,
|
|
137
|
+
createdAt: acpRecord?.createdAt || null,
|
|
138
|
+
updatedAt: acpRecord?.updatedAt || null,
|
|
139
|
+
lastActivity: acpRecord?.lastActivity || acpRecord?.updatedAt || acpRecord?.createdAt || null,
|
|
140
|
+
lastPromptAt: acpRecord?.lastPromptAt || null,
|
|
141
|
+
closedAt: acpRecord?.closedAt || null,
|
|
142
|
+
isClosed: Boolean(acpRecord?.isClosed)
|
|
143
|
+
};
|
|
144
|
+
|
|
155
145
|
return {
|
|
156
146
|
provider: normalizedProvider,
|
|
157
|
-
source:
|
|
158
|
-
session:
|
|
147
|
+
source: resolvedSession.source || 'acp',
|
|
148
|
+
session: resolvedSession,
|
|
159
149
|
project: {
|
|
160
150
|
name: matchedProject.name,
|
|
161
151
|
fullPath: matchedProject.fullPath || matchedProject.path,
|
|
@@ -165,6 +155,236 @@ async function resolveProviderSessionRoute({
|
|
|
165
155
|
};
|
|
166
156
|
}
|
|
167
157
|
|
|
158
|
+
function normalizeComparableText(value) {
|
|
159
|
+
return String(value || '').replace(/\r\n/g, '\n').trim();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function normalizeNullableString(value) {
|
|
163
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getAssistantTextGroupKey(event, index) {
|
|
167
|
+
return normalizeNullableString(event?.payload?.messageId || event?.extensions?.messageId)
|
|
168
|
+
|| `event:${event?.eventId || index}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function collectAssistantTextGroups(events = []) {
|
|
172
|
+
const groups = new Map();
|
|
173
|
+
(Array.isArray(events) ? events : []).forEach((event, index) => {
|
|
174
|
+
if (!ASSISTANT_TEXT_EVENT_KINDS.has(event?.kind)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const key = getAssistantTextGroupKey(event, index);
|
|
179
|
+
const group = groups.get(key) || {
|
|
180
|
+
events: [],
|
|
181
|
+
text: ''
|
|
182
|
+
};
|
|
183
|
+
group.events.push(event);
|
|
184
|
+
if (event.kind === 'assistant_text_delta') {
|
|
185
|
+
group.text += String(event.payload?.text || '');
|
|
186
|
+
}
|
|
187
|
+
groups.set(key, group);
|
|
188
|
+
});
|
|
189
|
+
return groups;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isAssistantTextCoveredByRunTexts(text, runAssistantTexts) {
|
|
193
|
+
const normalizedText = normalizeComparableText(text);
|
|
194
|
+
if (!normalizedText || !(runAssistantTexts instanceof Set) || runAssistantTexts.size === 0) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (runAssistantTexts.has(normalizedText)) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const parts = Array.from(runAssistantTexts)
|
|
203
|
+
.map((part) => normalizeComparableText(part))
|
|
204
|
+
.filter((part) => part && normalizedText.includes(part))
|
|
205
|
+
.sort((left, right) => right.length - left.length);
|
|
206
|
+
|
|
207
|
+
if (parts.length === 0) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const reachable = new Array(normalizedText.length + 1).fill(false);
|
|
212
|
+
reachable[0] = true;
|
|
213
|
+
for (let index = 0; index < normalizedText.length; index += 1) {
|
|
214
|
+
if (!reachable[index]) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
for (const part of parts) {
|
|
218
|
+
if (normalizedText.startsWith(part, index)) {
|
|
219
|
+
reachable[index + part.length] = true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return reachable[normalizedText.length] === true;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function collectRunRegistryConversationEvents({ provider, sessionId, runRegistry = defaultRunRegistry } = {}) {
|
|
228
|
+
if (!runRegistry || typeof runRegistry.listSessionRuns !== 'function' || typeof runRegistry.listRunEvents !== 'function') {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return runRegistry
|
|
233
|
+
.listSessionRuns({ provider, sessionId })
|
|
234
|
+
.flatMap((run) => runRegistry.listRunEvents(run.runId, { after: 0 }))
|
|
235
|
+
.filter((runEvent) => runEvent?.type === 'conversation-event' && runEvent.event)
|
|
236
|
+
.map((runEvent) => ({
|
|
237
|
+
...runEvent.event,
|
|
238
|
+
provider: runEvent.event.provider || runEvent.provider || provider,
|
|
239
|
+
sessionId: runEvent.event.sessionId || runEvent.sessionId || sessionId
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function collectUserMessageTimesByText(events = []) {
|
|
244
|
+
const timesByText = new Map();
|
|
245
|
+
(Array.isArray(events) ? events : []).forEach((event) => {
|
|
246
|
+
if (event?.kind !== 'user_message') {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const text = normalizeComparableText(event.payload?.text);
|
|
251
|
+
if (!text) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const timestamp = Date.parse(event.timestamp || '');
|
|
256
|
+
const times = timesByText.get(text) || [];
|
|
257
|
+
times.push(Number.isFinite(timestamp) ? timestamp : 0);
|
|
258
|
+
timesByText.set(text, times);
|
|
259
|
+
});
|
|
260
|
+
return timesByText;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function isDuplicateAcpUserReplay(event, runUserTimesByText) {
|
|
264
|
+
if (event?.kind !== 'user_message') {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const text = normalizeComparableText(event.payload?.text);
|
|
269
|
+
if (!text) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const runTimes = runUserTimesByText.get(text);
|
|
274
|
+
if (!Array.isArray(runTimes) || runTimes.length === 0) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const acpTime = Date.parse(event.timestamp || '');
|
|
279
|
+
if (!Number.isFinite(acpTime)) {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return runTimes.some((runTime) => runTime <= acpTime);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function getConversationEventDedupeKey(event) {
|
|
287
|
+
if (event?.eventId) {
|
|
288
|
+
return `id:${event.eventId}`;
|
|
289
|
+
}
|
|
290
|
+
return [
|
|
291
|
+
'shape',
|
|
292
|
+
event?.kind || '',
|
|
293
|
+
event?.provider || '',
|
|
294
|
+
event?.sessionId || '',
|
|
295
|
+
event?.timestamp || '',
|
|
296
|
+
JSON.stringify(event?.payload || {})
|
|
297
|
+
].join('\u0000');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function loadLocalSessionEventsResult(result, provider, sessionId) {
|
|
301
|
+
const rawMessages = Array.isArray(result)
|
|
302
|
+
? result
|
|
303
|
+
: Array.isArray(result?.messages)
|
|
304
|
+
? result.messages
|
|
305
|
+
: Array.isArray(result?.events)
|
|
306
|
+
? result.events
|
|
307
|
+
: [];
|
|
308
|
+
|
|
309
|
+
const events = normalizeLegacyHistoryEntries(rawMessages, provider, sessionId);
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
source: 'native-history',
|
|
313
|
+
events,
|
|
314
|
+
total: typeof result?.total === 'number' ? result.total : rawMessages.length,
|
|
315
|
+
hasMore: typeof result?.hasMore === 'boolean' ? result.hasMore : false,
|
|
316
|
+
offset: typeof result?.offset === 'number' ? result.offset : 0,
|
|
317
|
+
limit: Object.prototype.hasOwnProperty.call(result || {}, 'limit') ? result.limit : null,
|
|
318
|
+
...(result?.tokenUsage ? { tokenUsage: result.tokenUsage } : {})
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function mergeSessionEventsWithRunRegistryEvents(events = [], {
|
|
323
|
+
provider,
|
|
324
|
+
sessionId,
|
|
325
|
+
runRegistry = defaultRunRegistry
|
|
326
|
+
} = {}) {
|
|
327
|
+
const acpEvents = Array.isArray(events) ? events : [];
|
|
328
|
+
const runEvents = collectRunRegistryConversationEvents({ provider, sessionId, runRegistry });
|
|
329
|
+
|
|
330
|
+
if (runEvents.length === 0) {
|
|
331
|
+
return acpEvents;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const runAssistantTexts = new Set(
|
|
335
|
+
Array.from(collectAssistantTextGroups(runEvents).values())
|
|
336
|
+
.map((group) => normalizeComparableText(group.text))
|
|
337
|
+
.filter(Boolean)
|
|
338
|
+
);
|
|
339
|
+
const runUserTimesByText = collectUserMessageTimesByText(runEvents);
|
|
340
|
+
const duplicateAcpAssistantEventIds = new Set();
|
|
341
|
+
const duplicateAcpAssistantEventRefs = new Set();
|
|
342
|
+
|
|
343
|
+
for (const group of collectAssistantTextGroups(acpEvents).values()) {
|
|
344
|
+
const text = normalizeComparableText(group.text);
|
|
345
|
+
if (!text || !isAssistantTextCoveredByRunTexts(text, runAssistantTexts)) {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
group.events.forEach((event) => {
|
|
349
|
+
if (event?.eventId) {
|
|
350
|
+
duplicateAcpAssistantEventIds.add(event.eventId);
|
|
351
|
+
} else {
|
|
352
|
+
duplicateAcpAssistantEventRefs.add(event);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const filteredAcpEvents = acpEvents.filter((event) => !(
|
|
358
|
+
isDuplicateAcpUserReplay(event, runUserTimesByText) ||
|
|
359
|
+
(event?.eventId && duplicateAcpAssistantEventIds.has(event.eventId)) ||
|
|
360
|
+
duplicateAcpAssistantEventRefs.has(event)
|
|
361
|
+
));
|
|
362
|
+
const ordered = [...filteredAcpEvents, ...runEvents].map((event, index) => ({ event, index }));
|
|
363
|
+
ordered.sort((left, right) => {
|
|
364
|
+
const leftTime = Date.parse(left.event?.timestamp || '');
|
|
365
|
+
const rightTime = Date.parse(right.event?.timestamp || '');
|
|
366
|
+
const normalizedLeftTime = Number.isFinite(leftTime) ? leftTime : 0;
|
|
367
|
+
const normalizedRightTime = Number.isFinite(rightTime) ? rightTime : 0;
|
|
368
|
+
if (normalizedLeftTime !== normalizedRightTime) {
|
|
369
|
+
return normalizedLeftTime - normalizedRightTime;
|
|
370
|
+
}
|
|
371
|
+
return left.index - right.index;
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const seen = new Set();
|
|
375
|
+
const merged = [];
|
|
376
|
+
for (const { event } of ordered) {
|
|
377
|
+
const key = getConversationEventDedupeKey(event);
|
|
378
|
+
if (seen.has(key)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
seen.add(key);
|
|
382
|
+
merged.push(event);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return merged;
|
|
386
|
+
}
|
|
387
|
+
|
|
168
388
|
router.get('/providers', async (req, res) => {
|
|
169
389
|
try {
|
|
170
390
|
const providers = await discoverAllProviders({ projectPath: req.query.projectPath });
|
|
@@ -176,16 +396,33 @@ router.get('/providers', async (req, res) => {
|
|
|
176
396
|
|
|
177
397
|
router.get('/providers/:provider', async (req, res) => {
|
|
178
398
|
try {
|
|
179
|
-
const result = await discoverProvider(req.params.provider, {
|
|
399
|
+
const result = await discoverProvider(req.params.provider, {
|
|
400
|
+
projectPath: req.query.projectPath,
|
|
401
|
+
force: req.query.force
|
|
402
|
+
});
|
|
180
403
|
res.json({ success: true, ...result });
|
|
181
404
|
} catch (error) {
|
|
182
405
|
res.status(500).json({ success: false, error: error.message });
|
|
183
406
|
}
|
|
184
407
|
});
|
|
185
408
|
|
|
409
|
+
router.get('/providers/:provider/acp-capabilities', async (req, res) => {
|
|
410
|
+
try {
|
|
411
|
+
const agentCommandOverrides = getRequestAgentCommandOverrides(req);
|
|
412
|
+
const result = await probeAgentCapabilities(req.params.provider, {
|
|
413
|
+
projectPath: req.query.projectPath,
|
|
414
|
+
force: req.query.force,
|
|
415
|
+
...(Object.keys(agentCommandOverrides).length > 0 ? { agentCommandOverrides } : {}),
|
|
416
|
+
});
|
|
417
|
+
res.json({ success: true, provider: req.params.provider, capabilitySnapshot: result });
|
|
418
|
+
} catch (error) {
|
|
419
|
+
res.status(500).json({ success: false, provider: req.params.provider, error: error.message });
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
186
423
|
router.get('/projects/:projectName/history-index', async (req, res) => {
|
|
187
424
|
try {
|
|
188
|
-
const projects = await
|
|
425
|
+
const projects = await getProjectsList();
|
|
189
426
|
const project = projects.find((entry) => entry.name === req.params.projectName);
|
|
190
427
|
if (!project) {
|
|
191
428
|
return res.status(404).json({ success: false, error: 'Project not found' });
|
|
@@ -197,7 +434,9 @@ router.get('/projects/:projectName/history-index', async (req, res) => {
|
|
|
197
434
|
fullPath: project.fullPath || project.path,
|
|
198
435
|
displayName: project.displayName || project.name
|
|
199
436
|
},
|
|
200
|
-
sessions: await flattenProjectSessions(project
|
|
437
|
+
sessions: await flattenProjectSessions(project, {
|
|
438
|
+
agentCommandOverrides: getRequestAgentCommandOverrides(req)
|
|
439
|
+
})
|
|
201
440
|
});
|
|
202
441
|
} catch (error) {
|
|
203
442
|
res.status(500).json({ success: false, error: error.message });
|
|
@@ -215,11 +454,13 @@ router.get('/sessions/:provider/:sessionId/resolve', async (req, res) => {
|
|
|
215
454
|
|
|
216
455
|
const result = await resolveProviderSessionRoute({
|
|
217
456
|
provider: requestedProvider,
|
|
218
|
-
sessionId: requestedSessionId
|
|
457
|
+
sessionId: requestedSessionId,
|
|
458
|
+
projectPath: req.query.projectPath || req.query.cwd || req.query.workdir || req.query.project || req.query.projectName || null,
|
|
459
|
+
agentCommandOverrides: getRequestAgentCommandOverrides(req)
|
|
219
460
|
});
|
|
220
461
|
|
|
221
462
|
if (!result) {
|
|
222
|
-
return res.
|
|
463
|
+
return res.json({ success: true, found: false });
|
|
223
464
|
}
|
|
224
465
|
|
|
225
466
|
res.json({
|
|
@@ -234,19 +475,26 @@ router.get('/sessions/:provider/:sessionId/resolve', async (req, res) => {
|
|
|
234
475
|
|
|
235
476
|
router.get('/sessions/:provider/:sessionId/events', async (req, res) => {
|
|
236
477
|
try {
|
|
237
|
-
const adapter = getProviderAdapter(req.params.provider);
|
|
238
478
|
const parsedLimit = req.query.limit ? parseInt(req.query.limit, 10) : null;
|
|
239
479
|
const parsedOffset = req.query.offset ? parseInt(req.query.offset, 10) : 0;
|
|
240
|
-
const result = await
|
|
480
|
+
const result = loadLocalSessionEventsResult(await getLocalProviderSessionMessages({
|
|
481
|
+
provider: req.params.provider,
|
|
241
482
|
projectName: req.query.projectName,
|
|
242
|
-
projectPath: req.query.projectPath,
|
|
243
483
|
sessionId: req.params.sessionId,
|
|
244
484
|
limit: parsedLimit,
|
|
245
485
|
offset: parsedOffset
|
|
246
|
-
});
|
|
486
|
+
}), req.params.provider, req.params.sessionId);
|
|
247
487
|
|
|
248
|
-
const
|
|
249
|
-
const
|
|
488
|
+
const rawEvents = Array.isArray(result) ? result : (result?.events || result?.messages || []);
|
|
489
|
+
const events = mergeSessionEventsWithRunRegistryEvents(rawEvents, {
|
|
490
|
+
provider: req.params.provider,
|
|
491
|
+
sessionId: req.params.sessionId,
|
|
492
|
+
runRegistry: defaultRunRegistry
|
|
493
|
+
});
|
|
494
|
+
const total = Math.max(
|
|
495
|
+
typeof result?.total === 'number' ? result.total : rawEvents.length,
|
|
496
|
+
events.length
|
|
497
|
+
);
|
|
250
498
|
const hasMore = typeof result?.hasMore === 'boolean' ? result.hasMore : false;
|
|
251
499
|
const offset = typeof result?.offset === 'number' ? result.offset : parsedOffset;
|
|
252
500
|
const limit = Object.prototype.hasOwnProperty.call(result || {}, 'limit')
|
|
@@ -257,7 +505,7 @@ router.get('/sessions/:provider/:sessionId/events', async (req, res) => {
|
|
|
257
505
|
success: true,
|
|
258
506
|
provider: req.params.provider,
|
|
259
507
|
sessionId: req.params.sessionId,
|
|
260
|
-
source: result?.source || '
|
|
508
|
+
source: result?.source || 'acp',
|
|
261
509
|
events,
|
|
262
510
|
total,
|
|
263
511
|
hasMore,
|