dr-claw 1.1.2
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/LICENSE +1380 -0
- package/NOTICE +17 -0
- package/README.md +875 -0
- package/README.zh-CN.md +857 -0
- package/dist/api-docs.html +878 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/_basePickBy-C0n1uo4u.js +1 -0
- package/dist/assets/_baseUniq-BRNmhFkc.js +1 -0
- package/dist/assets/arc-DOMBmC_A.js +1 -0
- package/dist/assets/architectureDiagram-VXUJARFQ-CtMWglbD.js +36 -0
- package/dist/assets/blockDiagram-VD42YOAC-DK_gTkJy.js +122 -0
- package/dist/assets/c4Diagram-YG6GDRKO-DrDAvsn7.js +10 -0
- package/dist/assets/channel-BFOE7OyC.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-CGbhdZcN.js +1 -0
- package/dist/assets/chunk-55IACEB6-sGeFSxen.js +1 -0
- package/dist/assets/chunk-B4BG7PRW-RxgHAU_P.js +165 -0
- package/dist/assets/chunk-DI55MBZ5-CvHUXZrx.js +220 -0
- package/dist/assets/chunk-FMBD7UC4-Sn7cXVNU.js +15 -0
- package/dist/assets/chunk-QN33PNHL-bDKOHppN.js +1 -0
- package/dist/assets/chunk-QZHKN3VN-B9kN5LGm.js +1 -0
- package/dist/assets/chunk-TZMSLE5B-CCBe6len.js +1 -0
- package/dist/assets/classDiagram-2ON5EDUG-DQoMNYlY.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-DQoMNYlY.js +1 -0
- package/dist/assets/clone-BulQSblH.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-r6sLiHBW.js +1 -0
- package/dist/assets/cytoscape.esm-5J0xJHOV.js +321 -0
- package/dist/assets/dagre-6UL2VRFP-AbsaR1Oa.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-PSM6KHXK-DjCvdUyy.js +24 -0
- package/dist/assets/diagram-QEK2KX5R-CqiocXkH.js +43 -0
- package/dist/assets/diagram-S2PKOQOG-D5ALeoII.js +24 -0
- package/dist/assets/erDiagram-Q2GNP2WA-C1AeOsiz.js +60 -0
- package/dist/assets/flowDiagram-NV44I4VS-C98ve_DO.js +162 -0
- package/dist/assets/ganttDiagram-JELNMOA3-3zdLvcxD.js +267 -0
- package/dist/assets/gitGraphDiagram-V2S2FVAM-nDuDMsHh.js +65 -0
- package/dist/assets/graph-Blm-emkR.js +1 -0
- package/dist/assets/index-B8Yutsnm.css +1 -0
- package/dist/assets/index-Cf6gyhc3.js +654 -0
- package/dist/assets/infoDiagram-HS3SLOUP-CmzE3q0R.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/journeyDiagram-XKPGCS4Q-CYaW7LrP.js +139 -0
- package/dist/assets/kanban-definition-3W4ZIXB7-mkmW7p0j.js +89 -0
- package/dist/assets/layout-Ct2kFIId.js +1 -0
- package/dist/assets/linear-CKG9EwIJ.js +1 -0
- package/dist/assets/mermaid.core-CaKcUnnd.js +250 -0
- package/dist/assets/mindmap-definition-VGOIOE7T-Bct-pYin.js +68 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pieDiagram-ADFJNKIX-C8wFD4Jr.js +30 -0
- package/dist/assets/quadrantDiagram-AYHSOK5B-CuiE_f46.js +7 -0
- package/dist/assets/requirementDiagram-UZGBJVZJ-C4v1CqU8.js +64 -0
- package/dist/assets/sankeyDiagram-TZEHDZUN-keAqqs-Y.js +10 -0
- package/dist/assets/sequenceDiagram-WL72ISMW-ClNbvKTB.js +145 -0
- package/dist/assets/stateDiagram-FKZM4ZOC-D8We-2vq.js +1 -0
- package/dist/assets/stateDiagram-v2-4FDKWEC3-BUURfjrE.js +1 -0
- package/dist/assets/timeline-definition-IT6M3QCI-DsvEMT5T.js +61 -0
- package/dist/assets/treemap-GDKQZRPO-TJrzN7Bj.js +162 -0
- package/dist/assets/vendor-codemirror-ansM-eig.js +33 -0
- package/dist/assets/vendor-react-BWF6YQQ3.js +8 -0
- package/dist/assets/vendor-xterm-DfcmCpbH.js +66 -0
- package/dist/assets/xychartDiagram-PRI3JC2R-BdATmY5F.js +7 -0
- package/dist/clear-cache.html +85 -0
- package/dist/convert-icons.md +53 -0
- package/dist/dr-claw.png +0 -0
- package/dist/favicon.png +0 -0
- package/dist/generate-icons.js +49 -0
- package/dist/icons/claude-ai-icon.svg +1 -0
- package/dist/icons/codex-white.svg +3 -0
- package/dist/icons/codex.svg +3 -0
- package/dist/icons/cursor-white.svg +12 -0
- package/dist/icons/cursor.svg +1 -0
- package/dist/icons/file.svg +16997 -0
- package/dist/icons/generate-icons.md +19 -0
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/news/arxiv.svg +1 -0
- package/dist/icons/news/huggingface.svg +8 -0
- package/dist/icons/news/x-black.png +0 -0
- package/dist/icons/news/x-white.png +0 -0
- package/dist/icons/news/xiaohongshu.png +0 -0
- package/dist/index.html +51 -0
- package/dist/leveraged-cognition.png +0 -0
- package/dist/manifest.json +61 -0
- package/dist/screenshots/chat.png +0 -0
- package/dist/screenshots/new_claw.png +0 -0
- package/dist/screenshots/news_dashboard.png +0 -0
- package/dist/screenshots/project_dashboard.png +0 -0
- package/dist/screenshots/research_lab.png +0 -0
- package/dist/screenshots/skill_library.png +0 -0
- package/dist/screenshots/task_list.png +0 -0
- package/dist/screenshots/terminal_example.png +0 -0
- package/dist/screenshots/terminal_example1.png +0 -0
- package/dist/sw.js +58 -0
- package/dist/wechat-group-qr.jpg +0 -0
- package/package.json +133 -0
- package/public/api-docs.html +878 -0
- package/public/clear-cache.html +85 -0
- package/public/convert-icons.md +53 -0
- package/public/dr-claw.png +0 -0
- package/public/favicon.png +0 -0
- package/public/generate-icons.js +49 -0
- package/public/icons/claude-ai-icon.svg +1 -0
- package/public/icons/codex-white.svg +3 -0
- package/public/icons/codex.svg +3 -0
- package/public/icons/cursor-white.svg +12 -0
- package/public/icons/cursor.svg +1 -0
- package/public/icons/file.svg +16997 -0
- package/public/icons/generate-icons.md +19 -0
- package/public/icons/icon-128x128.png +0 -0
- package/public/icons/icon-144x144.png +0 -0
- package/public/icons/icon-152x152.png +0 -0
- package/public/icons/icon-192x192.png +0 -0
- package/public/icons/icon-384x384.png +0 -0
- package/public/icons/icon-512x512.png +0 -0
- package/public/icons/icon-72x72.png +0 -0
- package/public/icons/icon-96x96.png +0 -0
- package/public/icons/news/arxiv.svg +1 -0
- package/public/icons/news/huggingface.svg +8 -0
- package/public/icons/news/x-black.png +0 -0
- package/public/icons/news/x-white.png +0 -0
- package/public/icons/news/xiaohongshu.png +0 -0
- package/public/leveraged-cognition.png +0 -0
- package/public/manifest.json +61 -0
- package/public/screenshots/chat.png +0 -0
- package/public/screenshots/new_claw.png +0 -0
- package/public/screenshots/news_dashboard.png +0 -0
- package/public/screenshots/project_dashboard.png +0 -0
- package/public/screenshots/research_lab.png +0 -0
- package/public/screenshots/skill_library.png +0 -0
- package/public/screenshots/task_list.png +0 -0
- package/public/screenshots/terminal_example.png +0 -0
- package/public/screenshots/terminal_example1.png +0 -0
- package/public/sw.js +58 -0
- package/public/wechat-group-qr.jpg +0 -0
- package/scripts/export-skills-catalog-v2.mjs +746 -0
- package/scripts/fix-node-pty.js +45 -0
- package/scripts/gemini_image_core.py +470 -0
- package/scripts/install-python-tools.js +64 -0
- package/server/AGENTS.md +73 -0
- package/server/__tests__/gemini-legacy-thought.test.mjs +88 -0
- package/server/__tests__/runtimePorts.test.mjs +115 -0
- package/server/__tests__/session-delete.test.mjs +166 -0
- package/server/bin/compute-node +70 -0
- package/server/claude-sdk.js +799 -0
- package/server/cli-chat.js +488 -0
- package/server/cli.js +354 -0
- package/server/compute-node.js +439 -0
- package/server/constants/config.js +5 -0
- package/server/cursor-cli.js +340 -0
- package/server/database/auth.db +0 -0
- package/server/database/db.js +1852 -0
- package/server/database/init.sql +194 -0
- package/server/gemini-cli.js +1352 -0
- package/server/index.js +3154 -0
- package/server/load-env.js +71 -0
- package/server/local-gpu.js +860 -0
- package/server/middleware/auth.js +100 -0
- package/server/openai-codex.js +724 -0
- package/server/openrouter.js +890 -0
- package/server/project-token-usage.js +407 -0
- package/server/projects.js +3973 -0
- package/server/routes/agent.js +1272 -0
- package/server/routes/auth.js +128 -0
- package/server/routes/auto-research.js +558 -0
- package/server/routes/cli-auth.js +958 -0
- package/server/routes/codex.js +344 -0
- package/server/routes/commands.js +601 -0
- package/server/routes/compute.js +358 -0
- package/server/routes/cursor.js +795 -0
- package/server/routes/git.js +1348 -0
- package/server/routes/mcp-utils.js +48 -0
- package/server/routes/mcp.js +618 -0
- package/server/routes/news.js +711 -0
- package/server/routes/projects.js +633 -0
- package/server/routes/references.js +338 -0
- package/server/routes/settings.js +285 -0
- package/server/routes/skills.js +914 -0
- package/server/routes/taskmaster.js +2566 -0
- package/server/routes/telemetry.js +89 -0
- package/server/routes/user.js +155 -0
- package/server/scripts/fix_session_names.js +57 -0
- package/server/scripts/research-news/common_words.py +19 -0
- package/server/scripts/research-news/link_keywords.py +298 -0
- package/server/scripts/research-news/scan_existing_notes.py +266 -0
- package/server/scripts/research-news/scoring_utils.py +227 -0
- package/server/scripts/research-news/search_arxiv.py +842 -0
- package/server/scripts/research-news/search_huggingface.py +438 -0
- package/server/scripts/research-news/search_x.py +490 -0
- package/server/scripts/research-news/search_xiaohongshu.py +610 -0
- package/server/taskmaster-templates/README.md +150 -0
- package/server/taskmaster-templates/README.zh-CN.md +150 -0
- package/server/taskmaster-templates/ai-research-dataset.json +230 -0
- package/server/taskmaster-templates/ai-research-method-model.json +230 -0
- package/server/taskmaster-templates/ai-research-position-paper.json +230 -0
- package/server/telemetry.js +121 -0
- package/server/templates/AGENTS.md +103 -0
- package/server/templates/CLAUDE.md +104 -0
- package/server/templates/GEMINI.md +103 -0
- package/server/templates/cursor-project.md +108 -0
- package/server/templates/index.js +37 -0
- package/server/utils/__tests__/sessionTokenUsage.test.js +82 -0
- package/server/utils/auto-research-mailer.js +57 -0
- package/server/utils/cliResolution.js +170 -0
- package/server/utils/commandParser.js +303 -0
- package/server/utils/cursorCommand.js +61 -0
- package/server/utils/geminiApiKey.js +28 -0
- package/server/utils/gitConfig.js +24 -0
- package/server/utils/imageAttachmentFiles.js +35 -0
- package/server/utils/mcp-detector.js +198 -0
- package/server/utils/parsers/bibtex-parser.js +207 -0
- package/server/utils/permissions.js +100 -0
- package/server/utils/runtimePorts.js +212 -0
- package/server/utils/sessionFormatting.js +68 -0
- package/server/utils/sessionIndex.js +100 -0
- package/server/utils/sessionMode.js +95 -0
- package/server/utils/sessionTokenUsage.js +105 -0
- package/server/utils/taskmaster-websocket.js +129 -0
- package/server/utils/zotero-client.js +136 -0
- package/shared/errorClassifier.js +102 -0
- package/shared/geminiThinkingSupport.d.ts +22 -0
- package/shared/geminiThinkingSupport.js +96 -0
- package/shared/geminiThoughtParser.d.ts +8 -0
- package/shared/geminiThoughtParser.js +30 -0
- package/shared/modelConstants.js +139 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import net from 'net';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_BACKEND_PORT = 3001;
|
|
11
|
+
export const DEFAULT_FRONTEND_PORT = 5173;
|
|
12
|
+
const DEFAULT_MAX_PORT_ATTEMPTS = 20;
|
|
13
|
+
|
|
14
|
+
function getProjectRoot() {
|
|
15
|
+
return path.join(__dirname, '../..');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getRuntimePortsPath() {
|
|
19
|
+
if (process.env.DR_CLAW_RUNTIME_FILE) {
|
|
20
|
+
return process.env.DR_CLAW_RUNTIME_FILE;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const runtimeDir = process.env.DR_CLAW_RUNTIME_DIR || path.join(getProjectRoot(), '.runtime');
|
|
24
|
+
return path.join(runtimeDir, 'ports.json');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ensureRuntimeDirSync() {
|
|
28
|
+
fs.mkdirSync(path.dirname(getRuntimePortsPath()), { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isPidAlive(pid) {
|
|
32
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
process.kill(pid, 0);
|
|
38
|
+
return true;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return error.code === 'EPERM';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sanitizeRuntimeEntry(entry) {
|
|
45
|
+
if (!entry || typeof entry !== 'object') {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const port = Number(entry.port);
|
|
50
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const pid = Number(entry.pid);
|
|
55
|
+
if (Number.isInteger(pid) && pid > 0 && !isPidAlive(pid)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
port,
|
|
61
|
+
pid: Number.isInteger(pid) && pid > 0 ? pid : null,
|
|
62
|
+
updatedAt: typeof entry.updatedAt === 'string' ? entry.updatedAt : null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readRawRuntimePortsSync() {
|
|
67
|
+
try {
|
|
68
|
+
const content = fs.readFileSync(getRuntimePortsPath(), 'utf8');
|
|
69
|
+
const parsed = JSON.parse(content);
|
|
70
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error.code !== 'ENOENT') {
|
|
73
|
+
console.warn('[runtimePorts] Failed to read runtime ports file:', error.message);
|
|
74
|
+
}
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function writeRawRuntimePortsSync(state) {
|
|
80
|
+
ensureRuntimeDirSync();
|
|
81
|
+
fs.writeFileSync(getRuntimePortsPath(), `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function parsePortNumber(value, fallback) {
|
|
85
|
+
const parsed = Number.parseInt(value, 10);
|
|
86
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getRuntimePortStateSync() {
|
|
90
|
+
const rawState = readRawRuntimePortsSync();
|
|
91
|
+
return {
|
|
92
|
+
backend: sanitizeRuntimeEntry(rawState.backend),
|
|
93
|
+
frontend: sanitizeRuntimeEntry(rawState.frontend),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getRuntimePortSync(kind, fallback) {
|
|
98
|
+
const state = getRuntimePortStateSync();
|
|
99
|
+
return state[kind]?.port ?? fallback;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getBackendPortSync(fallback = DEFAULT_BACKEND_PORT) {
|
|
103
|
+
return getRuntimePortSync('backend', fallback);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getFrontendPortSync(fallback = DEFAULT_FRONTEND_PORT) {
|
|
107
|
+
return getRuntimePortSync('frontend', fallback);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function setRuntimePortSync(kind, port, pid = process.pid) {
|
|
111
|
+
const normalizedPort = parsePortNumber(port, null);
|
|
112
|
+
if (!normalizedPort) {
|
|
113
|
+
throw new Error(`Invalid port for ${kind}: ${port}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const rawState = readRawRuntimePortsSync();
|
|
117
|
+
rawState[kind] = {
|
|
118
|
+
port: normalizedPort,
|
|
119
|
+
pid,
|
|
120
|
+
updatedAt: new Date().toISOString(),
|
|
121
|
+
};
|
|
122
|
+
writeRawRuntimePortsSync(rawState);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function isPortAvailable(port, host) {
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const probe = net.createServer();
|
|
128
|
+
|
|
129
|
+
const handleError = (error) => {
|
|
130
|
+
cleanup();
|
|
131
|
+
if (error.code === 'EADDRINUSE') {
|
|
132
|
+
resolve(false);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
reject(error);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const cleanup = () => {
|
|
139
|
+
probe.off('error', handleError);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
probe.once('error', handleError);
|
|
143
|
+
probe.listen(port, host, () => {
|
|
144
|
+
probe.close((error) => {
|
|
145
|
+
cleanup();
|
|
146
|
+
if (error) {
|
|
147
|
+
reject(error);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
resolve(true);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function listenOnce(server, port, host) {
|
|
157
|
+
await new Promise((resolve, reject) => {
|
|
158
|
+
const handleError = (error) => {
|
|
159
|
+
cleanup();
|
|
160
|
+
reject(error);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const cleanup = () => {
|
|
164
|
+
server.off('error', handleError);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
server.once('error', handleError);
|
|
168
|
+
server.listen(port, host, () => {
|
|
169
|
+
cleanup();
|
|
170
|
+
resolve();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function listenOnAvailablePort(server, options = {}) {
|
|
176
|
+
const startPort = parsePortNumber(options.startPort, DEFAULT_BACKEND_PORT);
|
|
177
|
+
const host = options.host || '0.0.0.0';
|
|
178
|
+
const maxAttempts = Number.isInteger(options.maxAttempts) && options.maxAttempts > 0
|
|
179
|
+
? options.maxAttempts
|
|
180
|
+
: DEFAULT_MAX_PORT_ATTEMPTS;
|
|
181
|
+
|
|
182
|
+
let lastError = null;
|
|
183
|
+
|
|
184
|
+
for (let offset = 0; offset < maxAttempts; offset += 1) {
|
|
185
|
+
const port = startPort + offset;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const available = await isPortAvailable(port, host);
|
|
189
|
+
if (!available) {
|
|
190
|
+
const portBusyError = new Error(`Port ${port} is already in use.`);
|
|
191
|
+
portBusyError.code = 'EADDRINUSE';
|
|
192
|
+
lastError = portBusyError;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
await listenOnce(server, port, host);
|
|
197
|
+
|
|
198
|
+
return port;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (error.code !== 'EADDRINUSE') {
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
lastError = error;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const endPort = startPort + maxAttempts - 1;
|
|
208
|
+
const error = new Error(`No available port found between ${startPort} and ${endPort}.`);
|
|
209
|
+
error.code = 'EADDRINUSE';
|
|
210
|
+
error.cause = lastError;
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for formatting and cleaning session content.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Strips internal [Context: ...] prefixes from message text.
|
|
7
|
+
* Handles full prefixes [Context: ...] and common truncated ones like [Context: Tre...
|
|
8
|
+
* @param {string} text - The message text
|
|
9
|
+
* @param {boolean} returnDefaultOnEmpty - Whether to return 'New Session' if result is empty
|
|
10
|
+
* @returns {string|null} - Cleaned text or null/default if empty
|
|
11
|
+
*/
|
|
12
|
+
export function stripInternalContextPrefix(text, returnDefaultOnEmpty = true) {
|
|
13
|
+
if (typeof text !== 'string') return returnDefaultOnEmpty ? '' : null;
|
|
14
|
+
|
|
15
|
+
let cleaned = text;
|
|
16
|
+
let hasMatch = false;
|
|
17
|
+
|
|
18
|
+
const internalCommandTagPattern = /<\/?(?:command-name|command-message|command-args|local-command-stdout)>/i;
|
|
19
|
+
const skillContentPattern = /Base directory for this skill:\s*\S+/i;
|
|
20
|
+
|
|
21
|
+
if (internalCommandTagPattern.test(cleaned) || skillContentPattern.test(cleaned)) {
|
|
22
|
+
cleaned = cleaned
|
|
23
|
+
.replace(/<command-name>[^<]*<\/command-name>/gi, '')
|
|
24
|
+
.replace(/<command-message>[^<]*<\/command-message>/gi, '')
|
|
25
|
+
.replace(/<command-args>[^<]*<\/command-args>/gi, '')
|
|
26
|
+
.replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/gi, '')
|
|
27
|
+
.replace(/^[❯>]\s*Base directory for this skill:\s*\S+\s*/gim, '')
|
|
28
|
+
.replace(/^Base directory for this skill:\s*\S+\s*/gim, '')
|
|
29
|
+
.trim();
|
|
30
|
+
hasMatch = true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 1. Match full [Context: ...] prefixes at the start of the string, including multiple ones
|
|
34
|
+
const fullPrefixPattern = /^\s*\[Context:[^\]]*\]\s*/i;
|
|
35
|
+
while (fullPrefixPattern.test(cleaned)) {
|
|
36
|
+
cleaned = cleaned.replace(fullPrefixPattern, '');
|
|
37
|
+
hasMatch = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 2. Match common truncated prefixes like "[Context: session-mode=..." or "[Context: Tre..."
|
|
41
|
+
// This is specifically for database entries where the summary was truncated before the closing bracket
|
|
42
|
+
const truncatedPrefixPattern = /^\s*\[Context:[^\]]*$/i;
|
|
43
|
+
if (truncatedPrefixPattern.test(cleaned)) {
|
|
44
|
+
// If it's JUST a truncated context prefix and we have no other content, return default or null
|
|
45
|
+
return returnDefaultOnEmpty ? 'New Session' : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = cleaned.trim();
|
|
49
|
+
|
|
50
|
+
// If we didn't find any context prefix and we have text, return it as is
|
|
51
|
+
if (!hasMatch && result) {
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// If it's empty after cleaning, but we had a match (it was pure context)
|
|
56
|
+
if (!result && hasMatch) {
|
|
57
|
+
if (!returnDefaultOnEmpty) return null;
|
|
58
|
+
|
|
59
|
+
// Fallback: If it's a new session and we ONLY have context,
|
|
60
|
+
// try to find some semantic info in the context itself or return a better default
|
|
61
|
+
if (text.includes('session-mode=workspace_qa')) return 'Workspace Q&A';
|
|
62
|
+
if (text.includes('session-mode=research')) return 'Research Session';
|
|
63
|
+
|
|
64
|
+
return 'New Session';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return result || (returnDefaultOnEmpty ? 'New Session' : null);
|
|
68
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { sessionDb, tagDb } from '../database/db.js';
|
|
2
|
+
import { encodeProjectPath } from '../projects.js';
|
|
3
|
+
|
|
4
|
+
function defaultSessionName(provider) {
|
|
5
|
+
switch (provider) {
|
|
6
|
+
case 'cursor':
|
|
7
|
+
return 'Untitled Session';
|
|
8
|
+
case 'codex':
|
|
9
|
+
return 'Codex Session';
|
|
10
|
+
case 'gemini':
|
|
11
|
+
return 'Gemini Session';
|
|
12
|
+
case 'openrouter':
|
|
13
|
+
return 'OpenRouter Session';
|
|
14
|
+
case 'local':
|
|
15
|
+
return 'Local GPU Session';
|
|
16
|
+
default:
|
|
17
|
+
return 'New Session';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeStageTagKeys(stageTagKeys = []) {
|
|
22
|
+
const normalized = Array.from(new Set(
|
|
23
|
+
(Array.isArray(stageTagKeys) ? stageTagKeys : [])
|
|
24
|
+
.map((value) => String(value || '').trim().toLowerCase())
|
|
25
|
+
.filter((value) => ['survey', 'ideation', 'experiment', 'publication', 'promotion'].includes(value))
|
|
26
|
+
));
|
|
27
|
+
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function applyStageTagsToSession({
|
|
32
|
+
sessionId,
|
|
33
|
+
projectPath = null,
|
|
34
|
+
projectName = null,
|
|
35
|
+
stageTagKeys = [],
|
|
36
|
+
source = 'chat_context',
|
|
37
|
+
linkedBy = null,
|
|
38
|
+
}) {
|
|
39
|
+
if (!sessionId) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const normalizedStageTagKeys = normalizeStageTagKeys(stageTagKeys);
|
|
44
|
+
if (normalizedStageTagKeys.length === 0) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const resolvedProjectName = projectName || (projectPath ? encodeProjectPath(projectPath) : null);
|
|
49
|
+
if (!resolvedProjectName) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
tagDb.ensureDefaultStageTags(resolvedProjectName);
|
|
54
|
+
return tagDb.appendSessionTagsByKeys(sessionId, resolvedProjectName, 'stage', normalizedStageTagKeys, {
|
|
55
|
+
source,
|
|
56
|
+
linkedBy,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function recordIndexedSession({
|
|
61
|
+
sessionId,
|
|
62
|
+
provider,
|
|
63
|
+
projectPath,
|
|
64
|
+
sessionMode = 'research',
|
|
65
|
+
displayName = null,
|
|
66
|
+
lastActivity = null,
|
|
67
|
+
stageTagKeys = [],
|
|
68
|
+
tagSource = 'chat_context',
|
|
69
|
+
linkedBy = null,
|
|
70
|
+
}) {
|
|
71
|
+
if (!sessionId || !provider || !projectPath) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const projectName = encodeProjectPath(projectPath);
|
|
76
|
+
sessionDb.upsertSessionPlaceholder(
|
|
77
|
+
sessionId,
|
|
78
|
+
projectName,
|
|
79
|
+
provider,
|
|
80
|
+
displayName || defaultSessionName(provider),
|
|
81
|
+
lastActivity || new Date().toISOString(),
|
|
82
|
+
{
|
|
83
|
+
sessionMode,
|
|
84
|
+
projectPath,
|
|
85
|
+
indexState: 'placeholder',
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Dual-path tag application: tags are also applied at spawn start (in the CLI modules)
|
|
90
|
+
// for immediate tagging of existing sessions. This second call ensures tags are applied
|
|
91
|
+
// for newly created sessions. INSERT OR IGNORE in appendSessionTagsByKeys prevents duplicates.
|
|
92
|
+
applyStageTagsToSession({
|
|
93
|
+
sessionId,
|
|
94
|
+
projectPath,
|
|
95
|
+
projectName,
|
|
96
|
+
stageTagKeys,
|
|
97
|
+
source: tagSource,
|
|
98
|
+
linkedBy,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const EXPLICIT_SESSION_MODES = new Set(['research', 'workspace_qa']);
|
|
2
|
+
|
|
3
|
+
function normalizeSessionModeValue(value) {
|
|
4
|
+
return typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function normalizeSessionMode(value) {
|
|
8
|
+
return normalizeSessionModeValue(value) === 'workspace_qa' ? 'workspace_qa' : 'research';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function readExplicitSessionModeValue(value) {
|
|
12
|
+
const normalized = normalizeSessionModeValue(value);
|
|
13
|
+
return EXPLICIT_SESSION_MODES.has(normalized) ? normalized : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function extractSessionModeFromMetadata(metadata) {
|
|
17
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
18
|
+
return 'research';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return readExplicitSessionModeFromMetadata(metadata) || 'research';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function readExplicitSessionModeFromMetadata(metadata) {
|
|
25
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return readExplicitSessionModeValue(metadata.sessionMode)
|
|
30
|
+
|| readExplicitSessionModeValue(metadata.mode);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function extractSessionModeFromText(value) {
|
|
34
|
+
const text = String(value || '');
|
|
35
|
+
if (/\[Context:\s*session-mode=workspace_qa\]/i.test(text)) {
|
|
36
|
+
return 'workspace_qa';
|
|
37
|
+
}
|
|
38
|
+
if (/\[Context:\s*session-mode=research\]/i.test(text)) {
|
|
39
|
+
return 'research';
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function inferSessionModeFromUserMessage(value) {
|
|
45
|
+
const text = String(value || '').trim();
|
|
46
|
+
if (!text) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const strongWorkspacePatterns = [
|
|
51
|
+
/\bpull request\b/i,
|
|
52
|
+
/\bgit\b/i,
|
|
53
|
+
/\bcommit\b/i,
|
|
54
|
+
/\bpush\b/i,
|
|
55
|
+
/\brepo(?:sitory)?\b/i,
|
|
56
|
+
/提交/,
|
|
57
|
+
/推送/,
|
|
58
|
+
/仓库/,
|
|
59
|
+
];
|
|
60
|
+
if (strongWorkspacePatterns.some((pattern) => pattern.test(text))) {
|
|
61
|
+
return 'workspace_qa';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const gitBranchPatterns = [
|
|
65
|
+
/\bgit\s+branch(?:es)?\b/i,
|
|
66
|
+
/git.*分支/i,
|
|
67
|
+
/分支.*git/i,
|
|
68
|
+
];
|
|
69
|
+
if (gitBranchPatterns.some((pattern) => pattern.test(text))) {
|
|
70
|
+
return 'workspace_qa';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const researchPatterns = [
|
|
74
|
+
/\bresearch\b/i,
|
|
75
|
+
/\bpaper(?:s)?\b/i,
|
|
76
|
+
/\bdataset(?:s)?\b/i,
|
|
77
|
+
/\bexperiment(?:s|al)?\b/i,
|
|
78
|
+
/\bhypothesis\b/i,
|
|
79
|
+
/\bsurvey\b/i,
|
|
80
|
+
/\bbenchmark(?:s)?\b/i,
|
|
81
|
+
/\bliterature\b/i,
|
|
82
|
+
/研究/,
|
|
83
|
+
/论文/,
|
|
84
|
+
/实验/,
|
|
85
|
+
/数据集/,
|
|
86
|
+
/调研/,
|
|
87
|
+
/综述/,
|
|
88
|
+
/假设/,
|
|
89
|
+
];
|
|
90
|
+
if (researchPatterns.some((pattern) => pattern.test(text))) {
|
|
91
|
+
return 'research';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
function getFiniteNumber(value) {
|
|
2
|
+
return Number.isFinite(value) ? value : null;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function findNestedTokenTotal(source, paths) {
|
|
6
|
+
for (const path of paths) {
|
|
7
|
+
let cursor = source;
|
|
8
|
+
let missing = false;
|
|
9
|
+
for (const key of path) {
|
|
10
|
+
if (!cursor || typeof cursor !== 'object' || !(key in cursor)) {
|
|
11
|
+
missing = true;
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
cursor = cursor[key];
|
|
15
|
+
}
|
|
16
|
+
if (!missing) {
|
|
17
|
+
const candidate = getFiniteNumber(cursor);
|
|
18
|
+
if (candidate != null) return candidate;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildCodexUnsupportedContextTokenUsage({
|
|
25
|
+
total = 200000,
|
|
26
|
+
lifetimeTokens = null,
|
|
27
|
+
} = {}) {
|
|
28
|
+
return {
|
|
29
|
+
used: null,
|
|
30
|
+
total,
|
|
31
|
+
unsupportedContext: true,
|
|
32
|
+
message: 'Current context usage is unavailable for Codex sessions.',
|
|
33
|
+
...(lifetimeTokens != null ? { lifetimeTokens } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildCodexTokenUsageFromJsonl(fileContent) {
|
|
38
|
+
const lines = String(fileContent || '').trim().split('\n').filter(Boolean);
|
|
39
|
+
|
|
40
|
+
let contextWindow = 200000;
|
|
41
|
+
let lifetimeTokens = null;
|
|
42
|
+
|
|
43
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
44
|
+
try {
|
|
45
|
+
const entry = JSON.parse(lines[i]);
|
|
46
|
+
if (entry.type !== 'event_msg' || entry.payload?.type !== 'token_count' || !entry.payload?.info) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const tokenInfo = entry.payload.info;
|
|
51
|
+
const contextFromPayload = findNestedTokenTotal(tokenInfo, [
|
|
52
|
+
['current_context_usage', 'total_tokens'],
|
|
53
|
+
['current_context_token_usage', 'total_tokens'],
|
|
54
|
+
['context_usage', 'total_tokens'],
|
|
55
|
+
['active_context_usage', 'total_tokens'],
|
|
56
|
+
['context_window_usage', 'total_tokens'],
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
const totalFromPayload = getFiniteNumber(tokenInfo.model_context_window);
|
|
60
|
+
if (totalFromPayload != null) {
|
|
61
|
+
contextWindow = totalFromPayload;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
lifetimeTokens = findNestedTokenTotal(tokenInfo, [
|
|
65
|
+
['total_token_usage', 'total_tokens'],
|
|
66
|
+
['lifetime_token_usage', 'total_tokens'],
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
if (contextFromPayload != null) {
|
|
70
|
+
return {
|
|
71
|
+
used: contextFromPayload,
|
|
72
|
+
total: contextWindow,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
break;
|
|
77
|
+
} catch {
|
|
78
|
+
// Ignore malformed lines and keep scanning backwards.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return buildCodexUnsupportedContextTokenUsage({
|
|
83
|
+
total: contextWindow,
|
|
84
|
+
lifetimeTokens,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function buildCodexRealtimeTokenBudget(eventUsage, contextWindow = 200000) {
|
|
89
|
+
const contextTokens = findNestedTokenTotal(eventUsage, [
|
|
90
|
+
['current_context_usage', 'total_tokens'],
|
|
91
|
+
['context_usage', 'total_tokens'],
|
|
92
|
+
['context_window_usage', 'total_tokens'],
|
|
93
|
+
['current_context_tokens'],
|
|
94
|
+
['context_tokens'],
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
if (contextTokens != null) {
|
|
98
|
+
return {
|
|
99
|
+
used: contextTokens,
|
|
100
|
+
total: contextWindow,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return buildCodexUnsupportedContextTokenUsage({ total: contextWindow });
|
|
105
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASKMASTER WEBSOCKET UTILITIES
|
|
3
|
+
* ==============================
|
|
4
|
+
*
|
|
5
|
+
* Utilities for broadcasting TaskMaster state changes via WebSocket.
|
|
6
|
+
* Integrates with the existing WebSocket system to provide real-time updates.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Broadcast TaskMaster project update to all connected clients
|
|
11
|
+
* @param {WebSocket.Server} wss - WebSocket server instance
|
|
12
|
+
* @param {string} projectName - Name of the updated project
|
|
13
|
+
* @param {Object} taskMasterData - Updated TaskMaster data
|
|
14
|
+
*/
|
|
15
|
+
export function broadcastTaskMasterProjectUpdate(wss, projectName, taskMasterData) {
|
|
16
|
+
if (!wss || !projectName) {
|
|
17
|
+
console.warn('TaskMaster WebSocket broadcast: Missing wss or projectName');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const message = {
|
|
22
|
+
type: 'taskmaster-project-updated',
|
|
23
|
+
projectName,
|
|
24
|
+
taskMasterData,
|
|
25
|
+
timestamp: new Date().toISOString()
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
wss.clients.forEach((client) => {
|
|
30
|
+
if (client.readyState === 1) { // WebSocket.OPEN
|
|
31
|
+
try {
|
|
32
|
+
client.send(JSON.stringify(message));
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Error sending TaskMaster project update:', error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Broadcast TaskMaster tasks update for a specific project
|
|
42
|
+
* @param {WebSocket.Server} wss - WebSocket server instance
|
|
43
|
+
* @param {string} projectName - Name of the project with updated tasks
|
|
44
|
+
* @param {Object} tasksData - Updated tasks data
|
|
45
|
+
*/
|
|
46
|
+
export function broadcastTaskMasterTasksUpdate(wss, projectName, tasksData) {
|
|
47
|
+
if (!wss || !projectName) {
|
|
48
|
+
console.warn('TaskMaster WebSocket broadcast: Missing wss or projectName');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const message = {
|
|
53
|
+
type: 'taskmaster-tasks-updated',
|
|
54
|
+
projectName,
|
|
55
|
+
tasksData,
|
|
56
|
+
timestamp: new Date().toISOString()
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
wss.clients.forEach((client) => {
|
|
61
|
+
if (client.readyState === 1) { // WebSocket.OPEN
|
|
62
|
+
try {
|
|
63
|
+
client.send(JSON.stringify(message));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Error sending TaskMaster tasks update:', error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Broadcast MCP server status change
|
|
73
|
+
* @param {WebSocket.Server} wss - WebSocket server instance
|
|
74
|
+
* @param {Object} mcpStatus - Updated MCP server status
|
|
75
|
+
*/
|
|
76
|
+
export function broadcastMCPStatusChange(wss, mcpStatus) {
|
|
77
|
+
if (!wss) {
|
|
78
|
+
console.warn('TaskMaster WebSocket broadcast: Missing wss');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const message = {
|
|
83
|
+
type: 'taskmaster-mcp-status-changed',
|
|
84
|
+
mcpStatus,
|
|
85
|
+
timestamp: new Date().toISOString()
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
wss.clients.forEach((client) => {
|
|
90
|
+
if (client.readyState === 1) { // WebSocket.OPEN
|
|
91
|
+
try {
|
|
92
|
+
client.send(JSON.stringify(message));
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('Error sending TaskMaster MCP status update:', error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Broadcast general TaskMaster update notification
|
|
102
|
+
* @param {WebSocket.Server} wss - WebSocket server instance
|
|
103
|
+
* @param {string} updateType - Type of update (e.g., 'initialization', 'configuration')
|
|
104
|
+
* @param {Object} data - Additional data about the update
|
|
105
|
+
*/
|
|
106
|
+
export function broadcastTaskMasterUpdate(wss, updateType, data = {}) {
|
|
107
|
+
if (!wss || !updateType) {
|
|
108
|
+
console.warn('TaskMaster WebSocket broadcast: Missing wss or updateType');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const message = {
|
|
113
|
+
type: 'taskmaster-update',
|
|
114
|
+
updateType,
|
|
115
|
+
data,
|
|
116
|
+
timestamp: new Date().toISOString()
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
wss.clients.forEach((client) => {
|
|
121
|
+
if (client.readyState === 1) { // WebSocket.OPEN
|
|
122
|
+
try {
|
|
123
|
+
client.send(JSON.stringify(message));
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('Error sending TaskMaster update:', error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|