@axhub/genie 0.2.6 → 0.2.7

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.
Files changed (120) hide show
  1. package/dist/assets/App-BWSqiXAT.js +220 -0
  2. package/dist/assets/App-DrlLKa8f.css +1 -0
  3. package/dist/assets/ReviewApp-nz3mbArg.js +1 -0
  4. package/dist/assets/{_basePickBy-DkiHsp3X.js → _basePickBy-C19AekOu.js} +1 -1
  5. package/dist/assets/{_baseUniq-7ElXb2sX.js → _baseUniq-JsnevLw_.js} +1 -1
  6. package/dist/assets/{arc-CEsS3MdK.js → arc-BLpcuBlf.js} +1 -1
  7. package/dist/assets/architectureDiagram-2XIMDMQ5-CarjBOOv.js +36 -0
  8. package/dist/assets/{blockDiagram-WCTKOSBZ-Cza6M6Ht.js → blockDiagram-WCTKOSBZ-DQBLwsUS.js} +3 -3
  9. package/dist/assets/c4Diagram-IC4MRINW-CGobwBIj.js +10 -0
  10. package/dist/assets/channel-DkFNxV_H.js +1 -0
  11. package/dist/assets/{chunk-4BX2VUAB--HkodwbY.js → chunk-4BX2VUAB-De63kbgc.js} +1 -1
  12. package/dist/assets/{chunk-55IACEB6-CyBuez4e.js → chunk-55IACEB6-DtTDDdM9.js} +1 -1
  13. package/dist/assets/{chunk-FMBD7UC4-CuzG4iAl.js → chunk-FMBD7UC4-DHuwd8tw.js} +1 -1
  14. package/dist/assets/{chunk-JSJVCQXG-BNi8S861.js → chunk-JSJVCQXG-BgytFtmO.js} +1 -1
  15. package/dist/assets/{chunk-KX2RTZJC-D817O-GT.js → chunk-KX2RTZJC-nZdp86aN.js} +1 -1
  16. package/dist/assets/chunk-NQ4KR5QH-CMH6EDP2.js +220 -0
  17. package/dist/assets/{chunk-QZHKN3VN-VMEn-zxh.js → chunk-QZHKN3VN-DvUQ3mnO.js} +1 -1
  18. package/dist/assets/chunk-WL4C6EOR-Dn7db_6t.js +189 -0
  19. package/dist/assets/classDiagram-VBA2DB6C-DtwCEe8S.js +1 -0
  20. package/dist/assets/classDiagram-v2-RAHNMMFH-DtwCEe8S.js +1 -0
  21. package/dist/assets/clone-C0lCEIEO.js +1 -0
  22. package/dist/assets/cose-bilkent-S5V4N54A-DD_nzqsz.js +1 -0
  23. package/dist/assets/cytoscape.esm-5J0xJHOV.js +321 -0
  24. package/dist/assets/{dagre-KLK3FWXG-Bqp7DjEa.js → dagre-KLK3FWXG-CHYIvW47.js} +1 -1
  25. package/dist/assets/diagram-E7M64L7V-TVdvHtGc.js +24 -0
  26. package/dist/assets/{diagram-IFDJBPK2--fHfW6V2.js → diagram-IFDJBPK2-Dzsiln_C.js} +1 -1
  27. package/dist/assets/{diagram-P4PSJMXO-D1kQI5RB.js → diagram-P4PSJMXO-DKnGbUpE.js} +1 -1
  28. package/dist/assets/erDiagram-INFDFZHY-5Kw0bByo.js +70 -0
  29. package/dist/assets/{flowDiagram-PKNHOUZH-DWeNr4yg.js → flowDiagram-PKNHOUZH-BAZ2-jKp.js} +4 -4
  30. package/dist/assets/ganttDiagram-A5KZAMGK-CsADFkcq.js +292 -0
  31. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-B5a8UWjN.js → gitGraphDiagram-K3NZZRJ6-BflpyjGy.js} +1 -1
  32. package/dist/assets/{graph-Cw1rYoD9.js → graph-suelaXFh.js} +1 -1
  33. package/dist/assets/highlighted-body-OFNGDK62-CZrBMazC.js +1 -0
  34. package/dist/assets/index-B01NxbUv.css +1 -0
  35. package/dist/assets/index-DW5pGgQ_.js +2 -0
  36. package/dist/assets/{infoDiagram-LFFYTUFH-D2u70rhN.js → infoDiagram-LFFYTUFH-pfD1FA3p.js} +1 -1
  37. package/dist/assets/ishikawaDiagram-PHBUUO56-ndm9snwO.js +70 -0
  38. package/dist/assets/journeyDiagram-4ABVD52K-HgF2t7z5.js +139 -0
  39. package/dist/assets/{kanban-definition-K7BYSVSG-DbVt0v29.js → kanban-definition-K7BYSVSG-FWinmur1.js} +5 -5
  40. package/dist/assets/{layout-W_tRx4UV.js → layout-vcz43XvZ.js} +1 -1
  41. package/dist/assets/{linear-CcMb2ay-.js → linear-le4gc0vx.js} +1 -1
  42. package/dist/assets/mermaid-GHXKKRXX-CK8m3lad.js +870 -0
  43. package/dist/assets/mindmap-definition-YRQLILUH-CNq9SKj4.js +68 -0
  44. package/dist/assets/{pieDiagram-SKSYHLDU-CDyJaACv.js → pieDiagram-SKSYHLDU-C7PKDh3b.js} +2 -2
  45. package/dist/assets/quadrantDiagram-337W2JSQ-B7FnztNO.js +7 -0
  46. package/dist/assets/requirementDiagram-Z7DCOOCP-Bl_BM2Th.js +73 -0
  47. package/dist/assets/{sankeyDiagram-WA2Y5GQK-Di1ShaMF.js → sankeyDiagram-WA2Y5GQK-4gulcOP4.js} +3 -3
  48. package/dist/assets/sequenceDiagram-2WXFIKYE-VEuJDwyJ.js +145 -0
  49. package/dist/assets/{stateDiagram-RAJIS63D-CVZYMqyW.js → stateDiagram-RAJIS63D-CB4Vl7qM.js} +1 -1
  50. package/dist/assets/stateDiagram-v2-FVOUBMTO-C85ucl39.js +1 -0
  51. package/dist/assets/timeline-definition-YZTLITO2-BPGKhi7f.js +61 -0
  52. package/dist/assets/{treemap-KZPCXAKY-CGG4gx3C.js → treemap-KZPCXAKY-DZSEE6Hz.js} +58 -58
  53. package/dist/assets/vendor-codemirror-CyOKkaQZ.js +31 -0
  54. package/dist/assets/vendor-react-CP4yFTs7.js +8 -0
  55. package/dist/assets/vendor-xterm-DfcmCpbH.js +66 -0
  56. package/dist/assets/{vennDiagram-LZ73GAT5-Dds37L2k.js → vennDiagram-LZ73GAT5-8E_G06fI.js} +4 -4
  57. package/dist/assets/xychartDiagram-JWTSCODW-CbBk50-O.js +7 -0
  58. package/dist/index.html +4 -4
  59. package/package.json +2 -1
  60. package/server/_legacy-providers/README.md +30 -0
  61. package/server/_legacy-providers/claude-sdk.js +956 -0
  62. package/server/_legacy-providers/gemini-cli.js +368 -0
  63. package/server/_legacy-providers/openai-codex.js +705 -0
  64. package/server/_legacy-providers/opencode-cli.js +674 -0
  65. package/server/acp-runtime/client.js +1805 -0
  66. package/server/acp-runtime/client.test.js +688 -0
  67. package/server/acp-runtime/index.js +419 -0
  68. package/server/acp-runtime/registry.js +45 -0
  69. package/server/acp-runtime/session-store.js +254 -0
  70. package/server/acp-runtime/session-store.test.js +89 -0
  71. package/server/channels/runtime/AgentRuntimeAdapter.js +21 -70
  72. package/server/claude-sdk.js +24 -944
  73. package/server/cli.js +4 -2
  74. package/server/external-agent/service.js +52 -63
  75. package/server/gemini-cli.js +23 -360
  76. package/server/index.js +47 -44
  77. package/server/openai-codex.js +24 -698
  78. package/server/opencode-cli.js +70 -640
  79. package/server/routes/agent.js +2 -0
  80. package/server/routes/git.js +3 -20
  81. package/server/routes/session-core.js +44 -10
  82. package/server/session-core/abortSession.js +2 -18
  83. package/server/session-core/eventStore.js +5 -1
  84. package/server/session-core/providerAdapters.js +98 -10
  85. package/server/session-core/runtimeState.js +16 -17
  86. package/server/session-core/runtimeWriter.js +19 -12
  87. package/shared/conversationEvents.js +347 -10
  88. package/shared/conversationEvents.test.js +403 -0
  89. package/dist/assets/App-CYTE30Cf.js +0 -484
  90. package/dist/assets/App-qxJ8_QYu.css +0 -32
  91. package/dist/assets/ReviewApp-BEicSBzW.js +0 -1
  92. package/dist/assets/architectureDiagram-2XIMDMQ5-BubZ7T3U.js +0 -36
  93. package/dist/assets/c4Diagram-IC4MRINW-jhjtOQ12.js +0 -10
  94. package/dist/assets/channel-RmqTALN0.js +0 -1
  95. package/dist/assets/chunk-NQ4KR5QH-DyujyOvx.js +0 -220
  96. package/dist/assets/chunk-WL4C6EOR-CQHHFLvx.js +0 -189
  97. package/dist/assets/classDiagram-VBA2DB6C-wvVV1ggz.js +0 -1
  98. package/dist/assets/classDiagram-v2-RAHNMMFH-wvVV1ggz.js +0 -1
  99. package/dist/assets/clone-oT5aWXpf.js +0 -1
  100. package/dist/assets/cose-bilkent-S5V4N54A-qykDd54p.js +0 -1
  101. package/dist/assets/cytoscape.esm-2ZfV8NB5.js +0 -331
  102. package/dist/assets/diagram-E7M64L7V-BKtx468K.js +0 -24
  103. package/dist/assets/erDiagram-INFDFZHY-DT9YzdNw.js +0 -70
  104. package/dist/assets/ganttDiagram-A5KZAMGK--IgwcUhI.js +0 -292
  105. package/dist/assets/highlighted-body-TPN3WLV5-BCxJHuqY.js +0 -1
  106. package/dist/assets/index-CBuAXA5S.js +0 -2
  107. package/dist/assets/index-CyLWKyxy.css +0 -1
  108. package/dist/assets/ishikawaDiagram-PHBUUO56-Cl8yrezU.js +0 -70
  109. package/dist/assets/journeyDiagram-4ABVD52K-ddP0AMU9.js +0 -139
  110. package/dist/assets/mermaid-O7DHMXV3-BBJqt8pT.js +0 -988
  111. package/dist/assets/mindmap-definition-YRQLILUH-BGhZa7Na.js +0 -68
  112. package/dist/assets/quadrantDiagram-337W2JSQ-BSYuqf0Q.js +0 -7
  113. package/dist/assets/requirementDiagram-Z7DCOOCP-Cfi9YX9H.js +0 -73
  114. package/dist/assets/sequenceDiagram-2WXFIKYE-CYTTG38e.js +0 -145
  115. package/dist/assets/stateDiagram-v2-FVOUBMTO-Bbl0b4-i.js +0 -1
  116. package/dist/assets/timeline-definition-YZTLITO2-B1sdb5mK.js +0 -61
  117. package/dist/assets/vendor-codemirror-Dz7_EqNA.js +0 -39
  118. package/dist/assets/vendor-react-Cpt6D04s.js +0 -59
  119. package/dist/assets/vendor-xterm-DfaPXD3y.js +0 -66
  120. package/dist/assets/xychartDiagram-JWTSCODW-C8QKSyRR.js +0 -7
@@ -97,6 +97,7 @@ router.post('/abort', validateExternalApiKey, async (req, res) => {
97
97
  return res.json({
98
98
  success: false,
99
99
  aborted: false,
100
+ runtime: 'acp',
100
101
  sessionId: normalized.sessionId,
101
102
  provider: normalized.provider,
102
103
  error: 'Active session not found'
@@ -106,6 +107,7 @@ router.post('/abort', validateExternalApiKey, async (req, res) => {
106
107
  return res.json({
107
108
  success: true,
108
109
  aborted: true,
110
+ runtime: 'acp',
109
111
  sessionId: normalized.sessionId,
110
112
  provider: normalized.provider,
111
113
  message: 'Session aborted'
@@ -610,26 +610,9 @@ Generate the commit message:`;
610
610
  const parsed = typeof data === 'string' ? JSON.parse(data) : data;
611
611
  console.log('🔍 Writer received message type:', parsed.type);
612
612
 
613
- // Handle different message formats from supported providers
614
- // Claude SDK sends: {type: 'claude-response', data: {message: {content: [...]}}}
615
- if (parsed.type === 'claude-response' && parsed.data) {
616
- const message = parsed.data.message || parsed.data;
617
- console.log('📦 Claude response message:', JSON.stringify(message, null, 2).substring(0, 500));
618
- if (message.content && Array.isArray(message.content)) {
619
- // Extract text from content array
620
- for (const item of message.content) {
621
- if (item.type === 'text' && item.text) {
622
- console.log('✅ Extracted text chunk:', item.text.substring(0, 100));
623
- responseText += item.text;
624
- }
625
- }
626
- }
627
- }
628
- else if (parsed.type === 'claude-response' && parsed.data?.type === 'content_block_delta' && parsed.data?.delta?.text) {
629
- responseText += parsed.data.delta.text;
630
- }
631
- // Also handle direct text messages
632
- else if (parsed.type === 'text' && parsed.text) {
613
+ if (parsed.type === 'conversation-event' && parsed.event?.kind === 'assistant_text_delta' && parsed.event?.payload?.text) {
614
+ responseText += parsed.event.payload.text;
615
+ } else if (parsed.type === 'text' && parsed.text) {
633
616
  console.log('✅ Direct text:', parsed.text.substring(0, 100));
634
617
  responseText += parsed.text;
635
618
  }
@@ -2,10 +2,17 @@ import express from 'express';
2
2
  import { getProjects } from '../projects.js';
3
3
  import { discoverAllProviders, discoverProvider } from '../session-core/providerDiscovery.js';
4
4
  import { getProviderAdapter } from '../session-core/providerAdapters.js';
5
+ import { listAcpSessions } from '../acp-runtime/session-store.js';
5
6
 
6
7
  const router = express.Router();
7
8
 
8
- function flattenProjectSessions(project) {
9
+ router.use((req, res, next) => {
10
+ res.setHeader('X-Runtime-Engine', 'acp');
11
+ next();
12
+ });
13
+
14
+ async function flattenProjectSessions(project) {
15
+ const projectPath = project.fullPath || project.path || null;
9
16
  const groups = [
10
17
  { provider: 'claude', items: project.sessions || [] },
11
18
  { provider: 'codex', items: project.codexSessions || [] },
@@ -13,7 +20,12 @@ function flattenProjectSessions(project) {
13
20
  { provider: 'opencode', items: project.opencodeSessions || [] }
14
21
  ];
15
22
 
16
- return groups.flatMap(({ provider, items }) => items.map((item) => ({ ...item, provider, __provider: provider })))
23
+ const acpSessions = await listAcpSessions({ projectPath });
24
+
25
+ return [
26
+ ...groups.flatMap(({ provider, items }) => items.map((item) => ({ ...item, provider, __provider: provider, source: item?.source || 'legacy' }))),
27
+ ...acpSessions.map((item) => ({ ...item, provider: item.provider, __provider: item.provider, source: 'acp' }))
28
+ ]
17
29
  .sort((a, b) => new Date(b.lastActivity || b.updated_at || b.createdAt || 0) - new Date(a.lastActivity || a.updated_at || a.createdAt || 0));
18
30
  }
19
31
 
@@ -42,7 +54,15 @@ router.get('/projects/:projectName/history-index', async (req, res) => {
42
54
  if (!project) {
43
55
  return res.status(404).json({ success: false, error: 'Project not found' });
44
56
  }
45
- res.json({ success: true, project: { name: project.name, fullPath: project.fullPath || project.path, displayName: project.displayName || project.name }, sessions: flattenProjectSessions(project) });
57
+ res.json({
58
+ success: true,
59
+ project: {
60
+ name: project.name,
61
+ fullPath: project.fullPath || project.path,
62
+ displayName: project.displayName || project.name
63
+ },
64
+ sessions: await flattenProjectSessions(project)
65
+ });
46
66
  } catch (error) {
47
67
  res.status(500).json({ success: false, error: error.message });
48
68
  }
@@ -66,19 +86,26 @@ router.get('/sessions/:sessionId/resolve', async (req, res) => {
66
86
  'opencode'
67
87
  ].filter((provider, index, all) => provider && all.indexOf(provider) === index);
68
88
 
69
- const directProjectMatch = projects.find((project) => {
70
- const flattened = flattenProjectSessions(project);
71
- return flattened.some((session) => session.id === requestedSessionId);
72
- });
89
+ let directProjectMatch = null;
90
+ let directMatchSessions = [];
91
+
92
+ for (const project of projects) {
93
+ const flattened = await flattenProjectSessions(project);
94
+ if (flattened.some((session) => session.id === requestedSessionId)) {
95
+ directProjectMatch = project;
96
+ directMatchSessions = flattened;
97
+ break;
98
+ }
99
+ }
73
100
 
74
101
  if (directProjectMatch) {
75
- const flattened = flattenProjectSessions(directProjectMatch);
76
- const matchedSession = flattened.find((session) => session.id === requestedSessionId);
102
+ const matchedSession = directMatchSessions.find((session) => session.id === requestedSessionId);
77
103
 
78
104
  return res.json({
79
105
  success: true,
80
106
  found: true,
81
107
  provider: matchedSession.provider,
108
+ source: matchedSession.source || 'legacy',
82
109
  session: matchedSession,
83
110
  project: {
84
111
  name: directProjectMatch.name,
@@ -97,7 +124,12 @@ router.get('/sessions/:sessionId/resolve', async (req, res) => {
97
124
 
98
125
  try {
99
126
  if (provider === 'claude') {
100
- const result = await adapter.listSessions({ projectName: project.name, limit: 1000, offset: 0 });
127
+ const result = await adapter.listSessions({
128
+ projectName: project.name,
129
+ projectPath: project.fullPath || project.path,
130
+ limit: 1000,
131
+ offset: 0
132
+ });
101
133
  sessions = Array.isArray(result) ? result : [];
102
134
  } else {
103
135
  sessions = await adapter.listSessions({ projectPath: project.fullPath || project.path, limit: 0 });
@@ -115,6 +147,7 @@ router.get('/sessions/:sessionId/resolve', async (req, res) => {
115
147
  success: true,
116
148
  found: true,
117
149
  provider,
150
+ source: matchedSession.source || 'legacy',
118
151
  session: matchedSession,
119
152
  project: {
120
153
  name: project.name,
@@ -157,6 +190,7 @@ router.get('/sessions/:provider/:sessionId/events', async (req, res) => {
157
190
  success: true,
158
191
  provider: req.params.provider,
159
192
  sessionId: req.params.sessionId,
193
+ source: result?.source || 'legacy',
160
194
  events,
161
195
  total,
162
196
  hasMore,
@@ -1,7 +1,4 @@
1
- import { abortClaudeSDKSession } from '../claude-sdk.js';
2
- import { abortCodexSession } from '../openai-codex.js';
3
- import { abortGeminiSession } from '../gemini-cli.js';
4
- import { abortOpencodeSession } from '../opencode-cli.js';
1
+ import { abortAgentSession as abortAcpAgentSession } from '../acp-runtime/index.js';
5
2
 
6
3
  export const ABORTABLE_AGENT_PROVIDERS = ['claude', 'codex', 'gemini', 'opencode'];
7
4
 
@@ -11,20 +8,7 @@ export function isAbortableAgentProvider(provider) {
11
8
 
12
9
  export async function abortAgentSession(provider, sessionId) {
13
10
  const normalizedProvider = String(provider || 'claude').trim().toLowerCase();
14
-
15
- if (normalizedProvider === 'codex') {
16
- return abortCodexSession(sessionId);
17
- }
18
-
19
- if (normalizedProvider === 'gemini') {
20
- return abortGeminiSession(sessionId);
21
- }
22
-
23
- if (normalizedProvider === 'opencode') {
24
- return abortOpencodeSession(sessionId);
25
- }
26
-
27
- return abortClaudeSDKSession(sessionId);
11
+ return abortAcpAgentSession(normalizedProvider, sessionId);
28
12
  }
29
13
 
30
14
  export async function abortAgentSessionWithWriter({ provider = 'claude', sessionId, writer }) {
@@ -24,7 +24,11 @@ function normalizePersistedEvents(events = []) {
24
24
  return events.filter((event) => (
25
25
  isConversationEvent(event) &&
26
26
  event.sessionId &&
27
- PERSISTED_EVENT_KINDS.has(event.kind)
27
+ (
28
+ event.extensions?.runtimeSource === 'acp' ||
29
+ event.rawRef?.runtime === 'acp' ||
30
+ PERSISTED_EVENT_KINDS.has(event.kind)
31
+ )
28
32
  ));
29
33
  }
30
34
 
@@ -10,6 +10,10 @@ import {
10
10
  getOpencodeSessions,
11
11
  getGeminiSessions
12
12
  } from '../projects.js';
13
+ import {
14
+ findAcpSessionRecord,
15
+ listAcpSessions
16
+ } from '../acp-runtime/session-store.js';
13
17
 
14
18
  async function flattenLegacyMessages(result) {
15
19
  if (Array.isArray(result)) return result;
@@ -28,47 +32,131 @@ async function normalizeLegacyLoadResult(result, provider, sessionId) {
28
32
 
29
33
  return {
30
34
  ...result,
31
- events
35
+ events,
36
+ source: 'legacy'
37
+ };
38
+ }
39
+
40
+ function mergeSessionLists(legacySessions = [], acpSessions = []) {
41
+ const merged = new Map();
42
+
43
+ legacySessions.forEach((session) => {
44
+ if (session?.id) {
45
+ merged.set(session.id, session);
46
+ }
47
+ });
48
+
49
+ acpSessions.forEach((session) => {
50
+ if (session?.id) {
51
+ merged.set(session.id, session);
52
+ }
53
+ });
54
+
55
+ return Array.from(merged.values()).sort((left, right) => {
56
+ const leftTime = new Date(left?.lastActivity || left?.updatedAt || left?.createdAt || 0).getTime();
57
+ const rightTime = new Date(right?.lastActivity || right?.updatedAt || right?.createdAt || 0).getTime();
58
+ return rightTime - leftTime;
59
+ });
60
+ }
61
+
62
+ async function loadAcpEvents(provider, sessionId) {
63
+ const record = await findAcpSessionRecord(sessionId, provider);
64
+ if (!record) {
65
+ return null;
66
+ }
67
+
68
+ const events = await readMirroredConversationEvents(provider, sessionId);
69
+ return {
70
+ events,
71
+ total: events.length,
72
+ hasMore: false,
73
+ offset: 0,
74
+ limit: null,
75
+ source: 'acp'
32
76
  };
33
77
  }
34
78
 
35
79
  const PROVIDER_ADAPTERS = {
36
80
  claude: {
37
- async listSessions({ projectName, limit = 50, offset = 0 }) {
38
- const result = await getSessions(projectName, limit, offset);
39
- return (result?.sessions || []).map((session) => ({ ...session, provider: 'claude' }));
81
+ async listSessions({ projectName, projectPath, limit = 50, offset = 0 }) {
82
+ const [result, acpSessions] = await Promise.all([
83
+ getSessions(projectName, limit, offset),
84
+ listAcpSessions({ provider: 'claude', projectPath: projectPath || null })
85
+ ]);
86
+ return mergeSessionLists(
87
+ (result?.sessions || []).map((session) => ({ ...session, provider: 'claude', source: 'legacy' })),
88
+ acpSessions
89
+ );
40
90
  },
41
91
  async loadEvents({ projectName, sessionId, limit = null, offset = 0 }) {
92
+ const acpResult = await loadAcpEvents('claude', sessionId);
93
+ if (acpResult) {
94
+ return acpResult;
95
+ }
96
+
42
97
  const rawMessages = await getSessionMessages(projectName, sessionId, limit, offset);
43
98
  return normalizeLegacyLoadResult(rawMessages, 'claude', sessionId);
44
99
  }
45
100
  },
46
101
  codex: {
47
102
  async listSessions({ projectPath, limit = 50 }) {
48
- const sessions = await getCodexSessions(projectPath, { limit });
49
- return sessions.map((session) => ({ ...session, provider: 'codex' }));
103
+ const [sessions, acpSessions] = await Promise.all([
104
+ getCodexSessions(projectPath, { limit }),
105
+ listAcpSessions({ provider: 'codex', projectPath: projectPath || null })
106
+ ]);
107
+ return mergeSessionLists(
108
+ sessions.map((session) => ({ ...session, provider: 'codex', source: 'legacy' })),
109
+ acpSessions
110
+ );
50
111
  },
51
112
  async loadEvents({ sessionId, limit = null, offset = 0 }) {
113
+ const acpResult = await loadAcpEvents('codex', sessionId);
114
+ if (acpResult) {
115
+ return acpResult;
116
+ }
117
+
52
118
  const rawMessages = await getCodexSessionMessages(sessionId, limit, offset);
53
119
  return normalizeLegacyLoadResult(rawMessages, 'codex', sessionId);
54
120
  }
55
121
  },
56
122
  gemini: {
57
123
  async listSessions({ projectPath, limit = 50 }) {
58
- const sessions = await getGeminiSessions(projectPath, { limit });
59
- return sessions.map((session) => ({ ...session, provider: 'gemini' }));
124
+ const [sessions, acpSessions] = await Promise.all([
125
+ getGeminiSessions(projectPath, { limit }),
126
+ listAcpSessions({ provider: 'gemini', projectPath: projectPath || null })
127
+ ]);
128
+ return mergeSessionLists(
129
+ sessions.map((session) => ({ ...session, provider: 'gemini', source: 'legacy' })),
130
+ acpSessions
131
+ );
60
132
  },
61
133
  async loadEvents({ sessionId, limit = null, offset = 0 }) {
134
+ const acpResult = await loadAcpEvents('gemini', sessionId);
135
+ if (acpResult) {
136
+ return acpResult;
137
+ }
138
+
62
139
  const rawMessages = await getGeminiSessionMessages(sessionId, limit, offset);
63
140
  return normalizeLegacyLoadResult(rawMessages, 'gemini', sessionId);
64
141
  }
65
142
  },
66
143
  opencode: {
67
144
  async listSessions({ projectPath, limit = 50 }) {
68
- const sessions = await getOpencodeSessions(projectPath, { limit });
69
- return sessions.map((session) => ({ ...session, provider: 'opencode' }));
145
+ const [sessions, acpSessions] = await Promise.all([
146
+ getOpencodeSessions(projectPath, { limit }),
147
+ listAcpSessions({ provider: 'opencode', projectPath: projectPath || null })
148
+ ]);
149
+ return mergeSessionLists(
150
+ sessions.map((session) => ({ ...session, provider: 'opencode', source: 'legacy' })),
151
+ acpSessions
152
+ );
70
153
  },
71
154
  async loadEvents({ sessionId, limit = null, offset = 0 }) {
155
+ const acpResult = await loadAcpEvents('opencode', sessionId);
156
+ if (acpResult) {
157
+ return acpResult;
158
+ }
159
+
72
160
  const rawMessages = await getOpencodeSessionMessages(sessionId, limit, offset);
73
161
  return normalizeLegacyLoadResult(rawMessages, 'opencode', sessionId);
74
162
  }
@@ -7,10 +7,8 @@ import {
7
7
  } from './eventStore.js';
8
8
  import { getProviderAdapter } from './providerAdapters.js';
9
9
  import { getProjects } from '../projects.js';
10
- import { isClaudeSDKSessionActive } from '../claude-sdk.js';
11
- import { isCodexSessionActive } from '../openai-codex.js';
12
- import { isGeminiSessionActive } from '../gemini-cli.js';
13
- import { isOpencodeSessionActive } from '../opencode-cli.js';
10
+ import { findAcpSessionRecord } from '../acp-runtime/session-store.js';
11
+ import { isAgentSessionActive } from '../acp-runtime/index.js';
14
12
 
15
13
  export const AGENT_RUNTIME_PHASES = {
16
14
  IDLE: 'idle',
@@ -86,6 +84,18 @@ async function resolveSessionProjectContext(provider, sessionId) {
86
84
  return null;
87
85
  }
88
86
 
87
+ const acpRecord = await findAcpSessionRecord(normalizedSessionId, normalizedProvider);
88
+ if (acpRecord) {
89
+ return {
90
+ projectName: null,
91
+ projectPath: acpRecord.projectPath || null,
92
+ session: {
93
+ id: normalizedSessionId,
94
+ source: 'acp'
95
+ }
96
+ };
97
+ }
98
+
89
99
  const projects = await getProjects();
90
100
 
91
101
  for (const project of projects) {
@@ -149,19 +159,7 @@ function isSessionActive(provider, sessionId) {
149
159
  return false;
150
160
  }
151
161
 
152
- if (normalizedProvider === 'codex') {
153
- return Boolean(isCodexSessionActive(normalizedSessionId));
154
- }
155
-
156
- if (normalizedProvider === 'gemini') {
157
- return Boolean(isGeminiSessionActive(normalizedSessionId));
158
- }
159
-
160
- if (normalizedProvider === 'opencode') {
161
- return Boolean(isOpencodeSessionActive(normalizedSessionId));
162
- }
163
-
164
- return Boolean(isClaudeSDKSessionActive(normalizedSessionId));
162
+ return Boolean(isAgentSessionActive(normalizedProvider, normalizedSessionId));
165
163
  }
166
164
 
167
165
  function inferPhaseFromEvents(events = []) {
@@ -204,6 +202,7 @@ function inferPhaseFromEvents(events = []) {
204
202
  event.kind === CONVERSATION_EVENT_KINDS.TOOL_CALL_INPUT ||
205
203
  event.kind === CONVERSATION_EVENT_KINDS.TOOL_CALL_END ||
206
204
  event.kind === CONVERSATION_EVENT_KINDS.TOOL_RESULT ||
205
+ event.kind === CONVERSATION_EVENT_KINDS.PLAN_UPDATE ||
207
206
  event.kind === CONVERSATION_EVENT_KINDS.SYSTEM_NOTICE
208
207
  ) {
209
208
  inferredPhase = AGENT_RUNTIME_PHASES.STREAMING;
@@ -1,4 +1,7 @@
1
- import { normalizeRealtimePayloadToConversationEvents } from '../../shared/conversationEvents.js';
1
+ import {
2
+ isConversationEvent,
3
+ normalizeRealtimePayloadToConversationEvents
4
+ } from '../../shared/conversationEvents.js';
2
5
  import { appendMirroredConversationEvents } from './eventStore.js';
3
6
  import { publishSessionRuntimeStateChanges } from './runtimeState.js';
4
7
 
@@ -18,19 +21,23 @@ export class SessionEventMirrorWriter {
18
21
 
19
22
  this.writer.send(data);
20
23
 
21
- const normalizedEvents = normalizeRealtimePayloadToConversationEvents({
22
- ...data,
23
- provider: data?.provider || this.provider,
24
- sessionId: data?.sessionId || this.sessionId
25
- }, this.provider);
24
+ const normalizedEvents = data?.type === 'conversation-event' && isConversationEvent(data?.event)
25
+ ? [data.event]
26
+ : normalizeRealtimePayloadToConversationEvents({
27
+ ...data,
28
+ provider: data?.provider || this.provider,
29
+ sessionId: data?.sessionId || this.sessionId
30
+ }, this.provider);
26
31
 
27
32
  normalizedEvents.forEach((event) => {
28
- this.writer.send({
29
- type: 'conversation-event',
30
- provider: event.provider,
31
- sessionId: event.sessionId,
32
- event
33
- });
33
+ if (!(data?.type === 'conversation-event' && data?.event?.eventId === event.eventId)) {
34
+ this.writer.send({
35
+ type: 'conversation-event',
36
+ provider: event.provider,
37
+ sessionId: event.sessionId,
38
+ event
39
+ });
40
+ }
34
41
  });
35
42
 
36
43
  if (normalizedEvents.length > 0) {