aicodeswitch 5.2.11 → 5.2.13

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.
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractActivityEvents = exports.registerAgentMapRoutes = exports.agentMapService = exports.AgentMapService = void 0;
4
+ var agent_map_service_1 = require("./agent-map-service");
5
+ Object.defineProperty(exports, "AgentMapService", { enumerable: true, get: function () { return agent_map_service_1.AgentMapService; } });
6
+ Object.defineProperty(exports, "agentMapService", { enumerable: true, get: function () { return agent_map_service_1.agentMapService; } });
7
+ var routes_1 = require("./routes");
8
+ Object.defineProperty(exports, "registerAgentMapRoutes", { enumerable: true, get: function () { return routes_1.registerAgentMapRoutes; } });
9
+ var activity_extractor_1 = require("./activity-extractor");
10
+ Object.defineProperty(exports, "extractActivityEvents", { enumerable: true, get: function () { return activity_extractor_1.extractActivityEvents; } });
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.registerAgentMapRoutes = registerAgentMapRoutes;
13
+ function registerAgentMapRoutes(app, service) {
14
+ // SSE 实时流
15
+ app.get('/api/agent-map/stream', (req, res) => {
16
+ res.writeHead(200, {
17
+ 'Content-Type': 'text/event-stream',
18
+ 'Cache-Control': 'no-cache',
19
+ 'Connection': 'keep-alive',
20
+ 'X-Accel-Buffering': 'no',
21
+ });
22
+ // init 快照
23
+ const snap = service.getSnapshot();
24
+ res.write(`data: ${JSON.stringify(Object.assign({ type: 'init' }, snap))}\n\n`);
25
+ const onSessionUpdate = (s) => {
26
+ res.write(`data: ${JSON.stringify({ type: 'session-update', session: s })}\n\n`);
27
+ };
28
+ const onActivity = (e) => {
29
+ res.write(`data: ${JSON.stringify({ type: 'activity', event: e })}\n\n`);
30
+ };
31
+ const onStats = (s) => {
32
+ res.write(`data: ${JSON.stringify({ type: 'stats', stats: s })}\n\n`);
33
+ };
34
+ service.on('session-update', onSessionUpdate);
35
+ service.on('activity', onActivity);
36
+ service.on('stats', onStats);
37
+ const heartbeat = setInterval(() => {
38
+ res.write(`data: ${JSON.stringify({ type: 'heartbeat', timestamp: Date.now() })}\n\n`);
39
+ }, 3000);
40
+ req.on('close', () => {
41
+ service.off('session-update', onSessionUpdate);
42
+ service.off('activity', onActivity);
43
+ service.off('stats', onStats);
44
+ clearInterval(heartbeat);
45
+ });
46
+ });
47
+ app.get('/api/agent-map/sessions', (_req, res) => {
48
+ res.json(service.getSnapshot().sessions);
49
+ });
50
+ app.get('/api/agent-map/sessions/:id/events', (req, res) => __awaiter(this, void 0, void 0, function* () {
51
+ const sinceRaw = typeof req.query.since === 'string' ? parseInt(req.query.since, 10) : NaN;
52
+ const since = Number.isFinite(sinceRaw) ? sinceRaw : undefined;
53
+ res.json(yield service.getSessionEvents(req.params.id, since));
54
+ }));
55
+ // 按需解析会话的项目路径 + 原始标题(仅 global 来源会在本机解析;access-key 返回 source 标记)
56
+ app.get('/api/agent-map/sessions/:id/meta', (req, res) => __awaiter(this, void 0, void 0, function* () {
57
+ try {
58
+ const meta = yield service.getSessionMeta(req.params.id);
59
+ res.json(meta);
60
+ }
61
+ catch (err) {
62
+ console.error('[AgentMap] getSessionMeta error:', err);
63
+ res.json({ source: 'unknown' });
64
+ }
65
+ }));
66
+ app.get('/api/agent-map/stats', (_req, res) => {
67
+ res.json(service.getSnapshot().stats);
68
+ });
69
+ // 任务结束 OS 通知:开关 / 页面后台态上报 / 测试
70
+ app.get('/api/agent-map/notify', (_req, res) => {
71
+ res.json({ enabled: service.getNotifyEnabled() });
72
+ });
73
+ app.post('/api/agent-map/notify', (req, res) => {
74
+ var _a;
75
+ const enabled = !!((_a = req.body) === null || _a === void 0 ? void 0 : _a.enabled);
76
+ service.setNotifyEnabled(enabled);
77
+ res.json({ enabled });
78
+ });
79
+ app.post('/api/agent-map/notify-focus', (req, res) => {
80
+ var _a;
81
+ service.setPageHidden(!!((_a = req.body) === null || _a === void 0 ? void 0 : _a.hidden));
82
+ res.json({ ok: true });
83
+ });
84
+ app.post('/api/agent-map/notify-test', (_req, res) => {
85
+ service.notifyTest();
86
+ res.json({ ok: true });
87
+ });
88
+ }
@@ -0,0 +1,300 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.resolveSessionMeta = resolveSessionMeta;
13
+ exports.clearSessionMetaCache = clearSessionMetaCache;
14
+ /**
15
+ * 会话元信息解析器:从本机 Claude Code / Codex 的会话存储里
16
+ * 读取每个会话的「项目路径(cwd)」与「原始标题」。
17
+ *
18
+ * 仅适用于「非 AccessKey」场景——只有当 Claude Code / Codex 运行在本机
19
+ * (代理写本地配置、工具在本地落会话文件)时,磁盘文件才在本机可读。
20
+ * AccessKey 流量来自远端客户端,其会话文件不在本机,无法解析。
21
+ *
22
+ * 数据来源(调研自本机真实文件):
23
+ * - Claude Code:~/.claude/projects 下每个项目目录的 sessions-index.json(含 projectPath/summary);
24
+ * 回退:在 projects 各目录下找 sessionId.jsonl,读行内 cwd 与 type=ai-title 的 aiTitle。
25
+ * - Codex:~/.codex/sessions(按年月日嵌套)与 archived_sessions 下匹配 sessionId 的 rollout-*.jsonl,
26
+ * 读首行 session_meta.payload.cwd;标题优先 session_index.jsonl 的 thread_name,回退首条用户消息。
27
+ */
28
+ const fs_1 = require("fs");
29
+ const path_1 = require("path");
30
+ const os_1 = require("os");
31
+ // 解析结果缓存(含空结果,避免重复扫盘)
32
+ const cache = new Map();
33
+ let claudeIndex = null;
34
+ function getClaudeIndex() {
35
+ if (claudeIndex)
36
+ return claudeIndex;
37
+ const map = new Map();
38
+ const root = (0, path_1.join)((0, os_1.homedir)(), '.claude', 'projects');
39
+ let dirs = [];
40
+ try {
41
+ dirs = (0, fs_1.readdirSync)(root);
42
+ }
43
+ catch (_a) {
44
+ claudeIndex = map;
45
+ return map;
46
+ }
47
+ for (const d of dirs) {
48
+ const idxFile = (0, path_1.join)(root, d, 'sessions-index.json');
49
+ if (!(0, fs_1.existsSync)(idxFile))
50
+ continue;
51
+ try {
52
+ const data = JSON.parse((0, fs_1.readFileSync)(idxFile, 'utf-8'));
53
+ const dirOriginal = data === null || data === void 0 ? void 0 : data.originalPath;
54
+ const entries = Array.isArray(data) ? data : ((data === null || data === void 0 ? void 0 : data.sessions) || (data === null || data === void 0 ? void 0 : data.entries) || []);
55
+ for (const e of entries) {
56
+ if (e && e.sessionId) {
57
+ map.set(e.sessionId, {
58
+ projectPath: e.projectPath || e.originalPath || dirOriginal,
59
+ originalPath: e.originalPath || dirOriginal,
60
+ fullPath: e.fullPath,
61
+ summary: e.summary,
62
+ firstPrompt: e.firstPrompt,
63
+ title: e.title,
64
+ });
65
+ }
66
+ }
67
+ }
68
+ catch ( /* skip unreadable index */_b) { /* skip unreadable index */ }
69
+ }
70
+ claudeIndex = map;
71
+ return map;
72
+ }
73
+ function parseClaudeJsonl(file) {
74
+ let projectPath;
75
+ let title;
76
+ try {
77
+ const lines = (0, fs_1.readFileSync)(file, 'utf-8').split('\n');
78
+ for (let i = 0; i < Math.min(lines.length, 120); i++) {
79
+ const line = lines[i];
80
+ if (!line || !line.trim())
81
+ continue;
82
+ let obj;
83
+ try {
84
+ obj = JSON.parse(line);
85
+ }
86
+ catch (_a) {
87
+ continue;
88
+ }
89
+ if (!projectPath && obj.cwd)
90
+ projectPath = obj.cwd;
91
+ if (!title && obj.type === 'ai-title' && obj.aiTitle)
92
+ title = obj.aiTitle;
93
+ if (projectPath && title)
94
+ break;
95
+ }
96
+ }
97
+ catch ( /* ignore */_b) { /* ignore */ }
98
+ return { projectPath, title };
99
+ }
100
+ /** 清洗作为标题用的 prompt 文本:剥掉开头的 <tag>...</tag> 注入块 */
101
+ function cleanPromptTitle(s) {
102
+ if (!s)
103
+ return undefined;
104
+ let t = s.trim();
105
+ t = t.replace(/^<[^>]+>[\s\S]*?<\/[^>]+>\s*/, '').trim();
106
+ return t || undefined;
107
+ }
108
+ function findClaudeJsonl(sessionId) {
109
+ const root = (0, path_1.join)((0, os_1.homedir)(), '.claude', 'projects');
110
+ let dirs = [];
111
+ try {
112
+ dirs = (0, fs_1.readdirSync)(root);
113
+ }
114
+ catch (_a) {
115
+ return null;
116
+ }
117
+ for (const d of dirs) {
118
+ const f = (0, path_1.join)(root, d, sessionId + '.jsonl');
119
+ if ((0, fs_1.existsSync)(f))
120
+ return f;
121
+ }
122
+ return null;
123
+ }
124
+ function resolveClaude(sessionId) {
125
+ const entry = getClaudeIndex().get(sessionId);
126
+ let projectPath = (entry === null || entry === void 0 ? void 0 : entry.projectPath) || (entry === null || entry === void 0 ? void 0 : entry.originalPath);
127
+ let title;
128
+ // 标题优先取 .jsonl 里的 ai-title(最标准);项目路径也可从行内 cwd 兜底
129
+ const jsonl = (entry === null || entry === void 0 ? void 0 : entry.fullPath) && (0, fs_1.existsSync)(entry.fullPath) ? entry.fullPath : findClaudeJsonl(sessionId);
130
+ if (jsonl) {
131
+ const m = parseClaudeJsonl(jsonl);
132
+ if (m.title)
133
+ title = m.title;
134
+ if (!projectPath && m.projectPath)
135
+ projectPath = m.projectPath;
136
+ }
137
+ // 回退标题:index 的 summary / firstPrompt(清洗掉注入标签)
138
+ if (!title)
139
+ title = cleanPromptTitle(entry === null || entry === void 0 ? void 0 : entry.summary) || cleanPromptTitle(entry === null || entry === void 0 ? void 0 : entry.firstPrompt);
140
+ return { projectPath, title };
141
+ }
142
+ // ─── Codex 文件索引(sessionId → 文件路径,一次构建) ───
143
+ const UUID_RE = /-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
144
+ let codexFileIndex = null;
145
+ function getCodexFileIndex() {
146
+ if (codexFileIndex)
147
+ return codexFileIndex;
148
+ const map = new Map();
149
+ const bases = [
150
+ (0, path_1.join)((0, os_1.homedir)(), '.codex', 'sessions'),
151
+ (0, path_1.join)((0, os_1.homedir)(), '.codex', 'archived_sessions'),
152
+ ];
153
+ for (const base of bases)
154
+ walkIndex(base, map);
155
+ codexFileIndex = map;
156
+ return map;
157
+ }
158
+ function walkIndex(dir, map) {
159
+ let entries = [];
160
+ try {
161
+ entries = (0, fs_1.readdirSync)(dir);
162
+ }
163
+ catch (_a) {
164
+ return;
165
+ }
166
+ for (const name of entries) {
167
+ const p = (0, path_1.join)(dir, name);
168
+ let st;
169
+ try {
170
+ st = (0, fs_1.statSync)(p);
171
+ }
172
+ catch (_b) {
173
+ continue;
174
+ }
175
+ if (st.isDirectory()) {
176
+ walkIndex(p, map);
177
+ }
178
+ else if (name.endsWith('.jsonl')) {
179
+ const m = name.match(UUID_RE);
180
+ if (m)
181
+ map.set(m[1], p);
182
+ }
183
+ }
184
+ }
185
+ let codexThreadIndex = null;
186
+ function getCodexThreadIndex() {
187
+ if (codexThreadIndex)
188
+ return codexThreadIndex;
189
+ const map = new Map();
190
+ try {
191
+ const f = (0, path_1.join)((0, os_1.homedir)(), '.codex', 'session_index.jsonl');
192
+ if ((0, fs_1.existsSync)(f)) {
193
+ const lines = (0, fs_1.readFileSync)(f, 'utf-8').split('\n');
194
+ for (const line of lines) {
195
+ if (!line.trim())
196
+ continue;
197
+ try {
198
+ const o = JSON.parse(line);
199
+ if (o && o.id && o.thread_name)
200
+ map.set(o.id, o.thread_name);
201
+ }
202
+ catch ( /* skip */_a) { /* skip */ }
203
+ }
204
+ }
205
+ }
206
+ catch ( /* ignore */_b) { /* ignore */ }
207
+ codexThreadIndex = map;
208
+ return map;
209
+ }
210
+ function stripEnvContext(text) {
211
+ return text
212
+ .replace(/<environment_context>[\s\S]*?<\/environment_context>/g, '')
213
+ .replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g, '') // 剥掉其它注入标签包裹的占位
214
+ .trim();
215
+ }
216
+ function extractCodexUserText(content) {
217
+ let text = '';
218
+ if (typeof content === 'string')
219
+ text = content;
220
+ else if (Array.isArray(content)) {
221
+ for (const c of content) {
222
+ if (typeof c === 'string')
223
+ text += c;
224
+ else if (c && typeof c.text === 'string')
225
+ text += c.text;
226
+ }
227
+ }
228
+ const cleaned = stripEnvContext(text);
229
+ return cleaned || undefined;
230
+ }
231
+ function parseCodexSessionFile(file) {
232
+ var _a, _b, _c;
233
+ let projectPath;
234
+ let title;
235
+ try {
236
+ const lines = (0, fs_1.readFileSync)(file, 'utf-8').split('\n');
237
+ for (let i = 0; i < Math.min(lines.length, 60); i++) {
238
+ const line = lines[i];
239
+ if (!line || !line.trim())
240
+ continue;
241
+ let obj;
242
+ try {
243
+ obj = JSON.parse(line);
244
+ }
245
+ catch (_d) {
246
+ continue;
247
+ }
248
+ if (!projectPath && obj.type === 'session_meta' && ((_a = obj.payload) === null || _a === void 0 ? void 0 : _a.cwd)) {
249
+ projectPath = obj.payload.cwd;
250
+ }
251
+ if (!title && obj.type === 'response_item' && ((_b = obj.payload) === null || _b === void 0 ? void 0 : _b.type) === 'message' && ((_c = obj.payload) === null || _c === void 0 ? void 0 : _c.role) === 'user') {
252
+ title = extractCodexUserText(obj.payload.content);
253
+ }
254
+ if (projectPath && title)
255
+ break;
256
+ }
257
+ }
258
+ catch ( /* ignore */_e) { /* ignore */ }
259
+ return { projectPath, title };
260
+ }
261
+ function resolveCodex(sessionId) {
262
+ const file = getCodexFileIndex().get(sessionId);
263
+ let projectPath;
264
+ let title;
265
+ if (file) {
266
+ const m = parseCodexSessionFile(file);
267
+ projectPath = m.projectPath;
268
+ title = m.title;
269
+ }
270
+ // 标题优先用 session_index.jsonl 的 thread_name(更标准),缺失再用首条消息
271
+ const threadName = getCodexThreadIndex().get(sessionId);
272
+ if (threadName)
273
+ title = threadName;
274
+ return { projectPath, title };
275
+ }
276
+ /**
277
+ * 解析会话的项目路径与原始标题。结果会被缓存。
278
+ * 注意:调用方需自行判断是否为 AccessKey 来源——AccessKey 会话不在本机,不应调用。
279
+ */
280
+ function resolveSessionMeta(sessionId, agent) {
281
+ return __awaiter(this, void 0, void 0, function* () {
282
+ const cached = cache.get(sessionId);
283
+ if (cached)
284
+ return cached;
285
+ let meta = {};
286
+ try {
287
+ meta = agent === 'codex' ? resolveCodex(sessionId) : resolveClaude(sessionId);
288
+ }
289
+ catch ( /* ignore */_a) { /* ignore */ }
290
+ cache.set(sessionId, meta);
291
+ return meta;
292
+ });
293
+ }
294
+ /** 清理缓存(供测试 / 配置变更后重建) */
295
+ function clearSessionMetaCache() {
296
+ cache.clear();
297
+ claudeIndex = null;
298
+ codexFileIndex = null;
299
+ codexThreadIndex = null;
300
+ }